Update section 1-16. Some bugs are fixed.

This commit is contained in:
Toshio Sekiya 2022-12-21 22:31:33 +09:00
parent afc7a8517d
commit fe3fba623c
87 changed files with 6244 additions and 6750 deletions

View file

@ -13,40 +13,43 @@ The table of contents is at the end of this abstract.
- Section 26 to 29 describes the list model and the list view (GtkListView, GtkGridView and GtkColumnView).
It also describes GtkExpression.
The latest version of the tutorial is located at [Gtk4-tutorial GitHub repository](https://github.com/ToshioCP/Gtk4-tutorial).
You can read it from there directly without having to download anything.
The latest version of the tutorial is located at [GTK4-tutorial GitHub repository](https://github.com/ToshioCP/GTK4-tutorial).
You can read it directly without download.
There's a GitHub Page of this tutorial at [`https://toshiocp.github.io/GTK4-tutorial/`](https://toshiocp.github.io/GTK4-tutorial/).
It is easier to read than the repository.
#### GTK 4 Documentation
Please refer to [GTK API Reference](https://docs.gtk.org/gtk4/index.html)
Please refer to [GTK 4 API Reference](https://docs.gtk.org/gtk4/index.html)
and [GNOME Developer Documentation Website](https://developer.gnome.org/) for further information.
These websites are newly opened lately (Aug/2021).
The old documentation is located at [GTK Reference Manual](https://developer-old.gnome.org/gtk4/stable/) and [GNOME Developer Center](https://developer-old.gnome.org/).
The new website is in progress at present, so you might need to refer to the old version.
These websites were opened in August of 2021.
The old documents are located at [GTK Reference Manual](https://developer-old.gnome.org/gtk4/stable/) and [GNOME Developer Center](https://developer-old.gnome.org/).
If you want to know about GObject and the type system, please refer to [GObject tutorial](https://github.com/ToshioCP/Gobject-tutorial).
The GObject details are easy to understand and also necessary to know when writing GTK 4 programs.
GObject is the base of GTK 4, so it is important for developers to understand GObject for writing GTK 4 programs.
#### Contribution
This tutorial is under development and unstable.
Even though the codes of the examples have been tested on GTK 4 (version 4.0), bugs may still exist.
This tutorial is still under development and unstable.
Even though the codes of the examples have been tested on GTK 4 (version 4.8.1), bugs may still exist.
If you find any bugs, errors or mistakes in the tutorial and C examples, please let me know.
You can post it to [GitHub issues](https://github.com/ToshioCP/Gtk4-tutorial/issues).
You can also post corrected files as a commit to [pull request](https://github.com/ToshioCP/Gtk4-tutorial/pulls).
When you make corrections, correct the source files, which are under the 'src' directory,
then run `rake` to create to create the output file. The GFM files under the 'gfm' directory are automatically updated.
You can post it to [GitHub issues](https://github.com/ToshioCP/GTK4-tutorial/issues).
You can also post updated files to [pull request](https://github.com/ToshioCP/GTK4-tutorial/pulls).
One thing you need to be careful is to correct the source files, which are under the 'src' directory.
Don't modify the files under `gfm` or `html` directories.
After modifying some source files (under `src` directory), run `rake` to create GFM (GitHub Flavoured Markdown) files or run `rake html` to create HTML files.
If you have a question, feel free to post it as an issue.
If you have a question, feel free to post it to `issue`.
All questions are helpful and will make this tutorial get better.
#### How to get a HTML or PDF version
#### How to get Gtk 4 tutorial with HTML or PDF format
If you want to get a HTML or PDF version, you can make them with `rake`, which is a ruby version of make.
If you want to get HTML or PDF format tutorial, make them with `rake` command, which is a ruby version of make.
Type `rake html` for HTML.
Type `rake pdf` for PDF.
There is a documentation \("[How to build GTK 4 Tutorial](gfm/Readme_for_developers.md)"\) that describes how to make them.
There is a document \("[How to build GTK 4 Tutorial](gfm/Readme_for_developers.md)"\) for further information.
## Table of contents
@ -55,17 +58,17 @@ There is a documentation \("[How to build GTK 4 Tutorial](gfm/Readme_for_develop
1. [GtkApplication and GtkApplicationWindow](gfm/sec3.md)
1. [Widgets (1)](gfm/sec4.md)
1. [Widgets (2)](gfm/sec5.md)
1. [String and memory management](gfm/sec6.md)
1. [Strings and memory management](gfm/sec6.md)
1. [Widgets (3)](gfm/sec7.md)
1. [Defining a child object](gfm/sec8.md)
1. [The User Interface (UI) file and GtkBuilder](gfm/sec9.md)
1. [Defining a final class](gfm/sec8.md)
1. [GtkBuilder and UI file](gfm/sec9.md)
1. [Build system](gfm/sec10.md)
1. [Initialization and destruction of instances](gfm/sec11.md)
1. [Instance Initialization and destruction](gfm/sec11.md)
1. [Signals](gfm/sec12.md)
1. [Functions in TfeTextView](gfm/sec13.md)
1. [Functions in GtkNotebook](gfm/sec14.md)
1. [tfeapplication.c](gfm/sec15.md)
1. [tfe5 source files](gfm/sec16.md)
1. [How to build tfe (text file editor)](gfm/sec16.md)
1. [Menu and action](gfm/sec17.md)
1. [Stateful action](gfm/sec18.md)
1. [Ui file for menu and action entries](gfm/sec19.md)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -116,44 +116,46 @@ editor <code>tfe</code> (Text File Editor).</li>
GtkExpression.</li>
</ul>
<p>The latest version of the tutorial is located at <a
href="https://github.com/ToshioCP/Gtk4-tutorial">Gtk4-tutorial GitHub
repository</a>. You can read it from there directly without having to
download anything.</p>
href="https://github.com/ToshioCP/GTK4-tutorial">GTK4-tutorial GitHub
repository</a>. You can read it directly without download.</p>
<p>Theres a GitHub Page of this tutorial at <a
href="https://toshiocp.github.io/GTK4-tutorial/"><code>https://toshiocp.github.io/GTK4-tutorial/</code></a>.
It is easier to read than the repository.</p>
<h4 id="gtk-4-documentation">GTK 4 Documentation</h4>
<p>Please refer to <a href="https://docs.gtk.org/gtk4/index.html">GTK
<p>Please refer to <a href="https://docs.gtk.org/gtk4/index.html">GTK 4
API Reference</a> and <a href="https://developer.gnome.org/">GNOME
Developer Documentation Website</a> for further information.</p>
<p>These websites are newly opened lately (Aug/2021). The old
documentation is located at <a
href="https://developer-old.gnome.org/gtk4/stable/">GTK Reference
Manual</a> and <a href="https://developer-old.gnome.org/">GNOME
Developer Center</a>. The new website is in progress at present, so you
might need to refer to the old version.</p>
<p>These websites were opened in August of 2021. The old documents are
located at <a href="https://developer-old.gnome.org/gtk4/stable/">GTK
Reference Manual</a> and <a
href="https://developer-old.gnome.org/">GNOME Developer Center</a>.</p>
<p>If you want to know about GObject and the type system, please refer
to <a href="https://github.com/ToshioCP/Gobject-tutorial">GObject
tutorial</a>. The GObject details are easy to understand and also
necessary to know when writing GTK 4 programs.</p>
tutorial</a>. GObject is the base of GTK 4, so it is important for
developers to understand GObject for writing GTK 4 programs.</p>
<h4 id="contribution">Contribution</h4>
<p>This tutorial is under development and unstable. Even though the
codes of the examples have been tested on GTK 4 (version 4.0), bugs may
still exist. If you find any bugs, errors or mistakes in the tutorial
and C examples, please let me know. You can post it to <a
href="https://github.com/ToshioCP/Gtk4-tutorial/issues">GitHub
issues</a>. You can also post corrected files as a commit to <a
href="https://github.com/ToshioCP/Gtk4-tutorial/pulls">pull request</a>.
When you make corrections, correct the source files, which are under the
src directory, then run <code>rake</code> to create to create the
output file. The GFM files under the gfm directory are automatically
updated.</p>
<p>If you have a question, feel free to post it as an issue. All
questions are helpful and will make this tutorial get better.</p>
<h4 id="how-to-get-a-html-or-pdf-version">How to get a HTML or PDF
version</h4>
<p>If you want to get a HTML or PDF version, you can make them with
<code>rake</code>, which is a ruby version of make. Type
<p>This tutorial is still under development and unstable. Even though
the codes of the examples have been tested on GTK 4 (version 4.8.1),
bugs may still exist. If you find any bugs, errors or mistakes in the
tutorial and C examples, please let me know. You can post it to <a
href="https://github.com/ToshioCP/GTK4-tutorial/issues">GitHub
issues</a>. You can also post updated files to <a
href="https://github.com/ToshioCP/GTK4-tutorial/pulls">pull request</a>.
One thing you need to be careful is to correct the source files, which
are under the src directory. Dont modify the files under
<code>gfm</code> or <code>html</code> directories. After modifying some
source files (under <code>src</code> directory), run <code>rake</code>
to create GFM (GitHub Flavoured Markdown) files or run
<code>rake html</code> to create HTML files.</p>
<p>If you have a question, feel free to post it to <code>issue</code>.
All questions are helpful and will make this tutorial get better.</p>
<h4 id="how-to-get-gtk-4-tutorial-with-html-or-pdf-format">How to get
Gtk 4 tutorial with HTML or PDF format</h4>
<p>If you want to get HTML or PDF format tutorial, make them with
<code>rake</code> command, which is a ruby version of make. Type
<code>rake html</code> for HTML. Type <code>rake pdf</code> for PDF.
There is a documentation (“<a href="Readme_for_developers.html">How to
build GTK 4 Tutorial</a>”) that describes how to make them.</p>
There is a document (“<a href="Readme_for_developers.html">How to build
GTK 4 Tutorial</a>”) for further information.</p>
<h2 id="table-of-contents">Table of contents</h2>
<ol type="1">
<li><a href="sec1.html">Prerequisite and License</a></li>
@ -162,19 +164,18 @@ distributions</a></li>
<li><a href="sec3.html">GtkApplication and GtkApplicationWindow</a></li>
<li><a href="sec4.html">Widgets (1)</a></li>
<li><a href="sec5.html">Widgets (2)</a></li>
<li><a href="sec6.html">String and memory management</a></li>
<li><a href="sec6.html">Strings and memory management</a></li>
<li><a href="sec7.html">Widgets (3)</a></li>
<li><a href="sec8.html">Defining a child object</a></li>
<li><a href="sec9.html">The User Interface (UI) file and
GtkBuilder</a></li>
<li><a href="sec8.html">Defining a final class</a></li>
<li><a href="sec9.html">GtkBuilder and UI file</a></li>
<li><a href="sec10.html">Build system</a></li>
<li><a href="sec11.html">Initialization and destruction of
instances</a></li>
<li><a href="sec11.html">Instance Initialization and
destruction</a></li>
<li><a href="sec12.html">Signals</a></li>
<li><a href="sec13.html">Functions in TfeTextView</a></li>
<li><a href="sec14.html">Functions in GtkNotebook</a></li>
<li><a href="sec15.html">tfeapplication.c</a></li>
<li><a href="sec16.html">tfe5 source files</a></li>
<li><a href="sec16.html">How to build tfe (text file editor)</a></li>
<li><a href="sec17.html">Menu and action</a></li>
<li><a href="sec18.html">Stateful action</a></li>
<li><a href="sec19.html">Ui file for menu and action entries</a></li>

View file

@ -117,36 +117,37 @@ MacOS, with Vala, Python and so on. However, this tutorial describes
only <em>C programs on Linux</em>.</p>
<p>If you want to try the examples in the tutorial, you need:</p>
<ul>
<li>PC with Linux distribution like Ubuntu, Debian and so on.</li>
<li>Gcc.</li>
<li>GTK 4. The stable version of Gtk on Linux distributions is version
three at present. You need to install GTK 4 to your computer. See <a
href="sec3.html">Section 3</a> for the installation of GTK 4.</li>
<li>PC with Linux distribution like Ubuntu or Debian.</li>
<li>GCC.</li>
<li>GTK 4. The stable version of GTK is 4.8.2 at present (13/Dec/2022),
but older version (4.0 or higher) may work. See <a
href="sec3.html">Section 3</a> for the installation for GTK 4.</li>
</ul>
<h3 id="ruby-and-rake-for-making-the-document">Ruby and rake for making
the document</h3>
<p>This repository includes Ruby programs. They are used to make
Markdown files, HTML files, Latex files and a PDF file.</p>
<p>This repository includes Ruby programs. They are used to make GFM
(GitHub Flavoured Markdown) files, HTML files, Latex files and a PDF
file.</p>
<p>You need:</p>
<ul>
<li>Linux distribution like Ubuntu.</li>
<li>Linux.</li>
<li>Ruby programming language. There are two ways to install it. One is
installing the distributions package. The other is using rbenv and
ruby-build. If you want to use the latest version of ruby, use
rbenv.</li>
<li>Rake. It is a gem, which is a library written in Ruby. You can
install it as a package of your distribution or use gem command.</li>
<li>Rake. You dont need to install it separately because it is a
standard library of Ruby.</li>
</ul>
<h2 id="license">License</h2>
<p>Copyright (C) 2020 ToshioCP (Toshio Sekiya)</p>
<p>Gtk4-tutorial repository contains the tutorial document and software
<p>Copyright (C) 2020-2022 ToshioCP (Toshio Sekiya)</p>
<p>GTK4-tutorial repository contains tutorial documents and programs
such as converters, generators and controllers. All of them make up the
Gtk4-tutorial package. This package is simply called Gtk4-tutorial
in the following description. Gtk4-tutorial is free; you can
GTK4-tutorial package. This package is simply called GTK4-tutorial
in the following description. GTK4-tutorial is free; you can
redistribute it and/or modify it under the terms of the GNU General
Public License as published by the Free Software Foundation; either
version 3 of the License or, at your option, any later version.</p>
<p>Gtk4-tutorial is distributed in the hope that it will be useful,
<p>GTK4-tutorial is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the <a
href="https://www.gnu.org/licenses/gpl-3.0.html">GNU General Public

View file

@ -5,7 +5,7 @@
<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>Gtk4 tutorial</title>
<title>GTK 4 tutorial</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
@ -112,275 +112,373 @@
</div>
</nav>
<h1 id="build-system">Build system</h1>
<h2 id="what-do-we-need-to-think-about-to-manage-big-source-files">What do we need to think about to manage big source files?</h2>
<p>Weve compiled a small editor so far. But Some bad signs are beginning to appear.</p>
<h2 id="managing-big-source-files">Managing big source files</h2>
<p>Weve compiled a small editor so far. But Some bad signs are
beginning to appear.</p>
<ul>
<li>Weve had only one C source file and put everything into it. We need to sort it out.</li>
<li>There are two compilers, <code>gcc</code> and <code>glib-compile-resources</code>. We should control them by one building tool.</li>
<li>Weve had only one C source file and put everything in it. We need
to sort it out.</li>
<li>There are two compilers, <code>gcc</code> and
<code>glib-compile-resources</code>. We should control them by one
building tool.</li>
</ul>
<p>These ideas are useful to manage big source files.</p>
<h2 id="divide-a-c-source-file-into-two-parts.">Divide a C source file into two parts.</h2>
<p>When you divide C source file into several parts, each file should contain only one thing. For example, our source has two things, the definition of TfeTextView and functions related to GtkApplication and GtkApplicationWindow. It is a good idea to separate them into two files, <code>tfetextview.c</code> and <code>tfe.c</code>.</p>
<h2 id="divide-a-c-source-file-into-two-parts.">Divide a C source file
into two parts.</h2>
<p>When you divide C source file into several parts, each file should
contain one thing. For example, our source has two things, the
definition of TfeTextView and functions related to GtkApplication and
GtkApplicationWindow. It is a good idea to separate them into two files,
<code>tfetextview.c</code> and <code>tfe.c</code>.</p>
<ul>
<li><code>tfetextview.c</code> includes the definition and functions of TfeTextView.</li>
<li><code>tfe.c</code> includes functions like <code>main</code>, <code>app_activate</code>, <code>app_open</code> and so on, which relate to GtkApplication and GtkApplicationWindow</li>
<li><code>tfetextview.c</code> includes the definition and functions of
TfeTextView.</li>
<li><code>tfe.c</code> includes functions like <code>main</code>,
<code>app_activate</code>, <code>app_open</code> and so on, which relate
to GtkApplication and GtkApplicationWindow</li>
</ul>
<p>Now we have three source files, <code>tfetextview.c</code>, <code>tfe.c</code> and <code>tfe3.ui</code>. The <code>3</code> of <code>tfe3.ui</code> is like a version number. Managing version with filenames is one possible idea but it may make bothersome problem. You need to rewrite filename in each version and it affects to contents of source files that refer to filenames. So, we should take <code>3</code> away from the filename.</p>
<p>In <code>tfe.c</code> the function <code>tfe_text_view_new</code> is invoked to create a TfeTextView instance. But it is defined in <code>tfetextview.c</code>, not <code>tfe.c</code>. The lack of the declaration (not definition) of <code>tfe_text_view_new</code> makes error when <code>tfe.c</code> is compiled. The declaration is necessary in <code>tfe.c</code>. Those public information is usually written in header files. It has <code>.h</code> suffix like <code>tfetextview.h</code> And header files are included by C source files. For example, <code>tfetextview.h</code> is included by <code>tfe.c</code>.</p>
<p>Now we have three source files, <code>tfetextview.c</code>,
<code>tfe.c</code> and <code>tfe3.ui</code>. The <code>3</code> of
<code>tfe3.ui</code> is like a version number. Managing version with
filenames is one possible idea but it may make bothersome problem. You
need to rewrite filename in each version and it affects to contents of
source files that refer to filenames. So, we should take <code>3</code>
away from the filename.</p>
<p>In <code>tfe.c</code> the function <code>tfe_text_view_new</code> is
invoked to create a TfeTextView instance. But it is defined in
<code>tfetextview.c</code>, not <code>tfe.c</code>. The lack of the
declaration (not definition) of <code>tfe_text_view_new</code> makes
error when <code>tfe.c</code> is compiled. The declaration is necessary
in <code>tfe.c</code>. Those public information is usually written in
header files. It has <code>.h</code> suffix like
<code>tfetextview.h</code> And header files are included by C source
files. For example, <code>tfetextview.h</code> is included by
<code>tfe.c</code>.</p>
<p>All the source files are listed below.</p>
<p><code>tfetextview.h</code></p>
<div class="sourceCode" id="cb1"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb1-1"><a href="#cb1-1"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<div class="sourceCode" id="cb1"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb1-1"><a href="#cb1-1"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<span id="cb1-2"><a href="#cb1-2"></a></span>
<span id="cb1-3"><a href="#cb1-3"></a><span class="pp">#define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()</span></span>
<span id="cb1-4"><a href="#cb1-4"></a>G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)</span>
<span id="cb1-4"><a href="#cb1-4"></a>G_DECLARE_FINAL_TYPE <span class="op">(</span>TfeTextView<span class="op">,</span> tfe_text_view<span class="op">,</span> TFE<span class="op">,</span> TEXT_VIEW<span class="op">,</span> GtkTextView<span class="op">)</span></span>
<span id="cb1-5"><a href="#cb1-5"></a></span>
<span id="cb1-6"><a href="#cb1-6"></a><span class="dt">void</span></span>
<span id="cb1-7"><a href="#cb1-7"></a>tfe_text_view_set_file (TfeTextView *tv, GFile *f);</span>
<span id="cb1-7"><a href="#cb1-7"></a>tfe_text_view_set_file <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">,</span> GFile <span class="op">*</span>f<span class="op">);</span></span>
<span id="cb1-8"><a href="#cb1-8"></a></span>
<span id="cb1-9"><a href="#cb1-9"></a>GFile *</span>
<span id="cb1-10"><a href="#cb1-10"></a>tfe_text_view_get_file (TfeTextView *tv);</span>
<span id="cb1-9"><a href="#cb1-9"></a>GFile <span class="op">*</span></span>
<span id="cb1-10"><a href="#cb1-10"></a>tfe_text_view_get_file <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">);</span></span>
<span id="cb1-11"><a href="#cb1-11"></a></span>
<span id="cb1-12"><a href="#cb1-12"></a>GtkWidget *</span>
<span id="cb1-13"><a href="#cb1-13"></a>tfe_text_view_new (<span class="dt">void</span>);</span></code></pre></div>
<span id="cb1-12"><a href="#cb1-12"></a>GtkWidget <span class="op">*</span></span>
<span id="cb1-13"><a href="#cb1-13"></a>tfe_text_view_new <span class="op">(</span><span class="dt">void</span><span class="op">);</span></span></code></pre></div>
<p><code>tfetextview.c</code></p>
<div class="sourceCode" id="cb2"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb2-1"><a href="#cb2-1"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<div class="sourceCode" id="cb2"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb2-1"><a href="#cb2-1"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<span id="cb2-2"><a href="#cb2-2"></a><span class="pp">#include </span><span class="im">&quot;tfetextview.h&quot;</span></span>
<span id="cb2-3"><a href="#cb2-3"></a></span>
<span id="cb2-4"><a href="#cb2-4"></a><span class="kw">struct</span> _TfeTextView</span>
<span id="cb2-5"><a href="#cb2-5"></a>{</span>
<span id="cb2-6"><a href="#cb2-6"></a> GtkTextView parent;</span>
<span id="cb2-7"><a href="#cb2-7"></a> GFile *file;</span>
<span id="cb2-8"><a href="#cb2-8"></a>};</span>
<span id="cb2-5"><a href="#cb2-5"></a><span class="op">{</span></span>
<span id="cb2-6"><a href="#cb2-6"></a> GtkTextView parent<span class="op">;</span></span>
<span id="cb2-7"><a href="#cb2-7"></a> GFile <span class="op">*</span>file<span class="op">;</span></span>
<span id="cb2-8"><a href="#cb2-8"></a><span class="op">};</span></span>
<span id="cb2-9"><a href="#cb2-9"></a></span>
<span id="cb2-10"><a href="#cb2-10"></a>G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);</span>
<span id="cb2-10"><a href="#cb2-10"></a>G_DEFINE_TYPE <span class="op">(</span>TfeTextView<span class="op">,</span> tfe_text_view<span class="op">,</span> GTK_TYPE_TEXT_VIEW<span class="op">);</span></span>
<span id="cb2-11"><a href="#cb2-11"></a></span>
<span id="cb2-12"><a href="#cb2-12"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb2-13"><a href="#cb2-13"></a>tfe_text_view_init (TfeTextView *tv) {</span>
<span id="cb2-14"><a href="#cb2-14"></a>}</span>
<span id="cb2-13"><a href="#cb2-13"></a>tfe_text_view_init <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb2-14"><a href="#cb2-14"></a><span class="op">}</span></span>
<span id="cb2-15"><a href="#cb2-15"></a></span>
<span id="cb2-16"><a href="#cb2-16"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb2-17"><a href="#cb2-17"></a>tfe_text_view_class_init (TfeTextViewClass *class) {</span>
<span id="cb2-18"><a href="#cb2-18"></a>}</span>
<span id="cb2-17"><a href="#cb2-17"></a>tfe_text_view_class_init <span class="op">(</span>TfeTextViewClass <span class="op">*</span>class<span class="op">)</span> <span class="op">{</span></span>
<span id="cb2-18"><a href="#cb2-18"></a><span class="op">}</span></span>
<span id="cb2-19"><a href="#cb2-19"></a></span>
<span id="cb2-20"><a href="#cb2-20"></a><span class="dt">void</span></span>
<span id="cb2-21"><a href="#cb2-21"></a>tfe_text_view_set_file (TfeTextView *tv, GFile *f) {</span>
<span id="cb2-22"><a href="#cb2-22"></a> tv -&gt; file = f;</span>
<span id="cb2-23"><a href="#cb2-23"></a>}</span>
<span id="cb2-21"><a href="#cb2-21"></a>tfe_text_view_set_file <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">,</span> GFile <span class="op">*</span>f<span class="op">)</span> <span class="op">{</span></span>
<span id="cb2-22"><a href="#cb2-22"></a> tv <span class="op">-&gt;</span> file <span class="op">=</span> f<span class="op">;</span></span>
<span id="cb2-23"><a href="#cb2-23"></a><span class="op">}</span></span>
<span id="cb2-24"><a href="#cb2-24"></a></span>
<span id="cb2-25"><a href="#cb2-25"></a>GFile *</span>
<span id="cb2-26"><a href="#cb2-26"></a>tfe_text_view_get_file (TfeTextView *tv) {</span>
<span id="cb2-27"><a href="#cb2-27"></a> <span class="cf">return</span> tv -&gt; file;</span>
<span id="cb2-28"><a href="#cb2-28"></a>}</span>
<span id="cb2-25"><a href="#cb2-25"></a>GFile <span class="op">*</span></span>
<span id="cb2-26"><a href="#cb2-26"></a>tfe_text_view_get_file <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb2-27"><a href="#cb2-27"></a> <span class="cf">return</span> tv <span class="op">-&gt;</span> file<span class="op">;</span></span>
<span id="cb2-28"><a href="#cb2-28"></a><span class="op">}</span></span>
<span id="cb2-29"><a href="#cb2-29"></a></span>
<span id="cb2-30"><a href="#cb2-30"></a>GtkWidget *</span>
<span id="cb2-31"><a href="#cb2-31"></a>tfe_text_view_new (<span class="dt">void</span>) {</span>
<span id="cb2-32"><a href="#cb2-32"></a> <span class="cf">return</span> GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));</span>
<span id="cb2-33"><a href="#cb2-33"></a>}</span></code></pre></div>
<span id="cb2-30"><a href="#cb2-30"></a>GtkWidget <span class="op">*</span></span>
<span id="cb2-31"><a href="#cb2-31"></a>tfe_text_view_new <span class="op">(</span><span class="dt">void</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb2-32"><a href="#cb2-32"></a> <span class="cf">return</span> GTK_WIDGET <span class="op">(</span>g_object_new <span class="op">(</span>TFE_TYPE_TEXT_VIEW<span class="op">,</span> NULL<span class="op">));</span></span>
<span id="cb2-33"><a href="#cb2-33"></a><span class="op">}</span></span></code></pre></div>
<p><code>tfe.c</code></p>
<div class="sourceCode" id="cb3"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb3-1"><a href="#cb3-1"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<div class="sourceCode" id="cb3"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb3-1"><a href="#cb3-1"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<span id="cb3-2"><a href="#cb3-2"></a><span class="pp">#include </span><span class="im">&quot;tfetextview.h&quot;</span></span>
<span id="cb3-3"><a href="#cb3-3"></a></span>
<span id="cb3-4"><a href="#cb3-4"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb3-5"><a href="#cb3-5"></a>app_activate (GApplication *app, gpointer user_data) {</span>
<span id="cb3-6"><a href="#cb3-6"></a> g_print (<span class="st">&quot;You need a filename argument.</span><span class="sc">\n</span><span class="st">&quot;</span>);</span>
<span id="cb3-7"><a href="#cb3-7"></a>}</span>
<span id="cb3-5"><a href="#cb3-5"></a>app_activate <span class="op">(</span>GApplication <span class="op">*</span>app<span class="op">)</span> <span class="op">{</span></span>
<span id="cb3-6"><a href="#cb3-6"></a> g_print <span class="op">(</span><span class="st">&quot;You need a filename argument.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb3-7"><a href="#cb3-7"></a><span class="op">}</span></span>
<span id="cb3-8"><a href="#cb3-8"></a></span>
<span id="cb3-9"><a href="#cb3-9"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb3-10"><a href="#cb3-10"></a>app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {</span>
<span id="cb3-11"><a href="#cb3-11"></a> GtkWidget *win;</span>
<span id="cb3-12"><a href="#cb3-12"></a> GtkWidget *nb;</span>
<span id="cb3-13"><a href="#cb3-13"></a> GtkWidget *lab;</span>
<span id="cb3-14"><a href="#cb3-14"></a> GtkNotebookPage *nbp;</span>
<span id="cb3-15"><a href="#cb3-15"></a> GtkWidget *scr;</span>
<span id="cb3-16"><a href="#cb3-16"></a> GtkWidget *tv;</span>
<span id="cb3-17"><a href="#cb3-17"></a> GtkTextBuffer *tb;</span>
<span id="cb3-18"><a href="#cb3-18"></a> <span class="dt">char</span> *contents;</span>
<span id="cb3-19"><a href="#cb3-19"></a> gsize length;</span>
<span id="cb3-20"><a href="#cb3-20"></a> <span class="dt">char</span> *filename;</span>
<span id="cb3-21"><a href="#cb3-21"></a> <span class="dt">int</span> i;</span>
<span id="cb3-22"><a href="#cb3-22"></a> GtkBuilder *build;</span>
<span id="cb3-10"><a href="#cb3-10"></a>app_open <span class="op">(</span>GApplication <span class="op">*</span>app<span class="op">,</span> GFile <span class="op">**</span> files<span class="op">,</span> gint n_files<span class="op">,</span> gchar <span class="op">*</span>hint<span class="op">)</span> <span class="op">{</span></span>
<span id="cb3-11"><a href="#cb3-11"></a> GtkWidget <span class="op">*</span>win<span class="op">;</span></span>
<span id="cb3-12"><a href="#cb3-12"></a> GtkWidget <span class="op">*</span>nb<span class="op">;</span></span>
<span id="cb3-13"><a href="#cb3-13"></a> GtkWidget <span class="op">*</span>lab<span class="op">;</span></span>
<span id="cb3-14"><a href="#cb3-14"></a> GtkNotebookPage <span class="op">*</span>nbp<span class="op">;</span></span>
<span id="cb3-15"><a href="#cb3-15"></a> GtkWidget <span class="op">*</span>scr<span class="op">;</span></span>
<span id="cb3-16"><a href="#cb3-16"></a> GtkWidget <span class="op">*</span>tv<span class="op">;</span></span>
<span id="cb3-17"><a href="#cb3-17"></a> GtkTextBuffer <span class="op">*</span>tb<span class="op">;</span></span>
<span id="cb3-18"><a href="#cb3-18"></a> <span class="dt">char</span> <span class="op">*</span>contents<span class="op">;</span></span>
<span id="cb3-19"><a href="#cb3-19"></a> gsize length<span class="op">;</span></span>
<span id="cb3-20"><a href="#cb3-20"></a> <span class="dt">char</span> <span class="op">*</span>filename<span class="op">;</span></span>
<span id="cb3-21"><a href="#cb3-21"></a> <span class="dt">int</span> i<span class="op">;</span></span>
<span id="cb3-22"><a href="#cb3-22"></a> GtkBuilder <span class="op">*</span>build<span class="op">;</span></span>
<span id="cb3-23"><a href="#cb3-23"></a></span>
<span id="cb3-24"><a href="#cb3-24"></a> build = gtk_builder_new_from_resource (<span class="st">&quot;/com/github/ToshioCP/tfe3/tfe.ui&quot;</span>);</span>
<span id="cb3-25"><a href="#cb3-25"></a> win = GTK_WIDGET (gtk_builder_get_object (build, <span class="st">&quot;win&quot;</span>));</span>
<span id="cb3-26"><a href="#cb3-26"></a> gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));</span>
<span id="cb3-27"><a href="#cb3-27"></a> nb = GTK_WIDGET (gtk_builder_get_object (build, <span class="st">&quot;nb&quot;</span>));</span>
<span id="cb3-28"><a href="#cb3-28"></a> g_object_unref (build);</span>
<span id="cb3-29"><a href="#cb3-29"></a> <span class="cf">for</span> (i = <span class="dv">0</span>; i &lt; n_files; i++) {</span>
<span id="cb3-30"><a href="#cb3-30"></a> <span class="cf">if</span> (g_file_load_contents (files[i], NULL, &amp;contents, &amp;length, NULL, NULL)) {</span>
<span id="cb3-31"><a href="#cb3-31"></a> scr = gtk_scrolled_window_new ();</span>
<span id="cb3-32"><a href="#cb3-32"></a> tv = tfe_text_view_new ();</span>
<span id="cb3-33"><a href="#cb3-33"></a> tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));</span>
<span id="cb3-34"><a href="#cb3-34"></a> gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);</span>
<span id="cb3-35"><a href="#cb3-35"></a> gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);</span>
<span id="cb3-24"><a href="#cb3-24"></a> build <span class="op">=</span> gtk_builder_new_from_resource <span class="op">(</span><span class="st">&quot;/com/github/ToshioCP/tfe3/tfe.ui&quot;</span><span class="op">);</span></span>
<span id="cb3-25"><a href="#cb3-25"></a> win <span class="op">=</span> GTK_WIDGET <span class="op">(</span>gtk_builder_get_object <span class="op">(</span>build<span class="op">,</span> <span class="st">&quot;win&quot;</span><span class="op">));</span></span>
<span id="cb3-26"><a href="#cb3-26"></a> gtk_window_set_application <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> GTK_APPLICATION <span class="op">(</span>app<span class="op">));</span></span>
<span id="cb3-27"><a href="#cb3-27"></a> nb <span class="op">=</span> GTK_WIDGET <span class="op">(</span>gtk_builder_get_object <span class="op">(</span>build<span class="op">,</span> <span class="st">&quot;nb&quot;</span><span class="op">));</span></span>
<span id="cb3-28"><a href="#cb3-28"></a> g_object_unref <span class="op">(</span>build<span class="op">);</span></span>
<span id="cb3-29"><a href="#cb3-29"></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_files<span class="op">;</span> i<span class="op">++)</span> <span class="op">{</span></span>
<span id="cb3-30"><a href="#cb3-30"></a> <span class="cf">if</span> <span class="op">(</span>g_file_load_contents <span class="op">(</span>files<span class="op">[</span>i<span class="op">],</span> NULL<span class="op">,</span> <span class="op">&amp;</span>contents<span class="op">,</span> <span class="op">&amp;</span>length<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">))</span> <span class="op">{</span></span>
<span id="cb3-31"><a href="#cb3-31"></a> scr <span class="op">=</span> gtk_scrolled_window_new <span class="op">();</span></span>
<span id="cb3-32"><a href="#cb3-32"></a> tv <span class="op">=</span> tfe_text_view_new <span class="op">();</span></span>
<span id="cb3-33"><a href="#cb3-33"></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="cb3-34"><a href="#cb3-34"></a> gtk_text_view_set_wrap_mode <span class="op">(</span>GTK_TEXT_VIEW <span class="op">(</span>tv<span class="op">),</span> GTK_WRAP_WORD_CHAR<span class="op">);</span></span>
<span id="cb3-35"><a href="#cb3-35"></a> gtk_scrolled_window_set_child <span class="op">(</span>GTK_SCROLLED_WINDOW <span class="op">(</span>scr<span class="op">),</span> tv<span class="op">);</span></span>
<span id="cb3-36"><a href="#cb3-36"></a></span>
<span id="cb3-37"><a href="#cb3-37"></a> tfe_text_view_set_file (TFE_TEXT_VIEW (tv), g_file_dup (files[i]));</span>
<span id="cb3-38"><a href="#cb3-38"></a> gtk_text_buffer_set_text (tb, contents, length);</span>
<span id="cb3-39"><a href="#cb3-39"></a> g_free (contents);</span>
<span id="cb3-40"><a href="#cb3-40"></a> filename = g_file_get_basename (files[i]);</span>
<span id="cb3-41"><a href="#cb3-41"></a> lab = gtk_label_new (filename);</span>
<span id="cb3-42"><a href="#cb3-42"></a> gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);</span>
<span id="cb3-43"><a href="#cb3-43"></a> nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);</span>
<span id="cb3-44"><a href="#cb3-44"></a> g_object_set (nbp, <span class="st">&quot;tab-expand&quot;</span>, TRUE, NULL);</span>
<span id="cb3-45"><a href="#cb3-45"></a> g_free (filename);</span>
<span id="cb3-46"><a href="#cb3-46"></a> } <span class="cf">else</span> <span class="cf">if</span> ((filename = g_file_get_path (files[i])) != NULL) {</span>
<span id="cb3-47"><a href="#cb3-47"></a> g_print (<span class="st">&quot;No such file: %s.</span><span class="sc">\n</span><span class="st">&quot;</span>, filename);</span>
<span id="cb3-48"><a href="#cb3-48"></a> g_free (filename);</span>
<span id="cb3-49"><a href="#cb3-49"></a> } <span class="cf">else</span></span>
<span id="cb3-50"><a href="#cb3-50"></a> g_print (<span class="st">&quot;No valid file is given</span><span class="sc">\n</span><span class="st">&quot;</span>);</span>
<span id="cb3-51"><a href="#cb3-51"></a> }</span>
<span id="cb3-52"><a href="#cb3-52"></a> <span class="cf">if</span> (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) &gt; <span class="dv">0</span>) {</span>
<span id="cb3-53"><a href="#cb3-53"></a> gtk_widget_show (win);</span>
<span id="cb3-54"><a href="#cb3-54"></a> } <span class="cf">else</span></span>
<span id="cb3-55"><a href="#cb3-55"></a> gtk_window_destroy (GTK_WINDOW (win));</span>
<span id="cb3-56"><a href="#cb3-56"></a>}</span>
<span id="cb3-37"><a href="#cb3-37"></a> tfe_text_view_set_file <span class="op">(</span>TFE_TEXT_VIEW <span class="op">(</span>tv<span class="op">),</span> g_file_dup <span class="op">(</span>files<span class="op">[</span>i<span class="op">]));</span></span>
<span id="cb3-38"><a href="#cb3-38"></a> gtk_text_buffer_set_text <span class="op">(</span>tb<span class="op">,</span> contents<span class="op">,</span> length<span class="op">);</span></span>
<span id="cb3-39"><a href="#cb3-39"></a> g_free <span class="op">(</span>contents<span class="op">);</span></span>
<span id="cb3-40"><a href="#cb3-40"></a> filename <span class="op">=</span> g_file_get_basename <span class="op">(</span>files<span class="op">[</span>i<span class="op">]);</span></span>
<span id="cb3-41"><a href="#cb3-41"></a> lab <span class="op">=</span> gtk_label_new <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb3-42"><a href="#cb3-42"></a> gtk_notebook_append_page <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">),</span> scr<span class="op">,</span> lab<span class="op">);</span></span>
<span id="cb3-43"><a href="#cb3-43"></a> nbp <span class="op">=</span> gtk_notebook_get_page <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">),</span> scr<span class="op">);</span></span>
<span id="cb3-44"><a href="#cb3-44"></a> g_object_set <span class="op">(</span>nbp<span class="op">,</span> <span class="st">&quot;tab-expand&quot;</span><span class="op">,</span> TRUE<span class="op">,</span> NULL<span class="op">);</span></span>
<span id="cb3-45"><a href="#cb3-45"></a> g_free <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb3-46"><a href="#cb3-46"></a> <span class="op">}</span> <span class="cf">else</span> <span class="cf">if</span> <span class="op">((</span>filename <span class="op">=</span> g_file_get_path <span class="op">(</span>files<span class="op">[</span>i<span class="op">]))</span> <span class="op">!=</span> NULL<span class="op">)</span> <span class="op">{</span></span>
<span id="cb3-47"><a href="#cb3-47"></a> g_print <span class="op">(</span><span class="st">&quot;No such file: %s.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">,</span> filename<span class="op">);</span></span>
<span id="cb3-48"><a href="#cb3-48"></a> g_free <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb3-49"><a href="#cb3-49"></a> <span class="op">}</span> <span class="cf">else</span></span>
<span id="cb3-50"><a href="#cb3-50"></a> g_print <span class="op">(</span><span class="st">&quot;No valid file is given</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb3-51"><a href="#cb3-51"></a> <span class="op">}</span></span>
<span id="cb3-52"><a href="#cb3-52"></a> <span class="cf">if</span> <span class="op">(</span>gtk_notebook_get_n_pages <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">))</span> <span class="op">&gt;</span> <span class="dv">0</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb3-53"><a href="#cb3-53"></a> gtk_widget_show <span class="op">(</span>win<span class="op">);</span></span>
<span id="cb3-54"><a href="#cb3-54"></a> <span class="op">}</span> <span class="cf">else</span></span>
<span id="cb3-55"><a href="#cb3-55"></a> gtk_window_destroy <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">));</span></span>
<span id="cb3-56"><a href="#cb3-56"></a><span class="op">}</span></span>
<span id="cb3-57"><a href="#cb3-57"></a></span>
<span id="cb3-58"><a href="#cb3-58"></a><span class="dt">int</span></span>
<span id="cb3-59"><a href="#cb3-59"></a>main (<span class="dt">int</span> argc, <span class="dt">char</span> **argv) {</span>
<span id="cb3-60"><a href="#cb3-60"></a> GtkApplication *app;</span>
<span id="cb3-61"><a href="#cb3-61"></a> <span class="dt">int</span> stat;</span>
<span id="cb3-59"><a href="#cb3-59"></a>main <span class="op">(</span><span class="dt">int</span> argc<span class="op">,</span> <span class="dt">char</span> <span class="op">**</span>argv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb3-60"><a href="#cb3-60"></a> GtkApplication <span class="op">*</span>app<span class="op">;</span></span>
<span id="cb3-61"><a href="#cb3-61"></a> <span class="dt">int</span> stat<span class="op">;</span></span>
<span id="cb3-62"><a href="#cb3-62"></a></span>
<span id="cb3-63"><a href="#cb3-63"></a> app = gtk_application_new (<span class="st">&quot;com.github.ToshioCP.tfe&quot;</span>, G_APPLICATION_HANDLES_OPEN);</span>
<span id="cb3-64"><a href="#cb3-64"></a> g_signal_connect (app, <span class="st">&quot;activate&quot;</span>, G_CALLBACK (app_activate), NULL);</span>
<span id="cb3-65"><a href="#cb3-65"></a> g_signal_connect (app, <span class="st">&quot;open&quot;</span>, G_CALLBACK (app_open), NULL);</span>
<span id="cb3-66"><a href="#cb3-66"></a> stat =g_application_run (G_APPLICATION (app), argc, argv);</span>
<span id="cb3-67"><a href="#cb3-67"></a> g_object_unref (app);</span>
<span id="cb3-68"><a href="#cb3-68"></a> <span class="cf">return</span> stat;</span>
<span id="cb3-69"><a href="#cb3-69"></a>}</span></code></pre></div>
<p>The ui file <code>tfe.ui</code> is the same as <code>tfe3.ui</code> in the previous section.</p>
<span id="cb3-63"><a href="#cb3-63"></a> app <span class="op">=</span> gtk_application_new <span class="op">(</span><span class="st">&quot;com.github.ToshioCP.tfe&quot;</span><span class="op">,</span> G_APPLICATION_HANDLES_OPEN<span class="op">);</span></span>
<span id="cb3-64"><a href="#cb3-64"></a> g_signal_connect <span class="op">(</span>app<span class="op">,</span> <span class="st">&quot;activate&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>app_activate<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb3-65"><a href="#cb3-65"></a> g_signal_connect <span class="op">(</span>app<span class="op">,</span> <span class="st">&quot;open&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>app_open<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb3-66"><a href="#cb3-66"></a> stat <span class="op">=</span>g_application_run <span class="op">(</span>G_APPLICATION <span class="op">(</span>app<span class="op">),</span> argc<span class="op">,</span> argv<span class="op">);</span></span>
<span id="cb3-67"><a href="#cb3-67"></a> g_object_unref <span class="op">(</span>app<span class="op">);</span></span>
<span id="cb3-68"><a href="#cb3-68"></a> <span class="cf">return</span> stat<span class="op">;</span></span>
<span id="cb3-69"><a href="#cb3-69"></a><span class="op">}</span></span></code></pre></div>
<p>The ui file <code>tfe.ui</code> is the same as <code>tfe3.ui</code>
in the previous section.</p>
<p><code>tfe.gresource.xml</code></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="kw">&lt;?xml</span> version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;<span class="kw">?&gt;</span></span>
<span id="cb4-2"><a href="#cb4-2"></a><span class="kw">&lt;gresources&gt;</span></span>
<span id="cb4-3"><a href="#cb4-3"></a> <span class="kw">&lt;gresource</span><span class="ot"> prefix=</span><span class="st">&quot;/com/github/ToshioCP/tfe3&quot;</span><span class="kw">&gt;</span></span>
<span id="cb4-4"><a href="#cb4-4"></a> <span class="kw">&lt;file&gt;</span>tfe.ui<span class="kw">&lt;/file&gt;</span></span>
<span id="cb4-5"><a href="#cb4-5"></a> <span class="kw">&lt;/gresource&gt;</span></span>
<span id="cb4-6"><a href="#cb4-6"></a><span class="kw">&lt;/gresources&gt;</span></span></code></pre></div>
<h2 id="make">Make</h2>
<p>Dividing a file makes it easy to maintain source files. But now we are faced with a new problem. The building step increases.</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">gresources</span>&gt;</span>
<span id="cb4-3"><a href="#cb4-3"></a> &lt;<span class="kw">gresource</span><span class="ot"> prefix=</span><span class="st">&quot;/com/github/ToshioCP/tfe3&quot;</span>&gt;</span>
<span id="cb4-4"><a href="#cb4-4"></a> &lt;<span class="kw">file</span>&gt;tfe.ui&lt;/<span class="kw">file</span>&gt;</span>
<span id="cb4-5"><a href="#cb4-5"></a> &lt;/<span class="kw">gresource</span>&gt;</span>
<span id="cb4-6"><a href="#cb4-6"></a>&lt;/<span class="kw">gresources</span>&gt;</span></code></pre></div>
<p>Dividing a file makes it easy to maintain source files. But now we
face a new problem. The building step increases.</p>
<ul>
<li>Compiling the ui file <code>tfe.ui</code> into <code>resources.c</code>.</li>
<li>Compiling <code>tfe.c</code> into <code>tfe.o</code> (object file).</li>
<li>Compiling <code>tfetextview.c</code> into <code>tfetextview.o</code>.</li>
<li>Compiling <code>resources.c</code> into <code>resources.o</code>.</li>
<li>Compiling the ui file <code>tfe.ui</code> into
<code>resources.c</code>.</li>
<li>Compiling <code>tfe.c</code> into <code>tfe.o</code> (object
file).</li>
<li>Compiling <code>tfetextview.c</code> into
<code>tfetextview.o</code>.</li>
<li>Compiling <code>resources.c</code> into
<code>resources.o</code>.</li>
<li>Linking all the object files into application <code>tfe</code>.</li>
</ul>
<p>Now build tool is necessary to manage it. Make is one of the build tools. It was created in 1976. It is an old and widely used program.</p>
<p>Make analyzes Makefile and executes compilers. All instructions are written in Makefile.</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode makefile"><code class="sourceCode makefile"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true"></a><span class="dv">sample.o:</span><span class="dt"> sample.c</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true"></a> gcc -o sample.o sample.c</span></code></pre></div>
<p>The sample of Malefile above consists of three elements, <code>sample.o</code>, <code>sample.c</code> and <code>gcc -o sample.o sample.c</code>.</p>
<p>Build tools manage the steps. Ill show you three build tools, Meson
and Ninja, Make and Rake. Meson and Ninja is recommended as a C build
tool, but others are also fine. Its your choice.</p>
<h2 id="meson-and-ninja">Meson and Ninja</h2>
<p>Meson and Ninja is one of the most popular building tool to build C
language program. Many developers use Meson and Ninja lately. For
example, GTK 4 uses them.</p>
<p>You need to make <code>meson.build</code> file first.</p>
<div class="sourceCode" id="cb5"><pre
class="sourceCode numberSource numberLines"><code class="sourceCode"><span id="cb5-1"><a href="#cb5-1"></a>project(&#39;tfe&#39;, &#39;c&#39;)</span>
<span id="cb5-2"><a href="#cb5-2"></a></span>
<span id="cb5-3"><a href="#cb5-3"></a>gtkdep = dependency(&#39;gtk4&#39;)</span>
<span id="cb5-4"><a href="#cb5-4"></a></span>
<span id="cb5-5"><a href="#cb5-5"></a>gnome=import(&#39;gnome&#39;)</span>
<span id="cb5-6"><a href="#cb5-6"></a>resources = gnome.compile_resources(&#39;resources&#39;,&#39;tfe.gresource.xml&#39;)</span>
<span id="cb5-7"><a href="#cb5-7"></a></span>
<span id="cb5-8"><a href="#cb5-8"></a>sourcefiles=files(&#39;tfe.c&#39;, &#39;tfetextview.c&#39;)</span>
<span id="cb5-9"><a href="#cb5-9"></a></span>
<span id="cb5-10"><a href="#cb5-10"></a>executable(&#39;tfe&#39;, sourcefiles, resources, dependencies: gtkdep)</span></code></pre></div>
<ul>
<li><code>sample.o</code> is called target.</li>
<li><code>sample.c</code> is prerequisite.</li>
<li><code>gcc -o sample.o sample.c</code> is recipe. Recipes follow tab characters, not spaces. (It is very important. Use tab not space, or make wont work as you expected).</li>
<li>1: The function <code>project</code> defines things about the
project. The first parameter is the name of the project and the second
is the programming language.</li>
<li>2: <code>dependency</code> function defines a dependency that is
taken by <code>pkg-config</code>. We put <code>gtk4</code> as an
argument.</li>
<li>5: <code>import</code> function imports a module. In line 5, the
gnome module is imported and assigned to the variable
<code>gnome</code>. The gnome module provides helper tools to build GTK
programs.</li>
<li>6: <code>.compile_resources</code> is a method of the gnome module
and compiles files to resources under the instruction of xml file. In
line 6, the resource filename is <code>resources</code>, which means
<code>resources.c</code> and <code>resources.h</code>, and xml file is
<code>tfe.gresource.xml</code>. This method generates C source file by
default.</li>
<li>8: Defines source files.</li>
<li>10: Executable function generates a target file by compiling source
files. The first parameter is the filename of the target. The following
parameters are source files. The last parameter is an option
<code>dependencies</code>. <code>gtkdep</code> is used in the
compilation.</li>
</ul>
<p>Now run meson and ninja.</p>
<pre><code>$ meson _build
$ ninja -C _build</code></pre>
<p>Then, the executable file <code>tfe</code> is generated under the
directory <code>_build</code>.</p>
<pre><code>$ _build/tfe tfe.c tfetextview.c</code></pre>
<p>A window appears. It includes a notebook with two pages. One is
<code>tfe.c</code> and the other is <code>tfetextview.c</code>.</p>
<p>For further information, see <a href="https://mesonbuild.com/">The
Meson Build system</a>.</p>
<h2 id="make">Make</h2>
<p>Make is a build tool created in 1976. It was a standard build tool
for C compiling, but lately it is replaced by Meson and Ninja.</p>
<p>Make analyzes Makefile and executes compilers. All instructions are
written in Makefile.</p>
<p>For example,</p>
<div class="sourceCode" id="cb8"><pre
class="sourceCode makefile"><code class="sourceCode makefile"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="dv">sample.o:</span><span class="dt"> sample.c</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a> gcc -o sample.o sample.c</span></code></pre></div>
<p>Malefile above consists of three elements, <code>sample.o</code>,
<code>sample.c</code> and <code>gcc -o sample.o sample.c</code>.</p>
<ul>
<li><code>sample.o</code> is a target.</li>
<li><code>sample.c</code> is a prerequisite.</li>
<li><code>gcc -o sample.o sample.c</code> is a recipe. Recipes follow
tab characters, not spaces. (It is very important. Use tab, or make
wont work as you expected).</li>
</ul>
<p>The rule is:</p>
<p>If a prerequisite modified later than a target, then make executes the recipe.</p>
<p>In the example above, if <code>sample.c</code> is modified after the generation of <code>sample.o</code>, then make executes gcc and compile <code>sample.c</code> into <code>sample.o</code>. If the modification time of <code>sample.c</code> is older then the generation of <code>sample.o</code>, then no compiling is necessary, so make does nothing.</p>
<p>If a prerequisite modified later than a target, then make executes
the recipe.</p>
<p>In the example above, if <code>sample.c</code> is modified after the
generation of <code>sample.o</code>, then make executes gcc and compile
<code>sample.c</code> into <code>sample.o</code>. If the modification
time of <code>sample.c</code> is older then the generation of
<code>sample.o</code>, then no compiling is necessary, so make does
nothing.</p>
<p>The Makefile for <code>tfe</code> is as follows.</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode numberSource makefile numberLines"><code class="sourceCode makefile"><span id="cb6-1"><a href="#cb6-1"></a><span class="dv">all:</span><span class="dt"> tfe</span></span>
<span id="cb6-2"><a href="#cb6-2"></a></span>
<span id="cb6-3"><a href="#cb6-3"></a><span class="dv">tfe:</span><span class="dt"> tfe.o tfetextview.o resources.o</span></span>
<span id="cb6-4"><a href="#cb6-4"></a> gcc -o tfe tfe.o tfetextview.o resources.o `pkg-config --libs gtk4`</span>
<span id="cb6-5"><a href="#cb6-5"></a></span>
<span id="cb6-6"><a href="#cb6-6"></a><span class="dv">tfe.o:</span><span class="dt"> tfe.c tfetextview.h</span></span>
<span id="cb6-7"><a href="#cb6-7"></a> gcc -c -o tfe.o `pkg-config --cflags gtk4` tfe.c</span>
<span id="cb6-8"><a href="#cb6-8"></a><span class="dv">tfetextview.o:</span><span class="dt"> tfetextview.c tfetextview.h</span></span>
<span id="cb6-9"><a href="#cb6-9"></a> gcc -c -o tfetextview.o `pkg-config --cflags gtk4` tfetextview.c</span>
<span id="cb6-10"><a href="#cb6-10"></a><span class="dv">resources.o:</span><span class="dt"> resources.c</span></span>
<span id="cb6-11"><a href="#cb6-11"></a> gcc -c -o resources.o `pkg-config --cflags gtk4` resources.c</span>
<span id="cb6-12"><a href="#cb6-12"></a></span>
<span id="cb6-13"><a href="#cb6-13"></a><span class="dv">resources.c:</span><span class="dt"> tfe.gresource.xml tfe.ui</span></span>
<span id="cb6-14"><a href="#cb6-14"></a> glib-compile-resources tfe.gresource.xml --target=resources.c --generate-source</span>
<span id="cb6-15"><a href="#cb6-15"></a></span>
<span id="cb6-16"><a href="#cb6-16"></a><span class="ot">.Phony:</span><span class="dt"> clean</span></span>
<span id="cb6-17"><a href="#cb6-17"></a></span>
<span id="cb6-18"><a href="#cb6-18"></a><span class="dv">clean:</span></span>
<span id="cb6-19"><a href="#cb6-19"></a> rm -f tfe tfe.o tfetextview.o resources.o resources.c</span></code></pre></div>
<p>You only need to type <code>make</code>.</p>
<div class="sourceCode" id="cb9"><pre
class="sourceCode numberSource makefile numberLines"><code class="sourceCode makefile"><span id="cb9-1"><a href="#cb9-1"></a><span class="dv">all:</span><span class="dt"> tfe</span></span>
<span id="cb9-2"><a href="#cb9-2"></a></span>
<span id="cb9-3"><a href="#cb9-3"></a><span class="dv">tfe:</span><span class="dt"> tfe.o tfetextview.o resources.o</span></span>
<span id="cb9-4"><a href="#cb9-4"></a> gcc -o tfe tfe.o tfetextview.o resources.o `pkg-config --libs gtk4`</span>
<span id="cb9-5"><a href="#cb9-5"></a></span>
<span id="cb9-6"><a href="#cb9-6"></a><span class="dv">tfe.o:</span><span class="dt"> tfe.c tfetextview.h</span></span>
<span id="cb9-7"><a href="#cb9-7"></a> gcc -c -o tfe.o `pkg-config --cflags gtk4` tfe.c</span>
<span id="cb9-8"><a href="#cb9-8"></a><span class="dv">tfetextview.o:</span><span class="dt"> tfetextview.c tfetextview.h</span></span>
<span id="cb9-9"><a href="#cb9-9"></a> gcc -c -o tfetextview.o `pkg-config --cflags gtk4` tfetextview.c</span>
<span id="cb9-10"><a href="#cb9-10"></a><span class="dv">resources.o:</span><span class="dt"> resources.c</span></span>
<span id="cb9-11"><a href="#cb9-11"></a> gcc -c -o resources.o `pkg-config --cflags gtk4` resources.c</span>
<span id="cb9-12"><a href="#cb9-12"></a></span>
<span id="cb9-13"><a href="#cb9-13"></a><span class="dv">resources.c:</span><span class="dt"> tfe.gresource.xml tfe.ui</span></span>
<span id="cb9-14"><a href="#cb9-14"></a> glib-compile-resources tfe.gresource.xml --target=resources.c --generate-source</span>
<span id="cb9-15"><a href="#cb9-15"></a></span>
<span id="cb9-16"><a href="#cb9-16"></a><span class="ot">.Phony:</span><span class="dt"> clean</span></span>
<span id="cb9-17"><a href="#cb9-17"></a></span>
<span id="cb9-18"><a href="#cb9-18"></a><span class="dv">clean:</span></span>
<span id="cb9-19"><a href="#cb9-19"></a> rm -f tfe tfe.o tfetextview.o resources.o resources.c</span></code></pre></div>
<p>You just type <code>make</code> and everything will be done.</p>
<pre><code>$ make
gcc -c -o tfe.o `pkg-config --cflags gtk4` tfe.c
gcc -c -o tfetextview.o `pkg-config --cflags gtk4` tfetextview.c
glib-compile-resources tfe.gresource.xml --target=resources.c --generate-source
gcc -c -o resources.o `pkg-config --cflags gtk4` resources.c
gcc -o tfe tfe.o tfetextview.o resources.o `pkg-config --libs gtk4`</code></pre>
<p>I used only very basic rules to write this Makefile. There are many more convenient methods to make it more compact. But it will be long to explain it. So I want to finish explaining make and move on to the next topic.</p>
<p>I used only very basic rules to write this Makefile. There are many
more convenient methods to make it more compact. But it will be long to
explain it. So I want to finish with make and move on to the next
topic.</p>
<p>You can download “Gnu Make Manual” from <a
href="https://www.gnu.org/software/make/manual/">GNU website</a>.</p>
<h2 id="rake">Rake</h2>
<p>Rake is a similar program to make. It is written in Ruby code. If you dont use Ruby, you dont need to read this subsection. However, Ruby is really sophisticated and recommendable script language.</p>
<p>Rake is a similar program to make. It is written in Ruby language. If
you dont use Ruby, you dont need to read this subsection. However,
Ruby is really sophisticated and recommendable script language.</p>
<ul>
<li>Rakefile controls the behavior of <code>rake</code>.</li>
<li>You can write any Ruby code in Rakefile.</li>
</ul>
<p>Rake has task and file task, which is similar to target, prerequisite and recipe in make.</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode numberSource ruby numberLines"><code class="sourceCode ruby"><span id="cb8-1"><a href="#cb8-1"></a>require <span class="st">&#39;rake/clean&#39;</span></span>
<span id="cb8-2"><a href="#cb8-2"></a></span>
<span id="cb8-3"><a href="#cb8-3"></a>targetfile = <span class="st">&quot;tfe&quot;</span></span>
<span id="cb8-4"><a href="#cb8-4"></a>srcfiles = <span class="dt">FileList</span>[<span class="st">&quot;tfe.c&quot;</span>, <span class="st">&quot;tfetextview.c&quot;</span>, <span class="st">&quot;resources.c&quot;</span>]</span>
<span id="cb8-5"><a href="#cb8-5"></a>rscfile = srcfiles[<span class="dv">2</span>]</span>
<span id="cb8-6"><a href="#cb8-6"></a>objfiles = srcfiles.gsub(<span class="ot">/.c$/</span>, <span class="st">&#39;.o&#39;</span>)</span>
<span id="cb8-7"><a href="#cb8-7"></a></span>
<span id="cb8-8"><a href="#cb8-8"></a><span class="dt">CLEAN</span>.include(targetfile, objfiles, rscfile)</span>
<span id="cb8-9"><a href="#cb8-9"></a></span>
<span id="cb8-10"><a href="#cb8-10"></a>task <span class="st">default: </span>targetfile</span>
<span id="cb8-11"><a href="#cb8-11"></a></span>
<span id="cb8-12"><a href="#cb8-12"></a>file targetfile =&gt; objfiles <span class="kw">do</span> |t|</span>
<span id="cb8-13"><a href="#cb8-13"></a> sh <span class="st">&quot;gcc -o </span><span class="ot">#{</span>t.name<span class="ot">}</span><span class="st"> </span><span class="ot">#{</span>t.prerequisites.join(<span class="ch">&#39; &#39;</span>)<span class="ot">}</span><span class="st"> `pkg-config --libs gtk4`&quot;</span></span>
<span id="cb8-14"><a href="#cb8-14"></a><span class="kw">end</span></span>
<span id="cb8-15"><a href="#cb8-15"></a></span>
<span id="cb8-16"><a href="#cb8-16"></a>objfiles.each <span class="kw">do</span> |obj|</span>
<span id="cb8-17"><a href="#cb8-17"></a> src = obj.gsub(<span class="ot">/.o$/</span>,<span class="st">&#39;.c&#39;</span>)</span>
<span id="cb8-18"><a href="#cb8-18"></a> file obj =&gt; src <span class="kw">do</span> |t|</span>
<span id="cb8-19"><a href="#cb8-19"></a> sh <span class="st">&quot;gcc -c -o </span><span class="ot">#{</span>t.name<span class="ot">}</span><span class="st"> `pkg-config --cflags gtk4` </span><span class="ot">#{</span>t.source<span class="ot">}</span><span class="st">&quot;</span></span>
<span id="cb8-20"><a href="#cb8-20"></a> <span class="kw">end</span></span>
<span id="cb8-21"><a href="#cb8-21"></a><span class="kw">end</span></span>
<span id="cb8-22"><a href="#cb8-22"></a></span>
<span id="cb8-23"><a href="#cb8-23"></a>file rscfile =&gt; [<span class="st">&quot;tfe.gresource.xml&quot;</span>, <span class="st">&quot;tfe.ui&quot;</span>] <span class="kw">do</span> |t|</span>
<span id="cb8-24"><a href="#cb8-24"></a> sh <span class="st">&quot;glib-compile-resources </span><span class="ot">#{</span>t.prerequisites[<span class="dv">0</span>]<span class="ot">}</span><span class="st"> --target=</span><span class="ot">#{</span>t.name<span class="ot">}</span><span class="st"> --generate-source&quot;</span></span>
<span id="cb8-25"><a href="#cb8-25"></a><span class="kw">end</span></span></code></pre></div>
<p>The contents of the <code>Rakefile</code> is almost same as the <code>Makefile</code> in the previous subsection.</p>
<p>Rake has task and file task, which is similar to target, prerequisite
and recipe in make.</p>
<div class="sourceCode" id="cb11"><pre
class="sourceCode numberSource ruby numberLines"><code class="sourceCode ruby"><span id="cb11-1"><a href="#cb11-1"></a><span class="fu">require</span> <span class="vs">&#39;rake/clean&#39;</span></span>
<span id="cb11-2"><a href="#cb11-2"></a></span>
<span id="cb11-3"><a href="#cb11-3"></a>targetfile <span class="kw">=</span> <span class="st">&quot;tfe&quot;</span></span>
<span id="cb11-4"><a href="#cb11-4"></a>srcfiles <span class="kw">=</span> <span class="dt">FileList</span><span class="kw">[</span><span class="st">&quot;tfe.c&quot;</span>, <span class="st">&quot;tfetextview.c&quot;</span>, <span class="st">&quot;resources.c&quot;</span><span class="kw">]</span></span>
<span id="cb11-5"><a href="#cb11-5"></a>uifile <span class="kw">=</span> <span class="st">&quot;tfe.ui&quot;</span></span>
<span id="cb11-6"><a href="#cb11-6"></a>rscfile <span class="kw">=</span> srcfiles<span class="kw">[</span><span class="dv">2</span><span class="kw">]</span></span>
<span id="cb11-7"><a href="#cb11-7"></a>objfiles <span class="kw">=</span> srcfiles<span class="at">.ext</span>(<span class="st">&quot;.o&quot;</span>)</span>
<span id="cb11-8"><a href="#cb11-8"></a>gresource_xml <span class="kw">=</span> <span class="st">&quot;tfe.gresource.xml&quot;</span></span>
<span id="cb11-9"><a href="#cb11-9"></a></span>
<span id="cb11-10"><a href="#cb11-10"></a><span class="cn">CLEAN</span><span class="at">.include</span>(targetfile, objfiles, rscfile)</span>
<span id="cb11-11"><a href="#cb11-11"></a></span>
<span id="cb11-12"><a href="#cb11-12"></a>task <span class="wa">default: </span>targetfile</span>
<span id="cb11-13"><a href="#cb11-13"></a></span>
<span id="cb11-14"><a href="#cb11-14"></a>file targetfile <span class="kw">=&gt;</span> objfiles <span class="cf">do</span> <span class="kw">|</span>t<span class="kw">|</span></span>
<span id="cb11-15"><a href="#cb11-15"></a> sh <span class="st">&quot;gcc -o </span><span class="sc">#{</span>t<span class="at">.name</span><span class="sc">}</span><span class="st"> </span><span class="sc">#{</span>t<span class="at">.prerequisites.join</span>(<span class="ch">&#39; &#39;</span>)<span class="sc">}</span><span class="st"> `pkg-config --libs gtk4`&quot;</span></span>
<span id="cb11-16"><a href="#cb11-16"></a><span class="cf">end</span></span>
<span id="cb11-17"><a href="#cb11-17"></a></span>
<span id="cb11-18"><a href="#cb11-18"></a>objfiles<span class="at">.each</span> <span class="cf">do</span> <span class="kw">|</span>obj<span class="kw">|</span></span>
<span id="cb11-19"><a href="#cb11-19"></a> src <span class="kw">=</span> obj<span class="at">.ext</span>(<span class="st">&quot;.c&quot;</span>)</span>
<span id="cb11-20"><a href="#cb11-20"></a> file obj <span class="kw">=&gt;</span> src <span class="cf">do</span> <span class="kw">|</span>t<span class="kw">|</span></span>
<span id="cb11-21"><a href="#cb11-21"></a> sh <span class="st">&quot;gcc -c -o </span><span class="sc">#{</span>t<span class="at">.name</span><span class="sc">}</span><span class="st"> `pkg-config --cflags gtk4` </span><span class="sc">#{</span>t<span class="at">.source</span><span class="sc">}</span><span class="st">&quot;</span></span>
<span id="cb11-22"><a href="#cb11-22"></a> <span class="cf">end</span></span>
<span id="cb11-23"><a href="#cb11-23"></a><span class="cf">end</span></span>
<span id="cb11-24"><a href="#cb11-24"></a></span>
<span id="cb11-25"><a href="#cb11-25"></a>file rscfile <span class="kw">=&gt;</span> uifile <span class="cf">do</span> <span class="kw">|</span>t<span class="kw">|</span></span>
<span id="cb11-26"><a href="#cb11-26"></a> sh <span class="st">&quot;glib-compile-resources </span><span class="sc">#{</span>gresource_xml<span class="sc">}</span><span class="st"> --target=</span><span class="sc">#{</span>t<span class="at">.name</span><span class="sc">}</span><span class="st"> --generate-source&quot;</span></span>
<span id="cb11-27"><a href="#cb11-27"></a><span class="cf">end</span></span></code></pre></div>
<p>The contents of the <code>Rakefile</code> is almost same as the
<code>Makefile</code> in the previous subsection.</p>
<ul>
<li>3-6: Defines target file, source file and so on.</li>
<li>1, 8: Loads clean library. And defines CLEAN file list. The files included by CLEAN will be removed when <code>rake clean</code> is typed on the command line.</li>
<li>10: The default target depends on <code>targetfile</code>. The task <code>default</code> is the final goal of tasks.</li>
<li>12-14: <code>targetfile</code> depends on <code>objfiles</code>. The variable <code>t</code> is a task object.
<li>3-8: Defines target file, source files and so on.</li>
<li>1, 10 Requires rake/clean library. And clean files are added to
CLEAN. The files included by CLEAN will be removed when
<code>rake clean</code> is typed on the command line.</li>
<li>12: The default target depends on <code>targetfile</code>. The task
<code>default</code> is the final goal of tasks.</li>
<li>14-16: <code>targetfile</code> depends on <code>objfiles</code>. The
variable <code>t</code> is a task object.
<ul>
<li><code>t.name</code> is a target name</li>
<li><code>t.prerequisites</code> is an array of prerequisites.</li>
<li><code>t.source</code> is the first element of prerequisites.</li>
</ul></li>
<li><code>sh</code> is a method to give the following string to shell as an argument and executes the shell.</li>
<li>16-21: Theres a loop by each element of the array of <code>objfiles</code>. Each object depends on corresponding source file.</li>
<li>23-25: Resource file depends on xml file and ui file.</li>
<li><code>sh</code> is a method to give the following string to shell as
an argument and executes the shell.</li>
<li>18-23: An each iterator of the array <code>objfiles</code>. Each
object depends on corresponding source file.</li>
<li>25-27: Resource file depends on ui file.</li>
</ul>
<p>Rakefile might seem to be difficult for beginners. But, you can use any Ruby syntax in Rakefile, so it is really flexible. If you practice Ruby and Rakefile, it will be highly productive tools.</p>
<h2 id="meson-and-ninja">Meson and ninja</h2>
<p>Meson is one of the most popular building tool despite the developing version. And ninja is similar to make but much faster than make. Several years ago, most of the C developers used autotools and make. But now the situation has changed. Many developers are using meson and ninja now.</p>
<p>To use meson, you first need to write <code>meson.build</code> file.</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode numberSource numberLines"><code class="sourceCode"><span id="cb9-1"><a href="#cb9-1"></a>project(&#39;tfe&#39;, &#39;c&#39;)</span>
<span id="cb9-2"><a href="#cb9-2"></a></span>
<span id="cb9-3"><a href="#cb9-3"></a>gtkdep = dependency(&#39;gtk4&#39;)</span>
<span id="cb9-4"><a href="#cb9-4"></a></span>
<span id="cb9-5"><a href="#cb9-5"></a>gnome=import(&#39;gnome&#39;)</span>
<span id="cb9-6"><a href="#cb9-6"></a>resources = gnome.compile_resources(&#39;resources&#39;,&#39;tfe.gresource.xml&#39;)</span>
<span id="cb9-7"><a href="#cb9-7"></a></span>
<span id="cb9-8"><a href="#cb9-8"></a>sourcefiles=files(&#39;tfe.c&#39;, &#39;tfetextview.c&#39;)</span>
<span id="cb9-9"><a href="#cb9-9"></a></span>
<span id="cb9-10"><a href="#cb9-10"></a>executable(&#39;tfe&#39;, sourcefiles, resources, dependencies: gtkdep)</span></code></pre></div>
<ul>
<li>1: The function <code>project</code> defines things about the project. The first parameter is the name of the project and the second is the programming language.</li>
<li>2: <code>dependency</code> function defines a dependency that is taken by <code>pkg-config</code>. We put <code>gtk4</code> as an argument.</li>
<li>5: <code>import</code> function imports a module. In line 5, the gnome module is imported and assigned to the variable <code>gnome</code>. The gnome module provides helper tools to build GTK programs.</li>
<li>6: <code>.compile_resources</code> is a method of the gnome module and compiles files to resources under the instruction of xml file. In line 6, the resource filename is <code>resources</code>, which means <code>resources.c</code> and <code>resources.h</code>, and xml file is <code>tfe.gresource.xml</code>. This method generates C source file by default.</li>
<li>8: Defines source files.</li>
<li>10: Executable function generates a target file by compiling source files. The first parameter is the filename of the target. The following parameters are source files. The last parameter is an option <code>dependencies</code>. <code>gtkdep</code> is used in the compilation.</li>
</ul>
<p>Now run meson and ninja.</p>
<pre><code>$ meson _build
$ ninja -C _build</code></pre>
<p>Then, the executable file <code>tfe</code> is generated under the directory <code>_build</code>.</p>
<pre><code>$ _build/tfe tfe.c tfetextview.c</code></pre>
<p>Then the window appears. And two notebook pages are in the window. One notebook is <code>tfe.c</code> and the other is <code>tfetextview.c</code>.</p>
<p>Ive shown you three build tools. I think meson and ninja is the best choice for the present.</p>
<p>We divided a file into some categorized files and used a build tool. This method is used by many developers.</p>
<p>Rakefile might seem to be difficult for beginners. But, you can use
any Ruby syntax in the Rakefile, so it is really flexible. If you
practice Ruby and Rakefile, it will be highly productive tools.</p>
<p>For further information, see <a
href="https://toshiocp.github.io/Rake-tutorial-for-beginners-en/LearningRake.html">Rake
tutorial for beginners</a>.</p>
</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>

View file

@ -5,7 +5,7 @@
<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>Gtk4 tutorial</title>
<title>GTK 4 tutorial</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
@ -111,319 +111,503 @@
</div>
</div>
</nav>
<h1 id="initialization-and-destruction-of-instances">Initialization and destruction of instances</h1>
<p>A new version of the text file editor (<code>tfe</code>) will be made in this section and the following four sections. It is <code>tfe5</code>. There are many changes from the prior version. All the sources are listed in <a href="sec16.html">Section 16</a>. They are located in two directories, src/tfe5 and src/tfetextview.</p>
<h1 id="instance-initialization-and-destruction">Instance Initialization
and destruction</h1>
<p>A new version of the text file editor (<code>tfe</code>) will be made
in this section and the following four sections. It is
<code>tfe5</code>. There are many changes from the prior version. They
are located in two directories, src/tfe5 and src/tfetextview.</p>
<h2 id="encapsulation">Encapsulation</h2>
<p>Weve divided C source file into two parts. But it is not enough in terms of encapsulation.</p>
<p>Weve divided C source file into two parts. But it is not enough in
terms of encapsulation.</p>
<ul>
<li><code>tfe.c</code> includes everything other than TfeTextView. It should be divided at least into two parts, <code>tfeapplication.c</code> and <code>tfenotebook.c</code>.</li>
<li><code>tfe.c</code> includes everything other than TfeTextView. It
should be divided at least into two parts, <code>tfeapplication.c</code>
and <code>tfenotebook.c</code>.</li>
<li>Header files also need to be organized.</li>
</ul>
<p>However, first of all, Id like to focus on the object TfeTextView. It is a child object of GtkTextView and has a new member <code>file</code> in it. The important thing is to manage the Gfile object pointed by <code>file</code>.</p>
<p>However, first of all, Id like to focus on the object TfeTextView.
It is a child object of GtkTextView and has a new member
<code>file</code> in it. The important thing is to manage the Gfile
object pointed by <code>file</code>.</p>
<ul>
<li>What is necessary to GFile when creating (or initializing) TfeTextView?</li>
<li>What is necessary to GFile when creating (or initializing)
TfeTextView?</li>
<li>What is necessary to GFile when destructing TfeTextView?</li>
<li>TfeTextView should read/write a file by itself or not?</li>
<li>How it communicates with objects outside?</li>
</ul>
<p>You need to know at least class, instance and signals before thinking about them. I will explain them in this section and the next section. After that I will explain:</p>
<p>You need to know at least class, instance and signals before thinking
about them. I will explain them in this section and the next section.
After that I will explain:</p>
<ul>
<li>Organizing functions.</li>
<li>How to use GtkFileChooserDialog</li>
</ul>
<h2 id="gobject-and-its-children">GObject and its children</h2>
<p>GObject and its children are objects, which have both class and instance. First, think about instance of objects. Instance is structured memories. The structure is described as C language structure. The following is a structure of TfeTextView.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true"></a><span class="co">/* This typedef statement is automatically generated by the macro G_DECLARE_FINAL_TYPE */</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true"></a><span class="kw">typedef</span> <span class="kw">struct</span> _TfeTextView TfeTextView;</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true"></a></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true"></a><span class="kw">struct</span> _TfeTextView {</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true"></a> GtkTextView parent;</span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true"></a> GFile *file;</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true"></a>};</span></code></pre></div>
<p>GObject and its children are objects, which have both class and
object C structures. First, think about instances. An instance is
memories which has the object structure. The following is the structure
of TfeTextView.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="co">/* This typedef statement is automatically generated by the macro G_DECLARE_FINAL_TYPE */</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="kw">typedef</span> <span class="kw">struct</span> _TfeTextView TfeTextView<span class="op">;</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="kw">struct</span> _TfeTextView <span class="op">{</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a> GtkTextView parent<span class="op">;</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a> GFile <span class="op">*</span>file<span class="op">;</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="op">};</span></span></code></pre></div>
<p>The members of the structure are:</p>
<ul>
<li>The type of <code>parent</code> is GtkTextView which is C structure. It is declared in <code>gtktextview.h</code>. GtkTextView is the parent of TfeTextView.</li>
<li><code>file</code> is a pointer to GFile. It can be NULL if no file corresponds to the TfeTextView object.</li>
<li>The type of <code>parent</code> is GtkTextView which is C structure.
It is declared in <code>gtktextview.h</code>. GtkTextView is the parent
of TfeTextView.</li>
<li><code>file</code> is a pointer to a GFile. It can be NULL if no file
corresponds to the TfeTextView instance.</li>
</ul>
<p>Notice the program above is the declaration of the structure, not the definition. So, no memories are allocated at this moment. They are to be allocated when <code>tfe_text_view_new</code> function is invoked. The memory allocated with <code>tfe_text_view_new</code> is an instance of TfeTextView object. Therefore, There can be multiple TfeTextView instances if <code>tfe_text_view_new</code> is called multiple times.</p>
<p>You can find the declaration of the ancestors of TfeTextView in the source files of GTK and GLib. The following is extracts from the source files (not exactly the same).</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"></a><span class="kw">typedef</span> <span class="kw">struct</span> _GObject GObject;</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true"></a><span class="kw">typedef</span> <span class="kw">struct</span> _GObject GInitiallyUnowned;</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true"></a><span class="kw">struct</span> _GObject</span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true"></a>{</span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true"></a> GTypeInstance g_type_instance;</span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true"></a> <span class="dt">volatile</span> guint ref_count;</span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true"></a> GData *qdata;</span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true"></a>};</span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true"></a></span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true"></a><span class="kw">typedef</span> <span class="kw">struct</span> _GtkWidget GtkWidget;</span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true"></a><span class="kw">struct</span> _GtkWidget</span>
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true"></a>{</span>
<span id="cb2-13"><a href="#cb2-13" aria-hidden="true"></a> GInitiallyUnowned parent_instance;</span>
<span id="cb2-14"><a href="#cb2-14" aria-hidden="true"></a> GtkWidgetPrivate *priv;</span>
<span id="cb2-15"><a href="#cb2-15" aria-hidden="true"></a>};</span>
<span id="cb2-16"><a href="#cb2-16" aria-hidden="true"></a></span>
<span id="cb2-17"><a href="#cb2-17" aria-hidden="true"></a><span class="kw">typedef</span> <span class="kw">struct</span> _GtkTextView GtkTextView;</span>
<span id="cb2-18"><a href="#cb2-18" aria-hidden="true"></a><span class="kw">struct</span> _GtkTextView</span>
<span id="cb2-19"><a href="#cb2-19" aria-hidden="true"></a>{</span>
<span id="cb2-20"><a href="#cb2-20" aria-hidden="true"></a> GtkWidget parent_instance;</span>
<span id="cb2-21"><a href="#cb2-21" aria-hidden="true"></a> GtkTextViewPrivate *priv;</span>
<span id="cb2-22"><a href="#cb2-22" aria-hidden="true"></a>};</span></code></pre></div>
<p>In each structure, its parent is declared at the top of the members. So, every ancestors is included in the child instance. This is very important. It guarantees a child widget to inherit all the features from ancestors. The structure of <code>TfeTextView</code> is like the following diagram.</p>
<p>You can find the declaration of the ancestors object structures in
the source files of GTK and GLib. The following is extracted from the
source files (not exactly the same).</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><span class="kw">typedef</span> <span class="kw">struct</span> _GObject GObject<span class="op">;</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="kw">typedef</span> <span class="kw">struct</span> _GObject GInitiallyUnowned<span class="op">;</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="kw">struct</span> _GObject</span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="op">{</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a> GTypeInstance g_type_instance<span class="op">;</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a> <span class="dt">volatile</span> guint ref_count<span class="op">;</span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a> GData <span class="op">*</span>qdata<span class="op">;</span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a><span class="op">};</span></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a><span class="kw">typedef</span> <span class="kw">struct</span> _GtkWidget GtkWidget<span class="op">;</span></span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a><span class="kw">struct</span> _GtkWidget</span>
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a><span class="op">{</span></span>
<span id="cb2-13"><a href="#cb2-13" aria-hidden="true" tabindex="-1"></a> GInitiallyUnowned parent_instance<span class="op">;</span></span>
<span id="cb2-14"><a href="#cb2-14" aria-hidden="true" tabindex="-1"></a> GtkWidgetPrivate <span class="op">*</span>priv<span class="op">;</span></span>
<span id="cb2-15"><a href="#cb2-15" aria-hidden="true" tabindex="-1"></a><span class="op">};</span></span>
<span id="cb2-16"><a href="#cb2-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-17"><a href="#cb2-17" aria-hidden="true" tabindex="-1"></a><span class="kw">typedef</span> <span class="kw">struct</span> _GtkTextView GtkTextView<span class="op">;</span></span>
<span id="cb2-18"><a href="#cb2-18" aria-hidden="true" tabindex="-1"></a><span class="kw">struct</span> _GtkTextView</span>
<span id="cb2-19"><a href="#cb2-19" aria-hidden="true" tabindex="-1"></a><span class="op">{</span></span>
<span id="cb2-20"><a href="#cb2-20" aria-hidden="true" tabindex="-1"></a> GtkWidget parent_instance<span class="op">;</span></span>
<span id="cb2-21"><a href="#cb2-21" aria-hidden="true" tabindex="-1"></a> GtkTextViewPrivate <span class="op">*</span>priv<span class="op">;</span></span>
<span id="cb2-22"><a href="#cb2-22" aria-hidden="true" tabindex="-1"></a><span class="op">};</span></span></code></pre></div>
<p>In each structure, its parent is declared at the top of the members.
So, all the ancestors are included in the child object. The structure of
<code>TfeTextView</code> is like the following diagram.</p>
<figure>
<img src="image/TfeTextView.png" alt="" /><figcaption>The structure of the instance TfeTextView</figcaption>
<img src="image/TfeTextView.png"
alt="The structure of the instance TfeTextView" />
<figcaption aria-hidden="true">The structure of the instance
TfeTextView</figcaption>
</figure>
<h2 id="initialization-of-a-tfetextview-instance">Initialization of a TfeTextView instance</h2>
<p>The function <code>tfe_text_view_new</code> creates a new TfeTextView instance.</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb3-1"><a href="#cb3-1"></a>GtkWidget *</span>
<span id="cb3-2"><a href="#cb3-2"></a>tfe_text_view_new (<span class="dt">void</span>) {</span>
<span id="cb3-3"><a href="#cb3-3"></a> <span class="cf">return</span> GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));</span>
<span id="cb3-4"><a href="#cb3-4"></a>}</span></code></pre></div>
<p>When this function is invoked, a TfeTextView instance is created and initialized. The initialization process is as follows.</p>
<ol type="1">
<li>Initializes GObject (GInitiallyUnowned) part in TfeTextView instance.</li>
<li>Initializes GtkWidget part in TfeTextView instance.</li>
<li>Initializes GtkTextView part in TfeTextView instance.</li>
<li>Initializes TfeTextView part in TfeTextView instance.</li>
</ol>
<p>The step one through three is done by <code>g_object_init</code>, <code>gtk_widget_init</code> and <code>gtk_text_view_init</code>. They are called by the system automatically and you dont need to care about them. Step four is done by the function <code>tfe_text_view_init</code> in <code>tfetextview.c</code>.</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb4-1"><a href="#cb4-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb4-2"><a href="#cb4-2"></a>tfe_text_view_init (TfeTextView *tv) {</span>
<span id="cb4-3"><a href="#cb4-3"></a> tv-&gt;file = NULL;</span>
<span id="cb4-4"><a href="#cb4-4"></a>}</span></code></pre></div>
<p>This function just initializes <code>tv-&gt;file</code> to be <code>NULL</code>.</p>
<h2 id="functions-and-classes">Functions and Classes</h2>
<p>In Gtk, all objects derived from GObject have class and instance (except abstract object). An instance is memory of C structure, which is described in the previous two subsections. Each object can have more than one instance. Those instances have the same structure. An instance just keeps status of the instance. Therefore, it is insufficient to define its behavior. We need at least two things. One is functions and the other is class.</p>
<p>Youve already seen many functions. For example, <code>tfe_text_view_new</code> is a function to create a TfeTextView instance. These functions are similar to public object methods in object oriented languages such as Java or Ruby. Functions are public, which means that they are expected to be used by other objects.</p>
<p>Class comprises mainly pointers to functions. Those functions are used by the object itself or its descendant objects. For example, GObject class is declared in <code>gobject.h</code> in GLib source files.</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb5-1"><a href="#cb5-1"></a><span class="kw">typedef</span> <span class="kw">struct</span> _GObjectClass GObjectClass;</span>
<span id="cb5-2"><a href="#cb5-2"></a><span class="kw">typedef</span> <span class="kw">struct</span> _GObjectClass GInitiallyUnownedClass;</span>
<span id="cb5-3"><a href="#cb5-3"></a></span>
<span id="cb5-4"><a href="#cb5-4"></a><span class="kw">struct</span> _GObjectClass</span>
<span id="cb5-5"><a href="#cb5-5"></a>{</span>
<span id="cb5-6"><a href="#cb5-6"></a> GTypeClass g_type_class;</span>
<span id="cb5-7"><a href="#cb5-7"></a> <span class="co">/*&lt; private &gt;*/</span></span>
<span id="cb5-8"><a href="#cb5-8"></a> GSList *construct_properties;</span>
<span id="cb5-9"><a href="#cb5-9"></a> <span class="co">/*&lt; public &gt;*/</span></span>
<span id="cb5-10"><a href="#cb5-10"></a> <span class="co">/* seldom overridden */</span></span>
<span id="cb5-11"><a href="#cb5-11"></a> GObject* (*constructor) (GType type,</span>
<span id="cb5-12"><a href="#cb5-12"></a> guint n_construct_properties,</span>
<span id="cb5-13"><a href="#cb5-13"></a> GObjectConstructParam *construct_properties);</span>
<span id="cb5-14"><a href="#cb5-14"></a> <span class="co">/* overridable methods */</span></span>
<span id="cb5-15"><a href="#cb5-15"></a> <span class="dt">void</span> (*set_property) (GObject *object,</span>
<span id="cb5-16"><a href="#cb5-16"></a> guint property_id,</span>
<span id="cb5-17"><a href="#cb5-17"></a> <span class="dt">const</span> GValue *value,</span>
<span id="cb5-18"><a href="#cb5-18"></a> GParamSpec *pspec);</span>
<span id="cb5-19"><a href="#cb5-19"></a> <span class="dt">void</span> (*get_property) (GObject *object,</span>
<span id="cb5-20"><a href="#cb5-20"></a> guint property_id,</span>
<span id="cb5-21"><a href="#cb5-21"></a> GValue *value,</span>
<span id="cb5-22"><a href="#cb5-22"></a> GParamSpec *pspec);</span>
<span id="cb5-23"><a href="#cb5-23"></a> <span class="dt">void</span> (*dispose) (GObject *object);</span>
<span id="cb5-24"><a href="#cb5-24"></a> <span class="dt">void</span> (*finalize) (GObject *object);</span>
<span id="cb5-25"><a href="#cb5-25"></a> <span class="co">/* seldom overridden */</span></span>
<span id="cb5-26"><a href="#cb5-26"></a> <span class="dt">void</span> (*dispatch_properties_changed) (GObject *object,</span>
<span id="cb5-27"><a href="#cb5-27"></a> guint n_pspecs,</span>
<span id="cb5-28"><a href="#cb5-28"></a> GParamSpec **pspecs);</span>
<span id="cb5-29"><a href="#cb5-29"></a> <span class="co">/* signals */</span></span>
<span id="cb5-30"><a href="#cb5-30"></a> <span class="dt">void</span> (*notify) (GObject *object,</span>
<span id="cb5-31"><a href="#cb5-31"></a> GParamSpec *pspec);</span>
<span id="cb5-32"><a href="#cb5-32"></a> <span class="co">/* called when done constructing */</span></span>
<span id="cb5-33"><a href="#cb5-33"></a> <span class="dt">void</span> (*constructed) (GObject *object);</span>
<span id="cb5-34"><a href="#cb5-34"></a> <span class="co">/*&lt; private &gt;*/</span></span>
<span id="cb5-35"><a href="#cb5-35"></a> gsize flags;</span>
<span id="cb5-36"><a href="#cb5-36"></a> <span class="co">/* padding */</span></span>
<span id="cb5-37"><a href="#cb5-37"></a> gpointer pdummy[<span class="dv">6</span>];</span>
<span id="cb5-38"><a href="#cb5-38"></a>};</span></code></pre></div>
<p>Id like to explain some of the members. Theres a pointer to the function <code>dispose</code> in line 23.</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"></a><span class="dt">void</span> (*dispose) (GObject *object);</span></code></pre></div>
<p>The declaration is a bit complicated. The asterisk before the identifier <code>dispose</code> means pointer. So, the pointer <code>dispose</code> points to a function which has one parameter, which points a GObject structure, and returns no value. In the same way, line 24 says <code>finalize</code> is a pointer to the function which has one parameter, which points a GObject structure, and returns no value.</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"></a><span class="dt">void</span> (*finalize) (GObject *object);</span></code></pre></div>
<p>Look at the declaration of <code>_GObjectClass</code> so that you would find that most of the members are pointers to functions.</p>
<p>Derivable classes (ancestor classes) have their own private data area
which are not included by the structure above. For example, GtkWidget
has GtkWidgetPrivate (C structure) for its private data.</p>
<p>Notice declarations are not definitions. So, no memories are
allocated when C structures are declared. Memories are allocated to them
from the heap area when the <code>tfe_text_view_new</code> function is
called. At the same time, the ancestors private area allocated for the
TfeTetView. They are hidden from TfeTextView and it cant access to them
directly. The created memory is called instance. When a TfeTextView
instance is created, it is given three data area.</p>
<ul>
<li>11: A function pointed by <code>constructor</code> is called when the instance is generated. It completes the initialization of the instance.</li>
<li>23: A function pointed by <code>dispose</code> is called when the instance destructs itself. Destruction process is divided into two phases. The first one is called disposing. In this phase, the instance releases all the references to other instances. The second phase is finalizing.</li>
<li>24: A function pointed by <code>finalize</code> finishes the destruction process.</li>
<li>The other pointers point to functions which are called while the instance lives.</li>
<li>The instance (C structure).</li>
<li>GtkWidgetPrivate structure.</li>
<li>GtkTextViewPrivate structure.</li>
</ul>
<p>These functions are called class methods. The methods are open to its descendants. But not open to the objects which are not the descendants.</p>
<p>TfeTextView functions can access to its instance only. The
GtkWidgetPrivate and GtkTextViewPrivate are used by the ancestors
functions. See the following example.</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>GtkWidget <span class="op">*</span>tv <span class="op">=</span> tfe_text_view_new <span class="op">();</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>GtkTextBuffer <span class="op">*</span>buffer <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></code></pre></div>
<p>The parents function <code>gtk_text_view_get_buffer</code> accesses
the GtkTextViewPrivate data (owned by <code>tv</code>). There is a
pointer, which points the GtkBuffer, in the private area and the
function returns the pointer. (Actual behavior is a bit more
complicated.)</p>
<p>TfeTextView instances inherit the ancestors functions like this.</p>
<p>A TfeTextView instance is created every time the
<code>tfe_text_view_new</code> function is called. Therefore, multiple
TfeTextView instances can exist.</p>
<h2 id="initialization-of-tfetextview-instances">Initialization of
TfeTextView instances</h2>
<p>The function <code>tfe_text_view_new</code> creates a new TfeTextView
instance.</p>
<div class="sourceCode" id="cb4"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb4-1"><a href="#cb4-1"></a>GtkWidget <span class="op">*</span></span>
<span id="cb4-2"><a href="#cb4-2"></a>tfe_text_view_new <span class="op">(</span><span class="dt">void</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-3"><a href="#cb4-3"></a> <span class="cf">return</span> GTK_WIDGET <span class="op">(</span>g_object_new <span class="op">(</span>TFE_TYPE_TEXT_VIEW<span class="op">,</span> <span class="st">&quot;wrap-mode&quot;</span><span class="op">,</span> GTK_WRAP_WORD_CHAR<span class="op">,</span> NULL<span class="op">));</span></span>
<span id="cb4-4"><a href="#cb4-4"></a><span class="op">}</span></span></code></pre></div>
<p>When this function is invoked, a TfeTextView instance is created and
initialized. The initialization process is as follows.</p>
<ol type="1">
<li>When the instance is created, GtkWidgetPrivate and
GtkTextViewPrivate structures are also created</li>
<li>Initializes GObject (GInitiallyUnowned) part in the TfeTextView
instance.</li>
<li>Initializes GtkWidget part (the first <code>priv</code>) in the
TfeTextView instance and GtkWidgetPrivate structure.</li>
<li>Initializes GtkTextView part (the second <code>priv</code>) in the
TfeTextView instance and GtkTextViewPrivate structure.</li>
<li>Initializes TfeTextView part (<code>file</code>) in the TfeTextView
instance.</li>
</ol>
<p>The step two through four is done by <code>g_object_init</code>,
<code>gtk_widget_init</code> and <code>gtk_text_view_init</code>. They
are called by the system automatically and you dont need to care about
them. Step four is done by the function <code>tfe_text_view_init</code>
in <code>tfetextview.c</code>.</p>
<div class="sourceCode" id="cb5"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb5-1"><a href="#cb5-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb5-2"><a href="#cb5-2"></a>tfe_text_view_init <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-3"><a href="#cb5-3"></a> tv<span class="op">-&gt;</span>file <span class="op">=</span> NULL<span class="op">;</span></span>
<span id="cb5-4"><a href="#cb5-4"></a><span class="op">}</span></span></code></pre></div>
<p>This function just initializes <code>tv-&gt;file</code> to be
<code>NULL</code>.</p>
<h2 id="functions-and-classes">Functions and Classes</h2>
<p>In Gtk, all objects derived from GObject have class and instance
(except abstract object). Instances are memory of C structure, which are
described in the previous two subsections. Each object can have more
than one instance. Those instances have the same structure. Instances
just have data. Therefore, it doesnt define objects behavior. We need
at least two things. One is functions and the other is class
methods.</p>
<p>Youve already seen many functions. For example,</p>
<ul>
<li><code>TfeTextView *tfe_text_view_new (void);</code> is a function to
create a TfeTextView instance.</li>
<li><code>GtkTextBuffer *gtk_text_view_get_buffer (GtkTextView *textview)</code>
is a function to get a GtkTextBuffer from GtkTextView.</li>
</ul>
<p>Functions are public, which means that they are expected to be used
by other objects. They are similar to public methods in object oriented
languages.</p>
<p>Class (C structure) mainly consists of pointers to functions. The
functions are called class methods and used by the object itself or its
descendant objects. For example, GObject class is declared in
<code>gobject.h</code> in GLib source files.</p>
<div class="sourceCode" id="cb6"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb6-1"><a href="#cb6-1"></a><span class="kw">typedef</span> <span class="kw">struct</span> _GObjectClass GObjectClass<span class="op">;</span></span>
<span id="cb6-2"><a href="#cb6-2"></a><span class="kw">typedef</span> <span class="kw">struct</span> _GObjectClass GInitiallyUnownedClass<span class="op">;</span></span>
<span id="cb6-3"><a href="#cb6-3"></a></span>
<span id="cb6-4"><a href="#cb6-4"></a><span class="kw">struct</span> _GObjectClass</span>
<span id="cb6-5"><a href="#cb6-5"></a><span class="op">{</span></span>
<span id="cb6-6"><a href="#cb6-6"></a> GTypeClass g_type_class<span class="op">;</span></span>
<span id="cb6-7"><a href="#cb6-7"></a></span>
<span id="cb6-8"><a href="#cb6-8"></a> <span class="co">/*&lt; private &gt;*/</span></span>
<span id="cb6-9"><a href="#cb6-9"></a> GSList <span class="op">*</span>construct_properties<span class="op">;</span></span>
<span id="cb6-10"><a href="#cb6-10"></a></span>
<span id="cb6-11"><a href="#cb6-11"></a> <span class="co">/*&lt; public &gt;*/</span></span>
<span id="cb6-12"><a href="#cb6-12"></a> <span class="co">/* seldom overridden */</span></span>
<span id="cb6-13"><a href="#cb6-13"></a> GObject<span class="op">*</span> <span class="op">(*</span>constructor<span class="op">)</span> <span class="op">(</span>GType type<span class="op">,</span></span>
<span id="cb6-14"><a href="#cb6-14"></a> guint n_construct_properties<span class="op">,</span></span>
<span id="cb6-15"><a href="#cb6-15"></a> GObjectConstructParam <span class="op">*</span>construct_properties<span class="op">);</span></span>
<span id="cb6-16"><a href="#cb6-16"></a> <span class="co">/* overridable methods */</span></span>
<span id="cb6-17"><a href="#cb6-17"></a> <span class="dt">void</span> <span class="op">(*</span>set_property<span class="op">)</span> <span class="op">(</span>GObject <span class="op">*</span>object<span class="op">,</span></span>
<span id="cb6-18"><a href="#cb6-18"></a> guint property_id<span class="op">,</span></span>
<span id="cb6-19"><a href="#cb6-19"></a> <span class="dt">const</span> GValue <span class="op">*</span>value<span class="op">,</span></span>
<span id="cb6-20"><a href="#cb6-20"></a> GParamSpec <span class="op">*</span>pspec<span class="op">);</span></span>
<span id="cb6-21"><a href="#cb6-21"></a> <span class="dt">void</span> <span class="op">(*</span>get_property<span class="op">)</span> <span class="op">(</span>GObject <span class="op">*</span>object<span class="op">,</span></span>
<span id="cb6-22"><a href="#cb6-22"></a> guint property_id<span class="op">,</span></span>
<span id="cb6-23"><a href="#cb6-23"></a> GValue <span class="op">*</span>value<span class="op">,</span></span>
<span id="cb6-24"><a href="#cb6-24"></a> GParamSpec <span class="op">*</span>pspec<span class="op">);</span></span>
<span id="cb6-25"><a href="#cb6-25"></a> <span class="dt">void</span> <span class="op">(*</span>dispose<span class="op">)</span> <span class="op">(</span>GObject <span class="op">*</span>object<span class="op">);</span></span>
<span id="cb6-26"><a href="#cb6-26"></a> <span class="dt">void</span> <span class="op">(*</span>finalize<span class="op">)</span> <span class="op">(</span>GObject <span class="op">*</span>object<span class="op">);</span></span>
<span id="cb6-27"><a href="#cb6-27"></a> <span class="co">/* seldom overridden */</span></span>
<span id="cb6-28"><a href="#cb6-28"></a> <span class="dt">void</span> <span class="op">(*</span>dispatch_properties_changed<span class="op">)</span> <span class="op">(</span>GObject <span class="op">*</span>object<span class="op">,</span></span>
<span id="cb6-29"><a href="#cb6-29"></a> guint n_pspecs<span class="op">,</span></span>
<span id="cb6-30"><a href="#cb6-30"></a> GParamSpec <span class="op">**</span>pspecs<span class="op">);</span></span>
<span id="cb6-31"><a href="#cb6-31"></a> <span class="co">/* signals */</span></span>
<span id="cb6-32"><a href="#cb6-32"></a> <span class="dt">void</span> <span class="op">(*</span>notify<span class="op">)</span> <span class="op">(</span>GObject <span class="op">*</span>object<span class="op">,</span></span>
<span id="cb6-33"><a href="#cb6-33"></a> GParamSpec <span class="op">*</span>pspec<span class="op">);</span></span>
<span id="cb6-34"><a href="#cb6-34"></a></span>
<span id="cb6-35"><a href="#cb6-35"></a> <span class="co">/* called when done constructing */</span></span>
<span id="cb6-36"><a href="#cb6-36"></a> <span class="dt">void</span> <span class="op">(*</span>constructed<span class="op">)</span> <span class="op">(</span>GObject <span class="op">*</span>object<span class="op">);</span></span>
<span id="cb6-37"><a href="#cb6-37"></a></span>
<span id="cb6-38"><a href="#cb6-38"></a> <span class="co">/*&lt; private &gt;*/</span></span>
<span id="cb6-39"><a href="#cb6-39"></a> gsize flags<span class="op">;</span></span>
<span id="cb6-40"><a href="#cb6-40"></a></span>
<span id="cb6-41"><a href="#cb6-41"></a> gsize n_construct_properties<span class="op">;</span></span>
<span id="cb6-42"><a href="#cb6-42"></a></span>
<span id="cb6-43"><a href="#cb6-43"></a> gpointer pspecs<span class="op">;</span></span>
<span id="cb6-44"><a href="#cb6-44"></a> gsize n_pspecs<span class="op">;</span></span>
<span id="cb6-45"><a href="#cb6-45"></a></span>
<span id="cb6-46"><a href="#cb6-46"></a> <span class="co">/* padding */</span></span>
<span id="cb6-47"><a href="#cb6-47"></a> gpointer pdummy<span class="op">[</span><span class="dv">3</span><span class="op">];</span></span>
<span id="cb6-48"><a href="#cb6-48"></a><span class="op">};</span></span></code></pre></div>
<p>Theres a pointer to the function <code>dispose</code> in line
23.</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="dt">void</span> <span class="op">(*</span>dispose<span class="op">)</span> <span class="op">(</span>GObject <span class="op">*</span>object<span class="op">);</span></span></code></pre></div>
<p>The declaration is a bit complicated. The asterisk before the
identifier <code>dispose</code> means pointer. So, the pointer
<code>dispose</code> points to a function which has one parameter, which
points a GObject structure, and returns no value. In the same way, line
24 says <code>finalize</code> is a pointer to the function which has one
parameter, which points a GObject structure, and returns no value.</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="dt">void</span> <span class="op">(*</span>finalize<span class="op">)</span> <span class="op">(</span>GObject <span class="op">*</span>object<span class="op">);</span></span></code></pre></div>
<p>Look at the declaration of <code>_GObjectClass</code> so that you
would find that most of the members are pointers to functions.</p>
<ul>
<li>13: A function pointed by <code>constructor</code> is called when
the instance is generated. It completes the initialization of the
instance.</li>
<li>25: A function pointed by <code>dispose</code> is called when the
instance destructs itself. Destruction process is divided into two
phases. The first one is called disposing. In this phase, the instance
releases all the references to other instances. The second phase is
finalizing.</li>
<li>26: A function pointed by <code>finalize</code> finishes the
destruction process.</li>
<li>The other pointers point to functions which are called while the
instance lives.</li>
</ul>
<p>These functions are called class methods. The methods are open to its
descendants. But not open to the objects which are not the
descendants.</p>
<h2 id="tfetextview-class">TfeTextView class</h2>
<p>TfeTextView class is a structure and it includes all its ancestors class in it.</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"></a><span class="kw">typedef</span> _TfeTextView TfeTextView;</span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true"></a><span class="kw">struct</span> _TfeTextView {</span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true"></a> GtkTextView parent;</span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true"></a> GFile *file;</span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true"></a>};</span></code></pre></div>
<p>TfeTextView structure has GtkTextView type as the first member. In the same way, GtkTextView has its parent type (GtkWidget) as the first member. GtkWidget has its parent type (GtkInitiallyUnowned) as the first member. The structure of GtkInitiallyUnowned is the same as GObject. Therefore, TFeTextView includes GObject, GtkWidget and GtkTextView in itself.</p>
<pre><code>GObject -- GInitiallyUnowned -- GtkWidget -- GtkTextView -- TfeTextView</code></pre>
<p>The following is extracts from the source files (not exactly the same).</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="kw">struct</span> _GtkWidgetClass</span>
<span id="cb10-2"><a href="#cb10-2"></a>{</span>
<span id="cb10-3"><a href="#cb10-3"></a> GInitiallyUnownedClass parent_class;</span>
<p>TfeTextView class is a structure and it includes all its ancestors
classes in it. Therefore, classes have similar hierarchy to
instances.</p>
<pre><code>GObjectClass (GInitiallyUnownedClass) -- GtkWidgetClass -- GtkTextViewClass -- TfeTextViewClass</code></pre>
<p>The following is extracted from the source codes (not exactly the
same).</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="kw">struct</span> _GtkWidgetClass</span>
<span id="cb10-2"><a href="#cb10-2"></a><span class="op">{</span></span>
<span id="cb10-3"><a href="#cb10-3"></a> GInitiallyUnownedClass parent_class<span class="op">;</span></span>
<span id="cb10-4"><a href="#cb10-4"></a></span>
<span id="cb10-5"><a href="#cb10-5"></a> <span class="co">/*&lt; public &gt;*/</span></span>
<span id="cb10-6"><a href="#cb10-6"></a></span>
<span id="cb10-7"><a href="#cb10-7"></a> <span class="co">/* basics */</span></span>
<span id="cb10-8"><a href="#cb10-8"></a> <span class="dt">void</span> (* show) (GtkWidget *widget);</span>
<span id="cb10-9"><a href="#cb10-9"></a> <span class="dt">void</span> (* hide) (GtkWidget *widget);</span>
<span id="cb10-10"><a href="#cb10-10"></a> <span class="dt">void</span> (* map) (GtkWidget *widget);</span>
<span id="cb10-11"><a href="#cb10-11"></a> <span class="dt">void</span> (* unmap) (GtkWidget *widget);</span>
<span id="cb10-12"><a href="#cb10-12"></a> <span class="dt">void</span> (* realize) (GtkWidget *widget);</span>
<span id="cb10-13"><a href="#cb10-13"></a> <span class="dt">void</span> (* unrealize) (GtkWidget *widget);</span>
<span id="cb10-14"><a href="#cb10-14"></a> <span class="dt">void</span> (* root) (GtkWidget *widget);</span>
<span id="cb10-15"><a href="#cb10-15"></a> <span class="dt">void</span> (* unroot) (GtkWidget *widget);</span>
<span id="cb10-16"><a href="#cb10-16"></a> <span class="dt">void</span> (* size_allocate) (GtkWidget *widget,</span>
<span id="cb10-17"><a href="#cb10-17"></a> <span class="dt">int</span> width,</span>
<span id="cb10-18"><a href="#cb10-18"></a> <span class="dt">int</span> height,</span>
<span id="cb10-19"><a href="#cb10-19"></a> <span class="dt">int</span> baseline);</span>
<span id="cb10-20"><a href="#cb10-20"></a> <span class="dt">void</span> (* state_flags_changed) (GtkWidget *widget,</span>
<span id="cb10-21"><a href="#cb10-21"></a> GtkStateFlags previous_state_flags);</span>
<span id="cb10-22"><a href="#cb10-22"></a> <span class="dt">void</span> (* direction_changed) (GtkWidget *widget,</span>
<span id="cb10-23"><a href="#cb10-23"></a> GtkTextDirection previous_direction);</span>
<span id="cb10-8"><a href="#cb10-8"></a> <span class="dt">void</span> <span class="op">(*</span> show<span class="op">)</span> <span class="op">(</span>GtkWidget <span class="op">*</span>widget<span class="op">);</span></span>
<span id="cb10-9"><a href="#cb10-9"></a> <span class="dt">void</span> <span class="op">(*</span> hide<span class="op">)</span> <span class="op">(</span>GtkWidget <span class="op">*</span>widget<span class="op">);</span></span>
<span id="cb10-10"><a href="#cb10-10"></a> <span class="dt">void</span> <span class="op">(*</span> map<span class="op">)</span> <span class="op">(</span>GtkWidget <span class="op">*</span>widget<span class="op">);</span></span>
<span id="cb10-11"><a href="#cb10-11"></a> <span class="dt">void</span> <span class="op">(*</span> unmap<span class="op">)</span> <span class="op">(</span>GtkWidget <span class="op">*</span>widget<span class="op">);</span></span>
<span id="cb10-12"><a href="#cb10-12"></a> <span class="dt">void</span> <span class="op">(*</span> realize<span class="op">)</span> <span class="op">(</span>GtkWidget <span class="op">*</span>widget<span class="op">);</span></span>
<span id="cb10-13"><a href="#cb10-13"></a> <span class="dt">void</span> <span class="op">(*</span> unrealize<span class="op">)</span> <span class="op">(</span>GtkWidget <span class="op">*</span>widget<span class="op">);</span></span>
<span id="cb10-14"><a href="#cb10-14"></a> <span class="dt">void</span> <span class="op">(*</span> root<span class="op">)</span> <span class="op">(</span>GtkWidget <span class="op">*</span>widget<span class="op">);</span></span>
<span id="cb10-15"><a href="#cb10-15"></a> <span class="dt">void</span> <span class="op">(*</span> unroot<span class="op">)</span> <span class="op">(</span>GtkWidget <span class="op">*</span>widget<span class="op">);</span></span>
<span id="cb10-16"><a href="#cb10-16"></a> <span class="dt">void</span> <span class="op">(*</span> size_allocate<span class="op">)</span> <span class="op">(</span>GtkWidget <span class="op">*</span>widget<span class="op">,</span></span>
<span id="cb10-17"><a href="#cb10-17"></a> <span class="dt">int</span> width<span class="op">,</span></span>
<span id="cb10-18"><a href="#cb10-18"></a> <span class="dt">int</span> height<span class="op">,</span></span>
<span id="cb10-19"><a href="#cb10-19"></a> <span class="dt">int</span> baseline<span class="op">);</span></span>
<span id="cb10-20"><a href="#cb10-20"></a> <span class="dt">void</span> <span class="op">(*</span> state_flags_changed<span class="op">)</span> <span class="op">(</span>GtkWidget <span class="op">*</span>widget<span class="op">,</span></span>
<span id="cb10-21"><a href="#cb10-21"></a> GtkStateFlags previous_state_flags<span class="op">);</span></span>
<span id="cb10-22"><a href="#cb10-22"></a> <span class="dt">void</span> <span class="op">(*</span> direction_changed<span class="op">)</span> <span class="op">(</span>GtkWidget <span class="op">*</span>widget<span class="op">,</span></span>
<span id="cb10-23"><a href="#cb10-23"></a> GtkTextDirection previous_direction<span class="op">);</span></span>
<span id="cb10-24"><a href="#cb10-24"></a></span>
<span id="cb10-25"><a href="#cb10-25"></a> <span class="co">/* size requests */</span></span>
<span id="cb10-26"><a href="#cb10-26"></a> GtkSizeRequestMode (* get_request_mode) (GtkWidget *widget);</span>
<span id="cb10-27"><a href="#cb10-27"></a> <span class="dt">void</span> (* measure) (GtkWidget *widget,</span>
<span id="cb10-28"><a href="#cb10-28"></a> GtkOrientation orientation,</span>
<span id="cb10-29"><a href="#cb10-29"></a> <span class="dt">int</span> for_size,</span>
<span id="cb10-30"><a href="#cb10-30"></a> <span class="dt">int</span> *minimum,</span>
<span id="cb10-31"><a href="#cb10-31"></a> <span class="dt">int</span> *natural,</span>
<span id="cb10-32"><a href="#cb10-32"></a> <span class="dt">int</span> *minimum_baseline,</span>
<span id="cb10-33"><a href="#cb10-33"></a> <span class="dt">int</span> *natural_baseline);</span>
<span id="cb10-26"><a href="#cb10-26"></a> GtkSizeRequestMode <span class="op">(*</span> get_request_mode<span class="op">)</span> <span class="op">(</span>GtkWidget <span class="op">*</span>widget<span class="op">);</span></span>
<span id="cb10-27"><a href="#cb10-27"></a> <span class="dt">void</span> <span class="op">(*</span> measure<span class="op">)</span> <span class="op">(</span>GtkWidget <span class="op">*</span>widget<span class="op">,</span></span>
<span id="cb10-28"><a href="#cb10-28"></a> GtkOrientation orientation<span class="op">,</span></span>
<span id="cb10-29"><a href="#cb10-29"></a> <span class="dt">int</span> for_size<span class="op">,</span></span>
<span id="cb10-30"><a href="#cb10-30"></a> <span class="dt">int</span> <span class="op">*</span>minimum<span class="op">,</span></span>
<span id="cb10-31"><a href="#cb10-31"></a> <span class="dt">int</span> <span class="op">*</span>natural<span class="op">,</span></span>
<span id="cb10-32"><a href="#cb10-32"></a> <span class="dt">int</span> <span class="op">*</span>minimum_baseline<span class="op">,</span></span>
<span id="cb10-33"><a href="#cb10-33"></a> <span class="dt">int</span> <span class="op">*</span>natural_baseline<span class="op">);</span></span>
<span id="cb10-34"><a href="#cb10-34"></a></span>
<span id="cb10-35"><a href="#cb10-35"></a> <span class="co">/* Mnemonics */</span></span>
<span id="cb10-36"><a href="#cb10-36"></a> gboolean (* mnemonic_activate) (GtkWidget *widget,</span>
<span id="cb10-37"><a href="#cb10-37"></a> gboolean group_cycling);</span>
<span id="cb10-36"><a href="#cb10-36"></a> gboolean <span class="op">(*</span> mnemonic_activate<span class="op">)</span> <span class="op">(</span>GtkWidget <span class="op">*</span>widget<span class="op">,</span></span>
<span id="cb10-37"><a href="#cb10-37"></a> gboolean group_cycling<span class="op">);</span></span>
<span id="cb10-38"><a href="#cb10-38"></a></span>
<span id="cb10-39"><a href="#cb10-39"></a> <span class="co">/* explicit focus */</span></span>
<span id="cb10-40"><a href="#cb10-40"></a> gboolean (* grab_focus) (GtkWidget *widget);</span>
<span id="cb10-41"><a href="#cb10-41"></a> gboolean (* focus) (GtkWidget *widget,</span>
<span id="cb10-42"><a href="#cb10-42"></a> GtkDirectionType direction);</span>
<span id="cb10-43"><a href="#cb10-43"></a> <span class="dt">void</span> (* set_focus_child) (GtkWidget *widget,</span>
<span id="cb10-44"><a href="#cb10-44"></a> GtkWidget *child);</span>
<span id="cb10-40"><a href="#cb10-40"></a> gboolean <span class="op">(*</span> grab_focus<span class="op">)</span> <span class="op">(</span>GtkWidget <span class="op">*</span>widget<span class="op">);</span></span>
<span id="cb10-41"><a href="#cb10-41"></a> gboolean <span class="op">(*</span> focus<span class="op">)</span> <span class="op">(</span>GtkWidget <span class="op">*</span>widget<span class="op">,</span></span>
<span id="cb10-42"><a href="#cb10-42"></a> GtkDirectionType direction<span class="op">);</span></span>
<span id="cb10-43"><a href="#cb10-43"></a> <span class="dt">void</span> <span class="op">(*</span> set_focus_child<span class="op">)</span> <span class="op">(</span>GtkWidget <span class="op">*</span>widget<span class="op">,</span></span>
<span id="cb10-44"><a href="#cb10-44"></a> GtkWidget <span class="op">*</span>child<span class="op">);</span></span>
<span id="cb10-45"><a href="#cb10-45"></a></span>
<span id="cb10-46"><a href="#cb10-46"></a> <span class="co">/* keyboard navigation */</span></span>
<span id="cb10-47"><a href="#cb10-47"></a> <span class="dt">void</span> (* move_focus) (GtkWidget *widget,</span>
<span id="cb10-48"><a href="#cb10-48"></a> GtkDirectionType direction);</span>
<span id="cb10-49"><a href="#cb10-49"></a> gboolean (* keynav_failed) (GtkWidget *widget,</span>
<span id="cb10-50"><a href="#cb10-50"></a> GtkDirectionType direction);</span>
<span id="cb10-47"><a href="#cb10-47"></a> <span class="dt">void</span> <span class="op">(*</span> move_focus<span class="op">)</span> <span class="op">(</span>GtkWidget <span class="op">*</span>widget<span class="op">,</span></span>
<span id="cb10-48"><a href="#cb10-48"></a> GtkDirectionType direction<span class="op">);</span></span>
<span id="cb10-49"><a href="#cb10-49"></a> gboolean <span class="op">(*</span> keynav_failed<span class="op">)</span> <span class="op">(</span>GtkWidget <span class="op">*</span>widget<span class="op">,</span></span>
<span id="cb10-50"><a href="#cb10-50"></a> GtkDirectionType direction<span class="op">);</span></span>
<span id="cb10-51"><a href="#cb10-51"></a></span>
<span id="cb10-52"><a href="#cb10-52"></a> gboolean (* query_tooltip) (GtkWidget *widget,</span>
<span id="cb10-53"><a href="#cb10-53"></a> <span class="dt">int</span> x,</span>
<span id="cb10-54"><a href="#cb10-54"></a> <span class="dt">int</span> y,</span>
<span id="cb10-55"><a href="#cb10-55"></a> gboolean keyboard_tooltip,</span>
<span id="cb10-56"><a href="#cb10-56"></a> GtkTooltip *tooltip);</span>
<span id="cb10-52"><a href="#cb10-52"></a> gboolean <span class="op">(*</span> query_tooltip<span class="op">)</span> <span class="op">(</span>GtkWidget <span class="op">*</span>widget<span class="op">,</span></span>
<span id="cb10-53"><a href="#cb10-53"></a> <span class="dt">int</span> x<span class="op">,</span></span>
<span id="cb10-54"><a href="#cb10-54"></a> <span class="dt">int</span> y<span class="op">,</span></span>
<span id="cb10-55"><a href="#cb10-55"></a> gboolean keyboard_tooltip<span class="op">,</span></span>
<span id="cb10-56"><a href="#cb10-56"></a> GtkTooltip <span class="op">*</span>tooltip<span class="op">);</span></span>
<span id="cb10-57"><a href="#cb10-57"></a></span>
<span id="cb10-58"><a href="#cb10-58"></a> <span class="dt">void</span> (* compute_expand) (GtkWidget *widget,</span>
<span id="cb10-59"><a href="#cb10-59"></a> gboolean *hexpand_p,</span>
<span id="cb10-60"><a href="#cb10-60"></a> gboolean *vexpand_p);</span>
<span id="cb10-58"><a href="#cb10-58"></a> <span class="dt">void</span> <span class="op">(*</span> compute_expand<span class="op">)</span> <span class="op">(</span>GtkWidget <span class="op">*</span>widget<span class="op">,</span></span>
<span id="cb10-59"><a href="#cb10-59"></a> gboolean <span class="op">*</span>hexpand_p<span class="op">,</span></span>
<span id="cb10-60"><a href="#cb10-60"></a> gboolean <span class="op">*</span>vexpand_p<span class="op">);</span></span>
<span id="cb10-61"><a href="#cb10-61"></a></span>
<span id="cb10-62"><a href="#cb10-62"></a> <span class="dt">void</span> (* css_changed) (GtkWidget *widget,</span>
<span id="cb10-63"><a href="#cb10-63"></a> GtkCssStyleChange *change);</span>
<span id="cb10-62"><a href="#cb10-62"></a> <span class="dt">void</span> <span class="op">(*</span> css_changed<span class="op">)</span> <span class="op">(</span>GtkWidget <span class="op">*</span>widget<span class="op">,</span></span>
<span id="cb10-63"><a href="#cb10-63"></a> GtkCssStyleChange <span class="op">*</span>change<span class="op">);</span></span>
<span id="cb10-64"><a href="#cb10-64"></a></span>
<span id="cb10-65"><a href="#cb10-65"></a> <span class="dt">void</span> (* system_setting_changed) (GtkWidget *widget,</span>
<span id="cb10-66"><a href="#cb10-66"></a> GtkSystemSetting settings);</span>
<span id="cb10-65"><a href="#cb10-65"></a> <span class="dt">void</span> <span class="op">(*</span> system_setting_changed<span class="op">)</span> <span class="op">(</span>GtkWidget <span class="op">*</span>widget<span class="op">,</span></span>
<span id="cb10-66"><a href="#cb10-66"></a> GtkSystemSetting settings<span class="op">);</span></span>
<span id="cb10-67"><a href="#cb10-67"></a></span>
<span id="cb10-68"><a href="#cb10-68"></a> <span class="dt">void</span> (* snapshot) (GtkWidget *widget,</span>
<span id="cb10-69"><a href="#cb10-69"></a> GtkSnapshot *snapshot);</span>
<span id="cb10-68"><a href="#cb10-68"></a> <span class="dt">void</span> <span class="op">(*</span> snapshot<span class="op">)</span> <span class="op">(</span>GtkWidget <span class="op">*</span>widget<span class="op">,</span></span>
<span id="cb10-69"><a href="#cb10-69"></a> GtkSnapshot <span class="op">*</span>snapshot<span class="op">);</span></span>
<span id="cb10-70"><a href="#cb10-70"></a></span>
<span id="cb10-71"><a href="#cb10-71"></a> gboolean (* contains) (GtkWidget *widget,</span>
<span id="cb10-72"><a href="#cb10-72"></a> <span class="dt">double</span> x,</span>
<span id="cb10-73"><a href="#cb10-73"></a> <span class="dt">double</span> y);</span>
<span id="cb10-71"><a href="#cb10-71"></a> gboolean <span class="op">(*</span> contains<span class="op">)</span> <span class="op">(</span>GtkWidget <span class="op">*</span>widget<span class="op">,</span></span>
<span id="cb10-72"><a href="#cb10-72"></a> <span class="dt">double</span> x<span class="op">,</span></span>
<span id="cb10-73"><a href="#cb10-73"></a> <span class="dt">double</span> y<span class="op">);</span></span>
<span id="cb10-74"><a href="#cb10-74"></a></span>
<span id="cb10-75"><a href="#cb10-75"></a> <span class="co">/*&lt; private &gt;*/</span></span>
<span id="cb10-76"><a href="#cb10-76"></a></span>
<span id="cb10-77"><a href="#cb10-77"></a> GtkWidgetClassPrivate *priv;</span>
<span id="cb10-77"><a href="#cb10-77"></a> GtkWidgetClassPrivate <span class="op">*</span>priv<span class="op">;</span></span>
<span id="cb10-78"><a href="#cb10-78"></a></span>
<span id="cb10-79"><a href="#cb10-79"></a> gpointer padding[<span class="dv">8</span>];</span>
<span id="cb10-80"><a href="#cb10-80"></a>};</span>
<span id="cb10-79"><a href="#cb10-79"></a> gpointer padding<span class="op">[</span><span class="dv">8</span><span class="op">];</span></span>
<span id="cb10-80"><a href="#cb10-80"></a><span class="op">};</span></span>
<span id="cb10-81"><a href="#cb10-81"></a></span>
<span id="cb10-82"><a href="#cb10-82"></a><span class="kw">struct</span> _GtkTextViewClass</span>
<span id="cb10-83"><a href="#cb10-83"></a>{</span>
<span id="cb10-84"><a href="#cb10-84"></a> GtkWidgetClass parent_class;</span>
<span id="cb10-83"><a href="#cb10-83"></a><span class="op">{</span></span>
<span id="cb10-84"><a href="#cb10-84"></a> GtkWidgetClass parent_class<span class="op">;</span></span>
<span id="cb10-85"><a href="#cb10-85"></a></span>
<span id="cb10-86"><a href="#cb10-86"></a> <span class="co">/*&lt; public &gt;*/</span></span>
<span id="cb10-87"><a href="#cb10-87"></a></span>
<span id="cb10-88"><a href="#cb10-88"></a> <span class="dt">void</span> (* move_cursor) (GtkTextView *text_view,</span>
<span id="cb10-89"><a href="#cb10-89"></a> GtkMovementStep step,</span>
<span id="cb10-90"><a href="#cb10-90"></a> <span class="dt">int</span> count,</span>
<span id="cb10-91"><a href="#cb10-91"></a> gboolean extend_selection);</span>
<span id="cb10-92"><a href="#cb10-92"></a> <span class="dt">void</span> (* set_anchor) (GtkTextView *text_view);</span>
<span id="cb10-93"><a href="#cb10-93"></a> <span class="dt">void</span> (* insert_at_cursor) (GtkTextView *text_view,</span>
<span id="cb10-94"><a href="#cb10-94"></a> <span class="dt">const</span> <span class="dt">char</span> *str);</span>
<span id="cb10-95"><a href="#cb10-95"></a> <span class="dt">void</span> (* delete_from_cursor) (GtkTextView *text_view,</span>
<span id="cb10-96"><a href="#cb10-96"></a> GtkDeleteType type,</span>
<span id="cb10-97"><a href="#cb10-97"></a> <span class="dt">int</span> count);</span>
<span id="cb10-98"><a href="#cb10-98"></a> <span class="dt">void</span> (* backspace) (GtkTextView *text_view);</span>
<span id="cb10-99"><a href="#cb10-99"></a> <span class="dt">void</span> (* cut_clipboard) (GtkTextView *text_view);</span>
<span id="cb10-100"><a href="#cb10-100"></a> <span class="dt">void</span> (* copy_clipboard) (GtkTextView *text_view);</span>
<span id="cb10-101"><a href="#cb10-101"></a> <span class="dt">void</span> (* paste_clipboard) (GtkTextView *text_view);</span>
<span id="cb10-102"><a href="#cb10-102"></a> <span class="dt">void</span> (* toggle_overwrite) (GtkTextView *text_view);</span>
<span id="cb10-103"><a href="#cb10-103"></a> GtkTextBuffer * (* create_buffer) (GtkTextView *text_view);</span>
<span id="cb10-104"><a href="#cb10-104"></a> <span class="dt">void</span> (* snapshot_layer) (GtkTextView *text_view,</span>
<span id="cb10-105"><a href="#cb10-105"></a> GtkTextViewLayer layer,</span>
<span id="cb10-106"><a href="#cb10-106"></a> GtkSnapshot *snapshot);</span>
<span id="cb10-107"><a href="#cb10-107"></a> gboolean (* extend_selection) (GtkTextView *text_view,</span>
<span id="cb10-108"><a href="#cb10-108"></a> GtkTextExtendSelection granularity,</span>
<span id="cb10-109"><a href="#cb10-109"></a> <span class="dt">const</span> GtkTextIter *location,</span>
<span id="cb10-110"><a href="#cb10-110"></a> GtkTextIter *start,</span>
<span id="cb10-111"><a href="#cb10-111"></a> GtkTextIter *end);</span>
<span id="cb10-112"><a href="#cb10-112"></a> <span class="dt">void</span> (* insert_emoji) (GtkTextView *text_view);</span>
<span id="cb10-88"><a href="#cb10-88"></a> <span class="dt">void</span> <span class="op">(*</span> move_cursor<span class="op">)</span> <span class="op">(</span>GtkTextView <span class="op">*</span>text_view<span class="op">,</span></span>
<span id="cb10-89"><a href="#cb10-89"></a> GtkMovementStep step<span class="op">,</span></span>
<span id="cb10-90"><a href="#cb10-90"></a> <span class="dt">int</span> count<span class="op">,</span></span>
<span id="cb10-91"><a href="#cb10-91"></a> gboolean extend_selection<span class="op">);</span></span>
<span id="cb10-92"><a href="#cb10-92"></a> <span class="dt">void</span> <span class="op">(*</span> set_anchor<span class="op">)</span> <span class="op">(</span>GtkTextView <span class="op">*</span>text_view<span class="op">);</span></span>
<span id="cb10-93"><a href="#cb10-93"></a> <span class="dt">void</span> <span class="op">(*</span> insert_at_cursor<span class="op">)</span> <span class="op">(</span>GtkTextView <span class="op">*</span>text_view<span class="op">,</span></span>
<span id="cb10-94"><a href="#cb10-94"></a> <span class="dt">const</span> <span class="dt">char</span> <span class="op">*</span>str<span class="op">);</span></span>
<span id="cb10-95"><a href="#cb10-95"></a> <span class="dt">void</span> <span class="op">(*</span> delete_from_cursor<span class="op">)</span> <span class="op">(</span>GtkTextView <span class="op">*</span>text_view<span class="op">,</span></span>
<span id="cb10-96"><a href="#cb10-96"></a> GtkDeleteType type<span class="op">,</span></span>
<span id="cb10-97"><a href="#cb10-97"></a> <span class="dt">int</span> count<span class="op">);</span></span>
<span id="cb10-98"><a href="#cb10-98"></a> <span class="dt">void</span> <span class="op">(*</span> backspace<span class="op">)</span> <span class="op">(</span>GtkTextView <span class="op">*</span>text_view<span class="op">);</span></span>
<span id="cb10-99"><a href="#cb10-99"></a> <span class="dt">void</span> <span class="op">(*</span> cut_clipboard<span class="op">)</span> <span class="op">(</span>GtkTextView <span class="op">*</span>text_view<span class="op">);</span></span>
<span id="cb10-100"><a href="#cb10-100"></a> <span class="dt">void</span> <span class="op">(*</span> copy_clipboard<span class="op">)</span> <span class="op">(</span>GtkTextView <span class="op">*</span>text_view<span class="op">);</span></span>
<span id="cb10-101"><a href="#cb10-101"></a> <span class="dt">void</span> <span class="op">(*</span> paste_clipboard<span class="op">)</span> <span class="op">(</span>GtkTextView <span class="op">*</span>text_view<span class="op">);</span></span>
<span id="cb10-102"><a href="#cb10-102"></a> <span class="dt">void</span> <span class="op">(*</span> toggle_overwrite<span class="op">)</span> <span class="op">(</span>GtkTextView <span class="op">*</span>text_view<span class="op">);</span></span>
<span id="cb10-103"><a href="#cb10-103"></a> GtkTextBuffer <span class="op">*</span> <span class="op">(*</span> create_buffer<span class="op">)</span> <span class="op">(</span>GtkTextView <span class="op">*</span>text_view<span class="op">);</span></span>
<span id="cb10-104"><a href="#cb10-104"></a> <span class="dt">void</span> <span class="op">(*</span> snapshot_layer<span class="op">)</span> <span class="op">(</span>GtkTextView <span class="op">*</span>text_view<span class="op">,</span></span>
<span id="cb10-105"><a href="#cb10-105"></a> GtkTextViewLayer layer<span class="op">,</span></span>
<span id="cb10-106"><a href="#cb10-106"></a> GtkSnapshot <span class="op">*</span>snapshot<span class="op">);</span></span>
<span id="cb10-107"><a href="#cb10-107"></a> gboolean <span class="op">(*</span> extend_selection<span class="op">)</span> <span class="op">(</span>GtkTextView <span class="op">*</span>text_view<span class="op">,</span></span>
<span id="cb10-108"><a href="#cb10-108"></a> GtkTextExtendSelection granularity<span class="op">,</span></span>
<span id="cb10-109"><a href="#cb10-109"></a> <span class="dt">const</span> GtkTextIter <span class="op">*</span>location<span class="op">,</span></span>
<span id="cb10-110"><a href="#cb10-110"></a> GtkTextIter <span class="op">*</span>start<span class="op">,</span></span>
<span id="cb10-111"><a href="#cb10-111"></a> GtkTextIter <span class="op">*</span>end<span class="op">);</span></span>
<span id="cb10-112"><a href="#cb10-112"></a> <span class="dt">void</span> <span class="op">(*</span> insert_emoji<span class="op">)</span> <span class="op">(</span>GtkTextView <span class="op">*</span>text_view<span class="op">);</span></span>
<span id="cb10-113"><a href="#cb10-113"></a></span>
<span id="cb10-114"><a href="#cb10-114"></a> <span class="co">/*&lt; private &gt;*/</span></span>
<span id="cb10-115"><a href="#cb10-115"></a></span>
<span id="cb10-116"><a href="#cb10-116"></a> gpointer padding[<span class="dv">8</span>];</span>
<span id="cb10-117"><a href="#cb10-117"></a>};</span>
<span id="cb10-116"><a href="#cb10-116"></a> gpointer padding<span class="op">[</span><span class="dv">8</span><span class="op">];</span></span>
<span id="cb10-117"><a href="#cb10-117"></a><span class="op">};</span></span>
<span id="cb10-118"><a href="#cb10-118"></a></span>
<span id="cb10-119"><a href="#cb10-119"></a><span class="co">/* The following definition is generated by the macro G_DECLARE_FINAL_TYPE */</span></span>
<span id="cb10-120"><a href="#cb10-120"></a><span class="kw">typedef</span> <span class="kw">struct</span> {</span>
<span id="cb10-121"><a href="#cb10-121"></a> GtkTextView parent_class;</span>
<span id="cb10-122"><a href="#cb10-122"></a>} TfeTextViewClass;</span></code></pre></div>
<span id="cb10-120"><a href="#cb10-120"></a><span class="kw">typedef</span> <span class="kw">struct</span> <span class="op">{</span></span>
<span id="cb10-121"><a href="#cb10-121"></a> GtkTextView parent_class<span class="op">;</span></span>
<span id="cb10-122"><a href="#cb10-122"></a><span class="op">}</span> TfeTextViewClass<span class="op">;</span></span></code></pre></div>
<ul>
<li>120-122: This three lines are generated by the macro <code>G_DECLARE_FINAL_TYPE</code>. So, they are not written in either <code>tfe_text_view.h</code> or <code>tfe_text_view.c</code>.</li>
<li>3, 84, 121: Each derived class puts its parent class at the first member of its structure. It is the same as instance structures.</li>
<li>Class members in ancestors are open to the descendant class. So, they can be changed in <code>tfe_text_view_class_init</code> function. For example, the <code>dispose</code> pointer in GObjectClass will be overridden later in <code>tfe_text_view_class_init</code>. (Override is an object oriented programming terminology. Override is rewriting ancestors class methods in the descendant class.)</li>
<li>Some class methods are often overridden. <code>set_property</code>, <code>get_property</code>, <code>dispose</code>, <code>finalize</code> and <code>constructed</code> are such methods.</li>
<li>120-122: This three lines are generated by the macro
<code>G_DECLARE_FINAL_TYPE</code>. So, they are not written in either
<code>tfe_text_view.h</code> or <code>tfe_text_view.c</code>.</li>
<li>3, 84, 121: Each class has its parent class at the first member of
its structure. It is the same as instance structures.</li>
<li>Class members in ancestors are open to the descendant class. So,
they can be changed in <code>tfe_text_view_class_init</code> function.
For example, the <code>finalize</code> pointer in GObjectClass will be
overridden later in <code>tfe_text_view_class_init</code>. (Override is
an object oriented programming terminology. Override is rewriting
ancestors class methods in the descendant class.)</li>
<li>Some class methods are often overridden. <code>set_property</code>,
<code>get_property</code>, <code>dispose</code>, <code>finalize</code>
and <code>constructed</code> are such methods.</li>
</ul>
<p>TfeTextViewClass includes its ancestors class in it. It is illustrated in the following diagram.</p>
<p>TfeTextViewClass includes its ancestors class in it. It is
illustrated in the following diagram.</p>
<figure>
<img src="image/TfeTextViewClass.png" alt="" /><figcaption>The structure of TfeTextView Class</figcaption>
<img src="image/TfeTextViewClass.png"
alt="The structure of TfeTextView Class" />
<figcaption aria-hidden="true">The structure of TfeTextView
Class</figcaption>
</figure>
<h2 id="destruction-of-tfetextview">Destruction of TfeTextView</h2>
<p>Every Object derived from GObject has a reference count. If an object A refers to an object B, then A keeps a pointer to B in A and at the same time increases the reference count of B by one with the function <code>g_object_ref (B)</code>. If A doesnt need B any longer, then A discards the pointer to B (usually it is done by assigning NULL to the pointer) and decreases the reference count of B by one with the function <code>g_object_unref (B)</code>.</p>
<p>If two objects A and B refer to C, then the reference count of C is two. If A no longer needs C, A discards the pointer to C and decreases the reference count in C by one. Now the reference count of C is one. In the same way, if B no longer needs C, B discards the pointer to C and decreases the reference count in C by one. At this moment, no object refers to C and the reference count of C is zero. This means C is no longer useful. Then C destructs itself and finally the memories allocated to C is freed.</p>
<p>Every Object derived from GObject has a reference count. If an object
A refers to an object B, then A keeps a pointer to B in A and at the
same time increases the reference count of B by one with the function
<code>g_object_ref (B)</code>. If A doesnt need B any longer, then A
discards the pointer to B (usually it is done by assigning NULL to the
pointer) and decreases the reference count of B by one with the function
<code>g_object_unref (B)</code>.</p>
<p>If two objects A and B refer to C, then the reference count of C is
two. If A no longer needs C, A discards the pointer to C and decreases
the reference count in C by one. Now the reference count of C is one. In
the same way, if B no longer needs C, B discards the pointer to C and
decreases the reference count in C by one. At this moment, no object
refers to C and the reference count of C is zero. This means C is no
longer useful. Then C destructs itself and finally the memories
allocated to C is freed.</p>
<figure>
<img src="image/refcount.png" alt="" /><figcaption>Reference count of B</figcaption>
<img src="image/refcount.png" alt="Reference count of B" />
<figcaption aria-hidden="true">Reference count of B</figcaption>
</figure>
<p>The idea above is based on an assumption that an object referred by nothing has reference count of zero. When the reference count drops to zero, the object starts its destruction process. The destruction process is split into two phases: disposing and finalizing. In the disposing process, the object invokes the function pointed by <code>dispose</code> in its class to release all references to other objects. In the finalizing process, it invokes the function pointed by <code>finalize</code> in its class to complete the destruction process. These functions are also called handlers or methods. For example, dispose handler or dispose method.</p>
<p>In the destruction process of TfeTextView, the reference count of widgets related to TfeTextView is automatically decreased. But GFile pointed by <code>tv-&gt;file</code> needs to decrease its reference count by one. You must write the code in the dispose handler <code>tfe_text_view_dispose</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>tfe_text_view_dispose (GObject *gobject) {</span>
<span id="cb11-3"><a href="#cb11-3"></a> TfeTextView *tv = TFE_TEXT_VIEW (gobject);</span>
<p>The idea above is based on an assumption that an object referred by
nothing has reference count of zero. When the reference count drops to
zero, the object starts its destruction process. The destruction process
is split into two phases: disposing and finalizing. In the disposing
process, the object invokes the function pointed by <code>dispose</code>
in its class to release all references to other instances. After that,
it invokes the function pointed by <code>finalize</code> in its class to
complete the destruction process. For example, dispose handler or
dispose method.</p>
<p>In the destruction process, TfeTextView needs to unref the GFile
pointed by <code>tv-&gt;file</code>. You must write the dispose handler
<code>tfe_text_view_dispose</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>tfe_text_view_dispose <span class="op">(</span>GObject <span class="op">*</span>gobject<span class="op">)</span> <span class="op">{</span></span>
<span id="cb11-3"><a href="#cb11-3"></a> TfeTextView <span class="op">*</span>tv <span class="op">=</span> TFE_TEXT_VIEW <span class="op">(</span>gobject<span class="op">);</span></span>
<span id="cb11-4"><a href="#cb11-4"></a></span>
<span id="cb11-5"><a href="#cb11-5"></a> <span class="cf">if</span> (G_IS_FILE (tv-&gt;file))</span>
<span id="cb11-6"><a href="#cb11-6"></a> g_clear_object (&amp;tv-&gt;file);</span>
<span id="cb11-5"><a href="#cb11-5"></a> <span class="cf">if</span> <span class="op">(</span>G_IS_FILE <span class="op">(</span>tv<span class="op">-&gt;</span>file<span class="op">))</span></span>
<span id="cb11-6"><a href="#cb11-6"></a> g_clear_object <span class="op">(&amp;</span>tv<span class="op">-&gt;</span>file<span class="op">);</span></span>
<span id="cb11-7"><a href="#cb11-7"></a></span>
<span id="cb11-8"><a href="#cb11-8"></a> G_OBJECT_CLASS (tfe_text_view_parent_class)-&gt;dispose (gobject);</span>
<span id="cb11-9"><a href="#cb11-9"></a>}</span></code></pre></div>
<span id="cb11-8"><a href="#cb11-8"></a> G_OBJECT_CLASS <span class="op">(</span>tfe_text_view_parent_class<span class="op">)-&gt;</span>dispose <span class="op">(</span>gobject<span class="op">);</span></span>
<span id="cb11-9"><a href="#cb11-9"></a><span class="op">}</span></span></code></pre></div>
<ul>
<li>5,6: If <code>tv-&gt;file</code> points a GFile, decrease its reference count. <code>g_clear_object</code> decreases the reference count and assigns NULL to <code>tv-&gt;file</code>. In dispose handlers, we usually use <code>g_clear_object</code> rather than <code>g_object_unref</code>.</li>
<li>8: invokes parents dispose handler. (This will be explained later.)</li>
<li>5,6: If <code>tv-&gt;file</code> points a GFile, decrease its
reference count. <code>g_clear_object</code> decreases the reference
count and assigns NULL to <code>tv-&gt;file</code>. In dispose handlers,
we usually use <code>g_clear_object</code> rather than
<code>g_object_unref</code>.</li>
<li>8: invokes parents dispose handler. (This will be explained
later.)</li>
</ul>
<p>In the disposing process, the object uses the pointer in its class to call the handler. Therefore, <code>tfe_text_view_dispose</code> needs to be registered in the class when the TfeTextView class is initialized. The function <code>tfe_text_view_class_init</code> is the class initialization function and it is declared in the replacement produced by <code>G_DEFINE_TYPE</code> macro.</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true"></a>tfe_text_view_class_init (TfeTextViewClass *class) {</span>
<span id="cb12-3"><a href="#cb12-3" aria-hidden="true"></a> GObjectClass *object_class = G_OBJECT_CLASS (class);</span>
<span id="cb12-4"><a href="#cb12-4" aria-hidden="true"></a></span>
<span id="cb12-5"><a href="#cb12-5" aria-hidden="true"></a> object_class-&gt;dispose = tfe_text_view_dispose;</span>
<span id="cb12-6"><a href="#cb12-6" aria-hidden="true"></a></span>
<span id="cb12-7"><a href="#cb12-7" aria-hidden="true"></a>}</span></code></pre></div>
<p>Each ancestors class has been created before TfeTextViewClass is created. Therefore, there are four classes and each class has a pointer to each dispose handler. Look at the following diagram. There are four classes GObjectClass (GInitiallyUnownedClass), GtkWidgetClass, GtkTextViewClass and TfeTextViewClass. Each class has its own dispose handler <code>dh1</code>, <code>dh2</code>, <code>dh3</code> and <code>tfe_text_view_dispose</code>.</p>
<p>In the disposing process, the object uses the pointer in its class to
call the handler. Therefore, <code>tfe_text_view_dispose</code> needs to
be registered in the class when the TfeTextView class is initialized.
The function <code>tfe_text_view_class_init</code> is the class
initialization function and it is declared in the
<code>G_DEFINE_TYPE</code> macro expansion.</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a>tfe_text_view_class_init <span class="op">(</span>TfeTextViewClass <span class="op">*</span>class<span class="op">)</span> <span class="op">{</span></span>
<span id="cb12-3"><a href="#cb12-3" aria-hidden="true" tabindex="-1"></a> GObjectClass <span class="op">*</span>object_class <span class="op">=</span> G_OBJECT_CLASS <span class="op">(</span>class<span class="op">);</span></span>
<span id="cb12-4"><a href="#cb12-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-5"><a href="#cb12-5" aria-hidden="true" tabindex="-1"></a> object_class<span class="op">-&gt;</span>dispose <span class="op">=</span> tfe_text_view_dispose<span class="op">;</span></span>
<span id="cb12-6"><a href="#cb12-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-7"><a href="#cb12-7" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>Each ancestors class has been created before TfeTextViewClass is
created. Therefore, there are four classes and each class has a pointer
to each dispose handler. Look at the following diagram. There are four
classes GObjectClass (GInitiallyUnownedClass), GtkWidgetClass,
GtkTextViewClass and TfeTextViewClass. Each class has its own dispose
handler <code>dh1</code>, <code>dh2</code>, <code>dh3</code> and
<code>tfe_text_view_dispose</code>.</p>
<figure>
<img src="image/dispose_handler.png" alt="" /><figcaption>dispose handlers</figcaption>
<img src="image/dispose_handler.png" alt="dispose handlers" />
<figcaption aria-hidden="true">dispose handlers</figcaption>
</figure>
<p>Now, look at the <code>tfe_text_view_dispose</code> program above. It first releases the reference to GFile object pointed by <code>tv-&gt;file</code>. Then it invokes its parents dispose handler in line 8.</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"></a>G_OBJECT_CLASS (tfe_text_view_parent_class)-&gt;dispose (gobject);</span></code></pre></div>
<p><code>tfe_text_view_parent_class</code>,which is made by <code>G_DEFINE_TYPE</code> macro, is a pointer that points the parent object class. Therefore, <code>G_OBJECT_CLASS (tfe_text_view_parent_class)-&gt;dispose</code> points the handler <code>dh3</code> in the diagram above. And <code>gobject</code> is a pointer to TfeTextView instance which is casted as a GObject instance. <code>dh3</code> releases all the references to objects in the GtkTextView part (it is actually the private area pointed by <code>prev</code>) in TfeTextView instance. After that, <code>dh3</code> calls <code>dh2</code>, and <code>dh2</code> calls <code>dh1</code>. Finally all the references are released.</p>
<p>Now, look at the <code>tfe_text_view_dispose</code> program above. It
first releases the reference to GFile object pointed by
<code>tv-&gt;file</code>. Then it invokes its parents dispose handler
in line 8.</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>G_OBJECT_CLASS <span class="op">(</span>tfe_text_view_parent_class<span class="op">)-&gt;</span>dispose <span class="op">(</span>gobject<span class="op">);</span></span></code></pre></div>
<p>A variable <code>tfe_text_view_parent_class</code>, which is made by
<code>G_DEFINE_TYPE</code> macro, is a pointer that points the parent
object class. The variable <code>gobject</code> is a pointer to
TfeTextView instance which is casted as a GObject instance. Therefore,
<code>G_OBJECT_CLASS (tfe_text_view_parent_class)-&gt;dispose</code>
points the handler <code>dh3</code> in the diagram above. The statement
<code>G_OBJECT_CLASS (tfe_text_view_parent_class)-&gt;dispose (gobject)</code>
is the same as <code>dh3 (gobject)</code>, which means it releases all
the reference to the other instances in the GtkTextViewPrivate in the
TfeTextView instance. After that, <code>dh3</code> calls
<code>dh2</code>, and <code>dh2</code> calls <code>dh1</code>. Finally
all the references are released.</p>
</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>

View file

@ -5,7 +5,7 @@
<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>Gtk4 tutorial</title>
<title>GTK 4 tutorial</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
@ -113,117 +113,184 @@
</nav>
<h1 id="signals">Signals</h1>
<h2 id="signals-1">Signals</h2>
<p>In Gtk programming, each object is encapsulated. And it is not recommended to use global variables because they tend to make the program complicated. So, we need something to communicate between objects. There are two ways to do so.</p>
<p>Each object is encapsulated in Gtk programming. And it is not
recommended to use global variables because they are prone to make the
program complicated. So, we need something to communicate between
objects. There are two ways to do so.</p>
<ul>
<li>Functions. For example, <code>tb = gtk_text_view_get_buffer (tv)</code>. The caller requests <code>tv</code> to give <code>tb</code>, which is a GtkTextBuffer instance connected to <code>tv</code> to the caller.</li>
<li>Signals. For example, <code>activate</code> signal on GApplication object. When the application is activated, the signal is emitted. Then the handler, which has been connected to the signal, is invoked.</li>
<li>Functions. For example,
<code>tb = gtk_text_view_get_buffer (tv)</code>. The caller requests
<code>tv</code> to give <code>tb</code>, which is a GtkTextBuffer
instance connected to <code>tv</code>.</li>
<li>Signals. For example, <code>activate</code> signal on GApplication
object. When the application is activated, the signal is emitted. Then
the handler, which has been connected to the signal, is invoked.</li>
</ul>
<p>The caller of the function or the handler connected to the signal is usually out of the object. One of the difference between these two is that the object is active or passive. In functions the object passively responds to the caller. In signals the object actively sends a signal to the handler.</p>
<p>The caller of the function or the handler connected to the signal is
usually out of the object. One of the difference between these two is
that the object is active or passive. In functions the object passively
responds to the caller. In signals the object actively sends a signal to
the handler.</p>
<p>GObject signals are registered, connected and emitted.</p>
<ol type="1">
<li>Signals are registered with the object type on which they are emitted. The registration is done usually when the object class is initialized.</li>
<li>Signals are connected to handlers by <code>g_connect_signal</code> or its family functions. The connection is usually done out of the object.</li>
<li>When Signals are emitted, the connected handlers are invoked. Signal is emitted on the instance of the object.</li>
<li>Signals are registered with the object type on which they are
emitted. The registration is done usually when the object class is
initialized.</li>
<li>Signals are connected to handlers by <code>g_connect_signal</code>
or its family functions. The connection is usually done out of the
object.</li>
<li>When Signals are emitted, the connected handlers are invoked.
Signals are emitted on the instance of the object.</li>
</ol>
<h2 id="signal-registration">Signal registration</h2>
<p>In TfeTextView, two signals are registered.</p>
<ul>
<li>“change-file” signal. This signal is emitted when <code>tv-&gt;file</code> is changed.</li>
<li>“open-response” signal. <code>tfe_text_view_open</code> function is not able to return the status because it uses GtkFileChooserDialog. This signal is emitted instead of the return value of the function.</li>
<li>“change-file” signal. This signal is emitted when
<code>tv-&gt;file</code> is changed.</li>
<li>“open-response” signal. <code>tfe_text_view_open</code> function is
not able to return the status because it uses GtkFileChooserDialog. This
signal is emitted instead of the return value of the function.</li>
</ul>
<p>A static variable or array is used to store the signal ID. A static array is used to register two or more signals.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true"></a><span class="kw">enum</span> {</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true"></a> CHANGE_FILE,</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true"></a> OPEN_RESPONSE,</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true"></a> NUMBER_OF_SIGNALS</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true"></a>};</span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true"></a></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true"></a><span class="dt">static</span> guint tfe_text_view_signals[NUMBER_OF_SIGNALS];</span></code></pre></div>
<p>A static variable or array is used to store signal ID.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">enum</span> <span class="op">{</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a> CHANGE_FILE<span class="op">,</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a> OPEN_RESPONSE<span class="op">,</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a> NUMBER_OF_SIGNALS</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="op">};</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="dt">static</span> guint tfe_text_view_signals<span class="op">[</span>NUMBER_OF_SIGNALS<span class="op">];</span></span></code></pre></div>
<p>Signals are registered in the class initialization function.</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb2-1"><a href="#cb2-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb2-2"><a href="#cb2-2"></a>tfe_text_view_class_init (TfeTextViewClass *class) {</span>
<span id="cb2-3"><a href="#cb2-3"></a> GObjectClass *object_class = G_OBJECT_CLASS (class);</span>
<div class="sourceCode" id="cb2"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb2-1"><a href="#cb2-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb2-2"><a href="#cb2-2"></a>tfe_text_view_class_init <span class="op">(</span>TfeTextViewClass <span class="op">*</span>class<span class="op">)</span> <span class="op">{</span></span>
<span id="cb2-3"><a href="#cb2-3"></a> GObjectClass <span class="op">*</span>object_class <span class="op">=</span> G_OBJECT_CLASS <span class="op">(</span>class<span class="op">);</span></span>
<span id="cb2-4"><a href="#cb2-4"></a></span>
<span id="cb2-5"><a href="#cb2-5"></a> object_class-&gt;dispose = tfe_text_view_dispose;</span>
<span id="cb2-6"><a href="#cb2-6"></a> tfe_text_view_signals[CHANGE_FILE] = g_signal_new (<span class="st">&quot;change-file&quot;</span>,</span>
<span id="cb2-7"><a href="#cb2-7"></a> G_TYPE_FROM_CLASS (class),</span>
<span id="cb2-8"><a href="#cb2-8"></a> G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,</span>
<span id="cb2-9"><a href="#cb2-9"></a> <span class="dv">0</span> <span class="co">/* class offset */</span>,</span>
<span id="cb2-10"><a href="#cb2-10"></a> NULL <span class="co">/* accumulator */</span>,</span>
<span id="cb2-11"><a href="#cb2-11"></a> NULL <span class="co">/* accumulator data */</span>,</span>
<span id="cb2-12"><a href="#cb2-12"></a> NULL <span class="co">/* C marshaller */</span>,</span>
<span id="cb2-13"><a href="#cb2-13"></a> G_TYPE_NONE <span class="co">/* return_type */</span>,</span>
<span id="cb2-5"><a href="#cb2-5"></a> object_class<span class="op">-&gt;</span>dispose <span class="op">=</span> tfe_text_view_dispose<span class="op">;</span></span>
<span id="cb2-6"><a href="#cb2-6"></a> tfe_text_view_signals<span class="op">[</span>CHANGE_FILE<span class="op">]</span> <span class="op">=</span> g_signal_new <span class="op">(</span><span class="st">&quot;change-file&quot;</span><span class="op">,</span></span>
<span id="cb2-7"><a href="#cb2-7"></a> G_TYPE_FROM_CLASS <span class="op">(</span>class<span class="op">),</span></span>
<span id="cb2-8"><a href="#cb2-8"></a> G_SIGNAL_RUN_LAST <span class="op">|</span> G_SIGNAL_NO_RECURSE <span class="op">|</span> G_SIGNAL_NO_HOOKS<span class="op">,</span></span>
<span id="cb2-9"><a href="#cb2-9"></a> <span class="dv">0</span> <span class="co">/* class offset */</span><span class="op">,</span></span>
<span id="cb2-10"><a href="#cb2-10"></a> NULL <span class="co">/* accumulator */</span><span class="op">,</span></span>
<span id="cb2-11"><a href="#cb2-11"></a> NULL <span class="co">/* accumulator data */</span><span class="op">,</span></span>
<span id="cb2-12"><a href="#cb2-12"></a> NULL <span class="co">/* C marshaller */</span><span class="op">,</span></span>
<span id="cb2-13"><a href="#cb2-13"></a> G_TYPE_NONE <span class="co">/* return_type */</span><span class="op">,</span></span>
<span id="cb2-14"><a href="#cb2-14"></a> <span class="dv">0</span> <span class="co">/* n_params */</span></span>
<span id="cb2-15"><a href="#cb2-15"></a> );</span>
<span id="cb2-16"><a href="#cb2-16"></a> tfe_text_view_signals[OPEN_RESPONSE] = g_signal_new (<span class="st">&quot;open-response&quot;</span>,</span>
<span id="cb2-17"><a href="#cb2-17"></a> G_TYPE_FROM_CLASS (class),</span>
<span id="cb2-18"><a href="#cb2-18"></a> G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,</span>
<span id="cb2-19"><a href="#cb2-19"></a> <span class="dv">0</span> <span class="co">/* class offset */</span>,</span>
<span id="cb2-20"><a href="#cb2-20"></a> NULL <span class="co">/* accumulator */</span>,</span>
<span id="cb2-21"><a href="#cb2-21"></a> NULL <span class="co">/* accumulator data */</span>,</span>
<span id="cb2-22"><a href="#cb2-22"></a> NULL <span class="co">/* C marshaller */</span>,</span>
<span id="cb2-23"><a href="#cb2-23"></a> G_TYPE_NONE <span class="co">/* return_type */</span>,</span>
<span id="cb2-24"><a href="#cb2-24"></a> <span class="dv">1</span> <span class="co">/* n_params */</span>,</span>
<span id="cb2-15"><a href="#cb2-15"></a> <span class="op">);</span></span>
<span id="cb2-16"><a href="#cb2-16"></a> tfe_text_view_signals<span class="op">[</span>OPEN_RESPONSE<span class="op">]</span> <span class="op">=</span> g_signal_new <span class="op">(</span><span class="st">&quot;open-response&quot;</span><span class="op">,</span></span>
<span id="cb2-17"><a href="#cb2-17"></a> G_TYPE_FROM_CLASS <span class="op">(</span>class<span class="op">),</span></span>
<span id="cb2-18"><a href="#cb2-18"></a> G_SIGNAL_RUN_LAST <span class="op">|</span> G_SIGNAL_NO_RECURSE <span class="op">|</span> G_SIGNAL_NO_HOOKS<span class="op">,</span></span>
<span id="cb2-19"><a href="#cb2-19"></a> <span class="dv">0</span> <span class="co">/* class offset */</span><span class="op">,</span></span>
<span id="cb2-20"><a href="#cb2-20"></a> NULL <span class="co">/* accumulator */</span><span class="op">,</span></span>
<span id="cb2-21"><a href="#cb2-21"></a> NULL <span class="co">/* accumulator data */</span><span class="op">,</span></span>
<span id="cb2-22"><a href="#cb2-22"></a> NULL <span class="co">/* C marshaller */</span><span class="op">,</span></span>
<span id="cb2-23"><a href="#cb2-23"></a> G_TYPE_NONE <span class="co">/* return_type */</span><span class="op">,</span></span>
<span id="cb2-24"><a href="#cb2-24"></a> <span class="dv">1</span> <span class="co">/* n_params */</span><span class="op">,</span></span>
<span id="cb2-25"><a href="#cb2-25"></a> G_TYPE_INT</span>
<span id="cb2-26"><a href="#cb2-26"></a> );</span>
<span id="cb2-27"><a href="#cb2-27"></a>}</span></code></pre></div>
<span id="cb2-26"><a href="#cb2-26"></a> <span class="op">);</span></span>
<span id="cb2-27"><a href="#cb2-27"></a><span class="op">}</span></span></code></pre></div>
<ul>
<li>6-15: Registers “change-file” signal. <code>g_signal_new</code> function is used. The signal “change-file” has no default handler (object method handler). You usually dont need to set a default handler. If you need it, use <code>g_signal_new_class_handler</code> function. See <a href="https://docs.gtk.org/gobject/func.signal_new_class_handler.html">GObject API Reference, g_signal_new_class_handler</a> for further information.</li>
<li>The return value of <code>g_signal_new</code> is the signal id. The type of signal id is guint, which is the same as unsigned int. It is used in the function <code>g_signal_emit</code>.</li>
<li>16-26: Registers “open-response” signal. This signal has a parameter.</li>
<li>24: Number of the parameters. “open-response” signal has one parameter.</li>
<li>25: The type of the parameter. <code>G_TYPE_INT</code> is a type of integer. Such fundamental types are described in <a href="https://developer-old.gnome.org/gobject/stable/gobject-Type-Information.html">GObject reference manual</a>.</li>
<li>6-15: Registers “change-file” signal. <code>g_signal_new</code>
function is used. The signal “change-file” has no default handler
(object method handler) so the offset (the line 9) is set to zero. You
usually dont need a default handler. If you need it, use
<code>g_signal_new_class_handler</code> function instead of
<code>g_signal_new</code>. See <a
href="https://docs.gtk.org/gobject/func.signal_new_class_handler.html">GObject
API Reference</a> for further information.</li>
<li>The return value of <code>g_signal_new</code> is the signal id. The
type of signal id is guint, which is the same as unsigned int. It is
used in the function <code>g_signal_emit</code>.</li>
<li>16-26: Registers “open-response” signal. This signal has a
parameter.</li>
<li>24: Number of the parameters. “open-response” signal has one
parameter.</li>
<li>25: The type of the parameter. <code>G_TYPE_INT</code> is a type of
integer. Such fundamental types are described in <a
href="https://developer-old.gnome.org/gobject/stable/gobject-Type-Information.html">GObject
reference manual</a>.</li>
</ul>
<p>The handlers are declared as follows.</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"></a><span class="co">/* &quot;change-file&quot; signal handler */</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true"></a><span class="dt">void</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true"></a>user_function (TfeTextView *tv,</span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true"></a> gpointer user_data)</span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true"></a></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true"></a><span class="co">/* &quot;open-response&quot; signal handler */</span></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true"></a><span class="dt">void</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true"></a>user_function (TfeTextView *tv,</span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true"></a> TfeTextViewOpenResponseType response-id,</span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true"></a> gpointer user_data)</span></code></pre></div>
<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="co">/* &quot;change-file&quot; signal handler */</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a>user_function <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">,</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a> gpointer user_data<span class="op">)</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="co">/* &quot;open-response&quot; signal handler */</span></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a>user_function <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">,</span></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a> TfeTextViewOpenResponseType response<span class="op">-</span>id<span class="op">,</span></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a> gpointer user_data<span class="op">)</span></span></code></pre></div>
<ul>
<li>Because “change-file” signal doesnt have parameter, the handlers parameters are a TfeTextView instance and user data.</li>
<li>Because “open-response” signal has one parameter, the handlers parameters are a TfeTextView instance, the signals parameter and user data.</li>
<li><code>tv</code> is the object instance on which the signal is emitted.</li>
<li><code>user_data</code> comes from the fourth argument of <code>g_signal_connect</code>.</li>
<li><code>parameter</code> comes from the fourth argument of <code>g_signal_emit</code>.</li>
<li>Because “change-file” signal doesnt have parameter, the handlers
parameters are a TfeTextView instance and user data.</li>
<li>Because “open-response” signal has one parameter, the handlers
parameters are a TfeTextView instance, the signals parameter and user
data.</li>
<li><code>tv</code> is the object instance on which the signal is
emitted.</li>
<li><code>user_data</code> comes from the fourth argument of
<code>g_signal_connect</code>.</li>
<li><code>parameter</code> comes from the fourth argument of
<code>g_signal_emit</code>.</li>
</ul>
<p>The values of the parameter is defined in <code>tfetextview.h</code> because they are public.</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true"></a><span class="co">/* &quot;open-response&quot; signal response */</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true"></a><span class="kw">enum</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true"></a>{</span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true"></a> TFE_OPEN_RESPONSE_SUCCESS,</span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true"></a> TFE_OPEN_RESPONSE_CANCEL,</span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true"></a> TFE_OPEN_RESPONSE_ERROR</span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true"></a>};</span></code></pre></div>
<p>The values of the parameter is defined in <code>tfetextview.h</code>
because they are public.</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="co">/* &quot;open-response&quot; signal response */</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="kw">enum</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="op">{</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a> TFE_OPEN_RESPONSE_SUCCESS<span class="op">,</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a> TFE_OPEN_RESPONSE_CANCEL<span class="op">,</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a> TFE_OPEN_RESPONSE_ERROR</span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a><span class="op">};</span></span></code></pre></div>
<ul>
<li>The parameter is set to <code>TFE_OPEN_RESPONSE_SUCCESS</code> when <code>tfe_text_view_open</code> has successfully opened a file and read it.</li>
<li>The parameter is set to <code>TFE_OPEN_RESPONSE_CANCEL</code> when the user has canceled.</li>
<li>The parameter is set to <code>TFE_OPEN_RESPONSE_ERROR</code> when an error has occurred.</li>
<li>The parameter is set to <code>TFE_OPEN_RESPONSE_SUCCESS</code> when
<code>tfe_text_view_open</code> has successfully opened a file and read
it.</li>
<li>The parameter is set to <code>TFE_OPEN_RESPONSE_CANCEL</code> when
the user has canceled.</li>
<li>The parameter is set to <code>TFE_OPEN_RESPONSE_ERROR</code> when an
error has occurred.</li>
</ul>
<h2 id="signal-connection">Signal connection</h2>
<p>A signal and a handler are connected by the function <code>g_signal_connect</code>. There are some similar functions like <code>g_signal_connect_after</code>, <code>g_signal_connect_swapped</code> and so on. However, <code>g_signal_connect</code> is the most common. The signals “change-file” is connected to a callback function out of the TfeTextView object. In the same way, the signals “open-response” is connected to a callback function out of the TfeTextView object. Those callback functions are defined by users.</p>
<p>In the program <code>tfe</code>, callback functions are defined in <code>tfenotebook.c</code>. And their names are <code>file_changed</code> and <code>open_response</code>. They will be explained later.</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"></a>g_signal_connect (GTK_TEXT_VIEW (tv), <span class="st">&quot;change-file&quot;</span>, G_CALLBACK (file_changed), nb);</span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true"></a></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true"></a>g_signal_connect (TFE_TEXT_VIEW (tv), <span class="st">&quot;open-response&quot;</span>, G_CALLBACK (open_response), nb);</span></code></pre></div>
<p>A signal and a handler are connected by the function
<code>g_signal_connect</code>. There are some similar functions like
<code>g_signal_connect_after</code>,
<code>g_signal_connect_swapped</code> and so on. However,
<code>g_signal_connect</code> is the most common. The signals
“change-file” is connected to a callback function out of the TfeTextView
object. In the same way, the signals “open-response” is connected to a
callback function out of the TfeTextView object. Those callback
functions are defined by users.</p>
<p>In the program <code>tfe</code>, callback functions are defined in
<code>tfenotebook.c</code>. And their names are
<code>file_changed</code> and <code>open_response</code>. They will be
explained later.</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>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<span class="op">),</span> nb<span class="op">);</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a>g_signal_connect <span class="op">(</span>TFE_TEXT_VIEW <span class="op">(</span>tv<span class="op">),</span> <span class="st">&quot;open-response&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>open_response<span class="op">),</span> nb<span class="op">);</span></span></code></pre></div>
<h2 id="signal-emission">Signal emission</h2>
<p>Signals are emitted on an instance. The type of the instance is the second argument of <code>g_signal_new</code>. The relationship between the signal and object type is determined when the signal is registered.</p>
<p>A function <code>g_signal_emit</code> is used to emit the signal. The following lines are extracted from <code>tfetextview.c</code>. Each line comes from a different line.</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"></a>g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], <span class="dv">0</span>);</span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true"></a>g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], <span class="dv">0</span>, TFE_OPEN_RESPONSE_SUCCESS);</span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true"></a>g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], <span class="dv">0</span>, TFE_OPEN_RESPONSE_CANCEL);</span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true"></a>g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], <span class="dv">0</span>, TFE_OPEN_RESPONSE_ERROR);</span></code></pre></div>
<p>Signals are emitted on an instance. The type of the instance is the
second argument of <code>g_signal_new</code>. The relationship between
the signal and object type is determined when the signal is
registered.</p>
<p>A function <code>g_signal_emit</code> is used to emit the signal. The
following lines are extracted from <code>tfetextview.c</code>. Each line
comes from a different line.</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>g_signal_emit <span class="op">(</span>tv<span class="op">,</span> tfe_text_view_signals<span class="op">[</span>CHANGE_FILE<span class="op">],</span> <span class="dv">0</span><span class="op">);</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a>g_signal_emit <span class="op">(</span>tv<span class="op">,</span> tfe_text_view_signals<span class="op">[</span>OPEN_RESPONSE<span class="op">],</span> <span class="dv">0</span><span class="op">,</span> TFE_OPEN_RESPONSE_SUCCESS<span class="op">);</span></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a>g_signal_emit <span class="op">(</span>tv<span class="op">,</span> tfe_text_view_signals<span class="op">[</span>OPEN_RESPONSE<span class="op">],</span> <span class="dv">0</span><span class="op">,</span> TFE_OPEN_RESPONSE_CANCEL<span class="op">);</span></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a>g_signal_emit <span class="op">(</span>tv<span class="op">,</span> tfe_text_view_signals<span class="op">[</span>OPEN_RESPONSE<span class="op">],</span> <span class="dv">0</span><span class="op">,</span> TFE_OPEN_RESPONSE_ERROR<span class="op">);</span></span></code></pre></div>
<ul>
<li>The first argument is the instance on which the signal is emitted.</li>
<li>The first argument is the instance on which the signal is
emitted.</li>
<li>The second argument is the signal id.</li>
<li>The third argument is the detail of the signal. “change-file” signal and “open-response” signal doesnt have details and the argument is zero when no details.</li>
<li>“change-file” signal doesnt have parameter, so theres no fourth parameter.</li>
<li>“open-response” signal has one parameter. The fourth parameter is the parameter.</li>
<li>The third argument is the detail of the signal. “change-file” signal
and “open-response” signal doesnt have details and the argument is zero
when no details.</li>
<li>“change-file” signal doesnt have parameter, so theres no fourth
parameter.</li>
<li>“open-response” signal has one parameter. The fourth parameter is
the parameter.</li>
</ul>
</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>

View file

@ -112,117 +112,129 @@
</div>
</nav>
<h1 id="functions-in-tfetextview">Functions in TfeTextView</h1>
<p>In this section I will explain functions in TfeTextView object.</p>
<h2 id="tfe.h-and-tfetextview.h">tfe.h and tfetextview.h</h2>
<p><code>tfe.h</code> is a top header file and it includes
<code>gtk.h</code> and all the header files. C source files
<code>tfeapplication.c</code> and <code>tfenotebook.c</code> include
<code>tfe.h</code> at the beginning.</p>
<p>TfeTextView functions are described in this section.</p>
<h2 id="tfetextview.h">tfetextview.h</h2>
<p>The header file <code>tfetextview.h</code> provides:</p>
<ul>
<li>The type of TfeTextView, which is
<code>TFE_TYPE_TEXT_VIEW</code>.</li>
<li>The expansion of <code>G_DECLARE_FINAL_TYPE</code> includes some
useful macros.</li>
<li>Constants for the <code>open-response</code> signal is
defined..</li>
<li>Public functions of <code>tfetextview.c</code> are declared.</li>
</ul>
<p>Therefore, Any programs use TfeTextView needs to include
<code>tfetextview.h</code>.</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb1-1"><a href="#cb1-1"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<span id="cb1-2"><a href="#cb1-2"></a></span>
<span id="cb1-3"><a href="#cb1-3"></a><span class="pp">#include </span><span class="im">&quot;../tfetextview/tfetextview.h&quot;</span></span>
<span id="cb1-4"><a href="#cb1-4"></a><span class="pp">#include </span><span class="im">&quot;tfenotebook.h&quot;</span></span></code></pre></div>
<p><code>../tfetextview/tfetextview.h</code> is a header file which
describes the public functions in <code>tfetextview.c</code>.</p>
<div class="sourceCode" id="cb2"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb2-1"><a href="#cb2-1"></a><span class="pp">#ifndef __TFE_TEXT_VIEW_H__</span></span>
<span id="cb2-2"><a href="#cb2-2"></a><span class="pp">#define __TFE_TEXT_VIEW_H__</span></span>
<span id="cb2-3"><a href="#cb2-3"></a></span>
<span id="cb2-4"><a href="#cb2-4"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<span id="cb2-5"><a href="#cb2-5"></a></span>
<span id="cb2-6"><a href="#cb2-6"></a><span class="pp">#define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()</span></span>
<span id="cb2-7"><a href="#cb2-7"></a>G_DECLARE_FINAL_TYPE <span class="op">(</span>TfeTextView<span class="op">,</span> tfe_text_view<span class="op">,</span> TFE<span class="op">,</span> TEXT_VIEW<span class="op">,</span> GtkTextView<span class="op">)</span></span>
<span id="cb2-8"><a href="#cb2-8"></a></span>
<span id="cb2-9"><a href="#cb2-9"></a><span class="co">/* &quot;open-response&quot; signal response */</span></span>
<span id="cb2-10"><a href="#cb2-10"></a><span class="kw">enum</span> TfeTextViewOpenResponseType</span>
<span id="cb2-11"><a href="#cb2-11"></a><span class="op">{</span></span>
<span id="cb2-12"><a href="#cb2-12"></a> TFE_OPEN_RESPONSE_SUCCESS<span class="op">,</span></span>
<span id="cb2-13"><a href="#cb2-13"></a> TFE_OPEN_RESPONSE_CANCEL<span class="op">,</span></span>
<span id="cb2-14"><a href="#cb2-14"></a> TFE_OPEN_RESPONSE_ERROR</span>
<span id="cb2-15"><a href="#cb2-15"></a><span class="op">};</span></span>
<span id="cb2-16"><a href="#cb2-16"></a></span>
<span id="cb2-17"><a href="#cb2-17"></a>GFile <span class="op">*</span></span>
<span id="cb2-18"><a href="#cb2-18"></a>tfe_text_view_get_file <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">);</span></span>
<span id="cb2-19"><a href="#cb2-19"></a></span>
<span id="cb2-20"><a href="#cb2-20"></a><span class="dt">void</span></span>
<span id="cb2-21"><a href="#cb2-21"></a>tfe_text_view_open <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">,</span> GtkWindow <span class="op">*</span>win<span class="op">);</span></span>
<span id="cb2-22"><a href="#cb2-22"></a></span>
<span id="cb2-23"><a href="#cb2-23"></a><span class="dt">void</span></span>
<span id="cb2-24"><a href="#cb2-24"></a>tfe_text_view_save <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">);</span></span>
<span id="cb2-25"><a href="#cb2-25"></a></span>
<span id="cb2-26"><a href="#cb2-26"></a><span class="dt">void</span></span>
<span id="cb2-27"><a href="#cb2-27"></a>tfe_text_view_saveas <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">);</span></span>
<span id="cb2-28"><a href="#cb2-28"></a></span>
<span id="cb2-29"><a href="#cb2-29"></a>GtkWidget <span class="op">*</span></span>
<span id="cb2-30"><a href="#cb2-30"></a>tfe_text_view_new_with_file <span class="op">(</span>GFile <span class="op">*</span>file<span class="op">);</span></span>
<span id="cb2-31"><a href="#cb2-31"></a></span>
<span id="cb2-32"><a href="#cb2-32"></a>GtkWidget <span class="op">*</span></span>
<span id="cb2-33"><a href="#cb2-33"></a>tfe_text_view_new <span class="op">(</span><span class="dt">void</span><span class="op">);</span></span>
<span id="cb2-34"><a href="#cb2-34"></a></span>
<span id="cb2-35"><a href="#cb2-35"></a><span class="pp">#endif </span><span class="co">/* __TFE_TEXT_VIEW_H__ */</span></span></code></pre></div>
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb1-1"><a href="#cb1-1"></a><span class="pp">#ifndef __TFE_TEXT_VIEW_H__</span></span>
<span id="cb1-2"><a href="#cb1-2"></a><span class="pp">#define __TFE_TEXT_VIEW_H__</span></span>
<span id="cb1-3"><a href="#cb1-3"></a></span>
<span id="cb1-4"><a href="#cb1-4"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<span id="cb1-5"><a href="#cb1-5"></a></span>
<span id="cb1-6"><a href="#cb1-6"></a><span class="pp">#define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()</span></span>
<span id="cb1-7"><a href="#cb1-7"></a>G_DECLARE_FINAL_TYPE <span class="op">(</span>TfeTextView<span class="op">,</span> tfe_text_view<span class="op">,</span> TFE<span class="op">,</span> TEXT_VIEW<span class="op">,</span> GtkTextView<span class="op">)</span></span>
<span id="cb1-8"><a href="#cb1-8"></a></span>
<span id="cb1-9"><a href="#cb1-9"></a><span class="co">/* &quot;open-response&quot; signal response */</span></span>
<span id="cb1-10"><a href="#cb1-10"></a><span class="kw">enum</span> TfeTextViewOpenResponseType</span>
<span id="cb1-11"><a href="#cb1-11"></a><span class="op">{</span></span>
<span id="cb1-12"><a href="#cb1-12"></a> TFE_OPEN_RESPONSE_SUCCESS<span class="op">,</span></span>
<span id="cb1-13"><a href="#cb1-13"></a> TFE_OPEN_RESPONSE_CANCEL<span class="op">,</span></span>
<span id="cb1-14"><a href="#cb1-14"></a> TFE_OPEN_RESPONSE_ERROR</span>
<span id="cb1-15"><a href="#cb1-15"></a><span class="op">};</span></span>
<span id="cb1-16"><a href="#cb1-16"></a></span>
<span id="cb1-17"><a href="#cb1-17"></a>GFile <span class="op">*</span></span>
<span id="cb1-18"><a href="#cb1-18"></a>tfe_text_view_get_file <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">);</span></span>
<span id="cb1-19"><a href="#cb1-19"></a></span>
<span id="cb1-20"><a href="#cb1-20"></a><span class="dt">void</span></span>
<span id="cb1-21"><a href="#cb1-21"></a>tfe_text_view_open <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">,</span> GtkWindow <span class="op">*</span>win<span class="op">);</span></span>
<span id="cb1-22"><a href="#cb1-22"></a></span>
<span id="cb1-23"><a href="#cb1-23"></a><span class="dt">void</span></span>
<span id="cb1-24"><a href="#cb1-24"></a>tfe_text_view_save <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">);</span></span>
<span id="cb1-25"><a href="#cb1-25"></a></span>
<span id="cb1-26"><a href="#cb1-26"></a><span class="dt">void</span></span>
<span id="cb1-27"><a href="#cb1-27"></a>tfe_text_view_saveas <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">);</span></span>
<span id="cb1-28"><a href="#cb1-28"></a></span>
<span id="cb1-29"><a href="#cb1-29"></a>GtkWidget <span class="op">*</span></span>
<span id="cb1-30"><a href="#cb1-30"></a>tfe_text_view_new_with_file <span class="op">(</span>GFile <span class="op">*</span>file<span class="op">);</span></span>
<span id="cb1-31"><a href="#cb1-31"></a></span>
<span id="cb1-32"><a href="#cb1-32"></a>GtkWidget <span class="op">*</span></span>
<span id="cb1-33"><a href="#cb1-33"></a>tfe_text_view_new <span class="op">(</span><span class="dt">void</span><span class="op">);</span></span>
<span id="cb1-34"><a href="#cb1-34"></a></span>
<span id="cb1-35"><a href="#cb1-35"></a><span class="pp">#endif </span><span class="co">/* __TFE_TEXT_VIEW_H__ */</span></span></code></pre></div>
<ul>
<li>1,2,35: Thanks to these three lines, the following lines are
included only once.</li>
included only once. You can use <code>#pragma once</code> instead of
them. It is non-standard but widely used.</li>
<li>4: Includes gtk4 header files. The header file <code>gtk4</code>
also has the same mechanism to avoid including it multiple times.</li>
<li>6-7: These two lines define TfeTextView type, its class structure
and some useful macros.</li>
and some useful macros.
<ul>
<li><code>TfeTextView</code> and <code>TfeTextViewClass</code> are
declared as typedef of C structures.</li>
<li>You need to define a structure <code>_TfeTextView</code> later.</li>
<li>The class structure <code>_TfeTextViewClass</code> is defined here.
You dont need to define it by yourself.</li>
<li>Convenience functions <code>TFE_TEXT_VIEW ()</code> for casting and
<code>TFE_IS_TEXT_VIEW</code> for type check are defined.</li>
</ul></li>
<li>9-15: A definition of the value of the parameter of “open-response”
signal.</li>
<li>17-33: Declarations of public functions on TfeTextView.</li>
</ul>
<h2 id="functions-to-create-tfetextview-instances">Functions to create
TfeTextView instances</h2>
<h2 id="instance-creation-functions">Instance creation Functions</h2>
<p>A TfeTextView instance is created with <code>tfe_text_view_new</code>
or <code>tfe_text_view_new_with_file</code>.</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>GtkWidget <span class="op">*</span>tfe_text_view_new <span class="op">(</span><span class="dt">void</span><span class="op">);</span></span></code></pre></div>
<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>GtkWidget <span class="op">*</span>tfe_text_view_new <span class="op">(</span><span class="dt">void</span><span class="op">);</span></span></code></pre></div>
<p><code>tfe_text_view_new</code> just creates a new TfeTextView
instance and returns the pointer to the new instance.</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a>GtkWidget <span class="op">*</span>tfe_text_view_new_with_file <span class="op">(</span>GFile <span class="op">*</span>file<span class="op">);</span></span></code></pre></div>
<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>GtkWidget <span class="op">*</span>tfe_text_view_new_with_file <span class="op">(</span>GFile <span class="op">*</span>file<span class="op">);</span></span></code></pre></div>
<p><code>tfe_text_view_new_with_file</code> is given a Gfile object as
an argument and it loads the file into the GtkTextBuffer instance, then
returns the pointer to the new instance. If an error occurs during the
creation process, NULL is returned.</p>
<p>Each function is defined as follows.</p>
<div class="sourceCode" id="cb5"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb5-1"><a href="#cb5-1"></a>GtkWidget <span class="op">*</span></span>
<span id="cb5-2"><a href="#cb5-2"></a>tfe_text_view_new_with_file <span class="op">(</span>GFile <span class="op">*</span>file<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-3"><a href="#cb5-3"></a> g_return_val_if_fail <span class="op">(</span>G_IS_FILE <span class="op">(</span>file<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb5-4"><a href="#cb5-4"></a></span>
<span id="cb5-5"><a href="#cb5-5"></a> GtkWidget <span class="op">*</span>tv<span class="op">;</span></span>
<span id="cb5-6"><a href="#cb5-6"></a> GtkTextBuffer <span class="op">*</span>tb<span class="op">;</span></span>
<span id="cb5-7"><a href="#cb5-7"></a> <span class="dt">char</span> <span class="op">*</span>contents<span class="op">;</span></span>
<span id="cb5-8"><a href="#cb5-8"></a> gsize length<span class="op">;</span></span>
<span id="cb5-9"><a href="#cb5-9"></a></span>
<span id="cb5-10"><a href="#cb5-10"></a> <span class="cf">if</span> <span class="op">(!</span> g_file_load_contents <span class="op">(</span>file<span class="op">,</span> NULL<span class="op">,</span> <span class="op">&amp;</span>contents<span class="op">,</span> <span class="op">&amp;</span>length<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">))</span> <span class="co">/* read error */</span></span>
<span id="cb5-11"><a href="#cb5-11"></a> <span class="cf">return</span> NULL<span class="op">;</span></span>
<span id="cb5-12"><a href="#cb5-12"></a></span>
<span id="cb5-13"><a href="#cb5-13"></a> <span class="cf">if</span> <span class="op">((</span>tv <span class="op">=</span> tfe_text_view_new<span class="op">())</span> <span class="op">!=</span> NULL<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-14"><a href="#cb5-14"></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="cb5-15"><a href="#cb5-15"></a> gtk_text_buffer_set_text <span class="op">(</span>tb<span class="op">,</span> contents<span class="op">,</span> length<span class="op">);</span></span>
<span id="cb5-16"><a href="#cb5-16"></a> TFE_TEXT_VIEW <span class="op">(</span>tv<span class="op">)-&gt;</span>file <span class="op">=</span> g_file_dup <span class="op">(</span>file<span class="op">);</span></span>
<span id="cb5-17"><a href="#cb5-17"></a> gtk_text_buffer_set_modified <span class="op">(</span>tb<span class="op">,</span> FALSE<span class="op">);</span></span>
<span id="cb5-18"><a href="#cb5-18"></a> <span class="op">}</span></span>
<span id="cb5-19"><a href="#cb5-19"></a> g_free <span class="op">(</span>contents<span class="op">);</span></span>
<span id="cb5-20"><a href="#cb5-20"></a> <span class="cf">return</span> tv<span class="op">;</span></span>
<span id="cb5-21"><a href="#cb5-21"></a><span class="op">}</span></span>
<span id="cb5-22"><a href="#cb5-22"></a></span>
<span id="cb5-23"><a href="#cb5-23"></a>GtkWidget <span class="op">*</span></span>
<span id="cb5-24"><a href="#cb5-24"></a>tfe_text_view_new <span class="op">(</span><span class="dt">void</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-25"><a href="#cb5-25"></a> <span class="cf">return</span> GTK_WIDGET <span class="op">(</span>g_object_new <span class="op">(</span>TFE_TYPE_TEXT_VIEW<span class="op">,</span> NULL<span class="op">));</span></span>
<span id="cb5-26"><a href="#cb5-26"></a><span class="op">}</span></span></code></pre></div>
<div class="sourceCode" id="cb4"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb4-1"><a href="#cb4-1"></a>GtkWidget <span class="op">*</span></span>
<span id="cb4-2"><a href="#cb4-2"></a>tfe_text_view_new_with_file <span class="op">(</span>GFile <span class="op">*</span>file<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-3"><a href="#cb4-3"></a> g_return_val_if_fail <span class="op">(</span>G_IS_FILE <span class="op">(</span>file<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb4-4"><a href="#cb4-4"></a></span>
<span id="cb4-5"><a href="#cb4-5"></a> GtkWidget <span class="op">*</span>tv<span class="op">;</span></span>
<span id="cb4-6"><a href="#cb4-6"></a> GtkTextBuffer <span class="op">*</span>tb<span class="op">;</span></span>
<span id="cb4-7"><a href="#cb4-7"></a> <span class="dt">char</span> <span class="op">*</span>contents<span class="op">;</span></span>
<span id="cb4-8"><a href="#cb4-8"></a> gsize length<span class="op">;</span></span>
<span id="cb4-9"><a href="#cb4-9"></a></span>
<span id="cb4-10"><a href="#cb4-10"></a> <span class="cf">if</span> <span class="op">(!</span> g_file_load_contents <span class="op">(</span>file<span class="op">,</span> NULL<span class="op">,</span> <span class="op">&amp;</span>contents<span class="op">,</span> <span class="op">&amp;</span>length<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">))</span> <span class="co">/* read error */</span></span>
<span id="cb4-11"><a href="#cb4-11"></a> <span class="cf">return</span> NULL<span class="op">;</span></span>
<span id="cb4-12"><a href="#cb4-12"></a></span>
<span id="cb4-13"><a href="#cb4-13"></a> <span class="cf">if</span> <span class="op">((</span>tv <span class="op">=</span> tfe_text_view_new<span class="op">())</span> <span class="op">!=</span> NULL<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-14"><a href="#cb4-14"></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="cb4-15"><a href="#cb4-15"></a> gtk_text_buffer_set_text <span class="op">(</span>tb<span class="op">,</span> contents<span class="op">,</span> length<span class="op">);</span></span>
<span id="cb4-16"><a href="#cb4-16"></a> TFE_TEXT_VIEW <span class="op">(</span>tv<span class="op">)-&gt;</span>file <span class="op">=</span> g_file_dup <span class="op">(</span>file<span class="op">);</span></span>
<span id="cb4-17"><a href="#cb4-17"></a> gtk_text_buffer_set_modified <span class="op">(</span>tb<span class="op">,</span> FALSE<span class="op">);</span></span>
<span id="cb4-18"><a href="#cb4-18"></a> <span class="op">}</span></span>
<span id="cb4-19"><a href="#cb4-19"></a> g_free <span class="op">(</span>contents<span class="op">);</span></span>
<span id="cb4-20"><a href="#cb4-20"></a> <span class="cf">return</span> tv<span class="op">;</span></span>
<span id="cb4-21"><a href="#cb4-21"></a><span class="op">}</span></span>
<span id="cb4-22"><a href="#cb4-22"></a></span>
<span id="cb4-23"><a href="#cb4-23"></a>GtkWidget <span class="op">*</span></span>
<span id="cb4-24"><a href="#cb4-24"></a>tfe_text_view_new <span class="op">(</span><span class="dt">void</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-25"><a href="#cb4-25"></a> <span class="cf">return</span> GTK_WIDGET <span class="op">(</span>g_object_new <span class="op">(</span>TFE_TYPE_TEXT_VIEW<span class="op">,</span> <span class="st">&quot;wrap-mode&quot;</span><span class="op">,</span> GTK_WRAP_WORD_CHAR<span class="op">,</span> NULL<span class="op">));</span></span>
<span id="cb4-26"><a href="#cb4-26"></a><span class="op">}</span></span></code></pre></div>
<ul>
<li>23-25: <code>tfe_text_view_new</code> function. Just returns the
value from the function <code>g_object_new</code> but casts it to the
pointer to GtkWidget. Initialization is done in
<code>tfe_text_view_init</code> which is called in the process of
<code>g_object_new</code> function.</li>
pointer to GtkWidget. The function <code>g_object_new</code> creates any
instances of its descendant class. The arguments are the type of the
class, property list and NULL. Null is the end mark of the property
list. TfeTextView “wrap-mode” property has GTK_WRAP_WORD_CHAR as the
default value.</li>
<li>1-21: <code>tfe_text_view_new_with_file</code> function.</li>
<li>3: <code>g_return_val_if_fail</code> is described in <a
href="https://docs.gtk.org/glib/func.return_val_if_fail.html">GLib API
Reference, g_return_val_if_fail</a>. And also <a
href="https://docs.gtk.org/glib/logging.html">GLib API Reference,
Reference g_return_val_if_fail</a>. And also <a
href="https://docs.gtk.org/glib/logging.html">GLib API Reference
Message Logging</a>. It tests whether the argument <code>file</code> is
a pointer to GFile. If its true, then the program goes on to the next
line. If its false, then it returns NULL (the second argument)
@ -254,295 +266,409 @@ doesnt need to save the contents.</li>
<li>20: Returns <code>tv</code>, which is a pointer to the newly created
TfeTextView instance. If an error happens, NULL is returned.</li>
</ul>
<h2 id="save-and-saveas-functions">Save and saveas functions</h2>
<h2 id="save-related-functions">Save related functions</h2>
<p>Save and saveas functions write the contents in the GtkTextBuffer to
a file.</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">void</span> tfe_text_view_save <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">)</span></span></code></pre></div>
<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><span class="dt">void</span> tfe_text_view_save <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">)</span></span></code></pre></div>
<p>The function <code>tfe_text_view_save</code> writes the contents in
the GtkTextBuffer to a file specified by <code>tv-&gt;file</code>. If
<code>tv-&gt;file</code> is NULL, then it shows GtkFileChooserDialog and
prompts the user to choose a file to save. Then it saves the contents to
the file and sets <code>tv-&gt;file</code> to point the GFile instance
for the file.</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="dt">void</span> tfe_text_view_saveas <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">)</span></span></code></pre></div>
<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">void</span> tfe_text_view_saveas <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">)</span></span></code></pre></div>
<p>The function <code>saveas</code> uses GtkFileChooserDialog and
prompts the user to select a existed file or specify a new file to save.
Then, the function changes <code>tv-&gt;file</code> and save the
contents to the specified file. If an error occurs, it is shown to the
user through the message dialog. The error is managed only in the
TfeTextView and no information is notified to the caller.</p>
<div class="sourceCode" id="cb8"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb8-1"><a href="#cb8-1"></a><span class="dt">static</span> gboolean</span>
<span id="cb8-2"><a href="#cb8-2"></a>save_file <span class="op">(</span>GFile <span class="op">*</span>file<span class="op">,</span> GtkTextBuffer <span class="op">*</span>tb<span class="op">,</span> GtkWindow <span class="op">*</span>win<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-3"><a href="#cb8-3"></a> GtkTextIter start_iter<span class="op">;</span></span>
<span id="cb8-4"><a href="#cb8-4"></a> GtkTextIter end_iter<span class="op">;</span></span>
<span id="cb8-5"><a href="#cb8-5"></a> gchar <span class="op">*</span>contents<span class="op">;</span></span>
<span id="cb8-6"><a href="#cb8-6"></a> gboolean stat<span class="op">;</span></span>
<span id="cb8-7"><a href="#cb8-7"></a> GtkWidget <span class="op">*</span>message_dialog<span class="op">;</span></span>
<span id="cb8-8"><a href="#cb8-8"></a> GError <span class="op">*</span>err <span class="op">=</span> NULL<span class="op">;</span></span>
<span id="cb8-9"><a href="#cb8-9"></a></span>
<span id="cb8-10"><a href="#cb8-10"></a> gtk_text_buffer_get_bounds <span class="op">(</span>tb<span class="op">,</span> <span class="op">&amp;</span>start_iter<span class="op">,</span> <span class="op">&amp;</span>end_iter<span class="op">);</span></span>
<span id="cb8-11"><a href="#cb8-11"></a> contents <span class="op">=</span> gtk_text_buffer_get_text <span class="op">(</span>tb<span class="op">,</span> <span class="op">&amp;</span>start_iter<span class="op">,</span> <span class="op">&amp;</span>end_iter<span class="op">,</span> FALSE<span class="op">);</span></span>
<span id="cb8-12"><a href="#cb8-12"></a> <span class="cf">if</span> <span class="op">(</span>g_file_replace_contents <span class="op">(</span>file<span class="op">,</span> contents<span class="op">,</span> strlen <span class="op">(</span>contents<span class="op">),</span> NULL<span class="op">,</span> TRUE<span class="op">,</span> G_FILE_CREATE_NONE<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">,</span> <span class="op">&amp;</span>err<span class="op">))</span> <span class="op">{</span></span>
<span id="cb8-13"><a href="#cb8-13"></a> gtk_text_buffer_set_modified <span class="op">(</span>tb<span class="op">,</span> FALSE<span class="op">);</span></span>
<span id="cb8-14"><a href="#cb8-14"></a> stat <span class="op">=</span> TRUE<span class="op">;</span></span>
<span id="cb8-15"><a href="#cb8-15"></a> <span class="op">}</span> <span class="cf">else</span> <span class="op">{</span></span>
<span id="cb8-16"><a href="#cb8-16"></a> message_dialog <span class="op">=</span> gtk_message_dialog_new <span class="op">(</span>win<span class="op">,</span> GTK_DIALOG_MODAL<span class="op">,</span></span>
<span id="cb8-17"><a href="#cb8-17"></a> GTK_MESSAGE_ERROR<span class="op">,</span> GTK_BUTTONS_CLOSE<span class="op">,</span></span>
<span id="cb8-18"><a href="#cb8-18"></a> <span class="st">&quot;%s.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">,</span> err<span class="op">-&gt;</span>message<span class="op">);</span></span>
<span id="cb8-19"><a href="#cb8-19"></a> g_signal_connect <span class="op">(</span>message_dialog<span class="op">,</span> <span class="st">&quot;response&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>gtk_window_destroy<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb8-20"><a href="#cb8-20"></a> gtk_widget_show <span class="op">(</span>message_dialog<span class="op">);</span></span>
<span id="cb8-21"><a href="#cb8-21"></a> g_error_free <span class="op">(</span>err<span class="op">);</span></span>
<span id="cb8-22"><a href="#cb8-22"></a> stat <span class="op">=</span> FALSE<span class="op">;</span></span>
<span id="cb8-23"><a href="#cb8-23"></a> <span class="op">}</span></span>
<span id="cb8-24"><a href="#cb8-24"></a> g_free <span class="op">(</span>contents<span class="op">);</span></span>
<span id="cb8-25"><a href="#cb8-25"></a> <span class="cf">return</span> stat<span class="op">;</span></span>
<span id="cb8-26"><a href="#cb8-26"></a><span class="op">}</span></span>
<span id="cb8-27"><a href="#cb8-27"></a></span>
<span id="cb8-28"><a href="#cb8-28"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb8-29"><a href="#cb8-29"></a>saveas_dialog_response <span class="op">(</span>GtkWidget <span class="op">*</span>dialog<span class="op">,</span> gint response<span class="op">,</span> TfeTextView <span class="op">*</span>tv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-30"><a href="#cb8-30"></a> GtkTextBuffer <span class="op">*</span>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="cb8-31"><a href="#cb8-31"></a> GFile <span class="op">*</span>file<span class="op">;</span></span>
<span id="cb8-32"><a href="#cb8-32"></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>tv<span class="op">),</span> GTK_TYPE_WINDOW<span class="op">);</span></span>
<span id="cb8-33"><a href="#cb8-33"></a></span>
<span id="cb8-34"><a href="#cb8-34"></a> <span class="cf">if</span> <span class="op">(</span>response <span class="op">==</span> GTK_RESPONSE_ACCEPT<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-35"><a href="#cb8-35"></a> file <span class="op">=</span> gtk_file_chooser_get_file <span class="op">(</span>GTK_FILE_CHOOSER <span class="op">(</span>dialog<span class="op">));</span></span>
<span id="cb8-36"><a href="#cb8-36"></a> <span class="cf">if</span> <span class="op">(!</span> G_IS_FILE <span class="op">(</span>file<span class="op">))</span></span>
<span id="cb8-37"><a href="#cb8-37"></a> g_warning <span class="op">(</span><span class="st">&quot;TfeTextView: gtk_file_chooser_get_file returns non GFile.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb8-38"><a href="#cb8-38"></a> <span class="cf">else</span> <span class="cf">if</span> <span class="op">(</span>save_file<span class="op">(</span>file<span class="op">,</span> tb<span class="op">,</span> GTK_WINDOW <span class="op">(</span>win<span class="op">)))</span> <span class="op">{</span></span>
<span id="cb8-39"><a href="#cb8-39"></a> <span class="cf">if</span> <span class="op">(</span>G_IS_FILE <span class="op">(</span>tv<span class="op">-&gt;</span>file<span class="op">))</span></span>
<span id="cb8-40"><a href="#cb8-40"></a> g_object_unref <span class="op">(</span>tv<span class="op">-&gt;</span>file<span class="op">);</span></span>
<span id="cb8-41"><a href="#cb8-41"></a> tv<span class="op">-&gt;</span>file <span class="op">=</span> file<span class="op">;</span></span>
<span id="cb8-42"><a href="#cb8-42"></a> g_signal_emit <span class="op">(</span>tv<span class="op">,</span> tfe_text_view_signals<span class="op">[</span>CHANGE_FILE<span class="op">],</span> <span class="dv">0</span><span class="op">);</span></span>
<span id="cb8-43"><a href="#cb8-43"></a> <span class="op">}</span> <span class="cf">else</span></span>
<span id="cb8-44"><a href="#cb8-44"></a> g_object_unref <span class="op">(</span>file<span class="op">);</span></span>
<span id="cb8-45"><a href="#cb8-45"></a> <span class="op">}</span></span>
<span id="cb8-46"><a href="#cb8-46"></a> gtk_window_destroy <span class="op">(</span>GTK_WINDOW <span class="op">(</span>dialog<span class="op">));</span></span>
<span id="cb8-47"><a href="#cb8-47"></a><span class="op">}</span></span>
<span id="cb8-48"><a href="#cb8-48"></a></span>
<span id="cb8-49"><a href="#cb8-49"></a><span class="dt">void</span></span>
<span id="cb8-50"><a href="#cb8-50"></a>tfe_text_view_save <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-51"><a href="#cb8-51"></a> g_return_if_fail <span class="op">(</span>TFE_IS_TEXT_VIEW <span class="op">(</span>tv<span class="op">));</span></span>
<span id="cb8-52"><a href="#cb8-52"></a></span>
<span id="cb8-53"><a href="#cb8-53"></a> GtkTextBuffer <span class="op">*</span>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="cb8-54"><a href="#cb8-54"></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>tv<span class="op">),</span> GTK_TYPE_WINDOW<span class="op">);</span></span>
<span id="cb8-55"><a href="#cb8-55"></a></span>
<span id="cb8-56"><a href="#cb8-56"></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="cb8-57"><a href="#cb8-57"></a> <span class="cf">return</span><span class="op">;</span> <span class="co">/* no need to save it */</span></span>
<span id="cb8-58"><a href="#cb8-58"></a> <span class="cf">else</span> <span class="cf">if</span> <span class="op">(</span>tv<span class="op">-&gt;</span>file <span class="op">==</span> NULL<span class="op">)</span></span>
<span id="cb8-59"><a href="#cb8-59"></a> tfe_text_view_saveas <span class="op">(</span>tv<span class="op">);</span></span>
<span id="cb8-60"><a href="#cb8-60"></a> <span class="cf">else</span> <span class="cf">if</span> <span class="op">(!</span> G_IS_FILE <span class="op">(</span>tv<span class="op">-&gt;</span>file<span class="op">))</span></span>
<span id="cb8-61"><a href="#cb8-61"></a> g_error <span class="op">(</span><span class="st">&quot;TfeTextView: The pointer tv-&gt;file isn&#39;t NULL nor GFile.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb8-62"><a href="#cb8-62"></a> <span class="cf">else</span></span>
<span id="cb8-63"><a href="#cb8-63"></a> save_file <span class="op">(</span>tv<span class="op">-&gt;</span>file<span class="op">,</span> tb<span class="op">,</span> GTK_WINDOW <span class="op">(</span>win<span class="op">));</span></span>
<span id="cb8-64"><a href="#cb8-64"></a><span class="op">}</span></span>
<span id="cb8-65"><a href="#cb8-65"></a></span>
<span id="cb8-66"><a href="#cb8-66"></a><span class="dt">void</span></span>
<span id="cb8-67"><a href="#cb8-67"></a>tfe_text_view_saveas <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-68"><a href="#cb8-68"></a> g_return_if_fail <span class="op">(</span>TFE_IS_TEXT_VIEW <span class="op">(</span>tv<span class="op">));</span></span>
<span id="cb8-69"><a href="#cb8-69"></a></span>
<span id="cb8-70"><a href="#cb8-70"></a> GtkWidget <span class="op">*</span>dialog<span class="op">;</span></span>
<span id="cb8-71"><a href="#cb8-71"></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>tv<span class="op">),</span> GTK_TYPE_WINDOW<span class="op">);</span></span>
<span id="cb8-72"><a href="#cb8-72"></a></span>
<span id="cb8-73"><a href="#cb8-73"></a> dialog <span class="op">=</span> gtk_file_chooser_dialog_new <span class="op">(</span><span class="st">&quot;Save file&quot;</span><span class="op">,</span> GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> GTK_FILE_CHOOSER_ACTION_SAVE<span class="op">,</span></span>
<span id="cb8-74"><a href="#cb8-74"></a> <span class="st">&quot;Cancel&quot;</span><span class="op">,</span> GTK_RESPONSE_CANCEL<span class="op">,</span></span>
<span id="cb8-75"><a href="#cb8-75"></a> <span class="st">&quot;Save&quot;</span><span class="op">,</span> GTK_RESPONSE_ACCEPT<span class="op">,</span></span>
<span id="cb8-76"><a href="#cb8-76"></a> NULL<span class="op">);</span></span>
<span id="cb8-77"><a href="#cb8-77"></a> g_signal_connect <span class="op">(</span>dialog<span class="op">,</span> <span class="st">&quot;response&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>saveas_dialog_response<span class="op">),</span> tv<span class="op">);</span></span>
<span id="cb8-78"><a href="#cb8-78"></a> gtk_widget_show <span class="op">(</span>dialog<span class="op">);</span></span>
<span id="cb8-79"><a href="#cb8-79"></a><span class="op">}</span></span></code></pre></div>
<h3 id="save_file-function">save_file function</h3>
<div class="sourceCode" id="cb7"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb7-1"><a href="#cb7-1"></a><span class="dt">static</span> gboolean</span>
<span id="cb7-2"><a href="#cb7-2"></a>save_file <span class="op">(</span>GFile <span class="op">*</span>file<span class="op">,</span> GtkTextBuffer <span class="op">*</span>tb<span class="op">,</span> GtkWindow <span class="op">*</span>win<span class="op">)</span> <span class="op">{</span></span>
<span id="cb7-3"><a href="#cb7-3"></a> GtkTextIter start_iter<span class="op">;</span></span>
<span id="cb7-4"><a href="#cb7-4"></a> GtkTextIter end_iter<span class="op">;</span></span>
<span id="cb7-5"><a href="#cb7-5"></a> <span class="dt">char</span> <span class="op">*</span>contents<span class="op">;</span></span>
<span id="cb7-6"><a href="#cb7-6"></a> gboolean stat<span class="op">;</span></span>
<span id="cb7-7"><a href="#cb7-7"></a> GtkWidget <span class="op">*</span>message_dialog<span class="op">;</span></span>
<span id="cb7-8"><a href="#cb7-8"></a> GError <span class="op">*</span>err <span class="op">=</span> NULL<span class="op">;</span></span>
<span id="cb7-9"><a href="#cb7-9"></a></span>
<span id="cb7-10"><a href="#cb7-10"></a> gtk_text_buffer_get_bounds <span class="op">(</span>tb<span class="op">,</span> <span class="op">&amp;</span>start_iter<span class="op">,</span> <span class="op">&amp;</span>end_iter<span class="op">);</span></span>
<span id="cb7-11"><a href="#cb7-11"></a> contents <span class="op">=</span> gtk_text_buffer_get_text <span class="op">(</span>tb<span class="op">,</span> <span class="op">&amp;</span>start_iter<span class="op">,</span> <span class="op">&amp;</span>end_iter<span class="op">,</span> FALSE<span class="op">);</span></span>
<span id="cb7-12"><a href="#cb7-12"></a> stat <span class="op">=</span> g_file_replace_contents <span class="op">(</span>file<span class="op">,</span> contents<span class="op">,</span> strlen <span class="op">(</span>contents<span class="op">),</span> NULL<span class="op">,</span> TRUE<span class="op">,</span> G_FILE_CREATE_NONE<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">,</span> <span class="op">&amp;</span>err<span class="op">);</span></span>
<span id="cb7-13"><a href="#cb7-13"></a> <span class="cf">if</span> <span class="op">(</span>stat<span class="op">)</span></span>
<span id="cb7-14"><a href="#cb7-14"></a> gtk_text_buffer_set_modified <span class="op">(</span>tb<span class="op">,</span> FALSE<span class="op">);</span></span>
<span id="cb7-15"><a href="#cb7-15"></a> <span class="cf">else</span> <span class="op">{</span></span>
<span id="cb7-16"><a href="#cb7-16"></a> <span class="co">// Because error message is displayed here, the caller of &#39;save_file&#39; doesn&#39;t need to do anything about error.</span></span>
<span id="cb7-17"><a href="#cb7-17"></a> message_dialog <span class="op">=</span> gtk_message_dialog_new <span class="op">(</span>win<span class="op">,</span> GTK_DIALOG_MODAL<span class="op">,</span></span>
<span id="cb7-18"><a href="#cb7-18"></a> GTK_MESSAGE_ERROR<span class="op">,</span> GTK_BUTTONS_CLOSE<span class="op">,</span> <span class="st">&quot;%s.&quot;</span><span class="op">,</span> err<span class="op">-&gt;</span>message<span class="op">);</span></span>
<span id="cb7-19"><a href="#cb7-19"></a> g_signal_connect <span class="op">(</span>message_dialog<span class="op">,</span> <span class="st">&quot;response&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>gtk_window_destroy<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb7-20"><a href="#cb7-20"></a> gtk_widget_show <span class="op">(</span>message_dialog<span class="op">);</span></span>
<span id="cb7-21"><a href="#cb7-21"></a> g_error_free <span class="op">(</span>err<span class="op">);</span></span>
<span id="cb7-22"><a href="#cb7-22"></a> <span class="op">}</span></span>
<span id="cb7-23"><a href="#cb7-23"></a> g_free <span class="op">(</span>contents<span class="op">);</span></span>
<span id="cb7-24"><a href="#cb7-24"></a> <span class="cf">return</span> stat<span class="op">;</span></span>
<span id="cb7-25"><a href="#cb7-25"></a><span class="op">}</span></span></code></pre></div>
<ul>
<li>1-26: <code>save_file</code> function. This function is called from
<li>The function <code>save_file</code> is called from
<code>saveas_dialog_response</code> and <code>tfe_text_view_save</code>.
This function saves the contents of the buffer to the file given as an
argument. If error happens, it displays an error message. The class of
this function is <code>static</code>. Therefore, only functions in this
file (<code>tfeTetview.c</code>) call this function. Such static
functions usally dont have <code>g_return_val_if_fail</code>
function.</li>
argument. If error happens, it displays an error message. So, a caller
of this function dont need to take care of errors. The class of this
function is <code>static</code>. Therefore, only functions in this file
(<code>tfetextview.c</code>) call this function. Such static functions
usually dont have <code>g_return_val_if_fail</code> function.</li>
<li>10-11: Gets the text contents from the buffer.</li>
<li>12-14: Saves the contents to the file. If no error happens, set the
modified flag to be FALSE. This means that the buffer is not modified
since it has been saved. And set the return status <code>stat</code> to
be TRUE.</li>
<li>15-23: If it fails to save the contents, displays an error
message.</li>
<li>16-18: Creates a message dialog with the error message.</li>
<li>12: The function <code>g_file_replace_contents</code> writes the
contents to the file and returns the status (true = success/ false =
fail). It has many parameters, but some of them are almost always given
the same values.
<ul>
<li>GFile* file: GFile to which the contents are saved.</li>
<li>const char* contents: contents to be saved. The string is owned by
the caller.</li>
<li>gsize length: the length of the contents</li>
<li>const char* etag: entity tag. It is usually NULL.</li>
<li>gboolean make_backup: true to make a backup if the file exists.
false not to make it. the file will be overwritten.</li>
<li>GFileCreateFlags flags: usually <code>G_FILE_CREATE_NONE</code> is
fine.</li>
<li>char** new_etag: new entity tag. It is usually NULL.</li>
<li>GCancellable* cancellable: If a cancellable instance is set, the
other thread can cancel this operation. it is usually NULL.</li>
<li>GError** error: If error happens, GError will be set.</li>
</ul></li>
<li>13,14: If no error happens, set the modified flag to be FALSE. This
means that the buffer is not modified since it has been saved.</li>
<li>15-22: If it fails to save the contents, an error message will be
displayed.</li>
<li>17-18: Creates a message dialog. The parameters are:
<ul>
<li>GtkWindow* parent: transient parent window. This allows window
managers to keep the dialog on top of the parent window, or center the
dialog over the parent window. It is possible to give no parent window
to the dialog. However, it is encouraged to give parents to
dialogs.</li>
<li>GtkDialogFlags flags: GTK_DIALOG_MODAL for modal dialog. A modal
dialog is usually fine.</li>
<li>GtkMessageType type: GTK_MESSAGE_ERROR for error message. Other
options are GTK_MESSAGE_INFO, GTK_MESSAGE_WARNING and so on.</li>
<li>GtkButtonsType buttons: GTK_BUTTON_OK is used most often. Other
option is GTK_BUTTON_YES_NO, GTK_BUTTON_CANCEL and so on.</li>
<li>const gchar* message_format: gchar is the same as char. This format
is the same as printf. Arguments for the message format follow.</li>
</ul></li>
<li>19: Connects the “response” signal to
<code>gtk_window_destroy</code>, so that the dialog disappears when a
user clicked on the button.</li>
<li>20-21: Shows the window, frees <code>err</code> and set
<code>stat</code> to be FLASE.</li>
<li>24: Frees <code>contents</code>.</li>
<li>25: Returns to the caller.</li>
<li>28-47: <code>saveas_dialog_response</code> function. This is a
signal handler for the “response” signal on GtkFileChooserDialog
instance created by <code>tfe_text_view_saveas</code> function. This
handler analyzes the response and determines whether to save the
contents.</li>
<li>34-45: If the response is <code>GTK_RESPONSE_ACCEPT</code>, the user
has clicked on the <code>Save</code> button. So, it tries to save.</li>
<li>35: Gets the GFile <code>file</code> from GtkFileChooserDialog.</li>
<li>36-37: If it doesnt point GFile, it outputs an error message to the
log.</li>
<li>38: Otherwise, it calls <code>save_file</code> to save the contents
to the file.</li>
<li>39-42: If <code>save_file</code> has successfully saved the
contents, <code>tv-&gt;file</code> is updated. If the old GFile pointed
by <code>tv-&gt;file</code> exists, it is freed in advance. Emits
“change-file” signal.</li>
<li>44: Unrefs <code>file</code>.</li>
<li>46: destroys the file chooser dialog.</li>
<li>49-64: <code>tfe_text_view_save</code> function.</li>
<li>51: <code>tfe_text_view_save</code> is public, i.e. it is open to
the other files. So, it doesnt have <code>static</code> class. Public
functions should check the parameter type with
<code>g_return_if_fail</code> function. If <code>tv</code> is not a
pointer to a TfeTextView instance, then it logs an error message and
immediately returns. This function is similar to
<code>g_return_val_if_fail</code>, but no value is returned because
<code>tfe_text_view_save</code> doesnt return a value.</li>
<li>53-54: Gets GtkTextBuffer instance and GtkWidget instance and
assignes them to <code>tb</code> and<code>win</code> respectively.</li>
<li>56-57: If the buffer hasnt modified, then it doesnt need to save
it. So the function returns.</li>
<li>58-59: If <code>tv-&gt;file</code> is NULL, no file has given yet.
It calls <code>tfe_text_view_saveas</code> which prompts a user to
select a file or specify a new file to save.</li>
<li>60-61: If <code>tv-&gt;file</code> doesnt point GFile, somethig bad
has happened. Logs an error message.</li>
<li>62-63: Calls <code>save_file</code> to save the contents to the
file.</li>
<li>66-79: <code>tfe_text_view_saveas</code> function. It shows
GtkFileChooserDialog and prompts the user to choose a file.</li>
<li>73-76: Creates GtkFileChooserDialog. The title is “Save file”.
Transient parent of the dialog is <code>win</code>, which is the
top-level window. The action is save mode. The buttons are Cancel and
Save.</li>
<li>77: connects the “response” signal of the dialog and
<code>saveas_dialog_response</code> handler.</li>
<li>78: Shows the dialog.</li>
<code>gtk_window_destroy</code>, so that the dialog disappears when the
user clicks on the button.</li>
<li>20: Shows the message dialog.</li>
<li>21: Frees <code>err</code> with <code>g_error_free</code>
function.</li>
<li>23: Frees <code>contents</code>.</li>
<li>24: Returns to the caller.</li>
</ul>
<h3 id="saveas_dialog_response-function">saveas_dialog_response
function</h3>
<div class="sourceCode" id="cb8"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb8-1"><a href="#cb8-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb8-2"><a href="#cb8-2"></a>saveas_dialog_response <span class="op">(</span>GtkWidget <span class="op">*</span>dialog<span class="op">,</span> gint response<span class="op">,</span> TfeTextView <span class="op">*</span>tv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-3"><a href="#cb8-3"></a> GtkTextBuffer <span class="op">*</span>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="cb8-4"><a href="#cb8-4"></a> GFile <span class="op">*</span>file<span class="op">;</span></span>
<span id="cb8-5"><a href="#cb8-5"></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>tv<span class="op">),</span> GTK_TYPE_WINDOW<span class="op">);</span></span>
<span id="cb8-6"><a href="#cb8-6"></a></span>
<span id="cb8-7"><a href="#cb8-7"></a> <span class="cf">if</span> <span class="op">(</span>response <span class="op">==</span> GTK_RESPONSE_ACCEPT<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-8"><a href="#cb8-8"></a> file <span class="op">=</span> gtk_file_chooser_get_file <span class="op">(</span>GTK_FILE_CHOOSER <span class="op">(</span>dialog<span class="op">));</span></span>
<span id="cb8-9"><a href="#cb8-9"></a> <span class="cf">if</span> <span class="op">(!</span> G_IS_FILE <span class="op">(</span>file<span class="op">))</span></span>
<span id="cb8-10"><a href="#cb8-10"></a> g_warning <span class="op">(</span><span class="st">&quot;TfeTextView: gtk_file_chooser_get_file returns non GFile.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb8-11"><a href="#cb8-11"></a> <span class="cf">else</span> <span class="cf">if</span> <span class="op">(</span>save_file<span class="op">(</span>file<span class="op">,</span> tb<span class="op">,</span> GTK_WINDOW <span class="op">(</span>win<span class="op">)))</span> <span class="op">{</span></span>
<span id="cb8-12"><a href="#cb8-12"></a> <span class="co">// The following is complicated. The comments here will help your understanding</span></span>
<span id="cb8-13"><a href="#cb8-13"></a> <span class="co">// G_IS_FILE(tv-&gt;file) &amp;&amp; tv-&gt;file == file =&gt; nothing to do</span></span>
<span id="cb8-14"><a href="#cb8-14"></a> <span class="co">// G_IS_FILE(tv-&gt;file) &amp;&amp; tv-&gt;file != file =&gt; unref(tv-&gt;file), tv-&gt;file=file, signal emit</span></span>
<span id="cb8-15"><a href="#cb8-15"></a> <span class="co">// tv-&gt;file==NULL =&gt; tv-&gt;file=file, signal emit</span></span>
<span id="cb8-16"><a href="#cb8-16"></a> <span class="cf">if</span> <span class="op">(</span>G_IS_FILE <span class="op">(</span>tv<span class="op">-&gt;</span>file<span class="op">)</span> <span class="op">&amp;&amp;</span> <span class="op">(!</span> g_file_equal <span class="op">(</span>tv<span class="op">-&gt;</span>file<span class="op">,</span> file<span class="op">)))</span></span>
<span id="cb8-17"><a href="#cb8-17"></a> g_object_unref <span class="op">(</span>tv<span class="op">-&gt;</span>file<span class="op">);</span></span>
<span id="cb8-18"><a href="#cb8-18"></a> <span class="cf">if</span> <span class="op">(!</span> <span class="op">(</span>G_IS_FILE <span class="op">(</span>tv<span class="op">-&gt;</span>file<span class="op">)</span> <span class="op">&amp;&amp;</span> g_file_equal <span class="op">(</span>tv<span class="op">-&gt;</span>file<span class="op">,</span> file<span class="op">)))</span> <span class="op">{</span></span>
<span id="cb8-19"><a href="#cb8-19"></a> tv<span class="op">-&gt;</span>file <span class="op">=</span> file<span class="op">;</span> <span class="co">// The ownership of &#39;file&#39; moves to TfeTextView.</span></span>
<span id="cb8-20"><a href="#cb8-20"></a> g_signal_emit <span class="op">(</span>tv<span class="op">,</span> tfe_text_view_signals<span class="op">[</span>CHANGE_FILE<span class="op">],</span> <span class="dv">0</span><span class="op">);</span></span>
<span id="cb8-21"><a href="#cb8-21"></a> <span class="op">}</span></span>
<span id="cb8-22"><a href="#cb8-22"></a> g_object_unref <span class="op">(</span>file<span class="op">);</span></span>
<span id="cb8-23"><a href="#cb8-23"></a> <span class="op">}</span> <span class="cf">else</span></span>
<span id="cb8-24"><a href="#cb8-24"></a> g_object_unref <span class="op">(</span>file<span class="op">);</span></span>
<span id="cb8-25"><a href="#cb8-25"></a> <span class="op">}</span></span>
<span id="cb8-26"><a href="#cb8-26"></a> gtk_window_destroy <span class="op">(</span>GTK_WINDOW <span class="op">(</span>dialog<span class="op">));</span></span>
<span id="cb8-27"><a href="#cb8-27"></a><span class="op">}</span></span></code></pre></div>
<ul>
<li>The function <code>saveas_dialog_response</code> is a signal handler
for the “response” signal on GtkFileChooserDialog. This handler analyzes
the response and determines whether to save the contents or not.</li>
<li>7-25: If the response is <code>GTK_RESPONSE_ACCEPT</code>, the user
has clicked on the <code>Save</code> button and the contents will be
saved.</li>
<li>8: Gets the GFile <code>file</code> from the
GtkFileChooserDialog.</li>
<li>9-10: If it doesnt point GFile, a warning message will be output to
the log. This is not expected.</li>
<li>11: Otherwise, it calls <code>save_file</code> to save the contents
to the file.</li>
<li>12-22: If <code>save_file</code> has successfully saved the
contents, the following will be done.
<ul>
<li>If <code>tv-&gt;file</code> is GFile and <code>file</code> is a
different file, unref <code>tv-&gt;file</code>.</li>
<li>If <code>tv-&gt;file</code> is GFile and <code>file</code> points
the same file as <code>tv-&gt;file</code>, nothing needs to do.
Otherwise, <code>tv-&gt;file</code> is set to <code>file</code> and
“change-file” signal is emitted.</li>
</ul></li>
<li>22, 24: Unref <code>file</code>.</li>
<li>26: destroys the file chooser dialog.</li>
</ul>
<h3 id="tfe_text_view_save-function">tfe_text_view_save function</h3>
<div class="sourceCode" id="cb9"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb9-1"><a href="#cb9-1"></a><span class="dt">void</span></span>
<span id="cb9-2"><a href="#cb9-2"></a>tfe_text_view_save <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb9-3"><a href="#cb9-3"></a> g_return_if_fail <span class="op">(</span>TFE_IS_TEXT_VIEW <span class="op">(</span>tv<span class="op">));</span></span>
<span id="cb9-4"><a href="#cb9-4"></a></span>
<span id="cb9-5"><a href="#cb9-5"></a> GtkTextBuffer <span class="op">*</span>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="cb9-6"><a href="#cb9-6"></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>tv<span class="op">),</span> GTK_TYPE_WINDOW<span class="op">);</span></span>
<span id="cb9-7"><a href="#cb9-7"></a></span>
<span id="cb9-8"><a href="#cb9-8"></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="cb9-9"><a href="#cb9-9"></a> <span class="cf">return</span><span class="op">;</span> <span class="co">/* no need to save it */</span></span>
<span id="cb9-10"><a href="#cb9-10"></a> <span class="cf">else</span> <span class="cf">if</span> <span class="op">(</span>tv<span class="op">-&gt;</span>file <span class="op">==</span> NULL<span class="op">)</span></span>
<span id="cb9-11"><a href="#cb9-11"></a> tfe_text_view_saveas <span class="op">(</span>tv<span class="op">);</span></span>
<span id="cb9-12"><a href="#cb9-12"></a> <span class="cf">else</span> <span class="cf">if</span> <span class="op">(!</span> G_IS_FILE <span class="op">(</span>tv<span class="op">-&gt;</span>file<span class="op">))</span> <span class="co">// Unexpected error</span></span>
<span id="cb9-13"><a href="#cb9-13"></a> g_error <span class="op">(</span><span class="st">&quot;TfeTextView: The pointer tv-&gt;file isn&#39;t NULL nor GFile.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb9-14"><a href="#cb9-14"></a> <span class="cf">else</span></span>
<span id="cb9-15"><a href="#cb9-15"></a> save_file <span class="op">(</span>tv<span class="op">-&gt;</span>file<span class="op">,</span> tb<span class="op">,</span> GTK_WINDOW <span class="op">(</span>win<span class="op">));</span></span>
<span id="cb9-16"><a href="#cb9-16"></a><span class="op">}</span></span></code></pre></div>
<ul>
<li>The function <code>tfe_text_view_save</code> writes the contents to
the <code>tv-&gt;file</code> file. It calls
<code>tfe_text_view_saveas</code> or <code>save_file</code>.</li>
<li>1-3: The function is public, i.e. it is open to the other objects.
So, it doesnt have <code>static</code> class. Public functions should
check the parameter type with <code>g_return_if_fail</code> function. If
<code>tv</code> is not a pointer to a TfeTextView instance, then it logs
an error message and immediately returns. This function is similar to
<code>g_return_val_if_fail</code>, but no value is returned because
<code>tfe_text_view_save</code> doesnt return a value (void).</li>
<li>5-6: GtkTextBuffer <code>tb</code> and GtkWidget (GtkWindow)
<code>win</code> are set. The function
<code>gtk_widget_get_ancestor (widget, type)</code> returns the first
ancestor of the widget with type. The type is a GType. For example, the
type of GtkWindow is <code>GTK_TYPE_WINDOW</code> and the one of
TfeTextView is <code>TFE_TYPE_TEXT_VIEW</code>. Be careful. The
parent-child relationship here is the one for widgets, not classes. The
top level window may be a GtkApplicationWindow, but it depends on the
application. Because TfeTextView is a library, it cant determine the
top level window type (GtkWindow or GtkApplicationWindow). GtkWindow is
a parent class of GtkApplication window so it is more general.
Therefore, TfeTextView takes GtkWindow as a top level window so that it
can be used by any application.</li>
<li>8-9: If the buffer hasnt modified, it doesnt need to be
saved.</li>
<li>10-11: If <code>tv-&gt;file</code> is NULL, which means no file has
given yet, it calls <code>tfe_text_view_saveas</code> to prompt a user
to select a file and save the contents.</li>
<li>12-13: If <code>tv-&gt;file</code> doesnt point GFile, an error
message is logged out. It is not expected.</li>
<li>14-15: Otherwise, it calls <code>save_file</code> to save the
contents to the file <code>tv-&gt;file</code>.</li>
</ul>
<h3 id="tfe_text_view_saveas-function">tfe_text_view_saveas
function</h3>
<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">void</span></span>
<span id="cb10-2"><a href="#cb10-2"></a>tfe_text_view_saveas <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb10-3"><a href="#cb10-3"></a> g_return_if_fail <span class="op">(</span>TFE_IS_TEXT_VIEW <span class="op">(</span>tv<span class="op">));</span></span>
<span id="cb10-4"><a href="#cb10-4"></a></span>
<span id="cb10-5"><a href="#cb10-5"></a> GtkWidget <span class="op">*</span>dialog<span class="op">;</span></span>
<span id="cb10-6"><a href="#cb10-6"></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>tv<span class="op">),</span> GTK_TYPE_WINDOW<span class="op">);</span></span>
<span id="cb10-7"><a href="#cb10-7"></a></span>
<span id="cb10-8"><a href="#cb10-8"></a> dialog <span class="op">=</span> gtk_file_chooser_dialog_new <span class="op">(</span><span class="st">&quot;Save file&quot;</span><span class="op">,</span> GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> GTK_FILE_CHOOSER_ACTION_SAVE<span class="op">,</span></span>
<span id="cb10-9"><a href="#cb10-9"></a> <span class="st">&quot;Cancel&quot;</span><span class="op">,</span> GTK_RESPONSE_CANCEL<span class="op">,</span></span>
<span id="cb10-10"><a href="#cb10-10"></a> <span class="st">&quot;Save&quot;</span><span class="op">,</span> GTK_RESPONSE_ACCEPT<span class="op">,</span></span>
<span id="cb10-11"><a href="#cb10-11"></a> NULL<span class="op">);</span></span>
<span id="cb10-12"><a href="#cb10-12"></a> g_signal_connect <span class="op">(</span>dialog<span class="op">,</span> <span class="st">&quot;response&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>saveas_dialog_response<span class="op">),</span> tv<span class="op">);</span></span>
<span id="cb10-13"><a href="#cb10-13"></a> gtk_widget_show <span class="op">(</span>dialog<span class="op">);</span></span>
<span id="cb10-14"><a href="#cb10-14"></a><span class="op">}</span></span></code></pre></div>
<ul>
<li>The function <code>tfe_text_view_saveas</code> shows
GtkFileChooserDialog and prompts the user to choose a file and save the
contents.</li>
<li>1-3: Check the type of <code>tv</code> because the caller may be
other object. This function is public.</li>
<li>6: GtkWidget <code>win</code> is set to the top level window.</li>
<li>8-11: Creates GtkFileChooserDialog. It has at least four parameters.
<ul>
<li>const char* title: title of the dialog shown at the bar.</li>
<li>GtkWindow* parent: transient parent window.</li>
<li>GtkFileChooserAction action: action is one of
<code>GTK_FILE_CHOOSER_ACTION_OPEN</code>,
<code>GTK_FILE_CHOOSER_ACTION_SAVE</code> and
<code>GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER</code>. When you want to
save a file, <code>GTK_FILE_CHOOSER_ACTION_SAVE</code> is the
action.</li>
<li>const char* first_button_text: the label text of the first
button.</li>
<li>type: response ID for the sirst button. response ID is one of the
GtkReponseType. See <a
href="https://docs.gtk.org/gtk4/enum.ResponseType.html">GtkResponseType</a>.</li>
<li>followed by pairs of button text and response ID…</li>
<li>NULL is put at the end of the list.</li>
</ul></li>
<li>The GtkFileChooserDialog will have the title “Save file”, transient
parent <code>win</code>, save mode action, cancel and save button.</li>
<li>12: connects the “response” signal and
<code>saveas_dialog_response</code> handler.</li>
<li>13: Shows the dialog.</li>
</ul>
<figure>
<img src="image/saveas.png" alt="Saveas process" />
<figcaption aria-hidden="true">Saveas process</figcaption>
</figure>
<p>When you use GtkFileChooserDialog, you need to divide the program
into two parts. One is a function which creates GtkFileChooserDialog and
the other is a signal handler. The function just creates and shows
GtkFileChooserDialog. The rest is done by the handler. It gets Gfile
from GtkFileChooserDialog and saves the buffer to the file by calling
<code>save_file</code>.</p>
<h2 id="open-function">Open function</h2>
<p>Open function shows GtkFileChooserDialog to users and prompts them to
choose a file. Then it reads the file and puts the text into
<figure>
<img src="image/saveas.png" alt="Saveas process" />
<figcaption aria-hidden="true">Saveas process</figcaption>
</figure>
<h2 id="open-related-functions">Open related functions</h2>
<p>Open function shows GtkFileChooserDialog to a user and prompts them
to choose a file. Then it reads the file and puts the text into
GtkTextBuffer.</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="dt">void</span> tfe_text_view_open <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">,</span> GtkWindow <span class="op">*</span>win<span class="op">);</span></span></code></pre></div>
<div class="sourceCode" id="cb11"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> tfe_text_view_open <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">,</span> GtkWindow <span class="op">*</span>win<span class="op">);</span></span></code></pre></div>
<p>The parameter <code>win</code> is the top-level window. It will be a
transient parent window of GtkFileChooserDialog when the dialog is
created. This allows window managers to keep the dialog on top of the
parent window, or center the dialog over the parent window. It is
possible to give no parent window to the dialog. However, it is
encouraged to give a parent window to dialog. This function might be
called just after <code>tv</code> has been created. In that case,
<code>tv</code> has not been incorporated into the widget hierarchy.
Therefore it is impossible to get the top-level window from
<code>tv</code>. Thats why the function needs <code>win</code>
parameter.</p>
created.</p>
<p>This function may be called just after <code>tv</code> has been
created. In that case, <code>tv</code> has not been incorporated into
the widget hierarchy. Therefore it is impossible to get the top-level
window from <code>tv</code>. Thats why the function needs
<code>win</code> parameter.</p>
<p>This function is usually called when the buffer of <code>tv</code> is
empty. However, even if the buffer is not empty,
<code>tfe_text_view_open</code> doesnt treat it as an error. If you
want to revert the buffer, calling this function is appropriate.
Otherwise probably bad things will happen.</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> <span class="dt">void</span></span>
<span id="cb10-2"><a href="#cb10-2"></a>open_dialog_response<span class="op">(</span>GtkWidget <span class="op">*</span>dialog<span class="op">,</span> gint response<span class="op">,</span> TfeTextView <span class="op">*</span>tv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb10-3"><a href="#cb10-3"></a> GtkTextBuffer <span class="op">*</span>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="cb10-4"><a href="#cb10-4"></a> GFile <span class="op">*</span>file<span class="op">;</span></span>
<span id="cb10-5"><a href="#cb10-5"></a> <span class="dt">char</span> <span class="op">*</span>contents<span class="op">;</span></span>
<span id="cb10-6"><a href="#cb10-6"></a> gsize length<span class="op">;</span></span>
<span id="cb10-7"><a href="#cb10-7"></a> GtkWidget <span class="op">*</span>message_dialog<span class="op">;</span></span>
<span id="cb10-8"><a href="#cb10-8"></a> GError <span class="op">*</span>err <span class="op">=</span> NULL<span class="op">;</span></span>
<span id="cb10-9"><a href="#cb10-9"></a></span>
<span id="cb10-10"><a href="#cb10-10"></a> <span class="cf">if</span> <span class="op">(</span>response <span class="op">!=</span> GTK_RESPONSE_ACCEPT<span class="op">)</span></span>
<span id="cb10-11"><a href="#cb10-11"></a> g_signal_emit <span class="op">(</span>tv<span class="op">,</span> tfe_text_view_signals<span class="op">[</span>OPEN_RESPONSE<span class="op">],</span> <span class="dv">0</span><span class="op">,</span> TFE_OPEN_RESPONSE_CANCEL<span class="op">);</span></span>
<span id="cb10-12"><a href="#cb10-12"></a> <span class="cf">else</span> <span class="cf">if</span> <span class="op">(!</span> G_IS_FILE <span class="op">(</span>file <span class="op">=</span> gtk_file_chooser_get_file <span class="op">(</span>GTK_FILE_CHOOSER <span class="op">(</span>dialog<span class="op">))))</span> <span class="op">{</span></span>
<span id="cb10-13"><a href="#cb10-13"></a> g_warning <span class="op">(</span><span class="st">&quot;TfeTextView: gtk_file_chooser_get_file returns non GFile.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb10-14"><a href="#cb10-14"></a> g_signal_emit <span class="op">(</span>tv<span class="op">,</span> tfe_text_view_signals<span class="op">[</span>OPEN_RESPONSE<span class="op">],</span> <span class="dv">0</span><span class="op">,</span> TFE_OPEN_RESPONSE_ERROR<span class="op">);</span></span>
<span id="cb10-15"><a href="#cb10-15"></a> <span class="op">}</span> <span class="cf">else</span> <span class="cf">if</span> <span class="op">(!</span> g_file_load_contents <span class="op">(</span>file<span class="op">,</span> NULL<span class="op">,</span> <span class="op">&amp;</span>contents<span class="op">,</span> <span class="op">&amp;</span>length<span class="op">,</span> NULL<span class="op">,</span> <span class="op">&amp;</span>err<span class="op">))</span> <span class="op">{</span> <span class="co">/* read error */</span></span>
<span id="cb10-16"><a href="#cb10-16"></a> g_object_unref <span class="op">(</span>file<span class="op">);</span></span>
<span id="cb10-17"><a href="#cb10-17"></a> message_dialog <span class="op">=</span> gtk_message_dialog_new <span class="op">(</span>GTK_WINDOW <span class="op">(</span>dialog<span class="op">),</span> GTK_DIALOG_MODAL<span class="op">,</span></span>
<span id="cb10-18"><a href="#cb10-18"></a> GTK_MESSAGE_ERROR<span class="op">,</span> GTK_BUTTONS_CLOSE<span class="op">,</span></span>
<span id="cb10-19"><a href="#cb10-19"></a> <span class="st">&quot;%s.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">,</span> err<span class="op">-&gt;</span>message<span class="op">);</span></span>
<span id="cb10-20"><a href="#cb10-20"></a> g_signal_connect <span class="op">(</span>message_dialog<span class="op">,</span> <span class="st">&quot;response&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>gtk_window_destroy<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb10-21"><a href="#cb10-21"></a> gtk_widget_show <span class="op">(</span>message_dialog<span class="op">);</span></span>
<span id="cb10-22"><a href="#cb10-22"></a> g_error_free <span class="op">(</span>err<span class="op">);</span></span>
<span id="cb10-23"><a href="#cb10-23"></a> g_signal_emit <span class="op">(</span>tv<span class="op">,</span> tfe_text_view_signals<span class="op">[</span>OPEN_RESPONSE<span class="op">],</span> <span class="dv">0</span><span class="op">,</span> TFE_OPEN_RESPONSE_ERROR<span class="op">);</span></span>
<span id="cb10-24"><a href="#cb10-24"></a> <span class="op">}</span> <span class="cf">else</span> <span class="op">{</span></span>
<span id="cb10-25"><a href="#cb10-25"></a> gtk_text_buffer_set_text <span class="op">(</span>tb<span class="op">,</span> contents<span class="op">,</span> length<span class="op">);</span></span>
<span id="cb10-26"><a href="#cb10-26"></a> g_free <span class="op">(</span>contents<span class="op">);</span></span>
<span id="cb10-27"><a href="#cb10-27"></a> <span class="cf">if</span> <span class="op">(</span>G_IS_FILE <span class="op">(</span>tv<span class="op">-&gt;</span>file<span class="op">))</span></span>
<span id="cb10-28"><a href="#cb10-28"></a> g_object_unref <span class="op">(</span>tv<span class="op">-&gt;</span>file<span class="op">);</span></span>
<span id="cb10-29"><a href="#cb10-29"></a> tv<span class="op">-&gt;</span>file <span class="op">=</span> file<span class="op">;</span></span>
<span id="cb10-30"><a href="#cb10-30"></a> gtk_text_buffer_set_modified <span class="op">(</span>tb<span class="op">,</span> FALSE<span class="op">);</span></span>
<span id="cb10-31"><a href="#cb10-31"></a> g_signal_emit <span class="op">(</span>tv<span class="op">,</span> tfe_text_view_signals<span class="op">[</span>OPEN_RESPONSE<span class="op">],</span> <span class="dv">0</span><span class="op">,</span> TFE_OPEN_RESPONSE_SUCCESS<span class="op">);</span></span>
<span id="cb10-32"><a href="#cb10-32"></a> g_signal_emit <span class="op">(</span>tv<span class="op">,</span> tfe_text_view_signals<span class="op">[</span>CHANGE_FILE<span class="op">],</span> <span class="dv">0</span><span class="op">);</span></span>
<span id="cb10-33"><a href="#cb10-33"></a> <span class="op">}</span></span>
<span id="cb10-34"><a href="#cb10-34"></a> gtk_window_destroy <span class="op">(</span>GTK_WINDOW <span class="op">(</span>dialog<span class="op">));</span></span>
<span id="cb10-35"><a href="#cb10-35"></a><span class="op">}</span></span>
<span id="cb10-36"><a href="#cb10-36"></a></span>
<span id="cb10-37"><a href="#cb10-37"></a><span class="dt">void</span></span>
<span id="cb10-38"><a href="#cb10-38"></a>tfe_text_view_open <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">,</span> GtkWindow <span class="op">*</span>win<span class="op">)</span> <span class="op">{</span></span>
<span id="cb10-39"><a href="#cb10-39"></a> g_return_if_fail <span class="op">(</span>TFE_IS_TEXT_VIEW <span class="op">(</span>tv<span class="op">));</span></span>
<span id="cb10-40"><a href="#cb10-40"></a> g_return_if_fail <span class="op">(</span>GTK_IS_WINDOW <span class="op">(</span>win<span class="op">));</span></span>
<span id="cb10-41"><a href="#cb10-41"></a></span>
<span id="cb10-42"><a href="#cb10-42"></a> GtkWidget <span class="op">*</span>dialog<span class="op">;</span></span>
<span id="cb10-43"><a href="#cb10-43"></a></span>
<span id="cb10-44"><a href="#cb10-44"></a> dialog <span class="op">=</span> gtk_file_chooser_dialog_new <span class="op">(</span><span class="st">&quot;Open file&quot;</span><span class="op">,</span> win<span class="op">,</span> GTK_FILE_CHOOSER_ACTION_OPEN<span class="op">,</span></span>
<span id="cb10-45"><a href="#cb10-45"></a> <span class="st">&quot;Cancel&quot;</span><span class="op">,</span> GTK_RESPONSE_CANCEL<span class="op">,</span></span>
<span id="cb10-46"><a href="#cb10-46"></a> <span class="st">&quot;Open&quot;</span><span class="op">,</span> GTK_RESPONSE_ACCEPT<span class="op">,</span></span>
<span id="cb10-47"><a href="#cb10-47"></a> NULL<span class="op">);</span></span>
<span id="cb10-48"><a href="#cb10-48"></a> g_signal_connect <span class="op">(</span>dialog<span class="op">,</span> <span class="st">&quot;response&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>open_dialog_response<span class="op">),</span> tv<span class="op">);</span></span>
<span id="cb10-49"><a href="#cb10-49"></a> gtk_widget_show <span class="op">(</span>dialog<span class="op">);</span></span>
<span id="cb10-50"><a href="#cb10-50"></a><span class="op">}</span></span></code></pre></div>
want to revert the buffer, calling this function is appropriate.</p>
<p>Open and read process is divided into two phases. One is showing
GtkFileChooserDialog and the other is its response handler. The response
handler gets the filename, reads the contents of the file and puts it
into the GtkTextBuffer.</p>
<h3 id="open_dialog_response-function">open_dialog_response
function</h3>
<div class="sourceCode" id="cb12"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb12-1"><a href="#cb12-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb12-2"><a href="#cb12-2"></a>open_dialog_response<span class="op">(</span>GtkWidget <span class="op">*</span>dialog<span class="op">,</span> gint response<span class="op">,</span> TfeTextView <span class="op">*</span>tv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb12-3"><a href="#cb12-3"></a> GtkTextBuffer <span class="op">*</span>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="cb12-4"><a href="#cb12-4"></a> GFile <span class="op">*</span>file<span class="op">;</span></span>
<span id="cb12-5"><a href="#cb12-5"></a> <span class="dt">char</span> <span class="op">*</span>contents<span class="op">;</span></span>
<span id="cb12-6"><a href="#cb12-6"></a> gsize length<span class="op">;</span></span>
<span id="cb12-7"><a href="#cb12-7"></a> GtkWidget <span class="op">*</span>message_dialog<span class="op">;</span></span>
<span id="cb12-8"><a href="#cb12-8"></a> GError <span class="op">*</span>err <span class="op">=</span> NULL<span class="op">;</span></span>
<span id="cb12-9"><a href="#cb12-9"></a></span>
<span id="cb12-10"><a href="#cb12-10"></a> <span class="cf">if</span> <span class="op">(</span>response <span class="op">!=</span> GTK_RESPONSE_ACCEPT<span class="op">)</span></span>
<span id="cb12-11"><a href="#cb12-11"></a> g_signal_emit <span class="op">(</span>tv<span class="op">,</span> tfe_text_view_signals<span class="op">[</span>OPEN_RESPONSE<span class="op">],</span> <span class="dv">0</span><span class="op">,</span> TFE_OPEN_RESPONSE_CANCEL<span class="op">);</span></span>
<span id="cb12-12"><a href="#cb12-12"></a> <span class="cf">else</span> <span class="cf">if</span> <span class="op">(!</span> G_IS_FILE <span class="op">(</span>file <span class="op">=</span> gtk_file_chooser_get_file <span class="op">(</span>GTK_FILE_CHOOSER <span class="op">(</span>dialog<span class="op">))))</span> <span class="op">{</span></span>
<span id="cb12-13"><a href="#cb12-13"></a> g_warning <span class="op">(</span><span class="st">&quot;TfeTextView: gtk_file_chooser_get_file returns non GFile.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb12-14"><a href="#cb12-14"></a> g_signal_emit <span class="op">(</span>tv<span class="op">,</span> tfe_text_view_signals<span class="op">[</span>OPEN_RESPONSE<span class="op">],</span> <span class="dv">0</span><span class="op">,</span> TFE_OPEN_RESPONSE_ERROR<span class="op">);</span></span>
<span id="cb12-15"><a href="#cb12-15"></a> <span class="op">}</span> <span class="cf">else</span> <span class="cf">if</span> <span class="op">(!</span> g_file_load_contents <span class="op">(</span>file<span class="op">,</span> NULL<span class="op">,</span> <span class="op">&amp;</span>contents<span class="op">,</span> <span class="op">&amp;</span>length<span class="op">,</span> NULL<span class="op">,</span> <span class="op">&amp;</span>err<span class="op">))</span> <span class="op">{</span> <span class="co">/* read error */</span></span>
<span id="cb12-16"><a href="#cb12-16"></a> g_object_unref <span class="op">(</span>file<span class="op">);</span></span>
<span id="cb12-17"><a href="#cb12-17"></a> message_dialog <span class="op">=</span> gtk_message_dialog_new <span class="op">(</span>GTK_WINDOW <span class="op">(</span>dialog<span class="op">),</span> GTK_DIALOG_MODAL<span class="op">,</span></span>
<span id="cb12-18"><a href="#cb12-18"></a> GTK_MESSAGE_ERROR<span class="op">,</span> GTK_BUTTONS_CLOSE<span class="op">,</span> <span class="st">&quot;%s&quot;</span><span class="op">,</span> err<span class="op">-&gt;</span>message<span class="op">);</span></span>
<span id="cb12-19"><a href="#cb12-19"></a> g_signal_connect <span class="op">(</span>message_dialog<span class="op">,</span> <span class="st">&quot;response&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>gtk_window_destroy<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb12-20"><a href="#cb12-20"></a> gtk_widget_show <span class="op">(</span>message_dialog<span class="op">);</span></span>
<span id="cb12-21"><a href="#cb12-21"></a> g_error_free <span class="op">(</span>err<span class="op">);</span></span>
<span id="cb12-22"><a href="#cb12-22"></a> g_signal_emit <span class="op">(</span>tv<span class="op">,</span> tfe_text_view_signals<span class="op">[</span>OPEN_RESPONSE<span class="op">],</span> <span class="dv">0</span><span class="op">,</span> TFE_OPEN_RESPONSE_ERROR<span class="op">);</span></span>
<span id="cb12-23"><a href="#cb12-23"></a> <span class="op">}</span> <span class="cf">else</span> <span class="op">{</span></span>
<span id="cb12-24"><a href="#cb12-24"></a> gtk_text_buffer_set_text <span class="op">(</span>tb<span class="op">,</span> contents<span class="op">,</span> length<span class="op">);</span></span>
<span id="cb12-25"><a href="#cb12-25"></a> g_free <span class="op">(</span>contents<span class="op">);</span></span>
<span id="cb12-26"><a href="#cb12-26"></a> gtk_text_buffer_set_modified <span class="op">(</span>tb<span class="op">,</span> FALSE<span class="op">);</span></span>
<span id="cb12-27"><a href="#cb12-27"></a> <span class="co">// G_IS_FILE(tv-&gt;file) &amp;&amp; tv-&gt;file == file =&gt; unref(tv-&gt;file), tv-&gt;file=file, emit response with SUCCESS</span></span>
<span id="cb12-28"><a href="#cb12-28"></a> <span class="co">// G_IS_FILE(tv-&gt;file) &amp;&amp; tv-&gt;file != file =&gt; unref(tv-&gt;file), tv-&gt;file=file, emit response with SUCCESS, emit change-file</span></span>
<span id="cb12-29"><a href="#cb12-29"></a> <span class="co">// tv-&gt;file==NULL =&gt; tv-&gt;file=file, emit response with SUCCESS, emit change-file</span></span>
<span id="cb12-30"><a href="#cb12-30"></a> <span class="co">// The order is important. If you unref tv-&gt;file first, you can&#39;t compare tv-&gt;file and file anymore.</span></span>
<span id="cb12-31"><a href="#cb12-31"></a> <span class="co">// And open-response signal is emitted after new tv-&gt;file is set. Or the handler can&#39;t catch the new file.</span></span>
<span id="cb12-32"><a href="#cb12-32"></a> <span class="cf">if</span> <span class="op">(!</span> <span class="op">(</span>G_IS_FILE <span class="op">(</span>tv<span class="op">-&gt;</span>file<span class="op">)</span> <span class="op">&amp;&amp;</span> g_file_equal <span class="op">(</span>tv<span class="op">-&gt;</span>file<span class="op">,</span> file<span class="op">)))</span></span>
<span id="cb12-33"><a href="#cb12-33"></a> g_signal_emit <span class="op">(</span>tv<span class="op">,</span> tfe_text_view_signals<span class="op">[</span>CHANGE_FILE<span class="op">],</span> <span class="dv">0</span><span class="op">);</span></span>
<span id="cb12-34"><a href="#cb12-34"></a> <span class="cf">if</span> <span class="op">(</span>G_IS_FILE <span class="op">(</span>tv<span class="op">-&gt;</span>file<span class="op">))</span></span>
<span id="cb12-35"><a href="#cb12-35"></a> g_object_unref <span class="op">(</span>tv<span class="op">-&gt;</span>file<span class="op">);</span></span>
<span id="cb12-36"><a href="#cb12-36"></a> tv<span class="op">-&gt;</span>file <span class="op">=</span> file<span class="op">;</span> <span class="co">// The ownership of &#39;file&#39; moves to TfeTextView</span></span>
<span id="cb12-37"><a href="#cb12-37"></a> g_signal_emit <span class="op">(</span>tv<span class="op">,</span> tfe_text_view_signals<span class="op">[</span>OPEN_RESPONSE<span class="op">],</span> <span class="dv">0</span><span class="op">,</span> TFE_OPEN_RESPONSE_SUCCESS<span class="op">);</span></span>
<span id="cb12-38"><a href="#cb12-38"></a> <span class="op">}</span></span>
<span id="cb12-39"><a href="#cb12-39"></a> gtk_window_destroy <span class="op">(</span>GTK_WINDOW <span class="op">(</span>dialog<span class="op">));</span></span>
<span id="cb12-40"><a href="#cb12-40"></a><span class="op">}</span></span></code></pre></div>
<ul>
<li>37-50: <code>tfe_text_view_open</code> function.</li>
<li>44-47: Creates GtkFileChooserDialog. The title is “Open file”.
Transient parent window is the top-level window of the application,
which is given by the caller. The action is open mode. The buttons are
Cancel and Open.</li>
<li>48: connects the “response” signal of the dialog and
<code>open_dialog_response</code> signal handler.</li>
<li>49: Shows the dialog.</li>
<li>1-35: <code>open_dialog_response</code> signal handler.</li>
<li>10-11: If the response from GtkFileChooserDialog is not
<code>GTK_RESPONSE_ACCEPT</code>, the user has clicked on the “Cancel”
button or close button on the header bar. Then, “open-response” signal
is emitted. The parameter of the signal is
<code>TFE_OPEN_RESPONSE_CANCEL</code>.</li>
<li>12-14: Gets the pointer to the Gfile by
<li>2: The handler <code>open_dialog_response</code> has three
parameters.
<ul>
<li>GtkWidget *dialog: FileChooserDialog on which the “response” signal
is emitted.</li>
<li>gint response: response ID</li>
<li>TfeTextView *tv: textview to put the contents to</li>
</ul></li>
<li>10-11: If the response is not <code>GTK_RESPONSE_ACCEPT</code>, the
user has clicked on the “Cancel” button or close button on the header
bar. Then, “open-response” signal is emitted. The parameter of the
signal is <code>TFE_OPEN_RESPONSE_CANCEL</code>.</li>
<li>12-14: Gets the pointer to the GFile by
<code>gtk_file_chooser_get_file</code>. If it doesnt point GFile, maybe
an error has occurred. Then it emits “open-response” signal with the
parameter <code>TFE_OPEN_RESPONSE_ERROR</code>.</li>
<li>15-23: If an error occurs at file reading, then it decreases the
reference count of the Gfile, shows a message dialog to report the error
an error has occurred. It is not expected, though. Then it emits
“open-response” signal with the parameter
<code>TFE_OPEN_RESPONSE_ERROR</code>.</li>
<li>15-22: If an error occurs when file reading, then it decreases the
reference count of the GFile, shows a message dialog to report the error
to the user and emits “open-response” signal with the parameter
<code>TFE_OPEN_RESPONSE_ERROR</code>.</li>
<li>24-33: If the file has successfully been read, then the text is
inserted to GtkTextBuffer, frees the temporary buffer pointed by
<code>contents</code> and sets <code>tv-&gt;file</code> to point the
file (no duplication is not necessary). Then, it emits “open-response”
signal with the parameter <code>TFE_OPEN_RESPONSE_SUCCESS</code> and
emits “change-file” signal.</li>
<li>34: destroys GtkFileCooserDialog.</li>
<li>23-38: If the file has been successfully read, the following is
carried out. (It is not simple.)
<ul>
<li>the text is inserted to GtkTextBuffer</li>
<li>the temporary buffer <code>contents</code> is freed</li>
<li>modify-bit is set to FALSE</li>
<li>open-response signal is emitted with
<code>TFE_OPEN_REPONSE_SUCCESS</code></li>
<li>If the file are changed, change-file signal is emitted</li>
<li>If <code>tv-&gt;file</code> isnt NULL,
<code>g_object_unref(tv-&gt;file)</code> is called</li>
<li><code>tv-&gt;file</code> is assigned with <code>file</code></li>
</ul></li>
<li>39: destroys GtkFileChooserDialog.</li>
</ul>
<p>Now lets think about the whole process between the caller and
TfeTextView. It is shown in the following diagram and you would think
that it is really complicated. Because signal is the only way for
GtkFileChooserDialog to communicate with others. In GTK 3,
<code>gtk_dialog_run</code> function is available. It simplifies the
process. However, in GTK 4, <code>gtk_dialog_run</code> is unavailable
any more.</p>
<h3 id="tfe_text_view_open-function">tfe_text_view_open function</h3>
<div class="sourceCode" id="cb13"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb13-1"><a href="#cb13-1"></a><span class="dt">void</span></span>
<span id="cb13-2"><a href="#cb13-2"></a>tfe_text_view_open <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">,</span> GtkWindow <span class="op">*</span>win<span class="op">)</span> <span class="op">{</span></span>
<span id="cb13-3"><a href="#cb13-3"></a> g_return_if_fail <span class="op">(</span>TFE_IS_TEXT_VIEW <span class="op">(</span>tv<span class="op">));</span></span>
<span id="cb13-4"><a href="#cb13-4"></a> <span class="co">// &#39;win&#39; is used for a transient window of the GtkFileChooserDialog.</span></span>
<span id="cb13-5"><a href="#cb13-5"></a> <span class="co">// It can be NULL.</span></span>
<span id="cb13-6"><a href="#cb13-6"></a> g_return_if_fail <span class="op">(</span>GTK_IS_WINDOW <span class="op">(</span>win<span class="op">)</span> <span class="op">||</span> win <span class="op">==</span> NULL<span class="op">);</span></span>
<span id="cb13-7"><a href="#cb13-7"></a></span>
<span id="cb13-8"><a href="#cb13-8"></a> GtkWidget <span class="op">*</span>dialog<span class="op">;</span></span>
<span id="cb13-9"><a href="#cb13-9"></a></span>
<span id="cb13-10"><a href="#cb13-10"></a> dialog <span class="op">=</span> gtk_file_chooser_dialog_new <span class="op">(</span><span class="st">&quot;Open file&quot;</span><span class="op">,</span> win<span class="op">,</span> GTK_FILE_CHOOSER_ACTION_OPEN<span class="op">,</span></span>
<span id="cb13-11"><a href="#cb13-11"></a> <span class="st">&quot;Cancel&quot;</span><span class="op">,</span> GTK_RESPONSE_CANCEL<span class="op">,</span> <span class="st">&quot;Open&quot;</span><span class="op">,</span> GTK_RESPONSE_ACCEPT<span class="op">,</span> NULL<span class="op">);</span></span>
<span id="cb13-12"><a href="#cb13-12"></a> g_signal_connect <span class="op">(</span>dialog<span class="op">,</span> <span class="st">&quot;response&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>open_dialog_response<span class="op">),</span> tv<span class="op">);</span></span>
<span id="cb13-13"><a href="#cb13-13"></a> gtk_widget_show <span class="op">(</span>dialog<span class="op">);</span></span>
<span id="cb13-14"><a href="#cb13-14"></a><span class="op">}</span></span></code></pre></div>
<ul>
<li>3-4: Check the type of the arguments <code>tv</code> and
<code>win</code>. Public functions always need to check the
arguments.</li>
<li>10-11: Creates GtkFileChooserDialog.
<ul>
<li>The title is “Open file”.</li>
<li>Transient parent window is the top-level window
<code>win</code>.</li>
<li>The action is open mode.</li>
<li>The buttons are Cancel and Open.</li>
</ul></li>
<li>12: connects the “response” signal and the handler.</li>
<li>13: Shows the dialog.</li>
</ul>
<p>The whole process between the caller and TfeTextView is shown in the
following diagram. It is really complicated. Because signal is the only
way for GtkFileChooserDialog to communicate with others.</p>
<figure>
<img src="image/open.png" alt="Caller and TfeTextView" />
<figcaption aria-hidden="true">Caller and TfeTextView</figcaption>
@ -560,33 +686,34 @@ select a file from GtkFileChooserDialog.</li>
and emits a signal to inform the status as a response code.</li>
<li>The handler out of the TfeTextView receives the signal.</li>
</ol>
<h2 id="getting-gfile">Getting Gfile</h2>
<p><code>gtk_text_view_get_file</code> is a simple function shown as
follows.</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>GFile <span class="op">*</span></span>
<span id="cb11-2"><a href="#cb11-2"></a>tfe_text_view_get_file <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb11-3"><a href="#cb11-3"></a> g_return_val_if_fail <span class="op">(</span>TFE_IS_TEXT_VIEW <span class="op">(</span>tv<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb11-4"><a href="#cb11-4"></a></span>
<span id="cb11-5"><a href="#cb11-5"></a> <span class="cf">if</span> <span class="op">(</span>G_IS_FILE <span class="op">(</span>tv<span class="op">-&gt;</span>file<span class="op">))</span></span>
<span id="cb11-6"><a href="#cb11-6"></a> <span class="cf">return</span> g_file_dup <span class="op">(</span>tv<span class="op">-&gt;</span>file<span class="op">);</span></span>
<span id="cb11-7"><a href="#cb11-7"></a> <span class="cf">else</span></span>
<span id="cb11-8"><a href="#cb11-8"></a> <span class="cf">return</span> NULL<span class="op">;</span></span>
<span id="cb11-9"><a href="#cb11-9"></a><span class="op">}</span></span></code></pre></div>
<h2 id="getting-gfile-in-tfetextview">Getting GFile in TfeTextView</h2>
<p>You can get the GFile in a TfeTextView instance with
<code>tfe_text_view_get_file</code>. It is very simple.</p>
<div class="sourceCode" id="cb14"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb14-1"><a href="#cb14-1"></a>GFile <span class="op">*</span></span>
<span id="cb14-2"><a href="#cb14-2"></a>tfe_text_view_get_file <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb14-3"><a href="#cb14-3"></a> g_return_val_if_fail <span class="op">(</span>TFE_IS_TEXT_VIEW <span class="op">(</span>tv<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb14-4"><a href="#cb14-4"></a></span>
<span id="cb14-5"><a href="#cb14-5"></a> <span class="cf">if</span> <span class="op">(</span>G_IS_FILE <span class="op">(</span>tv<span class="op">-&gt;</span>file<span class="op">))</span></span>
<span id="cb14-6"><a href="#cb14-6"></a> <span class="cf">return</span> g_file_dup <span class="op">(</span>tv<span class="op">-&gt;</span>file<span class="op">);</span></span>
<span id="cb14-7"><a href="#cb14-7"></a> <span class="cf">else</span></span>
<span id="cb14-8"><a href="#cb14-8"></a> <span class="cf">return</span> NULL<span class="op">;</span></span>
<span id="cb14-9"><a href="#cb14-9"></a><span class="op">}</span></span></code></pre></div>
<p>The important thing is to duplicate <code>tv-&gt;file</code>.
Otherwise, if the caller frees the GFile object,
<code>tv-&gt;file</code> is no more guaranteed to point the GFile.
Another reason to use <code>g_file_dup</code> is that GFile isnt
thread-safe. If you use GFile in the different thread, the duplication
is necessary. See <a
href="https://docs.gtk.org/gio/method.File.dup.html">Gio API Reference,
href="https://docs.gtk.org/gio/method.File.dup.html">Gio API Reference
g_file_dup</a>.</p>
<h2 id="the-api-document-and-source-file-of-tfetextview.c">The API
document and source file of tfetextview.c</h2>
<p>Refer API document of TfeTextView. Its original markdown file is
under the directory <code>src/tfetextview</code>.</p>
<p>All the source files are listed in <a href="sec16.html">Section
16</a>. You can find them under src/tfe5 and src/tfetextview
<p>Refer <a
href="https://toshiocp.github.io/Gtk4-tutorial/tfetextview_doc.html">API
document of TfeTextView</a>. The markdown file is under the directory
<code>src/tfetextview</code>.</p>
<p>You can find all the TfeTextView source codes under src/tfetextview
directories.</p>
</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>

View file

@ -5,7 +5,7 @@
<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>Gtk4 tutorial</title>
<title>GTK 4 tutorial</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
@ -112,30 +112,43 @@
</div>
</nav>
<h1 id="functions-in-gtknotebook">Functions in GtkNotebook</h1>
<p>GtkNotebook is a very important object in the text file editor <code>tfe</code>. It connects the application and TfeTextView objects. A set of public functions are declared in <code>tfenotebook.h</code>. The word “tfenotebook” is used only in filenames. Theres no “TfeNotebook” object.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb1-1"><a href="#cb1-1"></a><span class="dt">void</span></span>
<span id="cb1-2"><a href="#cb1-2"></a>notebook_page_save(GtkNotebook *nb);</span>
<p>GtkNotebook is a very important object in the text file editor
<code>tfe</code>. It connects the application and TfeTextView objects. A
set of public functions are declared in <code>tfenotebook.h</code>. The
word “tfenotebook” is used only in filenames. Theres no “TfeNotebook”
object.</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb1-1"><a href="#cb1-1"></a><span class="dt">void</span></span>
<span id="cb1-2"><a href="#cb1-2"></a>notebook_page_save<span class="op">(</span>GtkNotebook <span class="op">*</span>nb<span class="op">);</span></span>
<span id="cb1-3"><a href="#cb1-3"></a></span>
<span id="cb1-4"><a href="#cb1-4"></a><span class="dt">void</span></span>
<span id="cb1-5"><a href="#cb1-5"></a>notebook_page_close (GtkNotebook *nb);</span>
<span id="cb1-5"><a href="#cb1-5"></a>notebook_page_close <span class="op">(</span>GtkNotebook <span class="op">*</span>nb<span class="op">);</span></span>
<span id="cb1-6"><a href="#cb1-6"></a></span>
<span id="cb1-7"><a href="#cb1-7"></a><span class="dt">void</span></span>
<span id="cb1-8"><a href="#cb1-8"></a>notebook_page_open (GtkNotebook *nb);</span>
<span id="cb1-8"><a href="#cb1-8"></a>notebook_page_open <span class="op">(</span>GtkNotebook <span class="op">*</span>nb<span class="op">);</span></span>
<span id="cb1-9"><a href="#cb1-9"></a></span>
<span id="cb1-10"><a href="#cb1-10"></a><span class="dt">void</span></span>
<span id="cb1-11"><a href="#cb1-11"></a>notebook_page_new_with_file (GtkNotebook *nb, GFile *file);</span>
<span id="cb1-11"><a href="#cb1-11"></a>notebook_page_new_with_file <span class="op">(</span>GtkNotebook <span class="op">*</span>nb<span class="op">,</span> GFile <span class="op">*</span>file<span class="op">);</span></span>
<span id="cb1-12"><a href="#cb1-12"></a></span>
<span id="cb1-13"><a href="#cb1-13"></a><span class="dt">void</span></span>
<span id="cb1-14"><a href="#cb1-14"></a>notebook_page_new (GtkNotebook *nb);</span></code></pre></div>
<p>This header file describes the public functions in <code>tfenotebook.c</code>.</p>
<span id="cb1-14"><a href="#cb1-14"></a>notebook_page_new <span class="op">(</span>GtkNotebook <span class="op">*</span>nb<span class="op">);</span></span></code></pre></div>
<p>This header file describes the public functions in
<code>tfenotebook.c</code>.</p>
<ul>
<li>1-2: <code>notebook_page_save</code> saves the current page to the file of which the name specified in the tab. If the name is <code>untitled</code> or <code>untitled</code> followed by digits, FileChooserDialog appears and a user can choose or specify a filename.</li>
<li>1-2: <code>notebook_page_save</code> saves the current page to the
file of which the name specified in the tab. If the name is
<code>untitled</code> or <code>untitled</code> followed by digits,
FileChooserDialog appears and a user can choose or specify a
filename.</li>
<li>4-5: <code>notebook_page_close</code> closes the current page.</li>
<li>7-8: <code>notebook_page_open</code> shows a file chooser dialog and a user can choose a file. The file is inserted to a new page.</li>
<li>10-11: <code>notebook_page_new_with_file</code> creates a new page and the file given as an argument is read and inserted into the page.</li>
<li>7-8: <code>notebook_page_open</code> shows a file chooser dialog and
a user can choose a file. The file is inserted to a new page.</li>
<li>10-11: <code>notebook_page_new_with_file</code> creates a new page
and a file given as an argument is read and inserted into the page.</li>
<li>13-14: <code>notebook_page_new</code> creates a new empty page.</li>
</ul>
<p>You probably find that the functions except <code>notebook_page_close</code> are higher level functions of</p>
<p>You probably find that the functions except
<code>notebook_page_close</code> are higher level functions of</p>
<ul>
<li><code>tfe_text_view_save</code></li>
<li><code>tef_text_view_open</code></li>
@ -143,199 +156,298 @@
<li><code>tfe_text_view_new</code></li>
</ul>
<p>respectively.</p>
<p>There are two layers. One of them is <code>tfe_text_view ...</code>, which is the lower level layer. The other is <code>note_book ...</code>, which is the higher level layer.</p>
<p>There are two layers. One of them is <code>tfe_text_view ...</code>,
which is the lower level layer. The other is <code>note_book ...</code>,
which is the higher level layer.</p>
<p>Now lets look at the program of each function.</p>
<h2 id="notebook_page_new">notebook_page_new</h2>
<div class="sourceCode" id="cb2"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb2-1"><a href="#cb2-1"></a><span class="dt">static</span> gchar*</span>
<span id="cb2-2"><a href="#cb2-2"></a>get_untitled () {</span>
<span id="cb2-3"><a href="#cb2-3"></a> <span class="dt">static</span> <span class="dt">int</span> c = -<span class="dv">1</span>;</span>
<span id="cb2-4"><a href="#cb2-4"></a> <span class="cf">if</span> (++c == <span class="dv">0</span>) </span>
<span id="cb2-5"><a href="#cb2-5"></a> <span class="cf">return</span> g_strdup_printf(<span class="st">&quot;Untitled&quot;</span>);</span>
<div class="sourceCode" id="cb2"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb2-1"><a href="#cb2-1"></a><span class="dt">static</span> <span class="dt">char</span><span class="op">*</span></span>
<span id="cb2-2"><a href="#cb2-2"></a>get_untitled <span class="op">()</span> <span class="op">{</span></span>
<span id="cb2-3"><a href="#cb2-3"></a> <span class="dt">static</span> <span class="dt">int</span> c <span class="op">=</span> <span class="op">-</span><span class="dv">1</span><span class="op">;</span></span>
<span id="cb2-4"><a href="#cb2-4"></a> <span class="cf">if</span> <span class="op">(++</span>c <span class="op">==</span> <span class="dv">0</span><span class="op">)</span> </span>
<span id="cb2-5"><a href="#cb2-5"></a> <span class="cf">return</span> g_strdup_printf<span class="op">(</span><span class="st">&quot;Untitled&quot;</span><span class="op">);</span></span>
<span id="cb2-6"><a href="#cb2-6"></a> <span class="cf">else</span></span>
<span id="cb2-7"><a href="#cb2-7"></a> <span class="cf">return</span> g_strdup_printf (<span class="st">&quot;Untitled%u&quot;</span>, c);</span>
<span id="cb2-8"><a href="#cb2-8"></a>}</span>
<span id="cb2-7"><a href="#cb2-7"></a> <span class="cf">return</span> g_strdup_printf <span class="op">(</span><span class="st">&quot;Untitled%u&quot;</span><span class="op">,</span> c<span class="op">);</span></span>
<span id="cb2-8"><a href="#cb2-8"></a><span class="op">}</span></span>
<span id="cb2-9"><a href="#cb2-9"></a></span>
<span id="cb2-10"><a href="#cb2-10"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb2-11"><a href="#cb2-11"></a>notebook_page_build (GtkNotebook *nb, GtkWidget *tv, <span class="dt">char</span> *filename) {</span>
<span id="cb2-12"><a href="#cb2-12"></a> GtkWidget *scr = gtk_scrolled_window_new ();</span>
<span id="cb2-13"><a href="#cb2-13"></a> GtkNotebookPage *nbp;</span>
<span id="cb2-14"><a href="#cb2-14"></a> GtkWidget *lab;</span>
<span id="cb2-15"><a href="#cb2-15"></a> <span class="dt">int</span> i;</span>
<span id="cb2-11"><a href="#cb2-11"></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">const</span> <span class="dt">char</span> <span class="op">*</span>filename<span class="op">)</span> <span class="op">{</span></span>
<span id="cb2-12"><a href="#cb2-12"></a> GtkWidget <span class="op">*</span>scr <span class="op">=</span> gtk_scrolled_window_new <span class="op">();</span></span>
<span id="cb2-13"><a href="#cb2-13"></a> GtkNotebookPage <span class="op">*</span>nbp<span class="op">;</span></span>
<span id="cb2-14"><a href="#cb2-14"></a> GtkWidget <span class="op">*</span>lab<span class="op">;</span></span>
<span id="cb2-15"><a href="#cb2-15"></a> <span class="dt">int</span> i<span class="op">;</span></span>
<span id="cb2-16"><a href="#cb2-16"></a></span>
<span id="cb2-17"><a href="#cb2-17"></a> gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);</span>
<span id="cb2-18"><a href="#cb2-18"></a> gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);</span>
<span id="cb2-19"><a href="#cb2-19"></a> lab = gtk_label_new (filename);</span>
<span id="cb2-20"><a href="#cb2-20"></a> i = gtk_notebook_append_page (nb, scr, lab);</span>
<span id="cb2-21"><a href="#cb2-21"></a> nbp = gtk_notebook_get_page (nb, scr);</span>
<span id="cb2-22"><a href="#cb2-22"></a> g_object_set (nbp, <span class="st">&quot;tab-expand&quot;</span>, TRUE, NULL);</span>
<span id="cb2-23"><a href="#cb2-23"></a> gtk_notebook_set_current_page (nb, i);</span>
<span id="cb2-24"><a href="#cb2-24"></a> g_signal_connect (GTK_TEXT_VIEW (tv), <span class="st">&quot;change-file&quot;</span>, G_CALLBACK (file_changed_cb), nb);</span>
<span id="cb2-25"><a href="#cb2-25"></a>}</span>
<span id="cb2-26"><a href="#cb2-26"></a></span>
<span id="cb2-27"><a href="#cb2-27"></a><span class="dt">void</span></span>
<span id="cb2-28"><a href="#cb2-28"></a>notebook_page_new (GtkNotebook *nb) {</span>
<span id="cb2-29"><a href="#cb2-29"></a> g_return_if_fail(GTK_IS_NOTEBOOK (nb));</span>
<span id="cb2-30"><a href="#cb2-30"></a></span>
<span id="cb2-31"><a href="#cb2-31"></a> GtkWidget *tv;</span>
<span id="cb2-32"><a href="#cb2-32"></a> <span class="dt">char</span> *filename;</span>
<span id="cb2-33"><a href="#cb2-33"></a></span>
<span id="cb2-34"><a href="#cb2-34"></a> <span class="cf">if</span> ((tv = tfe_text_view_new ()) == NULL)</span>
<span id="cb2-35"><a href="#cb2-35"></a> <span class="cf">return</span>;</span>
<span id="cb2-36"><a href="#cb2-36"></a> filename = get_untitled ();</span>
<span id="cb2-37"><a href="#cb2-37"></a> notebook_page_build (nb, tv, filename);</span>
<span id="cb2-38"><a href="#cb2-38"></a>}</span></code></pre></div>
<span id="cb2-17"><a href="#cb2-17"></a> gtk_scrolled_window_set_child <span class="op">(</span>GTK_SCROLLED_WINDOW <span class="op">(</span>scr<span class="op">),</span> tv<span class="op">);</span></span>
<span id="cb2-18"><a href="#cb2-18"></a> lab <span class="op">=</span> gtk_label_new <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb2-19"><a href="#cb2-19"></a> i <span class="op">=</span> gtk_notebook_append_page <span class="op">(</span>nb<span class="op">,</span> scr<span class="op">,</span> lab<span class="op">);</span></span>
<span id="cb2-20"><a href="#cb2-20"></a> nbp <span class="op">=</span> gtk_notebook_get_page <span class="op">(</span>nb<span class="op">,</span> scr<span class="op">);</span></span>
<span id="cb2-21"><a href="#cb2-21"></a> g_object_set <span class="op">(</span>nbp<span class="op">,</span> <span class="st">&quot;tab-expand&quot;</span><span class="op">,</span> TRUE<span class="op">,</span> NULL<span class="op">);</span></span>
<span id="cb2-22"><a href="#cb2-22"></a> gtk_notebook_set_current_page <span class="op">(</span>nb<span class="op">,</span> i<span class="op">);</span></span>
<span id="cb2-23"><a href="#cb2-23"></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> nb<span class="op">);</span></span>
<span id="cb2-24"><a href="#cb2-24"></a><span class="op">}</span></span>
<span id="cb2-25"><a href="#cb2-25"></a></span>
<span id="cb2-26"><a href="#cb2-26"></a><span class="dt">void</span></span>
<span id="cb2-27"><a href="#cb2-27"></a>notebook_page_new <span class="op">(</span>GtkNotebook <span class="op">*</span>nb<span class="op">)</span> <span class="op">{</span></span>
<span id="cb2-28"><a href="#cb2-28"></a> g_return_if_fail<span class="op">(</span>GTK_IS_NOTEBOOK <span class="op">(</span>nb<span class="op">));</span></span>
<span id="cb2-29"><a href="#cb2-29"></a></span>
<span id="cb2-30"><a href="#cb2-30"></a> GtkWidget <span class="op">*</span>tv<span class="op">;</span></span>
<span id="cb2-31"><a href="#cb2-31"></a> <span class="dt">char</span> <span class="op">*</span>filename<span class="op">;</span></span>
<span id="cb2-32"><a href="#cb2-32"></a></span>
<span id="cb2-33"><a href="#cb2-33"></a> <span class="cf">if</span> <span class="op">((</span>tv <span class="op">=</span> tfe_text_view_new <span class="op">())</span> <span class="op">==</span> NULL<span class="op">)</span></span>
<span id="cb2-34"><a href="#cb2-34"></a> <span class="cf">return</span><span class="op">;</span></span>
<span id="cb2-35"><a href="#cb2-35"></a> filename <span class="op">=</span> get_untitled <span class="op">();</span></span>
<span id="cb2-36"><a href="#cb2-36"></a> notebook_page_build <span class="op">(</span>nb<span class="op">,</span> tv<span class="op">,</span> filename<span class="op">);</span></span>
<span id="cb2-37"><a href="#cb2-37"></a> g_free <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb2-38"><a href="#cb2-38"></a><span class="op">}</span></span></code></pre></div>
<ul>
<li>27-38: <code>notebook_page_new</code> function.</li>
<li>29: <code>g_return_if_fail</code> is used to check the argument.</li>
<li>34: Creates TfeTextView object. If it fails, it returns to the caller.</li>
<li>36: Creates filename, which is “Untitled”, “Untitled1”, … .</li>
<li>26-38: <code>notebook_page_new</code> function.</li>
<li>28: <code>g_return_if_fail</code> is used to check the
argument.</li>
<li>33-34: Creates TfeTextView object. If it fails, no notebook page is
created and the function returns to the caller.</li>
<li>35: Creates filename, which is “Untitled”, “Untitled1”, … .</li>
<li>1-8: <code>get_untitled</code> function.</li>
<li>3: Static variable <code>c</code> is initialized at the first call of this function. After that <code>c</code> keeps its value unless it is changed explicitly.</li>
<li>4-7: Increases <code>c</code> by one and if it is zero then it returns “Untitled”. If it is a positive integer then it returns “Untitled&lt;the integer&gt;”, for example, “Untitled1”, “Untitled2”, and so on. The function <code>g_strdup_printf</code> creates a string and it should be freed by <code>g_free</code> when it becomes useless. The caller of <code>get_untitled</code> is in charge of freeing the string.</li>
<li>37: calls <code>notebook_page_build</code> to build the contents of the page.</li>
<li>10- 25: <code>notebook_page_build</code> function.</li>
<li>3: Static variable <code>c</code> is initialized at the first call
of this function. After that <code>c</code> keeps its value unless it is
changed explicitly.</li>
<li>4-7: Increases <code>c</code> by one and if it is zero, it returns
“Untitled”. If it is a positive integer, it returns “Untitled&lt;the
integer&gt;”, for example, “Untitled1”, “Untitled2”, and so on. The
function <code>g_strdup_printf</code> creates a string and it should be
freed by <code>g_free</code> when it becomes useless. The caller of
<code>get_untitled</code> is in charge of freeing the string.</li>
<li>36: calls <code>notebook_page_build</code> to build the contents of
the page.</li>
<li>37: frees <code>filename</code>.</li>
<li>10- 24: <code>notebook_page_build</code> function. A parameter with
<code>const</code> qualifier doesnt change in the function. It means
that the argument <code>filename</code> is owned by the caller. The
caller needs to free it when it becomes useless.</li>
<li>12: Creates GtkScrolledWindow.</li>
<li>17: Sets the wrap mode of <code>tv</code> to GTK_WRAP_WORD_CHAR so that lines are broken between words or graphemes.</li>
<li>18: Inserts <code>tv</code> to GtkscrolledWindow as a child.</li>
<li>19-20: Creates GtkLabel, then appends <code>scr</code> and <code>lab</code> to the GtkNotebook instance <code>nb</code>.</li>
<li>21-22: Sets “tab-expand” property to TRUE. The function <code>g_object_set</code> sets properties on an object. The object is any object derived from GObject. In many cases, an object has its own function to set its properties, but sometimes not. In that case, use <code>g_object_set</code> to set the property.</li>
<li>23: Sets the current page of <code>nb</code> to the newly created page.</li>
<li>24: Connects “change-file” signal and <code>file_changed_cb</code> handler.</li>
<li>17: Inserts <code>tv</code> to GtkscrolledWindow as a child.</li>
<li>18-19: Creates GtkLabel, then appends <code>scr</code> and
<code>lab</code> to the GtkNotebook instance <code>nb</code>.</li>
<li>20-21: Sets “tab-expand” property to TRUE. The function
<code>g_object_set</code> sets properties on an object. The object is
any object derived from GObject. In many cases, an object has its own
function to set its properties, but sometimes not. In that case, use
<code>g_object_set</code> to set the property.</li>
<li>22: Sets the current page to the newly created page.</li>
<li>23: Connects “change-file” signal and <code>file_changed_cb</code>
handler.</li>
</ul>
<h2 id="notebook_page_new_with_file">notebook_page_new_with_file</h2>
<div class="sourceCode" id="cb3"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb3-1"><a href="#cb3-1"></a><span class="dt">void</span></span>
<span id="cb3-2"><a href="#cb3-2"></a>notebook_page_new_with_file (GtkNotebook *nb, GFile *file) {</span>
<span id="cb3-3"><a href="#cb3-3"></a> g_return_if_fail(GTK_IS_NOTEBOOK (nb));</span>
<span id="cb3-4"><a href="#cb3-4"></a> g_return_if_fail(G_IS_FILE (file));</span>
<div class="sourceCode" id="cb3"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb3-1"><a href="#cb3-1"></a><span class="dt">void</span></span>
<span id="cb3-2"><a href="#cb3-2"></a>notebook_page_new_with_file <span class="op">(</span>GtkNotebook <span class="op">*</span>nb<span class="op">,</span> GFile <span class="op">*</span>file<span class="op">)</span> <span class="op">{</span></span>
<span id="cb3-3"><a href="#cb3-3"></a> g_return_if_fail<span class="op">(</span>GTK_IS_NOTEBOOK <span class="op">(</span>nb<span class="op">));</span></span>
<span id="cb3-4"><a href="#cb3-4"></a> g_return_if_fail<span class="op">(</span>G_IS_FILE <span class="op">(</span>file<span class="op">));</span></span>
<span id="cb3-5"><a href="#cb3-5"></a></span>
<span id="cb3-6"><a href="#cb3-6"></a> GtkWidget *tv;</span>
<span id="cb3-7"><a href="#cb3-7"></a> <span class="dt">char</span> *filename;</span>
<span id="cb3-6"><a href="#cb3-6"></a> GtkWidget <span class="op">*</span>tv<span class="op">;</span></span>
<span id="cb3-7"><a href="#cb3-7"></a> <span class="dt">char</span> <span class="op">*</span>filename<span class="op">;</span></span>
<span id="cb3-8"><a href="#cb3-8"></a></span>
<span id="cb3-9"><a href="#cb3-9"></a> <span class="cf">if</span> ((tv = tfe_text_view_new_with_file (file)) == NULL)</span>
<span id="cb3-10"><a href="#cb3-10"></a> <span class="cf">return</span>; <span class="co">/* read error */</span></span>
<span id="cb3-11"><a href="#cb3-11"></a> filename = g_file_get_basename (file);</span>
<span id="cb3-12"><a href="#cb3-12"></a> notebook_page_build (nb, tv, filename);</span>
<span id="cb3-13"><a href="#cb3-13"></a>}</span></code></pre></div>
<span id="cb3-9"><a href="#cb3-9"></a> <span class="cf">if</span> <span class="op">((</span>tv <span class="op">=</span> tfe_text_view_new_with_file <span class="op">(</span>file<span class="op">))</span> <span class="op">==</span> NULL<span class="op">)</span></span>
<span id="cb3-10"><a href="#cb3-10"></a> <span class="cf">return</span><span class="op">;</span> <span class="co">/* read error */</span></span>
<span id="cb3-11"><a href="#cb3-11"></a> filename <span class="op">=</span> g_file_get_basename <span class="op">(</span>file<span class="op">);</span></span>
<span id="cb3-12"><a href="#cb3-12"></a> notebook_page_build <span class="op">(</span>nb<span class="op">,</span> tv<span class="op">,</span> filename<span class="op">);</span></span>
<span id="cb3-13"><a href="#cb3-13"></a> g_free <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb3-14"><a href="#cb3-14"></a><span class="op">}</span></span></code></pre></div>
<ul>
<li>9-10: Calls <code>tfe_text_view_new_with_file</code>. If the function returns NULL, an error has happend. Then, it does nothing and returns.</li>
<li>11-12: Gets the filename and builds the contents of the page.</li>
<li>9-10: Calls <code>tfe_text_view_new_with_file</code>. If the
function returns NULL, an error has happend. Then, it does nothing and
returns.</li>
<li>11-13: Gets the filename, builds the contents of the page and frees
<code>filename</code>.</li>
</ul>
<h2 id="notebook_page_open">notebook_page_open</h2>
<div class="sourceCode" id="cb4"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb4-1"><a href="#cb4-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb4-2"><a href="#cb4-2"></a>open_response (TfeTextView *tv, <span class="dt">int</span> response, GtkNotebook *nb) {</span>
<span id="cb4-3"><a href="#cb4-3"></a> GFile *file;</span>
<span id="cb4-4"><a href="#cb4-4"></a> <span class="dt">char</span> *filename;</span>
<div class="sourceCode" id="cb4"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb4-1"><a href="#cb4-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb4-2"><a href="#cb4-2"></a>open_response <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">,</span> <span class="dt">int</span> response<span class="op">,</span> GtkNotebook <span class="op">*</span>nb<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-3"><a href="#cb4-3"></a> GFile <span class="op">*</span>file<span class="op">;</span></span>
<span id="cb4-4"><a href="#cb4-4"></a> <span class="dt">char</span> <span class="op">*</span>filename<span class="op">;</span></span>
<span id="cb4-5"><a href="#cb4-5"></a></span>
<span id="cb4-6"><a href="#cb4-6"></a> <span class="cf">if</span> (response != TFE_OPEN_RESPONSE_SUCCESS || ! G_IS_FILE (file = tfe_text_view_get_file (tv))) {</span>
<span id="cb4-7"><a href="#cb4-7"></a> g_object_ref_sink (tv);</span>
<span id="cb4-8"><a href="#cb4-8"></a> g_object_unref (tv);</span>
<span id="cb4-9"><a href="#cb4-9"></a> }<span class="cf">else</span> {</span>
<span id="cb4-10"><a href="#cb4-10"></a> filename = g_file_get_basename (file);</span>
<span id="cb4-11"><a href="#cb4-11"></a> g_object_unref (file);</span>
<span id="cb4-12"><a href="#cb4-12"></a> notebook_page_build (nb, GTK_WIDGET (tv), filename);</span>
<span id="cb4-13"><a href="#cb4-13"></a> }</span>
<span id="cb4-14"><a href="#cb4-14"></a>}</span>
<span id="cb4-15"><a href="#cb4-15"></a></span>
<span id="cb4-16"><a href="#cb4-16"></a><span class="dt">void</span></span>
<span id="cb4-17"><a href="#cb4-17"></a>notebook_page_open (GtkNotebook *nb) {</span>
<span id="cb4-18"><a href="#cb4-18"></a> g_return_if_fail(GTK_IS_NOTEBOOK (nb));</span>
<span id="cb4-19"><a href="#cb4-19"></a></span>
<span id="cb4-20"><a href="#cb4-20"></a> GtkWidget *tv;</span>
<span id="cb4-6"><a href="#cb4-6"></a> <span class="cf">if</span> <span class="op">(</span>response <span class="op">!=</span> TFE_OPEN_RESPONSE_SUCCESS<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-7"><a href="#cb4-7"></a> g_object_ref_sink <span class="op">(</span>tv<span class="op">);</span></span>
<span id="cb4-8"><a href="#cb4-8"></a> g_object_unref <span class="op">(</span>tv<span class="op">);</span></span>
<span id="cb4-9"><a href="#cb4-9"></a> <span class="op">}</span><span class="cf">else</span> <span class="op">{</span></span>
<span id="cb4-10"><a href="#cb4-10"></a> file <span class="op">=</span> tfe_text_view_get_file <span class="op">(</span>tv<span class="op">);</span></span>
<span id="cb4-11"><a href="#cb4-11"></a> filename <span class="op">=</span> g_file_get_basename <span class="op">(</span>file<span class="op">);</span></span>
<span id="cb4-12"><a href="#cb4-12"></a> g_object_unref <span class="op">(</span>file<span class="op">);</span></span>
<span id="cb4-13"><a href="#cb4-13"></a> notebook_page_build <span class="op">(</span>nb<span class="op">,</span> GTK_WIDGET <span class="op">(</span>tv<span class="op">),</span> filename<span class="op">);</span></span>
<span id="cb4-14"><a href="#cb4-14"></a> g_free <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb4-15"><a href="#cb4-15"></a> <span class="op">}</span></span>
<span id="cb4-16"><a href="#cb4-16"></a><span class="op">}</span></span>
<span id="cb4-17"><a href="#cb4-17"></a></span>
<span id="cb4-18"><a href="#cb4-18"></a><span class="dt">void</span></span>
<span id="cb4-19"><a href="#cb4-19"></a>notebook_page_open <span class="op">(</span>GtkNotebook <span class="op">*</span>nb<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-20"><a href="#cb4-20"></a> g_return_if_fail<span class="op">(</span>GTK_IS_NOTEBOOK <span class="op">(</span>nb<span class="op">));</span></span>
<span id="cb4-21"><a href="#cb4-21"></a></span>
<span id="cb4-22"><a href="#cb4-22"></a> <span class="cf">if</span> ((tv = tfe_text_view_new ()) == NULL)</span>
<span id="cb4-23"><a href="#cb4-23"></a> <span class="cf">return</span>;</span>
<span id="cb4-24"><a href="#cb4-24"></a> g_signal_connect (TFE_TEXT_VIEW (tv), <span class="st">&quot;open-response&quot;</span>, G_CALLBACK (open_response), nb);</span>
<span id="cb4-25"><a href="#cb4-25"></a> tfe_text_view_open (TFE_TEXT_VIEW (tv), GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW)));</span>
<span id="cb4-26"><a href="#cb4-26"></a>}</span></code></pre></div>
<span id="cb4-22"><a href="#cb4-22"></a> GtkWidget <span class="op">*</span>tv<span class="op">;</span></span>
<span id="cb4-23"><a href="#cb4-23"></a></span>
<span id="cb4-24"><a href="#cb4-24"></a> <span class="cf">if</span> <span class="op">((</span>tv <span class="op">=</span> tfe_text_view_new <span class="op">())</span> <span class="op">==</span> NULL<span class="op">)</span></span>
<span id="cb4-25"><a href="#cb4-25"></a> <span class="cf">return</span><span class="op">;</span></span>
<span id="cb4-26"><a href="#cb4-26"></a> g_signal_connect <span class="op">(</span>TFE_TEXT_VIEW <span class="op">(</span>tv<span class="op">),</span> <span class="st">&quot;open-response&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>open_response<span class="op">),</span> nb<span class="op">);</span></span>
<span id="cb4-27"><a href="#cb4-27"></a> tfe_text_view_open <span class="op">(</span>TFE_TEXT_VIEW <span class="op">(</span>tv<span class="op">),</span> GTK_WINDOW <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="cb4-28"><a href="#cb4-28"></a><span class="op">}</span></span></code></pre></div>
<ul>
<li>16-26: <code>notebook_page_open</code> function.</li>
<li>22-23: Creates TfeTextView object. If NULL is returned, an error has happened. Then, it returns to the caller.</li>
<li>24: Connects the signal “open-response” and the handler <code>open_response</code>.</li>
<li>25: Calls <code>tfe_text_view_open</code>. The “open-response” signal will be emitted later to inform the result of opening and reading a file.</li>
<li>1-14: <code>open_response</code> handler.</li>
<li>6-8: If the response code is NOT <code>TFE_OPEN_RESPONSE_SUCCESS</code> or <code>tfe_text_view_get_file</code> doesnt return the pointer to a GFile, it has failed to open and read a new file. Then, what <code>notebook_page_open</code> did in advance need to be canceled. The instance <code>tv</code> hasnt been a child widget of GtkScrolledWindow yet. Such instance has floating reference. Floating reference will be explained later in this subsection. You need to call <code>g_object_ref_sink</code> first. Then the floating reference is converted into an ordinary reference. Now you call <code>g_object_unref</code> to decrease the reference count by one.</li>
<li>9-13: Otherwise, everything is okay. Gets the filename, builds the contents of the page.</li>
<li>18-28: <code>notebook_page_open</code> function.</li>
<li>24-25: Creates TfeTextView object. If NULL is returned, an error has
happened. Then, it returns to the caller.</li>
<li>26: Connects the signal “open-response” and the handler
<code>open_response</code>.</li>
<li>27: Calls <code>tfe_text_view_open</code>. The “open-response”
signal will be emitted later to inform the result of opening and reading
a file.</li>
<li>1-16: <code>open_response</code> handler.</li>
<li>6-8: If the response code is not
<code>TFE_OPEN_RESPONSE_SUCCESS</code>, it has failed to open and read a
new file. Then, what <code>notebook_page_open</code> did in advance need
to be canceled. The instance <code>tv</code> hasnt been a child widget
of GtkScrolledWindow yet. Such instance has floating reference. Floating
reference will be explained later. You need to call
<code>g_object_ref_sink</code> first. Then the floating reference is
converted into an ordinary reference. Now you call
<code>g_object_unref</code> to decrease the reference count by one.</li>
<li>9-15: Otherwise, everything is okay. Gets the filename, builds the
contents of the page and frees <code>filename</code>.</li>
</ul>
<p>All the widgets are derived from GInitiallyUnowned. When an instance of GInitiallyUnowned or its descendant is created, the instance has a floating reference. The function <code>g_object_ref_sink</code> converts the floating reference into an ordinary reference. If the instance doesnt have a floating reference, <code>g_object_ref_sink</code> simply increases the reference count by one. On the other hand, when an instance of GObject (not GInitiallyUnowned) is created, no floating reference is given. And the instance has a normal reference count instead of floating reference.</p>
<p>If you use <code>g_object_unref</code> to an instance that has a floating reference, you need to convert the floating reference to a normal reference in advance. See <a href="https://developer-old.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#gobject-The-Base-Object-Type.description">GObject Reference Manual</a> for further information.</p>
<h2 id="floating-reference">Floating reference</h2>
<p>All the widgets are derived from GInitiallyUnowned. GObject and
GInitiallyUnowned are almost the same. The difference is like this. When
an instance of GInitiallyUnowned is created, the instance has a floating
reference and its reference count is zero. On the other hand, when an
instance of GObject (not GInitiallyUnowned) is created, no floating
reference is given. And the instance has a normal reference count
instead of floating reference. Their descendants inherits them, so every
widget has a floating reference at first. Non-widget class, for example,
GtkTextBuffer is a direct sub class of GObject and it doesnt have
floating reference. Its reference count is one when it is created.</p>
<p>The function <code>g_object_ref_sink</code> converts the floating
reference into an ordinary reference. If the instance doesnt have a
floating reference, <code>g_object_ref_sink</code> simply increases the
reference count by one. It is used when an widget is added to another
widget as a child.</p>
<pre><code>GtkTextView *tv = gtk_text_view_new (); // floating reference
GtkScrolledWindow *scr = gtk_scrolled_window_new ();
gtk_scrolled_window_set_child (scr, tv); // tv&#39;s reference count is one</code></pre>
<p>When <code>tv</code> is added to <code>scr</code> as a child,
<code>g_object_ref_sink</code> is used.</p>
<pre><code>g_object_ref_sink (tv);</code></pre>
<p>So, the floating reference is converted into an ordinary reference.
That is to say, floating reference is deleted, and reference count turns
to one. Thanks to this, the caller doesnt need to decrease tvs
reference count. If an Object_A is not a descendant of
GInitiallyUnowned, the program is like this:</p>
<pre><code>Object_A *obj_a = object_a_new (); // reference count is one
GtkScrolledWindow *scr = gtk_scrolled_window_new ();
gtk_scrolled_window_set_child (scr, obj_a); // obj_a&#39;s reference count is two
// obj_a is referred by the caller (this program) and scrolled window
g_object_unref (obj_a); // obj_a&#39;s reference count is one because the caller no longer refers obj_a.</code></pre>
<p>This example tells us that the caller needs to unref
<code>obj_a</code>.</p>
<p>If you use <code>g_object_unref</code> to an instance that has a
floating reference, you need to convert the floating reference to a
normal reference in advance. See <a
href="https://docs.gtk.org/gobject/floating-refs.html">GObject API
reference</a> for further information.</p>
<h2 id="notebook_page_close">notebook_page_close</h2>
<div class="sourceCode" id="cb5"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb5-1"><a href="#cb5-1"></a><span class="dt">void</span></span>
<span id="cb5-2"><a href="#cb5-2"></a>notebook_page_close (GtkNotebook *nb) {</span>
<span id="cb5-3"><a href="#cb5-3"></a> g_return_if_fail(GTK_IS_NOTEBOOK (nb));</span>
<span id="cb5-4"><a href="#cb5-4"></a></span>
<span id="cb5-5"><a href="#cb5-5"></a> GtkWidget *win;</span>
<span id="cb5-6"><a href="#cb5-6"></a> <span class="dt">int</span> i;</span>
<span id="cb5-7"><a href="#cb5-7"></a></span>
<span id="cb5-8"><a href="#cb5-8"></a> <span class="cf">if</span> (gtk_notebook_get_n_pages (nb) == <span class="dv">1</span>) {</span>
<span id="cb5-9"><a href="#cb5-9"></a> win = gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW);</span>
<span id="cb5-10"><a href="#cb5-10"></a> gtk_window_destroy(GTK_WINDOW (win));</span>
<span id="cb5-11"><a href="#cb5-11"></a> } <span class="cf">else</span> {</span>
<span id="cb5-12"><a href="#cb5-12"></a> i = gtk_notebook_get_current_page (nb);</span>
<span id="cb5-13"><a href="#cb5-13"></a> gtk_notebook_remove_page (GTK_NOTEBOOK (nb), i);</span>
<span id="cb5-14"><a href="#cb5-14"></a> }</span>
<span id="cb5-15"><a href="#cb5-15"></a>}</span></code></pre></div>
<p>This function closes the current page. If the page is the only page the notebook has, then the function destroys the top-level window and quits the application.</p>
<div class="sourceCode" id="cb8"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb8-1"><a href="#cb8-1"></a><span class="dt">void</span></span>
<span id="cb8-2"><a href="#cb8-2"></a>notebook_page_close <span class="op">(</span>GtkNotebook <span class="op">*</span>nb<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-3"><a href="#cb8-3"></a> g_return_if_fail<span class="op">(</span>GTK_IS_NOTEBOOK <span class="op">(</span>nb<span class="op">));</span></span>
<span id="cb8-4"><a href="#cb8-4"></a></span>
<span id="cb8-5"><a href="#cb8-5"></a> GtkWidget <span class="op">*</span>win<span class="op">;</span></span>
<span id="cb8-6"><a href="#cb8-6"></a> <span class="dt">int</span> i<span class="op">;</span></span>
<span id="cb8-7"><a href="#cb8-7"></a></span>
<span id="cb8-8"><a href="#cb8-8"></a> <span class="cf">if</span> <span class="op">(</span>gtk_notebook_get_n_pages <span class="op">(</span>nb<span class="op">)</span> <span class="op">==</span> <span class="dv">1</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-9"><a href="#cb8-9"></a> 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="cb8-10"><a href="#cb8-10"></a> gtk_window_destroy<span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">));</span></span>
<span id="cb8-11"><a href="#cb8-11"></a> <span class="op">}</span> <span class="cf">else</span> <span class="op">{</span></span>
<span id="cb8-12"><a href="#cb8-12"></a> i <span class="op">=</span> gtk_notebook_get_current_page <span class="op">(</span>nb<span class="op">);</span></span>
<span id="cb8-13"><a href="#cb8-13"></a> gtk_notebook_remove_page <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">),</span> i<span class="op">);</span></span>
<span id="cb8-14"><a href="#cb8-14"></a> <span class="op">}</span></span>
<span id="cb8-15"><a href="#cb8-15"></a><span class="op">}</span></span></code></pre></div>
<p>This function closes the current page. If the page is the only page
the notebook has, then the function destroys the top-level window and
quits the application.</p>
<ul>
<li>8-10: If the page is the only page the notebook has, it calls <code>gtk_window_destroy</code> to destroys the top-level window.</li>
<li>11-13: Otherwise, removes the current page.</li>
<li>8-10: If the page is the only page the notebook has, it calls
<code>gtk_window_destroy</code> to destroys the top-level window.</li>
<li>11-13: Otherwise, removes the current page. The child widget
(TfeTextView) is also destroyed.</li>
</ul>
<h2 id="notebook_page_save">notebook_page_save</h2>
<div class="sourceCode" id="cb6"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb6-1"><a href="#cb6-1"></a><span class="dt">static</span> TfeTextView *</span>
<span id="cb6-2"><a href="#cb6-2"></a>get_current_textview (GtkNotebook *nb) {</span>
<span id="cb6-3"><a href="#cb6-3"></a> <span class="dt">int</span> i;</span>
<span id="cb6-4"><a href="#cb6-4"></a> GtkWidget *scr;</span>
<span id="cb6-5"><a href="#cb6-5"></a> GtkWidget *tv;</span>
<span id="cb6-6"><a href="#cb6-6"></a></span>
<span id="cb6-7"><a href="#cb6-7"></a> i = gtk_notebook_get_current_page (nb);</span>
<span id="cb6-8"><a href="#cb6-8"></a> scr = gtk_notebook_get_nth_page (nb, i);</span>
<span id="cb6-9"><a href="#cb6-9"></a> tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));</span>
<span id="cb6-10"><a href="#cb6-10"></a> <span class="cf">return</span> TFE_TEXT_VIEW (tv);</span>
<span id="cb6-11"><a href="#cb6-11"></a>}</span>
<span id="cb6-12"><a href="#cb6-12"></a></span>
<span id="cb6-13"><a href="#cb6-13"></a><span class="dt">void</span></span>
<span id="cb6-14"><a href="#cb6-14"></a>notebook_page_save (GtkNotebook *nb) {</span>
<span id="cb6-15"><a href="#cb6-15"></a> g_return_if_fail(GTK_IS_NOTEBOOK (nb));</span>
<span id="cb6-16"><a href="#cb6-16"></a></span>
<span id="cb6-17"><a href="#cb6-17"></a> TfeTextView *tv;</span>
<span id="cb6-18"><a href="#cb6-18"></a></span>
<span id="cb6-19"><a href="#cb6-19"></a> tv = get_current_textview (nb);</span>
<span id="cb6-20"><a href="#cb6-20"></a> tfe_text_view_save (TFE_TEXT_VIEW (tv));</span>
<span id="cb6-21"><a href="#cb6-21"></a>}</span></code></pre></div>
<div class="sourceCode" id="cb9"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb9-1"><a href="#cb9-1"></a><span class="dt">static</span> TfeTextView <span class="op">*</span></span>
<span id="cb9-2"><a href="#cb9-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="cb9-3"><a href="#cb9-3"></a> <span class="dt">int</span> i<span class="op">;</span></span>
<span id="cb9-4"><a href="#cb9-4"></a> GtkWidget <span class="op">*</span>scr<span class="op">;</span></span>
<span id="cb9-5"><a href="#cb9-5"></a> GtkWidget <span class="op">*</span>tv<span class="op">;</span></span>
<span id="cb9-6"><a href="#cb9-6"></a></span>
<span id="cb9-7"><a href="#cb9-7"></a> i <span class="op">=</span> gtk_notebook_get_current_page <span class="op">(</span>nb<span class="op">);</span></span>
<span id="cb9-8"><a href="#cb9-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="cb9-9"><a href="#cb9-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="cb9-10"><a href="#cb9-10"></a> <span class="cf">return</span> TFE_TEXT_VIEW <span class="op">(</span>tv<span class="op">);</span></span>
<span id="cb9-11"><a href="#cb9-11"></a><span class="op">}</span></span>
<span id="cb9-12"><a href="#cb9-12"></a></span>
<span id="cb9-13"><a href="#cb9-13"></a><span class="dt">void</span></span>
<span id="cb9-14"><a href="#cb9-14"></a>notebook_page_save <span class="op">(</span>GtkNotebook <span class="op">*</span>nb<span class="op">)</span> <span class="op">{</span></span>
<span id="cb9-15"><a href="#cb9-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="cb9-16"><a href="#cb9-16"></a></span>
<span id="cb9-17"><a href="#cb9-17"></a> TfeTextView <span class="op">*</span>tv<span class="op">;</span></span>
<span id="cb9-18"><a href="#cb9-18"></a></span>
<span id="cb9-19"><a href="#cb9-19"></a> tv <span class="op">=</span> get_current_textview <span class="op">(</span>nb<span class="op">);</span></span>
<span id="cb9-20"><a href="#cb9-20"></a> tfe_text_view_save <span class="op">(</span>TFE_TEXT_VIEW <span class="op">(</span>tv<span class="op">));</span></span>
<span id="cb9-21"><a href="#cb9-21"></a><span class="op">}</span></span></code></pre></div>
<ul>
<li>13-21: <code>notebook_page_save</code>.</li>
<li>19: Gets TfeTextView belongs to the current page.</li>
<li>20: Calls <code>tfe_text_view_save</code>.</li>
<li>1-11: <code>get_current_textview</code>. This function gets the TfeTextView object belongs to the current page.</li>
<li>1-11: <code>get_current_textview</code>. This function gets the
TfeTextView object belongs to the current page.</li>
<li>7: Gets the page number of the current page.</li>
<li>8: Gets the child widget <code>scr</code>, which is a GtkScrolledWindow instance, of the current page.</li>
<li>9-10: Gets the child widget of <code>scr</code>, which is a TfeTextView instance, and returns it.</li>
<li>8: Gets the child widget <code>scr</code>, which is a
GtkScrolledWindow instance, of the current page.</li>
<li>9-10: Gets the child widget of <code>scr</code>, which is a
TfeTextView instance, and returns it.</li>
</ul>
<h2 id="file_changed_cb-handler">file_changed_cb handler</h2>
<p>The function <code>file_changed_cb</code> is a handler connected to “change-file” signal. If a file in a TfeTextView instance is changed, it emits this signal. This handler changes the label of GtkNotebookPage.</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb7-1"><a href="#cb7-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb7-2"><a href="#cb7-2"></a>file_changed_cb (TfeTextView *tv, GtkNotebook *nb) {</span>
<span id="cb7-3"><a href="#cb7-3"></a> GtkWidget *scr;</span>
<span id="cb7-4"><a href="#cb7-4"></a> GtkWidget *label;</span>
<span id="cb7-5"><a href="#cb7-5"></a> GFile *file;</span>
<span id="cb7-6"><a href="#cb7-6"></a> <span class="dt">char</span> *filename;</span>
<span id="cb7-7"><a href="#cb7-7"></a></span>
<span id="cb7-8"><a href="#cb7-8"></a> file = tfe_text_view_get_file (tv);</span>
<span id="cb7-9"><a href="#cb7-9"></a> scr = gtk_widget_get_parent (GTK_WIDGET (tv));</span>
<span id="cb7-10"><a href="#cb7-10"></a> <span class="cf">if</span> (G_IS_FILE (file)) {</span>
<span id="cb7-11"><a href="#cb7-11"></a> filename = g_file_get_basename (file);</span>
<span id="cb7-12"><a href="#cb7-12"></a> g_object_unref (file);</span>
<span id="cb7-13"><a href="#cb7-13"></a> } <span class="cf">else</span></span>
<span id="cb7-14"><a href="#cb7-14"></a> filename = get_untitled ();</span>
<span id="cb7-15"><a href="#cb7-15"></a> label = gtk_label_new (filename);</span>
<span id="cb7-16"><a href="#cb7-16"></a> g_free (filename);</span>
<span id="cb7-17"><a href="#cb7-17"></a> gtk_notebook_set_tab_label (nb, scr, label);</span>
<span id="cb7-18"><a href="#cb7-18"></a>}</span></code></pre></div>
<p>The function <code>file_changed_cb</code> is a handler connected to
“change-file” signal. If a file in a TfeTextView instance is changed, it
emits this signal. This handler changes the label of the
GtkNotebookPage.</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> <span class="dt">void</span></span>
<span id="cb10-2"><a href="#cb10-2"></a>file_changed_cb <span class="op">(</span>TfeTextView <span class="op">*</span>tv<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> GtkWidget <span class="op">*</span>scr<span class="op">;</span></span>
<span id="cb10-4"><a href="#cb10-4"></a> GtkWidget <span class="op">*</span>label<span class="op">;</span></span>
<span id="cb10-5"><a href="#cb10-5"></a> GFile <span class="op">*</span>file<span class="op">;</span></span>
<span id="cb10-6"><a href="#cb10-6"></a> <span class="dt">char</span> <span class="op">*</span>filename<span class="op">;</span></span>
<span id="cb10-7"><a href="#cb10-7"></a></span>
<span id="cb10-8"><a href="#cb10-8"></a> file <span class="op">=</span> tfe_text_view_get_file <span class="op">(</span>tv<span class="op">);</span></span>
<span id="cb10-9"><a href="#cb10-9"></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="cb10-10"><a href="#cb10-10"></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="cb10-11"><a href="#cb10-11"></a> filename <span class="op">=</span> g_file_get_basename <span class="op">(</span>file<span class="op">);</span></span>
<span id="cb10-12"><a href="#cb10-12"></a> g_object_unref <span class="op">(</span>file<span class="op">);</span></span>
<span id="cb10-13"><a href="#cb10-13"></a> <span class="op">}</span> <span class="cf">else</span></span>
<span id="cb10-14"><a href="#cb10-14"></a> filename <span class="op">=</span> get_untitled <span class="op">();</span></span>
<span id="cb10-15"><a href="#cb10-15"></a> label <span class="op">=</span> gtk_label_new <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb10-16"><a href="#cb10-16"></a> g_free <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb10-17"><a href="#cb10-17"></a> gtk_notebook_set_tab_label <span class="op">(</span>nb<span class="op">,</span> scr<span class="op">,</span> label<span class="op">);</span></span>
<span id="cb10-18"><a href="#cb10-18"></a><span class="op">}</span></span></code></pre></div>
<ul>
<li>8: Gets the GFile instance from <code>tv</code>.</li>
<li>9: Gets the GkScrolledWindow instance which is the parent widget of <code>tv</code>.</li>
<li>10-12: If <code>file</code> points GFile, then assigns the filename of the GFile into <code>filename</code>. Then, unref the GFile object <code>file</code>.</li>
<li>13-14: Otherwise (file is NULL), assigns untitled string to <code>filename</code>.</li>
<li>15-16: Creates a GtkLabel instance <code>label</code> with the filename and set the label of the GtkNotebookPage with <code>label</code>.</li>
<li>9: Gets the GkScrolledWindow instance which is the parent widget of
<code>tv</code>.</li>
<li>10-12: If <code>file</code> points GFile, then assigns the filename
of the GFile into <code>filename</code>. Then, unref the GFile object
<code>file</code>.</li>
<li>13-14: Otherwise (file is NULL), assigns untitled string to
<code>filename</code>.</li>
<li>15-16: Creates a GtkLabel instance <code>label</code> with the
filename and set the label of the GtkNotebookPage with
<code>label</code>.</li>
</ul>
</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>

View file

@ -224,7 +224,7 @@ substitute “textview” for the selector.</p>
<pre><code>textview {color: yellow; ...}</code></pre>
<p>Class, ID and some other things can be applied to the selector like
Web CSS. Refer to <a
href="https://docs.gtk.org/gtk4/css-overview.html">GTK 4 API Reference,
href="https://docs.gtk.org/gtk4/css-overview.html">GTK 4 API Reference
CSS in Gtk</a> for further information.</p>
<p>In line 30, the CSS is a string.</p>
<pre><code>textview {padding: 10px; font-family: monospace; font-size: 12pt;}</code></pre>
@ -396,9 +396,7 @@ class="sourceCode numberSource numberLines"><code class="sourceCode"><span id="c
<p>In this file, just the source file names are modified from the prior
version.</p>
<h2 id="source-files">source files</h2>
<p>The source files of the text editor <code>tfe</code> will be shown in
the next section.</p>
<p>You can also download the files from the <a
<p>You can download the files from the <a
href="https://github.com/ToshioCP/Gtk4-tutorial">repository</a>. There
are two options.</p>
<ul>

View file

@ -5,7 +5,7 @@
<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>Gtk4 tutorial</title>
<title>GTK 4 tutorial</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
@ -111,644 +111,54 @@
</div>
</div>
</nav>
<h1 id="tfe5-source-files">tfe5 source files</h1>
<h2 id="how-to-compile-and-execute-the-text-editor-tfe.">How to compile and execute the text editor tfe.</h2>
<p>First, source files are shown in the later subsections. How to download them is written at the end of the <a href="sec15.html">previous section</a>.</p>
<h1 id="how-to-build-tfe-text-file-editor">How to build tfe (text file
editor)</h1>
<h2 id="how-to-compile-and-execute-the-text-editor-tfe.">How to compile
and execute the text editor tfe.</h2>
<p>First, source files are in the <a
href="https://github.com/ToshioCP/Gtk4-tutorial">Gtk4-tutorila
repository</a>. How to download them is written at the end of the <a
href="sec15.html">previous section</a>.</p>
<p>The following is the instruction of compilation and execution.</p>
<ul>
<li>You need meson and ninja.</li>
<li>Set environment variables if necessary. If you have installed gtk4 from the source and you preferred the option <code>--prefix $HOME/local</code> (see <a href="sec2.html">Section 2</a>), type <code>. env.sh</code> to set the environment variables.</li>
<li>If you have installed gtk4 from the source, you need to set
environment variables to suit your installation.</li>
<li>Change your current directory to <code>src/tfe5</code>
directory.</li>
<li>Type <code>meson _build</code> for configuration.</li>
<li>Type <code>ninja -C _build</code> for compilation. Then the
application <code>tfe</code> is built under the <code>_build</code>
directory.</li>
<li>Type <code>_build/tfe</code> to execute it.</li>
</ul>
<pre><code>$ . env.sh</code></pre>
<p>Then the window appears. There are four buttons, <code>New</code>,
<code>Open</code>, <code>Save</code> and <code>Close</code>.</p>
<ul>
<li>change your current directory to <code>src/tfe5</code> directory.</li>
<li>type <code>meson _build</code> for configuration.</li>
<li>type <code>ninja -C _build</code> for compilation. Then the application <code>tfe</code> is built under the <code>_build</code> directory.</li>
<li>type <code>_build/tfe</code> to execute it.</li>
</ul>
<p>Then the window appears. There are four buttons, <code>New</code>, <code>Open</code>, <code>Save</code> and <code>Close</code>.</p>
<ul>
<li>Click on <code>Open</code> button, then a FileChooserDialog appears. Choose a file in the list and click on <code>Open</code> button. Then the file is read and a new Notebook Page appears.</li>
<li>Edit the file and click on <code>Save</code> button, then the text is saved to the original file.</li>
<li>Click on <code>Open</code> button, then a FileChooserDialog appears.
Choose a file in the list and click on <code>Open</code> button. Then
the file is read and a new Notebook Page appears.</li>
<li>Edit the file and click on <code>Save</code> button, then the text
is saved to the original file.</li>
<li>Click <code>Close</code>, then the Notebook Page disappears.</li>
<li>Click <code>Close</code> again, then the <code>Untitled</code> Notebook Page disappears and at the same time the application quits.</li>
<li>Click <code>Close</code> again, then the <code>Untitled</code>
Notebook Page disappears and at the same time the application
quits.</li>
</ul>
<p>This is a very simple editor. It is a good practice for you to add more features.</p>
<h2 id="meson.build">meson.build</h2>
<div class="sourceCode" id="cb2"><pre class="sourceCode numberSource numberLines"><code class="sourceCode"><span id="cb2-1"><a href="#cb2-1"></a>project(&#39;tfe&#39;, &#39;c&#39;)</span>
<span id="cb2-2"><a href="#cb2-2"></a></span>
<span id="cb2-3"><a href="#cb2-3"></a>gtkdep = dependency(&#39;gtk4&#39;)</span>
<span id="cb2-4"><a href="#cb2-4"></a></span>
<span id="cb2-5"><a href="#cb2-5"></a>gnome=import(&#39;gnome&#39;)</span>
<span id="cb2-6"><a href="#cb2-6"></a>resources = gnome.compile_resources(&#39;resources&#39;,&#39;tfe.gresource.xml&#39;)</span>
<span id="cb2-7"><a href="#cb2-7"></a></span>
<span id="cb2-8"><a href="#cb2-8"></a>sourcefiles=files(&#39;tfeapplication.c&#39;, &#39;tfenotebook.c&#39;, &#39;../tfetextview/tfetextview.c&#39;)</span>
<span id="cb2-9"><a href="#cb2-9"></a></span>
<span id="cb2-10"><a href="#cb2-10"></a>executable(&#39;tfe&#39;, sourcefiles, resources, dependencies: gtkdep)</span></code></pre></div>
<h2 id="tfe.gresource.xml">tfe.gresource.xml</h2>
<div class="sourceCode" id="cb3"><pre class="sourceCode numberSource xml numberLines"><code class="sourceCode xml"><span id="cb3-1"><a href="#cb3-1"></a><span class="kw">&lt;?xml</span> version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;<span class="kw">?&gt;</span></span>
<span id="cb3-2"><a href="#cb3-2"></a><span class="kw">&lt;gresources&gt;</span></span>
<span id="cb3-3"><a href="#cb3-3"></a> <span class="kw">&lt;gresource</span><span class="ot"> prefix=</span><span class="st">&quot;/com/github/ToshioCP/tfe&quot;</span><span class="kw">&gt;</span></span>
<span id="cb3-4"><a href="#cb3-4"></a> <span class="kw">&lt;file&gt;</span>tfe.ui<span class="kw">&lt;/file&gt;</span></span>
<span id="cb3-5"><a href="#cb3-5"></a> <span class="kw">&lt;/gresource&gt;</span></span>
<span id="cb3-6"><a href="#cb3-6"></a><span class="kw">&lt;/gresources&gt;</span></span></code></pre></div>
<h2 id="tfe.ui">tfe.ui</h2>
<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="kw">&lt;?xml</span> version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;<span class="kw">?&gt;</span></span>
<span id="cb4-2"><a href="#cb4-2"></a><span class="kw">&lt;interface&gt;</span></span>
<span id="cb4-3"><a href="#cb4-3"></a> <span class="kw">&lt;object</span><span class="ot"> class=</span><span class="st">&quot;GtkApplicationWindow&quot;</span><span class="ot"> id=</span><span class="st">&quot;win&quot;</span><span class="kw">&gt;</span></span>
<span id="cb4-4"><a href="#cb4-4"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;title&quot;</span><span class="kw">&gt;</span>file editor<span class="kw">&lt;/property&gt;</span></span>
<span id="cb4-5"><a href="#cb4-5"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;default-width&quot;</span><span class="kw">&gt;</span>600<span class="kw">&lt;/property&gt;</span></span>
<span id="cb4-6"><a href="#cb4-6"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;default-height&quot;</span><span class="kw">&gt;</span>400<span class="kw">&lt;/property&gt;</span></span>
<span id="cb4-7"><a href="#cb4-7"></a> <span class="kw">&lt;child&gt;</span></span>
<span id="cb4-8"><a href="#cb4-8"></a> <span class="kw">&lt;object</span><span class="ot"> class=</span><span class="st">&quot;GtkBox&quot;</span><span class="ot"> id=</span><span class="st">&quot;boxv&quot;</span><span class="kw">&gt;</span></span>
<span id="cb4-9"><a href="#cb4-9"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;orientation&quot;</span><span class="kw">&gt;</span>GTK_ORIENTATION_VERTICAL<span class="kw">&lt;/property&gt;</span></span>
<span id="cb4-10"><a href="#cb4-10"></a> <span class="kw">&lt;child&gt;</span></span>
<span id="cb4-11"><a href="#cb4-11"></a> <span class="kw">&lt;object</span><span class="ot"> class=</span><span class="st">&quot;GtkBox&quot;</span><span class="ot"> id=</span><span class="st">&quot;boxh&quot;</span><span class="kw">&gt;</span></span>
<span id="cb4-12"><a href="#cb4-12"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;orientation&quot;</span><span class="kw">&gt;</span>GTK_ORIENTATION_HORIZONTAL<span class="kw">&lt;/property&gt;</span></span>
<span id="cb4-13"><a href="#cb4-13"></a> <span class="kw">&lt;child&gt;</span></span>
<span id="cb4-14"><a href="#cb4-14"></a> <span class="kw">&lt;object</span><span class="ot"> class=</span><span class="st">&quot;GtkLabel&quot;</span><span class="ot"> id=</span><span class="st">&quot;dmy1&quot;</span><span class="kw">&gt;</span></span>
<span id="cb4-15"><a href="#cb4-15"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;width-chars&quot;</span><span class="kw">&gt;</span>10<span class="kw">&lt;/property&gt;</span></span>
<span id="cb4-16"><a href="#cb4-16"></a> <span class="kw">&lt;/object&gt;</span></span>
<span id="cb4-17"><a href="#cb4-17"></a> <span class="kw">&lt;/child&gt;</span></span>
<span id="cb4-18"><a href="#cb4-18"></a> <span class="kw">&lt;child&gt;</span></span>
<span id="cb4-19"><a href="#cb4-19"></a> <span class="kw">&lt;object</span><span class="ot"> class=</span><span class="st">&quot;GtkButton&quot;</span><span class="ot"> id=</span><span class="st">&quot;btnn&quot;</span><span class="kw">&gt;</span></span>
<span id="cb4-20"><a href="#cb4-20"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;label&quot;</span><span class="kw">&gt;</span>New<span class="kw">&lt;/property&gt;</span></span>
<span id="cb4-21"><a href="#cb4-21"></a> <span class="kw">&lt;/object&gt;</span></span>
<span id="cb4-22"><a href="#cb4-22"></a> <span class="kw">&lt;/child&gt;</span></span>
<span id="cb4-23"><a href="#cb4-23"></a> <span class="kw">&lt;child&gt;</span></span>
<span id="cb4-24"><a href="#cb4-24"></a> <span class="kw">&lt;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><span class="kw">&gt;</span></span>
<span id="cb4-25"><a href="#cb4-25"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;label&quot;</span><span class="kw">&gt;</span>Open<span class="kw">&lt;/property&gt;</span></span>
<span id="cb4-26"><a href="#cb4-26"></a> <span class="kw">&lt;/object&gt;</span></span>
<span id="cb4-27"><a href="#cb4-27"></a> <span class="kw">&lt;/child&gt;</span></span>
<span id="cb4-28"><a href="#cb4-28"></a> <span class="kw">&lt;child&gt;</span></span>
<span id="cb4-29"><a href="#cb4-29"></a> <span class="kw">&lt;object</span><span class="ot"> class=</span><span class="st">&quot;GtkLabel&quot;</span><span class="ot"> id=</span><span class="st">&quot;dmy2&quot;</span><span class="kw">&gt;</span></span>
<span id="cb4-30"><a href="#cb4-30"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;hexpand&quot;</span><span class="kw">&gt;</span>TRUE<span class="kw">&lt;/property&gt;</span></span>
<span id="cb4-31"><a href="#cb4-31"></a> <span class="kw">&lt;/object&gt;</span></span>
<span id="cb4-32"><a href="#cb4-32"></a> <span class="kw">&lt;/child&gt;</span></span>
<span id="cb4-33"><a href="#cb4-33"></a> <span class="kw">&lt;child&gt;</span></span>
<span id="cb4-34"><a href="#cb4-34"></a> <span class="kw">&lt;object</span><span class="ot"> class=</span><span class="st">&quot;GtkButton&quot;</span><span class="ot"> id=</span><span class="st">&quot;btns&quot;</span><span class="kw">&gt;</span></span>
<span id="cb4-35"><a href="#cb4-35"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;label&quot;</span><span class="kw">&gt;</span>Save<span class="kw">&lt;/property&gt;</span></span>
<span id="cb4-36"><a href="#cb4-36"></a> <span class="kw">&lt;/object&gt;</span></span>
<span id="cb4-37"><a href="#cb4-37"></a> <span class="kw">&lt;/child&gt;</span></span>
<span id="cb4-38"><a href="#cb4-38"></a> <span class="kw">&lt;child&gt;</span></span>
<span id="cb4-39"><a href="#cb4-39"></a> <span class="kw">&lt;object</span><span class="ot"> class=</span><span class="st">&quot;GtkButton&quot;</span><span class="ot"> id=</span><span class="st">&quot;btnc&quot;</span><span class="kw">&gt;</span></span>
<span id="cb4-40"><a href="#cb4-40"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;label&quot;</span><span class="kw">&gt;</span>Close<span class="kw">&lt;/property&gt;</span></span>
<span id="cb4-41"><a href="#cb4-41"></a> <span class="kw">&lt;/object&gt;</span></span>
<span id="cb4-42"><a href="#cb4-42"></a> <span class="kw">&lt;/child&gt;</span></span>
<span id="cb4-43"><a href="#cb4-43"></a> <span class="kw">&lt;child&gt;</span></span>
<span id="cb4-44"><a href="#cb4-44"></a> <span class="kw">&lt;object</span><span class="ot"> class=</span><span class="st">&quot;GtkLabel&quot;</span><span class="ot"> id=</span><span class="st">&quot;dmy3&quot;</span><span class="kw">&gt;</span></span>
<span id="cb4-45"><a href="#cb4-45"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;width-chars&quot;</span><span class="kw">&gt;</span>10<span class="kw">&lt;/property&gt;</span></span>
<span id="cb4-46"><a href="#cb4-46"></a> <span class="kw">&lt;/object&gt;</span></span>
<span id="cb4-47"><a href="#cb4-47"></a> <span class="kw">&lt;/child&gt;</span></span>
<span id="cb4-48"><a href="#cb4-48"></a> <span class="kw">&lt;/object&gt;</span></span>
<span id="cb4-49"><a href="#cb4-49"></a> <span class="kw">&lt;/child&gt;</span></span>
<span id="cb4-50"><a href="#cb4-50"></a> <span class="kw">&lt;child&gt;</span></span>
<span id="cb4-51"><a href="#cb4-51"></a> <span class="kw">&lt;object</span><span class="ot"> class=</span><span class="st">&quot;GtkNotebook&quot;</span><span class="ot"> id=</span><span class="st">&quot;nb&quot;</span><span class="kw">&gt;</span></span>
<span id="cb4-52"><a href="#cb4-52"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;scrollable&quot;</span><span class="kw">&gt;</span>TRUE<span class="kw">&lt;/property&gt;</span></span>
<span id="cb4-53"><a href="#cb4-53"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;hexpand&quot;</span><span class="kw">&gt;</span>TRUE<span class="kw">&lt;/property&gt;</span></span>
<span id="cb4-54"><a href="#cb4-54"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;vexpand&quot;</span><span class="kw">&gt;</span>TRUE<span class="kw">&lt;/property&gt;</span></span>
<span id="cb4-55"><a href="#cb4-55"></a> <span class="kw">&lt;/object&gt;</span></span>
<span id="cb4-56"><a href="#cb4-56"></a> <span class="kw">&lt;/child&gt;</span></span>
<span id="cb4-57"><a href="#cb4-57"></a> <span class="kw">&lt;/object&gt;</span></span>
<span id="cb4-58"><a href="#cb4-58"></a> <span class="kw">&lt;/child&gt;</span></span>
<span id="cb4-59"><a href="#cb4-59"></a> <span class="kw">&lt;/object&gt;</span></span>
<span id="cb4-60"><a href="#cb4-60"></a><span class="kw">&lt;/interface&gt;</span></span></code></pre></div>
<h2 id="tfe.h">tfe.h</h2>
<div class="sourceCode" id="cb5"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb5-1"><a href="#cb5-1"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<span id="cb5-2"><a href="#cb5-2"></a></span>
<span id="cb5-3"><a href="#cb5-3"></a><span class="pp">#include </span><span class="im">&quot;../tfetextview/tfetextview.h&quot;</span></span>
<span id="cb5-4"><a href="#cb5-4"></a><span class="pp">#include </span><span class="im">&quot;tfenotebook.h&quot;</span></span></code></pre></div>
<h2 id="tfeapplication.c">tfeapplication.c</h2>
<div class="sourceCode" id="cb6"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb6-1"><a href="#cb6-1"></a><span class="pp">#include </span><span class="im">&quot;tfe.h&quot;</span></span>
<span id="cb6-2"><a href="#cb6-2"></a></span>
<span id="cb6-3"><a href="#cb6-3"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb6-4"><a href="#cb6-4"></a>open_cb (GtkNotebook *nb) {</span>
<span id="cb6-5"><a href="#cb6-5"></a> notebook_page_open (nb);</span>
<span id="cb6-6"><a href="#cb6-6"></a>}</span>
<span id="cb6-7"><a href="#cb6-7"></a></span>
<span id="cb6-8"><a href="#cb6-8"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb6-9"><a href="#cb6-9"></a>new_cb (GtkNotebook *nb) {</span>
<span id="cb6-10"><a href="#cb6-10"></a> notebook_page_new (nb);</span>
<span id="cb6-11"><a href="#cb6-11"></a>}</span>
<span id="cb6-12"><a href="#cb6-12"></a></span>
<span id="cb6-13"><a href="#cb6-13"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb6-14"><a href="#cb6-14"></a>save_cb (GtkNotebook *nb) {</span>
<span id="cb6-15"><a href="#cb6-15"></a> notebook_page_save (nb);</span>
<span id="cb6-16"><a href="#cb6-16"></a>}</span>
<span id="cb6-17"><a href="#cb6-17"></a></span>
<span id="cb6-18"><a href="#cb6-18"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb6-19"><a href="#cb6-19"></a>close_cb (GtkNotebook *nb) {</span>
<span id="cb6-20"><a href="#cb6-20"></a> notebook_page_close (GTK_NOTEBOOK (nb));</span>
<span id="cb6-21"><a href="#cb6-21"></a>}</span>
<span id="cb6-22"><a href="#cb6-22"></a></span>
<span id="cb6-23"><a href="#cb6-23"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb6-24"><a href="#cb6-24"></a>app_activate (GApplication *application) {</span>
<span id="cb6-25"><a href="#cb6-25"></a> GtkApplication *app = GTK_APPLICATION (application);</span>
<span id="cb6-26"><a href="#cb6-26"></a> GtkWidget *win = GTK_WIDGET (gtk_application_get_active_window (app));</span>
<span id="cb6-27"><a href="#cb6-27"></a> GtkWidget *boxv = gtk_window_get_child (GTK_WINDOW (win));</span>
<span id="cb6-28"><a href="#cb6-28"></a> GtkNotebook *nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));</span>
<span id="cb6-29"><a href="#cb6-29"></a></span>
<span id="cb6-30"><a href="#cb6-30"></a> notebook_page_new (nb);</span>
<span id="cb6-31"><a href="#cb6-31"></a> gtk_widget_show (GTK_WIDGET (win));</span>
<span id="cb6-32"><a href="#cb6-32"></a>}</span>
<span id="cb6-33"><a href="#cb6-33"></a></span>
<span id="cb6-34"><a href="#cb6-34"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb6-35"><a href="#cb6-35"></a>app_open (GApplication *application, GFile ** files, gint n_files, <span class="dt">const</span> gchar *hint) {</span>
<span id="cb6-36"><a href="#cb6-36"></a> GtkApplication *app = GTK_APPLICATION (application);</span>
<span id="cb6-37"><a href="#cb6-37"></a> GtkWidget *win = GTK_WIDGET (gtk_application_get_active_window (app));</span>
<span id="cb6-38"><a href="#cb6-38"></a> GtkWidget *boxv = gtk_window_get_child (GTK_WINDOW (win));</span>
<span id="cb6-39"><a href="#cb6-39"></a> GtkNotebook *nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));</span>
<span id="cb6-40"><a href="#cb6-40"></a> <span class="dt">int</span> i;</span>
<span id="cb6-41"><a href="#cb6-41"></a></span>
<span id="cb6-42"><a href="#cb6-42"></a> <span class="cf">for</span> (i = <span class="dv">0</span>; i &lt; n_files; i++)</span>
<span id="cb6-43"><a href="#cb6-43"></a> notebook_page_new_with_file (nb, files[i]);</span>
<span id="cb6-44"><a href="#cb6-44"></a> <span class="cf">if</span> (gtk_notebook_get_n_pages (nb) == <span class="dv">0</span>)</span>
<span id="cb6-45"><a href="#cb6-45"></a> notebook_page_new (nb);</span>
<span id="cb6-46"><a href="#cb6-46"></a> gtk_widget_show (win);</span>
<span id="cb6-47"><a href="#cb6-47"></a>}</span>
<span id="cb6-48"><a href="#cb6-48"></a></span>
<span id="cb6-49"><a href="#cb6-49"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb6-50"><a href="#cb6-50"></a>app_startup (GApplication *application) {</span>
<span id="cb6-51"><a href="#cb6-51"></a> GtkApplication *app = GTK_APPLICATION (application);</span>
<span id="cb6-52"><a href="#cb6-52"></a> GtkBuilder *build;</span>
<span id="cb6-53"><a href="#cb6-53"></a> GtkApplicationWindow *win;</span>
<span id="cb6-54"><a href="#cb6-54"></a> GtkNotebook *nb;</span>
<span id="cb6-55"><a href="#cb6-55"></a> GtkButton *btno;</span>
<span id="cb6-56"><a href="#cb6-56"></a> GtkButton *btnn;</span>
<span id="cb6-57"><a href="#cb6-57"></a> GtkButton *btns;</span>
<span id="cb6-58"><a href="#cb6-58"></a> GtkButton *btnc;</span>
<span id="cb6-59"><a href="#cb6-59"></a></span>
<span id="cb6-60"><a href="#cb6-60"></a> build = gtk_builder_new_from_resource (<span class="st">&quot;/com/github/ToshioCP/tfe/tfe.ui&quot;</span>);</span>
<span id="cb6-61"><a href="#cb6-61"></a> win = GTK_APPLICATION_WINDOW (gtk_builder_get_object (build, <span class="st">&quot;win&quot;</span>));</span>
<span id="cb6-62"><a href="#cb6-62"></a> nb = GTK_NOTEBOOK (gtk_builder_get_object (build, <span class="st">&quot;nb&quot;</span>));</span>
<span id="cb6-63"><a href="#cb6-63"></a> gtk_window_set_application (GTK_WINDOW (win), app);</span>
<span id="cb6-64"><a href="#cb6-64"></a> btno = GTK_BUTTON (gtk_builder_get_object (build, <span class="st">&quot;btno&quot;</span>));</span>
<span id="cb6-65"><a href="#cb6-65"></a> btnn = GTK_BUTTON (gtk_builder_get_object (build, <span class="st">&quot;btnn&quot;</span>));</span>
<span id="cb6-66"><a href="#cb6-66"></a> btns = GTK_BUTTON (gtk_builder_get_object (build, <span class="st">&quot;btns&quot;</span>));</span>
<span id="cb6-67"><a href="#cb6-67"></a> btnc = GTK_BUTTON (gtk_builder_get_object (build, <span class="st">&quot;btnc&quot;</span>));</span>
<span id="cb6-68"><a href="#cb6-68"></a> g_signal_connect_swapped (btno, <span class="st">&quot;clicked&quot;</span>, G_CALLBACK (open_cb), nb);</span>
<span id="cb6-69"><a href="#cb6-69"></a> g_signal_connect_swapped (btnn, <span class="st">&quot;clicked&quot;</span>, G_CALLBACK (new_cb), nb);</span>
<span id="cb6-70"><a href="#cb6-70"></a> g_signal_connect_swapped (btns, <span class="st">&quot;clicked&quot;</span>, G_CALLBACK (save_cb), nb);</span>
<span id="cb6-71"><a href="#cb6-71"></a> g_signal_connect_swapped (btnc, <span class="st">&quot;clicked&quot;</span>, G_CALLBACK (close_cb), nb);</span>
<span id="cb6-72"><a href="#cb6-72"></a> g_object_unref(build);</span>
<span id="cb6-73"><a href="#cb6-73"></a></span>
<span id="cb6-74"><a href="#cb6-74"></a>GdkDisplay *display;</span>
<span id="cb6-75"><a href="#cb6-75"></a></span>
<span id="cb6-76"><a href="#cb6-76"></a> display = gtk_widget_get_display (GTK_WIDGET (win));</span>
<span id="cb6-77"><a href="#cb6-77"></a> GtkCssProvider *provider = gtk_css_provider_new ();</span>
<span id="cb6-78"><a href="#cb6-78"></a> gtk_css_provider_load_from_data (provider, <span class="st">&quot;textview {padding: 10px; font-family: monospace; font-size: 12pt;}&quot;</span>, -<span class="dv">1</span>);</span>
<span id="cb6-79"><a href="#cb6-79"></a> gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);</span>
<span id="cb6-80"><a href="#cb6-80"></a>}</span>
<span id="cb6-81"><a href="#cb6-81"></a></span>
<span id="cb6-82"><a href="#cb6-82"></a><span class="pp">#define APPLICATION_ID &quot;com.github.ToshioCP.tfe&quot;</span></span>
<span id="cb6-83"><a href="#cb6-83"></a></span>
<span id="cb6-84"><a href="#cb6-84"></a><span class="dt">int</span></span>
<span id="cb6-85"><a href="#cb6-85"></a>main (<span class="dt">int</span> argc, <span class="dt">char</span> **argv) {</span>
<span id="cb6-86"><a href="#cb6-86"></a> GtkApplication *app;</span>
<span id="cb6-87"><a href="#cb6-87"></a> <span class="dt">int</span> stat;</span>
<span id="cb6-88"><a href="#cb6-88"></a></span>
<span id="cb6-89"><a href="#cb6-89"></a> app = gtk_application_new (APPLICATION_ID, G_APPLICATION_HANDLES_OPEN);</span>
<span id="cb6-90"><a href="#cb6-90"></a></span>
<span id="cb6-91"><a href="#cb6-91"></a> g_signal_connect (app, <span class="st">&quot;startup&quot;</span>, G_CALLBACK (app_startup), NULL);</span>
<span id="cb6-92"><a href="#cb6-92"></a> g_signal_connect (app, <span class="st">&quot;activate&quot;</span>, G_CALLBACK (app_activate), NULL);</span>
<span id="cb6-93"><a href="#cb6-93"></a> g_signal_connect (app, <span class="st">&quot;open&quot;</span>, G_CALLBACK (app_open), NULL);</span>
<span id="cb6-94"><a href="#cb6-94"></a></span>
<span id="cb6-95"><a href="#cb6-95"></a> stat =g_application_run (G_APPLICATION (app), argc, argv);</span>
<span id="cb6-96"><a href="#cb6-96"></a> g_object_unref (app);</span>
<span id="cb6-97"><a href="#cb6-97"></a> <span class="cf">return</span> stat;</span>
<span id="cb6-98"><a href="#cb6-98"></a>}</span></code></pre></div>
<h2 id="tfenotebook.h">tfenotebook.h</h2>
<div class="sourceCode" id="cb7"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb7-1"><a href="#cb7-1"></a><span class="dt">void</span></span>
<span id="cb7-2"><a href="#cb7-2"></a>notebook_page_save(GtkNotebook *nb);</span>
<span id="cb7-3"><a href="#cb7-3"></a></span>
<span id="cb7-4"><a href="#cb7-4"></a><span class="dt">void</span></span>
<span id="cb7-5"><a href="#cb7-5"></a>notebook_page_close (GtkNotebook *nb);</span>
<span id="cb7-6"><a href="#cb7-6"></a></span>
<span id="cb7-7"><a href="#cb7-7"></a><span class="dt">void</span></span>
<span id="cb7-8"><a href="#cb7-8"></a>notebook_page_open (GtkNotebook *nb);</span>
<span id="cb7-9"><a href="#cb7-9"></a></span>
<span id="cb7-10"><a href="#cb7-10"></a><span class="dt">void</span></span>
<span id="cb7-11"><a href="#cb7-11"></a>notebook_page_new_with_file (GtkNotebook *nb, GFile *file);</span>
<span id="cb7-12"><a href="#cb7-12"></a></span>
<span id="cb7-13"><a href="#cb7-13"></a><span class="dt">void</span></span>
<span id="cb7-14"><a href="#cb7-14"></a>notebook_page_new (GtkNotebook *nb);</span></code></pre></div>
<h2 id="tfenotebook.c">tfenotebook.c</h2>
<div class="sourceCode" id="cb8"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb8-1"><a href="#cb8-1"></a><span class="pp">#include </span><span class="im">&quot;tfe.h&quot;</span></span>
<span id="cb8-2"><a href="#cb8-2"></a></span>
<span id="cb8-3"><a href="#cb8-3"></a><span class="co">/* The returned string should be freed with g_free() when no longer needed. */</span></span>
<span id="cb8-4"><a href="#cb8-4"></a><span class="dt">static</span> gchar*</span>
<span id="cb8-5"><a href="#cb8-5"></a>get_untitled () {</span>
<span id="cb8-6"><a href="#cb8-6"></a> <span class="dt">static</span> <span class="dt">int</span> c = -<span class="dv">1</span>;</span>
<span id="cb8-7"><a href="#cb8-7"></a> <span class="cf">if</span> (++c == <span class="dv">0</span>) </span>
<span id="cb8-8"><a href="#cb8-8"></a> <span class="cf">return</span> g_strdup_printf(<span class="st">&quot;Untitled&quot;</span>);</span>
<span id="cb8-9"><a href="#cb8-9"></a> <span class="cf">else</span></span>
<span id="cb8-10"><a href="#cb8-10"></a> <span class="cf">return</span> g_strdup_printf (<span class="st">&quot;Untitled%u&quot;</span>, c);</span>
<span id="cb8-11"><a href="#cb8-11"></a>}</span>
<span id="cb8-12"><a href="#cb8-12"></a></span>
<span id="cb8-13"><a href="#cb8-13"></a><span class="dt">static</span> TfeTextView *</span>
<span id="cb8-14"><a href="#cb8-14"></a>get_current_textview (GtkNotebook *nb) {</span>
<span id="cb8-15"><a href="#cb8-15"></a> <span class="dt">int</span> i;</span>
<span id="cb8-16"><a href="#cb8-16"></a> GtkWidget *scr;</span>
<span id="cb8-17"><a href="#cb8-17"></a> GtkWidget *tv;</span>
<span id="cb8-18"><a href="#cb8-18"></a></span>
<span id="cb8-19"><a href="#cb8-19"></a> i = gtk_notebook_get_current_page (nb);</span>
<span id="cb8-20"><a href="#cb8-20"></a> scr = gtk_notebook_get_nth_page (nb, i);</span>
<span id="cb8-21"><a href="#cb8-21"></a> tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));</span>
<span id="cb8-22"><a href="#cb8-22"></a> <span class="cf">return</span> TFE_TEXT_VIEW (tv);</span>
<span id="cb8-23"><a href="#cb8-23"></a>}</span>
<span id="cb8-24"><a href="#cb8-24"></a></span>
<span id="cb8-25"><a href="#cb8-25"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb8-26"><a href="#cb8-26"></a>file_changed_cb (TfeTextView *tv, GtkNotebook *nb) {</span>
<span id="cb8-27"><a href="#cb8-27"></a> GtkWidget *scr;</span>
<span id="cb8-28"><a href="#cb8-28"></a> GtkWidget *label;</span>
<span id="cb8-29"><a href="#cb8-29"></a> GFile *file;</span>
<span id="cb8-30"><a href="#cb8-30"></a> <span class="dt">char</span> *filename;</span>
<span id="cb8-31"><a href="#cb8-31"></a></span>
<span id="cb8-32"><a href="#cb8-32"></a> file = tfe_text_view_get_file (tv);</span>
<span id="cb8-33"><a href="#cb8-33"></a> scr = gtk_widget_get_parent (GTK_WIDGET (tv));</span>
<span id="cb8-34"><a href="#cb8-34"></a> <span class="cf">if</span> (G_IS_FILE (file)) {</span>
<span id="cb8-35"><a href="#cb8-35"></a> filename = g_file_get_basename (file);</span>
<span id="cb8-36"><a href="#cb8-36"></a> g_object_unref (file);</span>
<span id="cb8-37"><a href="#cb8-37"></a> } <span class="cf">else</span></span>
<span id="cb8-38"><a href="#cb8-38"></a> filename = get_untitled ();</span>
<span id="cb8-39"><a href="#cb8-39"></a> label = gtk_label_new (filename);</span>
<span id="cb8-40"><a href="#cb8-40"></a> g_free (filename);</span>
<span id="cb8-41"><a href="#cb8-41"></a> gtk_notebook_set_tab_label (nb, scr, label);</span>
<span id="cb8-42"><a href="#cb8-42"></a>}</span>
<span id="cb8-43"><a href="#cb8-43"></a></span>
<span id="cb8-44"><a href="#cb8-44"></a><span class="dt">void</span></span>
<span id="cb8-45"><a href="#cb8-45"></a>notebook_page_save (GtkNotebook *nb) {</span>
<span id="cb8-46"><a href="#cb8-46"></a> g_return_if_fail(GTK_IS_NOTEBOOK (nb));</span>
<span id="cb8-47"><a href="#cb8-47"></a></span>
<span id="cb8-48"><a href="#cb8-48"></a> TfeTextView *tv;</span>
<span id="cb8-49"><a href="#cb8-49"></a></span>
<span id="cb8-50"><a href="#cb8-50"></a> tv = get_current_textview (nb);</span>
<span id="cb8-51"><a href="#cb8-51"></a> tfe_text_view_save (TFE_TEXT_VIEW (tv));</span>
<span id="cb8-52"><a href="#cb8-52"></a>}</span>
<span id="cb8-53"><a href="#cb8-53"></a></span>
<span id="cb8-54"><a href="#cb8-54"></a><span class="dt">void</span></span>
<span id="cb8-55"><a href="#cb8-55"></a>notebook_page_close (GtkNotebook *nb) {</span>
<span id="cb8-56"><a href="#cb8-56"></a> g_return_if_fail(GTK_IS_NOTEBOOK (nb));</span>
<span id="cb8-57"><a href="#cb8-57"></a></span>
<span id="cb8-58"><a href="#cb8-58"></a> GtkWidget *win;</span>
<span id="cb8-59"><a href="#cb8-59"></a> <span class="dt">int</span> i;</span>
<span id="cb8-60"><a href="#cb8-60"></a></span>
<span id="cb8-61"><a href="#cb8-61"></a> <span class="cf">if</span> (gtk_notebook_get_n_pages (nb) == <span class="dv">1</span>) {</span>
<span id="cb8-62"><a href="#cb8-62"></a> win = gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW);</span>
<span id="cb8-63"><a href="#cb8-63"></a> gtk_window_destroy(GTK_WINDOW (win));</span>
<span id="cb8-64"><a href="#cb8-64"></a> } <span class="cf">else</span> {</span>
<span id="cb8-65"><a href="#cb8-65"></a> i = gtk_notebook_get_current_page (nb);</span>
<span id="cb8-66"><a href="#cb8-66"></a> gtk_notebook_remove_page (GTK_NOTEBOOK (nb), i);</span>
<span id="cb8-67"><a href="#cb8-67"></a> }</span>
<span id="cb8-68"><a href="#cb8-68"></a>}</span>
<span id="cb8-69"><a href="#cb8-69"></a></span>
<span id="cb8-70"><a href="#cb8-70"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb8-71"><a href="#cb8-71"></a>notebook_page_build (GtkNotebook *nb, GtkWidget *tv, <span class="dt">char</span> *filename) {</span>
<span id="cb8-72"><a href="#cb8-72"></a> GtkWidget *scr = gtk_scrolled_window_new ();</span>
<span id="cb8-73"><a href="#cb8-73"></a> GtkNotebookPage *nbp;</span>
<span id="cb8-74"><a href="#cb8-74"></a> GtkWidget *lab;</span>
<span id="cb8-75"><a href="#cb8-75"></a> <span class="dt">int</span> i;</span>
<span id="cb8-76"><a href="#cb8-76"></a></span>
<span id="cb8-77"><a href="#cb8-77"></a> gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);</span>
<span id="cb8-78"><a href="#cb8-78"></a> gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);</span>
<span id="cb8-79"><a href="#cb8-79"></a> lab = gtk_label_new (filename);</span>
<span id="cb8-80"><a href="#cb8-80"></a> i = gtk_notebook_append_page (nb, scr, lab);</span>
<span id="cb8-81"><a href="#cb8-81"></a> nbp = gtk_notebook_get_page (nb, scr);</span>
<span id="cb8-82"><a href="#cb8-82"></a> g_object_set (nbp, <span class="st">&quot;tab-expand&quot;</span>, TRUE, NULL);</span>
<span id="cb8-83"><a href="#cb8-83"></a> gtk_notebook_set_current_page (nb, i);</span>
<span id="cb8-84"><a href="#cb8-84"></a> g_signal_connect (GTK_TEXT_VIEW (tv), <span class="st">&quot;change-file&quot;</span>, G_CALLBACK (file_changed_cb), nb);</span>
<span id="cb8-85"><a href="#cb8-85"></a>}</span>
<span id="cb8-86"><a href="#cb8-86"></a></span>
<span id="cb8-87"><a href="#cb8-87"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb8-88"><a href="#cb8-88"></a>open_response (TfeTextView *tv, <span class="dt">int</span> response, GtkNotebook *nb) {</span>
<span id="cb8-89"><a href="#cb8-89"></a> GFile *file;</span>
<span id="cb8-90"><a href="#cb8-90"></a> <span class="dt">char</span> *filename;</span>
<span id="cb8-91"><a href="#cb8-91"></a></span>
<span id="cb8-92"><a href="#cb8-92"></a> <span class="cf">if</span> (response != TFE_OPEN_RESPONSE_SUCCESS || ! G_IS_FILE (file = tfe_text_view_get_file (tv))) {</span>
<span id="cb8-93"><a href="#cb8-93"></a> g_object_ref_sink (tv);</span>
<span id="cb8-94"><a href="#cb8-94"></a> g_object_unref (tv);</span>
<span id="cb8-95"><a href="#cb8-95"></a> }<span class="cf">else</span> {</span>
<span id="cb8-96"><a href="#cb8-96"></a> filename = g_file_get_basename (file);</span>
<span id="cb8-97"><a href="#cb8-97"></a> g_object_unref (file);</span>
<span id="cb8-98"><a href="#cb8-98"></a> notebook_page_build (nb, GTK_WIDGET (tv), filename);</span>
<span id="cb8-99"><a href="#cb8-99"></a> }</span>
<span id="cb8-100"><a href="#cb8-100"></a>}</span>
<span id="cb8-101"><a href="#cb8-101"></a></span>
<span id="cb8-102"><a href="#cb8-102"></a><span class="dt">void</span></span>
<span id="cb8-103"><a href="#cb8-103"></a>notebook_page_open (GtkNotebook *nb) {</span>
<span id="cb8-104"><a href="#cb8-104"></a> g_return_if_fail(GTK_IS_NOTEBOOK (nb));</span>
<span id="cb8-105"><a href="#cb8-105"></a></span>
<span id="cb8-106"><a href="#cb8-106"></a> GtkWidget *tv;</span>
<span id="cb8-107"><a href="#cb8-107"></a></span>
<span id="cb8-108"><a href="#cb8-108"></a> <span class="cf">if</span> ((tv = tfe_text_view_new ()) == NULL)</span>
<span id="cb8-109"><a href="#cb8-109"></a> <span class="cf">return</span>;</span>
<span id="cb8-110"><a href="#cb8-110"></a> g_signal_connect (TFE_TEXT_VIEW (tv), <span class="st">&quot;open-response&quot;</span>, G_CALLBACK (open_response), nb);</span>
<span id="cb8-111"><a href="#cb8-111"></a> tfe_text_view_open (TFE_TEXT_VIEW (tv), GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW)));</span>
<span id="cb8-112"><a href="#cb8-112"></a>}</span>
<span id="cb8-113"><a href="#cb8-113"></a></span>
<span id="cb8-114"><a href="#cb8-114"></a><span class="dt">void</span></span>
<span id="cb8-115"><a href="#cb8-115"></a>notebook_page_new_with_file (GtkNotebook *nb, GFile *file) {</span>
<span id="cb8-116"><a href="#cb8-116"></a> g_return_if_fail(GTK_IS_NOTEBOOK (nb));</span>
<span id="cb8-117"><a href="#cb8-117"></a> g_return_if_fail(G_IS_FILE (file));</span>
<span id="cb8-118"><a href="#cb8-118"></a></span>
<span id="cb8-119"><a href="#cb8-119"></a> GtkWidget *tv;</span>
<span id="cb8-120"><a href="#cb8-120"></a> <span class="dt">char</span> *filename;</span>
<span id="cb8-121"><a href="#cb8-121"></a></span>
<span id="cb8-122"><a href="#cb8-122"></a> <span class="cf">if</span> ((tv = tfe_text_view_new_with_file (file)) == NULL)</span>
<span id="cb8-123"><a href="#cb8-123"></a> <span class="cf">return</span>; <span class="co">/* read error */</span></span>
<span id="cb8-124"><a href="#cb8-124"></a> filename = g_file_get_basename (file);</span>
<span id="cb8-125"><a href="#cb8-125"></a> notebook_page_build (nb, tv, filename);</span>
<span id="cb8-126"><a href="#cb8-126"></a>}</span>
<span id="cb8-127"><a href="#cb8-127"></a></span>
<span id="cb8-128"><a href="#cb8-128"></a><span class="dt">void</span></span>
<span id="cb8-129"><a href="#cb8-129"></a>notebook_page_new (GtkNotebook *nb) {</span>
<span id="cb8-130"><a href="#cb8-130"></a> g_return_if_fail(GTK_IS_NOTEBOOK (nb));</span>
<span id="cb8-131"><a href="#cb8-131"></a></span>
<span id="cb8-132"><a href="#cb8-132"></a> GtkWidget *tv;</span>
<span id="cb8-133"><a href="#cb8-133"></a> <span class="dt">char</span> *filename;</span>
<span id="cb8-134"><a href="#cb8-134"></a></span>
<span id="cb8-135"><a href="#cb8-135"></a> <span class="cf">if</span> ((tv = tfe_text_view_new ()) == NULL)</span>
<span id="cb8-136"><a href="#cb8-136"></a> <span class="cf">return</span>;</span>
<span id="cb8-137"><a href="#cb8-137"></a> filename = get_untitled ();</span>
<span id="cb8-138"><a href="#cb8-138"></a> notebook_page_build (nb, tv, filename);</span>
<span id="cb8-139"><a href="#cb8-139"></a>}</span></code></pre></div>
<h2 id="tfetextview.h">tfetextview.h</h2>
<div class="sourceCode" id="cb9"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb9-1"><a href="#cb9-1"></a><span class="pp">#ifndef __TFE_TEXT_VIEW_H__</span></span>
<span id="cb9-2"><a href="#cb9-2"></a><span class="pp">#define __TFE_TEXT_VIEW_H__</span></span>
<span id="cb9-3"><a href="#cb9-3"></a></span>
<span id="cb9-4"><a href="#cb9-4"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<span id="cb9-5"><a href="#cb9-5"></a></span>
<span id="cb9-6"><a href="#cb9-6"></a><span class="pp">#define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()</span></span>
<span id="cb9-7"><a href="#cb9-7"></a>G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)</span>
<span id="cb9-8"><a href="#cb9-8"></a></span>
<span id="cb9-9"><a href="#cb9-9"></a><span class="co">/* &quot;open-response&quot; signal response */</span></span>
<span id="cb9-10"><a href="#cb9-10"></a><span class="kw">enum</span> TfeTextViewOpenResponseType</span>
<span id="cb9-11"><a href="#cb9-11"></a>{</span>
<span id="cb9-12"><a href="#cb9-12"></a> TFE_OPEN_RESPONSE_SUCCESS,</span>
<span id="cb9-13"><a href="#cb9-13"></a> TFE_OPEN_RESPONSE_CANCEL,</span>
<span id="cb9-14"><a href="#cb9-14"></a> TFE_OPEN_RESPONSE_ERROR</span>
<span id="cb9-15"><a href="#cb9-15"></a>};</span>
<span id="cb9-16"><a href="#cb9-16"></a></span>
<span id="cb9-17"><a href="#cb9-17"></a>GFile *</span>
<span id="cb9-18"><a href="#cb9-18"></a>tfe_text_view_get_file (TfeTextView *tv);</span>
<span id="cb9-19"><a href="#cb9-19"></a></span>
<span id="cb9-20"><a href="#cb9-20"></a><span class="dt">void</span></span>
<span id="cb9-21"><a href="#cb9-21"></a>tfe_text_view_open (TfeTextView *tv, GtkWindow *win);</span>
<span id="cb9-22"><a href="#cb9-22"></a></span>
<span id="cb9-23"><a href="#cb9-23"></a><span class="dt">void</span></span>
<span id="cb9-24"><a href="#cb9-24"></a>tfe_text_view_save (TfeTextView *tv);</span>
<span id="cb9-25"><a href="#cb9-25"></a></span>
<span id="cb9-26"><a href="#cb9-26"></a><span class="dt">void</span></span>
<span id="cb9-27"><a href="#cb9-27"></a>tfe_text_view_saveas (TfeTextView *tv);</span>
<span id="cb9-28"><a href="#cb9-28"></a></span>
<span id="cb9-29"><a href="#cb9-29"></a>GtkWidget *</span>
<span id="cb9-30"><a href="#cb9-30"></a>tfe_text_view_new_with_file (GFile *file);</span>
<span id="cb9-31"><a href="#cb9-31"></a></span>
<span id="cb9-32"><a href="#cb9-32"></a>GtkWidget *</span>
<span id="cb9-33"><a href="#cb9-33"></a>tfe_text_view_new (<span class="dt">void</span>);</span>
<span id="cb9-34"><a href="#cb9-34"></a></span>
<span id="cb9-35"><a href="#cb9-35"></a><span class="pp">#endif </span><span class="co">/* __TFE_TEXT_VIEW_H__ */</span></span></code></pre></div>
<h2 id="tfetextview.c">tfetextview.c</h2>
<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="pp">#include </span><span class="im">&lt;string.h&gt;</span></span>
<span id="cb10-2"><a href="#cb10-2"></a><span class="pp">#include </span><span class="im">&quot;tfetextview.h&quot;</span></span>
<span id="cb10-3"><a href="#cb10-3"></a></span>
<span id="cb10-4"><a href="#cb10-4"></a><span class="kw">struct</span> _TfeTextView {</span>
<span id="cb10-5"><a href="#cb10-5"></a> GtkTextView parent;</span>
<span id="cb10-6"><a href="#cb10-6"></a> GFile *file;</span>
<span id="cb10-7"><a href="#cb10-7"></a>};</span>
<span id="cb10-8"><a href="#cb10-8"></a></span>
<span id="cb10-9"><a href="#cb10-9"></a>G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);</span>
<span id="cb10-10"><a href="#cb10-10"></a></span>
<span id="cb10-11"><a href="#cb10-11"></a><span class="kw">enum</span> {</span>
<span id="cb10-12"><a href="#cb10-12"></a> CHANGE_FILE,</span>
<span id="cb10-13"><a href="#cb10-13"></a> OPEN_RESPONSE,</span>
<span id="cb10-14"><a href="#cb10-14"></a> NUMBER_OF_SIGNALS</span>
<span id="cb10-15"><a href="#cb10-15"></a>};</span>
<span id="cb10-16"><a href="#cb10-16"></a></span>
<span id="cb10-17"><a href="#cb10-17"></a><span class="dt">static</span> guint tfe_text_view_signals[NUMBER_OF_SIGNALS];</span>
<span id="cb10-18"><a href="#cb10-18"></a></span>
<span id="cb10-19"><a href="#cb10-19"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb10-20"><a href="#cb10-20"></a>tfe_text_view_dispose (GObject *gobject) {</span>
<span id="cb10-21"><a href="#cb10-21"></a> TfeTextView *tv = TFE_TEXT_VIEW (gobject);</span>
<span id="cb10-22"><a href="#cb10-22"></a></span>
<span id="cb10-23"><a href="#cb10-23"></a> <span class="cf">if</span> (G_IS_FILE (tv-&gt;file))</span>
<span id="cb10-24"><a href="#cb10-24"></a> g_clear_object (&amp;tv-&gt;file);</span>
<span id="cb10-25"><a href="#cb10-25"></a></span>
<span id="cb10-26"><a href="#cb10-26"></a> G_OBJECT_CLASS (tfe_text_view_parent_class)-&gt;dispose (gobject);</span>
<span id="cb10-27"><a href="#cb10-27"></a>}</span>
<span id="cb10-28"><a href="#cb10-28"></a></span>
<span id="cb10-29"><a href="#cb10-29"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb10-30"><a href="#cb10-30"></a>tfe_text_view_init (TfeTextView *tv) {</span>
<span id="cb10-31"><a href="#cb10-31"></a> tv-&gt;file = NULL;</span>
<span id="cb10-32"><a href="#cb10-32"></a>}</span>
<span id="cb10-33"><a href="#cb10-33"></a></span>
<span id="cb10-34"><a href="#cb10-34"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb10-35"><a href="#cb10-35"></a>tfe_text_view_class_init (TfeTextViewClass *class) {</span>
<span id="cb10-36"><a href="#cb10-36"></a> GObjectClass *object_class = G_OBJECT_CLASS (class);</span>
<span id="cb10-37"><a href="#cb10-37"></a></span>
<span id="cb10-38"><a href="#cb10-38"></a> object_class-&gt;dispose = tfe_text_view_dispose;</span>
<span id="cb10-39"><a href="#cb10-39"></a> tfe_text_view_signals[CHANGE_FILE] = g_signal_new (<span class="st">&quot;change-file&quot;</span>,</span>
<span id="cb10-40"><a href="#cb10-40"></a> G_TYPE_FROM_CLASS (class),</span>
<span id="cb10-41"><a href="#cb10-41"></a> G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,</span>
<span id="cb10-42"><a href="#cb10-42"></a> <span class="dv">0</span> <span class="co">/* class offset */</span>,</span>
<span id="cb10-43"><a href="#cb10-43"></a> NULL <span class="co">/* accumulator */</span>,</span>
<span id="cb10-44"><a href="#cb10-44"></a> NULL <span class="co">/* accumulator data */</span>,</span>
<span id="cb10-45"><a href="#cb10-45"></a> NULL <span class="co">/* C marshaller */</span>,</span>
<span id="cb10-46"><a href="#cb10-46"></a> G_TYPE_NONE <span class="co">/* return_type */</span>,</span>
<span id="cb10-47"><a href="#cb10-47"></a> <span class="dv">0</span> <span class="co">/* n_params */</span></span>
<span id="cb10-48"><a href="#cb10-48"></a> );</span>
<span id="cb10-49"><a href="#cb10-49"></a> tfe_text_view_signals[OPEN_RESPONSE] = g_signal_new (<span class="st">&quot;open-response&quot;</span>,</span>
<span id="cb10-50"><a href="#cb10-50"></a> G_TYPE_FROM_CLASS (class),</span>
<span id="cb10-51"><a href="#cb10-51"></a> G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,</span>
<span id="cb10-52"><a href="#cb10-52"></a> <span class="dv">0</span> <span class="co">/* class offset */</span>,</span>
<span id="cb10-53"><a href="#cb10-53"></a> NULL <span class="co">/* accumulator */</span>,</span>
<span id="cb10-54"><a href="#cb10-54"></a> NULL <span class="co">/* accumulator data */</span>,</span>
<span id="cb10-55"><a href="#cb10-55"></a> NULL <span class="co">/* C marshaller */</span>,</span>
<span id="cb10-56"><a href="#cb10-56"></a> G_TYPE_NONE <span class="co">/* return_type */</span>,</span>
<span id="cb10-57"><a href="#cb10-57"></a> <span class="dv">1</span> <span class="co">/* n_params */</span>,</span>
<span id="cb10-58"><a href="#cb10-58"></a> G_TYPE_INT</span>
<span id="cb10-59"><a href="#cb10-59"></a> );</span>
<span id="cb10-60"><a href="#cb10-60"></a>}</span>
<span id="cb10-61"><a href="#cb10-61"></a></span>
<span id="cb10-62"><a href="#cb10-62"></a>GFile *</span>
<span id="cb10-63"><a href="#cb10-63"></a>tfe_text_view_get_file (TfeTextView *tv) {</span>
<span id="cb10-64"><a href="#cb10-64"></a> g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);</span>
<span id="cb10-65"><a href="#cb10-65"></a></span>
<span id="cb10-66"><a href="#cb10-66"></a> <span class="cf">if</span> (G_IS_FILE (tv-&gt;file))</span>
<span id="cb10-67"><a href="#cb10-67"></a> <span class="cf">return</span> g_file_dup (tv-&gt;file);</span>
<span id="cb10-68"><a href="#cb10-68"></a> <span class="cf">else</span></span>
<span id="cb10-69"><a href="#cb10-69"></a> <span class="cf">return</span> NULL;</span>
<span id="cb10-70"><a href="#cb10-70"></a>}</span>
<span id="cb10-71"><a href="#cb10-71"></a></span>
<span id="cb10-72"><a href="#cb10-72"></a><span class="dt">static</span> gboolean</span>
<span id="cb10-73"><a href="#cb10-73"></a>save_file (GFile *file, GtkTextBuffer *tb, GtkWindow *win) {</span>
<span id="cb10-74"><a href="#cb10-74"></a> GtkTextIter start_iter;</span>
<span id="cb10-75"><a href="#cb10-75"></a> GtkTextIter end_iter;</span>
<span id="cb10-76"><a href="#cb10-76"></a> gchar *contents;</span>
<span id="cb10-77"><a href="#cb10-77"></a> gboolean stat;</span>
<span id="cb10-78"><a href="#cb10-78"></a> GtkWidget *message_dialog;</span>
<span id="cb10-79"><a href="#cb10-79"></a> GError *err = NULL;</span>
<span id="cb10-80"><a href="#cb10-80"></a></span>
<span id="cb10-81"><a href="#cb10-81"></a> gtk_text_buffer_get_bounds (tb, &amp;start_iter, &amp;end_iter);</span>
<span id="cb10-82"><a href="#cb10-82"></a> contents = gtk_text_buffer_get_text (tb, &amp;start_iter, &amp;end_iter, FALSE);</span>
<span id="cb10-83"><a href="#cb10-83"></a> <span class="cf">if</span> (g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &amp;err)) {</span>
<span id="cb10-84"><a href="#cb10-84"></a> gtk_text_buffer_set_modified (tb, FALSE);</span>
<span id="cb10-85"><a href="#cb10-85"></a> stat = TRUE;</span>
<span id="cb10-86"><a href="#cb10-86"></a> } <span class="cf">else</span> {</span>
<span id="cb10-87"><a href="#cb10-87"></a> message_dialog = gtk_message_dialog_new (win, GTK_DIALOG_MODAL,</span>
<span id="cb10-88"><a href="#cb10-88"></a> GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,</span>
<span id="cb10-89"><a href="#cb10-89"></a> <span class="st">&quot;%s.</span><span class="sc">\n</span><span class="st">&quot;</span>, err-&gt;message);</span>
<span id="cb10-90"><a href="#cb10-90"></a> g_signal_connect (message_dialog, <span class="st">&quot;response&quot;</span>, G_CALLBACK (gtk_window_destroy), NULL);</span>
<span id="cb10-91"><a href="#cb10-91"></a> gtk_widget_show (message_dialog);</span>
<span id="cb10-92"><a href="#cb10-92"></a> g_error_free (err);</span>
<span id="cb10-93"><a href="#cb10-93"></a> stat = FALSE;</span>
<span id="cb10-94"><a href="#cb10-94"></a> }</span>
<span id="cb10-95"><a href="#cb10-95"></a> g_free (contents);</span>
<span id="cb10-96"><a href="#cb10-96"></a> <span class="cf">return</span> stat;</span>
<span id="cb10-97"><a href="#cb10-97"></a>}</span>
<span id="cb10-98"><a href="#cb10-98"></a></span>
<span id="cb10-99"><a href="#cb10-99"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb10-100"><a href="#cb10-100"></a>saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {</span>
<span id="cb10-101"><a href="#cb10-101"></a> GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));</span>
<span id="cb10-102"><a href="#cb10-102"></a> GFile *file;</span>
<span id="cb10-103"><a href="#cb10-103"></a> GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);</span>
<span id="cb10-104"><a href="#cb10-104"></a></span>
<span id="cb10-105"><a href="#cb10-105"></a> <span class="cf">if</span> (response == GTK_RESPONSE_ACCEPT) {</span>
<span id="cb10-106"><a href="#cb10-106"></a> file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));</span>
<span id="cb10-107"><a href="#cb10-107"></a> <span class="cf">if</span> (! G_IS_FILE (file))</span>
<span id="cb10-108"><a href="#cb10-108"></a> g_warning (<span class="st">&quot;TfeTextView: gtk_file_chooser_get_file returns non GFile.</span><span class="sc">\n</span><span class="st">&quot;</span>);</span>
<span id="cb10-109"><a href="#cb10-109"></a> <span class="cf">else</span> <span class="cf">if</span> (save_file(file, tb, GTK_WINDOW (win))) {</span>
<span id="cb10-110"><a href="#cb10-110"></a> <span class="cf">if</span> (G_IS_FILE (tv-&gt;file))</span>
<span id="cb10-111"><a href="#cb10-111"></a> g_object_unref (tv-&gt;file);</span>
<span id="cb10-112"><a href="#cb10-112"></a> tv-&gt;file = file;</span>
<span id="cb10-113"><a href="#cb10-113"></a> g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], <span class="dv">0</span>);</span>
<span id="cb10-114"><a href="#cb10-114"></a> } <span class="cf">else</span></span>
<span id="cb10-115"><a href="#cb10-115"></a> g_object_unref (file);</span>
<span id="cb10-116"><a href="#cb10-116"></a> }</span>
<span id="cb10-117"><a href="#cb10-117"></a> gtk_window_destroy (GTK_WINDOW (dialog));</span>
<span id="cb10-118"><a href="#cb10-118"></a>}</span>
<span id="cb10-119"><a href="#cb10-119"></a></span>
<span id="cb10-120"><a href="#cb10-120"></a><span class="dt">void</span></span>
<span id="cb10-121"><a href="#cb10-121"></a>tfe_text_view_save (TfeTextView *tv) {</span>
<span id="cb10-122"><a href="#cb10-122"></a> g_return_if_fail (TFE_IS_TEXT_VIEW (tv));</span>
<span id="cb10-123"><a href="#cb10-123"></a></span>
<span id="cb10-124"><a href="#cb10-124"></a> GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));</span>
<span id="cb10-125"><a href="#cb10-125"></a> GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);</span>
<span id="cb10-126"><a href="#cb10-126"></a></span>
<span id="cb10-127"><a href="#cb10-127"></a> <span class="cf">if</span> (! gtk_text_buffer_get_modified (tb))</span>
<span id="cb10-128"><a href="#cb10-128"></a> <span class="cf">return</span>; <span class="co">/* no need to save it */</span></span>
<span id="cb10-129"><a href="#cb10-129"></a> <span class="cf">else</span> <span class="cf">if</span> (tv-&gt;file == NULL)</span>
<span id="cb10-130"><a href="#cb10-130"></a> tfe_text_view_saveas (tv);</span>
<span id="cb10-131"><a href="#cb10-131"></a> <span class="cf">else</span> <span class="cf">if</span> (! G_IS_FILE (tv-&gt;file))</span>
<span id="cb10-132"><a href="#cb10-132"></a> g_error (<span class="st">&quot;TfeTextView: The pointer tv-&gt;file isn&#39;t NULL nor GFile.</span><span class="sc">\n</span><span class="st">&quot;</span>);</span>
<span id="cb10-133"><a href="#cb10-133"></a> <span class="cf">else</span></span>
<span id="cb10-134"><a href="#cb10-134"></a> save_file (tv-&gt;file, tb, GTK_WINDOW (win));</span>
<span id="cb10-135"><a href="#cb10-135"></a>}</span>
<span id="cb10-136"><a href="#cb10-136"></a></span>
<span id="cb10-137"><a href="#cb10-137"></a><span class="dt">void</span></span>
<span id="cb10-138"><a href="#cb10-138"></a>tfe_text_view_saveas (TfeTextView *tv) {</span>
<span id="cb10-139"><a href="#cb10-139"></a> g_return_if_fail (TFE_IS_TEXT_VIEW (tv));</span>
<span id="cb10-140"><a href="#cb10-140"></a></span>
<span id="cb10-141"><a href="#cb10-141"></a> GtkWidget *dialog;</span>
<span id="cb10-142"><a href="#cb10-142"></a> GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);</span>
<span id="cb10-143"><a href="#cb10-143"></a></span>
<span id="cb10-144"><a href="#cb10-144"></a> dialog = gtk_file_chooser_dialog_new (<span class="st">&quot;Save file&quot;</span>, GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,</span>
<span id="cb10-145"><a href="#cb10-145"></a> <span class="st">&quot;Cancel&quot;</span>, GTK_RESPONSE_CANCEL,</span>
<span id="cb10-146"><a href="#cb10-146"></a> <span class="st">&quot;Save&quot;</span>, GTK_RESPONSE_ACCEPT,</span>
<span id="cb10-147"><a href="#cb10-147"></a> NULL);</span>
<span id="cb10-148"><a href="#cb10-148"></a> g_signal_connect (dialog, <span class="st">&quot;response&quot;</span>, G_CALLBACK (saveas_dialog_response), tv);</span>
<span id="cb10-149"><a href="#cb10-149"></a> gtk_widget_show (dialog);</span>
<span id="cb10-150"><a href="#cb10-150"></a>}</span>
<span id="cb10-151"><a href="#cb10-151"></a></span>
<span id="cb10-152"><a href="#cb10-152"></a>GtkWidget *</span>
<span id="cb10-153"><a href="#cb10-153"></a>tfe_text_view_new_with_file (GFile *file) {</span>
<span id="cb10-154"><a href="#cb10-154"></a> g_return_val_if_fail (G_IS_FILE (file), NULL);</span>
<span id="cb10-155"><a href="#cb10-155"></a></span>
<span id="cb10-156"><a href="#cb10-156"></a> GtkWidget *tv;</span>
<span id="cb10-157"><a href="#cb10-157"></a> GtkTextBuffer *tb;</span>
<span id="cb10-158"><a href="#cb10-158"></a> <span class="dt">char</span> *contents;</span>
<span id="cb10-159"><a href="#cb10-159"></a> gsize length;</span>
<span id="cb10-160"><a href="#cb10-160"></a></span>
<span id="cb10-161"><a href="#cb10-161"></a> <span class="cf">if</span> (! g_file_load_contents (file, NULL, &amp;contents, &amp;length, NULL, NULL)) <span class="co">/* read error */</span></span>
<span id="cb10-162"><a href="#cb10-162"></a> <span class="cf">return</span> NULL;</span>
<span id="cb10-163"><a href="#cb10-163"></a></span>
<span id="cb10-164"><a href="#cb10-164"></a> <span class="cf">if</span> ((tv = tfe_text_view_new()) != NULL) {</span>
<span id="cb10-165"><a href="#cb10-165"></a> tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));</span>
<span id="cb10-166"><a href="#cb10-166"></a> gtk_text_buffer_set_text (tb, contents, length);</span>
<span id="cb10-167"><a href="#cb10-167"></a> TFE_TEXT_VIEW (tv)-&gt;file = g_file_dup (file);</span>
<span id="cb10-168"><a href="#cb10-168"></a> gtk_text_buffer_set_modified (tb, FALSE);</span>
<span id="cb10-169"><a href="#cb10-169"></a> }</span>
<span id="cb10-170"><a href="#cb10-170"></a> g_free (contents);</span>
<span id="cb10-171"><a href="#cb10-171"></a> <span class="cf">return</span> tv;</span>
<span id="cb10-172"><a href="#cb10-172"></a>}</span>
<span id="cb10-173"><a href="#cb10-173"></a></span>
<span id="cb10-174"><a href="#cb10-174"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb10-175"><a href="#cb10-175"></a>open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {</span>
<span id="cb10-176"><a href="#cb10-176"></a> GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));</span>
<span id="cb10-177"><a href="#cb10-177"></a> GFile *file;</span>
<span id="cb10-178"><a href="#cb10-178"></a> <span class="dt">char</span> *contents;</span>
<span id="cb10-179"><a href="#cb10-179"></a> gsize length;</span>
<span id="cb10-180"><a href="#cb10-180"></a> GtkWidget *message_dialog;</span>
<span id="cb10-181"><a href="#cb10-181"></a> GError *err = NULL;</span>
<span id="cb10-182"><a href="#cb10-182"></a></span>
<span id="cb10-183"><a href="#cb10-183"></a> <span class="cf">if</span> (response != GTK_RESPONSE_ACCEPT)</span>
<span id="cb10-184"><a href="#cb10-184"></a> g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], <span class="dv">0</span>, TFE_OPEN_RESPONSE_CANCEL);</span>
<span id="cb10-185"><a href="#cb10-185"></a> <span class="cf">else</span> <span class="cf">if</span> (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)))) {</span>
<span id="cb10-186"><a href="#cb10-186"></a> g_warning (<span class="st">&quot;TfeTextView: gtk_file_chooser_get_file returns non GFile.</span><span class="sc">\n</span><span class="st">&quot;</span>);</span>
<span id="cb10-187"><a href="#cb10-187"></a> g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], <span class="dv">0</span>, TFE_OPEN_RESPONSE_ERROR);</span>
<span id="cb10-188"><a href="#cb10-188"></a> } <span class="cf">else</span> <span class="cf">if</span> (! g_file_load_contents (file, NULL, &amp;contents, &amp;length, NULL, &amp;err)) { <span class="co">/* read error */</span></span>
<span id="cb10-189"><a href="#cb10-189"></a> g_object_unref (file);</span>
<span id="cb10-190"><a href="#cb10-190"></a> message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,</span>
<span id="cb10-191"><a href="#cb10-191"></a> GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,</span>
<span id="cb10-192"><a href="#cb10-192"></a> <span class="st">&quot;%s.</span><span class="sc">\n</span><span class="st">&quot;</span>, err-&gt;message);</span>
<span id="cb10-193"><a href="#cb10-193"></a> g_signal_connect (message_dialog, <span class="st">&quot;response&quot;</span>, G_CALLBACK (gtk_window_destroy), NULL);</span>
<span id="cb10-194"><a href="#cb10-194"></a> gtk_widget_show (message_dialog);</span>
<span id="cb10-195"><a href="#cb10-195"></a> g_error_free (err);</span>
<span id="cb10-196"><a href="#cb10-196"></a> g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], <span class="dv">0</span>, TFE_OPEN_RESPONSE_ERROR);</span>
<span id="cb10-197"><a href="#cb10-197"></a> } <span class="cf">else</span> {</span>
<span id="cb10-198"><a href="#cb10-198"></a> gtk_text_buffer_set_text (tb, contents, length);</span>
<span id="cb10-199"><a href="#cb10-199"></a> g_free (contents);</span>
<span id="cb10-200"><a href="#cb10-200"></a> <span class="cf">if</span> (G_IS_FILE (tv-&gt;file))</span>
<span id="cb10-201"><a href="#cb10-201"></a> g_object_unref (tv-&gt;file);</span>
<span id="cb10-202"><a href="#cb10-202"></a> tv-&gt;file = file;</span>
<span id="cb10-203"><a href="#cb10-203"></a> gtk_text_buffer_set_modified (tb, FALSE);</span>
<span id="cb10-204"><a href="#cb10-204"></a> g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], <span class="dv">0</span>, TFE_OPEN_RESPONSE_SUCCESS);</span>
<span id="cb10-205"><a href="#cb10-205"></a> g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], <span class="dv">0</span>);</span>
<span id="cb10-206"><a href="#cb10-206"></a> }</span>
<span id="cb10-207"><a href="#cb10-207"></a> gtk_window_destroy (GTK_WINDOW (dialog));</span>
<span id="cb10-208"><a href="#cb10-208"></a>}</span>
<span id="cb10-209"><a href="#cb10-209"></a></span>
<span id="cb10-210"><a href="#cb10-210"></a><span class="dt">void</span></span>
<span id="cb10-211"><a href="#cb10-211"></a>tfe_text_view_open (TfeTextView *tv, GtkWindow *win) {</span>
<span id="cb10-212"><a href="#cb10-212"></a> g_return_if_fail (TFE_IS_TEXT_VIEW (tv));</span>
<span id="cb10-213"><a href="#cb10-213"></a> g_return_if_fail (GTK_IS_WINDOW (win));</span>
<span id="cb10-214"><a href="#cb10-214"></a></span>
<span id="cb10-215"><a href="#cb10-215"></a> GtkWidget *dialog;</span>
<span id="cb10-216"><a href="#cb10-216"></a></span>
<span id="cb10-217"><a href="#cb10-217"></a> dialog = gtk_file_chooser_dialog_new (<span class="st">&quot;Open file&quot;</span>, win, GTK_FILE_CHOOSER_ACTION_OPEN,</span>
<span id="cb10-218"><a href="#cb10-218"></a> <span class="st">&quot;Cancel&quot;</span>, GTK_RESPONSE_CANCEL,</span>
<span id="cb10-219"><a href="#cb10-219"></a> <span class="st">&quot;Open&quot;</span>, GTK_RESPONSE_ACCEPT,</span>
<span id="cb10-220"><a href="#cb10-220"></a> NULL);</span>
<span id="cb10-221"><a href="#cb10-221"></a> g_signal_connect (dialog, <span class="st">&quot;response&quot;</span>, G_CALLBACK (open_dialog_response), tv);</span>
<span id="cb10-222"><a href="#cb10-222"></a> gtk_widget_show (dialog);</span>
<span id="cb10-223"><a href="#cb10-223"></a>}</span>
<span id="cb10-224"><a href="#cb10-224"></a></span>
<span id="cb10-225"><a href="#cb10-225"></a>GtkWidget *</span>
<span id="cb10-226"><a href="#cb10-226"></a>tfe_text_view_new (<span class="dt">void</span>) {</span>
<span id="cb10-227"><a href="#cb10-227"></a> <span class="cf">return</span> GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));</span>
<span id="cb10-228"><a href="#cb10-228"></a>}</span></code></pre></div>
<h2 id="total-number-of-lines-words-and-characters">Total number of lines, words and characters</h2>
<pre><code>$ LANG=C wc tfe5/meson.build tfe5/tfeapplication.c tfe5/tfe.gresource.xml tfe5/tfe.h tfe5/tfenotebook.c tfe5/tfenotebook.h tfetextview/tfetextview.c tfetextview/tfetextview.h tfe5/tfe.ui
<p>This is a very simple editor. It is a good practice for you to add
more features.</p>
<h2 id="total-number-of-lines-words-and-characters">Total number of
lines, words and characters</h2>
<pre><code>$ LANG=C wc tfe5/meson.build tfe5/tfeapplication.c tfe5/tfe.gresource.xml tfe5/tfenotebook.c tfe5/tfenotebook.h tfetextview/tfetextview.c tfetextview/tfetextview.h tfe5/tfe.ui
10 17 294 tfe5/meson.build
99 304 3205 tfe5/tfeapplication.c
101 308 3274 tfe5/tfeapplication.c
6 9 153 tfe5/tfe.gresource.xml
4 6 87 tfe5/tfe.h
140 378 3601 tfe5/tfenotebook.c
145 387 3667 tfe5/tfenotebook.c
15 21 241 tfe5/tfenotebook.h
229 671 8017 tfetextview/tfetextview.c
239 863 9264 tfetextview/tfetextview.c
35 60 701 tfetextview/tfetextview.h
61 100 2073 tfe5/tfe.ui
599 1566 18372 total</code></pre>
612 1765 19667 total</code></pre>
</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>

View file

@ -115,24 +115,21 @@
Linux distributions</h1>
<p>This section describes how to install GTK 4 into Linux
distributions.</p>
<p>This tutorial is without any warranty. If you want to install GTK 4
to your computer, do it at your own risk.</p>
<p>The information in this section is the one on April/27/2022. The
words at present and/or now in this section means
April/27/2022.</p>
<p>There are three possible way to install GTK 4.</p>
<p>There are two ways to install GTK 4.</p>
<ul>
<li>Install it from the distribution packages.</li>
<li>Build it from the source file.</li>
<li>Install a GNOME 40 distribution with the GNOME Boxes.</li>
<li>Build it from the source files.</li>
</ul>
<h2 id="installation-from-the-distribution-packages">Installation from
the distribution packages</h2>
<p>The first way is easy to install. It is a recommended way. Ive
installed GTK 4 packages in Ubuntu 21.04. (Now, my Ubuntu version is
21.10).</p>
<pre><code>$ sudo apt-get install libgtk-4-bin libgtk-4-common libgtk-4-dev libgtk-4-doc</code></pre>
<p>Fedora, Arch, Debian and OpenSUSE are also possible. See <a
<p>The first way is the best and easiest way to install it. Ive
installed GTK 4 packages (version 4.8.1) in Ubuntu 22.10.</p>
<pre><code>$ sudo apt install libgtk-4-dev</code></pre>
<p>It is important to install the developing package (libgtk-4-dev). It
includes C header files. Otherwise, you cant compile any GTK 4 based
programs.</p>
<p>Fedora, Debian, Arch, Gentoo and OpenSUSE also have GTK 4 packages.
Package information for Arch, Debian/Ubuntu and Fedra is described by <a
href="https://www.gtk.org/docs/installations/linux#installing-gtk-from-packages">Installing
GTK from packages</a>. The following table shows the distributions which
support GTK 4.</p>
@ -142,336 +139,54 @@ support GTK 4.</p>
<th style="text-align: center;">Distribution</th>
<th style="text-align: center;">version</th>
<th style="text-align: center;">GTK 4</th>
<th style="text-align: center;">GNOME 40</th>
<th style="text-align: center;">GNOME</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: center;">Fedora</td>
<td style="text-align: center;">36</td>
<td style="text-align: center;">4.4.2</td>
<td style="text-align: center;">GNOME 42</td>
<td style="text-align: center;">37</td>
<td style="text-align: center;">4.8.2-2</td>
<td style="text-align: center;">GNOME 43</td>
</tr>
<tr class="even">
<td style="text-align: center;">Ubuntu</td>
<td style="text-align: center;">22.04lts</td>
<td style="text-align: center;">4.4</td>
<td style="text-align: center;">GNOME 41(4.6.2)</td>
<td style="text-align: center;">22.10</td>
<td style="text-align: center;">4.8.1</td>
<td style="text-align: center;">GNOME 43.0</td>
</tr>
<tr class="odd">
<td style="text-align: center;">Debian</td>
<td style="text-align: center;">bookworm(testing)</td>
<td style="text-align: center;">4.6.5</td>
<td style="text-align: center;">GNOME 42</td>
<td style="text-align: center;">4.8.2</td>
<td style="text-align: center;">GNOME 43</td>
</tr>
<tr class="even">
<td style="text-align: center;">Arch</td>
<td style="text-align: center;">rolling release</td>
<td style="text-align: center;">4.6.5</td>
<td style="text-align: center;">GNOME 42</td>
<td style="text-align: center;">4.8.2</td>
<td style="text-align: center;">GNOME 43</td>
</tr>
<tr class="odd">
<td style="text-align: center;">Gentoo</td>
<td style="text-align: center;">rolling release</td>
<td style="text-align: center;">4.6.5</td>
<td style="text-align: center;">GNOME 42</td>
<td style="text-align: center;">4.8.2</td>
<td style="text-align: center;">GNOME 43</td>
</tr>
<tr class="even">
<td style="text-align: center;">OpenSUSE</td>
<td style="text-align: center;">Tumbleweed(rolling release)</td>
<td style="text-align: center;">4.6.5</td>
<td style="text-align: center;">GNOME 42</td>
<td style="text-align: center;">4.8.2</td>
<td style="text-align: center;">GNOME 43</td>
</tr>
</tbody>
</table>
<p>If youve installed GTK 4 from the packages, you dont need to read
the rest of this section.</p>
<h2 id="installation-from-the-source-file">Installation from the source
file</h2>
<p>If your operating system doesnt have GTK 4 packages, you need to
build it from the source. Or, if you want the latest version of GTK 4
(4.6.3), you also need to build it from the source.</p>
<p>I installed GTK 4 from the source in January 2021. So, the following
information is old, especially for the version of each software. For the
latest information, see <a
href="https://docs.gtk.org/gtk4/building.html">GTK API Reference,
Building GTK</a>.</p>
<h3 id="prerequisites-for-gtk-4-installation">Prerequisites for GTK 4
installation</h3>
<ul>
<li>Linux operating system. For example, Ubuntu 20.10 or 20.04LTS. Other
distributions might be OK.</li>
<li>Packages for development such as gcc, meson, ninja, git, wget and so
on.</li>
<li>Dev package is necessary for each software below.</li>
</ul>
<h3 id="installation-target">Installation target</h3>
<p>I installed GTK 4 under the directory <code>$HOME/local</code>. This
is a private user area.</p>
<p>If you want to install it in the system area, <code>/opt/gtk4</code>
is one of good choices. <a
href="https://docs.gtk.org/gtk4/building.html">GTK API Reference,
Building GTK</a> gives an installation example to
<code>/opt/gtk4</code>.</p>
<p>Dont install it to <code>/usr/local</code> which is the default. It
is used by Ubuntu applications which are not build on GTK 4. Therefore,
the risk is high and probably bad things will happen. Actually I did it
and I needed to reinstall Ubuntu.</p>
<h3 id="installation-to-ubuntu-20.10">Installation to Ubuntu 20.10</h3>
<p>Most of the necessary libraries are included by Ubuntu 20.10.
Therefore, they can be installed with <code>apt-get</code> command. You
dont need to install them from the source tarballs. You can skip the
subsections below about prerequisite library installation (GLib, Pango,
GdkPixbuf and GTK-doc).</p>
<h3 id="glib-installation">GLib installation</h3>
<p>If your Ubuntu is 20.04LTS, you need to install prerequisite
libraries from the tarballs. Check the version of your library and if it
is lower than the necessary version, install it from the source.</p>
<p>For example,</p>
<pre><code>$ pkg-config --modversion glib-2.0
2.64.6</code></pre>
<p>The necessary version is 2.66.0 or higher. Therefore, the example
above shows that you need to install GLib.</p>
<p>I installed 2.67.1 which was the latest version at that time (January
2021). Download GLib source files from the repository, then decompress
and extract files.</p>
<pre><code>$ wget https://download.gnome.org/sources/glib/2.67/glib-2.67.1.tar.xz
$ tar -Jxf glib-2.67.1.tar.xz</code></pre>
<p>Some packages are required to build GLib. You can find them if you
run meson.</p>
<pre><code>$ meson --prefix $HOME/local _build</code></pre>
<p>Use apt-get and install the prerequisites. For example,</p>
<pre><code>$ sudo apt-get install -y libpcre2-dev libffi-dev</code></pre>
<p>After that, compile GLib.</p>
<pre><code>$ rm -rf _build
$ meson --prefix $HOME/local _build
$ ninja -C _build
$ ninja -C _build install</code></pre>
<p>Set several environment variables so that the GLib libraries
installed can be used by build tools. Make a text file below and save it
as <code>env.sh</code></p>
<pre><code># compiler
CPPFLAGS=&quot;-I$HOME/local/include&quot;
LDFLAGS=&quot;-L$HOME/local/lib&quot;
PKG_CONFIG_PATH=&quot;$HOME/local/lib/pkgconfig:$HOME/local/lib/x86_64-linux-gnu/pkgconfig&quot;
export CPPFLAGS LDFLAGS PKG_CONFIG_PATH
# linker
LD_LIBRARY_PATH=&quot;$HOME/local/lib/x86_64-linux-gnu/&quot;
PATH=&quot;$HOME/local/bin:$PATH&quot;
export LD_LIBRARY_PATH PATH
# gsetting
export GSETTINGS_SCHEMA_DIR=$HOME/local/share/glib-2.0/schemas</code></pre>
<p>Then, use . (dot) or source command to include these commands to the
current bash.</p>
<pre><code>$ . env.sh</code></pre>
<p>or</p>
<pre><code>$ source env.sh</code></pre>
<p>This command carries out the commands in <code>env.sh</code> and
changes the environment variables above in the current shell.</p>
<h3 id="pango-installation">Pango installation</h3>
<p>Download and untar.</p>
<pre><code>$ wget https://download.gnome.org/sources/pango/1.48/pango-1.48.0.tar.xz
$ tar -Jxf pango-1.48.0.tar.xz</code></pre>
<p>Try meson and check the required packages. Install all the
prerequisites. Then, compile and install Pango.</p>
<pre><code>$ meson --prefix $HOME/local _build
$ ninja -C _build
$ ninja -C _build install</code></pre>
<p>It installs Pango-1.0.gir under
<code>$HOME/local/share/gir-1.0</code>. If you installed Pango without
<code>--prefix</code> option, then it would be located at
<code>/usr/local/share/gir-1.0</code>. This directory (/usr/local/share)
is used by applications. They find the directory by the environment
variable <code>XDG_DATA_DIRS</code>. It is a text file which keep the
list of share directories like <code>/usr/share</code>,
<code>usr/local/share</code> and so on. Now
<code>$HOME/local/share</code> needs to be added to
<code>XDG_DATA_DIRS</code>, or error will occur in the later
compilation.</p>
<pre><code>$ export XDG_DATA_DIRS=$HOME/local/share:$XDG_DATA_DIRS</code></pre>
<h3 id="gdkpixbuf-and-gtk-doc-installation">GdkPixbuf and GTK-Doc
installation</h3>
<p>Download and untar.</p>
<pre><code>$ wget https://download.gnome.org/sources/gdk-pixbuf/2.42/gdk-pixbuf-2.42.2.tar.xz
$ tar -Jxf gdk-pixbuf-2.42.2.tar.xz
$ wget https://download.gnome.org/sources/gtk-doc/1.33/gtk-doc-1.33.1.tar.xz
$ tar -Jxf gtk-doc-1.33.1.tar.xz</code></pre>
<p>Same as before, install prerequisite packages, then compile and
install them.</p>
<p>The installation of GTK-Doc put <code>gtk-doc.pc</code> under
<code>$HOME/local/share/pkgconfig</code>. This file is used by
pkg-config, which is one of the build tools. The directory needs to be
added to the environment variable <code>PKG_CONFIG_PATH</code></p>
<pre><code>$ export PKG_CONFIG_PATH=&quot;$HOME/local/share/pkgconfig:$PKG_CONFIG_PATH&quot;</code></pre>
<h3 id="gtk-4-installation">GTK 4 installation</h3>
<p>If you want the latest development version of GTK 4, use git and
clone the repository.</p>
<pre><code>$ git clone https://gitlab.gnome.org/gnome/gtk.git</code></pre>
<p>If you want a stable version of GTK 4, then download it from <a
href="https://download.gnome.org/sources/gtk/">GNOME source website</a>.
The latest version is 4.3.1 (13/June/2021).</p>
<p>Compile and install it.</p>
<pre><code>$ meson --prefix $HOME/local _build
$ ninja -C _build
$ ninja -C _build install</code></pre>
<p>If you want to know more information, refer to <a
href="https://docs.gtk.org/gtk4/building.html">GTK 4 API Reference,
Building GTK</a>.</p>
<h3 id="modify-env.sh">Modify env.sh</h3>
<p>Because environment variables disappear when you log out, you need to
add them again. Modify <code>env.sh</code>.</p>
<pre><code># compiler
CPPFLAGS=&quot;-I$HOME/local/include&quot;
LDFLAGS=&quot;-L$HOME/local/lib&quot;
PKG_CONFIG_PATH=&quot;$HOME/local/lib/pkgconfig:$HOME/local/lib/x86_64-linux-gnu/pkgconfig:
$HOME/local/share/pkgconfig&quot;
export CPPFLAGS LDFLAGS PKG_CONFIG_PATH
# linker
LD_LIBRARY_PATH=&quot;$HOME/local/lib/x86_64-linux-gnu/&quot;
PATH=&quot;$HOME/local/bin:$PATH&quot;
export LD_LIBRARY_PATH PATH
# gir
XDG_DATA_DIRS=$HOME/local/share:$XDG_DATA_DIRS
export XDG_DATA_DIRS
# gsetting
export GSETTINGS_SCHEMA_DIR=$HOME/local/share/glib-2.0/schemas
# girepository-1.0
export GI_TYPELIB_PATH=$HOME/local/lib/x86_64-linux-gnu/girepository-1.0</code></pre>
<p>Include this file by . (dot) command before using GTK 4
libraries.</p>
<p>You may think you can add them in your <code>.profile</code>. But
its a wrong decision. Never write them to your <code>.profile</code>.
The environment variables above are necessary only when you compile and
run GTK 4 applications. Otherwise its not necessary. If you changed the
environment variables above and run GTK 3 applications, it probably
causes serious damage.</p>
<h3 id="compiling-gtk-4-applications">Compiling GTK 4 applications</h3>
<p>Before you compile GTK 4 applications, define environment variables
above.</p>
<pre><code>$ . env.sh</code></pre>
<p>After that you can compile them without anything. For example, to
compile <code>sample.c</code>, type the following.</p>
<pre><code>$ gcc `pkg-config --cflags gtk4` sample.c `pkg-config --libs gtk4`</code></pre>
<p>To know how to compile GTK 4 applications, refer to the section 3
(GtkApplication and GtkApplicationWindow) and after.</p>
<h2 id="installing-fedora-34-with-gnome-boxes">Installing Fedora 34 with
GNOME Boxes</h2>
<p>The last part of this section is about GNOME 40 and GNOME Boxes.
GNOME 40 is a new version of GNOME desktop system. And GTK 4 is
installed in the distribution. See <a
href="https://forty.gnome.org/">GNOME 40 website</a> first.</p>
<p><em>However, GNOME 40 is not necessary to compile and run GTK 4
applications.</em></p>
<p>There are seven choices at present.</p>
<ul>
<li>GNOME OS</li>
<li>Arch Linux</li>
<li>Gentoo Linux</li>
<li>Fedora 36</li>
<li>openSUSE Tumbleweed</li>
<li>Ubuntu 22.04</li>
<li>Debian bookworm</li>
</ul>
<p>Ive installed Fedora 34 with GNOME Boxes. My OS was Ubuntu 21.04 at
that time. GNOME Boxes creates a virtual machine in Ubuntu and Fedora
will be installed to that virtual machine.</p>
<p>The instruction is as follows.</p>
<ol type="1">
<li>Download Fedora 34 iso file. There is an link at the end of <a
href="https://forty.gnome.org/">GNOME 40 website</a>.</li>
<li>Install gnome-boxes with apt-get command.</li>
</ol>
<pre><code>$ sudo apt-get install gnome-boxes</code></pre>
<ol start="3" type="1">
<li>Run GNOME Boxes.</li>
<li>Click on <code>+</code> button on the top left corner and launch a
box creation wizard by clicking
<code>Create a Virtual Machine ...</code>. Then a dialog appears. Click
on <code>Operationg System Image File</code> and select the iso file you
have downloaded.</li>
<li>Then, the Fedoras installer is executed. Follow the instructions by
the installer. At the end of the installation, the installer instructs
to reboot the system. Click on the right of the title bar and select
reboot or shutdown.</li>
<li>Your display is back to the initial window of GNOME Boxes, but there
is a button <code>Fedora 34 Workstation</code> on the upper left of the
window. Click on the button then Fedora will be executed.</li>
<li>A setup dialog appears. Setup Fedora according to the wizard.</li>
</ol>
<p>Now you can use Fedora. It includes GTK 4 libraries already. But you
need to install the GTK 4 development package. Use <code>dnf</code> to
install <code>gtk4.x86_64</code> package.</p>
<pre><code>$ sudo dnf install gtk4.x86_64</code></pre>
<h3 id="gtk-4-compilation-test">GTK 4 compilation test</h3>
<p>You can test the GTK 4 development packages by compiling files which
are based on GTK 4. Ive tried compiling <code>tfe</code> text editor,
which is written in section 21.</p>
<ol type="1">
<li>Run Firefox.</li>
<li>Open this website (<a
href="https://github.com/ToshioCP/Gtk4-tutorial">Gtk4-Tutorial</a>).</li>
<li>Click on the green button labeled <code>Code</code>.</li>
<li>Select <code>Download ZIP</code> and download the codes from the
repository.</li>
<li>Unzip the file.</li>
<li>Change your current directory to <code>src/tfe7</code>.</li>
<li>Compile it.</li>
</ol>
<pre><code>$ meson _build
bash: meson: command not found...
Install package &#39;meson&#39; to provide command &#39;meson&#39;? [N/y] y
* Waiting in queue...
The following packages have to be installed:
meson-0.56.2-2.fc34.noarch High productivity build system
ninja-build-1.10.2-2.fc34.x86_64 Small build system with a focus on speed
vim-filesystem-2:8.2.2787-1.fc34.noarch VIM filesystem layout
Proceed with changes? [N/y] y
... ...
... ...
The Meson build system
Version: 0.56.2
... ...
... ...
Project name: tfe
Project version: undefined
C compiler for the host machine: cc (gcc 11.0.0 &quot;cc (GCC) 11.0.0 20210210 (Red Hat 11.0.0-0)&quot;)
C linker for the host machine: cc ld.bfd 2.35.1-38
Host machine cpu family: x86_64
Host machine cpu: x86_64
Found pkg-config: /usr/bin/pkg-config (1.7.3)
Run-time dependency gtk4 found: YES 4.2.0
Found pkg-config: /usr/bin/pkg-config (1.7.3)
Program glib-compile-resources found: YES (/usr/bin/glib-compile-resources)
Program glib-compile-schemas found: YES (/usr/bin/glib-compile-schemas)
Program glib-compile-schemas found: YES (/usr/bin/glib-compile-schemas)
Build targets in project: 4
Found ninja-1.10.2 at /usr/bin/ninja
$ ninja -C _build
ninja: Entering directory `_build&#39;
[12/12] Linking target tfe
$ ninja -C _build install
ninja: Entering directory `_build&#39;
[0/1] Installing files.
Installing tfe to /usr/local/bin
Installation failed due to insufficient permissions.
Attempting to use polkit to gain elevated privileges...
Installing tfe to /usr/local/bin
Installing /home/&lt;username&gt;/Gtk4-tutorial-main/src/tfe7/com.github.ToshioCP.tfe.gschema.xml to /usr/local/share/glib-2.0/schemas
Running custom install script &#39;/usr/bin/glib-compile-schemas /usr/local/share/glib-2.0/schemas/&#39;</code></pre>
<ol start="8" type="1">
<li>Execute it.</li>
</ol>
<pre><code>$ tfe</code></pre>
<p>Then, the window of <code>tfe</code> text editor appears. The
compilation and execution have succeeded.</p>
<p>If you want to install a developing version of GTK 4, you need to
build it from the source. See <a
href="https://docs.gtk.org/gtk4/building.html">Compiling the GTK
Libraries</a> secion in the GTK 4 API reference.</p>
</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>

View file

@ -116,9 +116,9 @@ GtkApplicationWindow</h1>
<h2 id="gtkapplication">GtkApplication</h2>
<h3 id="gtkapplication-and-g_application_run">GtkApplication and
g_application_run</h3>
<p>Usually people write programming code to make an application. What
are applications? Applications are software that runs using libraries,
which includes the OS, frameworks and so on. In GTK 4 programming, the
<p>People write programming code to make an application. What are
applications? Applications are software that runs using libraries, which
includes the OS, frameworks and so on. In GTK 4 programming, the
GtkApplication is a program (or executable) that runs using Gtk
libraries.</p>
<p>The basic way to write a GtkApplication is as follows.</p>
@ -136,24 +136,32 @@ class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span i
<span id="cb1-5"><a href="#cb1-5"></a> GtkApplication <span class="op">*</span>app<span class="op">;</span></span>
<span id="cb1-6"><a href="#cb1-6"></a> <span class="dt">int</span> stat<span class="op">;</span></span>
<span id="cb1-7"><a href="#cb1-7"></a></span>
<span id="cb1-8"><a href="#cb1-8"></a> app <span class="op">=</span> gtk_application_new <span class="op">(</span><span class="st">&quot;com.github.ToshioCP.pr1&quot;</span><span class="op">,</span> G_APPLICATION_FLAGS_NONE<span class="op">);</span></span>
<span id="cb1-8"><a href="#cb1-8"></a> app <span class="op">=</span> gtk_application_new <span class="op">(</span><span class="st">&quot;com.github.ToshioCP.pr1&quot;</span><span class="op">,</span> G_APPLICATION_DEFAULT_FLAGS<span class="op">);</span></span>
<span id="cb1-9"><a href="#cb1-9"></a> stat <span class="op">=</span>g_application_run <span class="op">(</span>G_APPLICATION <span class="op">(</span>app<span class="op">),</span> argc<span class="op">,</span> argv<span class="op">);</span></span>
<span id="cb1-10"><a href="#cb1-10"></a> g_object_unref <span class="op">(</span>app<span class="op">);</span></span>
<span id="cb1-11"><a href="#cb1-11"></a> <span class="cf">return</span> stat<span class="op">;</span></span>
<span id="cb1-12"><a href="#cb1-12"></a><span class="op">}</span></span></code></pre></div>
<p>The first line says that this program includes the header files of
the Gtk libraries. The function <code>main</code> above is a startup
function in C language. The variable <code>app</code> is defined as a
pointer to a GtkApplication instance. The function
<code>gtk_application_new</code> creates a GtkApplication instance and
returns a pointer to the instance. The GtkApplication instance is a C
structure data in which the information about the application is stored.
The meaning of the arguments will be explained later. The function
the Gtk libraries. The function <code>main</code> is a startup function
in C language. The variable <code>app</code> is defined as a pointer to
a GtkApplication instance. The function <code>gtk_application_new</code>
creates a GtkApplication instance and returns a pointer to the instance.
The GtkApplication instance is a C structure data in which the
information about the application is stored. The meaning of the
arguments will be explained later. The function
<code>g_application_run</code> runs an application that the instance
defined. (We often say that the function runs <code>app</code>.
Actually, <code>app</code> is not an application but a pointer to the
instance of the application. However, it is simple and short, and
probably no confusion occurs.)</p>
<p>Here I used the word <code>instance</code>. Instance, class and
object are terminologies in Object Oriented Programming. I use these
words in the same way. But, I will often use “object” instead of
“instance” in this tutorial. That means “object” and “instance” is the
same. Object is a bit ambiguous word. In a broad sense, object has wider
meaning than instance. So, readers should be careful of the contexts to
find the meaning of “object”. In many cases, object and instance are the
same.</p>
<p>To compile this, the following command needs to be run. The string
<code>pr1.c</code> is the filename of the C source code above.</p>
<pre><code>$ gcc `pkg-config --cflags gtk4` pr1.c `pkg-config --libs gtk4`</code></pre>
@ -180,16 +188,17 @@ what this message means.</p>
explain that to you first.</p>
<p>A signal is emitted when something happens. For example, a window is
created, a window is destroyed and so on. The signal “activate” is
emitted when the application is activated, or started. If the signal is
connected to a function, which is called a signal handler or simply
handler, then the function is invoked when the signal emits.</p>
emitted when the application is activated. (Activated is a bit different
from started, but you can think the both are almost same so far.) If the
signal is connected to a function, which is called a signal handler or
simply handler, then the function is invoked when the signal emits.</p>
<p>The flow is like this:</p>
<ol type="1">
<li>Something happens.</li>
<li>If its related to a certain signal, then the signal is
emitted.</li>
<li>If the signal as been connected to a handler, then the handler is
invoked.</li>
<li>If the signal has been connected to a handler in advance, then the
handler is invoked.</li>
</ol>
<p>Signals are defined in objects. For example, the “activate” signal
belongs to the GApplication object, which is a parent object of
@ -217,16 +226,18 @@ class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span i
<span id="cb5-10"><a href="#cb5-10"></a> GtkApplication <span class="op">*</span>app<span class="op">;</span></span>
<span id="cb5-11"><a href="#cb5-11"></a> <span class="dt">int</span> stat<span class="op">;</span></span>
<span id="cb5-12"><a href="#cb5-12"></a></span>
<span id="cb5-13"><a href="#cb5-13"></a> app <span class="op">=</span> gtk_application_new <span class="op">(</span><span class="st">&quot;com.github.ToshioCP.pr2&quot;</span><span class="op">,</span> G_APPLICATION_FLAGS_NONE<span class="op">);</span></span>
<span id="cb5-13"><a href="#cb5-13"></a> app <span class="op">=</span> gtk_application_new <span class="op">(</span><span class="st">&quot;com.github.ToshioCP.pr2&quot;</span><span class="op">,</span> G_APPLICATION_DEFAULT_FLAGS<span class="op">);</span></span>
<span id="cb5-14"><a href="#cb5-14"></a> g_signal_connect <span class="op">(</span>app<span class="op">,</span> <span class="st">&quot;activate&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>app_activate<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb5-15"><a href="#cb5-15"></a> stat <span class="op">=</span>g_application_run <span class="op">(</span>G_APPLICATION <span class="op">(</span>app<span class="op">),</span> argc<span class="op">,</span> argv<span class="op">);</span></span>
<span id="cb5-16"><a href="#cb5-16"></a> g_object_unref <span class="op">(</span>app<span class="op">);</span></span>
<span id="cb5-17"><a href="#cb5-17"></a> <span class="cf">return</span> stat<span class="op">;</span></span>
<span id="cb5-18"><a href="#cb5-18"></a><span class="op">}</span></span></code></pre></div>
<p>First, we define the handler <code>app_activate</code> which simply
displays a message. In the function <code>main</code>, we add
<code>g_signal_connect</code> before <code>g_application_run</code>. The
function <code>g_signal_connect</code> has four arguments.</p>
displays a message. The function <code>g_print</code> is defined in GLib
and its like a printf in the C standard library. In the function
<code>main</code>, we add <code>g_signal_connect</code> before
<code>g_application_run</code>. The function
<code>g_signal_connect</code> has four arguments.</p>
<ol type="1">
<li>An instance to which the signal belongs.</li>
<li>The name of the signal.</li>
@ -235,15 +246,36 @@ by <code>G_CALLBACK</code>.</li>
<li>Data to pass to the handler. If no data is necessary, NULL should be
given.</li>
</ol>
<p>You can find the description of each signal in the API reference
manual. For example, “activate” signal is in GApplication section in <a
href="https://docs.gtk.org/gio/signal.Application.activate.html">GIO API
Reference</a>. The handler function is described in it.</p>
<p>In addition, <code>g_signal_connect</code> is described in <a
<p>It is described in the <a
href="https://docs.gtk.org/gobject/func.signal_connect.html">GObject API
Reference</a>. API reference manual is very important. You should see
and understand it to write Gtk applications. They are located in <a
href="https://docs.gtk.org/">GTK Documentation</a>.</p>
Reference</a>. Correctly, <code>g_signal_connect</code> is a macro (not
a C 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="pp">#define g_signal_connect (</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a> instance<span class="op">,</span></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a> detailed_signal<span class="op">,</span></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a> c_handler<span class="op">,</span></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a> data</span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a><span class="op">)</span></span></code></pre></div>
<p>You can find the description of each signal in the API reference
manual. For example, “activate” signal is in <a
href="https://docs.gtk.org/gio/signal.Application.activate.html">GApplication
section</a> in the GIO API Reference.</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="dt">void</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a>activate <span class="op">(</span></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a> GApplication<span class="op">*</span> self<span class="op">,</span></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a> gpointer user_data</span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a><span class="op">)</span></span></code></pre></div>
<p>This is a declaration of the “activate” signal handler. You can use
any name instead of “activate” in the declaration above. The parameters
are:</p>
<ul>
<li>self is an instance to which the signal belongs.</li>
<li>user_data is a data defined in the fourth argument of the
<code>g_signal_connect</code> function. If it is NULL, then you can
ignore and left out the second parameter.</li>
</ul>
<p>API reference manual is very important. You should see and understand
it.</p>
<p>Lets compile the source file above (<code>pr2.c</code>) and run
it.</p>
<pre><code>$ gcc `pkg-config --cflags gtk4` pr2.c `pkg-config --libs gtk4`
@ -285,15 +317,15 @@ program. What we need to do is:</p>
</ol>
<p>Now rewrite the function <code>app_activate</code>.</p>
<h4 id="create-a-gtkwindow">Create a GtkWindow</h4>
<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> <span class="dt">void</span></span>
<span id="cb10-2"><a href="#cb10-2"></a>app_activate <span class="op">(</span>GApplication <span class="op">*</span>app<span class="op">,</span> gpointer user_data<span class="op">)</span> <span class="op">{</span></span>
<span id="cb10-3"><a href="#cb10-3"></a> GtkWidget <span class="op">*</span>win<span class="op">;</span></span>
<span id="cb10-4"><a href="#cb10-4"></a></span>
<span id="cb10-5"><a href="#cb10-5"></a> win <span class="op">=</span> gtk_window_new <span class="op">();</span></span>
<span id="cb10-6"><a href="#cb10-6"></a> gtk_window_set_application <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> GTK_APPLICATION <span class="op">(</span>app<span class="op">));</span></span>
<span id="cb10-7"><a href="#cb10-7"></a> gtk_widget_show <span class="op">(</span>win<span class="op">);</span></span>
<span id="cb10-8"><a href="#cb10-8"></a><span class="op">}</span></span></code></pre></div>
<div class="sourceCode" id="cb12"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb12-1"><a href="#cb12-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb12-2"><a href="#cb12-2"></a>app_activate <span class="op">(</span>GApplication <span class="op">*</span>app<span class="op">,</span> gpointer user_data<span class="op">)</span> <span class="op">{</span></span>
<span id="cb12-3"><a href="#cb12-3"></a> GtkWidget <span class="op">*</span>win<span class="op">;</span></span>
<span id="cb12-4"><a href="#cb12-4"></a></span>
<span id="cb12-5"><a href="#cb12-5"></a> win <span class="op">=</span> gtk_window_new <span class="op">();</span></span>
<span id="cb12-6"><a href="#cb12-6"></a> gtk_window_set_application <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> GTK_APPLICATION <span class="op">(</span>app<span class="op">));</span></span>
<span id="cb12-7"><a href="#cb12-7"></a> gtk_widget_show <span class="op">(</span>win<span class="op">);</span></span>
<span id="cb12-8"><a href="#cb12-8"></a><span class="op">}</span></span></code></pre></div>
<p>Widget is an abstract concept that includes all the GUI interfaces
such as windows, dialogs, buttons, multi-line text, containers and so
on. And GtkWidget is a base object from which all the GUI objects
@ -306,28 +338,29 @@ GtkWidget -- GtkWindow</code></pre>
<figcaption aria-hidden="true">GtkWindow and GtkWidget</figcaption>
</figure>
<p>The function <code>gtk_window_new</code> is defined as follows.</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a>GtkWidget <span class="op">*</span></span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a>gtk_window_new <span class="op">(</span><span class="dt">void</span><span class="op">);</span></span></code></pre></div>
<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>GtkWidget <span class="op">*</span></span>
<span id="cb14-2"><a href="#cb14-2" aria-hidden="true" tabindex="-1"></a>gtk_window_new <span class="op">(</span><span class="dt">void</span><span class="op">);</span></span></code></pre></div>
<p>By this definition, it returns a pointer to GtkWidget, not GtkWindow.
It actually creates a new GtkWindow instance (not GtkWidget) but returns
a pointer to GtkWidget. However,the pointer points the GtkWidget and at
the same time it also points GtkWindow that contains GtkWidget in
it.</p>
<p>If you want to use <code>win</code> as a pointer to the GtkWindow,
you need to cast it.</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><span class="op">(</span>GtkWindow <span class="op">*)</span> win</span></code></pre></div>
<p>Or you can use <code>GTK_WINDOW</code> macro that performs a similar
function.</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>GTK_WINDOW <span class="op">(</span>win<span class="op">)</span></span></code></pre></div>
<p>This is a recommended way.</p>
<p>If you want to use <code>win</code> as a pointer to a GtkWindow type
instance, you need to cast it.</p>
<div class="sourceCode" id="cb15"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb15-1"><a href="#cb15-1" aria-hidden="true" tabindex="-1"></a><span class="op">(</span>GtkWindow <span class="op">*)</span> win</span></code></pre></div>
<p>It works, but isnt usually used. Instead, <code>GTK_WINDOW</code>
macro is used.</p>
<div class="sourceCode" id="cb16"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb16-1"><a href="#cb16-1" aria-hidden="true" tabindex="-1"></a>GTK_WINDOW <span class="op">(</span>win<span class="op">)</span></span></code></pre></div>
<p>The macro is recommended because it does not only cast but also check
the type.</p>
<h4 id="connect-it-to-gtkapplication.">Connect it to
GtkApplication.</h4>
<p>The function <code>gtk_window_set_application</code> is used to
connect GtkWindow to GtkApplication.</p>
<div class="sourceCode" id="cb15"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb15-1"><a href="#cb15-1" aria-hidden="true" tabindex="-1"></a>gtk_window_set_application <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> GTK_APPLICATION <span class="op">(</span>app<span class="op">));</span></span></code></pre></div>
<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>gtk_window_set_application <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> GTK_APPLICATION <span class="op">(</span>app<span class="op">));</span></span></code></pre></div>
<p>You need to cast <code>win</code> to GtkWindow and <code>app</code>
to GtkApplication. <code>GTK_WINDOW</code> and
<code>GTK_APPLICATION</code> macro is appropriate for that.</p>
to GtkApplication with <code>GTK_WINDOW</code> and
<code>GTK_APPLICATION</code> macro.</p>
<p>GtkApplication continues to run until the related window is
destroyed. If you didnt connect GtkWindow and GtkApplication,
GtkApplication destroys itself immediately. Because no window is
@ -340,7 +373,7 @@ window.</p>
doesnt need this function to show itself. But, theres an exception.
Top window (this term will be explained later) isnt visible when it is
created. So you need to use the function above to show the window.</p>
<p>Save the program as <code>pr3.c</code> and compile and run it.</p>
<p>Save the program as <code>pr3.c</code>, then compile and run it.</p>
<pre><code>$ comp pr3
$ ./a.out</code></pre>
<p>A small window appears.</p>
@ -352,20 +385,20 @@ $ ./a.out</code></pre>
finishes.</p>
<h3 id="gtkapplicationwindow">GtkApplicationWindow</h3>
<p>GtkApplicationWindow is a child object of GtkWindow. It has some
extra functionality for better integration with GtkApplication. It is
recommended to use it instead of GtkWindow when you use
GtkApplication.</p>
extra feature for better integration with GtkApplication. It is
recommended to use it as the top-level window of the application instead
of GtkWindow.</p>
<p>Now rewrite the program and use GtkApplicationWindow.</p>
<div class="sourceCode" id="cb17"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb17-1"><a href="#cb17-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb17-2"><a href="#cb17-2"></a>app_activate <span class="op">(</span>GApplication <span class="op">*</span>app<span class="op">,</span> gpointer user_data<span class="op">)</span> <span class="op">{</span></span>
<span id="cb17-3"><a href="#cb17-3"></a> GtkWidget <span class="op">*</span>win<span class="op">;</span></span>
<span id="cb17-4"><a href="#cb17-4"></a></span>
<span id="cb17-5"><a href="#cb17-5"></a> win <span class="op">=</span> gtk_application_window_new <span class="op">(</span>GTK_APPLICATION <span class="op">(</span>app<span class="op">));</span></span>
<span id="cb17-6"><a href="#cb17-6"></a> gtk_window_set_title <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> <span class="st">&quot;pr4&quot;</span><span class="op">);</span></span>
<span id="cb17-7"><a href="#cb17-7"></a> gtk_window_set_default_size <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> <span class="dv">400</span><span class="op">,</span> <span class="dv">300</span><span class="op">);</span></span>
<span id="cb17-8"><a href="#cb17-8"></a> gtk_widget_show <span class="op">(</span>win<span class="op">);</span></span>
<span id="cb17-9"><a href="#cb17-9"></a><span class="op">}</span></span></code></pre></div>
<div class="sourceCode" id="cb19"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb19-1"><a href="#cb19-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb19-2"><a href="#cb19-2"></a>app_activate <span class="op">(</span>GApplication <span class="op">*</span>app<span class="op">,</span> gpointer user_data<span class="op">)</span> <span class="op">{</span></span>
<span id="cb19-3"><a href="#cb19-3"></a> GtkWidget <span class="op">*</span>win<span class="op">;</span></span>
<span id="cb19-4"><a href="#cb19-4"></a></span>
<span id="cb19-5"><a href="#cb19-5"></a> win <span class="op">=</span> gtk_application_window_new <span class="op">(</span>GTK_APPLICATION <span class="op">(</span>app<span class="op">));</span></span>
<span id="cb19-6"><a href="#cb19-6"></a> gtk_window_set_title <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> <span class="st">&quot;pr4&quot;</span><span class="op">);</span></span>
<span id="cb19-7"><a href="#cb19-7"></a> gtk_window_set_default_size <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> <span class="dv">400</span><span class="op">,</span> <span class="dv">300</span><span class="op">);</span></span>
<span id="cb19-8"><a href="#cb19-8"></a> gtk_widget_show <span class="op">(</span>win<span class="op">);</span></span>
<span id="cb19-9"><a href="#cb19-9"></a><span class="op">}</span></span></code></pre></div>
<p>When you create GtkApplicationWindow, you need to give GtkApplication
instance as an argument. Then it automatically connect these two
instances. So you dont need to call

View file

@ -5,7 +5,7 @@
<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>Gtk4 tutorial</title>
<title>GTK 4 tutorial</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
@ -112,46 +112,59 @@
</div>
</nav>
<h1 id="widgets-1">Widgets (1)</h1>
<h2 id="gtklabel-gtkbutton-and-gtkbox">GtkLabel, GtkButton and GtkBox</h2>
<h2 id="gtklabel-gtkbutton-and-gtkbox">GtkLabel, GtkButton and
GtkBox</h2>
<h3 id="gtklabel">GtkLabel</h3>
<p>In the previous section we made a window and displayed it on the screen. Now we go on to the next topic, where we add widgets to this window. The simplest widget is GtkLabel. It is a widget with text in it.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb1-1"><a href="#cb1-1"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<p>In the previous section we made a window and displayed it on the
screen. Now we go on to the next topic, where we add widgets to this
window. The simplest widget is GtkLabel. It is a widget with text in
it.</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb1-1"><a href="#cb1-1"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<span id="cb1-2"><a href="#cb1-2"></a></span>
<span id="cb1-3"><a href="#cb1-3"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb1-4"><a href="#cb1-4"></a>app_activate (GApplication *app, gpointer user_data) {</span>
<span id="cb1-5"><a href="#cb1-5"></a> GtkWidget *win;</span>
<span id="cb1-6"><a href="#cb1-6"></a> GtkWidget *lab;</span>
<span id="cb1-4"><a href="#cb1-4"></a>app_activate <span class="op">(</span>GApplication <span class="op">*</span>app<span class="op">)</span> <span class="op">{</span></span>
<span id="cb1-5"><a href="#cb1-5"></a> GtkWidget <span class="op">*</span>win<span class="op">;</span></span>
<span id="cb1-6"><a href="#cb1-6"></a> GtkWidget <span class="op">*</span>lab<span class="op">;</span></span>
<span id="cb1-7"><a href="#cb1-7"></a></span>
<span id="cb1-8"><a href="#cb1-8"></a> win = gtk_application_window_new (GTK_APPLICATION (app));</span>
<span id="cb1-9"><a href="#cb1-9"></a> gtk_window_set_title (GTK_WINDOW (win), <span class="st">&quot;lb1&quot;</span>);</span>
<span id="cb1-10"><a href="#cb1-10"></a> gtk_window_set_default_size (GTK_WINDOW (win), <span class="dv">400</span>, <span class="dv">300</span>);</span>
<span id="cb1-8"><a href="#cb1-8"></a> win <span class="op">=</span> gtk_application_window_new <span class="op">(</span>GTK_APPLICATION <span class="op">(</span>app<span class="op">));</span></span>
<span id="cb1-9"><a href="#cb1-9"></a> gtk_window_set_title <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> <span class="st">&quot;lb1&quot;</span><span class="op">);</span></span>
<span id="cb1-10"><a href="#cb1-10"></a> gtk_window_set_default_size <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> <span class="dv">400</span><span class="op">,</span> <span class="dv">300</span><span class="op">);</span></span>
<span id="cb1-11"><a href="#cb1-11"></a></span>
<span id="cb1-12"><a href="#cb1-12"></a> lab = gtk_label_new (<span class="st">&quot;Hello.&quot;</span>);</span>
<span id="cb1-13"><a href="#cb1-13"></a> gtk_window_set_child (GTK_WINDOW (win), lab);</span>
<span id="cb1-12"><a href="#cb1-12"></a> lab <span class="op">=</span> gtk_label_new <span class="op">(</span><span class="st">&quot;Hello.&quot;</span><span class="op">);</span></span>
<span id="cb1-13"><a href="#cb1-13"></a> gtk_window_set_child <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> lab<span class="op">);</span></span>
<span id="cb1-14"><a href="#cb1-14"></a></span>
<span id="cb1-15"><a href="#cb1-15"></a> gtk_widget_show (win);</span>
<span id="cb1-16"><a href="#cb1-16"></a>}</span>
<span id="cb1-15"><a href="#cb1-15"></a> gtk_widget_show <span class="op">(</span>win<span class="op">);</span></span>
<span id="cb1-16"><a href="#cb1-16"></a><span class="op">}</span></span>
<span id="cb1-17"><a href="#cb1-17"></a></span>
<span id="cb1-18"><a href="#cb1-18"></a><span class="dt">int</span></span>
<span id="cb1-19"><a href="#cb1-19"></a>main (<span class="dt">int</span> argc, <span class="dt">char</span> **argv) {</span>
<span id="cb1-20"><a href="#cb1-20"></a> GtkApplication *app;</span>
<span id="cb1-21"><a href="#cb1-21"></a> <span class="dt">int</span> stat;</span>
<span id="cb1-19"><a href="#cb1-19"></a>main <span class="op">(</span><span class="dt">int</span> argc<span class="op">,</span> <span class="dt">char</span> <span class="op">**</span>argv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb1-20"><a href="#cb1-20"></a> GtkApplication <span class="op">*</span>app<span class="op">;</span></span>
<span id="cb1-21"><a href="#cb1-21"></a> <span class="dt">int</span> stat<span class="op">;</span></span>
<span id="cb1-22"><a href="#cb1-22"></a></span>
<span id="cb1-23"><a href="#cb1-23"></a> app = gtk_application_new (<span class="st">&quot;com.github.ToshioCP.lb1&quot;</span>, G_APPLICATION_FLAGS_NONE);</span>
<span id="cb1-24"><a href="#cb1-24"></a> g_signal_connect (app, <span class="st">&quot;activate&quot;</span>, G_CALLBACK (app_activate), NULL);</span>
<span id="cb1-25"><a href="#cb1-25"></a> stat =g_application_run (G_APPLICATION (app), argc, argv);</span>
<span id="cb1-26"><a href="#cb1-26"></a> g_object_unref (app);</span>
<span id="cb1-27"><a href="#cb1-27"></a> <span class="cf">return</span> stat;</span>
<span id="cb1-28"><a href="#cb1-28"></a>}</span></code></pre></div>
<p>Save this program to a file <code>lb1.c</code>. Then compile and run it.</p>
<span id="cb1-23"><a href="#cb1-23"></a> app <span class="op">=</span> gtk_application_new <span class="op">(</span><span class="st">&quot;com.github.ToshioCP.lb1&quot;</span><span class="op">,</span> G_APPLICATION_DEFAULT_FLAGS<span class="op">);</span></span>
<span id="cb1-24"><a href="#cb1-24"></a> g_signal_connect <span class="op">(</span>app<span class="op">,</span> <span class="st">&quot;activate&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>app_activate<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb1-25"><a href="#cb1-25"></a> stat <span class="op">=</span>g_application_run <span class="op">(</span>G_APPLICATION <span class="op">(</span>app<span class="op">),</span> argc<span class="op">,</span> argv<span class="op">);</span></span>
<span id="cb1-26"><a href="#cb1-26"></a> g_object_unref <span class="op">(</span>app<span class="op">);</span></span>
<span id="cb1-27"><a href="#cb1-27"></a> <span class="cf">return</span> stat<span class="op">;</span></span>
<span id="cb1-28"><a href="#cb1-28"></a><span class="op">}</span></span></code></pre></div>
<p>Save this program to a file <code>lb1.c</code>. Then compile and run
it.</p>
<pre><code>$ comp lb1
$ ./a.out</code></pre>
<p>A window with a message “Hello.” appears.</p>
<figure>
<img src="image/screenshot_lb1.png" alt="" /><figcaption>Screenshot of the label</figcaption>
<img src="image/screenshot_lb1.png" alt="Screenshot of the label" />
<figcaption aria-hidden="true">Screenshot of the label</figcaption>
</figure>
<p>Theres only a little change between <code>pr4.c</code> and <code>lb1.c</code>. A program <code>diff</code> is good to know the difference between two files.</p>
<p>Theres only a little change between <code>pr4.c</code> and
<code>lb1.c</code>. A program <code>diff</code> is good to know the
difference between two files.</p>
<pre><code>$ cd misc; diff pr4.c lb1.c
4c4
&lt; app_activate (GApplication *app, gpointer user_data) {
---
&gt; app_activate (GApplication *app) {
5a6
&gt; GtkWidget *lab;
8c9
@ -164,193 +177,290 @@ $ ./a.out</code></pre>
&gt; gtk_window_set_child (GTK_WINDOW (win), lab);
&gt;
18c23
&lt; app = gtk_application_new (&quot;com.github.ToshioCP.pr4&quot;, G_APPLICATION_FLAGS_NONE);
&lt; app = gtk_application_new (&quot;com.github.ToshioCP.pr4&quot;, G_APPLICATION_DEFAULT_FLAGS);
---
&gt; app = gtk_application_new (&quot;com.github.ToshioCP.lb1&quot;, G_APPLICATION_FLAGS_NONE);</code></pre>
&gt; app = gtk_application_new (&quot;com.github.ToshioCP.lb1&quot;, G_APPLICATION_DEFAULT_FLAGS);</code></pre>
<p>This tells us:</p>
<ul>
<li>A signal handler <code>app_activate</code> doesnt have
<code>user_data</code> parameter. If the fourth argument of
<code>g_signal_connect</code> is NULL, you can leave out
<code>user_data</code>.</li>
<li>The definition of a new variable <code>lab</code> is added.</li>
<li>The title of the window is changed.</li>
<li>A label is created and connected to the window as a child.</li>
</ul>
<p>The function <code>gtk_window_set_child (GTK_WINDOW (win), lab)</code> makes the label <code>lab</code> a child widget of the window <code>win</code>. Be careful. A child widget is different from a child object. Objects have parent-child relationships and widgets also have parent-child relationships. But these two relationships are totally different. Dont be confused. In the program <code>lb1.c</code>, <code>lab</code> is a child widget of <code>win</code>. Child widgets are always located in their parent widget on the screen. See how the window has appeared on the screen. The application window includes the label.</p>
<p>The window <code>win</code> doesnt have any parents. We call such a window top-level window. An application can have more than one top-level window.</p>
<p>The function
<code>gtk_window_set_child (GTK_WINDOW (win), lab)</code> makes the
label <code>lab</code> a child widget of the window <code>win</code>. Be
careful. A child widget is different from a child object. Objects have
parent-child relationships and widgets also have parent-child
relationships. But these two relationships are totally different. Dont
be confused. In the program <code>lb1.c</code>, <code>lab</code> is a
child widget of <code>win</code>. Child widgets are always located in
their parent widget on the screen. See how the window has appeared on
the screen. The application window includes the label.</p>
<p>The window <code>win</code> doesnt have any parents. We call such a
window top-level window. An application can have more than one top-level
window.</p>
<h3 id="gtkbutton">GtkButton</h3>
<p>The next widget to introduce is GtkButton. It displays a button on the screen with a label or icon on it. In this subsection, we will make a button with a label. When the button is clicked, it emits a “clicked” signal. The following program shows how to catch the signal to then do something.</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb4-1"><a href="#cb4-1"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<p>The next widget is GtkButton. It displays a button on the screen with
a label or icon on it. In this subsection, we will make a button with a
label. When the button is clicked, it emits a “clicked” signal. The
following program shows how to catch the signal and do something.</p>
<div class="sourceCode" id="cb4"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb4-1"><a href="#cb4-1"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<span id="cb4-2"><a href="#cb4-2"></a></span>
<span id="cb4-3"><a href="#cb4-3"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb4-4"><a href="#cb4-4"></a>click_cb (GtkButton *btn, gpointer user_data) {</span>
<span id="cb4-5"><a href="#cb4-5"></a> g_print (<span class="st">&quot;Clicked.</span><span class="sc">\n</span><span class="st">&quot;</span>);</span>
<span id="cb4-6"><a href="#cb4-6"></a>}</span>
<span id="cb4-4"><a href="#cb4-4"></a>click_cb <span class="op">(</span>GtkButton <span class="op">*</span>btn<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-5"><a href="#cb4-5"></a> g_print <span class="op">(</span><span class="st">&quot;Clicked.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb4-6"><a href="#cb4-6"></a><span class="op">}</span></span>
<span id="cb4-7"><a href="#cb4-7"></a></span>
<span id="cb4-8"><a href="#cb4-8"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb4-9"><a href="#cb4-9"></a>app_activate (GApplication *app, gpointer user_data) {</span>
<span id="cb4-10"><a href="#cb4-10"></a> GtkWidget *win;</span>
<span id="cb4-11"><a href="#cb4-11"></a> GtkWidget *btn;</span>
<span id="cb4-9"><a href="#cb4-9"></a>app_activate <span class="op">(</span>GApplication <span class="op">*</span>app<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-10"><a href="#cb4-10"></a> GtkWidget <span class="op">*</span>win<span class="op">;</span></span>
<span id="cb4-11"><a href="#cb4-11"></a> GtkWidget <span class="op">*</span>btn<span class="op">;</span></span>
<span id="cb4-12"><a href="#cb4-12"></a></span>
<span id="cb4-13"><a href="#cb4-13"></a> win = gtk_application_window_new (GTK_APPLICATION (app));</span>
<span id="cb4-14"><a href="#cb4-14"></a> gtk_window_set_title (GTK_WINDOW (win), <span class="st">&quot;lb2&quot;</span>);</span>
<span id="cb4-15"><a href="#cb4-15"></a> gtk_window_set_default_size (GTK_WINDOW (win), <span class="dv">400</span>, <span class="dv">300</span>);</span>
<span id="cb4-13"><a href="#cb4-13"></a> win <span class="op">=</span> gtk_application_window_new <span class="op">(</span>GTK_APPLICATION <span class="op">(</span>app<span class="op">));</span></span>
<span id="cb4-14"><a href="#cb4-14"></a> gtk_window_set_title <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> <span class="st">&quot;lb2&quot;</span><span class="op">);</span></span>
<span id="cb4-15"><a href="#cb4-15"></a> gtk_window_set_default_size <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> <span class="dv">400</span><span class="op">,</span> <span class="dv">300</span><span class="op">);</span></span>
<span id="cb4-16"><a href="#cb4-16"></a></span>
<span id="cb4-17"><a href="#cb4-17"></a> btn = gtk_button_new_with_label (<span class="st">&quot;Click me&quot;</span>);</span>
<span id="cb4-18"><a href="#cb4-18"></a> gtk_window_set_child (GTK_WINDOW (win), btn);</span>
<span id="cb4-19"><a href="#cb4-19"></a> g_signal_connect (btn, <span class="st">&quot;clicked&quot;</span>, G_CALLBACK (click_cb), NULL);</span>
<span id="cb4-17"><a href="#cb4-17"></a> btn <span class="op">=</span> gtk_button_new_with_label <span class="op">(</span><span class="st">&quot;Click me&quot;</span><span class="op">);</span></span>
<span id="cb4-18"><a href="#cb4-18"></a> gtk_window_set_child <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> btn<span class="op">);</span></span>
<span id="cb4-19"><a href="#cb4-19"></a> g_signal_connect <span class="op">(</span>btn<span class="op">,</span> <span class="st">&quot;clicked&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>click_cb<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb4-20"><a href="#cb4-20"></a></span>
<span id="cb4-21"><a href="#cb4-21"></a> gtk_widget_show (win);</span>
<span id="cb4-22"><a href="#cb4-22"></a>}</span>
<span id="cb4-21"><a href="#cb4-21"></a> gtk_widget_show <span class="op">(</span>win<span class="op">);</span></span>
<span id="cb4-22"><a href="#cb4-22"></a><span class="op">}</span></span>
<span id="cb4-23"><a href="#cb4-23"></a></span>
<span id="cb4-24"><a href="#cb4-24"></a><span class="dt">int</span></span>
<span id="cb4-25"><a href="#cb4-25"></a>main (<span class="dt">int</span> argc, <span class="dt">char</span> **argv) {</span>
<span id="cb4-26"><a href="#cb4-26"></a> GtkApplication *app;</span>
<span id="cb4-27"><a href="#cb4-27"></a> <span class="dt">int</span> stat;</span>
<span id="cb4-25"><a href="#cb4-25"></a>main <span class="op">(</span><span class="dt">int</span> argc<span class="op">,</span> <span class="dt">char</span> <span class="op">**</span>argv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-26"><a href="#cb4-26"></a> GtkApplication <span class="op">*</span>app<span class="op">;</span></span>
<span id="cb4-27"><a href="#cb4-27"></a> <span class="dt">int</span> stat<span class="op">;</span></span>
<span id="cb4-28"><a href="#cb4-28"></a></span>
<span id="cb4-29"><a href="#cb4-29"></a> app = gtk_application_new (<span class="st">&quot;com.github.ToshioCP.lb2&quot;</span>, G_APPLICATION_FLAGS_NONE);</span>
<span id="cb4-30"><a href="#cb4-30"></a> g_signal_connect (app, <span class="st">&quot;activate&quot;</span>, G_CALLBACK (app_activate), NULL);</span>
<span id="cb4-31"><a href="#cb4-31"></a> stat =g_application_run (G_APPLICATION (app), argc, argv);</span>
<span id="cb4-32"><a href="#cb4-32"></a> g_object_unref (app);</span>
<span id="cb4-33"><a href="#cb4-33"></a> <span class="cf">return</span> stat;</span>
<span id="cb4-34"><a href="#cb4-34"></a>}</span></code></pre></div>
<p>Look at the line 17 to 19. First, it creates a GtkButton instance <code>btn</code> with a label “Click me”. Then, adds the button to the window <code>win</code> as a child. Finally, connects a “clicked” signal of the button to a handler (function) <code>click_cb</code>. So, if <code>btn</code> is clicked, the function <code>click_cb</code> is invoked. The suffix “cb” means “call back”.</p>
<p>Name the program <code>lb2.c</code> and save it. Now compile and run it.</p>
<span id="cb4-29"><a href="#cb4-29"></a> app <span class="op">=</span> gtk_application_new <span class="op">(</span><span class="st">&quot;com.github.ToshioCP.lb2&quot;</span><span class="op">,</span> G_APPLICATION_DEFAULT_FLAGS<span class="op">);</span></span>
<span id="cb4-30"><a href="#cb4-30"></a> g_signal_connect <span class="op">(</span>app<span class="op">,</span> <span class="st">&quot;activate&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>app_activate<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb4-31"><a href="#cb4-31"></a> stat <span class="op">=</span>g_application_run <span class="op">(</span>G_APPLICATION <span class="op">(</span>app<span class="op">),</span> argc<span class="op">,</span> argv<span class="op">);</span></span>
<span id="cb4-32"><a href="#cb4-32"></a> g_object_unref <span class="op">(</span>app<span class="op">);</span></span>
<span id="cb4-33"><a href="#cb4-33"></a> <span class="cf">return</span> stat<span class="op">;</span></span>
<span id="cb4-34"><a href="#cb4-34"></a><span class="op">}</span></span></code></pre></div>
<p>Look at the line 17 to 19. First, it creates a GtkButton instance
<code>btn</code> with a label “Click me”. Then, adds the button to the
window <code>win</code> as a child. Finally, connects a “clicked” signal
of the button to the handler <code>click_cb</code>. So, if
<code>btn</code> is clicked, the function <code>click_cb</code> is
invoked. The suffix “cb” means “call back”.</p>
<p>Name the program <code>lb2.c</code> and save it. Now compile and run
it.</p>
<figure>
<img src="image/screenshot_lb2.png" alt="" /><figcaption>Screenshot of the label</figcaption>
<img src="image/screenshot_lb2.png" alt="Screenshot of the label" />
<figcaption aria-hidden="true">Screenshot of the label</figcaption>
</figure>
<p>A window with the button appears. Click the button (it is a large button, you can click everywhere in the window), then a string “Clicked.” appears on the terminal. It shows the handler was invoked by clicking the button.</p>
<p>Its good that we make sure that the clicked signal was caught and the handler was invoked by using <code>g_print</code>. However, using g_print is out of harmony with Gtk which is a GUI library. So, we will change the handler. The following code is <code>lb3.c</code>.</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb5-1"><a href="#cb5-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb5-2"><a href="#cb5-2"></a>click_cb (GtkButton *btn, gpointer user_data) {</span>
<span id="cb5-3"><a href="#cb5-3"></a> GtkWindow *win = GTK_WINDOW (user_data);</span>
<span id="cb5-4"><a href="#cb5-4"></a> gtk_window_destroy (win);</span>
<span id="cb5-5"><a href="#cb5-5"></a>}</span>
<span id="cb5-6"><a href="#cb5-6"></a></span>
<span id="cb5-7"><a href="#cb5-7"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb5-8"><a href="#cb5-8"></a>app_activate (GApplication *app, gpointer user_data) {</span>
<span id="cb5-9"><a href="#cb5-9"></a> GtkWidget *win;</span>
<span id="cb5-10"><a href="#cb5-10"></a> GtkWidget *btn;</span>
<span id="cb5-11"><a href="#cb5-11"></a></span>
<span id="cb5-12"><a href="#cb5-12"></a> win = gtk_application_window_new (GTK_APPLICATION (app));</span>
<span id="cb5-13"><a href="#cb5-13"></a> gtk_window_set_title (GTK_WINDOW (win), <span class="st">&quot;lb3&quot;</span>);</span>
<span id="cb5-14"><a href="#cb5-14"></a> gtk_window_set_default_size (GTK_WINDOW (win), <span class="dv">400</span>, <span class="dv">300</span>);</span>
<span id="cb5-15"><a href="#cb5-15"></a></span>
<span id="cb5-16"><a href="#cb5-16"></a> btn = gtk_button_new_with_label (<span class="st">&quot;Quit&quot;</span>);</span>
<span id="cb5-17"><a href="#cb5-17"></a> gtk_window_set_child (GTK_WINDOW (win), btn);</span>
<span id="cb5-18"><a href="#cb5-18"></a> g_signal_connect (btn, <span class="st">&quot;clicked&quot;</span>, G_CALLBACK (click_cb), win);</span>
<span id="cb5-19"><a href="#cb5-19"></a></span>
<span id="cb5-20"><a href="#cb5-20"></a> gtk_widget_show (win);</span>
<span id="cb5-21"><a href="#cb5-21"></a>}</span></code></pre></div>
<p>And the difference between <code>lb2.c</code> and <code>lb3.c</code> is as follows.</p>
<p>A window with the button appears. Click the button (it is a large
button, you can click everywhere in the window), then a string
“Clicked.” appears on the terminal. It shows the handler was invoked by
clicking the button.</p>
<p>Its good that we make sure that the clicked signal was caught and
the handler was invoked by using <code>g_print</code>. However, using
<code>g_print</code> is out of harmony with GTK, which is a GUI library.
So, we will change the handler. The following code is
<code>lb3.c</code>.</p>
<div class="sourceCode" id="cb5"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb5-1"><a href="#cb5-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb5-2"><a href="#cb5-2"></a>click_cb <span class="op">(</span>GtkButton <span class="op">*</span>btn<span class="op">,</span> GtkWindow <span class="op">*</span>win<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-3"><a href="#cb5-3"></a> gtk_window_destroy <span class="op">(</span>win<span class="op">);</span></span>
<span id="cb5-4"><a href="#cb5-4"></a><span class="op">}</span></span>
<span id="cb5-5"><a href="#cb5-5"></a></span>
<span id="cb5-6"><a href="#cb5-6"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb5-7"><a href="#cb5-7"></a>app_activate <span class="op">(</span>GApplication <span class="op">*</span>app<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-8"><a href="#cb5-8"></a> GtkWidget <span class="op">*</span>win<span class="op">;</span></span>
<span id="cb5-9"><a href="#cb5-9"></a> GtkWidget <span class="op">*</span>btn<span class="op">;</span></span>
<span id="cb5-10"><a href="#cb5-10"></a></span>
<span id="cb5-11"><a href="#cb5-11"></a> win <span class="op">=</span> gtk_application_window_new <span class="op">(</span>GTK_APPLICATION <span class="op">(</span>app<span class="op">));</span></span>
<span id="cb5-12"><a href="#cb5-12"></a> gtk_window_set_title <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> <span class="st">&quot;lb3&quot;</span><span class="op">);</span></span>
<span id="cb5-13"><a href="#cb5-13"></a> gtk_window_set_default_size <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> <span class="dv">400</span><span class="op">,</span> <span class="dv">300</span><span class="op">);</span></span>
<span id="cb5-14"><a href="#cb5-14"></a></span>
<span id="cb5-15"><a href="#cb5-15"></a> btn <span class="op">=</span> gtk_button_new_with_label <span class="op">(</span><span class="st">&quot;Close&quot;</span><span class="op">);</span></span>
<span id="cb5-16"><a href="#cb5-16"></a> gtk_window_set_child <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> btn<span class="op">);</span></span>
<span id="cb5-17"><a href="#cb5-17"></a> g_signal_connect <span class="op">(</span>btn<span class="op">,</span> <span class="st">&quot;clicked&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>click_cb<span class="op">),</span> win<span class="op">);</span></span>
<span id="cb5-18"><a href="#cb5-18"></a></span>
<span id="cb5-19"><a href="#cb5-19"></a> gtk_widget_show <span class="op">(</span>win<span class="op">);</span></span>
<span id="cb5-20"><a href="#cb5-20"></a><span class="op">}</span></span></code></pre></div>
<p>And the difference between <code>lb2.c</code> and <code>lb3.c</code>
is as follows.</p>
<pre><code>$ cd misc; diff lb2.c lb3.c
5c5,6
4,5c4,5
&lt; click_cb (GtkButton *btn) {
&lt; g_print (&quot;Clicked.\n&quot;);
---
&gt; GtkWindow *win = GTK_WINDOW (user_data);
&gt; click_cb (GtkButton *btn, GtkWindow *win) {
&gt; gtk_window_destroy (win);
14c15
14c14
&lt; gtk_window_set_title (GTK_WINDOW (win), &quot;lb2&quot;);
---
&gt; gtk_window_set_title (GTK_WINDOW (win), &quot;lb3&quot;);
17c18
17c17
&lt; btn = gtk_button_new_with_label (&quot;Click me&quot;);
---
&gt; btn = gtk_button_new_with_label (&quot;Quit&quot;);
19c20
&gt; btn = gtk_button_new_with_label (&quot;Close&quot;);
19c19
&lt; g_signal_connect (btn, &quot;clicked&quot;, G_CALLBACK (click_cb), NULL);
---
&gt; g_signal_connect (btn, &quot;clicked&quot;, G_CALLBACK (click_cb), win);
29c30
&lt; app = gtk_application_new (&quot;com.github.ToshioCP.lb2&quot;, G_APPLICATION_FLAGS_NONE);
29c29
&lt; app = gtk_application_new (&quot;com.github.ToshioCP.lb2&quot;, G_APPLICATION_DEFAULT_FLAGS);
---
&gt; app = gtk_application_new (&quot;com.github.ToshioCP.lb3&quot;, G_APPLICATION_FLAGS_NONE);
35d35
&gt; app = gtk_application_new (&quot;com.github.ToshioCP.lb3&quot;, G_APPLICATION_DEFAULT_FLAGS);
35d34
&lt; </code></pre>
<p>The changes are:</p>
<ul>
<li>The function <code>g_print</code> in <code>lb2.c</code> was deleted and the two lines above are inserted instead.</li>
<li>The label of <code>btn</code> is changed from “Click me” to “Quit”.</li>
<li>The fourth argument of <code>g_signal_connect</code> is changed from <code>NULL</code> to <code>win</code>.</li>
<li>The function <code>g_print</code> in <code>lb2.c</code> was deleted
and two lines are inserted.
<ul>
<li><code>click_cb</code> has the second parameter, which comes from the
fourth argument of the <code>g_signal_connect</code> at the line 19. One
thing to be careful is the types are different between the second
parameter of <code>click_cb</code> and the fourth argument of
<code>g_signal_connect</code>. The former is <code>GtkWindow *</code>
and the latter is <code>GtkWidget *</code>. The compiler doesnt
complain because <code>g_signal_connect</code> uses gpointer (general
type of pointer). In this program the instance pointed by
<code>win</code> is a GtkApplicationWindow object. It is a descendant of
GtkWindow and GtkWidget class, so both <code>GtkWindow *</code> and
<code>GtkWidget *</code> are correct types for the instance.</li>
<li><code>gtk_destroy (win)</code> destroys the top-level window. Then
the application quits.</li>
</ul></li>
<li>The label of <code>btn</code> is changed from “Click me” to
“Close”.</li>
<li>The fourth argument of <code>g_signal_connect</code> is changed from
<code>NULL</code> to <code>win</code>.</li>
</ul>
<p>The most important change is the fourth argument of <code>g_signal_connect</code>. This argument is described as “data to pass to handler” in the definition of <code>g_signal_connect</code> in <a href="https://docs.gtk.org/gobject/func.signal_connect.html">GObject API Reference</a>. Therefore, <code>win</code> which is a pointer to GtkApplicationWindow is passed to the handler as a second parameter <code>user_data</code>. The handler then casts it to a pointer to GtkWindow and calls <code>gtk_window_destroy</code> to destroy the top-level window. The application then quits.</p>
<p>The most important change is the fourth argument of the
<code>g_signal_connect</code>. This argument is described as “data to
pass to handler” in the definition of <a
href="https://docs.gtk.org/gobject/func.signal_connect.html"><code>g_signal_connect</code></a>.</p>
<h3 id="gtkbox">GtkBox</h3>
<p>GtkWindow and GtkApplicationWindow can have only one child. If you want to add two or more widgets in a window, you need a container widget. GtkBox is one of the containers. It arranges two or more child widgets into a single row or column. The following procedure shows the way to add two buttons in a window.</p>
<p>GtkWindow and GtkApplicationWindow can have only one child. If you
want to add two or more widgets in a window, you need a container
widget. GtkBox is one of the containers. It arranges two or more child
widgets into a single row or column. The following procedure shows the
way to add two buttons in a window.</p>
<ul>
<li>Create a GtkApplicationWindow instance.</li>
<li>Create a GtkBox instance and add it to the GtkApplicationWindow as a child.</li>
<li>Create a GtkBox instance and add it to the GtkApplicationWindow as a
child.</li>
<li>Create a GtkButton instance and append it to the GtkBox.</li>
<li>Create another GtkButton instance and append it to the GtkBox.</li>
</ul>
<p>After this, the Widgets are connected as the following diagram.</p>
<figure>
<img src="image/box.png" alt="" /><figcaption>Parent-child relationship</figcaption>
<img src="image/box.png" alt="Parent-child relationship" />
<figcaption aria-hidden="true">Parent-child relationship</figcaption>
</figure>
<p>The program <code>lb4.c</code> includes these widgets. It is as follows.</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb7-1"><a href="#cb7-1"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<p>The program <code>lb4.c</code> is as follows.</p>
<div class="sourceCode" id="cb7"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb7-1"><a href="#cb7-1"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<span id="cb7-2"><a href="#cb7-2"></a></span>
<span id="cb7-3"><a href="#cb7-3"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb7-4"><a href="#cb7-4"></a>click1_cb (GtkButton *btn, gpointer user_data) {</span>
<span id="cb7-5"><a href="#cb7-5"></a> <span class="dt">const</span> gchar *s;</span>
<span id="cb7-4"><a href="#cb7-4"></a>click1_cb <span class="op">(</span>GtkButton <span class="op">*</span>btn<span class="op">)</span> <span class="op">{</span></span>
<span id="cb7-5"><a href="#cb7-5"></a> <span class="dt">const</span> <span class="dt">char</span> <span class="op">*</span>s<span class="op">;</span></span>
<span id="cb7-6"><a href="#cb7-6"></a></span>
<span id="cb7-7"><a href="#cb7-7"></a> s = gtk_button_get_label (btn);</span>
<span id="cb7-8"><a href="#cb7-8"></a> <span class="cf">if</span> (g_strcmp0 (s, <span class="st">&quot;Hello.&quot;</span>) == <span class="dv">0</span>)</span>
<span id="cb7-9"><a href="#cb7-9"></a> gtk_button_set_label (btn, <span class="st">&quot;Good-bye.&quot;</span>);</span>
<span id="cb7-7"><a href="#cb7-7"></a> s <span class="op">=</span> gtk_button_get_label <span class="op">(</span>btn<span class="op">);</span></span>
<span id="cb7-8"><a href="#cb7-8"></a> <span class="cf">if</span> <span class="op">(</span>g_strcmp0 <span class="op">(</span>s<span class="op">,</span> <span class="st">&quot;Hello.&quot;</span><span class="op">)</span> <span class="op">==</span> <span class="dv">0</span><span class="op">)</span></span>
<span id="cb7-9"><a href="#cb7-9"></a> gtk_button_set_label <span class="op">(</span>btn<span class="op">,</span> <span class="st">&quot;Good-bye.&quot;</span><span class="op">);</span></span>
<span id="cb7-10"><a href="#cb7-10"></a> <span class="cf">else</span></span>
<span id="cb7-11"><a href="#cb7-11"></a> gtk_button_set_label (btn, <span class="st">&quot;Hello.&quot;</span>);</span>
<span id="cb7-12"><a href="#cb7-12"></a>}</span>
<span id="cb7-11"><a href="#cb7-11"></a> gtk_button_set_label <span class="op">(</span>btn<span class="op">,</span> <span class="st">&quot;Hello.&quot;</span><span class="op">);</span></span>
<span id="cb7-12"><a href="#cb7-12"></a><span class="op">}</span></span>
<span id="cb7-13"><a href="#cb7-13"></a></span>
<span id="cb7-14"><a href="#cb7-14"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb7-15"><a href="#cb7-15"></a>click2_cb (GtkButton *btn, gpointer user_data) {</span>
<span id="cb7-16"><a href="#cb7-16"></a> GtkWindow *win = GTK_WINDOW (user_data);</span>
<span id="cb7-17"><a href="#cb7-17"></a> gtk_window_destroy (win);</span>
<span id="cb7-18"><a href="#cb7-18"></a>}</span>
<span id="cb7-19"><a href="#cb7-19"></a></span>
<span id="cb7-20"><a href="#cb7-20"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb7-21"><a href="#cb7-21"></a>app_activate (GApplication *app, gpointer user_data) {</span>
<span id="cb7-22"><a href="#cb7-22"></a> GtkWidget *win;</span>
<span id="cb7-23"><a href="#cb7-23"></a> GtkWidget *box;</span>
<span id="cb7-24"><a href="#cb7-24"></a> GtkWidget *btn1;</span>
<span id="cb7-25"><a href="#cb7-25"></a> GtkWidget *btn2;</span>
<span id="cb7-26"><a href="#cb7-26"></a></span>
<span id="cb7-27"><a href="#cb7-27"></a> win = gtk_application_window_new (GTK_APPLICATION (app));</span>
<span id="cb7-28"><a href="#cb7-28"></a> gtk_window_set_title (GTK_WINDOW (win), <span class="st">&quot;lb4&quot;</span>);</span>
<span id="cb7-29"><a href="#cb7-29"></a> gtk_window_set_default_size (GTK_WINDOW (win), <span class="dv">400</span>, <span class="dv">300</span>);</span>
<span id="cb7-30"><a href="#cb7-30"></a></span>
<span id="cb7-31"><a href="#cb7-31"></a> box = gtk_box_new (GTK_ORIENTATION_VERTICAL, <span class="dv">5</span>);</span>
<span id="cb7-32"><a href="#cb7-32"></a> gtk_box_set_homogeneous (GTK_BOX (box), TRUE);</span>
<span id="cb7-33"><a href="#cb7-33"></a> gtk_window_set_child (GTK_WINDOW (win), box);</span>
<span id="cb7-34"><a href="#cb7-34"></a></span>
<span id="cb7-35"><a href="#cb7-35"></a> btn1 = gtk_button_new_with_label (<span class="st">&quot;Hello.&quot;</span>);</span>
<span id="cb7-36"><a href="#cb7-36"></a> g_signal_connect (btn1, <span class="st">&quot;clicked&quot;</span>, G_CALLBACK (click1_cb), NULL);</span>
<span id="cb7-37"><a href="#cb7-37"></a></span>
<span id="cb7-38"><a href="#cb7-38"></a> btn2 = gtk_button_new_with_label (<span class="st">&quot;Quit&quot;</span>);</span>
<span id="cb7-39"><a href="#cb7-39"></a> g_signal_connect (btn2, <span class="st">&quot;clicked&quot;</span>, G_CALLBACK (click2_cb), win);</span>
<span id="cb7-40"><a href="#cb7-40"></a></span>
<span id="cb7-41"><a href="#cb7-41"></a> gtk_box_append (GTK_BOX (box), btn1);</span>
<span id="cb7-42"><a href="#cb7-42"></a> gtk_box_append (GTK_BOX (box), btn2);</span>
<span id="cb7-43"><a href="#cb7-43"></a></span>
<span id="cb7-44"><a href="#cb7-44"></a> gtk_widget_show (win);</span>
<span id="cb7-45"><a href="#cb7-45"></a>}</span>
<span id="cb7-46"><a href="#cb7-46"></a></span>
<span id="cb7-47"><a href="#cb7-47"></a><span class="dt">int</span></span>
<span id="cb7-48"><a href="#cb7-48"></a>main (<span class="dt">int</span> argc, <span class="dt">char</span> **argv) {</span>
<span id="cb7-49"><a href="#cb7-49"></a> GtkApplication *app;</span>
<span id="cb7-50"><a href="#cb7-50"></a> <span class="dt">int</span> stat;</span>
<span id="cb7-51"><a href="#cb7-51"></a></span>
<span id="cb7-52"><a href="#cb7-52"></a> app = gtk_application_new (<span class="st">&quot;com.github.ToshioCP.lb4&quot;</span>, G_APPLICATION_FLAGS_NONE);</span>
<span id="cb7-53"><a href="#cb7-53"></a> g_signal_connect (app, <span class="st">&quot;activate&quot;</span>, G_CALLBACK (app_activate), NULL);</span>
<span id="cb7-54"><a href="#cb7-54"></a> stat =g_application_run (G_APPLICATION (app), argc, argv);</span>
<span id="cb7-55"><a href="#cb7-55"></a> g_object_unref (app);</span>
<span id="cb7-56"><a href="#cb7-56"></a> <span class="cf">return</span> stat;</span>
<span id="cb7-57"><a href="#cb7-57"></a>}</span></code></pre></div>
<span id="cb7-15"><a href="#cb7-15"></a>click2_cb <span class="op">(</span>GtkButton <span class="op">*</span>btn<span class="op">,</span> GtkWindow <span class="op">*</span>win<span class="op">)</span> <span class="op">{</span></span>
<span id="cb7-16"><a href="#cb7-16"></a> gtk_window_destroy <span class="op">(</span>win<span class="op">);</span></span>
<span id="cb7-17"><a href="#cb7-17"></a><span class="op">}</span></span>
<span id="cb7-18"><a href="#cb7-18"></a></span>
<span id="cb7-19"><a href="#cb7-19"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb7-20"><a href="#cb7-20"></a>app_activate <span class="op">(</span>GApplication <span class="op">*</span>app<span class="op">)</span> <span class="op">{</span></span>
<span id="cb7-21"><a href="#cb7-21"></a> GtkWidget <span class="op">*</span>win<span class="op">;</span></span>
<span id="cb7-22"><a href="#cb7-22"></a> GtkWidget <span class="op">*</span>box<span class="op">;</span></span>
<span id="cb7-23"><a href="#cb7-23"></a> GtkWidget <span class="op">*</span>btn1<span class="op">;</span></span>
<span id="cb7-24"><a href="#cb7-24"></a> GtkWidget <span class="op">*</span>btn2<span class="op">;</span></span>
<span id="cb7-25"><a href="#cb7-25"></a></span>
<span id="cb7-26"><a href="#cb7-26"></a> win <span class="op">=</span> gtk_application_window_new <span class="op">(</span>GTK_APPLICATION <span class="op">(</span>app<span class="op">));</span></span>
<span id="cb7-27"><a href="#cb7-27"></a> gtk_window_set_title <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> <span class="st">&quot;lb4&quot;</span><span class="op">);</span></span>
<span id="cb7-28"><a href="#cb7-28"></a> gtk_window_set_default_size <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> <span class="dv">400</span><span class="op">,</span> <span class="dv">300</span><span class="op">);</span></span>
<span id="cb7-29"><a href="#cb7-29"></a></span>
<span id="cb7-30"><a href="#cb7-30"></a> box <span class="op">=</span> gtk_box_new <span class="op">(</span>GTK_ORIENTATION_VERTICAL<span class="op">,</span> <span class="dv">5</span><span class="op">);</span></span>
<span id="cb7-31"><a href="#cb7-31"></a> gtk_box_set_homogeneous <span class="op">(</span>GTK_BOX <span class="op">(</span>box<span class="op">),</span> TRUE<span class="op">);</span></span>
<span id="cb7-32"><a href="#cb7-32"></a> gtk_window_set_child <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> box<span class="op">);</span></span>
<span id="cb7-33"><a href="#cb7-33"></a></span>
<span id="cb7-34"><a href="#cb7-34"></a> btn1 <span class="op">=</span> gtk_button_new_with_label <span class="op">(</span><span class="st">&quot;Hello.&quot;</span><span class="op">);</span></span>
<span id="cb7-35"><a href="#cb7-35"></a> g_signal_connect <span class="op">(</span>btn1<span class="op">,</span> <span class="st">&quot;clicked&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>click1_cb<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb7-36"><a href="#cb7-36"></a></span>
<span id="cb7-37"><a href="#cb7-37"></a> btn2 <span class="op">=</span> gtk_button_new_with_label <span class="op">(</span><span class="st">&quot;Close&quot;</span><span class="op">);</span></span>
<span id="cb7-38"><a href="#cb7-38"></a> g_signal_connect <span class="op">(</span>btn2<span class="op">,</span> <span class="st">&quot;clicked&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>click2_cb<span class="op">),</span> win<span class="op">);</span></span>
<span id="cb7-39"><a href="#cb7-39"></a></span>
<span id="cb7-40"><a href="#cb7-40"></a> gtk_box_append <span class="op">(</span>GTK_BOX <span class="op">(</span>box<span class="op">),</span> btn1<span class="op">);</span></span>
<span id="cb7-41"><a href="#cb7-41"></a> gtk_box_append <span class="op">(</span>GTK_BOX <span class="op">(</span>box<span class="op">),</span> btn2<span class="op">);</span></span>
<span id="cb7-42"><a href="#cb7-42"></a></span>
<span id="cb7-43"><a href="#cb7-43"></a> gtk_widget_show <span class="op">(</span>win<span class="op">);</span></span>
<span id="cb7-44"><a href="#cb7-44"></a><span class="op">}</span></span>
<span id="cb7-45"><a href="#cb7-45"></a></span>
<span id="cb7-46"><a href="#cb7-46"></a><span class="dt">int</span></span>
<span id="cb7-47"><a href="#cb7-47"></a>main <span class="op">(</span><span class="dt">int</span> argc<span class="op">,</span> <span class="dt">char</span> <span class="op">**</span>argv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb7-48"><a href="#cb7-48"></a> GtkApplication <span class="op">*</span>app<span class="op">;</span></span>
<span id="cb7-49"><a href="#cb7-49"></a> <span class="dt">int</span> stat<span class="op">;</span></span>
<span id="cb7-50"><a href="#cb7-50"></a></span>
<span id="cb7-51"><a href="#cb7-51"></a> app <span class="op">=</span> gtk_application_new <span class="op">(</span><span class="st">&quot;com.github.ToshioCP.lb4&quot;</span><span class="op">,</span> G_APPLICATION_DEFAULT_FLAGS<span class="op">);</span></span>
<span id="cb7-52"><a href="#cb7-52"></a> g_signal_connect <span class="op">(</span>app<span class="op">,</span> <span class="st">&quot;activate&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>app_activate<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb7-53"><a href="#cb7-53"></a> stat <span class="op">=</span>g_application_run <span class="op">(</span>G_APPLICATION <span class="op">(</span>app<span class="op">),</span> argc<span class="op">,</span> argv<span class="op">);</span></span>
<span id="cb7-54"><a href="#cb7-54"></a> g_object_unref <span class="op">(</span>app<span class="op">);</span></span>
<span id="cb7-55"><a href="#cb7-55"></a> <span class="cf">return</span> stat<span class="op">;</span></span>
<span id="cb7-56"><a href="#cb7-56"></a><span class="op">}</span></span></code></pre></div>
<p>Look at the function <code>app_activate</code>.</p>
<p>After the creation of a GtkApplicationWindow instance, a GtkBox instance is created.</p>
<pre><code>box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
gtk_box_set_homogeneous (GTK_BOX (box), TRUE);</code></pre>
<p>The first argument arranges the children of the box vertically. The second argument is the size between the children. The next function fills the box with the children, giving them the same space.</p>
<p>After that, two buttons <code>btn1</code> and <code>btn2</code> are created and the signal handlers are set. Then, these two buttons are appended to the box.</p>
<p>After the creation of a GtkApplicationWindow instance, a GtkBox
instance is created.</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>box <span class="op">=</span> gtk_box_new<span class="op">(</span>GTK_ORIENTATION_VERTICAL<span class="op">,</span> <span class="dv">5</span><span class="op">);</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a>gtk_box_set_homogeneous <span class="op">(</span>GTK_BOX <span class="op">(</span>box<span class="op">),</span> TRUE<span class="op">);</span></span></code></pre></div>
<p>The first argument arranges the children of the box vertically. The
orientation constants are defined like this:</p>
<ul>
<li>GTK_ORIENTATION_VERTICAL: the children widgets are arranged
vertically</li>
<li>GTK_ORIENTATION_HORIZONTAL: the children widgets are arranged
horizontally</li>
</ul>
<p>The second argument is the size between the children. The unit of the
length is pixel.</p>
<p>The next function fills the box with the children, giving them the
same space.</p>
<p>After that, two buttons <code>btn1</code> and <code>btn2</code> are
created and the signal handlers are set. Then, these two buttons are
appended to the box.</p>
<div class="sourceCode" id="cb9"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb9-1"><a href="#cb9-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb9-2"><a href="#cb9-2"></a>click1_cb <span class="op">(</span>GtkButton <span class="op">*</span>btn<span class="op">)</span> <span class="op">{</span></span>
<span id="cb9-3"><a href="#cb9-3"></a> <span class="dt">const</span> <span class="dt">char</span> <span class="op">*</span>s<span class="op">;</span></span>
<span id="cb9-4"><a href="#cb9-4"></a></span>
<span id="cb9-5"><a href="#cb9-5"></a> s <span class="op">=</span> gtk_button_get_label <span class="op">(</span>btn<span class="op">);</span></span>
<span id="cb9-6"><a href="#cb9-6"></a> <span class="cf">if</span> <span class="op">(</span>g_strcmp0 <span class="op">(</span>s<span class="op">,</span> <span class="st">&quot;Hello.&quot;</span><span class="op">)</span> <span class="op">==</span> <span class="dv">0</span><span class="op">)</span></span>
<span id="cb9-7"><a href="#cb9-7"></a> gtk_button_set_label <span class="op">(</span>btn<span class="op">,</span> <span class="st">&quot;Good-bye.&quot;</span><span class="op">);</span></span>
<span id="cb9-8"><a href="#cb9-8"></a> <span class="cf">else</span></span>
<span id="cb9-9"><a href="#cb9-9"></a> gtk_button_set_label <span class="op">(</span>btn<span class="op">,</span> <span class="st">&quot;Hello.&quot;</span><span class="op">);</span></span>
<span id="cb9-10"><a href="#cb9-10"></a><span class="op">}</span></span></code></pre></div>
<p>The function <code>gtk_button_get_lable</code> returns a text from
the label. The string is owned by the button and you cant modify or
free it. The <code>const</code> qualifier is necessary for the string
<code>s</code>. If you change the string, your compiler will give you a
waring.</p>
<p>You always need to be careful with the const qualifier when you see
the GTK 4 API reference.</p>
<figure>
<img src="image/screenshot_lb4.png" alt="" /><figcaption>Screenshot of the box</figcaption>
<img src="image/screenshot_lb4.png" alt="Screenshot of the box" />
<figcaption aria-hidden="true">Screenshot of the box</figcaption>
</figure>
<p>The handler corresponds to <code>btn1</code> toggles its label. The handler corresponds to <code>btn2</code> destroys the top-level window and the application quits.</p>
<p>The handler corresponds to <code>btn1</code> toggles its label. The
handler corresponds to <code>btn2</code> destroys the top-level window
and the application quits.</p>
</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>

View file

@ -5,7 +5,7 @@
<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>Gtk4 tutorial</title>
<title>GTK 4 tutorial</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
@ -112,19 +112,24 @@
</div>
</nav>
<h1 id="widgets-2">Widgets (2)</h1>
<h2 id="gtktextview-gtktextbuffer-and-gtkscrolledwindow">GtkTextView, GtkTextBuffer and GtkScrolledWindow</h2>
<h3 id="gtktextview-and-gtktextbuffer">GtkTextView and GtkTextBuffer</h3>
<p>GtkTextView is a widget for multi-line text editing. GtkTextBuffer is a text buffer which is connected to GtkTextView. See the sample program <code>tfv1.c</code> below.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb1-1"><a href="#cb1-1"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<h2 id="gtktextview-gtktextbuffer-and-gtkscrolledwindow">GtkTextView,
GtkTextBuffer and GtkScrolledWindow</h2>
<h3 id="gtktextview-and-gtktextbuffer">GtkTextView and
GtkTextBuffer</h3>
<p>GtkTextView is a widget for multi-line text editing. GtkTextBuffer is
a text buffer which is connected to GtkTextView. See the sample program
<code>tfv1.c</code> below.</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb1-1"><a href="#cb1-1"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<span id="cb1-2"><a href="#cb1-2"></a></span>
<span id="cb1-3"><a href="#cb1-3"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb1-4"><a href="#cb1-4"></a>app_activate (GApplication *app, gpointer user_data) {</span>
<span id="cb1-5"><a href="#cb1-5"></a> GtkWidget *win;</span>
<span id="cb1-6"><a href="#cb1-6"></a> GtkWidget *tv;</span>
<span id="cb1-7"><a href="#cb1-7"></a> GtkTextBuffer *tb;</span>
<span id="cb1-8"><a href="#cb1-8"></a> gchar *text;</span>
<span id="cb1-4"><a href="#cb1-4"></a>app_activate <span class="op">(</span>GApplication <span class="op">*</span>app<span class="op">)</span> <span class="op">{</span></span>
<span id="cb1-5"><a href="#cb1-5"></a> GtkWidget <span class="op">*</span>win<span class="op">;</span></span>
<span id="cb1-6"><a href="#cb1-6"></a> GtkWidget <span class="op">*</span>tv<span class="op">;</span></span>
<span id="cb1-7"><a href="#cb1-7"></a> GtkTextBuffer <span class="op">*</span>tb<span class="op">;</span></span>
<span id="cb1-8"><a href="#cb1-8"></a> gchar <span class="op">*</span>text<span class="op">;</span></span>
<span id="cb1-9"><a href="#cb1-9"></a></span>
<span id="cb1-10"><a href="#cb1-10"></a> text =</span>
<span id="cb1-10"><a href="#cb1-10"></a> text <span class="op">=</span></span>
<span id="cb1-11"><a href="#cb1-11"></a> <span class="st">&quot;Once upon a time, there was an old man who was called Taketori-no-Okina. &quot;</span></span>
<span id="cb1-12"><a href="#cb1-12"></a> <span class="st">&quot;It is a japanese word that means a man whose work is making bamboo baskets.</span><span class="sc">\n</span><span class="st">&quot;</span></span>
<span id="cb1-13"><a href="#cb1-13"></a> <span class="st">&quot;One day, he went into a mountain and found a shining bamboo. &quot;</span></span>
@ -132,49 +137,77 @@
<span id="cb1-15"><a href="#cb1-15"></a> <span class="st">&quot;He cut it, then there was a small cute baby girl in it. &quot;</span></span>
<span id="cb1-16"><a href="#cb1-16"></a> <span class="st">&quot;The girl was shining faintly. &quot;</span></span>
<span id="cb1-17"><a href="#cb1-17"></a> <span class="st">&quot;He thought this baby girl is a gift from Heaven and took her home.</span><span class="sc">\n</span><span class="st">&quot;</span></span>
<span id="cb1-18"><a href="#cb1-18"></a> <span class="st">&quot;His wife was surprized at his tale. &quot;</span></span>
<span id="cb1-18"><a href="#cb1-18"></a> <span class="st">&quot;His wife was surprized at his story. &quot;</span></span>
<span id="cb1-19"><a href="#cb1-19"></a> <span class="st">&quot;They were very happy because they had no children. &quot;</span></span>
<span id="cb1-20"><a href="#cb1-20"></a> ;</span>
<span id="cb1-21"><a href="#cb1-21"></a> win = gtk_application_window_new (GTK_APPLICATION (app));</span>
<span id="cb1-22"><a href="#cb1-22"></a> gtk_window_set_title (GTK_WINDOW (win), <span class="st">&quot;Taketori&quot;</span>);</span>
<span id="cb1-23"><a href="#cb1-23"></a> gtk_window_set_default_size (GTK_WINDOW (win), <span class="dv">400</span>, <span class="dv">300</span>);</span>
<span id="cb1-20"><a href="#cb1-20"></a> <span class="op">;</span></span>
<span id="cb1-21"><a href="#cb1-21"></a> win <span class="op">=</span> gtk_application_window_new <span class="op">(</span>GTK_APPLICATION <span class="op">(</span>app<span class="op">));</span></span>
<span id="cb1-22"><a href="#cb1-22"></a> gtk_window_set_title <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> <span class="st">&quot;Taketori&quot;</span><span class="op">);</span></span>
<span id="cb1-23"><a href="#cb1-23"></a> gtk_window_set_default_size <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> <span class="dv">400</span><span class="op">,</span> <span class="dv">300</span><span class="op">);</span></span>
<span id="cb1-24"><a href="#cb1-24"></a></span>
<span id="cb1-25"><a href="#cb1-25"></a> tv = gtk_text_view_new ();</span>
<span id="cb1-26"><a href="#cb1-26"></a> tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));</span>
<span id="cb1-27"><a href="#cb1-27"></a> gtk_text_buffer_set_text (tb, text, -<span class="dv">1</span>);</span>
<span id="cb1-28"><a href="#cb1-28"></a> gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);</span>
<span id="cb1-25"><a href="#cb1-25"></a> tv <span class="op">=</span> gtk_text_view_new <span class="op">();</span></span>
<span id="cb1-26"><a href="#cb1-26"></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="cb1-27"><a href="#cb1-27"></a> gtk_text_buffer_set_text <span class="op">(</span>tb<span class="op">,</span> text<span class="op">,</span> <span class="op">-</span><span class="dv">1</span><span class="op">);</span></span>
<span id="cb1-28"><a href="#cb1-28"></a> gtk_text_view_set_wrap_mode <span class="op">(</span>GTK_TEXT_VIEW <span class="op">(</span>tv<span class="op">),</span> GTK_WRAP_WORD_CHAR<span class="op">);</span></span>
<span id="cb1-29"><a href="#cb1-29"></a></span>
<span id="cb1-30"><a href="#cb1-30"></a> gtk_window_set_child (GTK_WINDOW (win), tv);</span>
<span id="cb1-30"><a href="#cb1-30"></a> gtk_window_set_child <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> tv<span class="op">);</span></span>
<span id="cb1-31"><a href="#cb1-31"></a></span>
<span id="cb1-32"><a href="#cb1-32"></a> gtk_widget_show (win);</span>
<span id="cb1-33"><a href="#cb1-33"></a>}</span>
<span id="cb1-32"><a href="#cb1-32"></a> gtk_widget_show <span class="op">(</span>win<span class="op">);</span></span>
<span id="cb1-33"><a href="#cb1-33"></a><span class="op">}</span></span>
<span id="cb1-34"><a href="#cb1-34"></a></span>
<span id="cb1-35"><a href="#cb1-35"></a><span class="dt">int</span></span>
<span id="cb1-36"><a href="#cb1-36"></a>main (<span class="dt">int</span> argc, <span class="dt">char</span> **argv) {</span>
<span id="cb1-37"><a href="#cb1-37"></a> GtkApplication *app;</span>
<span id="cb1-38"><a href="#cb1-38"></a> <span class="dt">int</span> stat;</span>
<span id="cb1-36"><a href="#cb1-36"></a>main <span class="op">(</span><span class="dt">int</span> argc<span class="op">,</span> <span class="dt">char</span> <span class="op">**</span>argv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb1-37"><a href="#cb1-37"></a> GtkApplication <span class="op">*</span>app<span class="op">;</span></span>
<span id="cb1-38"><a href="#cb1-38"></a> <span class="dt">int</span> stat<span class="op">;</span></span>
<span id="cb1-39"><a href="#cb1-39"></a></span>
<span id="cb1-40"><a href="#cb1-40"></a> app = gtk_application_new (<span class="st">&quot;com.github.ToshioCP.tfv1&quot;</span>, G_APPLICATION_FLAGS_NONE);</span>
<span id="cb1-41"><a href="#cb1-41"></a> g_signal_connect (app, <span class="st">&quot;activate&quot;</span>, G_CALLBACK (app_activate), NULL);</span>
<span id="cb1-42"><a href="#cb1-42"></a> stat = g_application_run (G_APPLICATION (app), argc, argv);</span>
<span id="cb1-43"><a href="#cb1-43"></a> g_object_unref (app);</span>
<span id="cb1-44"><a href="#cb1-44"></a> <span class="cf">return</span> stat;</span>
<span id="cb1-45"><a href="#cb1-45"></a>}</span></code></pre></div>
<p>Look at line 25. A GtkTextView instance is created and its pointer is assigned to <code>tv</code>. When the GtkTextView instance is created, a GtkTextBuffer instance is also created and connected to the GtkTextView automatically. “GtkTextBuffer instance” will be referred to simply as “GtkTextBuffer” or “buffer”. In the next line, the pointer to the buffer is got and assigned to <code>tb</code>. Then, the text from line 10 to 20 is assigned to the buffer.</p>
<p>GtkTextView has a wrap mode. When it is set to <code>GTK_WRAP_WORD_CHAR</code>, text wraps in between words, or if that is not enough, also between graphemes.</p>
<p>In line 30, <code>tv</code> is added to <code>win</code> as a child.</p>
<span id="cb1-40"><a href="#cb1-40"></a> app <span class="op">=</span> gtk_application_new <span class="op">(</span><span class="st">&quot;com.github.ToshioCP.tfv1&quot;</span><span class="op">,</span> G_APPLICATION_DEFAULT_FLAGS<span class="op">);</span></span>
<span id="cb1-41"><a href="#cb1-41"></a> g_signal_connect <span class="op">(</span>app<span class="op">,</span> <span class="st">&quot;activate&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>app_activate<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb1-42"><a href="#cb1-42"></a> stat <span class="op">=</span> g_application_run <span class="op">(</span>G_APPLICATION <span class="op">(</span>app<span class="op">),</span> argc<span class="op">,</span> argv<span class="op">);</span></span>
<span id="cb1-43"><a href="#cb1-43"></a> g_object_unref <span class="op">(</span>app<span class="op">);</span></span>
<span id="cb1-44"><a href="#cb1-44"></a> <span class="cf">return</span> stat<span class="op">;</span></span>
<span id="cb1-45"><a href="#cb1-45"></a><span class="op">}</span></span></code></pre></div>
<p>Look at line 25. A GtkTextView instance is created and its pointer is
assigned to <code>tv</code>. When the GtkTextView instance is created, a
GtkTextBuffer instance is also created and connected to the GtkTextView
automatically. “GtkTextBuffer instance” will be referred to simply as
“GtkTextBuffer” or “buffer”. In the next line, the pointer to the buffer
is assigned to <code>tb</code>. Then, the text from line 10 to 20 is
assigned to the buffer. If the third argument of
<code>gtk_text_buffer_set_text</code> is a positive integer, it is the
length of the text. It it is -1, the string terminates with NULL.</p>
<p>GtkTextView has a wrap mode. When it is set to
<code>GTK_WRAP_WORD_CHAR</code>, text wraps in between words, or if that
is not enough, also between graphemes.</p>
<p>Wrap mode is written in <a
href="https://docs.gtk.org/gtk4/enum.WrapMode.html">Gtk_WrapMode</a> in
the GTK 4 API document.</p>
<p>In line 30, <code>tv</code> is added to <code>win</code> as a
child.</p>
<p>Now compile and run it.</p>
<pre><code>$ cd src/tfv
$ comp tfv1
$ ./a.out</code></pre>
<figure>
<img src="image/screenshot_tfv1.png" alt="" /><figcaption>GtkTextView</figcaption>
<img src="image/screenshot_tfv1.png" alt="GtkTextView" />
<figcaption aria-hidden="true">GtkTextView</figcaption>
</figure>
<p>Theres an I-beam pointer in the window. You can add or delete any characters on the GtkTextView, and your changes are kept in the GtkTextBuffer. If you add more characters beyond the limit of the window, the height increases and the window extends. If the height gets bigger than the height of the display screen, you wont be able to control the size of the window, and change it back to the original size. This is a problem and shows that there is a bug in our program. This can solve it by adding a GtkScrolledWindow between the GtkApplicationWindow and GtkTextView.</p>
<p>Theres an I-beam pointer in the window. You can add or delete any
characters on the GtkTextView, and your changes are kept in the
GtkTextBuffer. If you add more characters beyond the limit of the
window, the height increases and the window extends. If the height gets
bigger than the height of the display screen, you wont be able to
control the size of the window or change it back to the original size.
This is a problem, that is to say a bug. This can be solved by adding a
GtkScrolledWindow between the GtkApplicationWindow and GtkTextView.</p>
<h3 id="gtkscrolledwindow">GtkScrolledWindow</h3>
<p>What we need to do is:</p>
<ul>
<li>Create a GtkScrolledWindow and insert it as a child of the GtkApplicationWindow; and</li>
<li>Insert the GtkTextView widget to the GtkScrolledWindow as a child.</li>
<li>Create a GtkScrolledWindow and insert it as a child of the
GtkApplicationWindow</li>
<li>Insert the GtkTextView widget to the GtkScrolledWindow as a
child.</li>
</ul>
<p>Modify <code>tfv1.c</code> and save it as <code>tfv2.c</code>. The difference between these two files is small.</p>
<p>Modify <code>tfv1.c</code> and save it as <code>tfv2.c</code>. There
is only a few difference between these two files.</p>
<pre><code>$ cd tfv; diff tfv1.c tfv2.c
5a6
&gt; GtkWidget *scr;
@ -187,60 +220,63 @@
---
&gt; gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
40c44
&lt; app = gtk_application_new (&quot;com.github.ToshioCP.tfv1&quot;, G_APPLICATION_FLAGS_NONE);
&lt; app = gtk_application_new (&quot;com.github.ToshioCP.tfv1&quot;, G_APPLICATION_DEFAULT_FLAGS);
---
&gt; app = gtk_application_new (&quot;com.github.ToshioCP.tfv2&quot;, G_APPLICATION_FLAGS_NONE);</code></pre>
<p>Here is the complete code of <code>tfv2.c</code>.</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb3-1"><a href="#cb3-1"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<span id="cb3-2"><a href="#cb3-2"></a></span>
<span id="cb3-3"><a href="#cb3-3"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb3-4"><a href="#cb3-4"></a>app_activate (GApplication *app, gpointer user_data) {</span>
<span id="cb3-5"><a href="#cb3-5"></a> GtkWidget *win;</span>
<span id="cb3-6"><a href="#cb3-6"></a> GtkWidget *scr;</span>
<span id="cb3-7"><a href="#cb3-7"></a> GtkWidget *tv;</span>
<span id="cb3-8"><a href="#cb3-8"></a> GtkTextBuffer *tb;</span>
<span id="cb3-9"><a href="#cb3-9"></a> gchar *text;</span>
<span id="cb3-10"><a href="#cb3-10"></a></span>
<span id="cb3-11"><a href="#cb3-11"></a> text =</span>
<span id="cb3-12"><a href="#cb3-12"></a> <span class="st">&quot;Once upon a time, there was an old man who was called Taketori-no-Okina. &quot;</span></span>
<span id="cb3-13"><a href="#cb3-13"></a> <span class="st">&quot;It is a japanese word that means a man whose work is making bamboo baskets.</span><span class="sc">\n</span><span class="st">&quot;</span></span>
<span id="cb3-14"><a href="#cb3-14"></a> <span class="st">&quot;One day, he went into a mountain and found a shining bamboo. &quot;</span></span>
<span id="cb3-15"><a href="#cb3-15"></a> <span class="st">&quot;</span><span class="sc">\&quot;</span><span class="st">What a mysterious bamboo it is!,</span><span class="sc">\&quot;</span><span class="st"> he said. &quot;</span></span>
<span id="cb3-16"><a href="#cb3-16"></a> <span class="st">&quot;He cut it, then there was a small cute baby girl in it. &quot;</span></span>
<span id="cb3-17"><a href="#cb3-17"></a> <span class="st">&quot;The girl was shining faintly. &quot;</span></span>
<span id="cb3-18"><a href="#cb3-18"></a> <span class="st">&quot;He thought this baby girl is a gift from Heaven and took her home.</span><span class="sc">\n</span><span class="st">&quot;</span></span>
<span id="cb3-19"><a href="#cb3-19"></a> <span class="st">&quot;His wife was surprized at his tale. &quot;</span></span>
<span id="cb3-20"><a href="#cb3-20"></a> <span class="st">&quot;They were very happy because they had no children. &quot;</span></span>
<span id="cb3-21"><a href="#cb3-21"></a> ;</span>
<span id="cb3-22"><a href="#cb3-22"></a> win = gtk_application_window_new (GTK_APPLICATION (app));</span>
<span id="cb3-23"><a href="#cb3-23"></a> gtk_window_set_title (GTK_WINDOW (win), <span class="st">&quot;Taketori&quot;</span>);</span>
<span id="cb3-24"><a href="#cb3-24"></a> gtk_window_set_default_size (GTK_WINDOW (win), <span class="dv">400</span>, <span class="dv">300</span>);</span>
<span id="cb3-25"><a href="#cb3-25"></a></span>
<span id="cb3-26"><a href="#cb3-26"></a> scr = gtk_scrolled_window_new ();</span>
<span id="cb3-27"><a href="#cb3-27"></a> gtk_window_set_child (GTK_WINDOW (win), scr);</span>
<span id="cb3-28"><a href="#cb3-28"></a></span>
<span id="cb3-29"><a href="#cb3-29"></a> tv = gtk_text_view_new ();</span>
<span id="cb3-30"><a href="#cb3-30"></a> tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));</span>
<span id="cb3-31"><a href="#cb3-31"></a> gtk_text_buffer_set_text (tb, text, -<span class="dv">1</span>);</span>
<span id="cb3-32"><a href="#cb3-32"></a> gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);</span>
<span id="cb3-33"><a href="#cb3-33"></a></span>
<span id="cb3-34"><a href="#cb3-34"></a> gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);</span>
<span id="cb3-35"><a href="#cb3-35"></a></span>
<span id="cb3-36"><a href="#cb3-36"></a> gtk_widget_show (win);</span>
<span id="cb3-37"><a href="#cb3-37"></a>}</span>
<span id="cb3-38"><a href="#cb3-38"></a></span>
<span id="cb3-39"><a href="#cb3-39"></a><span class="dt">int</span></span>
<span id="cb3-40"><a href="#cb3-40"></a>main (<span class="dt">int</span> argc, <span class="dt">char</span> **argv) {</span>
<span id="cb3-41"><a href="#cb3-41"></a> GtkApplication *app;</span>
<span id="cb3-42"><a href="#cb3-42"></a> <span class="dt">int</span> stat;</span>
<span id="cb3-43"><a href="#cb3-43"></a></span>
<span id="cb3-44"><a href="#cb3-44"></a> app = gtk_application_new (<span class="st">&quot;com.github.ToshioCP.tfv2&quot;</span>, G_APPLICATION_FLAGS_NONE);</span>
<span id="cb3-45"><a href="#cb3-45"></a> g_signal_connect (app, <span class="st">&quot;activate&quot;</span>, G_CALLBACK (app_activate), NULL);</span>
<span id="cb3-46"><a href="#cb3-46"></a> stat = g_application_run (G_APPLICATION (app), argc, argv);</span>
<span id="cb3-47"><a href="#cb3-47"></a> g_object_unref (app);</span>
<span id="cb3-48"><a href="#cb3-48"></a> <span class="cf">return</span> stat;</span>
<span id="cb3-49"><a href="#cb3-49"></a>}</span></code></pre></div>
<p>Compile and run it. Notice how this time the window doesnt extend when you type a lot of characters, it just scrolls and displays a slider.</p>
&gt; app = gtk_application_new (&quot;com.github.ToshioCP.tfv2&quot;, G_APPLICATION_DEFAULT_FLAGS);</code></pre>
<p>The whole code of <code>tfv2.c</code> is as follows.</p>
<div class="sourceCode" id="cb4"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb4-1"><a href="#cb4-1"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<span id="cb4-2"><a href="#cb4-2"></a></span>
<span id="cb4-3"><a href="#cb4-3"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb4-4"><a href="#cb4-4"></a>app_activate <span class="op">(</span>GApplication <span class="op">*</span>app<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-5"><a href="#cb4-5"></a> GtkWidget <span class="op">*</span>win<span class="op">;</span></span>
<span id="cb4-6"><a href="#cb4-6"></a> GtkWidget <span class="op">*</span>scr<span class="op">;</span></span>
<span id="cb4-7"><a href="#cb4-7"></a> GtkWidget <span class="op">*</span>tv<span class="op">;</span></span>
<span id="cb4-8"><a href="#cb4-8"></a> GtkTextBuffer <span class="op">*</span>tb<span class="op">;</span></span>
<span id="cb4-9"><a href="#cb4-9"></a> gchar <span class="op">*</span>text<span class="op">;</span></span>
<span id="cb4-10"><a href="#cb4-10"></a></span>
<span id="cb4-11"><a href="#cb4-11"></a> text <span class="op">=</span></span>
<span id="cb4-12"><a href="#cb4-12"></a> <span class="st">&quot;Once upon a time, there was an old man who was called Taketori-no-Okina. &quot;</span></span>
<span id="cb4-13"><a href="#cb4-13"></a> <span class="st">&quot;It is a japanese word that means a man whose work is making bamboo baskets.</span><span class="sc">\n</span><span class="st">&quot;</span></span>
<span id="cb4-14"><a href="#cb4-14"></a> <span class="st">&quot;One day, he went into a mountain and found a shining bamboo. &quot;</span></span>
<span id="cb4-15"><a href="#cb4-15"></a> <span class="st">&quot;</span><span class="sc">\&quot;</span><span class="st">What a mysterious bamboo it is!,</span><span class="sc">\&quot;</span><span class="st"> he said. &quot;</span></span>
<span id="cb4-16"><a href="#cb4-16"></a> <span class="st">&quot;He cut it, then there was a small cute baby girl in it. &quot;</span></span>
<span id="cb4-17"><a href="#cb4-17"></a> <span class="st">&quot;The girl was shining faintly. &quot;</span></span>
<span id="cb4-18"><a href="#cb4-18"></a> <span class="st">&quot;He thought this baby girl is a gift from Heaven and took her home.</span><span class="sc">\n</span><span class="st">&quot;</span></span>
<span id="cb4-19"><a href="#cb4-19"></a> <span class="st">&quot;His wife was surprized at his story. &quot;</span></span>
<span id="cb4-20"><a href="#cb4-20"></a> <span class="st">&quot;They were very happy because they had no children. &quot;</span></span>
<span id="cb4-21"><a href="#cb4-21"></a> <span class="op">;</span></span>
<span id="cb4-22"><a href="#cb4-22"></a> win <span class="op">=</span> gtk_application_window_new <span class="op">(</span>GTK_APPLICATION <span class="op">(</span>app<span class="op">));</span></span>
<span id="cb4-23"><a href="#cb4-23"></a> gtk_window_set_title <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> <span class="st">&quot;Taketori&quot;</span><span class="op">);</span></span>
<span id="cb4-24"><a href="#cb4-24"></a> gtk_window_set_default_size <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> <span class="dv">400</span><span class="op">,</span> <span class="dv">300</span><span class="op">);</span></span>
<span id="cb4-25"><a href="#cb4-25"></a></span>
<span id="cb4-26"><a href="#cb4-26"></a> scr <span class="op">=</span> gtk_scrolled_window_new <span class="op">();</span></span>
<span id="cb4-27"><a href="#cb4-27"></a> gtk_window_set_child <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> scr<span class="op">);</span></span>
<span id="cb4-28"><a href="#cb4-28"></a></span>
<span id="cb4-29"><a href="#cb4-29"></a> tv <span class="op">=</span> gtk_text_view_new <span class="op">();</span></span>
<span id="cb4-30"><a href="#cb4-30"></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="cb4-31"><a href="#cb4-31"></a> gtk_text_buffer_set_text <span class="op">(</span>tb<span class="op">,</span> text<span class="op">,</span> <span class="op">-</span><span class="dv">1</span><span class="op">);</span></span>
<span id="cb4-32"><a href="#cb4-32"></a> gtk_text_view_set_wrap_mode <span class="op">(</span>GTK_TEXT_VIEW <span class="op">(</span>tv<span class="op">),</span> GTK_WRAP_WORD_CHAR<span class="op">);</span></span>
<span id="cb4-33"><a href="#cb4-33"></a></span>
<span id="cb4-34"><a href="#cb4-34"></a> gtk_scrolled_window_set_child <span class="op">(</span>GTK_SCROLLED_WINDOW <span class="op">(</span>scr<span class="op">),</span> tv<span class="op">);</span></span>
<span id="cb4-35"><a href="#cb4-35"></a></span>
<span id="cb4-36"><a href="#cb4-36"></a> gtk_widget_show <span class="op">(</span>win<span class="op">);</span></span>
<span id="cb4-37"><a href="#cb4-37"></a><span class="op">}</span></span>
<span id="cb4-38"><a href="#cb4-38"></a></span>
<span id="cb4-39"><a href="#cb4-39"></a><span class="dt">int</span></span>
<span id="cb4-40"><a href="#cb4-40"></a>main <span class="op">(</span><span class="dt">int</span> argc<span class="op">,</span> <span class="dt">char</span> <span class="op">**</span>argv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-41"><a href="#cb4-41"></a> GtkApplication <span class="op">*</span>app<span class="op">;</span></span>
<span id="cb4-42"><a href="#cb4-42"></a> <span class="dt">int</span> stat<span class="op">;</span></span>
<span id="cb4-43"><a href="#cb4-43"></a></span>
<span id="cb4-44"><a href="#cb4-44"></a> app <span class="op">=</span> gtk_application_new <span class="op">(</span><span class="st">&quot;com.github.ToshioCP.tfv2&quot;</span><span class="op">,</span> G_APPLICATION_DEFAULT_FLAGS<span class="op">);</span></span>
<span id="cb4-45"><a href="#cb4-45"></a> g_signal_connect <span class="op">(</span>app<span class="op">,</span> <span class="st">&quot;activate&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>app_activate<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb4-46"><a href="#cb4-46"></a> stat <span class="op">=</span> g_application_run <span class="op">(</span>G_APPLICATION <span class="op">(</span>app<span class="op">),</span> argc<span class="op">,</span> argv<span class="op">);</span></span>
<span id="cb4-47"><a href="#cb4-47"></a> g_object_unref <span class="op">(</span>app<span class="op">);</span></span>
<span id="cb4-48"><a href="#cb4-48"></a> <span class="cf">return</span> stat<span class="op">;</span></span>
<span id="cb4-49"><a href="#cb4-49"></a><span class="op">}</span></span></code></pre></div>
<p>Compile and run it.</p>
<p>Now, the window doesnt extend even if you type a lot of characters,
it just scrolls.</p>
</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>

View file

@ -111,18 +111,17 @@
</div>
</div>
</nav>
<h1 id="string-and-memory-management">String and memory management</h1>
<h1 id="strings-and-memory-management">Strings and memory
management</h1>
<p>GtkTextView and GtkTextBuffer have functions that use string
parameters or return a string. The knowledge of strings and memory
management is useful to understand how to use these functions.</p>
<h2 id="string-and-memory">String and memory</h2>
<p>A String is an array of characters that is terminated with \0.
Strings are not a C type such as char, int, float or double, but exist
as a pointer to a character array. They behaves like a string type which
you may be familiar from other languages. So, this pointer is often
called a string.</p>
<p>In the following, <code>a</code> and <code>b</code> defined as
character arrays, and are strings.</p>
String is not a C type such as char, int, float or double, but a
character array. It behaves like a string in other languages. So, the
pointer is often called a string.</p>
<p>The following is a sample program.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="dt">char</span> a<span class="op">[</span><span class="dv">10</span><span class="op">],</span> <span class="op">*</span>b<span class="op">;</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>a<span class="op">[</span><span class="dv">0</span><span class="op">]</span> <span class="op">=</span> <span class="ch">&#39;H&#39;</span><span class="op">;</span></span>
@ -135,97 +134,101 @@ character arrays, and are strings.</p>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a>b <span class="op">=</span> a<span class="op">;</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a><span class="co">/* *b is &#39;H&#39; */</span></span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a><span class="co">/* *(++b) is &#39;e&#39; */</span></span></code></pre></div>
<p>The array <code>a</code> has <code>char</code> elements and the size
of ten. The first six elements are H, e, l, l, o and \0.
This array represents the string “Hello”. The first five elements are
character codes that correspond to the characters. The sixth element is
\0, which is the same as zero, and indicates that the string ends
there. The size of the array is 10, so 4 bytes arent used, but thats
OK, they are just ignored.</p>
<p>The variable b is a pointer to a character. Because <code>b</code>
is assigned to be <code>a</code>, <code>a</code> and <code>b</code>
point the same character (H). The variable <code>a</code> is defined
as an array and it cant be changed. It always point the top address of
the array. On the other hand, b is a pointer, which is mutable, so
<code>b</code> can be change. It is then possible to write statements
like <code>++b</code>, which means take the value in b (n address),
increase it by one, and store that back in <code>b</code>.</p>
<p>If a pointer is NULL, it points to nothing. So, the pointer is not a
string. A NULL string on the other hand will be a pointer which points
to a location that contains <code>\0</code>, which is a string of length
0 (or ““). Programs that use strings will include bugs if you arent
careful when using NULL pointers.</p>
<p>Another annoying problem is the memory that a string is allocated.
There are four cases:</p>
<p>An array <code>a</code> is defined as a <code>char</code> type array
and its size is ten. The first five elements are H, e, l, l,
o. They are character codes. For example, H is the same as 0x48 or
72. The sixth element is \0, which is the same as zero, and indicates
that the sequence of the data ends there. The array represents the
string “Hello”.</p>
<p>The size of the array is 10, so 4 bytes arent used. But its OK.
They are just ignored. (If a is defined out of functions or its class
is static, they are assigned with zero. Otherwise, that is to say, the
class is auto or register, they are undefined.)</p>
<p>The variable b is a pointer to a character. It is assigned with
<code>a</code>, so <code>b</code> points the first element of
<code>a</code> (character H). The array <code>a</code> is immutable.
So <code>a=a+1</code> causes syntax error.</p>
<p>On the other hand, b is a pointer type variable, which is mutable.
So, <code>++b</code>, which increases <code>b</code> by one, is
allowed.</p>
<p>If a pointer is NULL, it points nothing. So, the pointer is not a
string. It is different from empty string. Empty string is a pointer
points <code>\0</code>.</p>
<p>There are four cases:</p>
<ul>
<li>The string is read only;</li>
<li>The string is in static memory area;</li>
<li>The string is in stack; and</li>
<li>The string is in memory allocated from the heap area.</li>
<li>The string is read only</li>
<li>The string is in static memory area</li>
<li>The string is in stack</li>
<li>The string is in memory allocated from the heap area</li>
</ul>
<h2 id="read-only-string">Read only string</h2>
<p>A string literal in a C program is surrounded by double quotes and
written as the following:</p>
<p>A string literal is surrounded by double quotes like this:</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><span class="dt">char</span> <span class="op">*</span>s<span class="op">;</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>s <span class="op">=</span> <span class="st">&quot;Hello&quot;</span></span></code></pre></div>
<p>“Hello” is a string literal, and is stored in program memory. A
string literal is read only. In the program above, <code>s</code> points
the string literal.</p>
<p>So, the following program is illegal.</p>
<p>“Hello” is a string literal, and is read only. So, the following
program is illegal.</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="op">*(</span>s<span class="op">+</span><span class="dv">1</span><span class="op">)</span> <span class="op">=</span> <span class="ch">&#39;a&#39;</span><span class="op">;</span></span></code></pre></div>
<p>The result is undefined. Probably a bad thing will happen, for
example, a segmentation fault.</p>
<p>NOTE: The memory of the literal string is allocated when the program
is compiled. It is possible to view all the literal strings defined in
your program by using the <code>string</code> command.</p>
is compiled. It is possible to see the literal strings with
<code>strings</code> command.</p>
<pre><code>$ strings src/tvf/a.out
/lib64/ld-linux-x86-64.so.2
cN&lt;5
... ... ...
... ... ...
Once upon a time, there was an old man who was called Taketori-no-Okina. It is a japanese word that means a man whose work is making bamboo baskets.
One day, he went into a mountain and found a shining bamboo. &quot;What a mysterious bamboo it is!,&quot; he said. He cut it, then there was a small cute baby girl in it. The girl was shining faintly. He thought this baby girl is a gift from Heaven and took her home.
His wife was surprized at his story. They were very happy because they had no children.
... ... ...
... ... ...</code></pre>
<p>It tells us that literal strings are embedded in program binary
codes.</p>
<h2 id="strings-defined-as-arrays">Strings defined as arrays</h2>
<p>If a string is defined as an array, its in either stored in the
static memory area or stack. This depends on the class of the array. If
the arrays class is <code>static</code>, then its placed in static
memory area. This allocation and memory address is fixed for the life of
the program. This area can be changed and is writable.</p>
<p>If the arrays class is <code>auto</code>, then its placed in stack.
If the array is defined inside a function, its default class is
<code>auto</code>. The stack area will disappear when the function exits
and returns to the caller. Arrays defined on the stack are writable.</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="dt">static</span> <span class="dt">char</span> a<span class="op">[]</span> <span class="op">=</span> <span class="op">{</span><span class="ch">&#39;H&#39;</span><span class="op">,</span> <span class="ch">&#39;e&#39;</span><span class="op">,</span> <span class="ch">&#39;l&#39;</span><span class="op">,</span> <span class="ch">&#39;l&#39;</span><span class="op">,</span> <span class="ch">&#39;o&#39;</span><span class="op">,</span> <span class="ch">&#39;\0&#39;</span><span class="op">};</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>print_strings <span class="op">(</span><span class="dt">void</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a> <span class="dt">char</span> b<span class="op">[]</span> <span class="op">=</span> <span class="st">&quot;Hello&quot;</span><span class="op">;</span></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a> a<span class="op">[</span><span class="dv">1</span><span class="op">]</span> <span class="op">=</span> <span class="ch">&#39;a&#39;</span><span class="op">;</span> <span class="co">/* Because the array is static, it&#39;s writable. */</span></span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a> b<span class="op">[</span><span class="dv">1</span><span class="op">]</span> <span class="op">=</span> <span class="ch">&#39;a&#39;</span><span class="op">;</span> <span class="co">/* Because the array is auto, it&#39;s writable. */</span></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a> printf <span class="op">(</span><span class="st">&quot;%s</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">,</span> a<span class="op">);</span> <span class="co">/* Hallo */</span></span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a> printf <span class="op">(</span><span class="st">&quot;%s</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">,</span> b<span class="op">);</span> <span class="co">/* Hallo */</span></span>
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>The array <code>a</code> is defined externally to a function and is
global in its scope. Such variables are placed in static memory area
even if the <code>static</code> class is left out. The compiler
calculates the number of the elements in the right hand side (six), and
then creates code that allocates six bytes in the static memory area and
copies the data to this memory.</p>
<p>The array <code>b</code> is defined inside the function so its class
<p>If a string is defined as an array, its stored in static memory area
or stack. It depends on the class of the array. If the arrays class is
<code>static</code>, then its placed in static memory area. The
allocated memory lives for the life of the program. This area is
writable.</p>
<p>If the arrays class is <code>auto</code>, its placed in stack. If
the array is defined inside a function, its default class is
<code>auto</code>. The stack area will disappear when the function
returns to the caller. Arrays defined on the stack are writable.</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><span class="dt">static</span> <span class="dt">char</span> a<span class="op">[]</span> <span class="op">=</span> <span class="op">{</span><span class="ch">&#39;H&#39;</span><span class="op">,</span> <span class="ch">&#39;e&#39;</span><span class="op">,</span> <span class="ch">&#39;l&#39;</span><span class="op">,</span> <span class="ch">&#39;l&#39;</span><span class="op">,</span> <span class="ch">&#39;o&#39;</span><span class="op">,</span> <span class="ch">&#39;\0&#39;</span><span class="op">};</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a>print_strings <span class="op">(</span><span class="dt">void</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a> <span class="dt">char</span> b<span class="op">[]</span> <span class="op">=</span> <span class="st">&quot;Hello&quot;</span><span class="op">;</span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a> a<span class="op">[</span><span class="dv">1</span><span class="op">]</span> <span class="op">=</span> <span class="ch">&#39;a&#39;</span><span class="op">;</span> <span class="co">/* Because the array is static, it&#39;s writable. */</span></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a> b<span class="op">[</span><span class="dv">1</span><span class="op">]</span> <span class="op">=</span> <span class="ch">&#39;a&#39;</span><span class="op">;</span> <span class="co">/* Because the array is auto, it&#39;s writable. */</span></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a> printf <span class="op">(</span><span class="st">&quot;%s</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">,</span> a<span class="op">);</span> <span class="co">/* Hallo */</span></span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a> printf <span class="op">(</span><span class="st">&quot;%s</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">,</span> b<span class="op">);</span> <span class="co">/* Hallo */</span></span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>The array <code>a</code> is defined out of functions. It is placed in
the static memory area even if the <code>static</code> class is left
out. The compiler calculates the number of the elements (six) and
allocates six bytes in the static memory area. Then, it copies “Hello”
literal string data to the memory.</p>
<p>The array <code>b</code> is defined inside the function, so its class
is <code>auto</code>. The compiler calculates the number of the elements
in the string literal. It has six elements as the zero termination
character is also included. The compiler creates code which allocates
six bytes memory in the stack and copies the data to the memory.</p>
in the string literal. It is six because it has \0 terminator. The
compiler allocates six bytes in the stack and copies “Hello” litaral
string to the stack memory.</p>
<p>Both <code>a</code> and <code>b</code> are writable.</p>
<p>The memory is managed by the executable program. You dont need your
program to allocate or free the memory for <code>a</code> and
<code>b</code>. The array <code>a</code> is created then the program is
first run and remains for the life of the program. The array
<code>b</code> is created on the stack then the function is called,
disappears when the function returns.</p>
<p>The memory is allocated and freed by the program automatically so you
dont need to allocate or free. The array <code>a</code> is alive during
the programs life time. The array <code>b</code> is alive when the
function is called until the function returns to the caller.</p>
<h2 id="strings-in-the-heap-area">Strings in the heap area</h2>
<p>You can also get, use and release memory from the heap area. The
standard C library provides <code>malloc</code> to get memory and
<p>You can get, use and release memory from the heap area. The standard
C library provides <code>malloc</code> to get memory and
<code>free</code> to put back memory. GLib provides the functions
<code>g_new</code> and <code>g_free</code> to do the same thing, with
support for some additional GLib functionality.</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>g_new <span class="op">(</span>struct_type<span class="op">,</span> n_struct<span class="op">)</span></span></code></pre></div>
<code>g_new</code> and <code>g_free</code>. They are similar to
<code>malloc</code> and <code>free</code>.</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>g_new <span class="op">(</span>struct_type<span class="op">,</span> n_struct<span class="op">)</span></span></code></pre></div>
<p><code>g_new</code> is a macro to allocate memory for an array.</p>
<ul>
<li><code>struct_type</code> is the type of the element of the
@ -235,37 +238,37 @@ array.</li>
<code>struct_type</code>.</li>
</ul>
<p>For example,</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">char</span> <span class="op">*</span>s<span class="op">;</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a>s <span class="op">=</span> g_new <span class="op">(</span><span class="dt">char</span><span class="op">,</span> <span class="dv">10</span><span class="op">);</span></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a><span class="co">/* s points an array of char. The size of the array is 10. */</span></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a><span class="kw">struct</span> tuple <span class="op">{</span><span class="dt">int</span> x<span class="op">,</span> y<span class="op">;}</span> <span class="op">*</span>t<span class="op">;</span></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a>t <span class="op">=</span> g_new <span class="op">(</span><span class="kw">struct</span> tuple<span class="op">,</span> <span class="dv">5</span><span class="op">);</span></span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a><span class="co">/* t points an array of struct tuple. */</span></span>
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a><span class="co">/* The size of the array is 5. */</span></span></code></pre></div>
<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="dt">char</span> <span class="op">*</span>s<span class="op">;</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a>s <span class="op">=</span> g_new <span class="op">(</span><span class="dt">char</span><span class="op">,</span> <span class="dv">10</span><span class="op">);</span></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a><span class="co">/* s points an array of char. The size of the array is 10. */</span></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a><span class="kw">struct</span> tuple <span class="op">{</span><span class="dt">int</span> x<span class="op">,</span> y<span class="op">;}</span> <span class="op">*</span>t<span class="op">;</span></span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a>t <span class="op">=</span> g_new <span class="op">(</span><span class="kw">struct</span> tuple<span class="op">,</span> <span class="dv">5</span><span class="op">);</span></span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a><span class="co">/* t points an array of struct tuple. */</span></span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a><span class="co">/* The size of the array is 5. */</span></span></code></pre></div>
<p><code>g_free</code> frees memory.</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="dt">void</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a>g_free <span class="op">(</span>gpointer mem<span class="op">);</span></span></code></pre></div>
<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="dt">void</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a>g_free <span class="op">(</span>gpointer mem<span class="op">);</span></span></code></pre></div>
<p>If <code>mem</code> is NULL, <code>g_free</code> does nothing.
<code>gpointer</code> is a type of general pointer. It is the same as
<code>void *</code>. This pointer can be casted to any pointer type.
Conversely, any pointer type can be casted to <code>gpointer</code>.</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>g_free <span class="op">(</span>s<span class="op">);</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a><span class="co">/* Frees the memory allocated to s. */</span></span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a>g_free <span class="op">(</span>t<span class="op">);</span></span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a><span class="co">/* Frees the memory allocated to t. */</span></span></code></pre></div>
<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>g_free <span class="op">(</span>s<span class="op">);</span></span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a><span class="co">/* Frees the memory allocated to s. */</span></span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a>g_free <span class="op">(</span>t<span class="op">);</span></span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a><span class="co">/* Frees the memory allocated to t. */</span></span></code></pre></div>
<p>If the argument doesnt point allocated memory it will cause an
error, specifically, a segmentation fault.</p>
<p>Some GLib functions allocate memory. For example,
<code>g_strdup</code> allocates memory and copies a string given as an
argument.</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="dt">char</span> <span class="op">*</span>s<span class="op">;</span></span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a>s <span class="op">=</span> g_strdup <span class="op">(</span><span class="st">&quot;Hello&quot;</span><span class="op">);</span></span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a>g_free <span class="op">(</span>s<span class="op">);</span></span></code></pre></div>
<div class="sourceCode" id="cb10"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="dt">char</span> <span class="op">*</span>s<span class="op">;</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a>s <span class="op">=</span> g_strdup <span class="op">(</span><span class="st">&quot;Hello&quot;</span><span class="op">);</span></span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a>g_free <span class="op">(</span>s<span class="op">);</span></span></code></pre></div>
<p>The string literal “Hello” has 6 bytes because the string has \0 at
the end it. <code>g_strdup</code> gets 6 bytes from the heap area and
copies the string to the memory. <code>s</code> is assigned the top
the end. <code>g_strdup</code> gets 6 bytes from the heap area and
copies the string to the memory. <code>s</code> is assigned the start
address of the memory. <code>g_free</code> returns the memory to the
heap area.</p>
<p><code>g_strdup</code> is described in <a
@ -275,26 +278,31 @@ Reference</a>. The following is extracted from the reference.</p>
<p>The returned string should be freed with <code>g_free()</code> when
no longer needed.</p>
</blockquote>
<p>The function reference will describe if the returned value needs to
be freed. If you forget to free the allocated memory it will remain
allocated. Repeated use will cause more memory to be allocated to the
program, which will grow over time. This is called a memory leak, and
the only way to address this bug is to close the program (and restart
it), which will automatically release all of the programs memory back to
the system.</p>
<p>Some GLib functions return a string which mustnt be freed by the
caller.</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="dt">const</span> <span class="dt">char</span> <span class="op">*</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a>g_quark_to_string <span class="op">(</span>GQuark quark<span class="op">);</span></span></code></pre></div>
<p>This function returns <code>const char*</code> type. The qualifier
<code>const</code> means that the returned value is immutable. The
characters pointed by the returned value arent be allowed to be changed
or freed.</p>
<p>If a variable is qualified with <code>const</code>, the variable
cant be assigned except during initialization.</p>
<p>If you forget to free the allocated memory it will remain until the
program ends. Repeated allocation and no freeing cause memory leak. It
is a bug and may bring a serious problem.</p>
<h2 id="const-qualifier">const qualifier</h2>
<p>A <code>const</code> qualified variable can be assigned to initialize
it. Once it is initialized, it is never allowed to change or free.</p>
<div class="sourceCode" id="cb11"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="dt">const</span> <span class="dt">int</span> x <span class="op">=</span> <span class="dv">10</span><span class="op">;</span> <span class="co">/* initialization is OK. */</span></span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a>x <span class="op">=</span> <span class="dv">20</span><span class="op">;</span> <span class="co">/* This is illegal because x is qualified with const */</span></span></code></pre></div>
<p>If a function returns <code>const char*</code> type, the string cant
be changed or freed. If a function has a <code>const char *</code> type
parameter, it ensures that the parameter is not changed in the
function.</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="co">// You never change or free the returned string.</span></span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a><span class="dt">const</span> <span class="dt">char</span><span class="op">*</span></span>
<span id="cb12-3"><a href="#cb12-3" aria-hidden="true" tabindex="-1"></a>gtk_label_get_text <span class="op">(</span></span>
<span id="cb12-4"><a href="#cb12-4" aria-hidden="true" tabindex="-1"></a> GtkLabel<span class="op">*</span> self</span>
<span id="cb12-5"><a href="#cb12-5" aria-hidden="true" tabindex="-1"></a><span class="op">)</span></span>
<span id="cb12-6"><a href="#cb12-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-7"><a href="#cb12-7" aria-hidden="true" tabindex="-1"></a><span class="co">// Str keeps itself during the function runs</span></span>
<span id="cb12-8"><a href="#cb12-8" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span></span>
<span id="cb12-9"><a href="#cb12-9" aria-hidden="true" tabindex="-1"></a>gtk_label_set_text <span class="op">(</span></span>
<span id="cb12-10"><a href="#cb12-10" aria-hidden="true" tabindex="-1"></a> GtkLabel<span class="op">*</span> self<span class="op">,</span></span>
<span id="cb12-11"><a href="#cb12-11" aria-hidden="true" tabindex="-1"></a> <span class="dt">const</span> <span class="dt">char</span><span class="op">*</span> str</span>
<span id="cb12-12"><a href="#cb12-12" aria-hidden="true" tabindex="-1"></a><span class="op">)</span></span></code></pre></div>
</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>

View file

@ -5,7 +5,7 @@
<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>Gtk4 tutorial</title>
<title>GTK 4 tutorial</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
@ -113,252 +113,354 @@
</nav>
<h1 id="widgets-3">Widgets (3)</h1>
<h2 id="open-signal">Open signal</h2>
<h3 id="g_application_handles_open-flag">G_APPLICATION_HANDLES_OPEN flag</h3>
<p>The GtkTextView, GtkTextBuffer and GtkScrolledWindow widgets have given us a minimum editor in the previous section. We will now add a function to read a file and rework the program into a file viewer. There are many ways to implement the function and because this is a tutorial for beginners, well take the easiest one.</p>
<p>When the program starts, we will give the filename to open as an argument.</p>
<h3 id="g_application_handles_open-flag">G_APPLICATION_HANDLES_OPEN
flag</h3>
<p>We made a very simple editor in the previous section with
GtkTextView, GtkTextBuffer and GtkScrolledWindow. We will add
file-reading ability to the program and improve it to a file viewer.</p>
<p>The easiest way to give a filename is to use a command line
argument.</p>
<pre><code>$ ./a.out filename</code></pre>
<p>It will open the file and insert its contents into the GtkTextBuffer.</p>
<p>To do this, we need to know how GtkApplication (or GApplication) recognizes arguments. This is described in the <a href="https://docs.gtk.org/gio/class.Application.html">GIO API Reference, Application</a>.</p>
<p>When GtkApplication is created, a flag (with the type GApplicationFlags) is provided as an argument.</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"></a>GtkApplication *</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true"></a>gtk_application_new (<span class="dt">const</span> gchar *application_id, GApplicationFlags flags);</span></code></pre></div>
<p>This tutorial explains only two flags, <code>G_APPLICATION_FLAGS_NONE</code> and <code>G_APPLICATION_HANDLES_OPEN</code>. If you want to handle command line arguments, the <code>G_APPLICATION_HANDLES_COMMAND_LINE</code> flag is what you need. How to use the new application method is described in <a href="https://docs.gtk.org/gio/method.Application.run.html">GIO API Reference, g_application_run</a>, and the flag is described in the <a href="https://docs.gtk.org/gio/flags.ApplicationFlags.html">GIO API Reference, ApplicationFlags</a>.</p>
<pre><code>GApplicationFlags&#39; Members
G_APPLICATION_FLAGS_NONE Default. (No argument allowed)
... ... ...
G_APPLICATION_HANDLES_OPEN This application handles opening files (in the primary instance).
... ... ...</code></pre>
<p>There are ten flags in total, but we only need two of them so far. Weve already used <code>G_APPLICATION_FLAGS_NONE</code>, as it is the simplest option, and no arguments are allowed. If you provide arguments when running the application, an error will occur.</p>
<p>The flag <code>G_APPLICATION_HANDLES_OPEN</code> is the second simplest option. It allows arguments but only files. The application assumes all the arguments are filenames and we will use this flag when creating our GtkApplication.</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true"></a>app = gtk_application_new (<span class="st">&quot;com.github.ToshioCP.tfv3&quot;</span>, G_APPLICATION_HANDLES_OPEN);</span></code></pre></div>
<p>The program will open the file and insert its contents into the
GtkTextBuffer.</p>
<p>To do this, we need to know how GtkApplication (or GApplication)
recognizes arguments. This is described in the <a
href="https://docs.gtk.org/gio/class.Application.html">GIO API Reference
Application</a>.</p>
<p>When GtkApplication is created, a flag (GApplicationFlags) is given
as an argument.</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>GtkApplication <span class="op">*</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>gtk_application_new <span class="op">(</span><span class="dt">const</span> gchar <span class="op">*</span>application_id<span class="op">,</span> GApplicationFlags flags<span class="op">);</span></span></code></pre></div>
<p>This tutorial explains only two flags,
<code>G_APPLICATION_DEFAULT_FLAGS</code> and
<code>G_APPLICATION_HANDLES_OPEN</code>.
(<code>G_APPLICATION_FLAGS_NONE</code> was used instead of
<code>G_APPLICATION_DEFAULT_FLAGS</code> before GIO version2.73.3 (GLib
2.73.3 5/Aug/2022). Some GTK 4 applications still use
<code>G_APPLICATION_FLAGS_NONE</code>. But now it is deprecated and
<code>G_APPLICATION_DEFAULT_FLAGS</code> is recommended.) If you want to
handle command line arguments, the
<code>G_APPLICATION_HANDLES_COMMAND_LINE</code> flag is what you
need.</p>
<p>For further information, see <a
href="https://docs.gtk.org/gio/flags.ApplicationFlags.html">GIO API
Reference ApplicationFlags</a> and <a
href="https://docs.gtk.org/gio/method.Application.run.html">GIO API
Reference g_application_run</a>.</p>
<p>Weve already used <code>G_APPLICATION_DEFAULT_FLAGS</code>, as it is
the simplest option, and no command line arguments are allowed. If you
give arguments, an error will occur.</p>
<p>The flag <code>G_APPLICATION_HANDLES_OPEN</code> is the second
simplest option. It allows arguments but only files. The application
assumes all the arguments are filenames.</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>app <span class="op">=</span> gtk_application_new <span class="op">(</span><span class="st">&quot;com.github.ToshioCP.tfv3&quot;</span><span class="op">,</span> G_APPLICATION_HANDLES_OPEN<span class="op">);</span></span></code></pre></div>
<h3 id="open-signal-1">open signal</h3>
<p>Now, when the application starts, two signals can be emitted.</p>
<p>When <code>G_APPLICATION_HANDLES_OPEN</code> flag is given to the
application, two signals are available.</p>
<ul>
<li>activate signal — This signal is emitted when theres no argument.</li>
<li>open signal — This signal is emitted when there is at least one argument.</li>
<li>activate signal — This signal is emitted when theres no
argument.</li>
<li>open signal — This signal is emitted when there is at least one
argument.</li>
</ul>
<p>The handler of the “open” signal is defined as follows.</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"></a><span class="dt">void</span> user_function (GApplication *application,</span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true"></a> gpointer files,</span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true"></a> gint n_files,</span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true"></a> gchar *hint,</span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true"></a> gpointer user_data)</span></code></pre></div>
<div class="sourceCode" id="cb4"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>open <span class="op">(</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a> GApplication<span class="op">*</span> self<span class="op">,</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a> gpointer files<span class="op">,</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a> gint n_files<span class="op">,</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a> gchar<span class="op">*</span> hint<span class="op">,</span></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a> gpointer user_data</span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a><span class="op">)</span></span></code></pre></div>
<p>The parameters are:</p>
<ul>
<li><code>application</code> — the application (usually GtkApplication)</li>
<li><code>files</code> — an array of GFiles. [array length=n_files] [element-type GFile]</li>
<li><code>n_files</code> — the number of the elements of <code>files</code></li>
<li><code>hint</code> — a hint provided by the calling instance (usually it can be ignored)</li>
<li><code>user_data</code> — user data set when the signal handler was connected.</li>
<li><code>self</code> — the application instance (usually
GtkApplication)</li>
<li><code>files</code> — an array of GFiles. [array length=n_files]
[element-type GFile]</li>
<li><code>n_files</code> — the number of the elements of
<code>files</code></li>
<li><code>hint</code> — a hint provided by the calling instance (usually
it can be ignored)</li>
<li><code>user_data</code> — user data set when the signal handler was
connected.</li>
</ul>
<p>How to read a specified file (GFile) will be described next.</p>
<h2 id="making-a-file-viewer">Making a file viewer</h2>
<h3 id="what-is-a-file-viewer">What is a file viewer?</h3>
<p>A file viewer is a program that displays the text file that is given as an argument. Our file viewer will work as follows.</p>
<p>A file viewer is a program that displays text files. Our file viewer
will work as follows.</p>
<ul>
<li>When arguments are given, it treats the first argument as a filename and opens it.</li>
<li>If opening the file succeeds, it reads the contents of the file and inserts it to GtkTextBuffer and then shows the window.</li>
<li>If it fails to open the file, it will show an error message and quit.</li>
<li>If theres no argument, it will shows an error message and quit.</li>
<li>If there are two or more arguments, the second one and any others are ignored.</li>
<li>When arguments are given, it recognizes the first argument as a
filename and opens it.</li>
<li>The second argument and after are ignored.</li>
<li>If theres no argument, it shows an error message and quit.</li>
<li>If it successfully opens the file, it reads the contents of the
file, inserts them to GtkTextBuffer and shows the window.</li>
<li>If it fails to open the file, it shows an error message and
quit.</li>
</ul>
<p>The program which does this is shown below.</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb6-1"><a href="#cb6-1"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<span id="cb6-2"><a href="#cb6-2"></a></span>
<span id="cb6-3"><a href="#cb6-3"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb6-4"><a href="#cb6-4"></a>app_activate (GApplication *app, gpointer user_data) {</span>
<span id="cb6-5"><a href="#cb6-5"></a> g_print (<span class="st">&quot;You need a filename argument.</span><span class="sc">\n</span><span class="st">&quot;</span>);</span>
<span id="cb6-6"><a href="#cb6-6"></a>}</span>
<span id="cb6-7"><a href="#cb6-7"></a></span>
<span id="cb6-8"><a href="#cb6-8"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb6-9"><a href="#cb6-9"></a>app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {</span>
<span id="cb6-10"><a href="#cb6-10"></a> GtkWidget *win;</span>
<span id="cb6-11"><a href="#cb6-11"></a> GtkWidget *scr;</span>
<span id="cb6-12"><a href="#cb6-12"></a> GtkWidget *tv;</span>
<span id="cb6-13"><a href="#cb6-13"></a> GtkTextBuffer *tb;</span>
<span id="cb6-14"><a href="#cb6-14"></a> <span class="dt">char</span> *contents;</span>
<span id="cb6-15"><a href="#cb6-15"></a> gsize length;</span>
<span id="cb6-16"><a href="#cb6-16"></a> <span class="dt">char</span> *filename;</span>
<span id="cb6-17"><a href="#cb6-17"></a></span>
<span id="cb6-18"><a href="#cb6-18"></a> win = gtk_application_window_new (GTK_APPLICATION (app));</span>
<span id="cb6-19"><a href="#cb6-19"></a> gtk_window_set_default_size (GTK_WINDOW (win), <span class="dv">400</span>, <span class="dv">300</span>);</span>
<span id="cb6-20"><a href="#cb6-20"></a></span>
<span id="cb6-21"><a href="#cb6-21"></a> scr = gtk_scrolled_window_new ();</span>
<span id="cb6-22"><a href="#cb6-22"></a> gtk_window_set_child (GTK_WINDOW (win), scr);</span>
<span id="cb6-23"><a href="#cb6-23"></a></span>
<span id="cb6-24"><a href="#cb6-24"></a> tv = gtk_text_view_new ();</span>
<span id="cb6-25"><a href="#cb6-25"></a> tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));</span>
<span id="cb6-26"><a href="#cb6-26"></a> gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);</span>
<span id="cb6-27"><a href="#cb6-27"></a> gtk_text_view_set_editable (GTK_TEXT_VIEW (tv), FALSE);</span>
<span id="cb6-28"><a href="#cb6-28"></a> gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);</span>
<span id="cb6-29"><a href="#cb6-29"></a></span>
<span id="cb6-30"><a href="#cb6-30"></a> <span class="cf">if</span> (g_file_load_contents (files[<span class="dv">0</span>], NULL, &amp;contents, &amp;length, NULL, NULL)) {</span>
<span id="cb6-31"><a href="#cb6-31"></a> gtk_text_buffer_set_text (tb, contents, length);</span>
<span id="cb6-32"><a href="#cb6-32"></a> g_free (contents);</span>
<span id="cb6-33"><a href="#cb6-33"></a> <span class="cf">if</span> ((filename = g_file_get_basename (files[<span class="dv">0</span>])) != NULL) {</span>
<span id="cb6-34"><a href="#cb6-34"></a> gtk_window_set_title (GTK_WINDOW (win), filename);</span>
<span id="cb6-35"><a href="#cb6-35"></a> g_free (filename);</span>
<span id="cb6-36"><a href="#cb6-36"></a> }</span>
<span id="cb6-37"><a href="#cb6-37"></a> gtk_widget_show (win);</span>
<span id="cb6-38"><a href="#cb6-38"></a> } <span class="cf">else</span> {</span>
<span id="cb6-39"><a href="#cb6-39"></a> <span class="cf">if</span> ((filename = g_file_get_path (files[<span class="dv">0</span>])) != NULL) {</span>
<span id="cb6-40"><a href="#cb6-40"></a> g_print (<span class="st">&quot;No such file: %s.</span><span class="sc">\n</span><span class="st">&quot;</span>, filename);</span>
<span id="cb6-41"><a href="#cb6-41"></a> g_free (filename);</span>
<span id="cb6-42"><a href="#cb6-42"></a> }</span>
<span id="cb6-43"><a href="#cb6-43"></a> gtk_window_destroy (GTK_WINDOW (win));</span>
<span id="cb6-44"><a href="#cb6-44"></a> }</span>
<span id="cb6-45"><a href="#cb6-45"></a>}</span>
<span id="cb6-46"><a href="#cb6-46"></a></span>
<span id="cb6-47"><a href="#cb6-47"></a><span class="dt">int</span></span>
<span id="cb6-48"><a href="#cb6-48"></a>main (<span class="dt">int</span> argc, <span class="dt">char</span> **argv) {</span>
<span id="cb6-49"><a href="#cb6-49"></a> GtkApplication *app;</span>
<span id="cb6-50"><a href="#cb6-50"></a> <span class="dt">int</span> stat;</span>
<span id="cb6-51"><a href="#cb6-51"></a></span>
<span id="cb6-52"><a href="#cb6-52"></a> app = gtk_application_new (<span class="st">&quot;com.github.ToshioCP.tfv3&quot;</span>, G_APPLICATION_HANDLES_OPEN);</span>
<span id="cb6-53"><a href="#cb6-53"></a> g_signal_connect (app, <span class="st">&quot;activate&quot;</span>, G_CALLBACK (app_activate), NULL);</span>
<span id="cb6-54"><a href="#cb6-54"></a> g_signal_connect (app, <span class="st">&quot;open&quot;</span>, G_CALLBACK (app_open), NULL);</span>
<span id="cb6-55"><a href="#cb6-55"></a> stat = g_application_run (G_APPLICATION (app), argc, argv);</span>
<span id="cb6-56"><a href="#cb6-56"></a> g_object_unref (app);</span>
<span id="cb6-57"><a href="#cb6-57"></a> <span class="cf">return</span> stat;</span>
<span id="cb6-58"><a href="#cb6-58"></a>}</span></code></pre></div>
<p>The program is shown below.</p>
<div class="sourceCode" id="cb5"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb5-1"><a href="#cb5-1"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<span id="cb5-2"><a href="#cb5-2"></a></span>
<span id="cb5-3"><a href="#cb5-3"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb5-4"><a href="#cb5-4"></a>app_activate <span class="op">(</span>GApplication <span class="op">*</span>app<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-5"><a href="#cb5-5"></a> g_printerr <span class="op">(</span><span class="st">&quot;You need a filename argument.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb5-6"><a href="#cb5-6"></a><span class="op">}</span></span>
<span id="cb5-7"><a href="#cb5-7"></a></span>
<span id="cb5-8"><a href="#cb5-8"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb5-9"><a href="#cb5-9"></a>app_open <span class="op">(</span>GApplication <span class="op">*</span>app<span class="op">,</span> GFile <span class="op">**</span> files<span class="op">,</span> <span class="dt">int</span> n_files<span class="op">,</span> <span class="dt">char</span> <span class="op">*</span>hint<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-10"><a href="#cb5-10"></a> GtkWidget <span class="op">*</span>win<span class="op">;</span></span>
<span id="cb5-11"><a href="#cb5-11"></a> GtkWidget <span class="op">*</span>scr<span class="op">;</span></span>
<span id="cb5-12"><a href="#cb5-12"></a> GtkWidget <span class="op">*</span>tv<span class="op">;</span></span>
<span id="cb5-13"><a href="#cb5-13"></a> GtkTextBuffer <span class="op">*</span>tb<span class="op">;</span></span>
<span id="cb5-14"><a href="#cb5-14"></a> <span class="dt">char</span> <span class="op">*</span>contents<span class="op">;</span></span>
<span id="cb5-15"><a href="#cb5-15"></a> gsize length<span class="op">;</span></span>
<span id="cb5-16"><a href="#cb5-16"></a> <span class="dt">char</span> <span class="op">*</span>filename<span class="op">;</span></span>
<span id="cb5-17"><a href="#cb5-17"></a></span>
<span id="cb5-18"><a href="#cb5-18"></a> win <span class="op">=</span> gtk_application_window_new <span class="op">(</span>GTK_APPLICATION <span class="op">(</span>app<span class="op">));</span></span>
<span id="cb5-19"><a href="#cb5-19"></a> gtk_window_set_default_size <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> <span class="dv">400</span><span class="op">,</span> <span class="dv">300</span><span class="op">);</span></span>
<span id="cb5-20"><a href="#cb5-20"></a></span>
<span id="cb5-21"><a href="#cb5-21"></a> scr <span class="op">=</span> gtk_scrolled_window_new <span class="op">();</span></span>
<span id="cb5-22"><a href="#cb5-22"></a> gtk_window_set_child <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> scr<span class="op">);</span></span>
<span id="cb5-23"><a href="#cb5-23"></a></span>
<span id="cb5-24"><a href="#cb5-24"></a> tv <span class="op">=</span> gtk_text_view_new <span class="op">();</span></span>
<span id="cb5-25"><a href="#cb5-25"></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="cb5-26"><a href="#cb5-26"></a> gtk_text_view_set_wrap_mode <span class="op">(</span>GTK_TEXT_VIEW <span class="op">(</span>tv<span class="op">),</span> GTK_WRAP_WORD_CHAR<span class="op">);</span></span>
<span id="cb5-27"><a href="#cb5-27"></a> gtk_text_view_set_editable <span class="op">(</span>GTK_TEXT_VIEW <span class="op">(</span>tv<span class="op">),</span> FALSE<span class="op">);</span></span>
<span id="cb5-28"><a href="#cb5-28"></a> gtk_scrolled_window_set_child <span class="op">(</span>GTK_SCROLLED_WINDOW <span class="op">(</span>scr<span class="op">),</span> tv<span class="op">);</span></span>
<span id="cb5-29"><a href="#cb5-29"></a></span>
<span id="cb5-30"><a href="#cb5-30"></a> <span class="cf">if</span> <span class="op">(</span>g_file_load_contents <span class="op">(</span>files<span class="op">[</span><span class="dv">0</span><span class="op">],</span> NULL<span class="op">,</span> <span class="op">&amp;</span>contents<span class="op">,</span> <span class="op">&amp;</span>length<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">))</span> <span class="op">{</span></span>
<span id="cb5-31"><a href="#cb5-31"></a> gtk_text_buffer_set_text <span class="op">(</span>tb<span class="op">,</span> contents<span class="op">,</span> length<span class="op">);</span></span>
<span id="cb5-32"><a href="#cb5-32"></a> g_free <span class="op">(</span>contents<span class="op">);</span></span>
<span id="cb5-33"><a href="#cb5-33"></a> <span class="cf">if</span> <span class="op">((</span>filename <span class="op">=</span> g_file_get_basename <span class="op">(</span>files<span class="op">[</span><span class="dv">0</span><span class="op">]))</span> <span class="op">!=</span> NULL<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-34"><a href="#cb5-34"></a> gtk_window_set_title <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> filename<span class="op">);</span></span>
<span id="cb5-35"><a href="#cb5-35"></a> g_free <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb5-36"><a href="#cb5-36"></a> <span class="op">}</span></span>
<span id="cb5-37"><a href="#cb5-37"></a> gtk_widget_show <span class="op">(</span>win<span class="op">);</span></span>
<span id="cb5-38"><a href="#cb5-38"></a> <span class="op">}</span> <span class="cf">else</span> <span class="op">{</span></span>
<span id="cb5-39"><a href="#cb5-39"></a> <span class="cf">if</span> <span class="op">((</span>filename <span class="op">=</span> g_file_get_path <span class="op">(</span>files<span class="op">[</span><span class="dv">0</span><span class="op">]))</span> <span class="op">!=</span> NULL<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-40"><a href="#cb5-40"></a> g_printerr <span class="op">(</span><span class="st">&quot;No such file: %s.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">,</span> filename<span class="op">);</span></span>
<span id="cb5-41"><a href="#cb5-41"></a> g_free <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb5-42"><a href="#cb5-42"></a> <span class="op">}</span> <span class="cf">else</span></span>
<span id="cb5-43"><a href="#cb5-43"></a> g_printerr <span class="op">(</span><span class="st">&quot;File can&#39;t be opened.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb5-44"><a href="#cb5-44"></a> gtk_window_destroy <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">));</span></span>
<span id="cb5-45"><a href="#cb5-45"></a> <span class="op">}</span></span>
<span id="cb5-46"><a href="#cb5-46"></a><span class="op">}</span></span>
<span id="cb5-47"><a href="#cb5-47"></a></span>
<span id="cb5-48"><a href="#cb5-48"></a><span class="dt">int</span></span>
<span id="cb5-49"><a href="#cb5-49"></a>main <span class="op">(</span><span class="dt">int</span> argc<span class="op">,</span> <span class="dt">char</span> <span class="op">**</span>argv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-50"><a href="#cb5-50"></a> GtkApplication <span class="op">*</span>app<span class="op">;</span></span>
<span id="cb5-51"><a href="#cb5-51"></a> <span class="dt">int</span> stat<span class="op">;</span></span>
<span id="cb5-52"><a href="#cb5-52"></a></span>
<span id="cb5-53"><a href="#cb5-53"></a> app <span class="op">=</span> gtk_application_new <span class="op">(</span><span class="st">&quot;com.github.ToshioCP.tfv3&quot;</span><span class="op">,</span> G_APPLICATION_HANDLES_OPEN<span class="op">);</span></span>
<span id="cb5-54"><a href="#cb5-54"></a> g_signal_connect <span class="op">(</span>app<span class="op">,</span> <span class="st">&quot;activate&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>app_activate<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb5-55"><a href="#cb5-55"></a> g_signal_connect <span class="op">(</span>app<span class="op">,</span> <span class="st">&quot;open&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>app_open<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb5-56"><a href="#cb5-56"></a> stat <span class="op">=</span> g_application_run <span class="op">(</span>G_APPLICATION <span class="op">(</span>app<span class="op">),</span> argc<span class="op">,</span> argv<span class="op">);</span></span>
<span id="cb5-57"><a href="#cb5-57"></a> g_object_unref <span class="op">(</span>app<span class="op">);</span></span>
<span id="cb5-58"><a href="#cb5-58"></a> <span class="cf">return</span> stat<span class="op">;</span></span>
<span id="cb5-59"><a href="#cb5-59"></a><span class="op">}</span></span></code></pre></div>
<p>Save it as <code>tfv3.c</code>. Then compile and run it.</p>
<pre><code>$ comp tfv3
$ ./a.out tfv3.c</code></pre>
<figure>
<img src="image/screenshot_tfv3.png" alt="" /><figcaption>File viewer</figcaption>
<img src="image/screenshot_tfv3.png" alt="File viewer" />
<figcaption aria-hidden="true">File viewer</figcaption>
</figure>
<p>Lets explain how the program <code>tfv3.c</code> works. First, the function <code>main</code> has only two changes from the previous version.</p>
<p>The function <code>main</code> has only two changes from the previous
version.</p>
<ul>
<li><code>G_APPLICATION_FLAGS_NONE</code> is replaced by <code>G_APPLICATION_HANDLES_OPEN</code>; and</li>
<li><code>g_signal_connect (app, "open", G_CALLBACK (on_open), NULL)</code> is added.</li>
<li><code>G_APPLICATION_DEFAULT_FLAGS</code> is replaced by
<code>G_APPLICATION_HANDLES_OPEN</code></li>
<li><code>g_signal_connect (app, "open", G_CALLBACK (app_open), NULL)</code>
is added.</li>
</ul>
<p>Next, the handler <code>app_activate</code> is added and is very simple. It just outputs the error message and the application quits immediately because no window is created.</p>
<p>The main functionality is the in the handler <code>app_open</code>. It</p>
<p>When the flag <code>G_APPLICATION_HANDLES_OPEN</code> is given to
<code>gtk_application_new</code> function, the application behaves like
this:</p>
<ul>
<li>Creates GtkApplicationWindow, GtkScrolledWindow, GtkTextView and GtkTextBuffer and connects them together;</li>
<li>Sets wrap mode to <code>GTK_WRAP_WORD_CHAR</code> in GtktextView;</li>
<li>Sets GtkTextView to non-editable because the program isnt an editor but only a viewer;</li>
<li>Reads the file and inserts the text into GtkTextBuffer (this will be explained in detail later); and</li>
<li>If the file is not opened then outputs an error message and destroys the window. This makes the application quit.</li>
<li>If the application is run without command line arguments, it emits
“activate” signal when it is activated.</li>
<li>If the application is run with command line arguments, it emits
“open” signal when it is activated.</li>
</ul>
<p>The following is the important file reading part of the program and is shown again below.</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"></a><span class="cf">if</span> (g_file_load_contents (files[<span class="dv">0</span>], NULL, &amp;contents, &amp;length, NULL, NULL)) {</span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true"></a> gtk_text_buffer_set_text (tb, contents, length);</span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true"></a> g_free (contents);</span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true"></a> <span class="cf">if</span> ((filename = g_file_get_basename (files[<span class="dv">0</span>])) != NULL) {</span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true"></a> gtk_window_set_title (GTK_WINDOW (win), filename);</span>
<span id="cb8-6"><a href="#cb8-6" aria-hidden="true"></a> g_free (filename);</span>
<span id="cb8-7"><a href="#cb8-7" aria-hidden="true"></a> }</span>
<span id="cb8-8"><a href="#cb8-8" aria-hidden="true"></a> gtk_widget_show (win);</span>
<span id="cb8-9"><a href="#cb8-9" aria-hidden="true"></a>} <span class="cf">else</span> {</span>
<span id="cb8-10"><a href="#cb8-10" aria-hidden="true"></a> <span class="cf">if</span> ((filename = g_file_get_path (files[<span class="dv">0</span>])) != NULL) {</span>
<span id="cb8-11"><a href="#cb8-11" aria-hidden="true"></a> g_print (<span class="st">&quot;No such file: %s.</span><span class="sc">\n</span><span class="st">&quot;</span>, filename);</span>
<span id="cb8-12"><a href="#cb8-12" aria-hidden="true"></a> g_free (filename);</span>
<span id="cb8-13"><a href="#cb8-13" aria-hidden="true"></a> }</span>
<span id="cb8-14"><a href="#cb8-14" aria-hidden="true"></a> gtk_window_destroy (GTK_WINDOW (win));</span>
<span id="cb8-15"><a href="#cb8-15" aria-hidden="true"></a>}</span></code></pre></div>
<p>The function <code>g_file_load_contents</code> loads the file contents into a buffer, which is automatically allocated and sets <code>contents</code> to point that buffer. The length of the buffer is set to <code>length</code>. It returns <code>TRUE</code> if the files contents are successfully loaded and <code>FALSE</code> if an error occurs.</p>
<p>If this function succeeds, it inserts the contents into GtkTextBuffer, frees the buffer pointed by <code>contents</code>, sets the title of the window, frees the memories pointed by <code>filename</code> and then shows the window. If it fails, it outputs an error message and destroys the window, causing the program to quit.</p>
<p>The handler <code>app_activate</code> becomes very simple. It just
outputs the error message and return to the caller. Then the application
quits immediately because no window is created.</p>
<p>The main work is done in the handler <code>app_open</code>.</p>
<ul>
<li>Creates GtkApplicationWindow, GtkScrolledWindow, GtkTextView and
GtkTextBuffer and connects them together</li>
<li>Sets wrap mode to <code>GTK_WRAP_WORD_CHAR</code> in
GtktextView</li>
<li>Sets GtkTextView to non-editable because the program isnt an editor
but only a viewer</li>
<li>Reads the file and inserts the text into GtkTextBuffer (this will be
explained later)</li>
<li>If the file is not opened, outputs an error message and destroys the
window. This makes the application quit.</li>
</ul>
<p>The following is the file reading part of the program again.</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="cf">if</span> <span class="op">(</span>g_file_load_contents <span class="op">(</span>files<span class="op">[</span><span class="dv">0</span><span class="op">],</span> NULL<span class="op">,</span> <span class="op">&amp;</span>contents<span class="op">,</span> <span class="op">&amp;</span>length<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">))</span> <span class="op">{</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a> gtk_text_buffer_set_text <span class="op">(</span>tb<span class="op">,</span> contents<span class="op">,</span> length<span class="op">);</span></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a> g_free <span class="op">(</span>contents<span class="op">);</span></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">((</span>filename <span class="op">=</span> g_file_get_basename <span class="op">(</span>files<span class="op">[</span><span class="dv">0</span><span class="op">]))</span> <span class="op">!=</span> NULL<span class="op">)</span> <span class="op">{</span></span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a> gtk_window_set_title <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> filename<span class="op">);</span></span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a> g_free <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a> gtk_widget_show <span class="op">(</span>win<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="cf">else</span> <span class="op">{</span></span>
<span id="cb7-10"><a href="#cb7-10" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">((</span>filename <span class="op">=</span> g_file_get_path <span class="op">(</span>files<span class="op">[</span><span class="dv">0</span><span class="op">]))</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> g_printerr <span class="op">(</span><span class="st">&quot;No such file: %s.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">,</span> filename<span class="op">);</span></span>
<span id="cb7-12"><a href="#cb7-12" aria-hidden="true" tabindex="-1"></a> g_free <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb7-13"><a href="#cb7-13" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span> <span class="cf">else</span></span>
<span id="cb7-14"><a href="#cb7-14" aria-hidden="true" tabindex="-1"></a> g_printerr <span class="op">(</span><span class="st">&quot;File can&#39;t be opened.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb7-15"><a href="#cb7-15" aria-hidden="true" tabindex="-1"></a> gtk_window_destroy <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">));</span></span>
<span id="cb7-16"><a href="#cb7-16" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>The function <code>g_file_load_contents</code> loads the file
contents into a temporary buffer, which is automatically allocated and
sets <code>contents</code> to point the buffer. The length of the buffer
is assigned to <code>length</code>. It returns <code>TRUE</code> if the
files contents are successfully loaded or <code>FALSE</code> if an
error occurs. If you want to know the details about
g_file_load_contents, see <a
href="https://docs.gtk.org/gio/method.File.load_contents.html">g file
load contents</a>.</p>
<p>If it has successfully read the file, it inserts the contents into
GtkTextBuffer, frees the temporary buffer pointed by
<code>contents</code>, sets the title of the window, frees the memories
pointed by <code>filename</code> and then shows the window. If it fails,
it outputs an error message and destroys the window and finally make the
program quit.</p>
<h2 id="gtknotebook">GtkNotebook</h2>
<p>GtkNotebook is a container widget that uses tabs and contains multiple children. The child that is displayed depends on which tab has been selected.</p>
<p>GtkNotebook is a container widget that contains multiple widgets with
tabs. It shows only one child at a time. Another child will be shown
when its tab is clicked.</p>
<figure>
<img src="image/screenshot_gtk_notebook.png" alt="" /><figcaption>GtkNotebook</figcaption>
<img src="image/screenshot_gtk_notebook.png" alt="GtkNotebook" />
<figcaption aria-hidden="true">GtkNotebook</figcaption>
</figure>
<p>Looking at the screenshots above, the left one is the window at the startup. It shows the file <code>pr1.c</code> and the filename is in the left tab. After clicking on the right tab, the contents of the file <code>tfv1.c</code> are shown instead. This is shown in the right screenshot.</p>
<p>The GtkNotebook widget is inserted as a child of GtkApplicationWindow and contains a GtkScrolledWindow for each file that is being displayed. The code to do this is given in <code>tfv4.c</code> and is:</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb9-1"><a href="#cb9-1"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<span id="cb9-2"><a href="#cb9-2"></a></span>
<span id="cb9-3"><a href="#cb9-3"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb9-4"><a href="#cb9-4"></a>app_activate (GApplication *app, gpointer user_data) {</span>
<span id="cb9-5"><a href="#cb9-5"></a> g_print (<span class="st">&quot;You need a filename argument.</span><span class="sc">\n</span><span class="st">&quot;</span>);</span>
<span id="cb9-6"><a href="#cb9-6"></a>}</span>
<span id="cb9-7"><a href="#cb9-7"></a></span>
<span id="cb9-8"><a href="#cb9-8"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb9-9"><a href="#cb9-9"></a>app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {</span>
<span id="cb9-10"><a href="#cb9-10"></a> GtkWidget *win;</span>
<span id="cb9-11"><a href="#cb9-11"></a> GtkWidget *nb;</span>
<span id="cb9-12"><a href="#cb9-12"></a> GtkWidget *lab;</span>
<span id="cb9-13"><a href="#cb9-13"></a> GtkNotebookPage *nbp;</span>
<span id="cb9-14"><a href="#cb9-14"></a> GtkWidget *scr;</span>
<span id="cb9-15"><a href="#cb9-15"></a> GtkWidget *tv;</span>
<span id="cb9-16"><a href="#cb9-16"></a> GtkTextBuffer *tb;</span>
<span id="cb9-17"><a href="#cb9-17"></a> <span class="dt">char</span> *contents;</span>
<span id="cb9-18"><a href="#cb9-18"></a> gsize length;</span>
<span id="cb9-19"><a href="#cb9-19"></a> <span class="dt">char</span> *filename;</span>
<span id="cb9-20"><a href="#cb9-20"></a> <span class="dt">int</span> i;</span>
<span id="cb9-21"><a href="#cb9-21"></a></span>
<span id="cb9-22"><a href="#cb9-22"></a> win = gtk_application_window_new (GTK_APPLICATION (app));</span>
<span id="cb9-23"><a href="#cb9-23"></a> gtk_window_set_title (GTK_WINDOW (win), <span class="st">&quot;file viewer&quot;</span>);</span>
<span id="cb9-24"><a href="#cb9-24"></a> gtk_window_set_default_size (GTK_WINDOW (win), <span class="dv">400</span>, <span class="dv">300</span>);</span>
<span id="cb9-25"><a href="#cb9-25"></a> gtk_window_maximize (GTK_WINDOW (win));</span>
<span id="cb9-26"><a href="#cb9-26"></a></span>
<span id="cb9-27"><a href="#cb9-27"></a> nb = gtk_notebook_new ();</span>
<span id="cb9-28"><a href="#cb9-28"></a> gtk_window_set_child (GTK_WINDOW (win), nb);</span>
<span id="cb9-29"><a href="#cb9-29"></a></span>
<span id="cb9-30"><a href="#cb9-30"></a> <span class="cf">for</span> (i = <span class="dv">0</span>; i &lt; n_files; i++) {</span>
<span id="cb9-31"><a href="#cb9-31"></a> <span class="cf">if</span> (g_file_load_contents (files[i], NULL, &amp;contents, &amp;length, NULL, NULL)) {</span>
<span id="cb9-32"><a href="#cb9-32"></a> scr = gtk_scrolled_window_new ();</span>
<span id="cb9-33"><a href="#cb9-33"></a> tv = gtk_text_view_new ();</span>
<span id="cb9-34"><a href="#cb9-34"></a> tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));</span>
<span id="cb9-35"><a href="#cb9-35"></a> gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);</span>
<span id="cb9-36"><a href="#cb9-36"></a> gtk_text_view_set_editable (GTK_TEXT_VIEW (tv), FALSE);</span>
<span id="cb9-37"><a href="#cb9-37"></a> gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);</span>
<span id="cb9-38"><a href="#cb9-38"></a></span>
<span id="cb9-39"><a href="#cb9-39"></a> gtk_text_buffer_set_text (tb, contents, length);</span>
<span id="cb9-40"><a href="#cb9-40"></a> g_free (contents);</span>
<span id="cb9-41"><a href="#cb9-41"></a> <span class="cf">if</span> ((filename = g_file_get_basename (files[i])) != NULL) {</span>
<span id="cb9-42"><a href="#cb9-42"></a> lab = gtk_label_new (filename);</span>
<span id="cb9-43"><a href="#cb9-43"></a> g_free (filename);</span>
<span id="cb9-44"><a href="#cb9-44"></a> } <span class="cf">else</span></span>
<span id="cb9-45"><a href="#cb9-45"></a> lab = gtk_label_new (<span class="st">&quot;&quot;</span>);</span>
<span id="cb9-46"><a href="#cb9-46"></a> gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);</span>
<span id="cb9-47"><a href="#cb9-47"></a> nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);</span>
<span id="cb9-48"><a href="#cb9-48"></a> g_object_set (nbp, <span class="st">&quot;tab-expand&quot;</span>, TRUE, NULL);</span>
<span id="cb9-49"><a href="#cb9-49"></a> } <span class="cf">else</span> <span class="cf">if</span> ((filename = g_file_get_path (files[i])) != NULL) {</span>
<span id="cb9-50"><a href="#cb9-50"></a> g_print (<span class="st">&quot;No such file: %s.</span><span class="sc">\n</span><span class="st">&quot;</span>, filename);</span>
<span id="cb9-51"><a href="#cb9-51"></a> g_free (filename);</span>
<span id="cb9-52"><a href="#cb9-52"></a> } <span class="cf">else</span></span>
<span id="cb9-53"><a href="#cb9-53"></a> g_print (<span class="st">&quot;No valid file is given</span><span class="sc">\n</span><span class="st">&quot;</span>);</span>
<span id="cb9-54"><a href="#cb9-54"></a> }</span>
<span id="cb9-55"><a href="#cb9-55"></a> <span class="cf">if</span> (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) &gt; <span class="dv">0</span>)</span>
<span id="cb9-56"><a href="#cb9-56"></a> gtk_widget_show (win);</span>
<span id="cb9-57"><a href="#cb9-57"></a> <span class="cf">else</span></span>
<span id="cb9-58"><a href="#cb9-58"></a> gtk_window_destroy (GTK_WINDOW (win));</span>
<span id="cb9-59"><a href="#cb9-59"></a>}</span>
<span id="cb9-60"><a href="#cb9-60"></a></span>
<span id="cb9-61"><a href="#cb9-61"></a><span class="dt">int</span></span>
<span id="cb9-62"><a href="#cb9-62"></a>main (<span class="dt">int</span> argc, <span class="dt">char</span> **argv) {</span>
<span id="cb9-63"><a href="#cb9-63"></a> GtkApplication *app;</span>
<span id="cb9-64"><a href="#cb9-64"></a> <span class="dt">int</span> stat;</span>
<span id="cb9-65"><a href="#cb9-65"></a></span>
<span id="cb9-66"><a href="#cb9-66"></a> app = gtk_application_new (<span class="st">&quot;com.github.ToshioCP.tfv4&quot;</span>, G_APPLICATION_HANDLES_OPEN);</span>
<span id="cb9-67"><a href="#cb9-67"></a> g_signal_connect (app, <span class="st">&quot;activate&quot;</span>, G_CALLBACK (app_activate), NULL);</span>
<span id="cb9-68"><a href="#cb9-68"></a> g_signal_connect (app, <span class="st">&quot;open&quot;</span>, G_CALLBACK (app_open), NULL);</span>
<span id="cb9-69"><a href="#cb9-69"></a> stat = g_application_run (G_APPLICATION (app), argc, argv);</span>
<span id="cb9-70"><a href="#cb9-70"></a> g_object_unref (app);</span>
<span id="cb9-71"><a href="#cb9-71"></a> <span class="cf">return</span> stat;</span>
<span id="cb9-72"><a href="#cb9-72"></a>}</span></code></pre></div>
<p>Most of the changes are in the function <code>app_open</code>. The numbers at the left of the following items are line numbers in the source code.</p>
<p>The left image is the window at the startup. The file
<code>pr1.c</code> is shown and its filename is in the left tab. After
clicking on the right tab, the contents of the file <code>tfv1.c</code>
is shown. The right image is the screenshot.</p>
<p>The following is <code>tfv4.c</code>. It has GtkNoteBook widget. It
is inserted as a child of GtkApplicationWindow and contains multiple
GtkScrolledWindow.</p>
<div class="sourceCode" id="cb8"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb8-1"><a href="#cb8-1"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<span id="cb8-2"><a href="#cb8-2"></a></span>
<span id="cb8-3"><a href="#cb8-3"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb8-4"><a href="#cb8-4"></a>app_activate <span class="op">(</span>GApplication <span class="op">*</span>app<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-5"><a href="#cb8-5"></a> g_printerr <span class="op">(</span><span class="st">&quot;You need a filename argument.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb8-6"><a href="#cb8-6"></a><span class="op">}</span></span>
<span id="cb8-7"><a href="#cb8-7"></a></span>
<span id="cb8-8"><a href="#cb8-8"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb8-9"><a href="#cb8-9"></a>app_open <span class="op">(</span>GApplication <span class="op">*</span>app<span class="op">,</span> GFile <span class="op">**</span> files<span class="op">,</span> gint n_files<span class="op">,</span> gchar <span class="op">*</span>hint<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-10"><a href="#cb8-10"></a> GtkWidget <span class="op">*</span>win<span class="op">;</span></span>
<span id="cb8-11"><a href="#cb8-11"></a> GtkWidget <span class="op">*</span>nb<span class="op">;</span></span>
<span id="cb8-12"><a href="#cb8-12"></a> GtkWidget <span class="op">*</span>lab<span class="op">;</span></span>
<span id="cb8-13"><a href="#cb8-13"></a> GtkNotebookPage <span class="op">*</span>nbp<span class="op">;</span></span>
<span id="cb8-14"><a href="#cb8-14"></a> GtkWidget <span class="op">*</span>scr<span class="op">;</span></span>
<span id="cb8-15"><a href="#cb8-15"></a> GtkWidget <span class="op">*</span>tv<span class="op">;</span></span>
<span id="cb8-16"><a href="#cb8-16"></a> GtkTextBuffer <span class="op">*</span>tb<span class="op">;</span></span>
<span id="cb8-17"><a href="#cb8-17"></a> <span class="dt">char</span> <span class="op">*</span>contents<span class="op">;</span></span>
<span id="cb8-18"><a href="#cb8-18"></a> gsize length<span class="op">;</span></span>
<span id="cb8-19"><a href="#cb8-19"></a> <span class="dt">char</span> <span class="op">*</span>filename<span class="op">;</span></span>
<span id="cb8-20"><a href="#cb8-20"></a> <span class="dt">int</span> i<span class="op">;</span></span>
<span id="cb8-21"><a href="#cb8-21"></a></span>
<span id="cb8-22"><a href="#cb8-22"></a> win <span class="op">=</span> gtk_application_window_new <span class="op">(</span>GTK_APPLICATION <span class="op">(</span>app<span class="op">));</span></span>
<span id="cb8-23"><a href="#cb8-23"></a> gtk_window_set_title <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> <span class="st">&quot;file viewer&quot;</span><span class="op">);</span></span>
<span id="cb8-24"><a href="#cb8-24"></a> gtk_window_set_default_size <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> <span class="dv">600</span><span class="op">,</span> <span class="dv">400</span><span class="op">);</span></span>
<span id="cb8-25"><a href="#cb8-25"></a></span>
<span id="cb8-26"><a href="#cb8-26"></a> nb <span class="op">=</span> gtk_notebook_new <span class="op">();</span></span>
<span id="cb8-27"><a href="#cb8-27"></a> gtk_window_set_child <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> nb<span class="op">);</span></span>
<span id="cb8-28"><a href="#cb8-28"></a></span>
<span id="cb8-29"><a href="#cb8-29"></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_files<span class="op">;</span> i<span class="op">++)</span> <span class="op">{</span></span>
<span id="cb8-30"><a href="#cb8-30"></a> <span class="cf">if</span> <span class="op">(</span>g_file_load_contents <span class="op">(</span>files<span class="op">[</span>i<span class="op">],</span> NULL<span class="op">,</span> <span class="op">&amp;</span>contents<span class="op">,</span> <span class="op">&amp;</span>length<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">))</span> <span class="op">{</span></span>
<span id="cb8-31"><a href="#cb8-31"></a> scr <span class="op">=</span> gtk_scrolled_window_new <span class="op">();</span></span>
<span id="cb8-32"><a href="#cb8-32"></a> tv <span class="op">=</span> gtk_text_view_new <span class="op">();</span></span>
<span id="cb8-33"><a href="#cb8-33"></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="cb8-34"><a href="#cb8-34"></a> gtk_text_view_set_wrap_mode <span class="op">(</span>GTK_TEXT_VIEW <span class="op">(</span>tv<span class="op">),</span> GTK_WRAP_WORD_CHAR<span class="op">);</span></span>
<span id="cb8-35"><a href="#cb8-35"></a> gtk_text_view_set_editable <span class="op">(</span>GTK_TEXT_VIEW <span class="op">(</span>tv<span class="op">),</span> FALSE<span class="op">);</span></span>
<span id="cb8-36"><a href="#cb8-36"></a> gtk_scrolled_window_set_child <span class="op">(</span>GTK_SCROLLED_WINDOW <span class="op">(</span>scr<span class="op">),</span> tv<span class="op">);</span></span>
<span id="cb8-37"><a href="#cb8-37"></a></span>
<span id="cb8-38"><a href="#cb8-38"></a> gtk_text_buffer_set_text <span class="op">(</span>tb<span class="op">,</span> contents<span class="op">,</span> length<span class="op">);</span></span>
<span id="cb8-39"><a href="#cb8-39"></a> g_free <span class="op">(</span>contents<span class="op">);</span></span>
<span id="cb8-40"><a href="#cb8-40"></a> <span class="cf">if</span> <span class="op">((</span>filename <span class="op">=</span> g_file_get_basename <span class="op">(</span>files<span class="op">[</span>i<span class="op">]))</span> <span class="op">!=</span> NULL<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-41"><a href="#cb8-41"></a> lab <span class="op">=</span> gtk_label_new <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb8-42"><a href="#cb8-42"></a> g_free <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb8-43"><a href="#cb8-43"></a> <span class="op">}</span> <span class="cf">else</span></span>
<span id="cb8-44"><a href="#cb8-44"></a> lab <span class="op">=</span> gtk_label_new <span class="op">(</span><span class="st">&quot;&quot;</span><span class="op">);</span></span>
<span id="cb8-45"><a href="#cb8-45"></a> gtk_notebook_append_page <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">),</span> scr<span class="op">,</span> lab<span class="op">);</span></span>
<span id="cb8-46"><a href="#cb8-46"></a> nbp <span class="op">=</span> gtk_notebook_get_page <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">),</span> scr<span class="op">);</span></span>
<span id="cb8-47"><a href="#cb8-47"></a> g_object_set <span class="op">(</span>nbp<span class="op">,</span> <span class="st">&quot;tab-expand&quot;</span><span class="op">,</span> TRUE<span class="op">,</span> NULL<span class="op">);</span></span>
<span id="cb8-48"><a href="#cb8-48"></a> <span class="op">}</span> <span class="cf">else</span> <span class="cf">if</span> <span class="op">((</span>filename <span class="op">=</span> g_file_get_path <span class="op">(</span>files<span class="op">[</span>i<span class="op">]))</span> <span class="op">!=</span> NULL<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-49"><a href="#cb8-49"></a> g_printerr <span class="op">(</span><span class="st">&quot;No such file: %s.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">,</span> filename<span class="op">);</span></span>
<span id="cb8-50"><a href="#cb8-50"></a> g_free <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb8-51"><a href="#cb8-51"></a> <span class="op">}</span> <span class="cf">else</span></span>
<span id="cb8-52"><a href="#cb8-52"></a> g_printerr <span class="op">(</span><span class="st">&quot;No valid file is given</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb8-53"><a href="#cb8-53"></a> <span class="op">}</span></span>
<span id="cb8-54"><a href="#cb8-54"></a> <span class="cf">if</span> <span class="op">(</span>gtk_notebook_get_n_pages <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">))</span> <span class="op">&gt;</span> <span class="dv">0</span><span class="op">)</span></span>
<span id="cb8-55"><a href="#cb8-55"></a> gtk_widget_show <span class="op">(</span>win<span class="op">);</span></span>
<span id="cb8-56"><a href="#cb8-56"></a> <span class="cf">else</span></span>
<span id="cb8-57"><a href="#cb8-57"></a> gtk_window_destroy <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">));</span></span>
<span id="cb8-58"><a href="#cb8-58"></a><span class="op">}</span></span>
<span id="cb8-59"><a href="#cb8-59"></a></span>
<span id="cb8-60"><a href="#cb8-60"></a><span class="dt">int</span></span>
<span id="cb8-61"><a href="#cb8-61"></a>main <span class="op">(</span><span class="dt">int</span> argc<span class="op">,</span> <span class="dt">char</span> <span class="op">**</span>argv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-62"><a href="#cb8-62"></a> GtkApplication <span class="op">*</span>app<span class="op">;</span></span>
<span id="cb8-63"><a href="#cb8-63"></a> <span class="dt">int</span> stat<span class="op">;</span></span>
<span id="cb8-64"><a href="#cb8-64"></a></span>
<span id="cb8-65"><a href="#cb8-65"></a> app <span class="op">=</span> gtk_application_new <span class="op">(</span><span class="st">&quot;com.github.ToshioCP.tfv4&quot;</span><span class="op">,</span> G_APPLICATION_HANDLES_OPEN<span class="op">);</span></span>
<span id="cb8-66"><a href="#cb8-66"></a> g_signal_connect <span class="op">(</span>app<span class="op">,</span> <span class="st">&quot;activate&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>app_activate<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb8-67"><a href="#cb8-67"></a> g_signal_connect <span class="op">(</span>app<span class="op">,</span> <span class="st">&quot;open&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>app_open<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb8-68"><a href="#cb8-68"></a> stat <span class="op">=</span> g_application_run <span class="op">(</span>G_APPLICATION <span class="op">(</span>app<span class="op">),</span> argc<span class="op">,</span> argv<span class="op">);</span></span>
<span id="cb8-69"><a href="#cb8-69"></a> g_object_unref <span class="op">(</span>app<span class="op">);</span></span>
<span id="cb8-70"><a href="#cb8-70"></a> <span class="cf">return</span> stat<span class="op">;</span></span>
<span id="cb8-71"><a href="#cb8-71"></a><span class="op">}</span></span></code></pre></div>
<p>Most of the changes are in the function <code>app_open</code>. The
numbers at the left of the following items are line numbers in the
source code.</p>
<ul>
<li>11-13: Variables <code>nb</code>, <code>lab</code> and <code>nbp</code> are defined and will point to a new GtkNotebook, GtkLabel and GtkNotebookPage widget respectively.</li>
<li>11-13: Variables <code>nb</code>, <code>lab</code> and
<code>nbp</code> are defined. They point GtkNotebook, GtkLabel and
GtkNotebookPage respectively.</li>
<li>23: The windows title is set to “file viewer”.</li>
<li>25: The size of the window is set to maximum because a big window is appropriate for file viewers.</li>
<li>27-28 GtkNotebook is created and inserted to the GtkApplicationWindow as a child.</li>
<li>30-59 For-loop. Each loop corresponds to a filename argument, and <code>files[i]</code> is GFile object containing the i-th argument.</li>
<li>32-37 GtkScrollledWindow, GtkTextView are created and GtkTextBuffer found from the new GtkTextView. GtkTextView is connected to GtkScrolledWindow as a child. Each file gets their own copy of these widgets, so they are created inside the for-loop.</li>
<li>39-40 inserts the contents of the file into GtkTextBuffer and frees the memory pointed by <code>contents</code>.</li>
<li>41-43: Gets the filename and creates GtkLabel with the filename and then frees <code>filename</code>.</li>
<li>44-45: If <code>filename</code> is NULL, creates GtkLabel with the empty string.</li>
<li>46: Appends GtkScrolledWindow as a page, with the tab GtkLabel, to GtkNotebook. At this time a GtkNoteBookPage widget is created automatically. The GtkScrolledWindow widget is connected to the GtkNotebookPage. Therefore, the structure is like this:</li>
</ul>
<pre><code> GtkNotebook -- GtkNotebookPage -- GtkScrolledWindow</code></pre>
<ul>
<li>47: Gets GtkNotebookPage widget and sets <code>nbp</code> to point to this GtkNotebookPage.</li>
<li>48: GtkNotebookPage has a property set called “tab-expand”. If it is set to TRUE then the tab expands horizontally as long as possible. If it is FALSE, then the width of the tab is determined by the size of the label. <code>g_object_set</code> is a general function to set properties in objects. See <a href="https://docs.gtk.org/gobject/method.Object.set.html">GObject API Reference, g_object_set</a>.</li>
<li>49-51: If the file cannot be read, “No such file” message is displayed and the <code>filename</code> buffer is freed.</li>
<li>52-53: If <code>filename</code> is NULL, the “No valid file is given” message is outputted.</li>
<li>55-58: If at least one file was read, then the number of GtkNotebookPage is greater than zero. If its true, it shows the window. If its false, it destroys the window, which causes the program to quit.</li>
<li>24: The default size of the window is 600x400.</li>
<li>26-27 GtkNotebook is created and inserted to the
GtkApplicationWindow as a child.</li>
<li>29-58 For-loop. The variable <code>files[i]</code> points i-th
GFile, which is created by the GtkApplication from the i-th command line
argument.</li>
<li>31-36 GtkScrollledWindow, GtkTextView are created. GtkTextBuffer is
got from the GtkTextView. The GtkTextView is connected to the
GtkScrolledWindow as a child.</li>
<li>38-39 inserts the contents of the file into GtkTextBuffer and frees
the memory pointed by <code>contents</code>.</li>
<li>40-42: If the filename is taken from the GFile, GtkLabel is created
with the filename. The string <code>filename</code> is freed..</li>
<li>43-44: If it fails to take the filename, empty string GtkLabel is
created.</li>
<li>45-46: Appends a GtkScrolledWindow to the GtkNotebook as a child.
And the GtkLabel is set as the childs tab. At the same time, a
GtkNoteBookPage is created automatically. The function
<code>gtk_notebook_get_page</code> returns the GtkNotebookPage of the
child (GtkScrolledWindow).</li>
<li>47: GtkNotebookPage has “tab-expand” property. If it is set to TRUE
then the tab expands horizontally as long as possible. If it is FALSE,
then the width of the tab is determined by the size of the label.
<code>g_object_set</code> is a general function to set properties of
objects. See <a
href="https://docs.gtk.org/gobject/method.Object.set.html">GObject API
Reference g_object_set</a>.</li>
<li>48-50: If it fails to read the file and a filename is taken from the
GFile, “No such file” message is displayed. The <code>filename</code> is
freed.</li>
<li>51-52: If <code>filename</code> is NULL, the “No valid file is
given” message is displayed.</li>
<li>54-57: If at least one page exists, the window is shown. Otherwise,
the window is destroyed and the application quits.</li>
</ul>
</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>

View file

@ -111,62 +111,60 @@
</div>
</div>
</nav>
<h1 id="defining-a-child-object">Defining a child object</h1>
<h1 id="defining-a-final-class">Defining a final class</h1>
<h2 id="a-very-simple-editor">A very simple editor</h2>
<p>In the previous section we made a very simple file viewer. Now we go
<p>We made a very simple file viewer in the previous section. Now we go
on to rewrite it and turn it into very simple editor. Its source file is
in tfe1.c (text file editor 1).</p>
<p>GtkTextView has a feature for editing multiple lines. Therefore, we
dont need to write the program from scratch, we just add two things to
the file viewer:</p>
<code>tfe1.c</code> (text file editor 1) under <code>tfe</code>
directory.</p>
<p>GtkTextView is a multi-line editor. So, we dont need to write the
editor from scratch. We just add two things to the file viewer:</p>
<ul>
<li>Memory to store a pointer to the GFile instance.</li>
<li>A function to write the file.</li>
<li>Pointers to GFile instances.</li>
<li>A text-save function.</li>
</ul>
<p>There are a couple of ways to store the details of GFile.</p>
<p>There are a couple of ways to store the pointers.</p>
<ul>
<li>Use global variables; or</li>
<li>Make a child object, which can extend the instance memory for the
GFile object.</li>
<li>Use global variables</li>
<li>Make a child class of GtkTextView and its each instance holds a
pointer to the GFile instance.</li>
</ul>
<p>Using global variables is easy to implement. Define a sufficient size
array of pointers to GFile. For example,</p>
pointer array to GFile. For example,</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a>GFile <span class="op">*</span>f<span class="op">[</span><span class="dv">20</span><span class="op">];</span></span></code></pre></div>
<p>The variable <code>f[i]</code> corresponds to the file associated to
the i-th GtkNotebookPage. There are however two problems with this. The
first concerns the size of the array. If a user gives too many arguments
(more than 20 in the example above), it is impossible to store the
additional pointers to the GFile instances. The second is the increasing
difficulty for maintenance of the program. We have a small program so
far, but however, if you continue developing it, the size of the program
will grow. Generally speaking, the bigger the program size, the more
difficult it is to keep track of and maintain global variables. Global
variables can be used and changed anywhere throughout the entire
program.</p>
<p>Making a child object is a good idea in terms of maintenance. One
thing you need to be careful of is the difference between “child object”
and “child widget”. Here we are describing a “child object”. A child
object includes, and expands on its parent object, as a child object
derives everything from the parent object.</p>
<p>The variable <code>f[i]</code> corresponds to the file associated
with the i-th GtkNotebookPage.</p>
<p>However, There are two problems. The first is the size of the array.
If a user gives too many arguments (more than 20 in the example above),
it is impossible to store all the pointers to the GFile instances. The
second is difficulty to maintain the program. We have a small program so
far. But, the more you develop the program, the bigger its size grows.
Generally speaking, it is very difficult to maintain global variables in
a big program. When you check the global variable, you need to check all
the codes that use the variable.</p>
<p>Making a child class is a good idea in terms of maintenance. And we
prefer it rather than a global variable.</p>
<p>Be careful that we are thinking about “child class”, not “child
widget”. Child class and child widget are totally different. Class is a
term of GObject system. If you are not familiar with GObject, see:</p>
<ul>
<li><a href="https://docs.gtk.org/gobject/">GObject API
reference</a></li>
<li><a href="https://toshiocp.github.io/Gobject-tutorial/">GObject
tutorial for beginners</a></li>
</ul>
<p>A child class inherits everything from the parent and, in addition,
extends its performance. We will define TfeTextView as a child class of
GtkTextView. It has everything that GtkTextView has and adds a pointer
to a GFile.</p>
<figure>
<img src="image/child.png" alt="Child object of GtkTextView" />
<figcaption aria-hidden="true">Child object of GtkTextView</figcaption>
</figure>
<p>We will define TfeTextView as a child object of GtkTextView. It has
everything that GtkTextView has. Specifically, TfeTextView has a
GtkTextbuffer which corresponds to the GtkTextView inside TfeTextView.
The additional important thing is that TfeTextView can also keep an
additional pointer to GFile.</p>
<p>In general, this is how GObjects work. Understanding the general
theory about Gobjects is difficult, particularly for beginners. So, I
will just show you the way how to write the code and avoid the
theoretical side. If you want to know about GObject system, refer to
another <a
href="https://github.com/ToshioCP/Gobject-tutorial">tutorial</a>.</p>
<h2 id="how-to-define-a-child-object-of-gtktextview">How to define a
child object of GtkTextView</h2>
<p>Lets define the TfeTextView object, which is a child object of
GtkTextView. First, look at the program below.</p>
<h2 id="how-to-define-a-child-class-of-gtktextview">How to define a
child class of GtkTextView</h2>
<p>You need to know GObject system convention. First, look at the
program 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><span class="pp">#define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>G_DECLARE_FINAL_TYPE <span class="op">(</span>TfeTextView<span class="op">,</span> tfe_text_view<span class="op">,</span> TFE<span class="op">,</span> TEXT_VIEW<span class="op">,</span> GtkTextView<span class="op">)</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a></span>
@ -200,71 +198,73 @@ GtkTextView. First, look at the program below.</p>
<span id="cb2-31"><a href="#cb2-31" aria-hidden="true" tabindex="-1"></a>tfe_text_view_new <span class="op">(</span><span class="dt">void</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb2-32"><a href="#cb2-32" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> GTK_WIDGET <span class="op">(</span>g_object_new <span class="op">(</span>TFE_TYPE_TEXT_VIEW<span class="op">,</span> NULL<span class="op">));</span></span>
<span id="cb2-33"><a href="#cb2-33" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>If you are curious about the background theory of this program,
thats good, because knowing the theory is very important if you want to
program GTK applications. Look at <a
href="https://docs.gtk.org/gobject/">GObject API Reference</a>. All you
need is described there, or refer to <a
href="https://github.com/ToshioCP/Gobject-tutorial">GObject
tutorial</a>. Its a tough journey especially for beginners so for now,
you dont need to know about this difficult theory. It is enough to just
remember the instructions below.</p>
<ul>
<li>TfeTextView is divided into two parts. Tfe and TextView. Tfe is
called the prefix, namespace or module. TextView is called the
object.</li>
called prefix or namespace. TextView is called object.</li>
<li>There are three differnet identifier patterns. TfeTextView (camel
case), tfe_text_view (this is used to write functions) and TFE_TEXT_VIEW
(This is used to cast a pointer to point TfeTextView type).</li>
case), tfe_text_view (this is used for functions) and TFE_TEXT_VIEW
(This is used to cast a object to TfeTextView).</li>
<li>First, define TFE_TYPE_TEXT_VIEW macro as tfe_text_view_get_type ().
The name is always (prefix)_TYPE_(object) and the letters are upper
case. And the replacement text is always (prefix)_(object)_get_type ()
and the letters are lower case.</li>
<li>Next, use G_DECLARE_FINAL_TYPE macro. The arguments are the child
object name in camel case, lower case with underscore, prefix (upper
case), object (upper case with underscore) and parent object name (camel
case).</li>
and the letters are lower case. This definition is put before
G_DECLARE_FINAL_TYPE macro.</li>
<li>The arguments of G_DECLARE_FINAL_TYPE macro are the child class name
in camel case, lower case with underscore, prefix (upper case), object
(upper case with underscore) and parent class name (camel case). The
following two C structure is declared in the expansion of the macro.
<ul>
<li><code>typedef struct _TfeTextView TfeTextView</code></li>
<li><code>typedef struct {GtkTextViewClass parent_class; } TfeTextViewClass;</code></li>
</ul></li>
<li>These declaration tells us that TfeTextView and TfeTextViewClass are
C structures. “TfeTextView” has two meanings, class name and C structure
name. The C structure TfeTextView is called object. Similarly,
TfeTextViewClass is called class.</li>
<li>Declare the structure _TfeTextView. The underscore is necessary. The
first member is the parent object. Notice this is not a pointer but the
object itself. The second member and after are members of the child
object. TfeTextView structure has a pointer to a GFile instance as a
member.</li>
<li>Use G_DEFINE_TYPE macro. The arguments are the child object name in
first member is the parent object (C structure). Notice this is not a
pointer but the object itself. The second member and after are members
of the child object. TfeTextView structure has a pointer to a GFile
instance as a member.</li>
<li>G_DEFINE_TYPE macro. The arguments are the child object name in
camel case, lower case with underscore and parent object type
(prefix)_TYPE_(module).</li>
<li>Define instance init function (tfe_text_view_init). Usually you
dont need to do anything.</li>
<li>Define class init function (tfe_text_view_class_init). You dont
need to do anything in this object.</li>
<li>Write function codes you want to add (tfe_text_view_set_file and
tfe_text_view_get_file). <code>tv</code> is a pointer to the TfeTextView
object instance which is a C-structure declared with the tag
_TfeTextView. So, the structure has a member <code>file</code> as a
pointer to a GFile instance. <code>tv-&gt;file = f</code> is an
assignment of <code>f</code> to a member <code>file</code> of the
structure pointed by <code>tv</code>. This is an example how to use the
extended memory in a child widget.</li>
<li>Write a function to create an instance. Its name is
(prefix)_(object)_new. If the parent object function needs parameters,
this function also need them. You sometimes might want to add some
parameters. Its your choice. Use g_object_new function to create the
instance. The arguments are (prefix)_TYPE_(object), a list to initialize
properties and NULL. In this code no property needs to be initialized.
(prefix)_TYPE_(module). This macro is mainly used to register the new
class to the type system. Type system is a base system of GObject. Every
class has its own type. The types of GObject, GtkWidget and TfeTextView
are G_TYPE_OBJECT, GTK_TYPE_WIDGET and TFE_TYPE_TEXT_VIEW respectively.
Such type (for example, TFE_TYPE_TEXT_VIEW) is a macro and it is
expanded to a function (tfe_text_view_get_type()). It returns a integer
which is unique among all GObject system classes.</li>
<li>Instance init function (tfe_text_view_init) is called when the
instance is created. It is the same as a constructor in other object
oriented languages.</li>
<li>Class init function (tfe_text_view_class_init) is called when the
class is created.</li>
<li>Two functions tfe_text_view_set_file and tfe_text_view_get_file are
public functions. Public functions are open and you can call them
anywhere. They are the same as public method in other object oriented
languages. <code>tv</code> is a pointer to the TfeTextView object (C
structure). It has a member <code>file</code> and it is pointed by
<code>tv-&gt;file</code>.</li>
<li>TfeTextView instance creation function is
<code>tfe_text_view_new</code>. Its name is (prefix)_(object)_new. It
uses g_object_new function to create the instance. The arguments are
(prefix)_TYPE_(object), a list to initialize properties and NULL. NULL
is the end mark of the property list. No property is initialized here.
And the return value is casted to GtkWidget.</li>
</ul>
<p>This program is not perfect. It has some problems. It will be
modified later.</p>
<p>This program shows the outline how to define a child class.</p>
<h2 id="close-request-signal">Close-request signal</h2>
<p>Imagine that you are using this editor. First, you run the editor
with arguments. The arguments are filenames. The editor reads the files
and shows the window with the text of files in it. Then you edit the
text. After you finish editing, you exit the editor. The editor updates
files just before the window closes.</p>
<p>GtkWindow emits the “close-request” signal before it closes. We
connect the signal and the handler <code>before_close</code>. A handler
is a C function. When a function is connected to a certain signal, we
call it a handler. The function <code>before_close</code> is invoked
when the signal “close-request” is emitted.</p>
<p>GtkWindow emits the “close-request” signal before it closes. We will
connect the signal and the handler <code>before_close</code>. (A handler
is a C function which is connected to a signal.) The function
<code>before_close</code> is called when the signal “close-request” is
emitted.</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>g_signal_connect <span class="op">(</span>win<span class="op">,</span> <span class="st">&quot;close-request&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>before_close<span class="op">),</span> NULL<span class="op">);</span></span></code></pre></div>
<p>The argument <code>win</code> is a GtkApplicationWindow, in which the
signal “close-request” is defined, and <code>before_close</code> is the
@ -272,58 +272,70 @@ handler. <code>G_CALLBACK</code> cast is necessary for the handler. The
program of <code>before_close</code> is as follows.</p>
<div class="sourceCode" id="cb4"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb4-1"><a href="#cb4-1"></a><span class="dt">static</span> gboolean</span>
<span id="cb4-2"><a href="#cb4-2"></a>before_close <span class="op">(</span>GtkWindow <span class="op">*</span>win<span class="op">,</span> gpointer user_data<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-3"><a href="#cb4-3"></a> GtkWidget <span class="op">*</span>nb <span class="op">=</span> GTK_WIDGET <span class="op">(</span>user_data<span class="op">);</span></span>
<span id="cb4-4"><a href="#cb4-4"></a> GtkWidget <span class="op">*</span>scr<span class="op">;</span></span>
<span id="cb4-5"><a href="#cb4-5"></a> GtkWidget <span class="op">*</span>tv<span class="op">;</span></span>
<span id="cb4-6"><a href="#cb4-6"></a> GFile <span class="op">*</span>file<span class="op">;</span></span>
<span id="cb4-7"><a href="#cb4-7"></a> <span class="dt">char</span> <span class="op">*</span>pathname<span class="op">;</span></span>
<span id="cb4-8"><a href="#cb4-8"></a> GtkTextBuffer <span class="op">*</span>tb<span class="op">;</span></span>
<span id="cb4-9"><a href="#cb4-9"></a> GtkTextIter start_iter<span class="op">;</span></span>
<span id="cb4-10"><a href="#cb4-10"></a> GtkTextIter end_iter<span class="op">;</span></span>
<span id="cb4-11"><a href="#cb4-11"></a> <span class="dt">char</span> <span class="op">*</span>contents<span class="op">;</span></span>
<span id="cb4-12"><a href="#cb4-12"></a> <span class="dt">unsigned</span> <span class="dt">int</span> n<span class="op">;</span></span>
<span id="cb4-13"><a href="#cb4-13"></a> <span class="dt">unsigned</span> <span class="dt">int</span> i<span class="op">;</span></span>
<span id="cb4-14"><a href="#cb4-14"></a></span>
<span id="cb4-15"><a href="#cb4-15"></a> n <span class="op">=</span> gtk_notebook_get_n_pages <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">));</span></span>
<span id="cb4-16"><a href="#cb4-16"></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="cb4-17"><a href="#cb4-17"></a> scr <span class="op">=</span> gtk_notebook_get_nth_page <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">),</span> i<span class="op">);</span></span>
<span id="cb4-18"><a href="#cb4-18"></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="cb4-19"><a href="#cb4-19"></a> file <span class="op">=</span> tfe_text_view_get_file <span class="op">(</span>TFE_TEXT_VIEW <span class="op">(</span>tv<span class="op">));</span></span>
<span id="cb4-20"><a href="#cb4-20"></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="cb4-21"><a href="#cb4-21"></a> gtk_text_buffer_get_bounds <span class="op">(</span>tb<span class="op">,</span> <span class="op">&amp;</span>start_iter<span class="op">,</span> <span class="op">&amp;</span>end_iter<span class="op">);</span></span>
<span id="cb4-22"><a href="#cb4-22"></a> contents <span class="op">=</span> gtk_text_buffer_get_text <span class="op">(</span>tb<span class="op">,</span> <span class="op">&amp;</span>start_iter<span class="op">,</span> <span class="op">&amp;</span>end_iter<span class="op">,</span> FALSE<span class="op">);</span></span>
<span id="cb4-23"><a href="#cb4-23"></a> <span class="cf">if</span> <span class="op">(!</span> g_file_replace_contents <span class="op">(</span>file<span class="op">,</span> contents<span class="op">,</span> strlen <span class="op">(</span>contents<span class="op">),</span> NULL<span class="op">,</span> TRUE<span class="op">,</span> G_FILE_CREATE_NONE<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">))</span> <span class="op">{</span></span>
<span id="cb4-24"><a href="#cb4-24"></a> pathname <span class="op">=</span> g_file_get_path <span class="op">(</span>file<span class="op">);</span></span>
<span id="cb4-25"><a href="#cb4-25"></a> g_print <span class="op">(</span><span class="st">&quot;ERROR : Can&#39;t save %s.&quot;</span><span class="op">,</span> pathname<span class="op">);</span></span>
<span id="cb4-26"><a href="#cb4-26"></a> g_free <span class="op">(</span>pathname<span class="op">);</span></span>
<span id="cb4-27"><a href="#cb4-27"></a> <span class="op">}</span></span>
<span id="cb4-28"><a href="#cb4-28"></a> g_free <span class="op">(</span>contents<span class="op">);</span></span>
<span id="cb4-29"><a href="#cb4-29"></a> <span class="op">}</span></span>
<span id="cb4-30"><a href="#cb4-30"></a> <span class="cf">return</span> FALSE<span class="op">;</span></span>
<span id="cb4-31"><a href="#cb4-31"></a><span class="op">}</span></span></code></pre></div>
<span id="cb4-2"><a href="#cb4-2"></a>before_close <span class="op">(</span>GtkWindow <span class="op">*</span>win<span class="op">,</span> GtkWidget <span class="op">*</span>nb<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-3"><a href="#cb4-3"></a> GtkWidget <span class="op">*</span>scr<span class="op">;</span></span>
<span id="cb4-4"><a href="#cb4-4"></a> GtkWidget <span class="op">*</span>tv<span class="op">;</span></span>
<span id="cb4-5"><a href="#cb4-5"></a> GFile <span class="op">*</span>file<span class="op">;</span></span>
<span id="cb4-6"><a href="#cb4-6"></a> <span class="dt">char</span> <span class="op">*</span>pathname<span class="op">;</span></span>
<span id="cb4-7"><a href="#cb4-7"></a> GtkTextBuffer <span class="op">*</span>tb<span class="op">;</span></span>
<span id="cb4-8"><a href="#cb4-8"></a> GtkTextIter start_iter<span class="op">;</span></span>
<span id="cb4-9"><a href="#cb4-9"></a> GtkTextIter end_iter<span class="op">;</span></span>
<span id="cb4-10"><a href="#cb4-10"></a> <span class="dt">char</span> <span class="op">*</span>contents<span class="op">;</span></span>
<span id="cb4-11"><a href="#cb4-11"></a> <span class="dt">unsigned</span> <span class="dt">int</span> n<span class="op">;</span></span>
<span id="cb4-12"><a href="#cb4-12"></a> <span class="dt">unsigned</span> <span class="dt">int</span> i<span class="op">;</span></span>
<span id="cb4-13"><a href="#cb4-13"></a></span>
<span id="cb4-14"><a href="#cb4-14"></a> n <span class="op">=</span> gtk_notebook_get_n_pages <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">));</span></span>
<span id="cb4-15"><a href="#cb4-15"></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="cb4-16"><a href="#cb4-16"></a> scr <span class="op">=</span> gtk_notebook_get_nth_page <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">),</span> i<span class="op">);</span></span>
<span id="cb4-17"><a href="#cb4-17"></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="cb4-18"><a href="#cb4-18"></a> file <span class="op">=</span> tfe_text_view_get_file <span class="op">(</span>TFE_TEXT_VIEW <span class="op">(</span>tv<span class="op">));</span></span>
<span id="cb4-19"><a href="#cb4-19"></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="cb4-20"><a href="#cb4-20"></a> gtk_text_buffer_get_bounds <span class="op">(</span>tb<span class="op">,</span> <span class="op">&amp;</span>start_iter<span class="op">,</span> <span class="op">&amp;</span>end_iter<span class="op">);</span></span>
<span id="cb4-21"><a href="#cb4-21"></a> contents <span class="op">=</span> gtk_text_buffer_get_text <span class="op">(</span>tb<span class="op">,</span> <span class="op">&amp;</span>start_iter<span class="op">,</span> <span class="op">&amp;</span>end_iter<span class="op">,</span> FALSE<span class="op">);</span></span>
<span id="cb4-22"><a href="#cb4-22"></a> <span class="cf">if</span> <span class="op">(!</span> g_file_replace_contents <span class="op">(</span>file<span class="op">,</span> contents<span class="op">,</span> strlen <span class="op">(</span>contents<span class="op">),</span> NULL<span class="op">,</span> TRUE<span class="op">,</span> G_FILE_CREATE_NONE<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">))</span> <span class="op">{</span></span>
<span id="cb4-23"><a href="#cb4-23"></a> <span class="cf">if</span> <span class="op">((</span>pathname <span class="op">=</span> g_file_get_path <span class="op">(</span>file<span class="op">))</span> <span class="op">!=</span> NULL<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-24"><a href="#cb4-24"></a> g_printerr <span class="op">(</span><span class="st">&quot;Can&#39;t save %s.&quot;</span><span class="op">,</span> pathname<span class="op">);</span></span>
<span id="cb4-25"><a href="#cb4-25"></a> g_free <span class="op">(</span>pathname<span class="op">);</span></span>
<span id="cb4-26"><a href="#cb4-26"></a> <span class="op">}</span> <span class="cf">else</span></span>
<span id="cb4-27"><a href="#cb4-27"></a> g_printerr <span class="op">(</span><span class="st">&quot;Pathname not exist.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb4-28"><a href="#cb4-28"></a> <span class="op">}</span></span>
<span id="cb4-29"><a href="#cb4-29"></a> g_free <span class="op">(</span>contents<span class="op">);</span></span>
<span id="cb4-30"><a href="#cb4-30"></a> g_object_unref <span class="op">(</span>file<span class="op">);</span></span>
<span id="cb4-31"><a href="#cb4-31"></a> <span class="op">}</span></span>
<span id="cb4-32"><a href="#cb4-32"></a> <span class="cf">return</span> FALSE<span class="op">;</span></span>
<span id="cb4-33"><a href="#cb4-33"></a><span class="op">}</span></span></code></pre></div>
<p>The numbers on the left of items are line numbers in the source
code.</p>
<ul>
<li>15: Gets the number of pages <code>nb</code> has.</li>
<li>16-29: For loop with regard to the index to each pages.</li>
<li>17-19: Gets GtkScrolledWindow, TfeTextView and a pointer to GFile.
The pointer was stored when <code>app_open</code> handler had run. It
will be shown later.</li>
<li>20-22: Gets GtkTextBuffer and contents. <code>start_iter</code> and
<code>end_iter</code> are iterators of the buffer. I dont want to
explain them now because it would take a lot of time. Just remember
these lines for the present.</li>
<li>23-27: Writes the contents to the file. If it fails, it outputs an
error message.</li>
<li>28: Frees <code>contents</code>.</li>
<li>14: The number of pages of <code>nb</code> is assigned to
<code>n</code>.</li>
<li>15-31: For loop with regard to the index to each pages.</li>
<li>16-18: <code>scr</code>, <code>tv</code> and <code>file</code> is
assigned pointers to the GtkScrolledWindow, TfeTextView and GFile. The
GFile of TfeTextView was stored when <code>app_open</code> handler was
called. It will be shown later.</li>
<li>19-21: <code>tb</code> is assigned the GtkTextBuffer of the
TfeTextView. The buffer is accessed with iterators. Iterators points
somewhere in the buffer. The function
<code>gtk_text_buffer_get_bounds</code> assigns the start and end of the
buffer to <code>start_iter</code> and <code>end_iter</code>
respectively. Then the function <code>gtk_text_buffer_get_text</code>
returns the text between <code>start_iter</code> and
<code>end_iter</code>, which is the whole text in the buffer.</li>
<li>22-28: The text is saved to the file. If it fails, error messages
are displayed.</li>
<li>29: <code>contents</code> are freed.</li>
<li>30: GFile is useless. <code>g_object_unref</code> decreases the
reference count of the GFile. Reference count will be explained in the
later section. The reference count will be zero in this program and the
GFile instance will destroy itself.</li>
</ul>
<h2 id="source-code-of-tfe1.c">Source code of tfe1.c</h2>
<p>The following is the complete source code of <code>tfe1.c</code>.</p>
<p>The following is the whole source code of <code>tfe1.c</code>.</p>
<div class="sourceCode" id="cb5"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb5-1"><a href="#cb5-1"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<span id="cb5-2"><a href="#cb5-2"></a></span>
<span id="cb5-3"><a href="#cb5-3"></a><span class="co">/* Define TfeTextView Widget which is the child object of GtkTextView */</span></span>
<span id="cb5-3"><a href="#cb5-3"></a><span class="co">/* Define TfeTextView Widget which is the child class of GtkTextView */</span></span>
<span id="cb5-4"><a href="#cb5-4"></a></span>
<span id="cb5-5"><a href="#cb5-5"></a><span class="pp">#define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()</span></span>
<span id="cb5-6"><a href="#cb5-6"></a>G_DECLARE_FINAL_TYPE <span class="op">(</span>TfeTextView<span class="op">,</span> tfe_text_view<span class="op">,</span> TFE<span class="op">,</span> TEXT_VIEW<span class="op">,</span> GtkTextView<span class="op">)</span></span>
@ -338,32 +350,32 @@ class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span i
<span id="cb5-15"><a href="#cb5-15"></a></span>
<span id="cb5-16"><a href="#cb5-16"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb5-17"><a href="#cb5-17"></a>tfe_text_view_init <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-18"><a href="#cb5-18"></a><span class="op">}</span></span>
<span id="cb5-19"><a href="#cb5-19"></a></span>
<span id="cb5-20"><a href="#cb5-20"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb5-21"><a href="#cb5-21"></a>tfe_text_view_class_init <span class="op">(</span>TfeTextViewClass <span class="op">*</span>class<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-22"><a href="#cb5-22"></a><span class="op">}</span></span>
<span id="cb5-23"><a href="#cb5-23"></a></span>
<span id="cb5-24"><a href="#cb5-24"></a><span class="dt">void</span></span>
<span id="cb5-25"><a href="#cb5-25"></a>tfe_text_view_set_file <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">,</span> GFile <span class="op">*</span>f<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-26"><a href="#cb5-26"></a> tv <span class="op">-&gt;</span> file <span class="op">=</span> f<span class="op">;</span></span>
<span id="cb5-27"><a href="#cb5-27"></a><span class="op">}</span></span>
<span id="cb5-28"><a href="#cb5-28"></a></span>
<span id="cb5-29"><a href="#cb5-29"></a>GFile <span class="op">*</span></span>
<span id="cb5-30"><a href="#cb5-30"></a>tfe_text_view_get_file <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-31"><a href="#cb5-31"></a> <span class="cf">return</span> tv <span class="op">-&gt;</span> file<span class="op">;</span></span>
<span id="cb5-32"><a href="#cb5-32"></a><span class="op">}</span></span>
<span id="cb5-33"><a href="#cb5-33"></a></span>
<span id="cb5-34"><a href="#cb5-34"></a>GtkWidget <span class="op">*</span></span>
<span id="cb5-35"><a href="#cb5-35"></a>tfe_text_view_new <span class="op">(</span><span class="dt">void</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-36"><a href="#cb5-36"></a> <span class="cf">return</span> GTK_WIDGET <span class="op">(</span>g_object_new <span class="op">(</span>TFE_TYPE_TEXT_VIEW<span class="op">,</span> NULL<span class="op">));</span></span>
<span id="cb5-37"><a href="#cb5-37"></a><span class="op">}</span></span>
<span id="cb5-38"><a href="#cb5-38"></a></span>
<span id="cb5-39"><a href="#cb5-39"></a><span class="co">/* ---------- end of the definition of TfeTextView ---------- */</span></span>
<span id="cb5-40"><a href="#cb5-40"></a></span>
<span id="cb5-41"><a href="#cb5-41"></a><span class="dt">static</span> gboolean</span>
<span id="cb5-42"><a href="#cb5-42"></a>before_close <span class="op">(</span>GtkWindow <span class="op">*</span>win<span class="op">,</span> gpointer user_data<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-43"><a href="#cb5-43"></a> GtkWidget <span class="op">*</span>nb <span class="op">=</span> GTK_WIDGET <span class="op">(</span>user_data<span class="op">);</span></span>
<span id="cb5-18"><a href="#cb5-18"></a> tv<span class="op">-&gt;</span>file <span class="op">=</span> NULL<span class="op">;</span></span>
<span id="cb5-19"><a href="#cb5-19"></a><span class="op">}</span></span>
<span id="cb5-20"><a href="#cb5-20"></a></span>
<span id="cb5-21"><a href="#cb5-21"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb5-22"><a href="#cb5-22"></a>tfe_text_view_class_init <span class="op">(</span>TfeTextViewClass <span class="op">*</span>class<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-23"><a href="#cb5-23"></a><span class="op">}</span></span>
<span id="cb5-24"><a href="#cb5-24"></a></span>
<span id="cb5-25"><a href="#cb5-25"></a><span class="dt">void</span></span>
<span id="cb5-26"><a href="#cb5-26"></a>tfe_text_view_set_file <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">,</span> GFile <span class="op">*</span>f<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-27"><a href="#cb5-27"></a> tv<span class="op">-&gt;</span>file <span class="op">=</span> f<span class="op">;</span></span>
<span id="cb5-28"><a href="#cb5-28"></a><span class="op">}</span></span>
<span id="cb5-29"><a href="#cb5-29"></a></span>
<span id="cb5-30"><a href="#cb5-30"></a>GFile <span class="op">*</span></span>
<span id="cb5-31"><a href="#cb5-31"></a>tfe_text_view_get_file <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-32"><a href="#cb5-32"></a> <span class="cf">return</span> tv <span class="op">-&gt;</span> file<span class="op">;</span></span>
<span id="cb5-33"><a href="#cb5-33"></a><span class="op">}</span></span>
<span id="cb5-34"><a href="#cb5-34"></a></span>
<span id="cb5-35"><a href="#cb5-35"></a>GtkWidget <span class="op">*</span></span>
<span id="cb5-36"><a href="#cb5-36"></a>tfe_text_view_new <span class="op">(</span><span class="dt">void</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-37"><a href="#cb5-37"></a> <span class="cf">return</span> GTK_WIDGET <span class="op">(</span>g_object_new <span class="op">(</span>TFE_TYPE_TEXT_VIEW<span class="op">,</span> NULL<span class="op">));</span></span>
<span id="cb5-38"><a href="#cb5-38"></a><span class="op">}</span></span>
<span id="cb5-39"><a href="#cb5-39"></a></span>
<span id="cb5-40"><a href="#cb5-40"></a><span class="co">/* ---------- end of the definition of TfeTextView ---------- */</span></span>
<span id="cb5-41"><a href="#cb5-41"></a></span>
<span id="cb5-42"><a href="#cb5-42"></a><span class="dt">static</span> gboolean</span>
<span id="cb5-43"><a href="#cb5-43"></a>before_close <span class="op">(</span>GtkWindow <span class="op">*</span>win<span class="op">,</span> GtkWidget <span class="op">*</span>nb<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-44"><a href="#cb5-44"></a> GtkWidget <span class="op">*</span>scr<span class="op">;</span></span>
<span id="cb5-45"><a href="#cb5-45"></a> GtkWidget <span class="op">*</span>tv<span class="op">;</span></span>
<span id="cb5-46"><a href="#cb5-46"></a> GFile <span class="op">*</span>file<span class="op">;</span></span>
@ -384,96 +396,104 @@ class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span i
<span id="cb5-61"><a href="#cb5-61"></a> gtk_text_buffer_get_bounds <span class="op">(</span>tb<span class="op">,</span> <span class="op">&amp;</span>start_iter<span class="op">,</span> <span class="op">&amp;</span>end_iter<span class="op">);</span></span>
<span id="cb5-62"><a href="#cb5-62"></a> contents <span class="op">=</span> gtk_text_buffer_get_text <span class="op">(</span>tb<span class="op">,</span> <span class="op">&amp;</span>start_iter<span class="op">,</span> <span class="op">&amp;</span>end_iter<span class="op">,</span> FALSE<span class="op">);</span></span>
<span id="cb5-63"><a href="#cb5-63"></a> <span class="cf">if</span> <span class="op">(!</span> g_file_replace_contents <span class="op">(</span>file<span class="op">,</span> contents<span class="op">,</span> strlen <span class="op">(</span>contents<span class="op">),</span> NULL<span class="op">,</span> TRUE<span class="op">,</span> G_FILE_CREATE_NONE<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">))</span> <span class="op">{</span></span>
<span id="cb5-64"><a href="#cb5-64"></a> pathname <span class="op">=</span> g_file_get_path <span class="op">(</span>file<span class="op">);</span></span>
<span id="cb5-65"><a href="#cb5-65"></a> g_print <span class="op">(</span><span class="st">&quot;ERROR : Can&#39;t save %s.&quot;</span><span class="op">,</span> pathname<span class="op">);</span></span>
<span id="cb5-66"><a href="#cb5-66"></a> g_free <span class="op">(</span>pathname<span class="op">);</span></span>
<span id="cb5-67"><a href="#cb5-67"></a> <span class="op">}</span></span>
<span id="cb5-68"><a href="#cb5-68"></a> g_free <span class="op">(</span>contents<span class="op">);</span></span>
<span id="cb5-69"><a href="#cb5-69"></a> <span class="op">}</span></span>
<span id="cb5-70"><a href="#cb5-70"></a> <span class="cf">return</span> FALSE<span class="op">;</span></span>
<span id="cb5-71"><a href="#cb5-71"></a><span class="op">}</span></span>
<span id="cb5-72"><a href="#cb5-72"></a></span>
<span id="cb5-73"><a href="#cb5-73"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb5-74"><a href="#cb5-74"></a>app_activate <span class="op">(</span>GApplication <span class="op">*</span>app<span class="op">,</span> gpointer user_data<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-75"><a href="#cb5-75"></a> g_print <span class="op">(</span><span class="st">&quot;You need to give filenames as arguments.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb5-76"><a href="#cb5-76"></a><span class="op">}</span></span>
<span id="cb5-77"><a href="#cb5-77"></a></span>
<span id="cb5-78"><a href="#cb5-78"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb5-79"><a href="#cb5-79"></a>app_open <span class="op">(</span>GApplication <span class="op">*</span>app<span class="op">,</span> GFile <span class="op">**</span> files<span class="op">,</span> gint n_files<span class="op">,</span> gchar <span class="op">*</span>hint<span class="op">,</span> gpointer user_data<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-80"><a href="#cb5-80"></a> GtkWidget <span class="op">*</span>win<span class="op">;</span></span>
<span id="cb5-81"><a href="#cb5-81"></a> GtkWidget <span class="op">*</span>nb<span class="op">;</span></span>
<span id="cb5-82"><a href="#cb5-82"></a> GtkWidget <span class="op">*</span>lab<span class="op">;</span></span>
<span id="cb5-83"><a href="#cb5-83"></a> GtkNotebookPage <span class="op">*</span>nbp<span class="op">;</span></span>
<span id="cb5-84"><a href="#cb5-84"></a> GtkWidget <span class="op">*</span>scr<span class="op">;</span></span>
<span id="cb5-85"><a href="#cb5-85"></a> GtkWidget <span class="op">*</span>tv<span class="op">;</span></span>
<span id="cb5-86"><a href="#cb5-86"></a> GtkTextBuffer <span class="op">*</span>tb<span class="op">;</span></span>
<span id="cb5-87"><a href="#cb5-87"></a> <span class="dt">char</span> <span class="op">*</span>contents<span class="op">;</span></span>
<span id="cb5-88"><a href="#cb5-88"></a> gsize length<span class="op">;</span></span>
<span id="cb5-89"><a href="#cb5-89"></a> <span class="dt">char</span> <span class="op">*</span>filename<span class="op">;</span></span>
<span id="cb5-90"><a href="#cb5-90"></a> <span class="dt">int</span> i<span class="op">;</span></span>
<span id="cb5-91"><a href="#cb5-91"></a></span>
<span id="cb5-92"><a href="#cb5-92"></a> win <span class="op">=</span> gtk_application_window_new <span class="op">(</span>GTK_APPLICATION <span class="op">(</span>app<span class="op">));</span></span>
<span id="cb5-93"><a href="#cb5-93"></a> gtk_window_set_title <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> <span class="st">&quot;file editor&quot;</span><span class="op">);</span></span>
<span id="cb5-94"><a href="#cb5-94"></a> gtk_window_maximize <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">));</span></span>
<span id="cb5-95"><a href="#cb5-95"></a></span>
<span id="cb5-96"><a href="#cb5-96"></a> nb <span class="op">=</span> gtk_notebook_new <span class="op">();</span></span>
<span id="cb5-97"><a href="#cb5-97"></a> gtk_window_set_child <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> nb<span class="op">);</span></span>
<span id="cb5-64"><a href="#cb5-64"></a> <span class="cf">if</span> <span class="op">((</span>pathname <span class="op">=</span> g_file_get_path <span class="op">(</span>file<span class="op">))</span> <span class="op">!=</span> NULL<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-65"><a href="#cb5-65"></a> g_printerr <span class="op">(</span><span class="st">&quot;Can&#39;t save %s.&quot;</span><span class="op">,</span> pathname<span class="op">);</span></span>
<span id="cb5-66"><a href="#cb5-66"></a> g_free <span class="op">(</span>pathname<span class="op">);</span></span>
<span id="cb5-67"><a href="#cb5-67"></a> <span class="op">}</span> <span class="cf">else</span></span>
<span id="cb5-68"><a href="#cb5-68"></a> g_printerr <span class="op">(</span><span class="st">&quot;Pathname not exist.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb5-69"><a href="#cb5-69"></a> <span class="op">}</span></span>
<span id="cb5-70"><a href="#cb5-70"></a> g_free <span class="op">(</span>contents<span class="op">);</span></span>
<span id="cb5-71"><a href="#cb5-71"></a> g_object_unref <span class="op">(</span>file<span class="op">);</span></span>
<span id="cb5-72"><a href="#cb5-72"></a> <span class="op">}</span></span>
<span id="cb5-73"><a href="#cb5-73"></a> <span class="cf">return</span> FALSE<span class="op">;</span></span>
<span id="cb5-74"><a href="#cb5-74"></a><span class="op">}</span></span>
<span id="cb5-75"><a href="#cb5-75"></a></span>
<span id="cb5-76"><a href="#cb5-76"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb5-77"><a href="#cb5-77"></a>app_activate <span class="op">(</span>GApplication <span class="op">*</span>app<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-78"><a href="#cb5-78"></a> g_print <span class="op">(</span><span class="st">&quot;You need to give filenames as arguments.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb5-79"><a href="#cb5-79"></a><span class="op">}</span></span>
<span id="cb5-80"><a href="#cb5-80"></a></span>
<span id="cb5-81"><a href="#cb5-81"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb5-82"><a href="#cb5-82"></a>app_open <span class="op">(</span>GApplication <span class="op">*</span>app<span class="op">,</span> GFile <span class="op">**</span> files<span class="op">,</span> gint n_files<span class="op">,</span> gchar <span class="op">*</span>hint<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-83"><a href="#cb5-83"></a> GtkWidget <span class="op">*</span>win<span class="op">;</span></span>
<span id="cb5-84"><a href="#cb5-84"></a> GtkWidget <span class="op">*</span>nb<span class="op">;</span></span>
<span id="cb5-85"><a href="#cb5-85"></a> GtkWidget <span class="op">*</span>lab<span class="op">;</span></span>
<span id="cb5-86"><a href="#cb5-86"></a> GtkNotebookPage <span class="op">*</span>nbp<span class="op">;</span></span>
<span id="cb5-87"><a href="#cb5-87"></a> GtkWidget <span class="op">*</span>scr<span class="op">;</span></span>
<span id="cb5-88"><a href="#cb5-88"></a> GtkWidget <span class="op">*</span>tv<span class="op">;</span></span>
<span id="cb5-89"><a href="#cb5-89"></a> GtkTextBuffer <span class="op">*</span>tb<span class="op">;</span></span>
<span id="cb5-90"><a href="#cb5-90"></a> <span class="dt">char</span> <span class="op">*</span>contents<span class="op">;</span></span>
<span id="cb5-91"><a href="#cb5-91"></a> gsize length<span class="op">;</span></span>
<span id="cb5-92"><a href="#cb5-92"></a> <span class="dt">char</span> <span class="op">*</span>filename<span class="op">;</span></span>
<span id="cb5-93"><a href="#cb5-93"></a> <span class="dt">int</span> i<span class="op">;</span></span>
<span id="cb5-94"><a href="#cb5-94"></a></span>
<span id="cb5-95"><a href="#cb5-95"></a> win <span class="op">=</span> gtk_application_window_new <span class="op">(</span>GTK_APPLICATION <span class="op">(</span>app<span class="op">));</span></span>
<span id="cb5-96"><a href="#cb5-96"></a> gtk_window_set_title <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> <span class="st">&quot;file editor&quot;</span><span class="op">);</span></span>
<span id="cb5-97"><a href="#cb5-97"></a> gtk_window_set_default_size <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> <span class="dv">600</span><span class="op">,</span> <span class="dv">400</span><span class="op">);</span></span>
<span id="cb5-98"><a href="#cb5-98"></a></span>
<span id="cb5-99"><a href="#cb5-99"></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_files<span class="op">;</span> i<span class="op">++)</span> <span class="op">{</span></span>
<span id="cb5-100"><a href="#cb5-100"></a> <span class="cf">if</span> <span class="op">(</span>g_file_load_contents <span class="op">(</span>files<span class="op">[</span>i<span class="op">],</span> NULL<span class="op">,</span> <span class="op">&amp;</span>contents<span class="op">,</span> <span class="op">&amp;</span>length<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">))</span> <span class="op">{</span></span>
<span id="cb5-101"><a href="#cb5-101"></a> scr <span class="op">=</span> gtk_scrolled_window_new <span class="op">();</span></span>
<span id="cb5-102"><a href="#cb5-102"></a> tv <span class="op">=</span> tfe_text_view_new <span class="op">();</span></span>
<span id="cb5-103"><a href="#cb5-103"></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="cb5-104"><a href="#cb5-104"></a> gtk_text_view_set_wrap_mode <span class="op">(</span>GTK_TEXT_VIEW <span class="op">(</span>tv<span class="op">),</span> GTK_WRAP_WORD_CHAR<span class="op">);</span></span>
<span id="cb5-105"><a href="#cb5-105"></a> gtk_scrolled_window_set_child <span class="op">(</span>GTK_SCROLLED_WINDOW <span class="op">(</span>scr<span class="op">),</span> tv<span class="op">);</span></span>
<span id="cb5-106"><a href="#cb5-106"></a></span>
<span id="cb5-107"><a href="#cb5-107"></a> tfe_text_view_set_file <span class="op">(</span>TFE_TEXT_VIEW <span class="op">(</span>tv<span class="op">),</span> g_file_dup <span class="op">(</span>files<span class="op">[</span>i<span class="op">]));</span></span>
<span id="cb5-108"><a href="#cb5-108"></a> gtk_text_buffer_set_text <span class="op">(</span>tb<span class="op">,</span> contents<span class="op">,</span> length<span class="op">);</span></span>
<span id="cb5-109"><a href="#cb5-109"></a> g_free <span class="op">(</span>contents<span class="op">);</span></span>
<span id="cb5-110"><a href="#cb5-110"></a> filename <span class="op">=</span> g_file_get_basename <span class="op">(</span>files<span class="op">[</span>i<span class="op">]);</span></span>
<span id="cb5-111"><a href="#cb5-111"></a> lab <span class="op">=</span> gtk_label_new <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb5-112"><a href="#cb5-112"></a> gtk_notebook_append_page <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">),</span> scr<span class="op">,</span> lab<span class="op">);</span></span>
<span id="cb5-113"><a href="#cb5-113"></a> nbp <span class="op">=</span> gtk_notebook_get_page <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">),</span> scr<span class="op">);</span></span>
<span id="cb5-114"><a href="#cb5-114"></a> g_object_set <span class="op">(</span>nbp<span class="op">,</span> <span class="st">&quot;tab-expand&quot;</span><span class="op">,</span> TRUE<span class="op">,</span> NULL<span class="op">);</span></span>
<span id="cb5-115"><a href="#cb5-115"></a> g_free <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb5-116"><a href="#cb5-116"></a> <span class="op">}</span> <span class="cf">else</span> <span class="cf">if</span> <span class="op">((</span>filename <span class="op">=</span> g_file_get_path <span class="op">(</span>files<span class="op">[</span>i<span class="op">]))</span> <span class="op">!=</span> NULL<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-117"><a href="#cb5-117"></a> g_print <span class="op">(</span><span class="st">&quot;No such file: %s.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">,</span> filename<span class="op">);</span></span>
<span id="cb5-118"><a href="#cb5-118"></a> g_free <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb5-119"><a href="#cb5-119"></a> <span class="op">}</span> <span class="cf">else</span></span>
<span id="cb5-120"><a href="#cb5-120"></a> g_print <span class="op">(</span><span class="st">&quot;No valid file is given</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb5-121"><a href="#cb5-121"></a> <span class="op">}</span></span>
<span id="cb5-122"><a href="#cb5-122"></a> <span class="cf">if</span> <span class="op">(</span>gtk_notebook_get_n_pages <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">))</span> <span class="op">&gt;</span> <span class="dv">0</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-123"><a href="#cb5-123"></a> g_signal_connect <span class="op">(</span>win<span class="op">,</span> <span class="st">&quot;close-request&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>before_close<span class="op">),</span> nb<span class="op">);</span></span>
<span id="cb5-124"><a href="#cb5-124"></a> gtk_widget_show <span class="op">(</span>win<span class="op">);</span></span>
<span id="cb5-125"><a href="#cb5-125"></a> <span class="op">}</span> <span class="cf">else</span></span>
<span id="cb5-126"><a href="#cb5-126"></a> gtk_window_destroy <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">));</span></span>
<span id="cb5-127"><a href="#cb5-127"></a><span class="op">}</span></span>
<span id="cb5-128"><a href="#cb5-128"></a></span>
<span id="cb5-129"><a href="#cb5-129"></a><span class="dt">int</span></span>
<span id="cb5-130"><a href="#cb5-130"></a>main <span class="op">(</span><span class="dt">int</span> argc<span class="op">,</span> <span class="dt">char</span> <span class="op">**</span>argv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-131"><a href="#cb5-131"></a> GtkApplication <span class="op">*</span>app<span class="op">;</span></span>
<span id="cb5-132"><a href="#cb5-132"></a> <span class="dt">int</span> stat<span class="op">;</span></span>
<span id="cb5-133"><a href="#cb5-133"></a></span>
<span id="cb5-134"><a href="#cb5-134"></a> app <span class="op">=</span> gtk_application_new <span class="op">(</span><span class="st">&quot;com.github.ToshioCP.tfe1&quot;</span><span class="op">,</span> G_APPLICATION_HANDLES_OPEN<span class="op">);</span></span>
<span id="cb5-135"><a href="#cb5-135"></a> g_signal_connect <span class="op">(</span>app<span class="op">,</span> <span class="st">&quot;activate&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>app_activate<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb5-136"><a href="#cb5-136"></a> g_signal_connect <span class="op">(</span>app<span class="op">,</span> <span class="st">&quot;open&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>app_open<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb5-137"><a href="#cb5-137"></a> stat <span class="op">=</span>g_application_run <span class="op">(</span>G_APPLICATION <span class="op">(</span>app<span class="op">),</span> argc<span class="op">,</span> argv<span class="op">);</span></span>
<span id="cb5-138"><a href="#cb5-138"></a> g_object_unref <span class="op">(</span>app<span class="op">);</span></span>
<span id="cb5-139"><a href="#cb5-139"></a> <span class="cf">return</span> stat<span class="op">;</span></span>
<span id="cb5-140"><a href="#cb5-140"></a><span class="op">}</span></span></code></pre></div>
<span id="cb5-99"><a href="#cb5-99"></a> nb <span class="op">=</span> gtk_notebook_new <span class="op">();</span></span>
<span id="cb5-100"><a href="#cb5-100"></a> gtk_window_set_child <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> nb<span class="op">);</span></span>
<span id="cb5-101"><a href="#cb5-101"></a></span>
<span id="cb5-102"><a href="#cb5-102"></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_files<span class="op">;</span> i<span class="op">++)</span> <span class="op">{</span></span>
<span id="cb5-103"><a href="#cb5-103"></a> <span class="cf">if</span> <span class="op">(</span>g_file_load_contents <span class="op">(</span>files<span class="op">[</span>i<span class="op">],</span> NULL<span class="op">,</span> <span class="op">&amp;</span>contents<span class="op">,</span> <span class="op">&amp;</span>length<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">))</span> <span class="op">{</span></span>
<span id="cb5-104"><a href="#cb5-104"></a> scr <span class="op">=</span> gtk_scrolled_window_new <span class="op">();</span></span>
<span id="cb5-105"><a href="#cb5-105"></a> tv <span class="op">=</span> tfe_text_view_new <span class="op">();</span></span>
<span id="cb5-106"><a href="#cb5-106"></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="cb5-107"><a href="#cb5-107"></a> gtk_text_view_set_wrap_mode <span class="op">(</span>GTK_TEXT_VIEW <span class="op">(</span>tv<span class="op">),</span> GTK_WRAP_WORD_CHAR<span class="op">);</span></span>
<span id="cb5-108"><a href="#cb5-108"></a> gtk_scrolled_window_set_child <span class="op">(</span>GTK_SCROLLED_WINDOW <span class="op">(</span>scr<span class="op">),</span> tv<span class="op">);</span></span>
<span id="cb5-109"><a href="#cb5-109"></a></span>
<span id="cb5-110"><a href="#cb5-110"></a> tfe_text_view_set_file <span class="op">(</span>TFE_TEXT_VIEW <span class="op">(</span>tv<span class="op">),</span> g_file_dup <span class="op">(</span>files<span class="op">[</span>i<span class="op">]));</span></span>
<span id="cb5-111"><a href="#cb5-111"></a> gtk_text_buffer_set_text <span class="op">(</span>tb<span class="op">,</span> contents<span class="op">,</span> length<span class="op">);</span></span>
<span id="cb5-112"><a href="#cb5-112"></a> g_free <span class="op">(</span>contents<span class="op">);</span></span>
<span id="cb5-113"><a href="#cb5-113"></a> filename <span class="op">=</span> g_file_get_basename <span class="op">(</span>files<span class="op">[</span>i<span class="op">]);</span></span>
<span id="cb5-114"><a href="#cb5-114"></a> lab <span class="op">=</span> gtk_label_new <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb5-115"><a href="#cb5-115"></a> gtk_notebook_append_page <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">),</span> scr<span class="op">,</span> lab<span class="op">);</span></span>
<span id="cb5-116"><a href="#cb5-116"></a> nbp <span class="op">=</span> gtk_notebook_get_page <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">),</span> scr<span class="op">);</span></span>
<span id="cb5-117"><a href="#cb5-117"></a> g_object_set <span class="op">(</span>nbp<span class="op">,</span> <span class="st">&quot;tab-expand&quot;</span><span class="op">,</span> TRUE<span class="op">,</span> NULL<span class="op">);</span></span>
<span id="cb5-118"><a href="#cb5-118"></a> g_free <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb5-119"><a href="#cb5-119"></a> <span class="op">}</span> <span class="cf">else</span> <span class="cf">if</span> <span class="op">((</span>filename <span class="op">=</span> g_file_get_path <span class="op">(</span>files<span class="op">[</span>i<span class="op">]))</span> <span class="op">!=</span> NULL<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-120"><a href="#cb5-120"></a> g_print <span class="op">(</span><span class="st">&quot;No such file: %s.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">,</span> filename<span class="op">);</span></span>
<span id="cb5-121"><a href="#cb5-121"></a> g_free <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb5-122"><a href="#cb5-122"></a> <span class="op">}</span> <span class="cf">else</span></span>
<span id="cb5-123"><a href="#cb5-123"></a> g_print <span class="op">(</span><span class="st">&quot;No valid file is given</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb5-124"><a href="#cb5-124"></a> <span class="op">}</span></span>
<span id="cb5-125"><a href="#cb5-125"></a> <span class="cf">if</span> <span class="op">(</span>gtk_notebook_get_n_pages <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">))</span> <span class="op">&gt;</span> <span class="dv">0</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-126"><a href="#cb5-126"></a> g_signal_connect <span class="op">(</span>win<span class="op">,</span> <span class="st">&quot;close-request&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>before_close<span class="op">),</span> nb<span class="op">);</span></span>
<span id="cb5-127"><a href="#cb5-127"></a> gtk_widget_show <span class="op">(</span>win<span class="op">);</span></span>
<span id="cb5-128"><a href="#cb5-128"></a> <span class="op">}</span> <span class="cf">else</span></span>
<span id="cb5-129"><a href="#cb5-129"></a> gtk_window_destroy <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">));</span></span>
<span id="cb5-130"><a href="#cb5-130"></a><span class="op">}</span></span>
<span id="cb5-131"><a href="#cb5-131"></a></span>
<span id="cb5-132"><a href="#cb5-132"></a><span class="dt">int</span></span>
<span id="cb5-133"><a href="#cb5-133"></a>main <span class="op">(</span><span class="dt">int</span> argc<span class="op">,</span> <span class="dt">char</span> <span class="op">**</span>argv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-134"><a href="#cb5-134"></a> GtkApplication <span class="op">*</span>app<span class="op">;</span></span>
<span id="cb5-135"><a href="#cb5-135"></a> <span class="dt">int</span> stat<span class="op">;</span></span>
<span id="cb5-136"><a href="#cb5-136"></a></span>
<span id="cb5-137"><a href="#cb5-137"></a> app <span class="op">=</span> gtk_application_new <span class="op">(</span><span class="st">&quot;com.github.ToshioCP.tfe1&quot;</span><span class="op">,</span> G_APPLICATION_HANDLES_OPEN<span class="op">);</span></span>
<span id="cb5-138"><a href="#cb5-138"></a> g_signal_connect <span class="op">(</span>app<span class="op">,</span> <span class="st">&quot;activate&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>app_activate<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb5-139"><a href="#cb5-139"></a> g_signal_connect <span class="op">(</span>app<span class="op">,</span> <span class="st">&quot;open&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>app_open<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb5-140"><a href="#cb5-140"></a> stat <span class="op">=</span>g_application_run <span class="op">(</span>G_APPLICATION <span class="op">(</span>app<span class="op">),</span> argc<span class="op">,</span> argv<span class="op">);</span></span>
<span id="cb5-141"><a href="#cb5-141"></a> g_object_unref <span class="op">(</span>app<span class="op">);</span></span>
<span id="cb5-142"><a href="#cb5-142"></a> <span class="cf">return</span> stat<span class="op">;</span></span>
<span id="cb5-143"><a href="#cb5-143"></a><span class="op">}</span></span></code></pre></div>
<ul>
<li>107: Sets the pointer to GFile into TfeTextView.
<code>files[i]</code> is a pointer to GFile structure. It will be freed
by the system. So you need to copy it. <code>g_file_dup</code>
duplicates the given GFile structure.</li>
<li>123: Connects “close-request” signal and <code>before_close</code>
handler. The fourth argument is called user data and it is given to the
signal handler. So, <code>nb</code> is given to
<code>before_close</code> as the second argument.</li>
<li>110: The GFile pointer of the TfeTextView is set with
<code>files[i]</code>, which is a GFile created with the command line
argument. But the GFile will be destroyed by the system later. So it
needs to be copied before the assignment. <code>g_file_dup</code>
duplicates the GFile.</li>
<li>126: The “close-request” signal is connected to
<code>before_close</code> handler. The fourth argument is called “user
data” and it will be the second argument of the signal handler. So,
<code>nb</code> is given to <code>before_close</code> as the second
argument.</li>
</ul>
<p>Now compile and run it. Theres a sample file in the directory
<code>tfe</code>. Type <code>./a.out taketori.txt</code>. Modify the
contents and close the window. Make sure that the file is modified.</p>
<p>Now its time to compile and run.</p>
<pre><code>$ cd src/tfe
$ comp tfe1
$ ./a.out taketori.txt`.</code></pre>
<p>Modify the contents and close the window. Make sure that the file is
modified.</p>
<p>Now we got a very simple editor. Its not smart. We need more
features like open, save, saveas, change font and so on. We will add
them in the next section and after.</p>

View file

@ -5,7 +5,7 @@
<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>Gtk4 tutorial</title>
<title>GTK 4 tutorial</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
@ -111,194 +111,251 @@
</div>
</div>
</nav>
<h1 id="the-user-interface-ui-file-and-gtkbuilder">The User Interface (UI) file and GtkBuilder</h1>
<h1 id="gtkbuilder-and-ui-file">GtkBuilder and UI file</h1>
<h2 id="new-open-and-save-button">New, Open and Save button</h2>
<p>In the last section we made the almost simplest editor possible. It reads files in the <code>app_open</code> function at start-up and writes them out when closing the window. It works but is not very good. It would be better if we had “New”, “Open”, “Save” and “Close” buttons. This section describes how to put those buttons into the window. Signals and handlers will be explained later.</p>
<p>We made very simple editor in the previous section. It reads files at
the start and writes them out at the end of the program. It works, but
is not so good. It would be better if we had “New”, “Open”, “Save” and
“Close” buttons. This section describes how to put those buttons into
the window.</p>
<figure>
<img src="image/screenshot_tfe2.png" alt="" /><figcaption>Screenshot of the file editor</figcaption>
<img src="image/screenshot_tfe2.png"
alt="Screenshot of the file editor" />
<figcaption aria-hidden="true">Screenshot of the file
editor</figcaption>
</figure>
<p>The screenshot above shows the layout. The function <code>app_open</code> in the source code <code>tfe2.c</code> is as follows.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb1-1"><a href="#cb1-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb1-2"><a href="#cb1-2"></a>app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {</span>
<span id="cb1-3"><a href="#cb1-3"></a> GtkWidget *win;</span>
<span id="cb1-4"><a href="#cb1-4"></a> GtkWidget *nb;</span>
<span id="cb1-5"><a href="#cb1-5"></a> GtkWidget *lab;</span>
<span id="cb1-6"><a href="#cb1-6"></a> GtkNotebookPage *nbp;</span>
<span id="cb1-7"><a href="#cb1-7"></a> GtkWidget *scr;</span>
<span id="cb1-8"><a href="#cb1-8"></a> GtkWidget *tv;</span>
<span id="cb1-9"><a href="#cb1-9"></a> GtkTextBuffer *tb;</span>
<span id="cb1-10"><a href="#cb1-10"></a> <span class="dt">char</span> *contents;</span>
<span id="cb1-11"><a href="#cb1-11"></a> gsize length;</span>
<span id="cb1-12"><a href="#cb1-12"></a> <span class="dt">char</span> *filename;</span>
<span id="cb1-13"><a href="#cb1-13"></a> <span class="dt">int</span> i;</span>
<p>The screenshot above shows the layout. The function
<code>app_open</code> in the source code <code>tfe2.c</code> is as
follows.</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb1-1"><a href="#cb1-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb1-2"><a href="#cb1-2"></a>app_open <span class="op">(</span>GApplication <span class="op">*</span>app<span class="op">,</span> GFile <span class="op">**</span> files<span class="op">,</span> gint n_files<span class="op">,</span> gchar <span class="op">*</span>hint<span class="op">)</span> <span class="op">{</span></span>
<span id="cb1-3"><a href="#cb1-3"></a> GtkWidget <span class="op">*</span>win<span class="op">;</span></span>
<span id="cb1-4"><a href="#cb1-4"></a> GtkWidget <span class="op">*</span>nb<span class="op">;</span></span>
<span id="cb1-5"><a href="#cb1-5"></a> GtkWidget <span class="op">*</span>lab<span class="op">;</span></span>
<span id="cb1-6"><a href="#cb1-6"></a> GtkNotebookPage <span class="op">*</span>nbp<span class="op">;</span></span>
<span id="cb1-7"><a href="#cb1-7"></a> GtkWidget <span class="op">*</span>scr<span class="op">;</span></span>
<span id="cb1-8"><a href="#cb1-8"></a> GtkWidget <span class="op">*</span>tv<span class="op">;</span></span>
<span id="cb1-9"><a href="#cb1-9"></a> GtkTextBuffer <span class="op">*</span>tb<span class="op">;</span></span>
<span id="cb1-10"><a href="#cb1-10"></a> <span class="dt">char</span> <span class="op">*</span>contents<span class="op">;</span></span>
<span id="cb1-11"><a href="#cb1-11"></a> gsize length<span class="op">;</span></span>
<span id="cb1-12"><a href="#cb1-12"></a> <span class="dt">char</span> <span class="op">*</span>filename<span class="op">;</span></span>
<span id="cb1-13"><a href="#cb1-13"></a> <span class="dt">int</span> i<span class="op">;</span></span>
<span id="cb1-14"><a href="#cb1-14"></a></span>
<span id="cb1-15"><a href="#cb1-15"></a> GtkWidget *boxv;</span>
<span id="cb1-16"><a href="#cb1-16"></a> GtkWidget *boxh;</span>
<span id="cb1-17"><a href="#cb1-17"></a> GtkWidget *dmy1;</span>
<span id="cb1-18"><a href="#cb1-18"></a> GtkWidget *dmy2;</span>
<span id="cb1-19"><a href="#cb1-19"></a> GtkWidget *dmy3;</span>
<span id="cb1-20"><a href="#cb1-20"></a> GtkWidget *btnn; <span class="co">/* button for new */</span></span>
<span id="cb1-21"><a href="#cb1-21"></a> GtkWidget *btno; <span class="co">/* button for open */</span></span>
<span id="cb1-22"><a href="#cb1-22"></a> GtkWidget *btns; <span class="co">/* button for save */</span></span>
<span id="cb1-23"><a href="#cb1-23"></a> GtkWidget *btnc; <span class="co">/* button for close */</span></span>
<span id="cb1-15"><a href="#cb1-15"></a> GtkWidget <span class="op">*</span>boxv<span class="op">;</span></span>
<span id="cb1-16"><a href="#cb1-16"></a> GtkWidget <span class="op">*</span>boxh<span class="op">;</span></span>
<span id="cb1-17"><a href="#cb1-17"></a> GtkWidget <span class="op">*</span>dmy1<span class="op">;</span></span>
<span id="cb1-18"><a href="#cb1-18"></a> GtkWidget <span class="op">*</span>dmy2<span class="op">;</span></span>
<span id="cb1-19"><a href="#cb1-19"></a> GtkWidget <span class="op">*</span>dmy3<span class="op">;</span></span>
<span id="cb1-20"><a href="#cb1-20"></a> GtkWidget <span class="op">*</span>btnn<span class="op">;</span> <span class="co">/* button for new */</span></span>
<span id="cb1-21"><a href="#cb1-21"></a> GtkWidget <span class="op">*</span>btno<span class="op">;</span> <span class="co">/* button for open */</span></span>
<span id="cb1-22"><a href="#cb1-22"></a> GtkWidget <span class="op">*</span>btns<span class="op">;</span> <span class="co">/* button for save */</span></span>
<span id="cb1-23"><a href="#cb1-23"></a> GtkWidget <span class="op">*</span>btnc<span class="op">;</span> <span class="co">/* button for close */</span></span>
<span id="cb1-24"><a href="#cb1-24"></a></span>
<span id="cb1-25"><a href="#cb1-25"></a> win = gtk_application_window_new (GTK_APPLICATION (app));</span>
<span id="cb1-26"><a href="#cb1-26"></a> gtk_window_set_title (GTK_WINDOW (win), <span class="st">&quot;file editor&quot;</span>);</span>
<span id="cb1-27"><a href="#cb1-27"></a> gtk_window_set_default_size (GTK_WINDOW (win), <span class="dv">600</span>, <span class="dv">400</span>);</span>
<span id="cb1-25"><a href="#cb1-25"></a> win <span class="op">=</span> gtk_application_window_new <span class="op">(</span>GTK_APPLICATION <span class="op">(</span>app<span class="op">));</span></span>
<span id="cb1-26"><a href="#cb1-26"></a> gtk_window_set_title <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> <span class="st">&quot;file editor&quot;</span><span class="op">);</span></span>
<span id="cb1-27"><a href="#cb1-27"></a> gtk_window_set_default_size <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> <span class="dv">600</span><span class="op">,</span> <span class="dv">400</span><span class="op">);</span></span>
<span id="cb1-28"><a href="#cb1-28"></a></span>
<span id="cb1-29"><a href="#cb1-29"></a> boxv = gtk_box_new (GTK_ORIENTATION_VERTICAL, <span class="dv">0</span>);</span>
<span id="cb1-30"><a href="#cb1-30"></a> gtk_window_set_child (GTK_WINDOW (win), boxv);</span>
<span id="cb1-29"><a href="#cb1-29"></a> boxv <span class="op">=</span> gtk_box_new <span class="op">(</span>GTK_ORIENTATION_VERTICAL<span class="op">,</span> <span class="dv">0</span><span class="op">);</span></span>
<span id="cb1-30"><a href="#cb1-30"></a> gtk_window_set_child <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> boxv<span class="op">);</span></span>
<span id="cb1-31"><a href="#cb1-31"></a></span>
<span id="cb1-32"><a href="#cb1-32"></a> boxh = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, <span class="dv">0</span>);</span>
<span id="cb1-33"><a href="#cb1-33"></a> gtk_box_append (GTK_BOX (boxv), boxh);</span>
<span id="cb1-32"><a href="#cb1-32"></a> boxh <span class="op">=</span> gtk_box_new <span class="op">(</span>GTK_ORIENTATION_HORIZONTAL<span class="op">,</span> <span class="dv">0</span><span class="op">);</span></span>
<span id="cb1-33"><a href="#cb1-33"></a> gtk_box_append <span class="op">(</span>GTK_BOX <span class="op">(</span>boxv<span class="op">),</span> boxh<span class="op">);</span></span>
<span id="cb1-34"><a href="#cb1-34"></a></span>
<span id="cb1-35"><a href="#cb1-35"></a> dmy1 = gtk_label_new(NULL); <span class="co">/* dummy label for left space */</span></span>
<span id="cb1-36"><a href="#cb1-36"></a> gtk_label_set_width_chars (GTK_LABEL (dmy1), <span class="dv">10</span>);</span>
<span id="cb1-37"><a href="#cb1-37"></a> dmy2 = gtk_label_new(NULL); <span class="co">/* dummy label for center space */</span></span>
<span id="cb1-38"><a href="#cb1-38"></a> gtk_widget_set_hexpand (dmy2, TRUE);</span>
<span id="cb1-39"><a href="#cb1-39"></a> dmy3 = gtk_label_new(NULL); <span class="co">/* dummy label for right space */</span></span>
<span id="cb1-40"><a href="#cb1-40"></a> gtk_label_set_width_chars (GTK_LABEL (dmy3), <span class="dv">10</span>);</span>
<span id="cb1-41"><a href="#cb1-41"></a> btnn = gtk_button_new_with_label (<span class="st">&quot;New&quot;</span>);</span>
<span id="cb1-42"><a href="#cb1-42"></a> btno = gtk_button_new_with_label (<span class="st">&quot;Open&quot;</span>);</span>
<span id="cb1-43"><a href="#cb1-43"></a> btns = gtk_button_new_with_label (<span class="st">&quot;Save&quot;</span>);</span>
<span id="cb1-44"><a href="#cb1-44"></a> btnc = gtk_button_new_with_label (<span class="st">&quot;Close&quot;</span>);</span>
<span id="cb1-35"><a href="#cb1-35"></a> dmy1 <span class="op">=</span> gtk_label_new<span class="op">(</span>NULL<span class="op">);</span> <span class="co">/* dummy label for left space */</span></span>
<span id="cb1-36"><a href="#cb1-36"></a> gtk_label_set_width_chars <span class="op">(</span>GTK_LABEL <span class="op">(</span>dmy1<span class="op">),</span> <span class="dv">10</span><span class="op">);</span></span>
<span id="cb1-37"><a href="#cb1-37"></a> dmy2 <span class="op">=</span> gtk_label_new<span class="op">(</span>NULL<span class="op">);</span> <span class="co">/* dummy label for center space */</span></span>
<span id="cb1-38"><a href="#cb1-38"></a> gtk_widget_set_hexpand <span class="op">(</span>dmy2<span class="op">,</span> TRUE<span class="op">);</span></span>
<span id="cb1-39"><a href="#cb1-39"></a> dmy3 <span class="op">=</span> gtk_label_new<span class="op">(</span>NULL<span class="op">);</span> <span class="co">/* dummy label for right space */</span></span>
<span id="cb1-40"><a href="#cb1-40"></a> gtk_label_set_width_chars <span class="op">(</span>GTK_LABEL <span class="op">(</span>dmy3<span class="op">),</span> <span class="dv">10</span><span class="op">);</span></span>
<span id="cb1-41"><a href="#cb1-41"></a> btnn <span class="op">=</span> gtk_button_new_with_label <span class="op">(</span><span class="st">&quot;New&quot;</span><span class="op">);</span></span>
<span id="cb1-42"><a href="#cb1-42"></a> btno <span class="op">=</span> gtk_button_new_with_label <span class="op">(</span><span class="st">&quot;Open&quot;</span><span class="op">);</span></span>
<span id="cb1-43"><a href="#cb1-43"></a> btns <span class="op">=</span> gtk_button_new_with_label <span class="op">(</span><span class="st">&quot;Save&quot;</span><span class="op">);</span></span>
<span id="cb1-44"><a href="#cb1-44"></a> btnc <span class="op">=</span> gtk_button_new_with_label <span class="op">(</span><span class="st">&quot;Close&quot;</span><span class="op">);</span></span>
<span id="cb1-45"><a href="#cb1-45"></a></span>
<span id="cb1-46"><a href="#cb1-46"></a> gtk_box_append (GTK_BOX (boxh), dmy1);</span>
<span id="cb1-47"><a href="#cb1-47"></a> gtk_box_append (GTK_BOX (boxh), btnn);</span>
<span id="cb1-48"><a href="#cb1-48"></a> gtk_box_append (GTK_BOX (boxh), btno);</span>
<span id="cb1-49"><a href="#cb1-49"></a> gtk_box_append (GTK_BOX (boxh), dmy2);</span>
<span id="cb1-50"><a href="#cb1-50"></a> gtk_box_append (GTK_BOX (boxh), btns);</span>
<span id="cb1-51"><a href="#cb1-51"></a> gtk_box_append (GTK_BOX (boxh), btnc);</span>
<span id="cb1-52"><a href="#cb1-52"></a> gtk_box_append (GTK_BOX (boxh), dmy3);</span>
<span id="cb1-46"><a href="#cb1-46"></a> gtk_box_append <span class="op">(</span>GTK_BOX <span class="op">(</span>boxh<span class="op">),</span> dmy1<span class="op">);</span></span>
<span id="cb1-47"><a href="#cb1-47"></a> gtk_box_append <span class="op">(</span>GTK_BOX <span class="op">(</span>boxh<span class="op">),</span> btnn<span class="op">);</span></span>
<span id="cb1-48"><a href="#cb1-48"></a> gtk_box_append <span class="op">(</span>GTK_BOX <span class="op">(</span>boxh<span class="op">),</span> btno<span class="op">);</span></span>
<span id="cb1-49"><a href="#cb1-49"></a> gtk_box_append <span class="op">(</span>GTK_BOX <span class="op">(</span>boxh<span class="op">),</span> dmy2<span class="op">);</span></span>
<span id="cb1-50"><a href="#cb1-50"></a> gtk_box_append <span class="op">(</span>GTK_BOX <span class="op">(</span>boxh<span class="op">),</span> btns<span class="op">);</span></span>
<span id="cb1-51"><a href="#cb1-51"></a> gtk_box_append <span class="op">(</span>GTK_BOX <span class="op">(</span>boxh<span class="op">),</span> btnc<span class="op">);</span></span>
<span id="cb1-52"><a href="#cb1-52"></a> gtk_box_append <span class="op">(</span>GTK_BOX <span class="op">(</span>boxh<span class="op">),</span> dmy3<span class="op">);</span></span>
<span id="cb1-53"><a href="#cb1-53"></a></span>
<span id="cb1-54"><a href="#cb1-54"></a> nb = gtk_notebook_new ();</span>
<span id="cb1-55"><a href="#cb1-55"></a> gtk_widget_set_hexpand (nb, TRUE);</span>
<span id="cb1-56"><a href="#cb1-56"></a> gtk_widget_set_vexpand (nb, TRUE);</span>
<span id="cb1-57"><a href="#cb1-57"></a> gtk_box_append (GTK_BOX (boxv), nb);</span>
<span id="cb1-54"><a href="#cb1-54"></a> nb <span class="op">=</span> gtk_notebook_new <span class="op">();</span></span>
<span id="cb1-55"><a href="#cb1-55"></a> gtk_widget_set_hexpand <span class="op">(</span>nb<span class="op">,</span> TRUE<span class="op">);</span></span>
<span id="cb1-56"><a href="#cb1-56"></a> gtk_widget_set_vexpand <span class="op">(</span>nb<span class="op">,</span> TRUE<span class="op">);</span></span>
<span id="cb1-57"><a href="#cb1-57"></a> gtk_box_append <span class="op">(</span>GTK_BOX <span class="op">(</span>boxv<span class="op">),</span> nb<span class="op">);</span></span>
<span id="cb1-58"><a href="#cb1-58"></a></span>
<span id="cb1-59"><a href="#cb1-59"></a> <span class="cf">for</span> (i = <span class="dv">0</span>; i &lt; n_files; i++) {</span>
<span id="cb1-60"><a href="#cb1-60"></a> <span class="cf">if</span> (g_file_load_contents (files[i], NULL, &amp;contents, &amp;length, NULL, NULL)) {</span>
<span id="cb1-61"><a href="#cb1-61"></a> scr = gtk_scrolled_window_new ();</span>
<span id="cb1-62"><a href="#cb1-62"></a> tv = tfe_text_view_new ();</span>
<span id="cb1-63"><a href="#cb1-63"></a> tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));</span>
<span id="cb1-64"><a href="#cb1-64"></a> gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);</span>
<span id="cb1-65"><a href="#cb1-65"></a> gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);</span>
<span id="cb1-59"><a href="#cb1-59"></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_files<span class="op">;</span> i<span class="op">++)</span> <span class="op">{</span></span>
<span id="cb1-60"><a href="#cb1-60"></a> <span class="cf">if</span> <span class="op">(</span>g_file_load_contents <span class="op">(</span>files<span class="op">[</span>i<span class="op">],</span> NULL<span class="op">,</span> <span class="op">&amp;</span>contents<span class="op">,</span> <span class="op">&amp;</span>length<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">))</span> <span class="op">{</span></span>
<span id="cb1-61"><a href="#cb1-61"></a> scr <span class="op">=</span> gtk_scrolled_window_new <span class="op">();</span></span>
<span id="cb1-62"><a href="#cb1-62"></a> tv <span class="op">=</span> tfe_text_view_new <span class="op">();</span></span>
<span id="cb1-63"><a href="#cb1-63"></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="cb1-64"><a href="#cb1-64"></a> gtk_text_view_set_wrap_mode <span class="op">(</span>GTK_TEXT_VIEW <span class="op">(</span>tv<span class="op">),</span> GTK_WRAP_WORD_CHAR<span class="op">);</span></span>
<span id="cb1-65"><a href="#cb1-65"></a> gtk_scrolled_window_set_child <span class="op">(</span>GTK_SCROLLED_WINDOW <span class="op">(</span>scr<span class="op">),</span> tv<span class="op">);</span></span>
<span id="cb1-66"><a href="#cb1-66"></a></span>
<span id="cb1-67"><a href="#cb1-67"></a> tfe_text_view_set_file (TFE_TEXT_VIEW (tv), g_file_dup (files[i]));</span>
<span id="cb1-68"><a href="#cb1-68"></a> gtk_text_buffer_set_text (tb, contents, length);</span>
<span id="cb1-69"><a href="#cb1-69"></a> g_free (contents);</span>
<span id="cb1-70"><a href="#cb1-70"></a> filename = g_file_get_basename (files[i]);</span>
<span id="cb1-71"><a href="#cb1-71"></a> lab = gtk_label_new (filename);</span>
<span id="cb1-72"><a href="#cb1-72"></a> gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);</span>
<span id="cb1-73"><a href="#cb1-73"></a> nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);</span>
<span id="cb1-74"><a href="#cb1-74"></a> g_object_set (nbp, <span class="st">&quot;tab-expand&quot;</span>, TRUE, NULL);</span>
<span id="cb1-75"><a href="#cb1-75"></a> g_free (filename);</span>
<span id="cb1-76"><a href="#cb1-76"></a> } <span class="cf">else</span> <span class="cf">if</span> ((filename = g_file_get_path (files[i])) != NULL) {</span>
<span id="cb1-77"><a href="#cb1-77"></a> g_print (<span class="st">&quot;No such file: %s.</span><span class="sc">\n</span><span class="st">&quot;</span>, filename);</span>
<span id="cb1-78"><a href="#cb1-78"></a> g_free (filename);</span>
<span id="cb1-79"><a href="#cb1-79"></a> } <span class="cf">else</span></span>
<span id="cb1-80"><a href="#cb1-80"></a> g_print (<span class="st">&quot;No valid file is given</span><span class="sc">\n</span><span class="st">&quot;</span>);</span>
<span id="cb1-81"><a href="#cb1-81"></a> }</span>
<span id="cb1-82"><a href="#cb1-82"></a> <span class="cf">if</span> (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) &gt; <span class="dv">0</span>) {</span>
<span id="cb1-83"><a href="#cb1-83"></a> gtk_widget_show (win);</span>
<span id="cb1-84"><a href="#cb1-84"></a> } <span class="cf">else</span></span>
<span id="cb1-85"><a href="#cb1-85"></a> gtk_window_destroy (GTK_WINDOW (win));</span>
<span id="cb1-86"><a href="#cb1-86"></a>}</span></code></pre></div>
<p>The aim is to build the widgets of the main application window.</p>
<span id="cb1-67"><a href="#cb1-67"></a> tfe_text_view_set_file <span class="op">(</span>TFE_TEXT_VIEW <span class="op">(</span>tv<span class="op">),</span> g_file_dup <span class="op">(</span>files<span class="op">[</span>i<span class="op">]));</span></span>
<span id="cb1-68"><a href="#cb1-68"></a> gtk_text_buffer_set_text <span class="op">(</span>tb<span class="op">,</span> contents<span class="op">,</span> length<span class="op">);</span></span>
<span id="cb1-69"><a href="#cb1-69"></a> g_free <span class="op">(</span>contents<span class="op">);</span></span>
<span id="cb1-70"><a href="#cb1-70"></a> filename <span class="op">=</span> g_file_get_basename <span class="op">(</span>files<span class="op">[</span>i<span class="op">]);</span></span>
<span id="cb1-71"><a href="#cb1-71"></a> lab <span class="op">=</span> gtk_label_new <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb1-72"><a href="#cb1-72"></a> gtk_notebook_append_page <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">),</span> scr<span class="op">,</span> lab<span class="op">);</span></span>
<span id="cb1-73"><a href="#cb1-73"></a> nbp <span class="op">=</span> gtk_notebook_get_page <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">),</span> scr<span class="op">);</span></span>
<span id="cb1-74"><a href="#cb1-74"></a> g_object_set <span class="op">(</span>nbp<span class="op">,</span> <span class="st">&quot;tab-expand&quot;</span><span class="op">,</span> TRUE<span class="op">,</span> NULL<span class="op">);</span></span>
<span id="cb1-75"><a href="#cb1-75"></a> g_free <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb1-76"><a href="#cb1-76"></a> <span class="op">}</span> <span class="cf">else</span> <span class="cf">if</span> <span class="op">((</span>filename <span class="op">=</span> g_file_get_path <span class="op">(</span>files<span class="op">[</span>i<span class="op">]))</span> <span class="op">!=</span> NULL<span class="op">)</span> <span class="op">{</span></span>
<span id="cb1-77"><a href="#cb1-77"></a> g_print <span class="op">(</span><span class="st">&quot;No such file: %s.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">,</span> filename<span class="op">);</span></span>
<span id="cb1-78"><a href="#cb1-78"></a> g_free <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb1-79"><a href="#cb1-79"></a> <span class="op">}</span> <span class="cf">else</span></span>
<span id="cb1-80"><a href="#cb1-80"></a> g_print <span class="op">(</span><span class="st">&quot;No valid file is given</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb1-81"><a href="#cb1-81"></a> <span class="op">}</span></span>
<span id="cb1-82"><a href="#cb1-82"></a> <span class="cf">if</span> <span class="op">(</span>gtk_notebook_get_n_pages <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">))</span> <span class="op">&gt;</span> <span class="dv">0</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb1-83"><a href="#cb1-83"></a> gtk_widget_show <span class="op">(</span>win<span class="op">);</span></span>
<span id="cb1-84"><a href="#cb1-84"></a> <span class="op">}</span> <span class="cf">else</span></span>
<span id="cb1-85"><a href="#cb1-85"></a> gtk_window_destroy <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">));</span></span>
<span id="cb1-86"><a href="#cb1-86"></a><span class="op">}</span></span></code></pre></div>
<p>The function <code>app_open</code> builds the widgets in the main
application window.</p>
<ul>
<li>25-27: Creates a GtkApplicationWindow instance and sets the title and default size.</li>
<li>29-30: Creates a GtkBox instance <code>boxv</code>. It is a vertical box and a child of GtkApplicationWindow. It has two children. The first child is a horizontal box. The second child is a GtkNotebook.</li>
<li>32-33: Creates a GtkBox instance <code>boxh</code> and appends it to <code>boxv</code> as a first child.</li>
<li>35-40: Creates three dummy labels. The labels <code>dmy1</code> and <code>dmy3</code> has a character width of ten. The other label <code>dmy2</code> has hexpand property which is set to be TRUE. This makes the label expands horizontally as long as possible.</li>
<li>25-27: Creates a GtkApplicationWindow instance and sets the title
and default size.</li>
<li>29-30: Creates a GtkBox instance <code>boxv</code>. It is a vertical
box and a child of GtkApplicationWindow. It has two children. The first
child is a horizontal box. The second child is a GtkNotebook.</li>
<li>32-33: Creates a GtkBox instance <code>boxh</code> and appends it to
<code>boxv</code> as a first child.</li>
<li>35-40: Creates three dummy labels. The labels <code>dmy1</code> and
<code>dmy3</code> has a character width of ten. The other label
<code>dmy2</code> has hexpand property which is set to be TRUE. This
makes the label expands horizontally as long as possible.</li>
<li>41-44: Creates four buttons.</li>
<li>46-52: Appends these GtkLabel and GtkButton to <code>boxh</code>.</li>
<li>54-57: Creates a GtkNotebook instance and sets hexpand and vexpand properties TRUE. This makes it expand horizontally and vertically as big as possible. It is appended to <code>boxv</code> as the second child.</li>
<li>46-52: Appends these GtkLabel and GtkButton to
<code>boxh</code>.</li>
<li>54-57: Creates a GtkNotebook instance and sets hexpand and vexpand
properties to be TRUE. This makes it expand horizontally and vertically
as big as possible. It is appended to <code>boxv</code> as the second
child.</li>
</ul>
<p>The number of lines to build the widgets is 33(=57-25+1). We also needed many additional variables (<code>boxv</code>, <code>boxh</code>, <code>dmy1</code>, …), most of which werent necessary, except for building the widgets. Are there any good solution to reduce these work?</p>
<p>Gtk provides GtkBuilder. It reads user interface (UI) data and builds a window. It reduces this cumbersome work.</p>
<p>The number of widget-build lines is 33(=57-25+1). We also needed many
variables (<code>boxv</code>, <code>boxh</code>, <code>dmy1</code>, …)
and most of them used only for building the widgets. Are there any good
solution to reduce these work?</p>
<p>Gtk provides GtkBuilder. It reads user interface (UI) data and builds
a window. It reduces this cumbersome work.</p>
<h2 id="the-ui-file">The UI File</h2>
<p>First, lets look at the UI file <code>tfe3.ui</code> that is used to define the widget structure.</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode numberSource xml numberLines"><code class="sourceCode xml"><span id="cb2-1"><a href="#cb2-1"></a><span class="kw">&lt;?xml</span> version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;<span class="kw">?&gt;</span></span>
<span id="cb2-2"><a href="#cb2-2"></a><span class="kw">&lt;interface&gt;</span></span>
<span id="cb2-3"><a href="#cb2-3"></a> <span class="kw">&lt;object</span><span class="ot"> class=</span><span class="st">&quot;GtkApplicationWindow&quot;</span><span class="ot"> id=</span><span class="st">&quot;win&quot;</span><span class="kw">&gt;</span></span>
<span id="cb2-4"><a href="#cb2-4"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;title&quot;</span><span class="kw">&gt;</span>file editor<span class="kw">&lt;/property&gt;</span></span>
<span id="cb2-5"><a href="#cb2-5"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;default-width&quot;</span><span class="kw">&gt;</span>600<span class="kw">&lt;/property&gt;</span></span>
<span id="cb2-6"><a href="#cb2-6"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;default-height&quot;</span><span class="kw">&gt;</span>400<span class="kw">&lt;/property&gt;</span></span>
<span id="cb2-7"><a href="#cb2-7"></a> <span class="kw">&lt;child&gt;</span></span>
<span id="cb2-8"><a href="#cb2-8"></a> <span class="kw">&lt;object</span><span class="ot"> class=</span><span class="st">&quot;GtkBox&quot;</span><span class="ot"> id=</span><span class="st">&quot;boxv&quot;</span><span class="kw">&gt;</span></span>
<span id="cb2-9"><a href="#cb2-9"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;orientation&quot;</span><span class="kw">&gt;</span>GTK_ORIENTATION_VERTICAL<span class="kw">&lt;/property&gt;</span></span>
<span id="cb2-10"><a href="#cb2-10"></a> <span class="kw">&lt;child&gt;</span></span>
<span id="cb2-11"><a href="#cb2-11"></a> <span class="kw">&lt;object</span><span class="ot"> class=</span><span class="st">&quot;GtkBox&quot;</span><span class="ot"> id=</span><span class="st">&quot;boxh&quot;</span><span class="kw">&gt;</span></span>
<span id="cb2-12"><a href="#cb2-12"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;orientation&quot;</span><span class="kw">&gt;</span>GTK_ORIENTATION_HORIZONTAL<span class="kw">&lt;/property&gt;</span></span>
<span id="cb2-13"><a href="#cb2-13"></a> <span class="kw">&lt;child&gt;</span></span>
<span id="cb2-14"><a href="#cb2-14"></a> <span class="kw">&lt;object</span><span class="ot"> class=</span><span class="st">&quot;GtkLabel&quot;</span><span class="ot"> id=</span><span class="st">&quot;dmy1&quot;</span><span class="kw">&gt;</span></span>
<span id="cb2-15"><a href="#cb2-15"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;width-chars&quot;</span><span class="kw">&gt;</span>10<span class="kw">&lt;/property&gt;</span></span>
<span id="cb2-16"><a href="#cb2-16"></a> <span class="kw">&lt;/object&gt;</span></span>
<span id="cb2-17"><a href="#cb2-17"></a> <span class="kw">&lt;/child&gt;</span></span>
<span id="cb2-18"><a href="#cb2-18"></a> <span class="kw">&lt;child&gt;</span></span>
<span id="cb2-19"><a href="#cb2-19"></a> <span class="kw">&lt;object</span><span class="ot"> class=</span><span class="st">&quot;GtkButton&quot;</span><span class="ot"> id=</span><span class="st">&quot;btnn&quot;</span><span class="kw">&gt;</span></span>
<span id="cb2-20"><a href="#cb2-20"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;label&quot;</span><span class="kw">&gt;</span>New<span class="kw">&lt;/property&gt;</span></span>
<span id="cb2-21"><a href="#cb2-21"></a> <span class="kw">&lt;/object&gt;</span></span>
<span id="cb2-22"><a href="#cb2-22"></a> <span class="kw">&lt;/child&gt;</span></span>
<span id="cb2-23"><a href="#cb2-23"></a> <span class="kw">&lt;child&gt;</span></span>
<span id="cb2-24"><a href="#cb2-24"></a> <span class="kw">&lt;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><span class="kw">&gt;</span></span>
<span id="cb2-25"><a href="#cb2-25"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;label&quot;</span><span class="kw">&gt;</span>Open<span class="kw">&lt;/property&gt;</span></span>
<span id="cb2-26"><a href="#cb2-26"></a> <span class="kw">&lt;/object&gt;</span></span>
<span id="cb2-27"><a href="#cb2-27"></a> <span class="kw">&lt;/child&gt;</span></span>
<span id="cb2-28"><a href="#cb2-28"></a> <span class="kw">&lt;child&gt;</span></span>
<span id="cb2-29"><a href="#cb2-29"></a> <span class="kw">&lt;object</span><span class="ot"> class=</span><span class="st">&quot;GtkLabel&quot;</span><span class="ot"> id=</span><span class="st">&quot;dmy2&quot;</span><span class="kw">&gt;</span></span>
<span id="cb2-30"><a href="#cb2-30"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;hexpand&quot;</span><span class="kw">&gt;</span>TRUE<span class="kw">&lt;/property&gt;</span></span>
<span id="cb2-31"><a href="#cb2-31"></a> <span class="kw">&lt;/object&gt;</span></span>
<span id="cb2-32"><a href="#cb2-32"></a> <span class="kw">&lt;/child&gt;</span></span>
<span id="cb2-33"><a href="#cb2-33"></a> <span class="kw">&lt;child&gt;</span></span>
<span id="cb2-34"><a href="#cb2-34"></a> <span class="kw">&lt;object</span><span class="ot"> class=</span><span class="st">&quot;GtkButton&quot;</span><span class="ot"> id=</span><span class="st">&quot;btns&quot;</span><span class="kw">&gt;</span></span>
<span id="cb2-35"><a href="#cb2-35"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;label&quot;</span><span class="kw">&gt;</span>Save<span class="kw">&lt;/property&gt;</span></span>
<span id="cb2-36"><a href="#cb2-36"></a> <span class="kw">&lt;/object&gt;</span></span>
<span id="cb2-37"><a href="#cb2-37"></a> <span class="kw">&lt;/child&gt;</span></span>
<span id="cb2-38"><a href="#cb2-38"></a> <span class="kw">&lt;child&gt;</span></span>
<span id="cb2-39"><a href="#cb2-39"></a> <span class="kw">&lt;object</span><span class="ot"> class=</span><span class="st">&quot;GtkButton&quot;</span><span class="ot"> id=</span><span class="st">&quot;btnc&quot;</span><span class="kw">&gt;</span></span>
<span id="cb2-40"><a href="#cb2-40"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;label&quot;</span><span class="kw">&gt;</span>Close<span class="kw">&lt;/property&gt;</span></span>
<span id="cb2-41"><a href="#cb2-41"></a> <span class="kw">&lt;/object&gt;</span></span>
<span id="cb2-42"><a href="#cb2-42"></a> <span class="kw">&lt;/child&gt;</span></span>
<span id="cb2-43"><a href="#cb2-43"></a> <span class="kw">&lt;child&gt;</span></span>
<span id="cb2-44"><a href="#cb2-44"></a> <span class="kw">&lt;object</span><span class="ot"> class=</span><span class="st">&quot;GtkLabel&quot;</span><span class="ot"> id=</span><span class="st">&quot;dmy3&quot;</span><span class="kw">&gt;</span></span>
<span id="cb2-45"><a href="#cb2-45"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;width-chars&quot;</span><span class="kw">&gt;</span>10<span class="kw">&lt;/property&gt;</span></span>
<span id="cb2-46"><a href="#cb2-46"></a> <span class="kw">&lt;/object&gt;</span></span>
<span id="cb2-47"><a href="#cb2-47"></a> <span class="kw">&lt;/child&gt;</span></span>
<span id="cb2-48"><a href="#cb2-48"></a> <span class="kw">&lt;/object&gt;</span></span>
<span id="cb2-49"><a href="#cb2-49"></a> <span class="kw">&lt;/child&gt;</span></span>
<span id="cb2-50"><a href="#cb2-50"></a> <span class="kw">&lt;child&gt;</span></span>
<span id="cb2-51"><a href="#cb2-51"></a> <span class="kw">&lt;object</span><span class="ot"> class=</span><span class="st">&quot;GtkNotebook&quot;</span><span class="ot"> id=</span><span class="st">&quot;nb&quot;</span><span class="kw">&gt;</span></span>
<span id="cb2-52"><a href="#cb2-52"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;hexpand&quot;</span><span class="kw">&gt;</span>TRUE<span class="kw">&lt;/property&gt;</span></span>
<span id="cb2-53"><a href="#cb2-53"></a> <span class="kw">&lt;property</span><span class="ot"> name=</span><span class="st">&quot;vexpand&quot;</span><span class="kw">&gt;</span>TRUE<span class="kw">&lt;/property&gt;</span></span>
<span id="cb2-54"><a href="#cb2-54"></a> <span class="kw">&lt;/object&gt;</span></span>
<span id="cb2-55"><a href="#cb2-55"></a> <span class="kw">&lt;/child&gt;</span></span>
<span id="cb2-56"><a href="#cb2-56"></a> <span class="kw">&lt;/object&gt;</span></span>
<span id="cb2-57"><a href="#cb2-57"></a> <span class="kw">&lt;/child&gt;</span></span>
<span id="cb2-58"><a href="#cb2-58"></a> <span class="kw">&lt;/object&gt;</span></span>
<span id="cb2-59"><a href="#cb2-59"></a><span class="kw">&lt;/interface&gt;</span></span></code></pre></div>
<p>The structure of this file is XML. Constructs that begin with <code>&lt;</code> and end with <code>&gt;</code> are called tags. There are two types of tags, the start tag and the end tag. For example, <code>&lt;interface&gt;</code> is a start tag and <code>&lt;/interface&gt;</code> is an end tag. The UI file begins and ends with interface tags. Some tags, for example object tags, can have a class and id attributes in their start tag.</p>
<p>Look at the UI file <code>tfe3.ui</code> that defines widget
structure.</p>
<div class="sourceCode" id="cb2"><pre
class="sourceCode numberSource xml numberLines"><code class="sourceCode xml"><span id="cb2-1"><a href="#cb2-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="cb2-2"><a href="#cb2-2"></a>&lt;<span class="kw">interface</span>&gt;</span>
<span id="cb2-3"><a href="#cb2-3"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkApplicationWindow&quot;</span><span class="ot"> id=</span><span class="st">&quot;win&quot;</span>&gt;</span>
<span id="cb2-4"><a href="#cb2-4"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;title&quot;</span>&gt;file editor&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb2-5"><a href="#cb2-5"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;default-width&quot;</span>&gt;600&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb2-6"><a href="#cb2-6"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;default-height&quot;</span>&gt;400&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb2-7"><a href="#cb2-7"></a> &lt;<span class="kw">child</span>&gt;</span>
<span id="cb2-8"><a href="#cb2-8"></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;boxv&quot;</span>&gt;</span>
<span id="cb2-9"><a href="#cb2-9"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;orientation&quot;</span>&gt;GTK_ORIENTATION_VERTICAL&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb2-10"><a href="#cb2-10"></a> &lt;<span class="kw">child</span>&gt;</span>
<span id="cb2-11"><a href="#cb2-11"></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;boxh&quot;</span>&gt;</span>
<span id="cb2-12"><a href="#cb2-12"></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="cb2-13"><a href="#cb2-13"></a> &lt;<span class="kw">child</span>&gt;</span>
<span id="cb2-14"><a href="#cb2-14"></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;dmy1&quot;</span>&gt;</span>
<span id="cb2-15"><a href="#cb2-15"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;width-chars&quot;</span>&gt;10&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb2-16"><a href="#cb2-16"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb2-17"><a href="#cb2-17"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb2-18"><a href="#cb2-18"></a> &lt;<span class="kw">child</span>&gt;</span>
<span id="cb2-19"><a href="#cb2-19"></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;btnn&quot;</span>&gt;</span>
<span id="cb2-20"><a href="#cb2-20"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;label&quot;</span>&gt;New&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb2-21"><a href="#cb2-21"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb2-22"><a href="#cb2-22"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb2-23"><a href="#cb2-23"></a> &lt;<span class="kw">child</span>&gt;</span>
<span id="cb2-24"><a href="#cb2-24"></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="cb2-25"><a href="#cb2-25"></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="cb2-26"><a href="#cb2-26"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb2-27"><a href="#cb2-27"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb2-28"><a href="#cb2-28"></a> &lt;<span class="kw">child</span>&gt;</span>
<span id="cb2-29"><a href="#cb2-29"></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;dmy2&quot;</span>&gt;</span>
<span id="cb2-30"><a href="#cb2-30"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;hexpand&quot;</span>&gt;TRUE&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb2-31"><a href="#cb2-31"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb2-32"><a href="#cb2-32"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb2-33"><a href="#cb2-33"></a> &lt;<span class="kw">child</span>&gt;</span>
<span id="cb2-34"><a href="#cb2-34"></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;btns&quot;</span>&gt;</span>
<span id="cb2-35"><a href="#cb2-35"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;label&quot;</span>&gt;Save&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb2-36"><a href="#cb2-36"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb2-37"><a href="#cb2-37"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb2-38"><a href="#cb2-38"></a> &lt;<span class="kw">child</span>&gt;</span>
<span id="cb2-39"><a href="#cb2-39"></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;btnc&quot;</span>&gt;</span>
<span id="cb2-40"><a href="#cb2-40"></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="cb2-41"><a href="#cb2-41"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb2-42"><a href="#cb2-42"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb2-43"><a href="#cb2-43"></a> &lt;<span class="kw">child</span>&gt;</span>
<span id="cb2-44"><a href="#cb2-44"></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;dmy3&quot;</span>&gt;</span>
<span id="cb2-45"><a href="#cb2-45"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;width-chars&quot;</span>&gt;10&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb2-46"><a href="#cb2-46"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb2-47"><a href="#cb2-47"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb2-48"><a href="#cb2-48"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb2-49"><a href="#cb2-49"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb2-50"><a href="#cb2-50"></a> &lt;<span class="kw">child</span>&gt;</span>
<span id="cb2-51"><a href="#cb2-51"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkNotebook&quot;</span><span class="ot"> id=</span><span class="st">&quot;nb&quot;</span>&gt;</span>
<span id="cb2-52"><a href="#cb2-52"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;hexpand&quot;</span>&gt;TRUE&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb2-53"><a href="#cb2-53"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;vexpand&quot;</span>&gt;TRUE&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb2-54"><a href="#cb2-54"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb2-55"><a href="#cb2-55"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb2-56"><a href="#cb2-56"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb2-57"><a href="#cb2-57"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb2-58"><a href="#cb2-58"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb2-59"><a href="#cb2-59"></a>&lt;/<span class="kw">interface</span>&gt;</span></code></pre></div>
<p>The is a XML file. Tags begin with <code>&lt;</code> and end with
<code>&gt;</code>. There are two types of tags, the start tag and the
end tag. For example, <code>&lt;interface&gt;</code> is a start tag and
<code>&lt;/interface&gt;</code> is an end tag. The UI file begins and
ends with interface tags. Some tags, for example object tags, can have a
class and id attributes in their start tag.</p>
<ul>
<li>1: The first line is XML declaration. It specifies that the version of XML is 1.0 and the encoding is UTF-8. Even if the line is left out, GtkBuilder builds objects from the ui file. But ui files must use UTF-8 encoding, or GtkBuilder cant recognize it and a fatal error occurs.</li>
<li>3-6: An object with <code>GtkApplicationWindow</code> class and <code>win</code> id is defined. This is the top level window. And the three properties of the window are defined. <code>title</code> property is “file editor”, <code>default-width</code> property is 600 and <code>default-height</code> property is 400.</li>
<li>7: child tag means a child of the widget above. For example, line 7 tells us that GtkBox object which id is “boxv” is a child widget of <code>win</code>.</li>
<li>1: XML declaration. It specifies that the XML version is 1.0 and the
encoding is UTF-8.</li>
<li>3-6: An object tag with <code>GtkApplicationWindow</code> class and
<code>win</code> id. This is the top level window. And the three
properties of the window are defined. The <code>title</code> property is
“file editor”, <code>default-width</code> property is 600 and
<code>default-height</code> property is 400.</li>
<li>7: child tag means a child widget. For example, line 7 tells us that
GtkBox object with “boxv” id attribute is a child widget of
<code>win</code>.</li>
</ul>
<p>Compare this ui file and the lines 25-57 in the source code of <code>app_open</code> function. Those two describe the same structure of widgets.</p>
<p>Compare this ui file and the lines 25-57 in the <code>tfe2.c</code>
source code. Both builds the same window with its descendant
widgets.</p>
<p>You can check the ui file with <code>gtk4-builder-tool</code>.</p>
<ul>
<li><code>gtk4-builder-tool validate &lt;ui file name&gt;</code> validates the ui file. If the ui file includes some syntactical error, <code>gtk4-builder-tool</code> prints the error.</li>
<li><code>gtk4-builder-tool simplify &lt;ui file name&gt;</code> simplifies the ui file and prints the result. If <code>--replace</code> option is given, it replaces the ui file with the simplified one. If the ui file specifies a value of property but it is default, then it will be removed. And some values are simplified. For example, “TRUE”and “FALSE” becomes “1” and “0” respectively. However, “TRUE” or “FALSE” is better for maintenance.</li>
<li><code>gtk4-builder-tool validate &lt;ui file name&gt;</code>
validates the ui file. If the ui file includes some syntactical error,
<code>gtk4-builder-tool</code> prints the error.</li>
<li><code>gtk4-builder-tool simplify &lt;ui file name&gt;</code>
simplifies the ui file and prints the result. If <code>--replace</code>
option is given, it replaces the ui file with the simplified one. If the
ui file specifies a value of property but it is default, then it will be
removed. And some values are simplified. For example, “TRUE”and “FALSE”
becomes “1” and “0” respectively. However, “TRUE” or “FALSE” is better
for maintenance.</li>
</ul>
<p>It is a good idea to check your ui file before compiling.</p>
<h2 id="gtkbuilder">GtkBuilder</h2>
<p>GtkBuilder builds widgets based on the ui file.</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"></a>GtkBuilder *build;</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true"></a></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true"></a>build = gtk_builder_new_from_file (<span class="st">&quot;tfe3.ui&quot;</span>);</span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true"></a>win = GTK_WIDGET (gtk_builder_get_object (build, <span class="st">&quot;win&quot;</span>));</span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true"></a>gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));</span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true"></a>nb = GTK_WIDGET (gtk_builder_get_object (build, <span class="st">&quot;nb&quot;</span>));</span></code></pre></div>
<p>The function <code>gtk_builder_new_from_file</code> reads the file given as an argument. Then, it builds the widgets and creates GtkBuilder object. The function <code>gtk_builder_get_object (build, "win")</code> returns the pointer to the widget <code>win</code>, which is the id in the ui file. All the widgets are connected based on the parent-children relationship described in the ui file. We only need <code>win</code> and <code>nb</code> for the program after this, so we dont need to take out any other widgets. This reduces lines in the C source file.</p>
<p>GtkBuilder builds widgets based on a ui file.</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>GtkBuilder <span class="op">*</span>build<span class="op">;</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a>build <span class="op">=</span> gtk_builder_new_from_file <span class="op">(</span><span class="st">&quot;tfe3.ui&quot;</span><span class="op">);</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>win <span class="op">=</span> GTK_WIDGET <span class="op">(</span>gtk_builder_get_object <span class="op">(</span>build<span class="op">,</span> <span class="st">&quot;win&quot;</span><span class="op">));</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a>gtk_window_set_application <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> GTK_APPLICATION <span class="op">(</span>app<span class="op">));</span></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a>nb <span class="op">=</span> GTK_WIDGET <span class="op">(</span>gtk_builder_get_object <span class="op">(</span>build<span class="op">,</span> <span class="st">&quot;nb&quot;</span><span class="op">));</span></span></code></pre></div>
<p>The function <code>gtk_builder_new_from_file</code> reads the file
given as an argument. Then, it builds the widgets and creates GtkBuilder
object. The function <code>gtk_builder_get_object (build, "win")</code>
returns the pointer to the widget <code>win</code>, which is the id in
the ui file. All the widgets are connected based on the parent-children
relationship described in the ui file. We only need <code>win</code> and
<code>nb</code> for the program below. This reduces lines in the C
source file.</p>
<pre><code>$ cd tfe; diff tfe2.c tfe3.c
58a59
&gt; GtkBuilder *build;
@ -356,96 +413,124 @@
138c100
&lt; app = gtk_application_new (&quot;com.github.ToshioCP.tfe2&quot;, G_APPLICATION_HANDLES_OPEN);
---
&gt; app = gtk_application_new (&quot;com.github.ToshioCP.tfe3&quot;, G_APPLICATION_HANDLES_OPEN);</code></pre>
<p><code>60,103c61,65</code> means 44 (=103-60+1) lines are changed to 5 (=65-61+1) lines. Therefore, 39 lines are reduced. Using ui file not only shortens C source files, but also makes the widgets structure clear.</p>
<p>Now Ill show you <code>app_open</code> function in the C file <code>tfe3.c</code>.</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb5-1"><a href="#cb5-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb5-2"><a href="#cb5-2"></a>app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {</span>
<span id="cb5-3"><a href="#cb5-3"></a> GtkWidget *win;</span>
<span id="cb5-4"><a href="#cb5-4"></a> GtkWidget *nb;</span>
<span id="cb5-5"><a href="#cb5-5"></a> GtkWidget *lab;</span>
<span id="cb5-6"><a href="#cb5-6"></a> GtkNotebookPage *nbp;</span>
<span id="cb5-7"><a href="#cb5-7"></a> GtkWidget *scr;</span>
<span id="cb5-8"><a href="#cb5-8"></a> GtkWidget *tv;</span>
<span id="cb5-9"><a href="#cb5-9"></a> GtkTextBuffer *tb;</span>
<span id="cb5-10"><a href="#cb5-10"></a> <span class="dt">char</span> *contents;</span>
<span id="cb5-11"><a href="#cb5-11"></a> gsize length;</span>
<span id="cb5-12"><a href="#cb5-12"></a> <span class="dt">char</span> *filename;</span>
<span id="cb5-13"><a href="#cb5-13"></a> <span class="dt">int</span> i;</span>
<span id="cb5-14"><a href="#cb5-14"></a> GtkBuilder *build;</span>
&gt; app = gtk_application_new (&quot;com.github.ToshioCP.tfe3&quot;, G_APPLICATION_HANDLES_OPEN);
144a107
&gt; </code></pre>
<p><code>60,103c61,65</code> means 44 (=103-60+1) lines are changed to 5
(=65-61+1) lines. Therefore, 39 lines are reduced. Using ui file not
only shortens C source files, but also makes the widgets structure
clear.</p>
<p>Now Ill show you <code>app_open</code> function in the C file
<code>tfe3.c</code>.</p>
<div class="sourceCode" id="cb5"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb5-1"><a href="#cb5-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb5-2"><a href="#cb5-2"></a>app_open <span class="op">(</span>GApplication <span class="op">*</span>app<span class="op">,</span> GFile <span class="op">**</span> files<span class="op">,</span> gint n_files<span class="op">,</span> gchar <span class="op">*</span>hint<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-3"><a href="#cb5-3"></a> GtkWidget <span class="op">*</span>win<span class="op">;</span></span>
<span id="cb5-4"><a href="#cb5-4"></a> GtkWidget <span class="op">*</span>nb<span class="op">;</span></span>
<span id="cb5-5"><a href="#cb5-5"></a> GtkWidget <span class="op">*</span>lab<span class="op">;</span></span>
<span id="cb5-6"><a href="#cb5-6"></a> GtkNotebookPage <span class="op">*</span>nbp<span class="op">;</span></span>
<span id="cb5-7"><a href="#cb5-7"></a> GtkWidget <span class="op">*</span>scr<span class="op">;</span></span>
<span id="cb5-8"><a href="#cb5-8"></a> GtkWidget <span class="op">*</span>tv<span class="op">;</span></span>
<span id="cb5-9"><a href="#cb5-9"></a> GtkTextBuffer <span class="op">*</span>tb<span class="op">;</span></span>
<span id="cb5-10"><a href="#cb5-10"></a> <span class="dt">char</span> <span class="op">*</span>contents<span class="op">;</span></span>
<span id="cb5-11"><a href="#cb5-11"></a> gsize length<span class="op">;</span></span>
<span id="cb5-12"><a href="#cb5-12"></a> <span class="dt">char</span> <span class="op">*</span>filename<span class="op">;</span></span>
<span id="cb5-13"><a href="#cb5-13"></a> <span class="dt">int</span> i<span class="op">;</span></span>
<span id="cb5-14"><a href="#cb5-14"></a> GtkBuilder <span class="op">*</span>build<span class="op">;</span></span>
<span id="cb5-15"><a href="#cb5-15"></a></span>
<span id="cb5-16"><a href="#cb5-16"></a> build = gtk_builder_new_from_file (<span class="st">&quot;tfe3.ui&quot;</span>);</span>
<span id="cb5-17"><a href="#cb5-17"></a> win = GTK_WIDGET (gtk_builder_get_object (build, <span class="st">&quot;win&quot;</span>));</span>
<span id="cb5-18"><a href="#cb5-18"></a> gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));</span>
<span id="cb5-19"><a href="#cb5-19"></a> nb = GTK_WIDGET (gtk_builder_get_object (build, <span class="st">&quot;nb&quot;</span>));</span>
<span id="cb5-20"><a href="#cb5-20"></a> g_object_unref(build);</span>
<span id="cb5-21"><a href="#cb5-21"></a> <span class="cf">for</span> (i = <span class="dv">0</span>; i &lt; n_files; i++) {</span>
<span id="cb5-22"><a href="#cb5-22"></a> <span class="cf">if</span> (g_file_load_contents (files[i], NULL, &amp;contents, &amp;length, NULL, NULL)) {</span>
<span id="cb5-23"><a href="#cb5-23"></a> scr = gtk_scrolled_window_new ();</span>
<span id="cb5-24"><a href="#cb5-24"></a> tv = tfe_text_view_new ();</span>
<span id="cb5-25"><a href="#cb5-25"></a> tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));</span>
<span id="cb5-26"><a href="#cb5-26"></a> gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);</span>
<span id="cb5-27"><a href="#cb5-27"></a> gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);</span>
<span id="cb5-16"><a href="#cb5-16"></a> build <span class="op">=</span> gtk_builder_new_from_file <span class="op">(</span><span class="st">&quot;tfe3.ui&quot;</span><span class="op">);</span></span>
<span id="cb5-17"><a href="#cb5-17"></a> win <span class="op">=</span> GTK_WIDGET <span class="op">(</span>gtk_builder_get_object <span class="op">(</span>build<span class="op">,</span> <span class="st">&quot;win&quot;</span><span class="op">));</span></span>
<span id="cb5-18"><a href="#cb5-18"></a> gtk_window_set_application <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> GTK_APPLICATION <span class="op">(</span>app<span class="op">));</span></span>
<span id="cb5-19"><a href="#cb5-19"></a> nb <span class="op">=</span> GTK_WIDGET <span class="op">(</span>gtk_builder_get_object <span class="op">(</span>build<span class="op">,</span> <span class="st">&quot;nb&quot;</span><span class="op">));</span></span>
<span id="cb5-20"><a href="#cb5-20"></a> g_object_unref<span class="op">(</span>build<span class="op">);</span></span>
<span id="cb5-21"><a href="#cb5-21"></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_files<span class="op">;</span> i<span class="op">++)</span> <span class="op">{</span></span>
<span id="cb5-22"><a href="#cb5-22"></a> <span class="cf">if</span> <span class="op">(</span>g_file_load_contents <span class="op">(</span>files<span class="op">[</span>i<span class="op">],</span> NULL<span class="op">,</span> <span class="op">&amp;</span>contents<span class="op">,</span> <span class="op">&amp;</span>length<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">))</span> <span class="op">{</span></span>
<span id="cb5-23"><a href="#cb5-23"></a> scr <span class="op">=</span> gtk_scrolled_window_new <span class="op">();</span></span>
<span id="cb5-24"><a href="#cb5-24"></a> tv <span class="op">=</span> tfe_text_view_new <span class="op">();</span></span>
<span id="cb5-25"><a href="#cb5-25"></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="cb5-26"><a href="#cb5-26"></a> gtk_text_view_set_wrap_mode <span class="op">(</span>GTK_TEXT_VIEW <span class="op">(</span>tv<span class="op">),</span> GTK_WRAP_WORD_CHAR<span class="op">);</span></span>
<span id="cb5-27"><a href="#cb5-27"></a> gtk_scrolled_window_set_child <span class="op">(</span>GTK_SCROLLED_WINDOW <span class="op">(</span>scr<span class="op">),</span> tv<span class="op">);</span></span>
<span id="cb5-28"><a href="#cb5-28"></a></span>
<span id="cb5-29"><a href="#cb5-29"></a> tfe_text_view_set_file (TFE_TEXT_VIEW (tv), g_file_dup (files[i]));</span>
<span id="cb5-30"><a href="#cb5-30"></a> gtk_text_buffer_set_text (tb, contents, length);</span>
<span id="cb5-31"><a href="#cb5-31"></a> g_free (contents);</span>
<span id="cb5-32"><a href="#cb5-32"></a> filename = g_file_get_basename (files[i]);</span>
<span id="cb5-33"><a href="#cb5-33"></a> lab = gtk_label_new (filename);</span>
<span id="cb5-34"><a href="#cb5-34"></a> gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);</span>
<span id="cb5-35"><a href="#cb5-35"></a> nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);</span>
<span id="cb5-36"><a href="#cb5-36"></a> g_object_set (nbp, <span class="st">&quot;tab-expand&quot;</span>, TRUE, NULL);</span>
<span id="cb5-37"><a href="#cb5-37"></a> g_free (filename);</span>
<span id="cb5-38"><a href="#cb5-38"></a> } <span class="cf">else</span> <span class="cf">if</span> ((filename = g_file_get_path (files[i])) != NULL) {</span>
<span id="cb5-39"><a href="#cb5-39"></a> g_print (<span class="st">&quot;No such file: %s.</span><span class="sc">\n</span><span class="st">&quot;</span>, filename);</span>
<span id="cb5-40"><a href="#cb5-40"></a> g_free (filename);</span>
<span id="cb5-41"><a href="#cb5-41"></a> } <span class="cf">else</span></span>
<span id="cb5-42"><a href="#cb5-42"></a> g_print (<span class="st">&quot;No valid file is given</span><span class="sc">\n</span><span class="st">&quot;</span>);</span>
<span id="cb5-43"><a href="#cb5-43"></a> }</span>
<span id="cb5-44"><a href="#cb5-44"></a> <span class="cf">if</span> (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) &gt; <span class="dv">0</span>) {</span>
<span id="cb5-45"><a href="#cb5-45"></a> gtk_widget_show (win);</span>
<span id="cb5-46"><a href="#cb5-46"></a> } <span class="cf">else</span></span>
<span id="cb5-47"><a href="#cb5-47"></a> gtk_window_destroy (GTK_WINDOW (win));</span>
<span id="cb5-48"><a href="#cb5-48"></a>}</span></code></pre></div>
<p>The whole source code of <code>tfe3.c</code> is stored in src/tfe directory. If you want to see it, click the link above.</p>
<span id="cb5-29"><a href="#cb5-29"></a> tfe_text_view_set_file <span class="op">(</span>TFE_TEXT_VIEW <span class="op">(</span>tv<span class="op">),</span> g_file_dup <span class="op">(</span>files<span class="op">[</span>i<span class="op">]));</span></span>
<span id="cb5-30"><a href="#cb5-30"></a> gtk_text_buffer_set_text <span class="op">(</span>tb<span class="op">,</span> contents<span class="op">,</span> length<span class="op">);</span></span>
<span id="cb5-31"><a href="#cb5-31"></a> g_free <span class="op">(</span>contents<span class="op">);</span></span>
<span id="cb5-32"><a href="#cb5-32"></a> filename <span class="op">=</span> g_file_get_basename <span class="op">(</span>files<span class="op">[</span>i<span class="op">]);</span></span>
<span id="cb5-33"><a href="#cb5-33"></a> lab <span class="op">=</span> gtk_label_new <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb5-34"><a href="#cb5-34"></a> gtk_notebook_append_page <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">),</span> scr<span class="op">,</span> lab<span class="op">);</span></span>
<span id="cb5-35"><a href="#cb5-35"></a> nbp <span class="op">=</span> gtk_notebook_get_page <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">),</span> scr<span class="op">);</span></span>
<span id="cb5-36"><a href="#cb5-36"></a> g_object_set <span class="op">(</span>nbp<span class="op">,</span> <span class="st">&quot;tab-expand&quot;</span><span class="op">,</span> TRUE<span class="op">,</span> NULL<span class="op">);</span></span>
<span id="cb5-37"><a href="#cb5-37"></a> g_free <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb5-38"><a href="#cb5-38"></a> <span class="op">}</span> <span class="cf">else</span> <span class="cf">if</span> <span class="op">((</span>filename <span class="op">=</span> g_file_get_path <span class="op">(</span>files<span class="op">[</span>i<span class="op">]))</span> <span class="op">!=</span> NULL<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-39"><a href="#cb5-39"></a> g_print <span class="op">(</span><span class="st">&quot;No such file: %s.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">,</span> filename<span class="op">);</span></span>
<span id="cb5-40"><a href="#cb5-40"></a> g_free <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb5-41"><a href="#cb5-41"></a> <span class="op">}</span> <span class="cf">else</span></span>
<span id="cb5-42"><a href="#cb5-42"></a> g_print <span class="op">(</span><span class="st">&quot;No valid file is given</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb5-43"><a href="#cb5-43"></a> <span class="op">}</span></span>
<span id="cb5-44"><a href="#cb5-44"></a> <span class="cf">if</span> <span class="op">(</span>gtk_notebook_get_n_pages <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">))</span> <span class="op">&gt;</span> <span class="dv">0</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-45"><a href="#cb5-45"></a> gtk_widget_show <span class="op">(</span>win<span class="op">);</span></span>
<span id="cb5-46"><a href="#cb5-46"></a> <span class="op">}</span> <span class="cf">else</span></span>
<span id="cb5-47"><a href="#cb5-47"></a> gtk_window_destroy <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">));</span></span>
<span id="cb5-48"><a href="#cb5-48"></a><span class="op">}</span></span></code></pre></div>
<p>The whole source code of <code>tfe3.c</code> is stored in src/tfe
directory.</p>
<h3 id="using-ui-string">Using ui string</h3>
<p>GtkBuilder can build widgets using string. Use the function <code>gtk_builder_new_from_string</code> instead of <code>gtk_builder_new_from_file</code>.</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"></a><span class="dt">char</span> *uistring;</span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true"></a></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true"></a>uistring =</span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true"></a><span class="st">&quot;&lt;interface&gt;&quot;</span></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true"></a> <span class="st">&quot;&lt;object class=&quot;</span>GtkApplicationWindow<span class="st">&quot; id=&quot;</span>win<span class="st">&quot;&gt;&quot;</span></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true"></a> <span class="st">&quot;&lt;property name=</span><span class="sc">\&quot;</span><span class="st">title</span><span class="sc">\&quot;</span><span class="st">&gt;file editor&lt;/property&gt;&quot;</span></span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true"></a> <span class="st">&quot;&lt;property name=</span><span class="sc">\&quot;</span><span class="st">default-width</span><span class="sc">\&quot;</span><span class="st">&gt;600&lt;/property&gt;&quot;</span></span>
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true"></a> <span class="st">&quot;&lt;property name=</span><span class="sc">\&quot;</span><span class="st">default-height</span><span class="sc">\&quot;</span><span class="st">&gt;400&lt;/property&gt;&quot;</span></span>
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true"></a> <span class="st">&quot;&lt;child&gt;&quot;</span></span>
<span id="cb6-10"><a href="#cb6-10" aria-hidden="true"></a> <span class="st">&quot;&lt;object class=</span><span class="sc">\&quot;</span><span class="st">GtkBox</span><span class="sc">\&quot;</span><span class="st"> id=</span><span class="sc">\&quot;</span><span class="st">boxv</span><span class="sc">\&quot;</span><span class="st">&gt;&quot;</span></span>
<span id="cb6-11"><a href="#cb6-11" aria-hidden="true"></a> <span class="st">&quot;&lt;property name=&quot;</span>orientation<span class="st">&quot;&gt;GTK_ORIENTATION_VERTICAL&lt;/property&gt;&quot;</span></span>
<span id="cb6-12"><a href="#cb6-12" aria-hidden="true"></a>... ... ...</span>
<span id="cb6-13"><a href="#cb6-13" aria-hidden="true"></a>... ... ...</span>
<span id="cb6-14"><a href="#cb6-14" aria-hidden="true"></a><span class="st">&quot;&lt;/interface&gt;&quot;</span>;</span>
<span id="cb6-15"><a href="#cb6-15" aria-hidden="true"></a></span>
<span id="cb6-16"><a href="#cb6-16" aria-hidden="true"></a>build = gtk_builder_new_from_stringfile (uistring);</span></code></pre></div>
<p>This method has an advantage and disadvantage. The advantage is that the ui string is written in the source code. So ui file is not necessary on runtime. The disadvantage is that writing C string is a bit bothersome because of the double quotes. If you want to use this method, you should write a script that transforms ui file into C-string.</p>
<p>GtkBuilder can build widgets with string. Use
<code>gtk_builder_new_from_string</code> instead of
<code>gtk_builder_new_from_file</code>.</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">char</span> <span class="op">*</span>uistring<span class="op">;</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a>uistring <span class="op">=</span></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a><span class="st">&quot;&lt;interface&gt;&quot;</span></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;&lt;object class=&quot;</span>GtkApplicationWindow<span class="st">&quot; id=&quot;</span>win<span class="st">&quot;&gt;&quot;</span></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;&lt;property name=</span><span class="sc">\&quot;</span><span class="st">title</span><span class="sc">\&quot;</span><span class="st">&gt;file editor&lt;/property&gt;&quot;</span></span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;&lt;property name=</span><span class="sc">\&quot;</span><span class="st">default-width</span><span class="sc">\&quot;</span><span class="st">&gt;600&lt;/property&gt;&quot;</span></span>
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;&lt;property name=</span><span class="sc">\&quot;</span><span class="st">default-height</span><span class="sc">\&quot;</span><span class="st">&gt;400&lt;/property&gt;&quot;</span></span>
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;&lt;child&gt;&quot;</span></span>
<span id="cb6-10"><a href="#cb6-10" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;&lt;object class=</span><span class="sc">\&quot;</span><span class="st">GtkBox</span><span class="sc">\&quot;</span><span class="st"> id=</span><span class="sc">\&quot;</span><span class="st">boxv</span><span class="sc">\&quot;</span><span class="st">&gt;&quot;</span></span>
<span id="cb6-11"><a href="#cb6-11" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;&lt;property name=&quot;</span>orientation<span class="st">&quot;&gt;GTK_ORIENTATION_VERTICAL&lt;/property&gt;&quot;</span></span>
<span id="cb6-12"><a href="#cb6-12" aria-hidden="true" tabindex="-1"></a><span class="op">...</span> <span class="op">...</span> <span class="op">...</span></span>
<span id="cb6-13"><a href="#cb6-13" aria-hidden="true" tabindex="-1"></a><span class="op">...</span> <span class="op">...</span> <span class="op">...</span></span>
<span id="cb6-14"><a href="#cb6-14" aria-hidden="true" tabindex="-1"></a><span class="st">&quot;&lt;/interface&gt;&quot;</span><span class="op">;</span></span>
<span id="cb6-15"><a href="#cb6-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-16"><a href="#cb6-16" aria-hidden="true" tabindex="-1"></a>build <span class="op">=</span> gtk_builder_new_from_string <span class="op">(</span>uistring<span class="op">,</span> <span class="op">-</span><span class="dv">1</span><span class="op">);</span></span></code></pre></div>
<p>This method has an advantage and disadvantage. The advantage is that
the ui string is written in the source code. So, no ui file is needed on
runtime. The disadvantage is that writing C string is a bit bothersome
because of the double quotes. If you want to use this method, you should
write a script that transforms ui file into C-string.</p>
<ul>
<li>Add backslash before each double quote.</li>
<li>Add double quotes at the left and right of the string in each line.</li>
<li>Add double quotes at the left and right of the string in each
line.</li>
</ul>
<h3 id="using-gresource">Using Gresource</h3>
<p>Using Gresource is similar to using string. But Gresource is compressed binary data, not text data. And theres a compiler that compiles ui file into Gresource. It can compile not only text files but also binary files such as images, sounds and so on. And after compilation, it bundles them up into one Gresource object.</p>
<p>An xml file is necessary for the resource compiler <code>glib-compile-resources</code>. It describes resource files.</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode numberSource xml numberLines"><code class="sourceCode xml"><span id="cb7-1"><a href="#cb7-1"></a><span class="kw">&lt;?xml</span> version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;<span class="kw">?&gt;</span></span>
<span id="cb7-2"><a href="#cb7-2"></a><span class="kw">&lt;gresources&gt;</span></span>
<span id="cb7-3"><a href="#cb7-3"></a> <span class="kw">&lt;gresource</span><span class="ot"> prefix=</span><span class="st">&quot;/com/github/ToshioCP/tfe3&quot;</span><span class="kw">&gt;</span></span>
<span id="cb7-4"><a href="#cb7-4"></a> <span class="kw">&lt;file&gt;</span>tfe3.ui<span class="kw">&lt;/file&gt;</span></span>
<span id="cb7-5"><a href="#cb7-5"></a> <span class="kw">&lt;/gresource&gt;</span></span>
<span id="cb7-6"><a href="#cb7-6"></a><span class="kw">&lt;/gresources&gt;</span></span></code></pre></div>
<h3 id="gresource">Gresource</h3>
<p>Using Gresource is similar to using string. But Gresource is
compressed binary data, not text data. And theres a compiler that
compiles ui file into Gresource. It can compile not only text files but
also binary files such as images, sounds and so on. And after
compilation, it bundles them up into one Gresource object.</p>
<p>An xml file is necessary for the resource compiler
<code>glib-compile-resources</code>. It describes resource files.</p>
<div class="sourceCode" id="cb7"><pre
class="sourceCode numberSource xml numberLines"><code class="sourceCode xml"><span id="cb7-1"><a href="#cb7-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="cb7-2"><a href="#cb7-2"></a>&lt;<span class="kw">gresources</span>&gt;</span>
<span id="cb7-3"><a href="#cb7-3"></a> &lt;<span class="kw">gresource</span><span class="ot"> prefix=</span><span class="st">&quot;/com/github/ToshioCP/tfe3&quot;</span>&gt;</span>
<span id="cb7-4"><a href="#cb7-4"></a> &lt;<span class="kw">file</span>&gt;tfe3.ui&lt;/<span class="kw">file</span>&gt;</span>
<span id="cb7-5"><a href="#cb7-5"></a> &lt;/<span class="kw">gresource</span>&gt;</span>
<span id="cb7-6"><a href="#cb7-6"></a>&lt;/<span class="kw">gresources</span>&gt;</span></code></pre></div>
<ul>
<li>2: <code>gresources</code> tag can include multiple gresources (gresource tags). However, this xml has only one gresource.</li>
<li>3: The gresource has a prefix <code>/com/github/ToshioCP/tfe3</code>.</li>
<li>4: The gresource has <code>tfe3.ui</code>. And it is pointed by <code>/com/github/ToshioCP/tfe3/tfe3.ui</code> because it needs prefix. If you want to add more files, then insert them between line 4 and 5.</li>
<li>2: <code>gresources</code> tag can include multiple gresources
(gresource tags). However, this xml has only one gresource.</li>
<li>3: The gresource has a prefix
<code>/com/github/ToshioCP/tfe3</code>.</li>
<li>4: The name of the gresource is <code>tfe3.ui</code>. The resource
will be pointed with <code>/com/github/ToshioCP/tfe3/tfe3.ui</code> by
GtkBuilder. The pattern is “prefix” + “name”. If you want to add more
files, insert them between line 4 and 5.</li>
</ul>
<p>Save this xml text to <code>tfe3.gresource.xml</code>. The gresource compiler <code>glib-compile-resources</code> shows its usage with the argument <code>--help</code>.</p>
<p>Save this xml text to <code>tfe3.gresource.xml</code>. The gresource
compiler <code>glib-compile-resources</code> shows its usage with the
argument <code>--help</code>.</p>
<pre><code>$ LANG=C glib-compile-resources --help
Usage:
glib-compile-resources [OPTION?] FILE
@ -475,14 +560,20 @@ Application Options:
</code></pre>
<p>Now run the compiler.</p>
<pre><code>$ glib-compile-resources tfe3.gresource.xml --target=resources.c --generate-source</code></pre>
<p>Then a C source file <code>resources.c</code> is generated. Modify <code>tfe3.c</code> and save it as <code>tfe3_r.c</code>.</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true"></a><span class="pp">#include </span><span class="im">&quot;resources.c&quot;</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true"></a>... ... ...</span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true"></a>... ... ...</span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true"></a>build = gtk_builder_new_from_resource (<span class="st">&quot;/com/github/ToshioCP/tfe3/tfe3.ui&quot;</span>);</span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true"></a>... ... ...</span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true"></a>... ... ...</span></code></pre></div>
<p>Then, compile and run it. The window appears and it is the same as the screenshot at the beginning of this page.</p>
<p>Then a C source file <code>resources.c</code> is generated. Modify
<code>tfe3.c</code> and save it as <code>tfe3_r.c</code>.</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&quot;resources.c&quot;</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a><span class="op">...</span> <span class="op">...</span> <span class="op">...</span></span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a><span class="op">...</span> <span class="op">...</span> <span class="op">...</span></span>
<span id="cb10-4"><a href="#cb10-4" 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/tfe3/tfe3.ui&quot;</span><span class="op">);</span></span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a><span class="op">...</span> <span class="op">...</span> <span class="op">...</span></span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a><span class="op">...</span> <span class="op">...</span> <span class="op">...</span></span></code></pre></div>
<p>The function <code>gtk_builder_new_from_resource</code> builds
widgets from a resource.</p>
<p>Then, compile and run it. A window appears and it is the same as the
screenshot at the beginning of this page.</p>
<p>Generally, resource is the best way for C language. If you use other
languages like Ruby, string is better than resource.</p>
</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>

View file

@ -5,7 +5,7 @@
<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>Gtk4 tutorial</title>
<title>GTK 4 tutorial</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
@ -106,7 +106,8 @@
</div>
</nav>
<h1 id="tfetextview-api-reference">TfeTextView API reference</h1>
<p>TfeTextView Child object of GtkTextView. It holds GFile which the contents of GtkTextBuffer correponds to.</p>
<p>TfeTextView Child object of GtkTextView. It is connected to a
certain file.</p>
<h2 id="functions">Functions</h2>
<ul>
<li>GFile *tfe_text_view_get_file ()</li>
@ -136,7 +137,8 @@
<h2 id="includes">Includes</h2>
<pre><code>#include &lt;gtk/gtk.h&gt;</code></pre>
<h2 id="description">Description</h2>
<p>TfeTextView holds GFile which the contents of GtkTextBuffer corresponds to. File manipulation functions are added to this object.</p>
<p>TfeTextView holds GFile corresponds to the contents of GtkTextBuffer.
It has some file manipulation functions.</p>
<h2 id="functions-1">Functions</h2>
<h3 id="tfe_text_view_get_file">tfe_text_view_get_file()</h3>
<pre><code>GFile *
@ -149,7 +151,13 @@ tfe_text_view_get_file (TfeTextView *tv);</code></pre>
<h3 id="tfe_text_view_open">tfe_text_view_open()</h3>
<pre><code>void
tfe_text_view_open (TfeTextView *tv, GtkWidget *win);</code></pre>
<p>Just shows a GtkFileChooserDialog so that a user can choose a file to read. This function doesnt do any I/O operations. They are done by the signal handler connected to the <code>response</code> signal emitted by GtkFileChooserDialog. Therefore the caller cant know the I/O status directly from the function. Instead, the status is informed by <code>open-response</code> signal. The caller needs to set a handler to this signal in advance.</p>
<p>Just shows a GtkFileChooserDialog so that a user can choose a file to
read. This function doesnt do any I/O operations. They are done by the
signal handler connected to the <code>response</code> signal emitted by
GtkFileChooserDialog. Therefore the caller cant know the I/O status
directly from <code>tfe_text_view_open</code>. Instead, the status is
informed by <code>open-response</code> signal. The caller needs to set a
handler to this signal in advance.</p>
<p>parameters</p>
<ul>
<li>tv: a TfeTextView</li>
@ -158,7 +166,9 @@ tfe_text_view_open (TfeTextView *tv, GtkWidget *win);</code></pre>
<h3 id="tfe_text_view_save">tfe_text_view_save()</h3>
<pre><code>void
tfe_text_view_save (TfeTextView *tv);</code></pre>
<p>Saves the contents of a TfeTextView to a file. If <code>tv</code> holds a GFile, it is used. Otherwise, this function shows GtkFileChosserDialog so that a user can choose a file to save.</p>
<p>Saves the contents of the TfeTextView to a file. If <code>tv</code>
holds a GFile, it is used. Otherwise, this function shows
GtkFileChooserDialog so that the user can choose a file to save.</p>
<p>Parameters</p>
<ul>
<li>tv: a TfeTextView</li>
@ -166,7 +176,8 @@ tfe_text_view_save (TfeTextView *tv);</code></pre>
<h3 id="tfe_text_view_saveas">tfe_text_view_saveas()</h3>
<pre><code>void
tfe_text_view_saveas (TfeTextView *tv);</code></pre>
<p>Saves the content of a TfeTextView to a file. This function shows GtkFileChosserDialog so that a user can choose a file to save.</p>
<p>Saves the content of a TfeTextView to a file. This function shows
GtkFileChooserDialog so that a user can choose a file to save.</p>
<p>Parameters</p>
<ul>
<li>tv: a TfeTextView</li>
@ -174,7 +185,10 @@ tfe_text_view_saveas (TfeTextView *tv);</code></pre>
<h3 id="tfe_text_view_new_with_file">tfe_text_view_new_with_file()</h3>
<pre><code>GtkWidget *
tfe_text_view_new_with_file (GFile *file);</code></pre>
<p>Creates a new TfeTextView and reads the contents of the <code>file</code> and set it to the GtkTextBuffer corresponds to the newly created TfeTextView. Then returns the TfeTextView as GtkWidget. If an error happens, it returns <code>NULL</code>.</p>
<p>Creates a new TfeTextView and reads the contents of the
<code>file</code> and set it to the GtkTextBuffer corresponds to the
newly created TfeTextView. Then returns the TfeTextView as GtkWidget. If
an error happens, it returns <code>NULL</code>.</p>
<p>Parameters</p>
<ul>
<li>file: a GFile</li>
@ -186,7 +200,8 @@ tfe_text_view_new_with_file (GFile *file);</code></pre>
<h3 id="tfe_text_view_new">tfe_text_view_new()</h3>
<pre><code>GtkWidget *
tfe_text_view_new (void);</code></pre>
<p>Creates a new TfeTextView and returns the TfeTextView as GtkWidget. If an error happens, it returns <code>NULL</code>.</p>
<p>Creates a new TfeTextView and returns the TfeTextView as GtkWidget.
If an error happens, it returns <code>NULL</code>.</p>
<p>Returns</p>
<ul>
<li>a new TfeTextView.</li>
@ -199,37 +214,50 @@ struct _TfeTextView
GtkTextView parent;
GFile *file;
};</code></pre>
<p>The members of this structure are not allowed to be accessed by any outer objects. If you want to obtain a copy of the GFile, use <code>tfe_text_view_get_file</code>.</p>
<p>The members of this structure are not allowed to be accessed by any
outer objects. If you want to obtain a copy of the GFile, use
<code>tfe_text_view_get_file</code>.</p>
<h3 id="tfetextviewclass">TfeTextViewClass</h3>
<pre><code>typedef struct {
GtkTextViewClass parent_class;
} TfeTextViewClass;</code></pre>
<p>No member is added because TfeTextView is a final type object.</p>
<h3 id="enum-tfetextviewopenresponsetype">enum TfeTextViewOpenResponseType</h3>
<p>Predefined values for the response id given by <code>open-response</code> signal.</p>
<h3 id="enum-tfetextviewopenresponsetype">enum
TfeTextViewOpenResponseType</h3>
<p>Predefined values for the response id given by
<code>open-response</code> signal.</p>
<p>Members:</p>
<ul>
<li>TFE_OPEN_RESPONSE_SUCCESS: The file is successfully opened.</li>
<li>TFE_OPEN_RESPONSE_CANCEL: Reading file is canceled by the user.</li>
<li>TFE_OPEN_RESPONSE_ERROR: An error happened during the opening or reading process.</li>
<li>TFE_OPEN_RESPONSE_ERROR: An error happened during the opening or
reading process.</li>
</ul>
<h2 id="properties">Properties</h2>
<h3 id="wrap-mode">wrap-mode</h3>
<p>The property “wrap-mode” belongs to GtkTextView. TfeTextView inherits
it and the value is set to GTK_WRAP_WORD_CHAR as a default.</p>
<h2 id="signals-1">Signals</h2>
<h3 id="change-file">change-file</h3>
<pre><code>void
user_function (TfeTextView *tv,
gpointer user_data)</code></pre>
<p>Emitted when the GFile in the TfeTextView object is changed. The signal is emitted when:</p>
<p>Emitted when the GFile in the TfeTextView object is changed. The
signal is emitted when:</p>
<ul>
<li>a new file is opened and read</li>
<li>a user choose a file with GtkFileChooserDialog and save the contents.</li>
<li>an error occured during I/O operation, and GFile is removed as a result.</li>
<li>a user choose a file with GtkFileChooserDialog and save the
contents.</li>
<li>an error occured during I/O operation, and GFile is removed as a
result.</li>
</ul>
<h3 id="open-response">open-response</h3>
<pre><code>void
user_function (TfeTextView *tv,
TfeTextViewOpenResponseType response-id,
gpointer user_data)</code></pre>
<p>Emitted after the user calls <code>tfe_text_view_open</code>. This signal informs the status of file opening and reading.</p>
<p>Emitted after the user calls <code>tfe_text_view_open</code>. This
signal informs the status of file opening and reading.</p>
</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>

View file

@ -8,45 +8,42 @@ Up: [README.md](../README.md), Next: [Section 2](sec2.md)
This tutorial is about GTK 4 libraries.
It is originally used on Linux with C compiler, but now it is used more widely, on Windows and MacOS, with Vala, Python and so on.
However, this tutorial describes only _C programs on Linux_.
However, this tutorial describes only *C programs on Linux*.
If you want to try the examples in the tutorial, you need:
- PC with Linux distribution like Ubuntu, Debian and so on.
- Gcc.
- PC with Linux distribution like Ubuntu or Debian.
- GCC.
- GTK 4.
The stable version of Gtk on Linux distributions is version three at present.
You need to install GTK 4 to your computer.
See [Section 3](sec3.md) for the installation of GTK 4.
The stable version of GTK is 4.8.2 at present (13/Dec/2022), but older version (4.0 or higher) may work.
See [Section 3](sec3.md) for the installation for GTK 4.
### Ruby and rake for making the document
This repository includes Ruby programs.
They are used to make Markdown files, HTML files, Latex files and a PDF file.
They are used to make GFM (GitHub Flavoured Markdown) files, HTML files, Latex files and a PDF file.
You need:
- Linux distribution like Ubuntu.
- Linux.
- Ruby programming language.
There are two ways to install it.
One is installing the distribution's package.
The other is using rbenv and ruby-build.
If you want to use the latest version of ruby, use rbenv.
- Rake.
It is a gem, which is a library written in Ruby.
You can install it as a package of your distribution or use gem command.
You don't need to install it separately because it is a standard library of Ruby.
## License
Copyright (C) 2020 ToshioCP (Toshio Sekiya)
Copyright (C) 2020-2022 ToshioCP (Toshio Sekiya)
Gtk4-tutorial repository contains the tutorial document and software such as converters, generators and controllers.
All of them make up the 'Gtk4-tutorial' package.
This package is simply called 'Gtk4-tutorial' in the following description.
'Gtk4-tutorial' is free; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License or, at your option, any later version.
GTK4-tutorial repository contains tutorial documents and programs such as converters, generators and controllers.
All of them make up the 'GTK4-tutorial' package.
This package is simply called 'GTK4-tutorial' in the following description.
'GTK4-tutorial' is free; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License or, at your option, any later version.
'Gtk4-tutorial' is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
'GTK4-tutorial' is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the [GNU General Public License](https://www.gnu.org/licenses/gpl-3.0.html) for more details.
Up: [README.md](../README.md), Next: [Section 2](sec2.md)

View file

@ -1,13 +1,13 @@
Up: [Readme.md](../Readme.md), Prev: [Section 9](sec9.md), Next: [Section 11](sec11.md)
Up: [README.md](../README.md), Prev: [Section 9](sec9.md), Next: [Section 11](sec11.md)
# Build system
## What do we need to think about to manage big source files?
## Managing big source files
We've compiled a small editor so far.
But Some bad signs are beginning to appear.
- We've had only one C source file and put everything into it.
- We've had only one C source file and put everything in it.
We need to sort it out.
- There are two compilers, `gcc` and `glib-compile-resources`.
We should control them by one building tool.
@ -16,7 +16,7 @@ These ideas are useful to manage big source files.
## Divide a C source file into two parts.
When you divide C source file into several parts, each file should contain only one thing.
When you divide C source file into several parts, each file should contain one thing.
For example, our source has two things, the definition of TfeTextView and functions related to GtkApplication and GtkApplicationWindow.
It is a good idea to separate them into two files, `tfetextview.c` and `tfe.c`.
@ -105,12 +105,12 @@ All the source files are listed below.
2 #include "tfetextview.h"
3
4 static void
5 app_activate (GApplication *app, gpointer user_data) {
5 app_activate (GApplication *app) {
6 g_print ("You need a filename argument.\n");
7 }
8
9 static void
10 app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
10 app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint) {
11 GtkWidget *win;
12 GtkWidget *nb;
13 GtkWidget *lab;
@ -186,10 +186,8 @@ The ui file `tfe.ui` is the same as `tfe3.ui` in the previous section.
6 </gresources>
~~~
## Make
Dividing a file makes it easy to maintain source files.
But now we are faced with a new problem.
But now we face a new problem.
The building step increases.
- Compiling the ui file `tfe.ui` into `resources.c`.
@ -198,141 +196,18 @@ The building step increases.
- Compiling `resources.c` into `resources.o`.
- Linking all the object files into application `tfe`.
Now build tool is necessary to manage it.
Make is one of the build tools.
It was created in 1976.
It is an old and widely used program.
Build tools manage the steps.
I'll show you three build tools, Meson and Ninja, Make and Rake.
Meson and Ninja is recommended as a C build tool, but others are also fine.
It's your choice.
Make analyzes Makefile and executes compilers.
All instructions are written in Makefile.
## Meson and Ninja
~~~makefile
sample.o: sample.c
gcc -o sample.o sample.c
~~~
Meson and Ninja is one of the most popular building tool to build C language program.
Many developers use Meson and Ninja lately.
For example, GTK 4 uses them.
The sample of Malefile above consists of three elements, `sample.o`, `sample.c` and `gcc -o sample.o sample.c`.
- `sample.o` is called target.
- `sample.c` is prerequisite.
- `gcc -o sample.o sample.c` is recipe.
Recipes follow tab characters, not spaces.
(It is very important. Use tab not space, or make won't work as you expected).
The rule is:
If a prerequisite modified later than a target, then make executes the recipe.
In the example above, if `sample.c` is modified after the generation of `sample.o`, then make executes gcc and compile `sample.c` into `sample.o`.
If the modification time of `sample.c` is older then the generation of `sample.o`, then no compiling is necessary, so make does nothing.
The Makefile for `tfe` is as follows.
~~~makefile
1 all: tfe
2
3 tfe: tfe.o tfetextview.o resources.o
4 gcc -o tfe tfe.o tfetextview.o resources.o `pkg-config --libs gtk4`
5
6 tfe.o: tfe.c tfetextview.h
7 gcc -c -o tfe.o `pkg-config --cflags gtk4` tfe.c
8 tfetextview.o: tfetextview.c tfetextview.h
9 gcc -c -o tfetextview.o `pkg-config --cflags gtk4` tfetextview.c
10 resources.o: resources.c
11 gcc -c -o resources.o `pkg-config --cflags gtk4` resources.c
12
13 resources.c: tfe.gresource.xml tfe.ui
14 glib-compile-resources tfe.gresource.xml --target=resources.c --generate-source
15
16 .Phony: clean
17
18 clean:
19 rm -f tfe tfe.o tfetextview.o resources.o resources.c
~~~
You only need to type `make`.
$ make
gcc -c -o tfe.o `pkg-config --cflags gtk4` tfe.c
gcc -c -o tfetextview.o `pkg-config --cflags gtk4` tfetextview.c
glib-compile-resources tfe.gresource.xml --target=resources.c --generate-source
gcc -c -o resources.o `pkg-config --cflags gtk4` resources.c
gcc -o tfe tfe.o tfetextview.o resources.o `pkg-config --libs gtk4`
I used only very basic rules to write this Makefile.
There are many more convenient methods to make it more compact.
But it will be long to explain it.
So I want to finish explaining make and move on to the next topic.
## Rake
Rake is a similar program to make.
It is written in Ruby code.
If you don't use Ruby, you don't need to read this subsection.
However, Ruby is really sophisticated and recommendable script language.
- Rakefile controls the behavior of `rake`.
- You can write any Ruby code in Rakefile.
Rake has task and file task, which is similar to target, prerequisite and recipe in make.
~~~ruby
1 require 'rake/clean'
2
3 targetfile = "tfe"
4 srcfiles = FileList["tfe.c", "tfetextview.c", "resources.c"]
5 rscfile = srcfiles[2]
6 objfiles = srcfiles.gsub(/.c$/, '.o')
7
8 CLEAN.include(targetfile, objfiles, rscfile)
9
10 task default: targetfile
11
12 file targetfile => objfiles do |t|
13 sh "gcc -o #{t.name} #{t.prerequisites.join(' ')} `pkg-config --libs gtk4`"
14 end
15
16 objfiles.each do |obj|
17 src = obj.gsub(/.o$/,'.c')
18 file obj => src do |t|
19 sh "gcc -c -o #{t.name} `pkg-config --cflags gtk4` #{t.source}"
20 end
21 end
22
23 file rscfile => ["tfe.gresource.xml", "tfe.ui"] do |t|
24 sh "glib-compile-resources #{t.prerequisites[0]} --target=#{t.name} --generate-source"
25 end
~~~
The contents of the `Rakefile` is almost same as the `Makefile` in the previous subsection.
- 3-6: Defines target file, source file and so on.
- 1, 8: Loads clean library. And defines CLEAN file list.
The files included by CLEAN will be removed when `rake clean` is typed on the command line.
- 10: The default target depends on `targetfile`.
The task `default` is the final goal of tasks.
- 12-14: `targetfile` depends on `objfiles`.
The variable `t` is a task object.
- `t.name` is a target name
- `t.prerequisites` is an array of prerequisites.
- `t.source` is the first element of prerequisites.
- `sh` is a method to give the following string to shell as an argument and executes the shell.
- 16-21: There's a loop by each element of the array of `objfiles`. Each object depends on corresponding source file.
- 23-25: Resource file depends on xml file and ui file.
Rakefile might seem to be difficult for beginners.
But, you can use any Ruby syntax in Rakefile, so it is really flexible.
If you practice Ruby and Rakefile, it will be highly productive tools.
## Meson and ninja
Meson is one of the most popular building tool despite the developing version.
And ninja is similar to make but much faster than make.
Several years ago, most of the C developers used autotools and make.
But now the situation has changed.
Many developers are using meson and ninja now.
To use meson, you first need to write `meson.build` file.
You need to make `meson.build` file first.
~~~meson
1 project('tfe', 'c')
@ -372,15 +247,146 @@ Then, the executable file `tfe` is generated under the directory `_build`.
$ _build/tfe tfe.c tfetextview.c
Then the window appears.
And two notebook pages are in the window.
One notebook is `tfe.c` and the other is `tfetextview.c`.
A window appears.
It includes a notebook with two pages.
One is `tfe.c` and the other is `tfetextview.c`.
I've shown you three build tools.
I think meson and ninja is the best choice for the present.
For further information, see [The Meson Build system](https://mesonbuild.com/).
We divided a file into some categorized files and used a build tool.
This method is used by many developers.
## Make
Make is a build tool created in 1976.
It was a standard build tool for C compiling, but lately it is replaced by Meson and Ninja.
Up: [Readme.md](../Readme.md), Prev: [Section 9](sec9.md), Next: [Section 11](sec11.md)
Make analyzes Makefile and executes compilers.
All instructions are written in Makefile.
For example,
~~~makefile
sample.o: sample.c
gcc -o sample.o sample.c
~~~
Malefile above consists of three elements, `sample.o`, `sample.c` and `gcc -o sample.o sample.c`.
- `sample.o` is a target.
- `sample.c` is a prerequisite.
- `gcc -o sample.o sample.c` is a recipe.
Recipes follow tab characters, not spaces.
(It is very important. Use tab, or make won't work as you expected).
The rule is:
If a prerequisite modified later than a target, then make executes the recipe.
In the example above, if `sample.c` is modified after the generation of `sample.o`, then make executes gcc and compile `sample.c` into `sample.o`.
If the modification time of `sample.c` is older then the generation of `sample.o`, then no compiling is necessary, so make does nothing.
The Makefile for `tfe` is as follows.
~~~makefile
1 all: tfe
2
3 tfe: tfe.o tfetextview.o resources.o
4 gcc -o tfe tfe.o tfetextview.o resources.o `pkg-config --libs gtk4`
5
6 tfe.o: tfe.c tfetextview.h
7 gcc -c -o tfe.o `pkg-config --cflags gtk4` tfe.c
8 tfetextview.o: tfetextview.c tfetextview.h
9 gcc -c -o tfetextview.o `pkg-config --cflags gtk4` tfetextview.c
10 resources.o: resources.c
11 gcc -c -o resources.o `pkg-config --cflags gtk4` resources.c
12
13 resources.c: tfe.gresource.xml tfe.ui
14 glib-compile-resources tfe.gresource.xml --target=resources.c --generate-source
15
16 .Phony: clean
17
18 clean:
19 rm -f tfe tfe.o tfetextview.o resources.o resources.c
~~~
You just type `make` and everything will be done.
~~~
$ make
gcc -c -o tfe.o `pkg-config --cflags gtk4` tfe.c
gcc -c -o tfetextview.o `pkg-config --cflags gtk4` tfetextview.c
glib-compile-resources tfe.gresource.xml --target=resources.c --generate-source
gcc -c -o resources.o `pkg-config --cflags gtk4` resources.c
gcc -o tfe tfe.o tfetextview.o resources.o `pkg-config --libs gtk4`
~~~
I used only very basic rules to write this Makefile.
There are many more convenient methods to make it more compact.
But it will be long to explain it.
So I want to finish with make and move on to the next topic.
You can download "Gnu Make Manual" from [GNU website](https://www.gnu.org/software/make/manual/).
## Rake
Rake is a similar program to make.
It is written in Ruby language.
If you don't use Ruby, you don't need to read this subsection.
However, Ruby is really sophisticated and recommendable script language.
- Rakefile controls the behavior of `rake`.
- You can write any Ruby code in Rakefile.
Rake has task and file task, which is similar to target, prerequisite and recipe in make.
~~~ruby
1 require 'rake/clean'
2
3 targetfile = "tfe"
4 srcfiles = FileList["tfe.c", "tfetextview.c", "resources.c"]
5 uifile = "tfe.ui"
6 rscfile = srcfiles[2]
7 objfiles = srcfiles.ext(".o")
8 gresource_xml = "tfe.gresource.xml"
9
10 CLEAN.include(targetfile, objfiles, rscfile)
11
12 task default: targetfile
13
14 file targetfile => objfiles do |t|
15 sh "gcc -o #{t.name} #{t.prerequisites.join(' ')} `pkg-config --libs gtk4`"
16 end
17
18 objfiles.each do |obj|
19 src = obj.ext(".c")
20 file obj => src do |t|
21 sh "gcc -c -o #{t.name} `pkg-config --cflags gtk4` #{t.source}"
22 end
23 end
24
25 file rscfile => uifile do |t|
26 sh "glib-compile-resources #{gresource_xml} --target=#{t.name} --generate-source"
27 end
~~~
The contents of the `Rakefile` is almost same as the `Makefile` in the previous subsection.
- 3-8: Defines target file, source files and so on.
- 1, 10 Requires rake/clean library. And clean files are added to CLEAN.
The files included by CLEAN will be removed when `rake clean` is typed on the command line.
- 12: The default target depends on `targetfile`.
The task `default` is the final goal of tasks.
- 14-16: `targetfile` depends on `objfiles`.
The variable `t` is a task object.
- `t.name` is a target name
- `t.prerequisites` is an array of prerequisites.
- `t.source` is the first element of prerequisites.
- `sh` is a method to give the following string to shell as an argument and executes the shell.
- 18-23: An each iterator of the array `objfiles`. Each object depends on corresponding source file.
- 25-27: Resource file depends on ui file.
Rakefile might seem to be difficult for beginners.
But, you can use any Ruby syntax in the Rakefile, so it is really flexible.
If you practice Ruby and Rakefile, it will be highly productive tools.
For further information, see [Rake tutorial for beginners](https://toshiocp.github.io/Rake-tutorial-for-beginners-en/LearningRake.html).
Up: [README.md](../README.md), Prev: [Section 9](sec9.md), Next: [Section 11](sec11.md)

View file

@ -1,11 +1,10 @@
Up: [Readme.md](../Readme.md), Prev: [Section 10](sec10.md), Next: [Section 12](sec12.md)
Up: [README.md](../README.md), Prev: [Section 10](sec10.md), Next: [Section 12](sec12.md)
# Initialization and destruction of instances
# Instance Initialization and destruction
A new version of the text file editor (`tfe`) will be made in this section and the following four sections.
It is `tfe5`.
There are many changes from the prior version.
All the sources are listed in [Section 16](sec16.md).
They are located in two directories, [src/tfe5](../src/tfe5) and [src/tfetextview](../src/tfetextview).
## Encapsulation
@ -35,11 +34,10 @@ After that I will explain:
## GObject and its children
GObject and its children are objects, which have both class and instance.
First, think about instance of objects.
Instance is structured memories.
The structure is described as C language structure.
The following is a structure of TfeTextView.
GObject and its children are objects, which have both class and object C structures.
First, think about instances.
An instance is memories which has the object structure.
The following is the structure of TfeTextView.
~~~C
/* This typedef statement is automatically generated by the macro G_DECLARE_FINAL_TYPE */
@ -56,16 +54,10 @@ The members of the structure are:
- The type of `parent` is GtkTextView which is C structure.
It is declared in `gtktextview.h`.
GtkTextView is the parent of TfeTextView.
- `file` is a pointer to GFile. It can be NULL if no file corresponds to the TfeTextView object.
- `file` is a pointer to a GFile. It can be NULL if no file corresponds to the TfeTextView instance.
Notice the program above is the declaration of the structure, not the definition.
So, no memories are allocated at this moment.
They are to be allocated when `tfe_text_view_new` function is invoked.
The memory allocated with `tfe_text_view_new` is an instance of TfeTextView object.
Therefore, There can be multiple TfeTextView instances if `tfe_text_view_new` is called multiple times.
You can find the declaration of the ancestors of TfeTextView in the source files of GTK and GLib.
The following is extracts from the source files (not exactly the same).
You can find the declaration of the ancestors' object structures in the source files of GTK and GLib.
The following is extracted from the source files (not exactly the same).
~~~C
typedef struct _GObject GObject;
@ -93,34 +85,65 @@ struct _GtkTextView
~~~
In each structure, its parent is declared at the top of the members.
So, every ancestors is included in the child instance.
This is very important.
It guarantees a child widget to inherit all the features from ancestors.
So, all the ancestors are included in the child object.
The structure of `TfeTextView` is like the following diagram.
![The structure of the instance TfeTextView](../image/TfeTextView.png)
Derivable classes (ancestor classes) have their own private data area which are not included by the structure above.
For example, GtkWidget has GtkWidgetPrivate (C structure) for its private data.
## Initialization of a TfeTextView instance
Notice declarations are not definitions.
So, no memories are allocated when C structures are declared.
Memories are allocated to them from the heap area when the `tfe_text_view_new` function is called.
At the same time, the ancestors' private area allocated for the TfeTetView.
They are hidden from TfeTextView and it can't access to them directly.
The created memory is called instance.
When a TfeTextView instance is created, it is given three data area.
- The instance (C structure).
- GtkWidgetPrivate structure.
- GtkTextViewPrivate structure.
TfeTextView functions can access to its instance only.
The GtkWidgetPrivate and GtkTextViewPrivate are used by the ancestors' functions.
See the following example.
~~~C
GtkWidget *tv = tfe_text_view_new ();
GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
~~~
The parent's function `gtk_text_view_get_buffer` accesses the GtkTextViewPrivate data (owned by `tv`).
There is a pointer, which points the GtkBuffer, in the private area and the function returns the pointer.
(Actual behavior is a bit more complicated.)
TfeTextView instances inherit the ancestors functions like this.
A TfeTextView instance is created every time the `tfe_text_view_new` function is called.
Therefore, multiple TfeTextView instances can exist.
## Initialization of TfeTextView instances
The function `tfe_text_view_new` creates a new TfeTextView instance.
~~~C
1 GtkWidget *
2 tfe_text_view_new (void) {
3 return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
3 return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, "wrap-mode", GTK_WRAP_WORD_CHAR, NULL));
4 }
~~~
When this function is invoked, a TfeTextView instance is created and initialized.
The initialization process is as follows.
1. Initializes GObject (GInitiallyUnowned) part in TfeTextView instance.
2. Initializes GtkWidget part in TfeTextView instance.
3. Initializes GtkTextView part in TfeTextView instance.
4. Initializes TfeTextView part in TfeTextView instance.
1. When the instance is created, GtkWidgetPrivate and GtkTextViewPrivate structures are also created
2. Initializes GObject (GInitiallyUnowned) part in the TfeTextView instance.
3. Initializes GtkWidget part (the first `priv`) in the TfeTextView instance and GtkWidgetPrivate structure.
4. Initializes GtkTextView part (the second `priv`) in the TfeTextView instance and GtkTextViewPrivate structure.
5. Initializes TfeTextView part (`file`) in the TfeTextView instance.
The step one through three is done by `g_object_init`, `gtk_widget_init` and `gtk_text_view_init`.
The step two through four is done by `g_object_init`, `gtk_widget_init` and `gtk_text_view_init`.
They are called by the system automatically and you don't need to care about them.
Step four is done by the function `tfe_text_view_init` in `tfetextview.c`.
@ -136,21 +159,25 @@ This function just initializes `tv->file` to be `NULL`.
## Functions and Classes
In Gtk, all objects derived from GObject have class and instance (except abstract object).
An instance is memory of C structure, which is described in the previous two subsections.
Instances are memory of C structure, which are described in the previous two subsections.
Each object can have more than one instance.
Those instances have the same structure.
An instance just keeps status of the instance.
Therefore, it is insufficient to define its behavior.
Instances just have data.
Therefore, it doesn't define object's behavior.
We need at least two things.
One is functions and the other is class.
One is functions and the other is class methods.
You've already seen many functions.
For example, `tfe_text_view_new` is a function to create a TfeTextView instance.
These functions are similar to public object methods in object oriented languages such as Java or Ruby.
Functions are public, which means that they are expected to be used by other objects.
For example,
Class comprises mainly pointers to functions.
Those functions are used by the object itself or its descendant objects.
- `TfeTextView *tfe_text_view_new (void);` is a function to create a TfeTextView instance.
- `GtkTextBuffer *gtk_text_view_get_buffer (GtkTextView *textview)` is a function to get a GtkTextBuffer from GtkTextView.
Functions are public, which means that they are expected to be used by other objects.
They are similar to public methods in object oriented languages.
Class (C structure) mainly consists of pointers to functions.
The functions are called class methods and used by the object itself or its descendant objects.
For example, GObject class is declared in `gobject.h` in GLib source files.
~~~C
@ -160,42 +187,51 @@ For example, GObject class is declared in `gobject.h` in GLib source files.
4 struct _GObjectClass
5 {
6 GTypeClass g_type_class;
7 /*< private >*/
8 GSList *construct_properties;
9 /*< public >*/
10 /* seldom overridden */
11 GObject* (*constructor) (GType type,
12 guint n_construct_properties,
13 GObjectConstructParam *construct_properties);
14 /* overridable methods */
15 void (*set_property) (GObject *object,
16 guint property_id,
17 const GValue *value,
18 GParamSpec *pspec);
19 void (*get_property) (GObject *object,
20 guint property_id,
21 GValue *value,
22 GParamSpec *pspec);
23 void (*dispose) (GObject *object);
24 void (*finalize) (GObject *object);
25 /* seldom overridden */
26 void (*dispatch_properties_changed) (GObject *object,
27 guint n_pspecs,
28 GParamSpec **pspecs);
29 /* signals */
30 void (*notify) (GObject *object,
31 GParamSpec *pspec);
32 /* called when done constructing */
33 void (*constructed) (GObject *object);
34 /*< private >*/
35 gsize flags;
36 /* padding */
37 gpointer pdummy[6];
38 };
39
7
8 /*< private >*/
9 GSList *construct_properties;
10
11 /*< public >*/
12 /* seldom overridden */
13 GObject* (*constructor) (GType type,
14 guint n_construct_properties,
15 GObjectConstructParam *construct_properties);
16 /* overridable methods */
17 void (*set_property) (GObject *object,
18 guint property_id,
19 const GValue *value,
20 GParamSpec *pspec);
21 void (*get_property) (GObject *object,
22 guint property_id,
23 GValue *value,
24 GParamSpec *pspec);
25 void (*dispose) (GObject *object);
26 void (*finalize) (GObject *object);
27 /* seldom overridden */
28 void (*dispatch_properties_changed) (GObject *object,
29 guint n_pspecs,
30 GParamSpec **pspecs);
31 /* signals */
32 void (*notify) (GObject *object,
33 GParamSpec *pspec);
34
35 /* called when done constructing */
36 void (*constructed) (GObject *object);
37
38 /*< private >*/
39 gsize flags;
40
41 gsize n_construct_properties;
42
43 gpointer pspecs;
44 gsize n_pspecs;
45
46 /* padding */
47 gpointer pdummy[3];
48 };
49
~~~
I'd like to explain some of the members.
There's a pointer to the function `dispose` in line 23.
~~~C
@ -213,13 +249,13 @@ void (*finalize) (GObject *object);
Look at the declaration of `_GObjectClass` so that you would find that most of the members are pointers to functions.
- 11: A function pointed by `constructor` is called when the instance is generated. It completes the initialization of the instance.
- 23: A function pointed by `dispose` is called when the instance destructs itself.
- 13: A function pointed by `constructor` is called when the instance is generated. It completes the initialization of the instance.
- 25: A function pointed by `dispose` is called when the instance destructs itself.
Destruction process is divided into two phases.
The first one is called disposing.
In this phase, the instance releases all the references to other instances.
The second phase is finalizing.
- 24: A function pointed by `finalize` finishes the destruction process.
- 26: A function pointed by `finalize` finishes the destruction process.
- The other pointers point to functions which are called while the instance lives.
These functions are called class methods.
@ -228,25 +264,14 @@ But not open to the objects which are not the descendants.
## TfeTextView class
TfeTextView class is a structure and it includes all its ancestors' class in it.
TfeTextView class is a structure and it includes all its ancestors' classes in it.
Therefore, classes have similar hierarchy to instances.
~~~C
typedef _TfeTextView TfeTextView;
struct _TfeTextView {
GtkTextView parent;
GFile *file;
};
~~~
GObjectClass (GInitiallyUnownedClass) -- GtkWidgetClass -- GtkTextViewClass -- TfeTextViewClass
~~~
TfeTextView structure has GtkTextView type as the first member.
In the same way, GtkTextView has its parent type (GtkWidget) as the first member.
GtkWidget has its parent type (GtkInitiallyUnowned) as the first member.
The structure of GtkInitiallyUnowned is the same as GObject.
Therefore, TFeTextView includes GObject, GtkWidget and GtkTextView in itself.
GObject -- GInitiallyUnowned -- GtkWidget -- GtkTextView -- TfeTextView
The following is extracts from the source files (not exactly the same).
The following is extracted from the source codes (not exactly the same).
~~~C
1 struct _GtkWidgetClass
@ -353,8 +378,8 @@ The following is extracts from the source files (not exactly the same).
102 void (* toggle_overwrite) (GtkTextView *text_view);
103 GtkTextBuffer * (* create_buffer) (GtkTextView *text_view);
104 void (* snapshot_layer) (GtkTextView *text_view,
105 GtkTextViewLayer layer,
106 GtkSnapshot *snapshot);
105 GtkTextViewLayer layer,
106 GtkSnapshot *snapshot);
107 gboolean (* extend_selection) (GtkTextView *text_view,
108 GtkTextExtendSelection granularity,
109 const GtkTextIter *location,
@ -376,11 +401,11 @@ The following is extracts from the source files (not exactly the same).
- 120-122: This three lines are generated by the macro `G_DECLARE_FINAL_TYPE`.
So, they are not written in either `tfe_text_view.h` or `tfe_text_view.c`.
- 3, 84, 121: Each derived class puts its parent class at the first member of its structure.
- 3, 84, 121: Each class has its parent class at the first member of its structure.
It is the same as instance structures.
- Class members in ancestors are open to the descendant class.
So, they can be changed in `tfe_text_view_class_init` function.
For example, the `dispose` pointer in GObjectClass will be overridden later in `tfe_text_view_class_init`.
For example, the `finalize` pointer in GObjectClass will be overridden later in `tfe_text_view_class_init`.
(Override is an object oriented programming terminology.
Override is rewriting ancestors' class methods in the descendant class.)
- Some class methods are often overridden.
@ -410,14 +435,12 @@ Then C destructs itself and finally the memories allocated to C is freed.
The idea above is based on an assumption that an object referred by nothing has reference count of zero.
When the reference count drops to zero, the object starts its destruction process.
The destruction process is split into two phases: disposing and finalizing.
In the disposing process, the object invokes the function pointed by `dispose` in its class to release all references to other objects.
In the finalizing process, it invokes the function pointed by `finalize` in its class to complete the destruction process.
These functions are also called handlers or methods.
In the disposing process, the object invokes the function pointed by `dispose` in its class to release all references to other instances.
After that, it invokes the function pointed by `finalize` in its class to complete the destruction process.
For example, dispose handler or dispose method.
In the destruction process of TfeTextView, the reference count of widgets related to TfeTextView is automatically decreased.
But GFile pointed by `tv->file` needs to decrease its reference count by one.
You must write the code in the dispose handler `tfe_text_view_dispose`.
In the destruction process, TfeTextView needs to unref the GFile pointed by `tv->file`.
You must write the dispose handler `tfe_text_view_dispose`.
~~~C
1 static void
@ -438,7 +461,7 @@ In dispose handlers, we usually use `g_clear_object` rather than `g_object_unref
In the disposing process, the object uses the pointer in its class to call the handler.
Therefore, `tfe_text_view_dispose` needs to be registered in the class when the TfeTextView class is initialized.
The function `tfe_text_view_class_init` is the class initialization function and it is declared in the replacement produced by `G_DEFINE_TYPE` macro.
The function `tfe_text_view_class_init` is the class initialization function and it is declared in the `G_DEFINE_TYPE` macro expansion.
~~~C
static void
@ -466,12 +489,11 @@ Then it invokes its parent's dispose handler in line 8.
G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject);
~~~
`tfe_text_view_parent_class`,which is made by `G_DEFINE_TYPE` macro, is a pointer that points the parent object class.
A variable `tfe_text_view_parent_class`, which is made by `G_DEFINE_TYPE` macro, is a pointer that points the parent object class.
The variable `gobject` is a pointer to TfeTextView instance which is casted as a GObject instance.
Therefore, `G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose` points the handler `dh3` in the diagram above.
And `gobject` is a pointer to TfeTextView instance which is casted as a GObject instance.
`dh3` releases all the references to objects in the GtkTextView part (it is actually the private area pointed by `prev`) in TfeTextView instance.
The statement `G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject)` is the same as `dh3 (gobject)`, which means it releases all the reference to the other instances in the GtkTextViewPrivate in the TfeTextView instance.
After that, `dh3` calls `dh2`, and `dh2` calls `dh1`.
Finally all the references are released.
Up: [Readme.md](../Readme.md), Prev: [Section 10](sec10.md), Next: [Section 12](sec12.md)
Up: [README.md](../README.md), Prev: [Section 10](sec10.md), Next: [Section 12](sec12.md)

View file

@ -1,17 +1,17 @@
Up: [Readme.md](../Readme.md), Prev: [Section 11](sec11.md), Next: [Section 13](sec13.md)
Up: [README.md](../README.md), Prev: [Section 11](sec11.md), Next: [Section 13](sec13.md)
# Signals
## Signals
In Gtk programming, each object is encapsulated.
And it is not recommended to use global variables because they tend to make the program complicated.
Each object is encapsulated in Gtk programming.
And it is not recommended to use global variables because they are prone to make the program complicated.
So, we need something to communicate between objects.
There are two ways to do so.
- Functions.
For example, `tb = gtk_text_view_get_buffer (tv)`.
The caller requests `tv` to give `tb`, which is a GtkTextBuffer instance connected to `tv` to the caller.
The caller requests `tv` to give `tb`, which is a GtkTextBuffer instance connected to `tv`.
- Signals.
For example, `activate` signal on GApplication object.
When the application is activated, the signal is emitted.
@ -29,7 +29,7 @@ The registration is done usually when the object class is initialized.
2. Signals are connected to handlers by `g_connect_signal` or its family functions.
The connection is usually done out of the object.
3. When Signals are emitted, the connected handlers are invoked.
Signal is emitted on the instance of the object.
Signals are emitted on the instance of the object.
## Signal registration
@ -41,8 +41,7 @@ This signal is emitted when `tv->file` is changed.
`tfe_text_view_open` function is not able to return the status because it uses GtkFileChooserDialog.
This signal is emitted instead of the return value of the function.
A static variable or array is used to store the signal ID.
A static array is used to register two or more signals.
A static variable or array is used to store signal ID.
~~~C
enum {
@ -88,10 +87,10 @@ Signals are registered in the class initialization function.
- 6-15: Registers "change-file" signal.
`g_signal_new` function is used.
The signal "change-file" has no default handler (object method handler).
You usually don't need to set a default handler.
If you need it, use `g_signal_new_class_handler` function.
See [GObject API Reference, g\_signal\_new\_class\_handler](https://docs.gtk.org/gobject/func.signal_new_class_handler.html) for further information.
The signal "change-file" has no default handler (object method handler) so the offset (the line 9) is set to zero.
You usually don't need a default handler.
If you need it, use `g_signal_new_class_handler` function instead of `g_signal_new`.
See [GObject API Reference](https://docs.gtk.org/gobject/func.signal_new_class_handler.html) for further information.
- The return value of `g_signal_new` is the signal id.
The type of signal id is guint, which is the same as unsigned int.
It is used in the function `g_signal_emit`.
@ -185,4 +184,4 @@ g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ER
The fourth parameter is the parameter.
Up: [Readme.md](../Readme.md), Prev: [Section 11](sec11.md), Next: [Section 13](sec13.md)
Up: [README.md](../README.md), Prev: [Section 11](sec11.md), Next: [Section 13](sec13.md)

View file

@ -2,21 +2,18 @@ Up: [README.md](../README.md), Prev: [Section 12](sec12.md), Next: [Section 14]
# Functions in TfeTextView
In this section I will explain functions in TfeTextView object.
TfeTextView functions are described in this section.
## tfe.h and tfetextview.h
## tfetextview.h
`tfe.h` is a top header file and it includes `gtk.h` and all the header files.
C source files `tfeapplication.c` and `tfenotebook.c` include `tfe.h` at the beginning.
The header file `tfetextview.h` provides:
~~~C
1 #include <gtk/gtk.h>
2
3 #include "../tfetextview/tfetextview.h"
4 #include "tfenotebook.h"
~~~
- The type of TfeTextView, which is `TFE_TYPE_TEXT_VIEW`.
- The expansion of `G_DECLARE_FINAL_TYPE` includes some useful macros.
- Constants for the `open-response` signal is defined..
- Public functions of `tfetextview.c` are declared.
`../tfetextview/tfetextview.h` is a header file which describes the public functions in `tfetextview.c`.
Therefore, Any programs use TfeTextView needs to include `tfetextview.h`.
~~~C
1 #ifndef __TFE_TEXT_VIEW_H__
@ -56,14 +53,20 @@ C source files `tfeapplication.c` and `tfenotebook.c` include `tfe.h` at the beg
35 #endif /* __TFE_TEXT_VIEW_H__ */
~~~
- 1,2,35: Thanks to these three lines, the following lines are included only once.
- 1,2,35: Thanks to these three lines, the following lines are included only once.
You can use `#pragma once` instead of them.
It is non-standard but widely used.
- 4: Includes gtk4 header files.
The header file `gtk4` also has the same mechanism to avoid including it multiple times.
- 6-7: These two lines define TfeTextView type, its class structure and some useful macros.
- `TfeTextView` and `TfeTextViewClass` are declared as typedef of C structures.
- You need to define a structure `_TfeTextView` later.
- The class structure `_TfeTextViewClass` is defined here. You don't need to define it by yourself.
- Convenience functions `TFE_TEXT_VIEW ()` for casting and `TFE_IS_TEXT_VIEW` for type check are defined.
- 9-15: A definition of the value of the parameter of "open-response" signal.
- 17-33: Declarations of public functions on TfeTextView.
## Functions to create TfeTextView instances
## Instance creation Functions
A TfeTextView instance is created with `tfe_text_view_new` or `tfe_text_view_new_with_file`.
@ -107,16 +110,19 @@ Each function is defined as follows.
22
23 GtkWidget *
24 tfe_text_view_new (void) {
25 return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
25 return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, "wrap-mode", GTK_WRAP_WORD_CHAR, NULL));
26 }
~~~
- 23-25: `tfe_text_view_new` function.
Just returns the value from the function `g_object_new` but casts it to the pointer to GtkWidget.
Initialization is done in `tfe_text_view_init` which is called in the process of `g_object_new` function.
The function `g_object_new` creates any instances of its descendant class.
The arguments are the type of the class, property list and NULL.
Null is the end mark of the property list.
TfeTextView "wrap-mode" property has GTK\_WRAP\_WORD\_CHAR as the default value.
- 1-21: `tfe_text_view_new_with_file` function.
- 3: `g_return_val_if_fail` is described in [GLib API Reference, g\_return\_val\_if\_fail](https://docs.gtk.org/glib/func.return_val_if_fail.html).
And also [GLib API Reference, Message Logging](https://docs.gtk.org/glib/logging.html).
- 3: `g_return_val_if_fail` is described in [GLib API Reference -- g\_return\_val\_if\_fail](https://docs.gtk.org/glib/func.return_val_if_fail.html).
And also [GLib API Reference -- Message Logging](https://docs.gtk.org/glib/logging.html).
It tests whether the argument `file` is a pointer to GFile.
If it's true, then the program goes on to the next line.
If it's false, then it returns NULL (the second argument) immediately.
@ -141,7 +147,7 @@ If the modification flag is FALSE, it doesn't need to save the contents.
- 20: Returns `tv`, which is a pointer to the newly created TfeTextView instance.
If an error happens, NULL is returned.
## Save and saveas functions
## Save related functions
Save and saveas functions write the contents in the GtkTextBuffer to a file.
@ -162,143 +168,196 @@ Then, the function changes `tv->file` and save the contents to the specified fil
If an error occurs, it is shown to the user through the message dialog.
The error is managed only in the TfeTextView and no information is notified to the caller.
### save\_file function
~~~C
1 static gboolean
2 save_file (GFile *file, GtkTextBuffer *tb, GtkWindow *win) {
3 GtkTextIter start_iter;
4 GtkTextIter end_iter;
5 gchar *contents;
5 char *contents;
6 gboolean stat;
7 GtkWidget *message_dialog;
8 GError *err = NULL;
9
10 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
11 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
12 if (g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err)) {
13 gtk_text_buffer_set_modified (tb, FALSE);
14 stat = TRUE;
15 } else {
16 message_dialog = gtk_message_dialog_new (win, GTK_DIALOG_MODAL,
17 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
18 "%s.\n", err->message);
12 stat = g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err);
13 if (stat)
14 gtk_text_buffer_set_modified (tb, FALSE);
15 else {
16 // Because error message is displayed here, the caller of 'save_file' doesn't need to do anything about error.
17 message_dialog = gtk_message_dialog_new (win, GTK_DIALOG_MODAL,
18 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s.", err->message);
19 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
20 gtk_widget_show (message_dialog);
21 g_error_free (err);
22 stat = FALSE;
23 }
24 g_free (contents);
25 return stat;
26 }
27
28 static void
29 saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
30 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
31 GFile *file;
32 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
33
34 if (response == GTK_RESPONSE_ACCEPT) {
35 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
36 if (! G_IS_FILE (file))
37 g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile.\n");
38 else if (save_file(file, tb, GTK_WINDOW (win))) {
39 if (G_IS_FILE (tv->file))
40 g_object_unref (tv->file);
41 tv->file = file;
42 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
43 } else
44 g_object_unref (file);
45 }
46 gtk_window_destroy (GTK_WINDOW (dialog));
47 }
48
49 void
50 tfe_text_view_save (TfeTextView *tv) {
51 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
52
53 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
54 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
55
56 if (! gtk_text_buffer_get_modified (tb))
57 return; /* no need to save it */
58 else if (tv->file == NULL)
59 tfe_text_view_saveas (tv);
60 else if (! G_IS_FILE (tv->file))
61 g_error ("TfeTextView: The pointer tv->file isn't NULL nor GFile.\n");
62 else
63 save_file (tv->file, tb, GTK_WINDOW (win));
64 }
65
66 void
67 tfe_text_view_saveas (TfeTextView *tv) {
68 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
69
70 GtkWidget *dialog;
71 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
72
73 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
74 "Cancel", GTK_RESPONSE_CANCEL,
75 "Save", GTK_RESPONSE_ACCEPT,
76 NULL);
77 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
78 gtk_widget_show (dialog);
79 }
22 }
23 g_free (contents);
24 return stat;
25 }
~~~
- 1-26: `save_file` function.
This function is called from `saveas_dialog_response` and `tfe_text_view_save`.
- The function `save_file` is called from `saveas_dialog_response` and `tfe_text_view_save`.
This function saves the contents of the buffer to the file given as an argument.
If error happens, it displays an error message.
So, a caller of this function don't need to take care of errors.
The class of this function is `static`.
Therefore, only functions in this file (`tfeTetview.c`) call this function.
Such static functions usally don't have `g_return_val_if_fail` function.
Therefore, only functions in this file (`tfetextview.c`) call this function.
Such static functions usually don't have `g_return_val_if_fail` function.
- 10-11: Gets the text contents from the buffer.
- 12-14: Saves the contents to the file.
If no error happens, set the modified flag to be FALSE.
- 12: The function `g_file_replace_contents` writes the contents to the file and returns the status (true = success/ false = fail).
It has many parameters, but some of them are almost always given the same values.
- GFile* file: GFile to which the contents are saved.
- const char* contents: contents to be saved. The string is owned by the caller.
- gsize length: the length of the contents
- const char* etag: entity tag. It is usually NULL.
- gboolean make_backup: true to make a backup if the file exists. false not to make it. the file will be overwritten.
- GFileCreateFlags flags: usually `G_FILE_CREATE_NONE` is fine.
- char** new_etag: new entity tag. It is usually NULL.
- GCancellable* cancellable: If a cancellable instance is set, the other thread can cancel this operation. it is usually NULL.
- GError** error: If error happens, GError will be set.
- 13,14: If no error happens, set the modified flag to be FALSE.
This means that the buffer is not modified since it has been saved.
And set the return status `stat` to be TRUE.
- 15-23: If it fails to save the contents, displays an error message.
- 16-18: Creates a message dialog with the error message.
- 19: Connects the "response" signal to `gtk_window_destroy`, so that the dialog disappears when a user clicked on the button.
- 20-21: Shows the window, frees `err` and set `stat` to be FLASE.
- 24: Frees `contents`.
- 25: Returns to the caller.
- 28-47: `saveas_dialog_response` function.
This is a signal handler for the "response" signal on GtkFileChooserDialog instance created by `tfe_text_view_saveas` function.
This handler analyzes the response and determines whether to save the contents.
- 34-45: If the response is `GTK_RESPONSE_ACCEPT`, the user has clicked on the `Save` button. So, it tries to save.
- 35: Gets the GFile `file` from GtkFileChooserDialog.
- 36-37: If it doesn't point GFile, it outputs an error message to the log.
- 38: Otherwise, it calls `save_file` to save the contents to the file.
- 39-42: If `save_file` has successfully saved the contents, `tv->file` is updated.
If the old GFile pointed by `tv->file` exists, it is freed in advance.
Emits "change-file" signal.
- 44: Unrefs `file`.
- 46: destroys the file chooser dialog.
- 49-64: `tfe_text_view_save` function.
- 51: `tfe_text_view_save` is public, i.e. it is open to the other files.
- 15-22: If it fails to save the contents, an error message will be displayed.
- 17-18: Creates a message dialog. The parameters are:
- GtkWindow* parent: transient parent window.
This allows window managers to keep the dialog on top of the parent window, or center the dialog over the parent window.
It is possible to give no parent window to the dialog.
However, it is encouraged to give parents to dialogs.
- GtkDialogFlags flags: GTK\_DIALOG\_MODAL for modal dialog. A modal dialog is usually fine.
- GtkMessageType type: GTK\_MESSAGE\_ERROR for error message. Other options are GTK\_MESSAGE\_INFO, GTK\_MESSAGE\_WARNING and so on.
- GtkButtonsType buttons: GTK\_BUTTON\_OK is used most often. Other option is GTK\_BUTTON\_YES\_NO, GTK\_BUTTON\_CANCEL and so on.
- const gchar* message_format: gchar is the same as char. This format is the same as printf. Arguments for the message format follow.
- 19: Connects the "response" signal to `gtk_window_destroy`, so that the dialog disappears when the user clicks on the button.
- 20: Shows the message dialog.
- 21: Frees `err` with `g_error_free` function.
- 23: Frees `contents`.
- 24: Returns to the caller.
### saveas\_dialog\_response function
~~~C
1 static void
2 saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
3 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
4 GFile *file;
5 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
6
7 if (response == GTK_RESPONSE_ACCEPT) {
8 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
9 if (! G_IS_FILE (file))
10 g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile.\n");
11 else if (save_file(file, tb, GTK_WINDOW (win))) {
12 // The following is complicated. The comments here will help your understanding
13 // G_IS_FILE(tv->file) && tv->file == file => nothing to do
14 // G_IS_FILE(tv->file) && tv->file != file => unref(tv->file), tv->file=file, signal emit
15 // tv->file==NULL => tv->file=file, signal emit
16 if (G_IS_FILE (tv->file) && (! g_file_equal (tv->file, file)))
17 g_object_unref (tv->file);
18 if (! (G_IS_FILE (tv->file) && g_file_equal (tv->file, file))) {
19 tv->file = file; // The ownership of 'file' moves to TfeTextView.
20 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
21 }
22 g_object_unref (file);
23 } else
24 g_object_unref (file);
25 }
26 gtk_window_destroy (GTK_WINDOW (dialog));
27 }
~~~
- The function `saveas_dialog_response` is a signal handler for the "response" signal on GtkFileChooserDialog.
This handler analyzes the response and determines whether to save the contents or not.
- 7-25: If the response is `GTK_RESPONSE_ACCEPT`, the user has clicked on the `Save` button and the contents will be saved.
- 8: Gets the GFile `file` from the GtkFileChooserDialog.
- 9-10: If it doesn't point GFile, a warning message will be output to the log. This is not expected.
- 11: Otherwise, it calls `save_file` to save the contents to the file.
- 12-22: If `save_file` has successfully saved the contents, the following will be done.
- If `tv->file` is GFile and `file` is a different file, unref `tv->file`.
- If `tv->file` is GFile and `file` points the same file as `tv->file`, nothing needs to do.
Otherwise, `tv->file` is set to `file` and "change-file" signal is emitted.
- 22, 24: Unref `file`.
- 26: destroys the file chooser dialog.
### tfe\_text\_view\_save function
~~~C
1 void
2 tfe_text_view_save (TfeTextView *tv) {
3 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
4
5 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
6 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
7
8 if (! gtk_text_buffer_get_modified (tb))
9 return; /* no need to save it */
10 else if (tv->file == NULL)
11 tfe_text_view_saveas (tv);
12 else if (! G_IS_FILE (tv->file)) // Unexpected error
13 g_error ("TfeTextView: The pointer tv->file isn't NULL nor GFile.\n");
14 else
15 save_file (tv->file, tb, GTK_WINDOW (win));
16 }
~~~
- The function `tfe_text_view_save` writes the contents to the `tv->file` file.
It calls `tfe_text_view_saveas` or `save_file`.
- 1-3: The function is public, i.e. it is open to the other objects.
So, it doesn't have `static` class.
Public functions should check the parameter type with `g_return_if_fail` function.
If `tv` is not a pointer to a TfeTextView instance, then it logs an error message and immediately returns.
This function is similar to `g_return_val_if_fail`, but no value is returned because `tfe_text_view_save` doesn't return a value.
- 53-54: Gets GtkTextBuffer instance and GtkWidget instance and assignes them to `tb` and`win` respectively.
- 56-57: If the buffer hasn't modified, then it doesn't need to save it.
So the function returns.
- 58-59: If `tv->file` is NULL, no file has given yet.
It calls `tfe_text_view_saveas` which prompts a user to select a file or specify a new file to save.
- 60-61: If `tv->file` doesn't point GFile, somethig bad has happened.
Logs an error message.
- 62-63: Calls `save_file` to save the contents to the file.
- 66-79: `tfe_text_view_saveas` function.
It shows GtkFileChooserDialog and prompts the user to choose a file.
- 73-76: Creates GtkFileChooserDialog.
The title is "Save file".
Transient parent of the dialog is `win`, which is the top-level window.
The action is save mode.
The buttons are Cancel and Save.
- 77: connects the "response" signal of the dialog and `saveas_dialog_response` handler.
- 78: Shows the dialog.
This function is similar to `g_return_val_if_fail`, but no value is returned because `tfe_text_view_save` doesn't return a value (void).
- 5-6: GtkTextBuffer `tb` and GtkWidget (GtkWindow) `win` are set.
The function `gtk_widget_get_ancestor (widget, type)` returns the first ancestor of the widget with type.
The type is a GType.
For example, the type of GtkWindow is `GTK_TYPE_WINDOW` and the one of TfeTextView is `TFE_TYPE_TEXT_VIEW`.
Be careful. The parent-child relationship here is the one for widgets, not classes.
The top level window may be a GtkApplicationWindow, but it depends on the application.
Because TfeTextView is a library, it can't determine the top level window type (GtkWindow or GtkApplicationWindow).
GtkWindow is a parent class of GtkApplication window so it is more general.
Therefore, TfeTextView takes GtkWindow as a top level window so that it can be used by any application.
- 8-9: If the buffer hasn't modified, it doesn't need to be saved.
- 10-11: If `tv->file` is NULL, which means no file has given yet, it calls `tfe_text_view_saveas` to prompt a user to select a file and save the contents.
- 12-13: If `tv->file` doesn't point GFile, an error message is logged out. It is not expected.
- 14-15: Otherwise, it calls `save_file` to save the contents to the file `tv->file`.
![Saveas process](../image/saveas.png)
### tfe\_text\_view\_saveas function
~~~C
1 void
2 tfe_text_view_saveas (TfeTextView *tv) {
3 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
4
5 GtkWidget *dialog;
6 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
7
8 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
9 "Cancel", GTK_RESPONSE_CANCEL,
10 "Save", GTK_RESPONSE_ACCEPT,
11 NULL);
12 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
13 gtk_widget_show (dialog);
14 }
~~~
- The function `tfe_text_view_saveas` shows GtkFileChooserDialog and prompts the user to choose a file and save the contents.
- 1-3: Check the type of `tv` because the caller may be other object. This function is public.
- 6: GtkWidget `win` is set to the top level window.
- 8-11: Creates GtkFileChooserDialog. It has at least four parameters.
- const char* title: title of the dialog shown at the bar.
- GtkWindow* parent: transient parent window.
- GtkFileChooserAction action: action is one of `GTK_FILE_CHOOSER_ACTION_OPEN`, `GTK_FILE_CHOOSER_ACTION_SAVE`
and `GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER`.
When you want to save a file, `GTK_FILE_CHOOSER_ACTION_SAVE` is the action.
- const char* first_button_text: the label text of the first button.
- type: response ID for the sirst button. response ID is one of the GtkReponseType.
See [GtkResponseType](https://docs.gtk.org/gtk4/enum.ResponseType.html).
- followed by pairs of button text and response ID...
- NULL is put at the end of the list.
- The GtkFileChooserDialog will have the title "Save file", transient parent `win`, save mode action, cancel and save button.
- 12: connects the "response" signal and `saveas_dialog_response` handler.
- 13: Shows the dialog.
When you use GtkFileChooserDialog, you need to divide the program into two parts.
One is a function which creates GtkFileChooserDialog and the other is a signal handler.
@ -306,9 +365,11 @@ The function just creates and shows GtkFileChooserDialog.
The rest is done by the handler.
It gets Gfile from GtkFileChooserDialog and saves the buffer to the file by calling `save_file`.
## Open function
![Saveas process](../image/saveas.png)
Open function shows GtkFileChooserDialog to users and prompts them to choose a file.
## Open related functions
Open function shows GtkFileChooserDialog to a user and prompts them to choose a file.
Then it reads the file and puts the text into GtkTextBuffer.
~~~C
@ -317,10 +378,8 @@ void tfe_text_view_open (TfeTextView *tv, GtkWindow *win);
The parameter `win` is the top-level window.
It will be a transient parent window of GtkFileChooserDialog when the dialog is created.
This allows window managers to keep the dialog on top of the parent window, or center the dialog over the parent window.
It is possible to give no parent window to the dialog.
However, it is encouraged to give a parent window to dialog.
This function might be called just after `tv` has been created.
This function may be called just after `tv` has been created.
In that case, `tv` has not been incorporated into the widget hierarchy.
Therefore it is impossible to get the top-level window from `tv`.
That's why the function needs `win` parameter.
@ -328,7 +387,12 @@ That's why the function needs `win` parameter.
This function is usually called when the buffer of `tv` is empty.
However, even if the buffer is not empty, `tfe_text_view_open` doesn't treat it as an error.
If you want to revert the buffer, calling this function is appropriate.
Otherwise probably bad things will happen.
Open and read process is divided into two phases.
One is showing GtkFileChooserDialog and the other is its response handler.
The response handler gets the filename, reads the contents of the file and puts it into the GtkTextBuffer.
### open\_dialog\_response function
~~~C
1 static void
@ -348,67 +412,85 @@ Otherwise probably bad things will happen.
15 } else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
16 g_object_unref (file);
17 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
18 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
19 "%s.\n", err->message);
20 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
21 gtk_widget_show (message_dialog);
22 g_error_free (err);
23 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
24 } else {
25 gtk_text_buffer_set_text (tb, contents, length);
26 g_free (contents);
27 if (G_IS_FILE (tv->file))
28 g_object_unref (tv->file);
29 tv->file = file;
30 gtk_text_buffer_set_modified (tb, FALSE);
31 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
32 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
33 }
34 gtk_window_destroy (GTK_WINDOW (dialog));
35 }
36
37 void
38 tfe_text_view_open (TfeTextView *tv, GtkWindow *win) {
39 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
40 g_return_if_fail (GTK_IS_WINDOW (win));
41
42 GtkWidget *dialog;
43
44 dialog = gtk_file_chooser_dialog_new ("Open file", win, GTK_FILE_CHOOSER_ACTION_OPEN,
45 "Cancel", GTK_RESPONSE_CANCEL,
46 "Open", GTK_RESPONSE_ACCEPT,
47 NULL);
48 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
49 gtk_widget_show (dialog);
50 }
18 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", err->message);
19 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
20 gtk_widget_show (message_dialog);
21 g_error_free (err);
22 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
23 } else {
24 gtk_text_buffer_set_text (tb, contents, length);
25 g_free (contents);
26 gtk_text_buffer_set_modified (tb, FALSE);
27 // G_IS_FILE(tv->file) && tv->file == file => unref(tv->file), tv->file=file, emit response with SUCCESS
28 // G_IS_FILE(tv->file) && tv->file != file => unref(tv->file), tv->file=file, emit response with SUCCESS, emit change-file
29 // tv->file==NULL => tv->file=file, emit response with SUCCESS, emit change-file
30 // The order is important. If you unref tv->file first, you can't compare tv->file and file anymore.
31 // And open-response signal is emitted after new tv->file is set. Or the handler can't catch the new file.
32 if (! (G_IS_FILE (tv->file) && g_file_equal (tv->file, file)))
33 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
34 if (G_IS_FILE (tv->file))
35 g_object_unref (tv->file);
36 tv->file = file; // The ownership of 'file' moves to TfeTextView
37 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
38 }
39 gtk_window_destroy (GTK_WINDOW (dialog));
40 }
~~~
- 37-50: `tfe_text_view_open` function.
- 44-47: Creates GtkFileChooserDialog.
The title is "Open file".
Transient parent window is the top-level window of the application, which is given by the caller.
The action is open mode.
The buttons are Cancel and Open.
- 48: connects the "response" signal of the dialog and `open_dialog_response` signal handler.
- 49: Shows the dialog.
- 1-35: `open_dialog_response` signal handler.
- 10-11: If the response from GtkFileChooserDialog is not `GTK_RESPONSE_ACCEPT`, the user has clicked on the "Cancel" button or close button on the header bar.
- 2: The handler `open_dialog_response` has three parameters.
- GtkWidget *dialog: FileChooserDialog on which the "response" signal is emitted.
- gint response: response ID
- TfeTextView *tv: textview to put the contents to
- 10-11: If the response is not `GTK_RESPONSE_ACCEPT`, the user has clicked on the "Cancel" button or close button on the header bar.
Then, "open-response" signal is emitted.
The parameter of the signal is `TFE_OPEN_RESPONSE_CANCEL`.
- 12-14: Gets the pointer to the Gfile by `gtk_file_chooser_get_file`.
- 12-14: Gets the pointer to the GFile by `gtk_file_chooser_get_file`.
If it doesn't point GFile, maybe an error has occurred.
It is not expected, though.
Then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`.
- 15-23: If an error occurs at file reading, then it decreases the reference count of the Gfile, shows a message dialog to report the error to the user and emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`.
- 24-33: If the file has successfully been read, then the text is inserted to GtkTextBuffer, frees the temporary buffer pointed by `contents` and sets `tv->file` to point the file (no duplication is not necessary).
Then, it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_SUCCESS` and emits "change-file" signal.
- 34: destroys GtkFileCooserDialog.
- 15-22: If an error occurs when file reading, then it decreases the reference count of the GFile, shows a message dialog to report the error to the user and emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`.
- 23-38: If the file has been successfully read, the following is carried out. (It is not simple.)
- the text is inserted to GtkTextBuffer
- the temporary buffer `contents` is freed
- modify-bit is set to FALSE
- open-response signal is emitted with `TFE_OPEN_REPONSE_SUCCESS`
- If the file are changed, change-file signal is emitted
- If `tv->file` isn't NULL, `g_object_unref(tv->file)` is called
- `tv->file` is assigned with `file`
- 39: destroys GtkFileChooserDialog.
Now let's think about the whole process between the caller and TfeTextView.
It is shown in the following diagram and you would think that it is really complicated.
### tfe\_text\_view\_open function
~~~C
1 void
2 tfe_text_view_open (TfeTextView *tv, GtkWindow *win) {
3 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
4 // 'win' is used for a transient window of the GtkFileChooserDialog.
5 // It can be NULL.
6 g_return_if_fail (GTK_IS_WINDOW (win) || win == NULL);
7
8 GtkWidget *dialog;
9
10 dialog = gtk_file_chooser_dialog_new ("Open file", win, GTK_FILE_CHOOSER_ACTION_OPEN,
11 "Cancel", GTK_RESPONSE_CANCEL, "Open", GTK_RESPONSE_ACCEPT, NULL);
12 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
13 gtk_widget_show (dialog);
14 }
~~~
- 3-4: Check the type of the arguments `tv` and `win`.
Public functions always need to check the arguments.
- 10-11: Creates GtkFileChooserDialog.
- The title is "Open file".
- Transient parent window is the top-level window `win`.
- The action is open mode.
- The buttons are Cancel and Open.
- 12: connects the "response" signal and the handler.
- 13: Shows the dialog.
The whole process between the caller and TfeTextView is shown in the following diagram.
It is really complicated.
Because signal is the only way for GtkFileChooserDialog to communicate with others.
In GTK 3, `gtk_dialog_run` function is available.
It simplifies the process.
However, in GTK 4, `gtk_dialog_run` is unavailable any more.
![Caller and TfeTextView](../image/open.png)
@ -419,9 +501,10 @@ However, in GTK 4, `gtk_dialog_run` is unavailable any more.
5. The handler reads the file and inserts the text into GtkTextBuffer and emits a signal to inform the status as a response code.
6. The handler out of the TfeTextView receives the signal.
## Getting Gfile
## Getting GFile in TfeTextView
`gtk_text_view_get_file` is a simple function shown as follows.
You can get the GFile in a TfeTextView instance with `tfe_text_view_get_file`.
It is very simple.
~~~C
1 GFile *
@ -439,15 +522,14 @@ The important thing is to duplicate `tv->file`.
Otherwise, if the caller frees the GFile object, `tv->file` is no more guaranteed to point the GFile.
Another reason to use `g_file_dup` is that GFile isn't thread-safe.
If you use GFile in the different thread, the duplication is necessary.
See [Gio API Reference, g\_file\_dup](https://docs.gtk.org/gio/method.File.dup.html).
See [Gio API Reference -- g\_file\_dup](https://docs.gtk.org/gio/method.File.dup.html).
## The API document and source file of tfetextview.c
Refer [API document of TfeTextView](tfetextview_doc.md).
It is under the directory `src/tfetextview`.
All the source files are listed in [Section 16](sec16.md).
You can find them under [src/tfe5](../src/tfe5) and [src/tfetextview](../src/tfetextview) directories.
You can find all the TfeTextView source codes under [src/tfetextview](../src/tfetextview) directories.
Up: [README.md](../README.md), Prev: [Section 12](sec12.md), Next: [Section 14](sec14.md)

View file

@ -1,4 +1,4 @@
Up: [Readme.md](../Readme.md), Prev: [Section 13](sec13.md), Next: [Section 15](sec15.md)
Up: [README.md](../README.md), Prev: [Section 13](sec13.md), Next: [Section 15](sec15.md)
# Functions in GtkNotebook
@ -32,7 +32,7 @@ This header file describes the public functions in `tfenotebook.c`.
If the name is `untitled` or `untitled` followed by digits, FileChooserDialog appears and a user can choose or specify a filename.
- 4-5: `notebook_page_close` closes the current page.
- 7-8: `notebook_page_open` shows a file chooser dialog and a user can choose a file. The file is inserted to a new page.
- 10-11: `notebook_page_new_with_file` creates a new page and the file given as an argument is read and inserted into the page.
- 10-11: `notebook_page_new_with_file` creates a new page and a file given as an argument is read and inserted into the page.
- 13-14: `notebook_page_new` creates a new empty page.
You probably find that the functions except `notebook_page_close` are higher level functions of
@ -53,7 +53,7 @@ Now let's look at the program of each function.
## notebook\_page\_new
~~~C
1 static gchar*
1 static char*
2 get_untitled () {
3 static int c = -1;
4 if (++c == 0)
@ -63,59 +63,62 @@ Now let's look at the program of each function.
8 }
9
10 static void
11 notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) {
11 notebook_page_build (GtkNotebook *nb, GtkWidget *tv, const char *filename) {
12 GtkWidget *scr = gtk_scrolled_window_new ();
13 GtkNotebookPage *nbp;
14 GtkWidget *lab;
15 int i;
16
17 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
18 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
19 lab = gtk_label_new (filename);
20 i = gtk_notebook_append_page (nb, scr, lab);
21 nbp = gtk_notebook_get_page (nb, scr);
22 g_object_set (nbp, "tab-expand", TRUE, NULL);
23 gtk_notebook_set_current_page (nb, i);
24 g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed_cb), nb);
25 }
26
27 void
28 notebook_page_new (GtkNotebook *nb) {
29 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
30
31 GtkWidget *tv;
32 char *filename;
33
34 if ((tv = tfe_text_view_new ()) == NULL)
35 return;
36 filename = get_untitled ();
37 notebook_page_build (nb, tv, filename);
17 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
18 lab = gtk_label_new (filename);
19 i = gtk_notebook_append_page (nb, scr, lab);
20 nbp = gtk_notebook_get_page (nb, scr);
21 g_object_set (nbp, "tab-expand", TRUE, NULL);
22 gtk_notebook_set_current_page (nb, i);
23 g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed_cb), nb);
24 }
25
26 void
27 notebook_page_new (GtkNotebook *nb) {
28 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
29
30 GtkWidget *tv;
31 char *filename;
32
33 if ((tv = tfe_text_view_new ()) == NULL)
34 return;
35 filename = get_untitled ();
36 notebook_page_build (nb, tv, filename);
37 g_free (filename);
38 }
~~~
- 27-38: `notebook_page_new` function.
- 29: `g_return_if_fail` is used to check the argument.
- 34: Creates TfeTextView object.
If it fails, it returns to the caller.
- 36: Creates filename, which is "Untitled", "Untitled1", ... .
- 26-38: `notebook_page_new` function.
- 28: `g_return_if_fail` is used to check the argument.
- 33-34: Creates TfeTextView object.
If it fails, no notebook page is created and the function returns to the caller.
- 35: Creates filename, which is "Untitled", "Untitled1", ... .
- 1-8: `get_untitled` function.
- 3: Static variable `c` is initialized at the first call of this function. After that `c` keeps its value unless it is changed explicitly.
- 4-7: Increases `c` by one and if it is zero then it returns "Untitled". If it is a positive integer then it returns "Untitled\<the integer\>", for example, "Untitled1", "Untitled2", and so on.
- 4-7: Increases `c` by one and if it is zero, it returns "Untitled". If it is a positive integer, it returns "Untitled\<the integer\>", for example, "Untitled1", "Untitled2", and so on.
The function `g_strdup_printf` creates a string and it should be freed by `g_free` when it becomes useless.
The caller of `get_untitled` is in charge of freeing the string.
- 37: calls `notebook_page_build` to build the contents of the page.
- 10- 25: `notebook_page_build` function.
- 36: calls `notebook_page_build` to build the contents of the page.
- 37: frees `filename`.
- 10- 24: `notebook_page_build` function.
A parameter with `const` qualifier doesn't change in the function.
It means that the argument `filename` is owned by the caller.
The caller needs to free it when it becomes useless.
- 12: Creates GtkScrolledWindow.
- 17: Sets the wrap mode of `tv` to GTK_WRAP_WORD_CHAR so that lines are broken between words or graphemes.
- 18: Inserts `tv` to GtkscrolledWindow as a child.
- 19-20: Creates GtkLabel, then appends `scr` and `lab` to the GtkNotebook instance `nb`.
- 21-22: Sets "tab-expand" property to TRUE.
- 17: Inserts `tv` to GtkscrolledWindow as a child.
- 18-19: Creates GtkLabel, then appends `scr` and `lab` to the GtkNotebook instance `nb`.
- 20-21: Sets "tab-expand" property to TRUE.
The function `g_object_set` sets properties on an object.
The object is any object derived from GObject.
In many cases, an object has its own function to set its properties, but sometimes not.
In that case, use `g_object_set` to set the property.
- 23: Sets the current page of `nb` to the newly created page.
- 24: Connects "change-file" signal and `file_changed_cb` handler.
- 22: Sets the current page to the newly created page.
- 23: Connects "change-file" signal and `file_changed_cb` handler.
## notebook\_page\_new\_with\_file
@ -132,13 +135,14 @@ In that case, use `g_object_set` to set the property.
10 return; /* read error */
11 filename = g_file_get_basename (file);
12 notebook_page_build (nb, tv, filename);
13 }
13 g_free (filename);
14 }
~~~
- 9-10: Calls `tfe_text_view_new_with_file`.
If the function returns NULL, an error has happend.
Then, it does nothing and returns.
- 11-12: Gets the filename and builds the contents of the page.
- 11-13: Gets the filename, builds the contents of the page and frees `filename`.
## notebook\_page\_open
@ -148,58 +152,95 @@ Then, it does nothing and returns.
3 GFile *file;
4 char *filename;
5
6 if (response != TFE_OPEN_RESPONSE_SUCCESS || ! G_IS_FILE (file = tfe_text_view_get_file (tv))) {
6 if (response != TFE_OPEN_RESPONSE_SUCCESS) {
7 g_object_ref_sink (tv);
8 g_object_unref (tv);
9 }else {
10 filename = g_file_get_basename (file);
11 g_object_unref (file);
12 notebook_page_build (nb, GTK_WIDGET (tv), filename);
13 }
14 }
15
16 void
17 notebook_page_open (GtkNotebook *nb) {
18 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
19
20 GtkWidget *tv;
10 file = tfe_text_view_get_file (tv);
11 filename = g_file_get_basename (file);
12 g_object_unref (file);
13 notebook_page_build (nb, GTK_WIDGET (tv), filename);
14 g_free (filename);
15 }
16 }
17
18 void
19 notebook_page_open (GtkNotebook *nb) {
20 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
21
22 if ((tv = tfe_text_view_new ()) == NULL)
23 return;
24 g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
25 tfe_text_view_open (TFE_TEXT_VIEW (tv), GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW)));
26 }
22 GtkWidget *tv;
23
24 if ((tv = tfe_text_view_new ()) == NULL)
25 return;
26 g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
27 tfe_text_view_open (TFE_TEXT_VIEW (tv), GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW)));
28 }
~~~
- 16-26: `notebook_page_open` function.
- 22-23: Creates TfeTextView object.
- 18-28: `notebook_page_open` function.
- 24-25: Creates TfeTextView object.
If NULL is returned, an error has happened.
Then, it returns to the caller.
- 24: Connects the signal "open-response" and the handler `open_response`.
- 25: Calls `tfe_text_view_open`.
- 26: Connects the signal "open-response" and the handler `open_response`.
- 27: Calls `tfe_text_view_open`.
The "open-response" signal will be emitted later to inform the result of opening and reading a file.
- 1-14: `open_response` handler.
- 6-8: If the response code is NOT `TFE_OPEN_RESPONSE_SUCCESS` or `tfe_text_view_get_file` doesn't return the pointer to a GFile,
it has failed to open and read a new file.
- 1-16: `open_response` handler.
- 6-8: If the response code is not `TFE_OPEN_RESPONSE_SUCCESS`, it has failed to open and read a new file.
Then, what `notebook_page_open` did in advance need to be canceled.
The instance `tv` hasn't been a child widget of GtkScrolledWindow yet.
Such instance has floating reference.
Floating reference will be explained later in this subsection.
Floating reference will be explained later.
You need to call `g_object_ref_sink` first.
Then the floating reference is converted into an ordinary reference.
Now you call `g_object_unref` to decrease the reference count by one.
- 9-13: Otherwise, everything is okay.
Gets the filename, builds the contents of the page.
- 9-15: Otherwise, everything is okay.
Gets the filename, builds the contents of the page and frees `filename`.
## Floating reference
All the widgets are derived from GInitiallyUnowned.
When an instance of GInitiallyUnowned or its descendant is created, the instance has a floating reference.
The function `g_object_ref_sink` converts the floating reference into an ordinary reference.
If the instance doesn't have a floating reference, `g_object_ref_sink` simply increases the reference count by one.
GObject and GInitiallyUnowned are almost the same.
The difference is like this.
When an instance of GInitiallyUnowned is created, the instance has a floating reference and its reference count is zero.
On the other hand, when an instance of GObject (not GInitiallyUnowned) is created, no floating reference is given.
And the instance has a normal reference count instead of floating reference.
Their descendants inherits them, so every widget has a floating reference at first.
Non-widget class, for example, GtkTextBuffer is a direct sub class of GObject and it doesn't have floating reference.
Its reference count is one when it is created.
The function `g_object_ref_sink` converts the floating reference into an ordinary reference.
If the instance doesn't have a floating reference, `g_object_ref_sink` simply increases the reference count by one.
It is used when an widget is added to another widget as a child.
~~~
GtkTextView *tv = gtk_text_view_new (); // floating reference
GtkScrolledWindow *scr = gtk_scrolled_window_new ();
gtk_scrolled_window_set_child (scr, tv); // tv's reference count is one
~~~
When `tv` is added to `scr` as a child, `g_object_ref_sink` is used.
~~~
g_object_ref_sink (tv);
~~~
So, the floating reference is converted into an ordinary reference.
That is to say, floating reference is deleted, and reference count turns to one.
Thanks to this, the caller doesn't need to decrease tv's reference count.
If an Object\_A is not a descendant of GInitiallyUnowned, the program is like this:
~~~
Object_A *obj_a = object_a_new (); // reference count is one
GtkScrolledWindow *scr = gtk_scrolled_window_new ();
gtk_scrolled_window_set_child (scr, obj_a); // obj_a's reference count is two
// obj_a is referred by the caller (this program) and scrolled window
g_object_unref (obj_a); // obj_a's reference count is one because the caller no longer refers obj_a.
~~~
This example tells us that the caller needs to unref `obj_a`.
If you use `g_object_unref` to an instance that has a floating reference, you need to convert the floating reference to a normal reference in advance.
See [GObject Reference Manual](https://developer-old.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#gobject-The-Base-Object-Type.description) for further information.
See [GObject API reference](https://docs.gtk.org/gobject/floating-refs.html) for further information.
## notebook\_page\_close
@ -226,6 +267,7 @@ If the page is the only page the notebook has, then the function destroys the to
- 8-10: If the page is the only page the notebook has, it calls `gtk_window_destroy` to destroys the top-level window.
- 11-13: Otherwise, removes the current page.
The child widget (TfeTextView) is also destroyed.
## notebook\_page\_save
@ -266,7 +308,7 @@ This function gets the TfeTextView object belongs to the current page.
The function `file_changed_cb` is a handler connected to "change-file" signal.
If a file in a TfeTextView instance is changed, it emits this signal.
This handler changes the label of GtkNotebookPage.
This handler changes the label of the GtkNotebookPage.
~~~C
1 static void
@ -297,4 +339,4 @@ Then, unref the GFile object `file`.
- 15-16: Creates a GtkLabel instance `label` with the filename and set the label of the GtkNotebookPage with `label`.
Up: [Readme.md](../Readme.md), Prev: [Section 13](sec13.md), Next: [Section 15](sec15.md)
Up: [README.md](../README.md), Prev: [Section 13](sec13.md), Next: [Section 15](sec15.md)

View file

@ -124,7 +124,7 @@ textview {color: yellow; ...}
~~~
Class, ID and some other things can be applied to the selector like Web CSS.
Refer to [GTK 4 API Reference, CSS in Gtk](https://docs.gtk.org/gtk4/css-overview.html) for further information.
Refer to [GTK 4 API Reference -- CSS in Gtk](https://docs.gtk.org/gtk4/css-overview.html) for further information.
In line 30, the CSS is a string.
@ -306,9 +306,7 @@ In this file, just the source file names are modified from the prior version.
## source files
The [source files](../src/tfe5) of the text editor `tfe` will be shown in the next section.
You can also download the files from the [repository](https://github.com/ToshioCP/Gtk4-tutorial).
You can download the files from the [repository](https://github.com/ToshioCP/Gtk4-tutorial).
There are two options.
- Use git and clone.

View file

@ -1,26 +1,21 @@
Up: [Readme.md](../Readme.md), Prev: [Section 15](sec15.md), Next: [Section 17](sec17.md)
Up: [README.md](../README.md), Prev: [Section 15](sec15.md), Next: [Section 17](sec17.md)
# tfe5 source files
# How to build tfe (text file editor)
## How to compile and execute the text editor 'tfe'.
First, source files are shown in the later subsections.
First, source files are in the [Gtk4-tutorila repository](https://github.com/ToshioCP/Gtk4-tutorial).
How to download them is written at the end of the [previous section](sec15.md).
The following is the instruction of compilation and execution.
- You need meson and ninja.
- Set environment variables if necessary.
If you have installed gtk4 from the source and you preferred the option `--prefix $HOME/local` (see [Section 2](sec2.md)), type `. env.sh` to set the environment variables.
~~~
$ . env.sh
~~~
- change your current directory to `src/tfe5` directory.
- type `meson _build` for configuration.
- type `ninja -C _build` for compilation.
- If you have installed gtk4 from the source, you need to set environment variables to suit your installation.
- Change your current directory to `src/tfe5` directory.
- Type `meson _build` for configuration.
- Type `ninja -C _build` for compilation.
Then the application `tfe` is built under the `_build` directory.
- type `_build/tfe` to execute it.
- Type `_build/tfe` to execute it.
Then the window appears.
There are four buttons, `New`, `Open`, `Save` and `Close`.
@ -35,665 +30,20 @@ Then the file is read and a new Notebook Page appears.
This is a very simple editor.
It is a good practice for you to add more features.
## meson.build
~~~meson
1 project('tfe', 'c')
2
3 gtkdep = dependency('gtk4')
4
5 gnome=import('gnome')
6 resources = gnome.compile_resources('resources','tfe.gresource.xml')
7
8 sourcefiles=files('tfeapplication.c', 'tfenotebook.c', '../tfetextview/tfetextview.c')
9
10 executable('tfe', sourcefiles, resources, dependencies: gtkdep)
~~~
## tfe.gresource.xml
~~~xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <gresources>
3 <gresource prefix="/com/github/ToshioCP/tfe">
4 <file>tfe.ui</file>
5 </gresource>
6 </gresources>
~~~
## tfe.ui
~~~xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <interface>
3 <object class="GtkApplicationWindow" id="win">
4 <property name="title">file editor</property>
5 <property name="default-width">600</property>
6 <property name="default-height">400</property>
7 <child>
8 <object class="GtkBox" id="boxv">
9 <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
10 <child>
11 <object class="GtkBox" id="boxh">
12 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
13 <child>
14 <object class="GtkLabel" id="dmy1">
15 <property name="width-chars">10</property>
16 </object>
17 </child>
18 <child>
19 <object class="GtkButton" id="btnn">
20 <property name="label">New</property>
21 </object>
22 </child>
23 <child>
24 <object class="GtkButton" id="btno">
25 <property name="label">Open</property>
26 </object>
27 </child>
28 <child>
29 <object class="GtkLabel" id="dmy2">
30 <property name="hexpand">TRUE</property>
31 </object>
32 </child>
33 <child>
34 <object class="GtkButton" id="btns">
35 <property name="label">Save</property>
36 </object>
37 </child>
38 <child>
39 <object class="GtkButton" id="btnc">
40 <property name="label">Close</property>
41 </object>
42 </child>
43 <child>
44 <object class="GtkLabel" id="dmy3">
45 <property name="width-chars">10</property>
46 </object>
47 </child>
48 </object>
49 </child>
50 <child>
51 <object class="GtkNotebook" id="nb">
52 <property name="scrollable">TRUE</property>
53 <property name="hexpand">TRUE</property>
54 <property name="vexpand">TRUE</property>
55 </object>
56 </child>
57 </object>
58 </child>
59 </object>
60 </interface>
61
~~~
## tfe.h
~~~C
1 #include <gtk/gtk.h>
2
3 #include "../tfetextview/tfetextview.h"
4 #include "tfenotebook.h"
~~~
## tfeapplication.c
~~~C
1 #include "tfe.h"
2
3 static void
4 open_cb (GtkNotebook *nb) {
5 notebook_page_open (nb);
6 }
7
8 static void
9 new_cb (GtkNotebook *nb) {
10 notebook_page_new (nb);
11 }
12
13 static void
14 save_cb (GtkNotebook *nb) {
15 notebook_page_save (nb);
16 }
17
18 static void
19 close_cb (GtkNotebook *nb) {
20 notebook_page_close (GTK_NOTEBOOK (nb));
21 }
22
23 static void
24 app_activate (GApplication *application) {
25 GtkApplication *app = GTK_APPLICATION (application);
26 GtkWidget *win = GTK_WIDGET (gtk_application_get_active_window (app));
27 GtkWidget *boxv = gtk_window_get_child (GTK_WINDOW (win));
28 GtkNotebook *nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
29
30 notebook_page_new (nb);
31 gtk_widget_show (GTK_WIDGET (win));
32 }
33
34 static void
35 app_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) {
36 GtkApplication *app = GTK_APPLICATION (application);
37 GtkWidget *win = GTK_WIDGET (gtk_application_get_active_window (app));
38 GtkWidget *boxv = gtk_window_get_child (GTK_WINDOW (win));
39 GtkNotebook *nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
40 int i;
41
42 for (i = 0; i < n_files; i++)
43 notebook_page_new_with_file (nb, files[i]);
44 if (gtk_notebook_get_n_pages (nb) == 0)
45 notebook_page_new (nb);
46 gtk_widget_show (win);
47 }
48
49 static void
50 app_startup (GApplication *application) {
51 GtkApplication *app = GTK_APPLICATION (application);
52 GtkBuilder *build;
53 GtkApplicationWindow *win;
54 GtkNotebook *nb;
55 GtkButton *btno;
56 GtkButton *btnn;
57 GtkButton *btns;
58 GtkButton *btnc;
59
60 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe/tfe.ui");
61 win = GTK_APPLICATION_WINDOW (gtk_builder_get_object (build, "win"));
62 nb = GTK_NOTEBOOK (gtk_builder_get_object (build, "nb"));
63 gtk_window_set_application (GTK_WINDOW (win), app);
64 btno = GTK_BUTTON (gtk_builder_get_object (build, "btno"));
65 btnn = GTK_BUTTON (gtk_builder_get_object (build, "btnn"));
66 btns = GTK_BUTTON (gtk_builder_get_object (build, "btns"));
67 btnc = GTK_BUTTON (gtk_builder_get_object (build, "btnc"));
68 g_signal_connect_swapped (btno, "clicked", G_CALLBACK (open_cb), nb);
69 g_signal_connect_swapped (btnn, "clicked", G_CALLBACK (new_cb), nb);
70 g_signal_connect_swapped (btns, "clicked", G_CALLBACK (save_cb), nb);
71 g_signal_connect_swapped (btnc, "clicked", G_CALLBACK (close_cb), nb);
72 g_object_unref(build);
73
74 GdkDisplay *display;
75
76 display = gtk_widget_get_display (GTK_WIDGET (win));
77 GtkCssProvider *provider = gtk_css_provider_new ();
78 gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1);
79 gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
80 }
81
82 #define APPLICATION_ID "com.github.ToshioCP.tfe"
83
84 int
85 main (int argc, char **argv) {
86 GtkApplication *app;
87 int stat;
88
89 app = gtk_application_new (APPLICATION_ID, G_APPLICATION_HANDLES_OPEN);
90
91 g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
92 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
93 g_signal_connect (app, "open", G_CALLBACK (app_open), NULL);
94
95 stat =g_application_run (G_APPLICATION (app), argc, argv);
96 g_object_unref (app);
97 return stat;
98 }
99
~~~
## tfenotebook.h
~~~C
1 void
2 notebook_page_save(GtkNotebook *nb);
3
4 void
5 notebook_page_close (GtkNotebook *nb);
6
7 void
8 notebook_page_open (GtkNotebook *nb);
9
10 void
11 notebook_page_new_with_file (GtkNotebook *nb, GFile *file);
12
13 void
14 notebook_page_new (GtkNotebook *nb);
15
~~~
## tfenotebook.c
~~~C
1 #include "tfe.h"
2
3 /* The returned string should be freed with g_free() when no longer needed. */
4 static gchar*
5 get_untitled () {
6 static int c = -1;
7 if (++c == 0)
8 return g_strdup_printf("Untitled");
9 else
10 return g_strdup_printf ("Untitled%u", c);
11 }
12
13 static TfeTextView *
14 get_current_textview (GtkNotebook *nb) {
15 int i;
16 GtkWidget *scr;
17 GtkWidget *tv;
18
19 i = gtk_notebook_get_current_page (nb);
20 scr = gtk_notebook_get_nth_page (nb, i);
21 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
22 return TFE_TEXT_VIEW (tv);
23 }
24
25 static void
26 file_changed_cb (TfeTextView *tv, GtkNotebook *nb) {
27 GtkWidget *scr;
28 GtkWidget *label;
29 GFile *file;
30 char *filename;
31
32 file = tfe_text_view_get_file (tv);
33 scr = gtk_widget_get_parent (GTK_WIDGET (tv));
34 if (G_IS_FILE (file)) {
35 filename = g_file_get_basename (file);
36 g_object_unref (file);
37 } else
38 filename = get_untitled ();
39 label = gtk_label_new (filename);
40 g_free (filename);
41 gtk_notebook_set_tab_label (nb, scr, label);
42 }
43
44 void
45 notebook_page_save (GtkNotebook *nb) {
46 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
47
48 TfeTextView *tv;
49
50 tv = get_current_textview (nb);
51 tfe_text_view_save (TFE_TEXT_VIEW (tv));
52 }
53
54 void
55 notebook_page_close (GtkNotebook *nb) {
56 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
57
58 GtkWidget *win;
59 int i;
60
61 if (gtk_notebook_get_n_pages (nb) == 1) {
62 win = gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW);
63 gtk_window_destroy(GTK_WINDOW (win));
64 } else {
65 i = gtk_notebook_get_current_page (nb);
66 gtk_notebook_remove_page (GTK_NOTEBOOK (nb), i);
67 }
68 }
69
70 static void
71 notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) {
72 GtkWidget *scr = gtk_scrolled_window_new ();
73 GtkNotebookPage *nbp;
74 GtkWidget *lab;
75 int i;
76
77 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
78 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
79 lab = gtk_label_new (filename);
80 i = gtk_notebook_append_page (nb, scr, lab);
81 nbp = gtk_notebook_get_page (nb, scr);
82 g_object_set (nbp, "tab-expand", TRUE, NULL);
83 gtk_notebook_set_current_page (nb, i);
84 g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed_cb), nb);
85 }
86
87 static void
88 open_response (TfeTextView *tv, int response, GtkNotebook *nb) {
89 GFile *file;
90 char *filename;
91
92 if (response != TFE_OPEN_RESPONSE_SUCCESS || ! G_IS_FILE (file = tfe_text_view_get_file (tv))) {
93 g_object_ref_sink (tv);
94 g_object_unref (tv);
95 }else {
96 filename = g_file_get_basename (file);
97 g_object_unref (file);
98 notebook_page_build (nb, GTK_WIDGET (tv), filename);
99 }
100 }
101
102 void
103 notebook_page_open (GtkNotebook *nb) {
104 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
105
106 GtkWidget *tv;
107
108 if ((tv = tfe_text_view_new ()) == NULL)
109 return;
110 g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
111 tfe_text_view_open (TFE_TEXT_VIEW (tv), GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW)));
112 }
113
114 void
115 notebook_page_new_with_file (GtkNotebook *nb, GFile *file) {
116 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
117 g_return_if_fail(G_IS_FILE (file));
118
119 GtkWidget *tv;
120 char *filename;
121
122 if ((tv = tfe_text_view_new_with_file (file)) == NULL)
123 return; /* read error */
124 filename = g_file_get_basename (file);
125 notebook_page_build (nb, tv, filename);
126 }
127
128 void
129 notebook_page_new (GtkNotebook *nb) {
130 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
131
132 GtkWidget *tv;
133 char *filename;
134
135 if ((tv = tfe_text_view_new ()) == NULL)
136 return;
137 filename = get_untitled ();
138 notebook_page_build (nb, tv, filename);
139 }
140
~~~
## tfetextview.h
~~~C
1 #ifndef __TFE_TEXT_VIEW_H__
2 #define __TFE_TEXT_VIEW_H__
3
4 #include <gtk/gtk.h>
5
6 #define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
7 G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
8
9 /* "open-response" signal response */
10 enum TfeTextViewOpenResponseType
11 {
12 TFE_OPEN_RESPONSE_SUCCESS,
13 TFE_OPEN_RESPONSE_CANCEL,
14 TFE_OPEN_RESPONSE_ERROR
15 };
16
17 GFile *
18 tfe_text_view_get_file (TfeTextView *tv);
19
20 void
21 tfe_text_view_open (TfeTextView *tv, GtkWindow *win);
22
23 void
24 tfe_text_view_save (TfeTextView *tv);
25
26 void
27 tfe_text_view_saveas (TfeTextView *tv);
28
29 GtkWidget *
30 tfe_text_view_new_with_file (GFile *file);
31
32 GtkWidget *
33 tfe_text_view_new (void);
34
35 #endif /* __TFE_TEXT_VIEW_H__ */
~~~
## tfetextview.c
~~~C
1 #include <string.h>
2 #include "tfetextview.h"
3
4 struct _TfeTextView {
5 GtkTextView parent;
6 GFile *file;
7 };
8
9 G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
10
11 enum {
12 CHANGE_FILE,
13 OPEN_RESPONSE,
14 NUMBER_OF_SIGNALS
15 };
16
17 static guint tfe_text_view_signals[NUMBER_OF_SIGNALS];
18
19 static void
20 tfe_text_view_dispose (GObject *gobject) {
21 TfeTextView *tv = TFE_TEXT_VIEW (gobject);
22
23 if (G_IS_FILE (tv->file))
24 g_clear_object (&tv->file);
25
26 G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject);
27 }
28
29 static void
30 tfe_text_view_init (TfeTextView *tv) {
31 tv->file = NULL;
32 }
33
34 static void
35 tfe_text_view_class_init (TfeTextViewClass *class) {
36 GObjectClass *object_class = G_OBJECT_CLASS (class);
37
38 object_class->dispose = tfe_text_view_dispose;
39 tfe_text_view_signals[CHANGE_FILE] = g_signal_new ("change-file",
40 G_TYPE_FROM_CLASS (class),
41 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
42 0 /* class offset */,
43 NULL /* accumulator */,
44 NULL /* accumulator data */,
45 NULL /* C marshaller */,
46 G_TYPE_NONE /* return_type */,
47 0 /* n_params */
48 );
49 tfe_text_view_signals[OPEN_RESPONSE] = g_signal_new ("open-response",
50 G_TYPE_FROM_CLASS (class),
51 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
52 0 /* class offset */,
53 NULL /* accumulator */,
54 NULL /* accumulator data */,
55 NULL /* C marshaller */,
56 G_TYPE_NONE /* return_type */,
57 1 /* n_params */,
58 G_TYPE_INT
59 );
60 }
61
62 GFile *
63 tfe_text_view_get_file (TfeTextView *tv) {
64 g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);
65
66 if (G_IS_FILE (tv->file))
67 return g_file_dup (tv->file);
68 else
69 return NULL;
70 }
71
72 static gboolean
73 save_file (GFile *file, GtkTextBuffer *tb, GtkWindow *win) {
74 GtkTextIter start_iter;
75 GtkTextIter end_iter;
76 gchar *contents;
77 gboolean stat;
78 GtkWidget *message_dialog;
79 GError *err = NULL;
80
81 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
82 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
83 if (g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err)) {
84 gtk_text_buffer_set_modified (tb, FALSE);
85 stat = TRUE;
86 } else {
87 message_dialog = gtk_message_dialog_new (win, GTK_DIALOG_MODAL,
88 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
89 "%s.\n", err->message);
90 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
91 gtk_widget_show (message_dialog);
92 g_error_free (err);
93 stat = FALSE;
94 }
95 g_free (contents);
96 return stat;
97 }
98
99 static void
100 saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
101 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
102 GFile *file;
103 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
104
105 if (response == GTK_RESPONSE_ACCEPT) {
106 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
107 if (! G_IS_FILE (file))
108 g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile.\n");
109 else if (save_file(file, tb, GTK_WINDOW (win))) {
110 if (G_IS_FILE (tv->file))
111 g_object_unref (tv->file);
112 tv->file = file;
113 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
114 } else
115 g_object_unref (file);
116 }
117 gtk_window_destroy (GTK_WINDOW (dialog));
118 }
119
120 void
121 tfe_text_view_save (TfeTextView *tv) {
122 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
123
124 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
125 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
126
127 if (! gtk_text_buffer_get_modified (tb))
128 return; /* no need to save it */
129 else if (tv->file == NULL)
130 tfe_text_view_saveas (tv);
131 else if (! G_IS_FILE (tv->file))
132 g_error ("TfeTextView: The pointer tv->file isn't NULL nor GFile.\n");
133 else
134 save_file (tv->file, tb, GTK_WINDOW (win));
135 }
136
137 void
138 tfe_text_view_saveas (TfeTextView *tv) {
139 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
140
141 GtkWidget *dialog;
142 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
143
144 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
145 "Cancel", GTK_RESPONSE_CANCEL,
146 "Save", GTK_RESPONSE_ACCEPT,
147 NULL);
148 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
149 gtk_widget_show (dialog);
150 }
151
152 GtkWidget *
153 tfe_text_view_new_with_file (GFile *file) {
154 g_return_val_if_fail (G_IS_FILE (file), NULL);
155
156 GtkWidget *tv;
157 GtkTextBuffer *tb;
158 char *contents;
159 gsize length;
160
161 if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */
162 return NULL;
163
164 if ((tv = tfe_text_view_new()) != NULL) {
165 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
166 gtk_text_buffer_set_text (tb, contents, length);
167 TFE_TEXT_VIEW (tv)->file = g_file_dup (file);
168 gtk_text_buffer_set_modified (tb, FALSE);
169 }
170 g_free (contents);
171 return tv;
172 }
173
174 static void
175 open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
176 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
177 GFile *file;
178 char *contents;
179 gsize length;
180 GtkWidget *message_dialog;
181 GError *err = NULL;
182
183 if (response != GTK_RESPONSE_ACCEPT)
184 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
185 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)))) {
186 g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile.\n");
187 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
188 } else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
189 g_object_unref (file);
190 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
191 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
192 "%s.\n", err->message);
193 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
194 gtk_widget_show (message_dialog);
195 g_error_free (err);
196 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
197 } else {
198 gtk_text_buffer_set_text (tb, contents, length);
199 g_free (contents);
200 if (G_IS_FILE (tv->file))
201 g_object_unref (tv->file);
202 tv->file = file;
203 gtk_text_buffer_set_modified (tb, FALSE);
204 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
205 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
206 }
207 gtk_window_destroy (GTK_WINDOW (dialog));
208 }
209
210 void
211 tfe_text_view_open (TfeTextView *tv, GtkWindow *win) {
212 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
213 g_return_if_fail (GTK_IS_WINDOW (win));
214
215 GtkWidget *dialog;
216
217 dialog = gtk_file_chooser_dialog_new ("Open file", win, GTK_FILE_CHOOSER_ACTION_OPEN,
218 "Cancel", GTK_RESPONSE_CANCEL,
219 "Open", GTK_RESPONSE_ACCEPT,
220 NULL);
221 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
222 gtk_widget_show (dialog);
223 }
224
225 GtkWidget *
226 tfe_text_view_new (void) {
227 return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
228 }
229
~~~
## Total number of lines, words and characters
~~~
$ LANG=C wc tfe5/meson.build tfe5/tfeapplication.c tfe5/tfe.gresource.xml tfe5/tfe.h tfe5/tfenotebook.c tfe5/tfenotebook.h tfetextview/tfetextview.c tfetextview/tfetextview.h tfe5/tfe.ui
$ LANG=C wc tfe5/meson.build tfe5/tfeapplication.c tfe5/tfe.gresource.xml tfe5/tfenotebook.c tfe5/tfenotebook.h tfetextview/tfetextview.c tfetextview/tfetextview.h tfe5/tfe.ui
10 17 294 tfe5/meson.build
99 304 3205 tfe5/tfeapplication.c
101 308 3274 tfe5/tfeapplication.c
6 9 153 tfe5/tfe.gresource.xml
4 6 87 tfe5/tfe.h
140 378 3601 tfe5/tfenotebook.c
145 387 3667 tfe5/tfenotebook.c
15 21 241 tfe5/tfenotebook.h
229 671 8017 tfetextview/tfetextview.c
239 863 9264 tfetextview/tfetextview.c
35 60 701 tfetextview/tfetextview.h
61 100 2073 tfe5/tfe.ui
599 1566 18372 total
612 1765 19667 total
~~~
Up: [Readme.md](../Readme.md), Prev: [Section 15](sec15.md), Next: [Section 17](sec17.md)
Up: [README.md](../README.md), Prev: [Section 15](sec15.md), Next: [Section 17](sec17.md)

View file

@ -4,371 +4,40 @@ Up: [README.md](../README.md), Prev: [Section 1](sec1.md), Next: [Section 3](se
This section describes how to install GTK 4 into Linux distributions.
This tutorial is without any warranty.
If you want to install GTK 4 to your computer, do it at your own risk.
The information in this section is the one on April/27/2022.
The words 'at present' and/or 'now' in this section means 'April/27/2022'.
There are three possible way to install GTK 4.
There are two ways to install GTK 4.
- Install it from the distribution packages.
- Build it from the source file.
- Install a GNOME 40 distribution with the GNOME Boxes.
- Build it from the source files.
## Installation from the distribution packages
The first way is easy to install.
It is a recommended way.
I've installed GTK 4 packages in Ubuntu 21.04.
(Now, my Ubuntu version is 21.10).
The first way is the best and easiest way to install it.
I've installed GTK 4 packages (version 4.8.1) in Ubuntu 22.10.
~~~
$ sudo apt-get install libgtk-4-bin libgtk-4-common libgtk-4-dev libgtk-4-doc
$ sudo apt install libgtk-4-dev
~~~
Fedora, Arch, Debian and OpenSUSE are also possible.
See [Installing GTK from packages](https://www.gtk.org/docs/installations/linux#installing-gtk-from-packages).
It is important to install the developing package (libgtk-4-dev).
It includes C header files.
Otherwise, you can't compile any GTK 4 based programs.
Fedora, Debian, Arch, Gentoo and OpenSUSE also have GTK 4 packages.
Package information for Arch, Debian/Ubuntu and Fedra is described by [Installing GTK from packages](https://www.gtk.org/docs/installations/linux#installing-gtk-from-packages).
The following table shows the distributions which support GTK 4.
|Distribution| version |GTK 4| GNOME 40 |
|:----------:|:-------------------------:|:---:|:-------------:|
| Fedora | 36 |4.4.2| GNOME 42 |
| Ubuntu | 22.04lts | 4.4 |GNOME 41(4.6.2)|
| Debian | bookworm(testing) |4.6.5| GNOME 42 |
| Arch | rolling release |4.6.5| GNOME 42 |
| Gentoo | rolling release |4.6.5| GNOME 42 |
| OpenSUSE |Tumbleweed(rolling release)|4.6.5| GNOME 42 |
If you've installed GTK 4 from the packages, you don't need to read the rest of this section.
|Distribution| version | GTK 4 | GNOME |
|:----------:|:-------------------------:|:-----:|:--------:|
| Fedora | 37 |4.8.2-2| GNOME 43 |
| Ubuntu | 22.10 | 4.8.1 |GNOME 43.0|
| Debian | bookworm(testing) | 4.8.2 | GNOME 43 |
| Arch | rolling release | 4.8.2 | GNOME 43 |
| Gentoo | rolling release | 4.8.2 | GNOME 43 |
| OpenSUSE |Tumbleweed(rolling release)| 4.8.2 | GNOME 43 |
## Installation from the source file
If your operating system doesn't have GTK 4 packages, you need to build it from the source.
Or, if you want the latest version of GTK 4 (4.6.3), you also need to build it from the source.
I installed GTK 4 from the source in January 2021.
So, the following information is old, especially for the version of each software.
For the latest information, see [GTK API Reference, Building GTK](https://docs.gtk.org/gtk4/building.html).
### Prerequisites for GTK 4 installation
- Linux operating system. For example, Ubuntu 20.10 or 20.04LTS.
Other distributions might be OK.
- Packages for development such as gcc, meson, ninja, git, wget and so on.
- Dev package is necessary for each software below.
### Installation target
I installed GTK 4 under the directory `$HOME/local`.
This is a private user area.
If you want to install it in the system area, `/opt/gtk4` is one of good choices.
[GTK API Reference, Building GTK](https://docs.gtk.org/gtk4/building.html) gives an installation example to `/opt/gtk4`.
Don't install it to `/usr/local` which is the default.
It is used by Ubuntu applications which are not build on GTK 4.
Therefore, the risk is high and probably bad things will happen.
Actually I did it and I needed to reinstall Ubuntu.
### Installation to Ubuntu 20.10
Most of the necessary libraries are included by Ubuntu 20.10.
Therefore, they can be installed with `apt-get` command.
You don't need to install them from the source tarballs.
You can skip the subsections below about prerequisite library installation (GLib, Pango, GdkPixbuf and GTK-doc).
### GLib installation
If your Ubuntu is 20.04LTS, you need to install prerequisite libraries from the tarballs.
Check the version of your library and if it is lower than the necessary version, install it from the source.
For example,
~~~
$ pkg-config --modversion glib-2.0
2.64.6
~~~
The necessary version is 2.66.0 or higher.
Therefore, the example above shows that you need to install GLib.
I installed 2.67.1 which was the latest version at that time (January 2021).
Download GLib source files from the repository, then decompress and extract files.
$ wget https://download.gnome.org/sources/glib/2.67/glib-2.67.1.tar.xz
$ tar -Jxf glib-2.67.1.tar.xz
Some packages are required to build GLib.
You can find them if you run meson.
$ meson --prefix $HOME/local _build
Use apt-get and install the prerequisites.
For example,
$ sudo apt-get install -y libpcre2-dev libffi-dev
After that, compile GLib.
$ rm -rf _build
$ meson --prefix $HOME/local _build
$ ninja -C _build
$ ninja -C _build install
Set several environment variables so that the GLib libraries installed can be used by build tools.
Make a text file below and save it as `env.sh`
# compiler
CPPFLAGS="-I$HOME/local/include"
LDFLAGS="-L$HOME/local/lib"
PKG_CONFIG_PATH="$HOME/local/lib/pkgconfig:$HOME/local/lib/x86_64-linux-gnu/pkgconfig"
export CPPFLAGS LDFLAGS PKG_CONFIG_PATH
# linker
LD_LIBRARY_PATH="$HOME/local/lib/x86_64-linux-gnu/"
PATH="$HOME/local/bin:$PATH"
export LD_LIBRARY_PATH PATH
# gsetting
export GSETTINGS_SCHEMA_DIR=$HOME/local/share/glib-2.0/schemas
Then, use . (dot) or source command to include these commands to the current bash.
$ . env.sh
or
$ source env.sh
This command carries out the commands in `env.sh` and changes the environment variables above in the current shell.
### Pango installation
Download and untar.
$ wget https://download.gnome.org/sources/pango/1.48/pango-1.48.0.tar.xz
$ tar -Jxf pango-1.48.0.tar.xz
Try meson and check the required packages.
Install all the prerequisites.
Then, compile and install Pango.
$ meson --prefix $HOME/local _build
$ ninja -C _build
$ ninja -C _build install
It installs Pango-1.0.gir under `$HOME/local/share/gir-1.0`.
If you installed Pango without `--prefix` option, then it would be located at `/usr/local/share/gir-1.0`.
This directory (/usr/local/share) is used by applications.
They find the directory by the environment variable `XDG_DATA_DIRS`.
It is a text file which keep the list of 'share' directories like `/usr/share`, `usr/local/share` and so on.
Now `$HOME/local/share` needs to be added to `XDG_DATA_DIRS`, or error will occur in the later compilation.
$ export XDG_DATA_DIRS=$HOME/local/share:$XDG_DATA_DIRS
### GdkPixbuf and GTK-Doc installation
Download and untar.
$ wget https://download.gnome.org/sources/gdk-pixbuf/2.42/gdk-pixbuf-2.42.2.tar.xz
$ tar -Jxf gdk-pixbuf-2.42.2.tar.xz
$ wget https://download.gnome.org/sources/gtk-doc/1.33/gtk-doc-1.33.1.tar.xz
$ tar -Jxf gtk-doc-1.33.1.tar.xz
Same as before, install prerequisite packages, then compile and install them.
The installation of GTK-Doc put `gtk-doc.pc` under `$HOME/local/share/pkgconfig`.
This file is used by pkg-config, which is one of the build tools.
The directory needs to be added to the environment variable `PKG_CONFIG_PATH`
$ export PKG_CONFIG_PATH="$HOME/local/share/pkgconfig:$PKG_CONFIG_PATH"
### GTK 4 installation
If you want the latest development version of GTK 4, use git and clone the repository.
$ git clone https://gitlab.gnome.org/gnome/gtk.git
If you want a stable version of GTK 4, then download it from [GNOME source website](https://download.gnome.org/sources/gtk/).
The latest version is 4.3.1 (13/June/2021).
Compile and install it.
$ meson --prefix $HOME/local _build
$ ninja -C _build
$ ninja -C _build install
If you want to know more information, refer to [GTK 4 API Reference, Building GTK](https://docs.gtk.org/gtk4/building.html).
### Modify env.sh
Because environment variables disappear when you log out, you need to add them again.
Modify `env.sh`.
# compiler
CPPFLAGS="-I$HOME/local/include"
LDFLAGS="-L$HOME/local/lib"
PKG_CONFIG_PATH="$HOME/local/lib/pkgconfig:$HOME/local/lib/x86_64-linux-gnu/pkgconfig:
$HOME/local/share/pkgconfig"
export CPPFLAGS LDFLAGS PKG_CONFIG_PATH
# linker
LD_LIBRARY_PATH="$HOME/local/lib/x86_64-linux-gnu/"
PATH="$HOME/local/bin:$PATH"
export LD_LIBRARY_PATH PATH
# gir
XDG_DATA_DIRS=$HOME/local/share:$XDG_DATA_DIRS
export XDG_DATA_DIRS
# gsetting
export GSETTINGS_SCHEMA_DIR=$HOME/local/share/glib-2.0/schemas
# girepository-1.0
export GI_TYPELIB_PATH=$HOME/local/lib/x86_64-linux-gnu/girepository-1.0
Include this file by . (dot) command before using GTK 4 libraries.
You may think you can add them in your `.profile`.
But it's a wrong decision.
Never write them to your `.profile`.
The environment variables above are necessary only when you compile and run GTK 4 applications.
Otherwise it's not necessary.
If you changed the environment variables above and run GTK 3 applications, it probably causes serious damage.
### Compiling GTK 4 applications
Before you compile GTK 4 applications, define environment variables above.
$ . env.sh
After that you can compile them without anything.
For example, to compile `sample.c`, type the following.
$ gcc `pkg-config --cflags gtk4` sample.c `pkg-config --libs gtk4`
To know how to compile GTK 4 applications, refer to the section 3 (GtkApplication and GtkApplicationWindow) and after.
## Installing Fedora 34 with GNOME Boxes
The last part of this section is about GNOME 40 and GNOME Boxes.
GNOME 40 is a new version of GNOME desktop system.
And GTK 4 is installed in the distribution.
See [GNOME 40 website](https://forty.gnome.org/) first.
*However, GNOME 40 is not necessary to compile and run GTK 4 applications.*
There are seven choices at present.
- GNOME OS
- Arch Linux
- Gentoo Linux
- Fedora 36
- openSUSE Tumbleweed
- Ubuntu 22.04
- Debian bookworm
I've installed Fedora 34 with GNOME Boxes.
My OS was Ubuntu 21.04 at that time.
GNOME Boxes creates a virtual machine in Ubuntu and Fedora will be installed to that virtual machine.
The instruction is as follows.
1. Download Fedora 34 iso file.
There is an link at the end of [GNOME 40 website](https://forty.gnome.org/).
2. Install gnome-boxes with apt-get command.
~~~
$ sudo apt-get install gnome-boxes
~~~
3. Run GNOME Boxes.
4. Click on `+` button on the top left corner and launch a box creation wizard by clicking `Create a Virtual Machine ...`.
Then a dialog appears.
Click on `Operationg System Image File` and select the iso file you have downloaded.
5. Then, the Fedora's installer is executed.
Follow the instructions by the installer.
At the end of the installation, the installer instructs to reboot the system.
Click on the right of the title bar and select reboot or shutdown.
6. Your display is back to the initial window of GNOME Boxes, but there is a button `Fedora 34 Workstation` on the upper left of the window.
Click on the button then Fedora will be executed.
7. A setup dialog appears.
Setup Fedora according to the wizard.
Now you can use Fedora.
It includes GTK 4 libraries already.
But you need to install the GTK 4 development package.
Use `dnf` to install `gtk4.x86_64` package.
~~~
$ sudo dnf install gtk4.x86_64
~~~
### GTK 4 compilation test
You can test the GTK 4 development packages by compiling files which are based on GTK 4.
I've tried compiling `tfe` text editor, which is written in section 21.
1. Run Firefox.
2. Open this website \([Gtk4-Tutorial](https://github.com/ToshioCP/Gtk4-tutorial)\).
3. Click on the green button labeled `Code`.
4. Select `Download ZIP` and download the codes from the repository.
5. Unzip the file.
6. Change your current directory to `src/tfe7`.
7. Compile it.
~~~
$ meson _build
bash: meson: command not found...
Install package 'meson' to provide command 'meson'? [N/y] y
* Waiting in queue...
The following packages have to be installed:
meson-0.56.2-2.fc34.noarch High productivity build system
ninja-build-1.10.2-2.fc34.x86_64 Small build system with a focus on speed
vim-filesystem-2:8.2.2787-1.fc34.noarch VIM filesystem layout
Proceed with changes? [N/y] y
... ...
... ...
The Meson build system
Version: 0.56.2
... ...
... ...
Project name: tfe
Project version: undefined
C compiler for the host machine: cc (gcc 11.0.0 "cc (GCC) 11.0.0 20210210 (Red Hat 11.0.0-0)")
C linker for the host machine: cc ld.bfd 2.35.1-38
Host machine cpu family: x86_64
Host machine cpu: x86_64
Found pkg-config: /usr/bin/pkg-config (1.7.3)
Run-time dependency gtk4 found: YES 4.2.0
Found pkg-config: /usr/bin/pkg-config (1.7.3)
Program glib-compile-resources found: YES (/usr/bin/glib-compile-resources)
Program glib-compile-schemas found: YES (/usr/bin/glib-compile-schemas)
Program glib-compile-schemas found: YES (/usr/bin/glib-compile-schemas)
Build targets in project: 4
Found ninja-1.10.2 at /usr/bin/ninja
$ ninja -C _build
ninja: Entering directory `_build'
[12/12] Linking target tfe
$ ninja -C _build install
ninja: Entering directory `_build'
[0/1] Installing files.
Installing tfe to /usr/local/bin
Installation failed due to insufficient permissions.
Attempting to use polkit to gain elevated privileges...
Installing tfe to /usr/local/bin
Installing /home/<username>/Gtk4-tutorial-main/src/tfe7/com.github.ToshioCP.tfe.gschema.xml to /usr/local/share/glib-2.0/schemas
Running custom install script '/usr/bin/glib-compile-schemas /usr/local/share/glib-2.0/schemas/'
~~~
8. Execute it.
~~~
$ tfe
~~~
Then, the window of `tfe` text editor appears.
The compilation and execution have succeeded.
If you want to install a developing version of GTK 4, you need to build it from the source.
See [Compiling the GTK Libraries](https://docs.gtk.org/gtk4/building.html) secion in the GTK 4 API reference.
Up: [README.md](../README.md), Prev: [Section 1](sec1.md), Next: [Section 3](sec3.md)

View file

@ -6,7 +6,7 @@ Up: [README.md](../README.md), Prev: [Section 2](sec2.md), Next: [Section 4](se
### GtkApplication and g\_application\_run
Usually people write programming code to make an application.
People write programming code to make an application.
What are applications?
Applications are software that runs using libraries, which includes the
OS, frameworks and so on.
@ -30,7 +30,7 @@ The following is the C code representing the scenario above.
5 GtkApplication *app;
6 int stat;
7
8 app = gtk_application_new ("com.github.ToshioCP.pr1", G_APPLICATION_FLAGS_NONE);
8 app = gtk_application_new ("com.github.ToshioCP.pr1", G_APPLICATION_DEFAULT_FLAGS);
9 stat =g_application_run (G_APPLICATION (app), argc, argv);
10 g_object_unref (app);
11 return stat;
@ -39,7 +39,7 @@ The following is the C code representing the scenario above.
~~~
The first line says that this program includes the header files of the Gtk libraries.
The function `main` above is a startup function in C language.
The function `main` is a startup function in C language.
The variable `app` is defined as a pointer to a GtkApplication instance.
The function `gtk_application_new` creates a GtkApplication instance and returns a pointer to the instance.
The GtkApplication instance is a C structure data in which the information about the application is stored.
@ -49,6 +49,16 @@ The function `g_application_run` runs an application that the instance defined.
Actually, `app` is not an application but a pointer to the instance of the application.
However, it is simple and short, and probably no confusion occurs.)
Here I used the word `instance`.
Instance, class and object are terminologies in Object Oriented Programming.
I use these words in the same way.
But, I will often use "object" instead of "instance" in this tutorial.
That means "object" and "instance" is the same.
Object is a bit ambiguous word.
In a broad sense, object has wider meaning than instance.
So, readers should be careful of the contexts to find the meaning of "object".
In many cases, object and instance are the same.
To compile this, the following command needs to be run.
The string `pr1.c` is the filename of the C source code above.
@ -85,7 +95,8 @@ So, I will explain that to you first.
A signal is emitted when something happens.
For example, a window is created, a window is destroyed and so on.
The signal "activate" is emitted when the application is activated, or started.
The signal "activate" is emitted when the application is activated.
(Activated is a bit different from started, but you can think the both are almost same so far.)
If the signal is connected to a function, which is called a signal handler or
simply handler, then the function is invoked when the signal emits.
@ -93,7 +104,7 @@ The flow is like this:
1. Something happens.
2. If it's related to a certain signal, then the signal is emitted.
3. If the signal as been connected to a handler, then the handler is invoked.
3. If the signal has been connected to a handler in advance, then the handler is invoked.
Signals are defined in objects.
For example, the "activate" signal belongs to the GApplication object, which is
@ -127,7 +138,7 @@ We use a function `g_signal_connect` which connects a signal to a handler.
10 GtkApplication *app;
11 int stat;
12
13 app = gtk_application_new ("com.github.ToshioCP.pr2", G_APPLICATION_FLAGS_NONE);
13 app = gtk_application_new ("com.github.ToshioCP.pr2", G_APPLICATION_DEFAULT_FLAGS);
14 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
15 stat =g_application_run (G_APPLICATION (app), argc, argv);
16 g_object_unref (app);
@ -137,6 +148,7 @@ We use a function `g_signal_connect` which connects a signal to a handler.
~~~
First, we define the handler `app_activate` which simply displays a message.
The function `g_print` is defined in GLib and it's like a printf in the C standard library.
In the function `main`, we add `g_signal_connect` before `g_application_run`.
The function `g_signal_connect` has four arguments.
@ -145,14 +157,39 @@ The function `g_signal_connect` has four arguments.
3. A handler function (also called callback), which needs to be casted by `G_CALLBACK`.
4. Data to pass to the handler. If no data is necessary, NULL should be given.
You can find the description of each signal in the API reference manual.
For example, "activate" signal is in GApplication section in [GIO API Reference](https://docs.gtk.org/gio/signal.Application.activate.html).
The handler function is described in it.
It is described in the [GObject API Reference](https://docs.gtk.org/gobject/func.signal_connect.html).
Correctly, `g_signal_connect` is a macro (not a C function).
~~~c
#define g_signal_connect (
instance,
detailed_signal,
c_handler,
data
)
~~~
You can find the description of each signal in the API reference manual.
For example, "activate" signal is in [GApplication section](https://docs.gtk.org/gio/signal.Application.activate.html) in the GIO API Reference.
~~~c
void
activate (
GApplication* self,
gpointer user_data
)
~~~
This is a declaration of the "activate" signal handler.
You can use any name instead of "activate" in the declaration above.
The parameters are:
- self is an instance to which the signal belongs.
- user\_data is a data defined in the fourth argument of the `g_signal_connect` function.
If it is NULL, then you can ignore and left out the second parameter.
In addition, `g_signal_connect` is described in [GObject API Reference](https://docs.gtk.org/gobject/func.signal_connect.html).
API reference manual is very important.
You should see and understand it to write Gtk applications.
They are located in ['GTK Documentation'](https://docs.gtk.org/).
You should see and understand it.
Let's compile the source file above (`pr2.c`) and run it.
@ -246,19 +283,20 @@ By this definition, it returns a pointer to GtkWidget, not GtkWindow.
It actually creates a new GtkWindow instance (not GtkWidget) but returns a pointer to GtkWidget.
However,the pointer points the GtkWidget and at the same time it also points GtkWindow that contains GtkWidget in it.
If you want to use `win` as a pointer to the GtkWindow, you need to cast it.
If you want to use `win` as a pointer to a GtkWindow type instance, you need to cast it.
~~~C
(GtkWindow *) win
~~~
Or you can use `GTK_WINDOW` macro that performs a similar function.
It works, but isn't usually used.
Instead, `GTK_WINDOW` macro is used.
~~~C
GTK_WINDOW (win)
~~~
This is a recommended way.
The macro is recommended because it does not only cast but also check the type.
#### Connect it to GtkApplication.
@ -268,8 +306,7 @@ The function `gtk_window_set_application` is used to connect GtkWindow to GtkApp
gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
~~~
You need to cast `win` to GtkWindow and `app` to GtkApplication.
`GTK_WINDOW` and `GTK_APPLICATION` macro is appropriate for that.
You need to cast `win` to GtkWindow and `app` to GtkApplication with `GTK_WINDOW` and `GTK_APPLICATION` macro.
GtkApplication continues to run until the related window is destroyed.
If you didn't connect GtkWindow and GtkApplication, GtkApplication destroys itself immediately.
@ -285,7 +322,7 @@ But, there's an exception.
Top window (this term will be explained later) isn't visible when it is created.
So you need to use the function above to show the window.
Save the program as `pr3.c` and compile and run it.
Save the program as `pr3.c`, then compile and run it.
~~~
$ comp pr3
@ -301,8 +338,8 @@ Click on the close button then the window disappears and the program finishes.
### GtkApplicationWindow
GtkApplicationWindow is a child object of GtkWindow.
It has some extra functionality for better integration with GtkApplication.
It is recommended to use it instead of GtkWindow when you use GtkApplication.
It has some extra feature for better integration with GtkApplication.
It is recommended to use it as the top-level window of the application instead of GtkWindow.
Now rewrite the program and use GtkApplicationWindow.

View file

@ -1,4 +1,4 @@
Up: [Readme.md](../Readme.md), Prev: [Section 3](sec3.md), Next: [Section 5](sec5.md)
Up: [README.md](../README.md), Prev: [Section 3](sec3.md), Next: [Section 5](sec5.md)
# Widgets (1)
@ -15,7 +15,7 @@ It is a widget with text in it.
1 #include <gtk/gtk.h>
2
3 static void
4 app_activate (GApplication *app, gpointer user_data) {
4 app_activate (GApplication *app) {
5 GtkWidget *win;
6 GtkWidget *lab;
7
@ -34,7 +34,7 @@ It is a widget with text in it.
20 GtkApplication *app;
21 int stat;
22
23 app = gtk_application_new ("com.github.ToshioCP.lb1", G_APPLICATION_FLAGS_NONE);
23 app = gtk_application_new ("com.github.ToshioCP.lb1", G_APPLICATION_DEFAULT_FLAGS);
24 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
25 stat =g_application_run (G_APPLICATION (app), argc, argv);
26 g_object_unref (app);
@ -58,6 +58,10 @@ A program `diff` is good to know the difference between two files.
~~~
$ cd misc; diff pr4.c lb1.c
4c4
< app_activate (GApplication *app, gpointer user_data) {
---
> app_activate (GApplication *app) {
5a6
> GtkWidget *lab;
8c9
@ -70,13 +74,15 @@ $ cd misc; diff pr4.c lb1.c
> gtk_window_set_child (GTK_WINDOW (win), lab);
>
18c23
< app = gtk_application_new ("com.github.ToshioCP.pr4", G_APPLICATION_FLAGS_NONE);
< app = gtk_application_new ("com.github.ToshioCP.pr4", G_APPLICATION_DEFAULT_FLAGS);
---
> app = gtk_application_new ("com.github.ToshioCP.lb1", G_APPLICATION_FLAGS_NONE);
> app = gtk_application_new ("com.github.ToshioCP.lb1", G_APPLICATION_DEFAULT_FLAGS);
~~~
This tells us:
- A signal handler `app_activate` doesn't have `user_data` parameter.
If the fourth argument of `g_signal_connect` is NULL, you can leave out `user_data`.
- The definition of a new variable `lab` is added.
- The title of the window is changed.
- A label is created and connected to the window as a child.
@ -98,22 +104,22 @@ An application can have more than one top-level window.
### GtkButton
The next widget to introduce is GtkButton.
The next widget is GtkButton.
It displays a button on the screen with a label or icon on it.
In this subsection, we will make a button with a label.
When the button is clicked, it emits a "clicked" signal.
The following program shows how to catch the signal to then do something.
The following program shows how to catch the signal and do something.
~~~C
1 #include <gtk/gtk.h>
2
3 static void
4 click_cb (GtkButton *btn, gpointer user_data) {
4 click_cb (GtkButton *btn) {
5 g_print ("Clicked.\n");
6 }
7
8 static void
9 app_activate (GApplication *app, gpointer user_data) {
9 app_activate (GApplication *app) {
10 GtkWidget *win;
11 GtkWidget *btn;
12
@ -133,7 +139,7 @@ The following program shows how to catch the signal to then do something.
26 GtkApplication *app;
27 int stat;
28
29 app = gtk_application_new ("com.github.ToshioCP.lb2", G_APPLICATION_FLAGS_NONE);
29 app = gtk_application_new ("com.github.ToshioCP.lb2", G_APPLICATION_DEFAULT_FLAGS);
30 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
31 stat =g_application_run (G_APPLICATION (app), argc, argv);
32 g_object_unref (app);
@ -145,7 +151,7 @@ The following program shows how to catch the signal to then do something.
Look at the line 17 to 19.
First, it creates a GtkButton instance `btn` with a label "Click me".
Then, adds the button to the window `win` as a child.
Finally, connects a "clicked" signal of the button to a handler (function) `click_cb`.
Finally, connects a "clicked" signal of the button to the handler `click_cb`.
So, if `btn` is clicked, the function `click_cb` is invoked.
The suffix "cb" means "call back".
@ -159,74 +165,78 @@ Click the button (it is a large button, you can click everywhere in the window),
It shows the handler was invoked by clicking the button.
It's good that we make sure that the clicked signal was caught and the handler was invoked by using `g_print`.
However, using g_print is out of harmony with Gtk which is a GUI library.
However, using `g_print` is out of harmony with GTK, which is a GUI library.
So, we will change the handler.
The following code is `lb3.c`.
~~~C
1 static void
2 click_cb (GtkButton *btn, gpointer user_data) {
3 GtkWindow *win = GTK_WINDOW (user_data);
4 gtk_window_destroy (win);
5 }
6
7 static void
8 app_activate (GApplication *app, gpointer user_data) {
9 GtkWidget *win;
10 GtkWidget *btn;
11
12 win = gtk_application_window_new (GTK_APPLICATION (app));
13 gtk_window_set_title (GTK_WINDOW (win), "lb3");
14 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
15
16 btn = gtk_button_new_with_label ("Quit");
17 gtk_window_set_child (GTK_WINDOW (win), btn);
18 g_signal_connect (btn, "clicked", G_CALLBACK (click_cb), win);
19
20 gtk_widget_show (win);
21 }
2 click_cb (GtkButton *btn, GtkWindow *win) {
3 gtk_window_destroy (win);
4 }
5
6 static void
7 app_activate (GApplication *app) {
8 GtkWidget *win;
9 GtkWidget *btn;
10
11 win = gtk_application_window_new (GTK_APPLICATION (app));
12 gtk_window_set_title (GTK_WINDOW (win), "lb3");
13 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
14
15 btn = gtk_button_new_with_label ("Close");
16 gtk_window_set_child (GTK_WINDOW (win), btn);
17 g_signal_connect (btn, "clicked", G_CALLBACK (click_cb), win);
18
19 gtk_widget_show (win);
20 }
~~~
And the difference between `lb2.c` and `lb3.c` is as follows.
~~~
$ cd misc; diff lb2.c lb3.c
5c5,6
4,5c4,5
< click_cb (GtkButton *btn) {
< g_print ("Clicked.\n");
---
> GtkWindow *win = GTK_WINDOW (user_data);
> click_cb (GtkButton *btn, GtkWindow *win) {
> gtk_window_destroy (win);
14c15
14c14
< gtk_window_set_title (GTK_WINDOW (win), "lb2");
---
> gtk_window_set_title (GTK_WINDOW (win), "lb3");
17c18
17c17
< btn = gtk_button_new_with_label ("Click me");
---
> btn = gtk_button_new_with_label ("Quit");
19c20
> btn = gtk_button_new_with_label ("Close");
19c19
< g_signal_connect (btn, "clicked", G_CALLBACK (click_cb), NULL);
---
> g_signal_connect (btn, "clicked", G_CALLBACK (click_cb), win);
29c30
< app = gtk_application_new ("com.github.ToshioCP.lb2", G_APPLICATION_FLAGS_NONE);
29c29
< app = gtk_application_new ("com.github.ToshioCP.lb2", G_APPLICATION_DEFAULT_FLAGS);
---
> app = gtk_application_new ("com.github.ToshioCP.lb3", G_APPLICATION_FLAGS_NONE);
35d35
> app = gtk_application_new ("com.github.ToshioCP.lb3", G_APPLICATION_DEFAULT_FLAGS);
35d34
<
~~~
The changes are:
- The function `g_print` in `lb2.c` was deleted and the two lines above are inserted instead.
- The label of `btn` is changed from "Click me" to "Quit".
- The function `g_print` in `lb2.c` was deleted and two lines are inserted.
- `click_cb` has the second parameter, which comes from the fourth argument of the `g_signal_connect` at the line 19.
One thing to be careful is the types are different between the second parameter of `click_cb` and the fourth argument of `g_signal_connect`.
The former is `GtkWindow *` and the latter is `GtkWidget *`.
The compiler doesn't complain because `g_signal_connect` uses gpointer (general type of pointer).
In this program the instance pointed by `win` is a GtkApplicationWindow object.
It is a descendant of GtkWindow and GtkWidget class, so both `GtkWindow *` and `GtkWidget *` are correct types for the instance.
- `gtk_destroy (win)` destroys the top-level window. Then the application quits.
- The label of `btn` is changed from "Click me" to "Close".
- The fourth argument of `g_signal_connect` is changed from `NULL` to `win`.
The most important change is the fourth argument of `g_signal_connect`.
This argument is described as "data to pass to handler" in the definition of `g_signal_connect` in [GObject API Reference](https://docs.gtk.org/gobject/func.signal_connect.html).
Therefore, `win` which is a pointer to GtkApplicationWindow is passed to the handler as a second parameter `user_data`.
The handler then casts it to a pointer to GtkWindow and calls `gtk_window_destroy` to destroy the top-level window.
The application then quits.
The most important change is the fourth argument of the `g_signal_connect`.
This argument is described as "data to pass to handler" in the definition of [`g_signal_connect`](https://docs.gtk.org/gobject/func.signal_connect.html).
### GtkBox
@ -245,15 +255,14 @@ After this, the Widgets are connected as the following diagram.
![Parent-child relationship](../image/box.png)
The program `lb4.c` includes these widgets.
It is as follows.
The program `lb4.c` is as follows.
~~~C
1 #include <gtk/gtk.h>
2
3 static void
4 click1_cb (GtkButton *btn, gpointer user_data) {
5 const gchar *s;
4 click1_cb (GtkButton *btn) {
5 const char *s;
6
7 s = gtk_button_get_label (btn);
8 if (g_strcmp0 (s, "Hello.") == 0)
@ -263,68 +272,96 @@ It is as follows.
12 }
13
14 static void
15 click2_cb (GtkButton *btn, gpointer user_data) {
16 GtkWindow *win = GTK_WINDOW (user_data);
17 gtk_window_destroy (win);
18 }
19
20 static void
21 app_activate (GApplication *app, gpointer user_data) {
22 GtkWidget *win;
23 GtkWidget *box;
24 GtkWidget *btn1;
25 GtkWidget *btn2;
26
27 win = gtk_application_window_new (GTK_APPLICATION (app));
28 gtk_window_set_title (GTK_WINDOW (win), "lb4");
29 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
30
31 box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5);
32 gtk_box_set_homogeneous (GTK_BOX (box), TRUE);
33 gtk_window_set_child (GTK_WINDOW (win), box);
34
35 btn1 = gtk_button_new_with_label ("Hello.");
36 g_signal_connect (btn1, "clicked", G_CALLBACK (click1_cb), NULL);
37
38 btn2 = gtk_button_new_with_label ("Quit");
39 g_signal_connect (btn2, "clicked", G_CALLBACK (click2_cb), win);
40
41 gtk_box_append (GTK_BOX (box), btn1);
42 gtk_box_append (GTK_BOX (box), btn2);
43
44 gtk_widget_show (win);
45 }
46
47 int
48 main (int argc, char **argv) {
49 GtkApplication *app;
50 int stat;
51
52 app = gtk_application_new ("com.github.ToshioCP.lb4", G_APPLICATION_FLAGS_NONE);
53 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
54 stat =g_application_run (G_APPLICATION (app), argc, argv);
55 g_object_unref (app);
56 return stat;
57 }
15 click2_cb (GtkButton *btn, GtkWindow *win) {
16 gtk_window_destroy (win);
17 }
18
19 static void
20 app_activate (GApplication *app) {
21 GtkWidget *win;
22 GtkWidget *box;
23 GtkWidget *btn1;
24 GtkWidget *btn2;
25
26 win = gtk_application_window_new (GTK_APPLICATION (app));
27 gtk_window_set_title (GTK_WINDOW (win), "lb4");
28 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
29
30 box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5);
31 gtk_box_set_homogeneous (GTK_BOX (box), TRUE);
32 gtk_window_set_child (GTK_WINDOW (win), box);
33
34 btn1 = gtk_button_new_with_label ("Hello.");
35 g_signal_connect (btn1, "clicked", G_CALLBACK (click1_cb), NULL);
36
37 btn2 = gtk_button_new_with_label ("Close");
38 g_signal_connect (btn2, "clicked", G_CALLBACK (click2_cb), win);
39
40 gtk_box_append (GTK_BOX (box), btn1);
41 gtk_box_append (GTK_BOX (box), btn2);
42
43 gtk_widget_show (win);
44 }
45
46 int
47 main (int argc, char **argv) {
48 GtkApplication *app;
49 int stat;
50
51 app = gtk_application_new ("com.github.ToshioCP.lb4", G_APPLICATION_DEFAULT_FLAGS);
52 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
53 stat =g_application_run (G_APPLICATION (app), argc, argv);
54 g_object_unref (app);
55 return stat;
56 }
~~~
Look at the function `app_activate`.
After the creation of a GtkApplicationWindow instance, a GtkBox instance is created.
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
gtk_box_set_homogeneous (GTK_BOX (box), TRUE);
~~~C
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
gtk_box_set_homogeneous (GTK_BOX (box), TRUE);
~~~
The first argument arranges the children of the box vertically.
The orientation constants are defined like this:
- GTK\_ORIENTATION\_VERTICAL: the children widgets are arranged vertically
- GTK\_ORIENTATION\_HORIZONTAL: the children widgets are arranged horizontally
The second argument is the size between the children.
The unit of the length is pixel.
The next function fills the box with the children, giving them the same space.
After that, two buttons `btn1` and `btn2` are created and the signal handlers are set.
Then, these two buttons are appended to the box.
~~~C
1 static void
2 click1_cb (GtkButton *btn) {
3 const char *s;
4
5 s = gtk_button_get_label (btn);
6 if (g_strcmp0 (s, "Hello.") == 0)
7 gtk_button_set_label (btn, "Good-bye.");
8 else
9 gtk_button_set_label (btn, "Hello.");
10 }
~~~
The function `gtk_button_get_lable` returns a text from the label.
The string is owned by the button and you can't modify or free it.
The `const` qualifier is necessary for the string `s`.
If you change the string, your compiler will give you a waring.
You always need to be careful with the const qualifier when you see the GTK 4 API reference.
![Screenshot of the box](../image/screenshot_lb4.png)
The handler corresponds to `btn1` toggles its label.
The handler corresponds to `btn2` destroys the top-level window and the application quits.
Up: [Readme.md](../Readme.md), Prev: [Section 3](sec3.md), Next: [Section 5](sec5.md)
Up: [README.md](../README.md), Prev: [Section 3](sec3.md), Next: [Section 5](sec5.md)

View file

@ -1,4 +1,4 @@
Up: [Readme.md](../Readme.md), Prev: [Section 4](sec4.md), Next: [Section 6](sec6.md)
Up: [README.md](../README.md), Prev: [Section 4](sec4.md), Next: [Section 6](sec6.md)
# Widgets (2)
@ -14,7 +14,7 @@ See the sample program `tfv1.c` below.
1 #include <gtk/gtk.h>
2
3 static void
4 app_activate (GApplication *app, gpointer user_data) {
4 app_activate (GApplication *app) {
5 GtkWidget *win;
6 GtkWidget *tv;
7 GtkTextBuffer *tb;
@ -28,7 +28,7 @@ See the sample program `tfv1.c` below.
15 "He cut it, then there was a small cute baby girl in it. "
16 "The girl was shining faintly. "
17 "He thought this baby girl is a gift from Heaven and took her home.\n"
18 "His wife was surprized at his tale. "
18 "His wife was surprized at his story. "
19 "They were very happy because they had no children. "
20 ;
21 win = gtk_application_window_new (GTK_APPLICATION (app));
@ -50,7 +50,7 @@ See the sample program `tfv1.c` below.
37 GtkApplication *app;
38 int stat;
39
40 app = gtk_application_new ("com.github.ToshioCP.tfv1", G_APPLICATION_FLAGS_NONE);
40 app = gtk_application_new ("com.github.ToshioCP.tfv1", G_APPLICATION_DEFAULT_FLAGS);
41 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
42 stat = g_application_run (G_APPLICATION (app), argc, argv);
43 g_object_unref (app);
@ -62,16 +62,26 @@ Look at line 25.
A GtkTextView instance is created and its pointer is assigned to `tv`.
When the GtkTextView instance is created, a GtkTextBuffer instance is also created and connected to the GtkTextView automatically.
"GtkTextBuffer instance" will be referred to simply as "GtkTextBuffer" or "buffer".
In the next line, the pointer to the buffer is got and assigned to `tb`.
In the next line, the pointer to the buffer is assigned to `tb`.
Then, the text from line 10 to 20 is assigned to the buffer.
If the third argument of `gtk_text_buffer_set_text` is a positive integer, it is the length of the text.
It it is -1, the string terminates with NULL.
GtkTextView has a wrap mode.
When it is set to `GTK_WRAP_WORD_CHAR`, text wraps in between words, or if that is not enough, also between graphemes.
Wrap mode is written in [Gtk\_WrapMode](https://docs.gtk.org/gtk4/enum.WrapMode.html) in the GTK 4 API document.
In line 30, `tv` is added to `win` as a child.
Now compile and run it.
```
$ cd src/tfv
$ comp tfv1
$ ./a.out
```
![GtkTextView](../image/screenshot_tfv1.png)
There's an I-beam pointer in the window.
@ -79,19 +89,19 @@ You can add or delete any characters on the GtkTextView,
and your changes are kept in the GtkTextBuffer.
If you add more characters beyond the limit of the window, the height increases and the window extends.
If the height gets bigger than the height of the display screen, you won't be
able to control the size of the window, and change it back to the original size.
This is a problem and shows that there is a bug in our program.
This can solve it by adding a GtkScrolledWindow between the GtkApplicationWindow and GtkTextView.
able to control the size of the window or change it back to the original size.
This is a problem, that is to say a bug.
This can be solved by adding a GtkScrolledWindow between the GtkApplicationWindow and GtkTextView.
### GtkScrolledWindow
What we need to do is:
- Create a GtkScrolledWindow and insert it as a child of the GtkApplicationWindow; and
- Create a GtkScrolledWindow and insert it as a child of the GtkApplicationWindow
- Insert the GtkTextView widget to the GtkScrolledWindow as a child.
Modify `tfv1.c` and save it as `tfv2.c`.
The difference between these two files is small.
There is only a few difference between these two files.
~~~
$ cd tfv; diff tfv1.c tfv2.c
@ -106,18 +116,18 @@ $ cd tfv; diff tfv1.c tfv2.c
---
> gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
40c44
< app = gtk_application_new ("com.github.ToshioCP.tfv1", G_APPLICATION_FLAGS_NONE);
< app = gtk_application_new ("com.github.ToshioCP.tfv1", G_APPLICATION_DEFAULT_FLAGS);
---
> app = gtk_application_new ("com.github.ToshioCP.tfv2", G_APPLICATION_FLAGS_NONE);
> app = gtk_application_new ("com.github.ToshioCP.tfv2", G_APPLICATION_DEFAULT_FLAGS);
~~~
Here is the complete code of `tfv2.c`.
The whole code of `tfv2.c` is as follows.
~~~C
1 #include <gtk/gtk.h>
2
3 static void
4 app_activate (GApplication *app, gpointer user_data) {
4 app_activate (GApplication *app) {
5 GtkWidget *win;
6 GtkWidget *scr;
7 GtkWidget *tv;
@ -132,7 +142,7 @@ Here is the complete code of `tfv2.c`.
16 "He cut it, then there was a small cute baby girl in it. "
17 "The girl was shining faintly. "
18 "He thought this baby girl is a gift from Heaven and took her home.\n"
19 "His wife was surprized at his tale. "
19 "His wife was surprized at his story. "
20 "They were very happy because they had no children. "
21 ;
22 win = gtk_application_window_new (GTK_APPLICATION (app));
@ -157,7 +167,7 @@ Here is the complete code of `tfv2.c`.
41 GtkApplication *app;
42 int stat;
43
44 app = gtk_application_new ("com.github.ToshioCP.tfv2", G_APPLICATION_FLAGS_NONE);
44 app = gtk_application_new ("com.github.ToshioCP.tfv2", G_APPLICATION_DEFAULT_FLAGS);
45 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
46 stat = g_application_run (G_APPLICATION (app), argc, argv);
47 g_object_unref (app);
@ -166,7 +176,8 @@ Here is the complete code of `tfv2.c`.
~~~
Compile and run it.
Notice how this time the window doesn't extend when you type a lot of characters,
it just scrolls and displays a slider.
Up: [Readme.md](../Readme.md), Prev: [Section 4](sec4.md), Next: [Section 6](sec6.md)
Now, the window doesn't extend even if you type a lot of characters,
it just scrolls.
Up: [README.md](../README.md), Prev: [Section 4](sec4.md), Next: [Section 6](sec6.md)

View file

@ -1,6 +1,6 @@
Up: [README.md](../README.md), Prev: [Section 5](sec5.md), Next: [Section 7](sec7.md)
# String and memory management
# Strings and memory management
GtkTextView and GtkTextBuffer have functions that use string parameters or return a string.
The knowledge of strings and memory management is useful to understand how to use these functions.
@ -8,12 +8,11 @@ The knowledge of strings and memory management is useful to understand how to us
## String and memory
A String is an array of characters that is terminated with '\0'.
Strings are not a C type such as char, int, float or double,
but exist as a pointer to a character array. They behaves like a string type
which you may be familiar from other languages.
So, this pointer is often called 'a string'.
String is not a C type such as char, int, float or double, but a character array.
It behaves like a string in other languages.
So, the pointer is often called 'a string'.
In the following, `a` and `b` defined as character arrays, and are strings.
The following is a sample program.
~~~C
char a[10], *b;
@ -30,50 +29,50 @@ b = a;
/* *(++b) is 'e' */
~~~
The array `a` has `char` elements and the size of ten.
The first six elements are 'H', 'e', 'l', 'l', 'o' and '\0'.
This array represents the string "Hello".
The first five elements are character codes that correspond to the characters.
The sixth element is '\0', which is the same as zero,
and indicates that the string ends there.
The size of the array is 10, so 4 bytes aren't used, but that's OK,
they are just ignored.
An array `a` is defined as a `char` type array and its size is ten.
The first five elements are 'H', 'e', 'l', 'l', 'o'.
They are character codes.
For example, 'H' is the same as 0x48 or 72.
The sixth element is '\0', which is the same as zero, and indicates that the sequence of the data ends there.
The array represents the string "Hello".
The size of the array is 10, so 4 bytes aren't used.
But it's OK.
They are just ignored.
(If 'a' is defined out of functions or its class is static, they are assigned with zero.
Otherwise, that is to say, the class is auto or register, they are undefined.)
The variable 'b' is a pointer to a character.
Because `b` is assigned to be `a`, `a` and `b` point the same character ('H').
The variable `a` is defined as an array and it can't be changed.
It always point the top address of the array.
On the other hand, 'b' is a pointer, which is mutable, so `b` can be change.
It is then possible to write statements like `++b`, which means take the value in b (n address),
increase it by one, and store that back in `b`.
It is assigned with `a`, so `b` points the first element of `a` (character 'H').
The array `a` is immutable.
So `a=a+1` causes syntax error.
If a pointer is NULL, it points to nothing.
On the other hand, 'b' is a pointer type variable, which is mutable.
So, `++b`, which increases `b` by one, is allowed.
If a pointer is NULL, it points nothing.
So, the pointer is not a string.
A NULL string on the other hand will be a pointer which points to a location
that contains `\0`, which is a string of length 0 (or "").
Programs that use strings will include bugs if you aren't careful when using NULL pointers.
It is different from empty string.
Empty string is a pointer points `\0`.
Another annoying problem is the memory that a string is allocated.
There are four cases:
- The string is read only;
- The string is in static memory area;
- The string is in stack; and
- The string is in memory allocated from the heap area.
- The string is read only
- The string is in static memory area
- The string is in stack
- The string is in memory allocated from the heap area
## Read only string
A string literal in a C program is surrounded by double quotes and written as the following:
A string literal is surrounded by double quotes like this:
~~~C
char *s;
s = "Hello"
~~~
"Hello" is a string literal, and is stored in program memory.
A string literal is read only.
In the program above, `s` points the string literal.
"Hello" is a string literal, and is read only.
So, the following program is illegal.
~~~C
@ -83,25 +82,38 @@ So, the following program is illegal.
The result is undefined.
Probably a bad thing will happen, for example, a segmentation fault.
NOTE: The memory of the literal string is allocated when the program is
compiled. It is possible to view all the literal strings defined in your program
by using the `string` command.
NOTE: The memory of the literal string is allocated when the program is compiled.
It is possible to see the literal strings with `strings` command.
~~~
$ strings src/tvf/a.out
/lib64/ld-linux-x86-64.so.2
cN<5
... ... ...
... ... ...
Once upon a time, there was an old man who was called Taketori-no-Okina. It is a japanese word that means a man whose work is making bamboo baskets.
One day, he went into a mountain and found a shining bamboo. "What a mysterious bamboo it is!," he said. He cut it, then there was a small cute baby girl in it. The girl was shining faintly. He thought this baby girl is a gift from Heaven and took her home.
His wife was surprized at his story. They were very happy because they had no children.
... ... ...
... ... ...
~~~
It tells us that literal strings are embedded in program binary codes.
## Strings defined as arrays
If a string is defined as an array, it's in either stored in the static memory area or stack.
This depends on the class of the array.
If a string is defined as an array, it's stored in static memory area or stack.
It depends on the class of the array.
If the array's class is `static`, then it's placed in static memory area.
This allocation and memory address is fixed for the life of the program.
This area can be changed and is writable.
The allocated memory lives for the life of the program.
This area is writable.
If the array's class is `auto`, then it's placed in stack.
If the array's class is `auto`, it's placed in stack.
If the array is defined inside a function, its default class is `auto`.
The stack area will disappear when the function exits and returns to the caller.
The stack area will disappear when the function returns to the caller.
Arrays defined on the stack are writable.
~~~C
static char a[] = {'H', 'e', 'l', 'l', 'o', '\0'};
void
@ -116,30 +128,28 @@ print_strings (void) {
}
~~~
The array `a` is defined externally to a function and is global in its scope.
Such variables are placed in static memory area even if the `static` class is left out.
The compiler calculates the number of the elements in the right hand side (six),
and then creates code that allocates six bytes in the static memory area and copies the data to this memory.
The array `a` is defined out of functions.
It is placed in the static memory area even if the `static` class is left out.
The compiler calculates the number of the elements (six) and allocates six bytes in the static memory area.
Then, it copies "Hello" literal string data to the memory.
The array `b` is defined inside the function
so its class is `auto`.
The array `b` is defined inside the function, so its class is `auto`.
The compiler calculates the number of the elements in the string literal.
It has six elements as the zero termination character is also included.
The compiler creates code which allocates six bytes memory in the stack and copies the data to the memory.
It is six because it has '\0' terminator.
The compiler allocates six bytes in the stack and copies "Hello" litaral string to the stack memory.
Both `a` and `b` are writable.
The memory is managed by the executable program.
You don't need your program to allocate or free the memory for `a` and `b`.
The array `a` is created then the program is first run and remains for the life of the program.
The array `b` is created on the stack then the function is called, disappears when the function returns.
The memory is allocated and freed by the program automatically so you don't need to allocate or free.
The array `a` is alive during the program's life time.
The array `b` is alive when the function is called until the function returns to the caller.
## Strings in the heap area
You can also get, use and release memory from the heap area.
You can get, use and release memory from the heap area.
The standard C library provides `malloc` to get memory and `free` to put back memory.
GLib provides the functions `g_new` and `g_free` to do the same thing, with support for
some additional GLib functionality.
GLib provides the functions `g_new` and `g_free`.
They are similar to `malloc` and `free`.
~~~C
g_new (struct_type, n_struct)
@ -197,9 +207,9 @@ s = g_strdup ("Hello");
g_free (s);
~~~
The string literal "Hello" has 6 bytes because the string has '\0' at the end it.
The string literal "Hello" has 6 bytes because the string has '\0' at the end.
`g_strdup` gets 6 bytes from the heap area and copies the string to the memory.
`s` is assigned the top address of the memory.
`s` is assigned the start address of the memory.
`g_free` returns the memory to the heap area.
`g_strdup` is described in [GLib API Reference](https://docs.gtk.org/glib/func.strdup.html).
@ -207,24 +217,14 @@ The following is extracted from the reference.
> The returned string should be freed with `g_free()` when no longer needed.
The function reference will describe if the returned value needs to be freed.
If you forget to free the allocated memory it will remain allocated. Repeated use will cause
more memory to be allocated to the program, which will grow over time. This is called a memory leak,
and the only way to address this bug is to close the program (and restart it),
which will automatically release all of the programs memory back to the system.
If you forget to free the allocated memory it will remain until the program ends.
Repeated allocation and no freeing cause memory leak.
It is a bug and may bring a serious problem.
Some GLib functions return a string which mustn't be freed by the caller.
## const qualifier
~~~C
const char *
g_quark_to_string (GQuark quark);
~~~
This function returns `const char*` type.
The qualifier `const` means that the returned value is immutable.
The characters pointed by the returned value aren't be allowed to be changed or freed.
If a variable is qualified with `const`, the variable can't be assigned except during initialization.
A `const` qualified variable can be assigned to initialize it.
Once it is initialized, it is never allowed to change or free.
~~~C
const int x = 10; /* initialization is OK. */
@ -232,4 +232,25 @@ const int x = 10; /* initialization is OK. */
x = 20; /* This is illegal because x is qualified with const */
~~~
If a function returns `const char*` type, the string can't be changed or freed.
If a function has a `const char *` type parameter, it ensures that the parameter is not changed in the function.
~~~C
// You never change or free the returned string.
const char*
gtk_label_get_text (
GtkLabel* self
)
// Str keeps itself during the function runs
void
gtk_label_set_text (
GtkLabel* self,
const char* str
)
~~~
Up: [README.md](../README.md), Prev: [Section 5](sec5.md), Next: [Section 7](sec7.md)

View file

@ -1,4 +1,4 @@
Up: [Readme.md](../Readme.md), Prev: [Section 6](sec6.md), Next: [Section 8](sec8.md)
Up: [README.md](../README.md), Prev: [Section 6](sec6.md), Next: [Section 8](sec8.md)
# Widgets (3)
@ -6,51 +6,40 @@ Up: [Readme.md](../Readme.md), Prev: [Section 6](sec6.md), Next: [Section 8](se
### G\_APPLICATION\_HANDLES\_OPEN flag
The GtkTextView, GtkTextBuffer and GtkScrolledWindow widgets have given us a minimum editor
in the previous section.
We will now add a function to read a file and rework the program into a file viewer.
There are many ways to implement the function and
because this is a tutorial for beginners, we'll take the easiest one.
We made a very simple editor in the previous section with GtkTextView, GtkTextBuffer and GtkScrolledWindow.
We will add file-reading ability to the program and improve it to a file viewer.
When the program starts, we will give the filename to open as an argument.
The easiest way to give a filename is to use a command line argument.
$ ./a.out filename
It will open the file and insert its contents into the GtkTextBuffer.
The program will open the file and insert its contents into the GtkTextBuffer.
To do this, we need to know how GtkApplication (or GApplication) recognizes arguments.
This is described in the [GIO API Reference, Application](https://docs.gtk.org/gio/class.Application.html).
This is described in the [GIO API Reference -- Application](https://docs.gtk.org/gio/class.Application.html).
When GtkApplication is created, a flag (with the type GApplicationFlags) is provided as an argument.
When GtkApplication is created, a flag (GApplicationFlags) is given as an argument.
~~~C
GtkApplication *
gtk_application_new (const gchar *application_id, GApplicationFlags flags);
~~~
This tutorial explains only two flags, `G_APPLICATION_FLAGS_NONE` and `G_APPLICATION_HANDLES_OPEN`.
This tutorial explains only two flags, `G_APPLICATION_DEFAULT_FLAGS` and `G_APPLICATION_HANDLES_OPEN`.
(`G_APPLICATION_FLAGS_NONE` was used instead of `G_APPLICATION_DEFAULT_FLAGS` before GIO version2.73.3 (GLib 2.73.3 5/Aug/2022).
Some GTK 4 applications still use `G_APPLICATION_FLAGS_NONE`.
But now it is deprecated and `G_APPLICATION_DEFAULT_FLAGS` is recommended.)
If you want to handle command line arguments, the `G_APPLICATION_HANDLES_COMMAND_LINE` flag is what you need.
How to use the new application method is described in [GIO API Reference, g\_application\_run](https://docs.gtk.org/gio/method.Application.run.html),
and the flag is described in the [GIO API Reference, ApplicationFlags](https://docs.gtk.org/gio/flags.ApplicationFlags.html).
~~~
GApplicationFlags' Members
For further information, see [GIO API Reference -- ApplicationFlags](https://docs.gtk.org/gio/flags.ApplicationFlags.html) and
[GIO API Reference -- g\_application\_run](https://docs.gtk.org/gio/method.Application.run.html).
G_APPLICATION_FLAGS_NONE Default. (No argument allowed)
... ... ...
G_APPLICATION_HANDLES_OPEN This application handles opening files (in the primary instance).
... ... ...
~~~
There are ten flags in total, but we only need two of them so far.
We've already used `G_APPLICATION_FLAGS_NONE`, as
it is the simplest option, and no arguments are allowed.
If you provide arguments when running the application, an error will occur.
We've already used `G_APPLICATION_DEFAULT_FLAGS`, as it is the simplest option, and no command line arguments are allowed.
If you give arguments, an error will occur.
The flag `G_APPLICATION_HANDLES_OPEN` is the second simplest option.
It allows arguments but only files.
The application assumes all the arguments are filenames and we will use this flag when creating
our GtkApplication.
The application assumes all the arguments are filenames.
~~~C
app = gtk_application_new ("com.github.ToshioCP.tfv3", G_APPLICATION_HANDLES_OPEN);
@ -58,7 +47,7 @@ app = gtk_application_new ("com.github.ToshioCP.tfv3", G_APPLICATION_HANDLES_OPE
### open signal
Now, when the application starts, two signals can be emitted.
When `G_APPLICATION_HANDLES_OPEN` flag is given to the application, two signals are available.
- activate signal --- This signal is emitted when there's no argument.
- open signal --- This signal is emitted when there is at least one argument.
@ -66,48 +55,49 @@ Now, when the application starts, two signals can be emitted.
The handler of the "open" signal is defined as follows.
~~~C
void user_function (GApplication *application,
gpointer files,
gint n_files,
gchar *hint,
gpointer user_data)
void
open (
GApplication* self,
gpointer files,
gint n_files,
gchar* hint,
gpointer user_data
)
~~~
The parameters are:
- `application` --- the application (usually GtkApplication)
- `self` --- the application instance (usually GtkApplication)
- `files` --- an array of GFiles. [array length=n\_files] [element-type GFile]
- `n_files` --- the number of the elements of `files`
- `hint` --- a hint provided by the calling instance (usually it can be ignored)
- `user_data` --- user data set when the signal handler was connected.
How to read a specified file (GFile) will be described next.
## Making a file viewer
### What is a file viewer?
A file viewer is a program that displays the text file that is given as an argument.
A file viewer is a program that displays text files.
Our file viewer will work as follows.
- When arguments are given, it treats the first argument as a filename and opens it.
- If opening the file succeeds, it reads the contents of the file and inserts it to GtkTextBuffer and then shows the window.
- If it fails to open the file, it will show an error message and quit.
- If there's no argument, it will shows an error message and quit.
- If there are two or more arguments, the second one and any others are ignored.
- When arguments are given, it recognizes the first argument as a filename and opens it.
- The second argument and after are ignored.
- If there's no argument, it shows an error message and quit.
- If it successfully opens the file, it reads the contents of the file, inserts them to GtkTextBuffer and shows the window.
- If it fails to open the file, it shows an error message and quit.
The program which does this is shown below.
The program is shown below.
~~~C
1 #include <gtk/gtk.h>
2
3 static void
4 app_activate (GApplication *app, gpointer user_data) {
5 g_print ("You need a filename argument.\n");
4 app_activate (GApplication *app) {
5 g_printerr ("You need a filename argument.\n");
6 }
7
8 static void
9 app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
9 app_open (GApplication *app, GFile ** files, int n_files, char *hint) {
10 GtkWidget *win;
11 GtkWidget *scr;
12 GtkWidget *tv;
@ -138,25 +128,26 @@ The program which does this is shown below.
37 gtk_widget_show (win);
38 } else {
39 if ((filename = g_file_get_path (files[0])) != NULL) {
40 g_print ("No such file: %s.\n", filename);
40 g_printerr ("No such file: %s.\n", filename);
41 g_free (filename);
42 }
43 gtk_window_destroy (GTK_WINDOW (win));
44 }
45 }
46
47 int
48 main (int argc, char **argv) {
49 GtkApplication *app;
50 int stat;
51
52 app = gtk_application_new ("com.github.ToshioCP.tfv3", G_APPLICATION_HANDLES_OPEN);
53 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
54 g_signal_connect (app, "open", G_CALLBACK (app_open), NULL);
55 stat = g_application_run (G_APPLICATION (app), argc, argv);
56 g_object_unref (app);
57 return stat;
58 }
42 } else
43 g_printerr ("File can't be opened.\n");
44 gtk_window_destroy (GTK_WINDOW (win));
45 }
46 }
47
48 int
49 main (int argc, char **argv) {
50 GtkApplication *app;
51 int stat;
52
53 app = gtk_application_new ("com.github.ToshioCP.tfv3", G_APPLICATION_HANDLES_OPEN);
54 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
55 g_signal_connect (app, "open", G_CALLBACK (app_open), NULL);
56 stat = g_application_run (G_APPLICATION (app), argc, argv);
57 g_object_unref (app);
58 return stat;
59 }
~~~
Save it as `tfv3.c`.
@ -167,25 +158,29 @@ Then compile and run it.
![File viewer](../image/screenshot_tfv3.png)
Let's explain how the program `tfv3.c` works.
First, the function `main` has only two changes from the previous version.
The function `main` has only two changes from the previous version.
- `G_APPLICATION_FLAGS_NONE` is replaced by `G_APPLICATION_HANDLES_OPEN`; and
- `g_signal_connect (app, "open", G_CALLBACK (on_open), NULL)` is added.
- `G_APPLICATION_DEFAULT_FLAGS` is replaced by `G_APPLICATION_HANDLES_OPEN`
- `g_signal_connect (app, "open", G_CALLBACK (app_open), NULL)` is added.
Next, the handler `app_activate` is added and is very simple.
It just outputs the error message and
the application quits immediately because no window is created.
When the flag `G_APPLICATION_HANDLES_OPEN` is given to `gtk_application_new` function, the application behaves like this:
The main functionality is the in the handler `app_open`. It
- If the application is run without command line arguments, it emits "activate" signal when it is activated.
- If the application is run with command line arguments, it emits "open" signal when it is activated.
- Creates GtkApplicationWindow, GtkScrolledWindow, GtkTextView and GtkTextBuffer and connects them together;
- Sets wrap mode to `GTK_WRAP_WORD_CHAR` in GtktextView;
- Sets GtkTextView to non-editable because the program isn't an editor but only a viewer;
- Reads the file and inserts the text into GtkTextBuffer (this will be explained in detail later); and
- If the file is not opened then outputs an error message and destroys the window. This makes the application quit.
The handler `app_activate` becomes very simple.
It just outputs the error message and return to the caller.
Then the application quits immediately because no window is created.
The following is the important file reading part of the program and is shown again below.
The main work is done in the handler `app_open`.
- Creates GtkApplicationWindow, GtkScrolledWindow, GtkTextView and GtkTextBuffer and connects them together
- Sets wrap mode to `GTK_WRAP_WORD_CHAR` in GtktextView
- Sets GtkTextView to non-editable because the program isn't an editor but only a viewer
- Reads the file and inserts the text into GtkTextBuffer (this will be explained later)
- If the file is not opened, outputs an error message and destroys the window. This makes the application quit.
The following is the file reading part of the program again.
~~~C
if (g_file_load_contents (files[0], NULL, &contents, &length, NULL, NULL)) {
@ -198,50 +193,52 @@ if (g_file_load_contents (files[0], NULL, &contents, &length, NULL, NULL)) {
gtk_widget_show (win);
} else {
if ((filename = g_file_get_path (files[0])) != NULL) {
g_print ("No such file: %s.\n", filename);
g_printerr ("No such file: %s.\n", filename);
g_free (filename);
}
} else
g_printerr ("File can't be opened.\n");
gtk_window_destroy (GTK_WINDOW (win));
}
~~~
The function `g_file_load_contents` loads the file contents into a buffer,
which is automatically allocated and sets `contents` to point that buffer.
The length of the buffer is set to `length`.
It returns `TRUE` if the file's contents are successfully loaded and `FALSE` if an error occurs.
The function `g_file_load_contents` loads the file contents into a temporary buffer,
which is automatically allocated and sets `contents` to point the buffer.
The length of the buffer is assigned to `length`.
It returns `TRUE` if the file's contents are successfully loaded or `FALSE` if an error occurs.
If you want to know the details about g\_file\_load\_contents, see [g file load contents](https://docs.gtk.org/gio/method.File.load_contents.html).
If this function succeeds, it inserts the contents into GtkTextBuffer,
frees the buffer pointed by `contents`, sets the title of the window,
If it has successfully read the file, it inserts the contents into GtkTextBuffer,
frees the temporary buffer pointed by `contents`, sets the title of the window,
frees the memories pointed by `filename` and then shows the window.
If it fails, it outputs an error message and destroys the window, causing the program to quit.
If it fails, it outputs an error message and destroys the window and finally make the program quit.
## GtkNotebook
GtkNotebook is a container widget that uses tabs and contains multiple children.
The child that is displayed depends on which tab has been selected.
GtkNotebook is a container widget that contains multiple widgets with tabs.
It shows only one child at a time.
Another child will be shown when its tab is clicked.
![GtkNotebook](../image/screenshot_gtk_notebook.png)
Looking at the screenshots above,
the left one is the window at the startup.
It shows the file `pr1.c` and the filename is in the left tab.
After clicking on the right tab, the contents of the file `tfv1.c` are shown instead.
This is shown in the right screenshot.
The left image is the window at the startup.
The file `pr1.c` is shown and its filename is in the left tab.
After clicking on the right tab, the contents of the file `tfv1.c` is shown.
The right image is the screenshot.
The GtkNotebook widget is inserted as a child of GtkApplicationWindow and contains a GtkScrolledWindow
for each file that is being displayed.
The code to do this is given in `tfv4.c` and is:
The following is `tfv4.c`.
It has GtkNoteBook widget.
It is inserted as a child of GtkApplicationWindow and contains multiple GtkScrolledWindow.
~~~C
1 #include <gtk/gtk.h>
2
3 static void
4 app_activate (GApplication *app, gpointer user_data) {
5 g_print ("You need a filename argument.\n");
4 app_activate (GApplication *app) {
5 g_printerr ("You need a filename argument.\n");
6 }
7
8 static void
9 app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
9 app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint) {
10 GtkWidget *win;
11 GtkWidget *nb;
12 GtkWidget *lab;
@ -256,90 +253,81 @@ The code to do this is given in `tfv4.c` and is:
21
22 win = gtk_application_window_new (GTK_APPLICATION (app));
23 gtk_window_set_title (GTK_WINDOW (win), "file viewer");
24 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
25 gtk_window_maximize (GTK_WINDOW (win));
26
27 nb = gtk_notebook_new ();
28 gtk_window_set_child (GTK_WINDOW (win), nb);
29
30 for (i = 0; i < n_files; i++) {
31 if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) {
32 scr = gtk_scrolled_window_new ();
33 tv = gtk_text_view_new ();
34 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
35 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
36 gtk_text_view_set_editable (GTK_TEXT_VIEW (tv), FALSE);
37 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
38
39 gtk_text_buffer_set_text (tb, contents, length);
40 g_free (contents);
41 if ((filename = g_file_get_basename (files[i])) != NULL) {
42 lab = gtk_label_new (filename);
43 g_free (filename);
44 } else
45 lab = gtk_label_new ("");
46 gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
47 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
48 g_object_set (nbp, "tab-expand", TRUE, NULL);
49 } else if ((filename = g_file_get_path (files[i])) != NULL) {
50 g_print ("No such file: %s.\n", filename);
51 g_free (filename);
52 } else
53 g_print ("No valid file is given\n");
54 }
55 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0)
56 gtk_widget_show (win);
57 else
58 gtk_window_destroy (GTK_WINDOW (win));
59 }
60
61 int
62 main (int argc, char **argv) {
63 GtkApplication *app;
64 int stat;
65
66 app = gtk_application_new ("com.github.ToshioCP.tfv4", G_APPLICATION_HANDLES_OPEN);
67 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
68 g_signal_connect (app, "open", G_CALLBACK (app_open), NULL);
69 stat = g_application_run (G_APPLICATION (app), argc, argv);
70 g_object_unref (app);
71 return stat;
72 }
24 gtk_window_set_default_size (GTK_WINDOW (win), 600, 400);
25
26 nb = gtk_notebook_new ();
27 gtk_window_set_child (GTK_WINDOW (win), nb);
28
29 for (i = 0; i < n_files; i++) {
30 if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) {
31 scr = gtk_scrolled_window_new ();
32 tv = gtk_text_view_new ();
33 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
34 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
35 gtk_text_view_set_editable (GTK_TEXT_VIEW (tv), FALSE);
36 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
37
38 gtk_text_buffer_set_text (tb, contents, length);
39 g_free (contents);
40 if ((filename = g_file_get_basename (files[i])) != NULL) {
41 lab = gtk_label_new (filename);
42 g_free (filename);
43 } else
44 lab = gtk_label_new ("");
45 gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
46 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
47 g_object_set (nbp, "tab-expand", TRUE, NULL);
48 } else if ((filename = g_file_get_path (files[i])) != NULL) {
49 g_printerr ("No such file: %s.\n", filename);
50 g_free (filename);
51 } else
52 g_printerr ("No valid file is given\n");
53 }
54 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0)
55 gtk_widget_show (win);
56 else
57 gtk_window_destroy (GTK_WINDOW (win));
58 }
59
60 int
61 main (int argc, char **argv) {
62 GtkApplication *app;
63 int stat;
64
65 app = gtk_application_new ("com.github.ToshioCP.tfv4", G_APPLICATION_HANDLES_OPEN);
66 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
67 g_signal_connect (app, "open", G_CALLBACK (app_open), NULL);
68 stat = g_application_run (G_APPLICATION (app), argc, argv);
69 g_object_unref (app);
70 return stat;
71 }
~~~
Most of the changes are in the function `app_open`.
The numbers at the left of the following items are line numbers in the source code.
- 11-13: Variables `nb`, `lab` and `nbp` are defined and will point to a new GtkNotebook, GtkLabel and GtkNotebookPage widget respectively.
- 11-13: Variables `nb`, `lab` and `nbp` are defined. They point GtkNotebook, GtkLabel and GtkNotebookPage respectively.
- 23: The window's title is set to "file viewer".
- 25: The size of the window is set to maximum because a big window is appropriate for file viewers.
- 27-28 GtkNotebook is created and inserted to the GtkApplicationWindow as a child.
- 30-59 For-loop. Each loop corresponds to a filename argument, and `files[i]` is GFile object containing the i-th argument.
- 32-37 GtkScrollledWindow, GtkTextView are created and GtkTextBuffer found from the new GtkTextView.
GtkTextView is connected to GtkScrolledWindow as a child.
Each file gets their own copy of these widgets, so they are created inside the for-loop.
- 39-40 inserts the contents of the file into GtkTextBuffer and frees the memory pointed by `contents`.
- 41-43: Gets the filename and creates GtkLabel with the filename and then frees `filename`.
- 44-45: If `filename` is NULL, creates GtkLabel with the empty string.
- 46: Appends GtkScrolledWindow as a page, with the tab GtkLabel, to GtkNotebook.
At this time a GtkNoteBookPage widget is created automatically.
The GtkScrolledWindow widget is connected to the GtkNotebookPage.
Therefore, the structure is like this:
~~~
GtkNotebook -- GtkNotebookPage -- GtkScrolledWindow
~~~
- 47: Gets GtkNotebookPage widget and sets `nbp` to point to this GtkNotebookPage.
- 48: GtkNotebookPage has a property set called "tab-expand".
- 24: The default size of the window is 600x400.
- 26-27 GtkNotebook is created and inserted to the GtkApplicationWindow as a child.
- 29-58 For-loop. The variable `files[i]` points i-th GFile, which is created by the GtkApplication from the i-th command line argument.
- 31-36 GtkScrollledWindow, GtkTextView are created. GtkTextBuffer is got from the GtkTextView.
The GtkTextView is connected to the GtkScrolledWindow as a child.
- 38-39 inserts the contents of the file into GtkTextBuffer and frees the memory pointed by `contents`.
- 40-42: If the filename is taken from the GFile, GtkLabel is created with the filename. The string `filename` is freed..
- 43-44: If it fails to take the filename, empty string GtkLabel is created.
- 45-46: Appends a GtkScrolledWindow to the GtkNotebook as a child.
And the GtkLabel is set as the child's tab.
At the same time, a GtkNoteBookPage is created automatically.
The function `gtk_notebook_get_page` returns the GtkNotebookPage of the child (GtkScrolledWindow).
- 47: GtkNotebookPage has "tab-expand" property.
If it is set to TRUE then the tab expands horizontally as long as possible.
If it is FALSE, then the width of the tab is determined by the size of the label.
`g_object_set` is a general function to set properties in objects.
See [GObject API Reference, g\_object\_set](https://docs.gtk.org/gobject/method.Object.set.html).
- 49-51: If the file cannot be read, "No such file" message is displayed and the `filename` buffer is freed.
- 52-53: If `filename` is NULL, the "No valid file is given" message is outputted.
- 55-58: If at least one file was read, then the number of GtkNotebookPage is greater than zero.
If it's true, it shows the window.
If it's false, it destroys the window, which causes the program to quit.
`g_object_set` is a general function to set properties of objects.
See [GObject API Reference -- g\_object\_set](https://docs.gtk.org/gobject/method.Object.set.html).
- 48-50: If it fails to read the file and a filename is taken from the GFile, "No such file" message is displayed. The `filename` is freed.
- 51-52: If `filename` is NULL, the "No valid file is given" message is displayed.
- 54-57: If at least one page exists, the window is shown.
Otherwise, the window is destroyed and the application quits.
Up: [Readme.md](../Readme.md), Prev: [Section 6](sec6.md), Next: [Section 8](sec8.md)
Up: [README.md](../README.md), Prev: [Section 6](sec6.md), Next: [Section 8](sec8.md)

View file

@ -1,62 +1,64 @@
Up: [README.md](../README.md), Prev: [Section 7](sec7.md), Next: [Section 9](sec9.md)
# Defining a child object
# Defining a final class
## A very simple editor
In the previous section we made a very simple file viewer.
We made a very simple file viewer in the previous section.
Now we go on to rewrite it and turn it into very simple editor.
Its source file is in tfe1.c (text file editor 1).
Its source file is `tfe1.c` (text file editor 1) under `tfe` directory.
GtkTextView has a feature for editing multiple lines. Therefore, we don't need to
write the program from scratch, we just add two things to the file viewer:
GtkTextView is a multi-line editor.
So, we don't need to write the editor from scratch.
We just add two things to the file viewer:
- Memory to store a pointer to the GFile instance.
- A function to write the file.
- Pointers to GFile instances.
- A text-save function.
There are a couple of ways to store the details of GFile.
There are a couple of ways to store the pointers.
- Use global variables; or
- Make a child object, which can extend the instance memory for the GFile object.
- Use global variables
- Make a child class of GtkTextView and its each instance holds a pointer to the GFile instance.
Using global variables is easy to implement.
Define a sufficient size array of pointers to GFile.
Define a sufficient size pointer array to GFile.
For example,
~~~C
GFile *f[20];
~~~
The variable `f[i]` corresponds to the file associated to the i-th GtkNotebookPage.
There are however two problems with this.
The first concerns the size of the array.
If a user gives too many arguments (more than 20 in the example above), it is impossible to store the additional pointers to the GFile instances.
The second is the increasing difficulty for maintenance of the program.
We have a small program so far,
but however, if you continue developing it, the size of the program will grow.
Generally speaking, the bigger the program size, the more difficult it is to keep track of and maintain global variables. Global variables can be used and changed anywhere throughout the entire program.
The variable `f[i]` corresponds to the file associated with the i-th GtkNotebookPage.
Making a child object is a good idea in terms of maintenance.
One thing you need to be careful of is the difference between "child object" and "child widget".
Here we are describing a "child object".
A child object includes, and expands on its parent object, as
a child object derives everything from the parent object.
However, There are two problems.
The first is the size of the array.
If a user gives too many arguments (more than 20 in the example above), it is impossible to store all the pointers to the GFile instances.
The second is difficulty to maintain the program.
We have a small program so far.
But, the more you develop the program, the bigger its size grows.
Generally speaking, it is very difficult to maintain global variables in a big program.
When you check the global variable, you need to check all the codes that use the variable.
Making a child class is a good idea in terms of maintenance.
And we prefer it rather than a global variable.
Be careful that we are thinking about "child class", not "child widget".
Child class and child widget are totally different.
Class is a term of GObject system.
If you are not familiar with GObject, see:
- [GObject API reference](https://docs.gtk.org/gobject/)
- [GObject tutorial for beginners](https://toshiocp.github.io/Gobject-tutorial/)
A child class inherits everything from the parent and, in addition, extends its performance.
We will define TfeTextView as a child class of GtkTextView.
It has everything that GtkTextView has and adds a pointer to a GFile.
![Child object of GtkTextView](../image/child.png)
We will define TfeTextView as a child object of GtkTextView.
It has everything that GtkTextView has.
Specifically, TfeTextView has a GtkTextbuffer which corresponds to the GtkTextView inside TfeTextView.
The additional important thing is that TfeTextView can also keep an additional pointer to GFile.
## How to define a child class of GtkTextView
In general, this is how GObjects work. Understanding the general theory about Gobject's is difficult,
particularly for beginners.
So, I will just show you the way how to write the code and avoid the theoretical side.
If you want to know about GObject system, refer to another [tutorial](https://github.com/ToshioCP/Gobject-tutorial).
## How to define a child object of GtkTextView
Let's define the TfeTextView object, which is a child object of GtkTextView.
You need to know GObject system convention.
First, look at the program below.
~~~C
@ -95,55 +97,55 @@ tfe_text_view_new (void) {
}
~~~
If you are curious about the background theory of this program, that's good,
because knowing the theory is very important if you want to program GTK applications.
Look at [GObject API Reference](https://docs.gtk.org/gobject/).
All you need is described there,
or refer to [GObject tutorial](https://github.com/ToshioCP/Gobject-tutorial).
It's a tough journey especially for beginners so for now, you don't need to know about this difficult theory.
It is enough to just remember the instructions below.
- TfeTextView is divided into two parts.
Tfe and TextView.
Tfe is called the prefix, namespace or module.
TextView is called the object.
Tfe is called prefix or namespace.
TextView is called object.
- There are three differnet identifier patterns.
TfeTextView (camel case), tfe\_text\_view (this is used to write functions) and TFE\_TEXT\_VIEW (This is used to cast a pointer to point TfeTextView type).
TfeTextView (camel case), tfe\_text\_view (this is used for functions) and TFE\_TEXT\_VIEW (This is used to cast a object to TfeTextView).
- First, define TFE\_TYPE\_TEXT\_VIEW macro as tfe\_text\_view\_get\_type ().
The name is always (prefix)\_TYPE\_(object) and the letters are upper case.
And the replacement text is always (prefix)\_(object)\_get\_type () and the letters are lower case.
- Next, use G\_DECLARE\_FINAL\_TYPE macro.
The arguments are the child object name in camel case, lower case with underscore, prefix (upper case), object (upper case with underscore) and parent object name (camel case).
This definition is put before G\_DECLARE\_FINAL\_TYPE macro.
- The arguments of G\_DECLARE\_FINAL\_TYPE macro are the child class name in camel case, lower case with underscore, prefix (upper case), object (upper case with underscore) and parent class name (camel case).
The following two C structure is declared in the expansion of the macro.
- `typedef struct _TfeTextView TfeTextView`
- `typedef struct {GtkTextViewClass parent_class; } TfeTextViewClass;`
- These declaration tells us that TfeTextView and TfeTextViewClass are C structures.
"TfeTextView" has two meanings, class name and C structure name.
The C structure TfeTextView is called object.
Similarly, TfeTextViewClass is called class.
- Declare the structure \_TfeTextView.
The underscore is necessary.
The first member is the parent object.
The first member is the parent object (C structure).
Notice this is not a pointer but the object itself.
The second member and after are members of the child object.
TfeTextView structure has a pointer to a GFile instance as a member.
- Use G\_DEFINE\_TYPE macro.
- G\_DEFINE\_TYPE macro.
The arguments are the child object name in camel case, lower case with underscore and parent object type (prefix)\_TYPE\_(module).
- Define instance init function (tfe\_text\_view\_init).
Usually you don't need to do anything.
- Define class init function (tfe\_text\_view\_class\_init).
You don't need to do anything in this object.
- Write function codes you want to add (tfe\_text\_view\_set\_file and tfe\_text\_view\_get\_file).
`tv` is a pointer to the TfeTextView object instance which is a C-structure declared with the tag \_TfeTextView.
So, the structure has a member `file` as a pointer to a GFile instance.
`tv->file = f` is an assignment of `f` to a member `file` of the structure pointed by `tv`.
This is an example how to use the extended memory in a child widget.
- Write a function to create an instance.
This macro is mainly used to register the new class to the type system.
Type system is a base system of GObject.
Every class has its own type.
The types of GObject, GtkWidget and TfeTextView are G\_TYPE\_OBJECT, GTK\_TYPE\_WIDGET and TFE\_TYPE\_TEXT\_VIEW respectively.
Such type (for example, TFE\_TYPE\_TEXT\_VIEW) is a macro and it is expanded to a function (tfe\_text\_view\_get\_type()).
It returns a integer which is unique among all GObject system classes.
- Instance init function (tfe\_text\_view\_init) is called when the instance is created.
It is the same as a constructor in other object oriented languages.
- Class init function (tfe\_text\_view\_class\_init) is called when the class is created.
- Two functions tfe\_text\_view\_set\_file and tfe\_text\_view\_get\_file are public functions.
Public functions are open and you can call them anywhere.
They are the same as public method in other object oriented languages.
`tv` is a pointer to the TfeTextView object (C structure).
It has a member `file` and it is pointed by `tv->file`.
- TfeTextView instance creation function is `tfe_text_view_new`.
Its name is (prefix)\_(object)\_new.
If the parent object function needs parameters, this function also need them.
You sometimes might want to add some parameters.
It's your choice.
Use g\_object\_new function to create the instance.
The arguments are (prefix)\_TYPE\_(object), a list to initialize properties and NULL.
In this code no property needs to be initialized.
It uses g\_object\_new function to create the instance.
The arguments are (prefix)\_TYPE\_(object), a list to initialize properties and NULL.
NULL is the end mark of the property list.
No property is initialized here.
And the return value is casted to GtkWidget.
This program is not perfect.
It has some problems.
It will be modified later.
This program shows the outline how to define a child class.
## Close-request signal
@ -156,10 +158,9 @@ After you finish editing, you exit the editor.
The editor updates files just before the window closes.
GtkWindow emits the "close-request" signal before it closes.
We connect the signal and the handler `before_close`.
A handler is a C function.
When a function is connected to a certain signal, we call it a handler.
The function `before_close` is invoked when the signal "close-request" is emitted.
We will connect the signal and the handler `before_close`.
(A handler is a C function which is connected to a signal.)
The function `before_close` is called when the signal "close-request" is emitted.
~~~C
g_signal_connect (win, "close-request", G_CALLBACK (before_close), NULL);
@ -171,59 +172,66 @@ The program of `before_close` is as follows.
~~~C
1 static gboolean
2 before_close (GtkWindow *win, gpointer user_data) {
3 GtkWidget *nb = GTK_WIDGET (user_data);
4 GtkWidget *scr;
5 GtkWidget *tv;
6 GFile *file;
7 char *pathname;
8 GtkTextBuffer *tb;
9 GtkTextIter start_iter;
10 GtkTextIter end_iter;
11 char *contents;
12 unsigned int n;
13 unsigned int i;
14
15 n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb));
16 for (i = 0; i < n; ++i) {
17 scr = gtk_notebook_get_nth_page (GTK_NOTEBOOK (nb), i);
18 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
19 file = tfe_text_view_get_file (TFE_TEXT_VIEW (tv));
20 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
21 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
22 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
23 if (! g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, NULL)) {
24 pathname = g_file_get_path (file);
25 g_print ("ERROR : Can't save %s.", pathname);
26 g_free (pathname);
27 }
28 g_free (contents);
29 }
30 return FALSE;
31 }
2 before_close (GtkWindow *win, GtkWidget *nb) {
3 GtkWidget *scr;
4 GtkWidget *tv;
5 GFile *file;
6 char *pathname;
7 GtkTextBuffer *tb;
8 GtkTextIter start_iter;
9 GtkTextIter end_iter;
10 char *contents;
11 unsigned int n;
12 unsigned int i;
13
14 n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb));
15 for (i = 0; i < n; ++i) {
16 scr = gtk_notebook_get_nth_page (GTK_NOTEBOOK (nb), i);
17 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
18 file = tfe_text_view_get_file (TFE_TEXT_VIEW (tv));
19 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
20 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
21 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
22 if (! g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, NULL)) {
23 if ((pathname = g_file_get_path (file)) != NULL) {
24 g_printerr ("Can't save %s.", pathname);
25 g_free (pathname);
26 } else
27 g_printerr ("Pathname not exist.\n");
28 }
29 g_free (contents);
30 g_object_unref (file);
31 }
32 return FALSE;
33 }
~~~
The numbers on the left of items are line numbers in the source code.
- 15: Gets the number of pages `nb` has.
- 16-29: For loop with regard to the index to each pages.
- 17-19: Gets GtkScrolledWindow, TfeTextView and a pointer to GFile.
The pointer was stored when `app_open` handler had run. It will be shown later.
- 20-22: Gets GtkTextBuffer and contents. `start_iter` and `end_iter` are iterators of the buffer.
I don't want to explain them now because it would take a lot of time.
Just remember these lines for the present.
- 23-27: Writes the contents to the file.
If it fails, it outputs an error message.
- 28: Frees `contents`.
- 14: The number of pages of `nb` is assigned to `n`.
- 15-31: For loop with regard to the index to each pages.
- 16-18: `scr`, `tv` and `file` is assigned pointers to the GtkScrolledWindow, TfeTextView and GFile.
The GFile of TfeTextView was stored when `app_open` handler was called. It will be shown later.
- 19-21: `tb` is assigned the GtkTextBuffer of the TfeTextView.
The buffer is accessed with iterators.
Iterators points somewhere in the buffer.
The function `gtk_text_buffer_get_bounds` assigns the start and end of the buffer to `start_iter` and `end_iter` respectively.
Then the function `gtk_text_buffer_get_text` returns the text between `start_iter` and `end_iter`, which is the whole text in the buffer.
- 22-28: The text is saved to the file.
If it fails, error messages are displayed.
- 29: `contents` are freed.
- 30: GFile is useless. `g_object_unref` decreases the reference count of the GFile.
Reference count will be explained in the later section.
The reference count will be zero in this program and the GFile instance will destroy itself.
## Source code of tfe1.c
The following is the complete source code of `tfe1.c`.
The following is the whole source code of `tfe1.c`.
~~~C
1 #include <gtk/gtk.h>
2
3 /* Define TfeTextView Widget which is the child object of GtkTextView */
3 /* Define TfeTextView Widget which is the child class of GtkTextView */
4
5 #define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
6 G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
@ -238,32 +246,32 @@ The following is the complete source code of `tfe1.c`.
15
16 static void
17 tfe_text_view_init (TfeTextView *tv) {
18 }
19
20 static void
21 tfe_text_view_class_init (TfeTextViewClass *class) {
22 }
23
24 void
25 tfe_text_view_set_file (TfeTextView *tv, GFile *f) {
26 tv -> file = f;
27 }
28
29 GFile *
30 tfe_text_view_get_file (TfeTextView *tv) {
31 return tv -> file;
32 }
33
34 GtkWidget *
35 tfe_text_view_new (void) {
36 return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
37 }
38
39 /* ---------- end of the definition of TfeTextView ---------- */
40
41 static gboolean
42 before_close (GtkWindow *win, gpointer user_data) {
43 GtkWidget *nb = GTK_WIDGET (user_data);
18 tv->file = NULL;
19 }
20
21 static void
22 tfe_text_view_class_init (TfeTextViewClass *class) {
23 }
24
25 void
26 tfe_text_view_set_file (TfeTextView *tv, GFile *f) {
27 tv->file = f;
28 }
29
30 GFile *
31 tfe_text_view_get_file (TfeTextView *tv) {
32 return tv -> file;
33 }
34
35 GtkWidget *
36 tfe_text_view_new (void) {
37 return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
38 }
39
40 /* ---------- end of the definition of TfeTextView ---------- */
41
42 static gboolean
43 before_close (GtkWindow *win, GtkWidget *nb) {
44 GtkWidget *scr;
45 GtkWidget *tv;
46 GFile *file;
@ -284,96 +292,104 @@ The following is the complete source code of `tfe1.c`.
61 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
62 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
63 if (! g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, NULL)) {
64 pathname = g_file_get_path (file);
65 g_print ("ERROR : Can't save %s.", pathname);
66 g_free (pathname);
67 }
68 g_free (contents);
69 }
70 return FALSE;
71 }
72
73 static void
74 app_activate (GApplication *app, gpointer user_data) {
75 g_print ("You need to give filenames as arguments.\n");
76 }
77
78 static void
79 app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
80 GtkWidget *win;
81 GtkWidget *nb;
82 GtkWidget *lab;
83 GtkNotebookPage *nbp;
84 GtkWidget *scr;
85 GtkWidget *tv;
86 GtkTextBuffer *tb;
87 char *contents;
88 gsize length;
89 char *filename;
90 int i;
91
92 win = gtk_application_window_new (GTK_APPLICATION (app));
93 gtk_window_set_title (GTK_WINDOW (win), "file editor");
94 gtk_window_maximize (GTK_WINDOW (win));
95
96 nb = gtk_notebook_new ();
97 gtk_window_set_child (GTK_WINDOW (win), nb);
64 if ((pathname = g_file_get_path (file)) != NULL) {
65 g_printerr ("Can't save %s.", pathname);
66 g_free (pathname);
67 } else
68 g_printerr ("Pathname not exist.\n");
69 }
70 g_free (contents);
71 g_object_unref (file);
72 }
73 return FALSE;
74 }
75
76 static void
77 app_activate (GApplication *app) {
78 g_print ("You need to give filenames as arguments.\n");
79 }
80
81 static void
82 app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint) {
83 GtkWidget *win;
84 GtkWidget *nb;
85 GtkWidget *lab;
86 GtkNotebookPage *nbp;
87 GtkWidget *scr;
88 GtkWidget *tv;
89 GtkTextBuffer *tb;
90 char *contents;
91 gsize length;
92 char *filename;
93 int i;
94
95 win = gtk_application_window_new (GTK_APPLICATION (app));
96 gtk_window_set_title (GTK_WINDOW (win), "file editor");
97 gtk_window_set_default_size (GTK_WINDOW (win), 600, 400);
98
99 for (i = 0; i < n_files; i++) {
100 if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) {
101 scr = gtk_scrolled_window_new ();
102 tv = tfe_text_view_new ();
103 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
104 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
105 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
106
107 tfe_text_view_set_file (TFE_TEXT_VIEW (tv), g_file_dup (files[i]));
108 gtk_text_buffer_set_text (tb, contents, length);
109 g_free (contents);
110 filename = g_file_get_basename (files[i]);
111 lab = gtk_label_new (filename);
112 gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
113 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
114 g_object_set (nbp, "tab-expand", TRUE, NULL);
115 g_free (filename);
116 } else if ((filename = g_file_get_path (files[i])) != NULL) {
117 g_print ("No such file: %s.\n", filename);
118 g_free (filename);
119 } else
120 g_print ("No valid file is given\n");
121 }
122 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
123 g_signal_connect (win, "close-request", G_CALLBACK (before_close), nb);
124 gtk_widget_show (win);
125 } else
126 gtk_window_destroy (GTK_WINDOW (win));
127 }
128
129 int
130 main (int argc, char **argv) {
131 GtkApplication *app;
132 int stat;
133
134 app = gtk_application_new ("com.github.ToshioCP.tfe1", G_APPLICATION_HANDLES_OPEN);
135 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
136 g_signal_connect (app, "open", G_CALLBACK (app_open), NULL);
137 stat =g_application_run (G_APPLICATION (app), argc, argv);
138 g_object_unref (app);
139 return stat;
140 }
99 nb = gtk_notebook_new ();
100 gtk_window_set_child (GTK_WINDOW (win), nb);
101
102 for (i = 0; i < n_files; i++) {
103 if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) {
104 scr = gtk_scrolled_window_new ();
105 tv = tfe_text_view_new ();
106 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
107 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
108 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
109
110 tfe_text_view_set_file (TFE_TEXT_VIEW (tv), g_file_dup (files[i]));
111 gtk_text_buffer_set_text (tb, contents, length);
112 g_free (contents);
113 filename = g_file_get_basename (files[i]);
114 lab = gtk_label_new (filename);
115 gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
116 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
117 g_object_set (nbp, "tab-expand", TRUE, NULL);
118 g_free (filename);
119 } else if ((filename = g_file_get_path (files[i])) != NULL) {
120 g_print ("No such file: %s.\n", filename);
121 g_free (filename);
122 } else
123 g_print ("No valid file is given\n");
124 }
125 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
126 g_signal_connect (win, "close-request", G_CALLBACK (before_close), nb);
127 gtk_widget_show (win);
128 } else
129 gtk_window_destroy (GTK_WINDOW (win));
130 }
131
132 int
133 main (int argc, char **argv) {
134 GtkApplication *app;
135 int stat;
136
137 app = gtk_application_new ("com.github.ToshioCP.tfe1", G_APPLICATION_HANDLES_OPEN);
138 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
139 g_signal_connect (app, "open", G_CALLBACK (app_open), NULL);
140 stat =g_application_run (G_APPLICATION (app), argc, argv);
141 g_object_unref (app);
142 return stat;
143 }
~~~
- 107: Sets the pointer to GFile into TfeTextView.
`files[i]` is a pointer to GFile structure.
It will be freed by the system. So you need to copy it.
`g_file_dup` duplicates the given GFile structure.
- 123: Connects "close-request" signal and `before_close` handler.
The fourth argument is called user data and it is given to the signal handler.
- 110: The GFile pointer of the TfeTextView is set with `files[i]`, which is a GFile created with the command line argument.
But the GFile will be destroyed by the system later.
So it needs to be copied before the assignment.
`g_file_dup` duplicates the GFile.
- 126: The "close-request" signal is connected to `before_close` handler.
The fourth argument is called "user data" and it will be the second argument of the signal handler.
So, `nb` is given to `before_close` as the second argument.
Now compile and run it.
There's a sample file in the directory `tfe`.
Type `./a.out taketori.txt`.
Now it's time to compile and run.
~~~
$ cd src/tfe
$ comp tfe1
$ ./a.out taketori.txt`.
~~~
Modify the contents and close the window.
Make sure that the file is modified.

View file

@ -1,15 +1,14 @@
Up: [Readme.md](../Readme.md), Prev: [Section 8](sec8.md), Next: [Section 10](sec10.md)
Up: [README.md](../README.md), Prev: [Section 8](sec8.md), Next: [Section 10](sec10.md)
# The User Interface (UI) file and GtkBuilder
# GtkBuilder and UI file
## New, Open and Save button
In the last section we made the almost simplest editor possible.
It reads files in the `app_open` function at start-up and writes them out when closing the window.
It works but is not very good.
We made very simple editor in the previous section.
It reads files at the start and writes them out at the end of the program.
It works, but is not so good.
It would be better if we had "New", "Open", "Save" and "Close" buttons.
This section describes how to put those buttons into the window.
Signals and handlers will be explained later.
![Screenshot of the file editor](../image/screenshot_tfe2.png)
@ -18,7 +17,7 @@ The function `app_open` in the source code `tfe2.c` is as follows.
~~~C
1 static void
2 app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
2 app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint) {
3 GtkWidget *win;
4 GtkWidget *nb;
5 GtkWidget *lab;
@ -105,7 +104,7 @@ The function `app_open` in the source code `tfe2.c` is as follows.
86 }
~~~
The aim is to build the widgets of the main application window.
The function `app_open` builds the widgets in the main application window.
- 25-27: Creates a GtkApplicationWindow instance and sets the title and default size.
- 29-30: Creates a GtkBox instance `boxv`.
@ -120,13 +119,12 @@ The other label `dmy2` has hexpand property which is set to be TRUE.
This makes the label expands horizontally as long as possible.
- 41-44: Creates four buttons.
- 46-52: Appends these GtkLabel and GtkButton to `boxh`.
- 54-57: Creates a GtkNotebook instance and sets hexpand and vexpand properties TRUE.
- 54-57: Creates a GtkNotebook instance and sets hexpand and vexpand properties to be TRUE.
This makes it expand horizontally and vertically as big as possible.
It is appended to `boxv` as the second child.
The number of lines to build the widgets is 33(=57-25+1).
We also needed many additional variables (`boxv`, `boxh`, `dmy1`, ...),
most of which weren't necessary, except for building the widgets.
The number of widget-build lines is 33(=57-25+1).
We also needed many variables (`boxv`, `boxh`, `dmy1`, ...) and most of them used only for building the widgets.
Are there any good solution to reduce these work?
Gtk provides GtkBuilder.
@ -135,7 +133,7 @@ It reduces this cumbersome work.
## The UI File
First, let's look at the UI file `tfe3.ui` that is used to define the widget structure.
Look at the UI file `tfe3.ui` that defines widget structure.
~~~xml
1 <?xml version="1.0" encoding="UTF-8"?>
@ -199,26 +197,24 @@ First, let's look at the UI file `tfe3.ui` that is used to define the widget str
59 </interface>
~~~
The structure of this file is XML.
Constructs that begin with `<` and end with `>` are called tags.
The is a XML file.
Tags begin with `<` and end with `>`.
There are two types of tags, the start tag and the end tag.
For example, `<interface>` is a start tag and `</interface>` is an end tag.
The UI file begins and ends with interface tags.
Some tags, for example object tags, can have a class and id attributes in their start tag.
- 1: The first line is XML declaration.
It specifies that the version of XML is 1.0 and the encoding is UTF-8.
Even if the line is left out, GtkBuilder builds objects from the ui file.
But ui files must use UTF-8 encoding, or GtkBuilder can't recognize it and a fatal error occurs.
- 3-6: An object with `GtkApplicationWindow` class and `win` id is defined.
- 1: XML declaration.
It specifies that the XML version is 1.0 and the encoding is UTF-8.
- 3-6: An object tag with `GtkApplicationWindow` class and `win` id.
This is the top level window.
And the three properties of the window are defined.
`title` property is "file editor", `default-width` property is 600 and `default-height` property is 400.
- 7: child tag means a child of the widget above.
For example, line 7 tells us that GtkBox object which id is "boxv" is a child widget of `win`.
The `title` property is "file editor", `default-width` property is 600 and `default-height` property is 400.
- 7: child tag means a child widget.
For example, line 7 tells us that GtkBox object with "boxv" id attribute is a child widget of `win`.
Compare this ui file and the lines 25-57 in the source code of `app_open` function.
Those two describe the same structure of widgets.
Compare this ui file and the lines 25-57 in the `tfe2.c` source code.
Both builds the same window with its descendant widgets.
You can check the ui file with `gtk4-builder-tool`.
@ -235,7 +231,7 @@ It is a good idea to check your ui file before compiling.
## GtkBuilder
GtkBuilder builds widgets based on the ui file.
GtkBuilder builds widgets based on a ui file.
~~~C
GtkBuilder *build;
@ -250,7 +246,7 @@ The function `gtk_builder_new_from_file` reads the file given as an argument.
Then, it builds the widgets and creates GtkBuilder object.
The function `gtk_builder_get_object (build, "win")` returns the pointer to the widget `win`, which is the id in the ui file.
All the widgets are connected based on the parent-children relationship described in the ui file.
We only need `win` and `nb` for the program after this, so we don't need to take out any other widgets.
We only need `win` and `nb` for the program below.
This reduces lines in the C source file.
~~~
@ -312,6 +308,8 @@ $ cd tfe; diff tfe2.c tfe3.c
< app = gtk_application_new ("com.github.ToshioCP.tfe2", G_APPLICATION_HANDLES_OPEN);
---
> app = gtk_application_new ("com.github.ToshioCP.tfe3", G_APPLICATION_HANDLES_OPEN);
144a107
>
~~~
`60,103c61,65` means 44 (=103-60+1) lines are changed to 5 (=65-61+1) lines.
@ -322,7 +320,7 @@ Now I'll show you `app_open` function in the C file `tfe3.c`.
~~~C
1 static void
2 app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
2 app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint) {
3 GtkWidget *win;
4 GtkWidget *nb;
5 GtkWidget *lab;
@ -372,12 +370,11 @@ Now I'll show you `app_open` function in the C file `tfe3.c`.
~~~
The whole source code of `tfe3.c` is stored in [src/tfe](../src/tfe) directory.
If you want to see it, click the link above.
### Using ui string
GtkBuilder can build widgets using string.
Use the function `gtk_builder_new_from_string` instead of `gtk_builder_new_from_file`.
GtkBuilder can build widgets with string.
Use `gtk_builder_new_from_string` instead of `gtk_builder_new_from_file`.
~~~C
char *uistring;
@ -395,19 +392,19 @@ uistring =
... ... ...
"</interface>";
build = gtk_builder_new_from_stringfile (uistring);
build = gtk_builder_new_from_string (uistring, -1);
~~~
This method has an advantage and disadvantage.
The advantage is that the ui string is written in the source code.
So ui file is not necessary on runtime.
So, no ui file is needed on runtime.
The disadvantage is that writing C string is a bit bothersome because of the double quotes.
If you want to use this method, you should write a script that transforms ui file into C-string.
- Add backslash before each double quote.
- Add double quotes at the left and right of the string in each line.
### Using Gresource
### Gresource
Using Gresource is similar to using string.
But Gresource is compressed binary data, not text data.
@ -430,9 +427,10 @@ It describes resource files.
- 2: `gresources` tag can include multiple gresources (gresource tags).
However, this xml has only one gresource.
- 3: The gresource has a prefix `/com/github/ToshioCP/tfe3`.
- 4: The gresource has `tfe3.ui`.
And it is pointed by `/com/github/ToshioCP/tfe3/tfe3.ui` because it needs prefix.
If you want to add more files, then insert them between line 4 and 5.
- 4: The name of the gresource is `tfe3.ui`.
The resource will be pointed with `/com/github/ToshioCP/tfe3/tfe3.ui` by GtkBuilder.
The pattern is "prefix" + "name".
If you want to add more files, insert them between line 4 and 5.
Save this xml text to `tfe3.gresource.xml`.
The gresource compiler `glib-compile-resources` shows its usage with the argument `--help`.
@ -469,7 +467,9 @@ Application Options:
Now run the compiler.
$ glib-compile-resources tfe3.gresource.xml --target=resources.c --generate-source
~~~
$ glib-compile-resources tfe3.gresource.xml --target=resources.c --generate-source
~~~
Then a C source file `resources.c` is generated.
Modify `tfe3.c` and save it as `tfe3_r.c`.
@ -483,7 +483,12 @@ build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe3/tfe3.ui");
... ... ...
~~~
Then, compile and run it.
The window appears and it is the same as the screenshot at the beginning of this page.
The function `gtk_builder_new_from_resource` builds widgets from a resource.
Up: [Readme.md](../Readme.md), Prev: [Section 8](sec8.md), Next: [Section 10](sec10.md)
Then, compile and run it.
A window appears and it is the same as the screenshot at the beginning of this page.
Generally, resource is the best way for C language.
If you use other languages like Ruby, string is better than resource.
Up: [README.md](../README.md), Prev: [Section 8](sec8.md), Next: [Section 10](sec10.md)

View file

@ -1,6 +1,6 @@
# TfeTextView API reference
TfeTextView -- Child object of GtkTextView. It holds GFile which the contents of GtkTextBuffer correponds to.
TfeTextView -- Child object of GtkTextView. It is connected to a certain file.
## Functions
- GFile *[tfe_text_view_get_file ()](../src/tfetextview/#tfe_text_view_get_file)
@ -39,8 +39,8 @@ GObject
## Description
TfeTextView holds GFile which the contents of GtkTextBuffer corresponds to.
File manipulation functions are added to this object.
TfeTextView holds GFile corresponds to the contents of GtkTextBuffer.
It has some file manipulation functions.
## Functions
@ -67,7 +67,7 @@ tfe_text_view_open (TfeTextView *tv, GtkWidget *win);
Just shows a GtkFileChooserDialog so that a user can choose a file to read.
This function doesn't do any I/O operations.
They are done by the signal handler connected to the `response` signal emitted by GtkFileChooserDialog.
Therefore the caller can't know the I/O status directly from the function.
Therefore the caller can't know the I/O status directly from `tfe_text_view_open`.
Instead, the status is informed by `open-response` signal.
The caller needs to set a handler to this signal in advance.
@ -83,9 +83,9 @@ void
tfe_text_view_save (TfeTextView *tv);
~~~
Saves the contents of a TfeTextView to a file.
Saves the contents of the TfeTextView to a file.
If `tv` holds a GFile, it is used.
Otherwise, this function shows GtkFileChosserDialog so that a user can choose a file to save.
Otherwise, this function shows GtkFileChooserDialog so that the user can choose a file to save.
Parameters
@ -99,7 +99,7 @@ tfe_text_view_saveas (TfeTextView *tv);
~~~
Saves the content of a TfeTextView to a file.
This function shows GtkFileChosserDialog so that a user can choose a file to save.
This function shows GtkFileChooserDialog so that a user can choose a file to save.
Parameters
@ -174,6 +174,13 @@ Members:
- TFE_OPEN_RESPONSE_CANCEL: Reading file is canceled by the user.
- TFE_OPEN_RESPONSE_ERROR: An error happened during the opening or reading process.
## Properties
### wrap-mode
The property "wrap-mode" belongs to GtkTextView.
TfeTextView inherits it and the value is set to GTK\_WRAP\_WORD\_CHAR as a default.
## Signals
### change-file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -9,43 +9,46 @@ The table of contents is at the end of this abstract.
- Section 26 to 29 describes the list model and the list view (GtkListView, GtkGridView and GtkColumnView).
It also describes GtkExpression.
The latest version of the tutorial is located at [Gtk4-tutorial GitHub repository](https://github.com/ToshioCP/Gtk4-tutorial).
You can read it from there directly without having to download anything.
The latest version of the tutorial is located at [GTK4-tutorial GitHub repository](https://github.com/ToshioCP/GTK4-tutorial).
You can read it directly without download.
There's a GitHub Page of this tutorial at [`https://toshiocp.github.io/GTK4-tutorial/`](https://toshiocp.github.io/GTK4-tutorial/).
It is easier to read than the repository.
#### GTK 4 Documentation
Please refer to [GTK API Reference](https://docs.gtk.org/gtk4/index.html)
Please refer to [GTK 4 API Reference](https://docs.gtk.org/gtk4/index.html)
and [GNOME Developer Documentation Website](https://developer.gnome.org/) for further information.
These websites are newly opened lately (Aug/2021).
The old documentation is located at [GTK Reference Manual](https://developer-old.gnome.org/gtk4/stable/) and [GNOME Developer Center](https://developer-old.gnome.org/).
The new website is in progress at present, so you might need to refer to the old version.
These websites were opened in August of 2021.
The old documents are located at [GTK Reference Manual](https://developer-old.gnome.org/gtk4/stable/) and [GNOME Developer Center](https://developer-old.gnome.org/).
If you want to know about GObject and the type system, please refer to [GObject tutorial](https://github.com/ToshioCP/Gobject-tutorial).
The GObject details are easy to understand and also necessary to know when writing GTK 4 programs.
GObject is the base of GTK 4, so it is important for developers to understand GObject for writing GTK 4 programs.
#### Contribution
This tutorial is under development and unstable.
Even though the codes of the examples have been tested on GTK 4 (version 4.0), bugs may still exist.
This tutorial is still under development and unstable.
Even though the codes of the examples have been tested on GTK 4 (version 4.8.1), bugs may still exist.
If you find any bugs, errors or mistakes in the tutorial and C examples, please let me know.
You can post it to [GitHub issues](https://github.com/ToshioCP/Gtk4-tutorial/issues).
You can also post corrected files as a commit to [pull request](https://github.com/ToshioCP/Gtk4-tutorial/pulls).
When you make corrections, correct the source files, which are under the 'src' directory,
then run `rake` to create to create the output file. The GFM files under the 'gfm' directory are automatically updated.
You can post it to [GitHub issues](https://github.com/ToshioCP/GTK4-tutorial/issues).
You can also post updated files to [pull request](https://github.com/ToshioCP/GTK4-tutorial/pulls).
One thing you need to be careful is to correct the source files, which are under the 'src' directory.
Don't modify the files under `gfm` or `html` directories.
After modifying some source files (under `src` directory), run `rake` to create GFM (GitHub Flavoured Markdown) files or run `rake html` to create HTML files.
If you have a question, feel free to post it as an issue.
If you have a question, feel free to post it to `issue`.
All questions are helpful and will make this tutorial get better.
#### How to get a HTML or PDF version
#### How to get Gtk 4 tutorial with HTML or PDF format
If you want to get a HTML or PDF version, you can make them with `rake`, which is a ruby version of make.
If you want to get HTML or PDF format tutorial, make them with `rake` command, which is a ruby version of make.
Type `rake html` for HTML.
Type `rake pdf` for PDF.
@@@if gfm
There is a documentation \("[How to build GTK 4 Tutorial](Readme_for_developers.src.md)"\) that describes how to make them.
There is a document \("[How to build GTK 4 Tutorial](Readme_for_developers.src.md)"\) for further information.
@@@elif html
There is a documentation \("[How to build GTK 4 Tutorial](Readme_for_developers.src.md)"\) that describes how to make them.
There is a document \("[How to build GTK 4 Tutorial](Readme_for_developers.src.md)"\) for further information.
@@@elif latex
An appendix "How to build GTK 4 Tutorial" describes how to make them.
@@@end

View file

@ -4,36 +4,46 @@ typedef struct _GObjectClass GInitiallyUnownedClass;
struct _GObjectClass
{
GTypeClass g_type_class;
/*< private >*/
GSList *construct_properties;
/*< public >*/
/* seldom overridden */
GObject* (*constructor) (GType type,
guint n_construct_properties,
GObjectConstructParam *construct_properties);
/* overridable methods */
void (*set_property) (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
void (*get_property) (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
void (*dispose) (GObject *object);
void (*finalize) (GObject *object);
void (*set_property) (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
void (*get_property) (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
void (*dispose) (GObject *object);
void (*finalize) (GObject *object);
/* seldom overridden */
void (*dispatch_properties_changed) (GObject *object,
guint n_pspecs,
GParamSpec **pspecs);
guint n_pspecs,
GParamSpec **pspecs);
/* signals */
void (*notify) (GObject *object,
GParamSpec *pspec);
void (*notify) (GObject *object,
GParamSpec *pspec);
/* called when done constructing */
void (*constructed) (GObject *object);
void (*constructed) (GObject *object);
/*< private >*/
gsize flags;
gsize flags;
gsize n_construct_properties;
gpointer pspecs;
gsize n_pspecs;
/* padding */
gpointer pdummy[6];
gpointer pdummy[3];
};

View file

@ -102,8 +102,8 @@ struct _GtkTextViewClass
void (* toggle_overwrite) (GtkTextView *text_view);
GtkTextBuffer * (* create_buffer) (GtkTextView *text_view);
void (* snapshot_layer) (GtkTextView *text_view,
GtkTextViewLayer layer,
GtkSnapshot *snapshot);
GtkTextViewLayer layer,
GtkSnapshot *snapshot);
gboolean (* extend_selection) (GtkTextView *text_view,
GtkTextExtendSelection granularity,
const GtkTextIter *location,

View file

@ -1,7 +1,7 @@
#include <gtk/gtk.h>
static void
app_activate (GApplication *app, gpointer user_data) {
app_activate (GApplication *app) {
GtkWidget *win;
GtkWidget *lab;
@ -20,7 +20,7 @@ main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.lb1", G_APPLICATION_FLAGS_NONE);
app = gtk_application_new ("com.github.ToshioCP.lb1", G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);

View file

@ -1,12 +1,12 @@
#include <gtk/gtk.h>
static void
click_cb (GtkButton *btn, gpointer user_data) {
click_cb (GtkButton *btn) {
g_print ("Clicked.\n");
}
static void
app_activate (GApplication *app, gpointer user_data) {
app_activate (GApplication *app) {
GtkWidget *win;
GtkWidget *btn;
@ -26,7 +26,7 @@ main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.lb2", G_APPLICATION_FLAGS_NONE);
app = gtk_application_new ("com.github.ToshioCP.lb2", G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);

View file

@ -1,13 +1,12 @@
#include <gtk/gtk.h>
static void
click_cb (GtkButton *btn, gpointer user_data) {
GtkWindow *win = GTK_WINDOW (user_data);
click_cb (GtkButton *btn, GtkWindow *win) {
gtk_window_destroy (win);
}
static void
app_activate (GApplication *app, gpointer user_data) {
app_activate (GApplication *app) {
GtkWidget *win;
GtkWidget *btn;
@ -15,7 +14,7 @@ app_activate (GApplication *app, gpointer user_data) {
gtk_window_set_title (GTK_WINDOW (win), "lb3");
gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
btn = gtk_button_new_with_label ("Quit");
btn = gtk_button_new_with_label ("Close");
gtk_window_set_child (GTK_WINDOW (win), btn);
g_signal_connect (btn, "clicked", G_CALLBACK (click_cb), win);
@ -27,7 +26,7 @@ main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.lb3", G_APPLICATION_FLAGS_NONE);
app = gtk_application_new ("com.github.ToshioCP.lb3", G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);

View file

@ -1,8 +1,8 @@
#include <gtk/gtk.h>
static void
click1_cb (GtkButton *btn, gpointer user_data) {
const gchar *s;
click1_cb (GtkButton *btn) {
const char *s;
s = gtk_button_get_label (btn);
if (g_strcmp0 (s, "Hello.") == 0)
@ -12,13 +12,12 @@ click1_cb (GtkButton *btn, gpointer user_data) {
}
static void
click2_cb (GtkButton *btn, gpointer user_data) {
GtkWindow *win = GTK_WINDOW (user_data);
click2_cb (GtkButton *btn, GtkWindow *win) {
gtk_window_destroy (win);
}
static void
app_activate (GApplication *app, gpointer user_data) {
app_activate (GApplication *app) {
GtkWidget *win;
GtkWidget *box;
GtkWidget *btn1;
@ -35,7 +34,7 @@ app_activate (GApplication *app, gpointer user_data) {
btn1 = gtk_button_new_with_label ("Hello.");
g_signal_connect (btn1, "clicked", G_CALLBACK (click1_cb), NULL);
btn2 = gtk_button_new_with_label ("Quit");
btn2 = gtk_button_new_with_label ("Close");
g_signal_connect (btn2, "clicked", G_CALLBACK (click2_cb), win);
gtk_box_append (GTK_BOX (box), btn1);
@ -49,7 +48,7 @@ main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.lb4", G_APPLICATION_FLAGS_NONE);
app = gtk_application_new ("com.github.ToshioCP.lb4", G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);

View file

@ -5,7 +5,7 @@ main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.pr1", G_APPLICATION_FLAGS_NONE);
app = gtk_application_new ("com.github.ToshioCP.pr1", G_APPLICATION_DEFAULT_FLAGS);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;

View file

@ -10,7 +10,7 @@ main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.pr2", G_APPLICATION_FLAGS_NONE);
app = gtk_application_new ("com.github.ToshioCP.pr2", G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);

View file

@ -14,7 +14,7 @@ main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.pr3", G_APPLICATION_FLAGS_NONE);
app = gtk_application_new ("com.github.ToshioCP.pr3", G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);

View file

@ -15,7 +15,7 @@ main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.pr4", G_APPLICATION_FLAGS_NONE);
app = gtk_application_new ("com.github.ToshioCP.pr4", G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);

View file

@ -6,43 +6,40 @@
This tutorial is about GTK 4 libraries.
It is originally used on Linux with C compiler, but now it is used more widely, on Windows and MacOS, with Vala, Python and so on.
However, this tutorial describes only _C programs on Linux_.
However, this tutorial describes only *C programs on Linux*.
If you want to try the examples in the tutorial, you need:
- PC with Linux distribution like Ubuntu, Debian and so on.
- Gcc.
- PC with Linux distribution like Ubuntu or Debian.
- GCC.
- GTK 4.
The stable version of Gtk on Linux distributions is version three at present.
You need to install GTK 4 to your computer.
See [Section 3](sec3.src.md) for the installation of GTK 4.
The stable version of GTK is 4.8.2 at present (13/Dec/2022), but older version (4.0 or higher) may work.
See [Section 3](sec3.src.md) for the installation for GTK 4.
### Ruby and rake for making the document
This repository includes Ruby programs.
They are used to make Markdown files, HTML files, Latex files and a PDF file.
They are used to make GFM (GitHub Flavoured Markdown) files, HTML files, Latex files and a PDF file.
You need:
- Linux distribution like Ubuntu.
- Linux.
- Ruby programming language.
There are two ways to install it.
One is installing the distribution's package.
The other is using rbenv and ruby-build.
If you want to use the latest version of ruby, use rbenv.
- Rake.
It is a gem, which is a library written in Ruby.
You can install it as a package of your distribution or use gem command.
You don't need to install it separately because it is a standard library of Ruby.
## License
Copyright (C) 2020 ToshioCP (Toshio Sekiya)
Copyright (C) 2020-2022 ToshioCP (Toshio Sekiya)
Gtk4-tutorial repository contains the tutorial document and software such as converters, generators and controllers.
All of them make up the 'Gtk4-tutorial' package.
This package is simply called 'Gtk4-tutorial' in the following description.
'Gtk4-tutorial' is free; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License or, at your option, any later version.
GTK4-tutorial repository contains tutorial documents and programs such as converters, generators and controllers.
All of them make up the 'GTK4-tutorial' package.
This package is simply called 'GTK4-tutorial' in the following description.
'GTK4-tutorial' is free; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License or, at your option, any later version.
'Gtk4-tutorial' is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
'GTK4-tutorial' is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the [GNU General Public License](https://www.gnu.org/licenses/gpl-3.0.html) for more details.

View file

@ -1,11 +1,11 @@
# Build system
## What do we need to think about to manage big source files?
## Managing big source files
We've compiled a small editor so far.
But Some bad signs are beginning to appear.
- We've had only one C source file and put everything into it.
- We've had only one C source file and put everything in it.
We need to sort it out.
- There are two compilers, `gcc` and `glib-compile-resources`.
We should control them by one building tool.
@ -14,7 +14,7 @@ These ideas are useful to manage big source files.
## Divide a C source file into two parts.
When you divide C source file into several parts, each file should contain only one thing.
When you divide C source file into several parts, each file should contain one thing.
For example, our source has two things, the definition of TfeTextView and functions related to GtkApplication and GtkApplicationWindow.
It is a good idea to separate them into two files, `tfetextview.c` and `tfe.c`.
@ -64,10 +64,8 @@ The ui file `tfe.ui` is the same as `tfe3.ui` in the previous section.
tfe4/tfe.gresource.xml
@@@
## Make
Dividing a file makes it easy to maintain source files.
But now we are faced with a new problem.
But now we face a new problem.
The building step increases.
- Compiling the ui file `tfe.ui` into `resources.c`.
@ -76,99 +74,18 @@ The building step increases.
- Compiling `resources.c` into `resources.o`.
- Linking all the object files into application `tfe`.
Now build tool is necessary to manage it.
Make is one of the build tools.
It was created in 1976.
It is an old and widely used program.
Build tools manage the steps.
I'll show you three build tools, Meson and Ninja, Make and Rake.
Meson and Ninja is recommended as a C build tool, but others are also fine.
It's your choice.
Make analyzes Makefile and executes compilers.
All instructions are written in Makefile.
## Meson and Ninja
~~~makefile
sample.o: sample.c
gcc -o sample.o sample.c
~~~
Meson and Ninja is one of the most popular building tool to build C language program.
Many developers use Meson and Ninja lately.
For example, GTK 4 uses them.
The sample of Malefile above consists of three elements, `sample.o`, `sample.c` and `gcc -o sample.o sample.c`.
- `sample.o` is called target.
- `sample.c` is prerequisite.
- `gcc -o sample.o sample.c` is recipe.
Recipes follow tab characters, not spaces.
(It is very important. Use tab not space, or make won't work as you expected).
The rule is:
If a prerequisite modified later than a target, then make executes the recipe.
In the example above, if `sample.c` is modified after the generation of `sample.o`, then make executes gcc and compile `sample.c` into `sample.o`.
If the modification time of `sample.c` is older then the generation of `sample.o`, then no compiling is necessary, so make does nothing.
The Makefile for `tfe` is as follows.
@@@include
tfe4/Makefile
@@@
You only need to type `make`.
$ make
gcc -c -o tfe.o `pkg-config --cflags gtk4` tfe.c
gcc -c -o tfetextview.o `pkg-config --cflags gtk4` tfetextview.c
glib-compile-resources tfe.gresource.xml --target=resources.c --generate-source
gcc -c -o resources.o `pkg-config --cflags gtk4` resources.c
gcc -o tfe tfe.o tfetextview.o resources.o `pkg-config --libs gtk4`
I used only very basic rules to write this Makefile.
There are many more convenient methods to make it more compact.
But it will be long to explain it.
So I want to finish explaining make and move on to the next topic.
## Rake
Rake is a similar program to make.
It is written in Ruby code.
If you don't use Ruby, you don't need to read this subsection.
However, Ruby is really sophisticated and recommendable script language.
- Rakefile controls the behavior of `rake`.
- You can write any Ruby code in Rakefile.
Rake has task and file task, which is similar to target, prerequisite and recipe in make.
@@@include
tfe4/Rakefile
@@@
The contents of the `Rakefile` is almost same as the `Makefile` in the previous subsection.
- 3-6: Defines target file, source file and so on.
- 1, 8: Loads clean library. And defines CLEAN file list.
The files included by CLEAN will be removed when `rake clean` is typed on the command line.
- 10: The default target depends on `targetfile`.
The task `default` is the final goal of tasks.
- 12-14: `targetfile` depends on `objfiles`.
The variable `t` is a task object.
- `t.name` is a target name
- `t.prerequisites` is an array of prerequisites.
- `t.source` is the first element of prerequisites.
- `sh` is a method to give the following string to shell as an argument and executes the shell.
- 16-21: There's a loop by each element of the array of `objfiles`. Each object depends on corresponding source file.
- 23-25: Resource file depends on xml file and ui file.
Rakefile might seem to be difficult for beginners.
But, you can use any Ruby syntax in Rakefile, so it is really flexible.
If you practice Ruby and Rakefile, it will be highly productive tools.
## Meson and ninja
Meson is one of the most popular building tool despite the developing version.
And ninja is similar to make but much faster than make.
Several years ago, most of the C developers used autotools and make.
But now the situation has changed.
Many developers are using meson and ninja now.
To use meson, you first need to write `meson.build` file.
You need to make `meson.build` file first.
@@@include
tfe4/meson.build
@ -199,13 +116,100 @@ Then, the executable file `tfe` is generated under the directory `_build`.
$ _build/tfe tfe.c tfetextview.c
Then the window appears.
And two notebook pages are in the window.
One notebook is `tfe.c` and the other is `tfetextview.c`.
A window appears.
It includes a notebook with two pages.
One is `tfe.c` and the other is `tfetextview.c`.
I've shown you three build tools.
I think meson and ninja is the best choice for the present.
For further information, see [The Meson Build system](https://mesonbuild.com/).
We divided a file into some categorized files and used a build tool.
This method is used by many developers.
## Make
Make is a build tool created in 1976.
It was a standard build tool for C compiling, but lately it is replaced by Meson and Ninja.
Make analyzes Makefile and executes compilers.
All instructions are written in Makefile.
For example,
~~~makefile
sample.o: sample.c
gcc -o sample.o sample.c
~~~
Malefile above consists of three elements, `sample.o`, `sample.c` and `gcc -o sample.o sample.c`.
- `sample.o` is a target.
- `sample.c` is a prerequisite.
- `gcc -o sample.o sample.c` is a recipe.
Recipes follow tab characters, not spaces.
(It is very important. Use tab, or make won't work as you expected).
The rule is:
If a prerequisite modified later than a target, then make executes the recipe.
In the example above, if `sample.c` is modified after the generation of `sample.o`, then make executes gcc and compile `sample.c` into `sample.o`.
If the modification time of `sample.c` is older then the generation of `sample.o`, then no compiling is necessary, so make does nothing.
The Makefile for `tfe` is as follows.
@@@include
tfe4/Makefile
@@@
You just type `make` and everything will be done.
~~~
$ make
gcc -c -o tfe.o `pkg-config --cflags gtk4` tfe.c
gcc -c -o tfetextview.o `pkg-config --cflags gtk4` tfetextview.c
glib-compile-resources tfe.gresource.xml --target=resources.c --generate-source
gcc -c -o resources.o `pkg-config --cflags gtk4` resources.c
gcc -o tfe tfe.o tfetextview.o resources.o `pkg-config --libs gtk4`
~~~
I used only very basic rules to write this Makefile.
There are many more convenient methods to make it more compact.
But it will be long to explain it.
So I want to finish with make and move on to the next topic.
You can download "Gnu Make Manual" from [GNU website](https://www.gnu.org/software/make/manual/).
## Rake
Rake is a similar program to make.
It is written in Ruby language.
If you don't use Ruby, you don't need to read this subsection.
However, Ruby is really sophisticated and recommendable script language.
- Rakefile controls the behavior of `rake`.
- You can write any Ruby code in Rakefile.
Rake has task and file task, which is similar to target, prerequisite and recipe in make.
@@@include
tfe4/Rakefile
@@@
The contents of the `Rakefile` is almost same as the `Makefile` in the previous subsection.
- 3-8: Defines target file, source files and so on.
- 1, 10 Requires rake/clean library. And clean files are added to CLEAN.
The files included by CLEAN will be removed when `rake clean` is typed on the command line.
- 12: The default target depends on `targetfile`.
The task `default` is the final goal of tasks.
- 14-16: `targetfile` depends on `objfiles`.
The variable `t` is a task object.
- `t.name` is a target name
- `t.prerequisites` is an array of prerequisites.
- `t.source` is the first element of prerequisites.
- `sh` is a method to give the following string to shell as an argument and executes the shell.
- 18-23: An each iterator of the array `objfiles`. Each object depends on corresponding source file.
- 25-27: Resource file depends on ui file.
Rakefile might seem to be difficult for beginners.
But, you can use any Ruby syntax in the Rakefile, so it is really flexible.
If you practice Ruby and Rakefile, it will be highly productive tools.
For further information, see [Rake tutorial for beginners](https://toshiocp.github.io/Rake-tutorial-for-beginners-en/LearningRake.html).

View file

@ -1,9 +1,8 @@
# Initialization and destruction of instances
# Instance Initialization and destruction
A new version of the text file editor (`tfe`) will be made in this section and the following four sections.
It is `tfe5`.
There are many changes from the prior version.
All the sources are listed in [Section 16](sec16.src.md).
They are located in two directories, [src/tfe5](tfe5) and [src/tfetextview](tfetextview).
## Encapsulation
@ -33,11 +32,10 @@ After that I will explain:
## GObject and its children
GObject and its children are objects, which have both class and instance.
First, think about instance of objects.
Instance is structured memories.
The structure is described as C language structure.
The following is a structure of TfeTextView.
GObject and its children are objects, which have both class and object C structures.
First, think about instances.
An instance is memories which has the object structure.
The following is the structure of TfeTextView.
~~~C
/* This typedef statement is automatically generated by the macro G_DECLARE_FINAL_TYPE */
@ -54,16 +52,10 @@ The members of the structure are:
- The type of `parent` is GtkTextView which is C structure.
It is declared in `gtktextview.h`.
GtkTextView is the parent of TfeTextView.
- `file` is a pointer to GFile. It can be NULL if no file corresponds to the TfeTextView object.
- `file` is a pointer to a GFile. It can be NULL if no file corresponds to the TfeTextView instance.
Notice the program above is the declaration of the structure, not the definition.
So, no memories are allocated at this moment.
They are to be allocated when `tfe_text_view_new` function is invoked.
The memory allocated with `tfe_text_view_new` is an instance of TfeTextView object.
Therefore, There can be multiple TfeTextView instances if `tfe_text_view_new` is called multiple times.
You can find the declaration of the ancestors of TfeTextView in the source files of GTK and GLib.
The following is extracts from the source files (not exactly the same).
You can find the declaration of the ancestors' object structures in the source files of GTK and GLib.
The following is extracted from the source files (not exactly the same).
~~~C
typedef struct _GObject GObject;
@ -91,15 +83,45 @@ struct _GtkTextView
~~~
In each structure, its parent is declared at the top of the members.
So, every ancestors is included in the child instance.
This is very important.
It guarantees a child widget to inherit all the features from ancestors.
So, all the ancestors are included in the child object.
The structure of `TfeTextView` is like the following diagram.
![The structure of the instance TfeTextView](../image/TfeTextView.png){width=14.39cm height=2.16cm}
Derivable classes (ancestor classes) have their own private data area which are not included by the structure above.
For example, GtkWidget has GtkWidgetPrivate (C structure) for its private data.
## Initialization of a TfeTextView instance
Notice declarations are not definitions.
So, no memories are allocated when C structures are declared.
Memories are allocated to them from the heap area when the `tfe_text_view_new` function is called.
At the same time, the ancestors' private area allocated for the TfeTetView.
They are hidden from TfeTextView and it can't access to them directly.
The created memory is called instance.
When a TfeTextView instance is created, it is given three data area.
- The instance (C structure).
- GtkWidgetPrivate structure.
- GtkTextViewPrivate structure.
TfeTextView functions can access to its instance only.
The GtkWidgetPrivate and GtkTextViewPrivate are used by the ancestors' functions.
See the following example.
~~~C
GtkWidget *tv = tfe_text_view_new ();
GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
~~~
The parent's function `gtk_text_view_get_buffer` accesses the GtkTextViewPrivate data (owned by `tv`).
There is a pointer, which points the GtkBuffer, in the private area and the function returns the pointer.
(Actual behavior is a bit more complicated.)
TfeTextView instances inherit the ancestors functions like this.
A TfeTextView instance is created every time the `tfe_text_view_new` function is called.
Therefore, multiple TfeTextView instances can exist.
## Initialization of TfeTextView instances
The function `tfe_text_view_new` creates a new TfeTextView instance.
@ -110,12 +132,13 @@ tfetextview/tfetextview.c tfe_text_view_new
When this function is invoked, a TfeTextView instance is created and initialized.
The initialization process is as follows.
1. Initializes GObject (GInitiallyUnowned) part in TfeTextView instance.
2. Initializes GtkWidget part in TfeTextView instance.
3. Initializes GtkTextView part in TfeTextView instance.
4. Initializes TfeTextView part in TfeTextView instance.
1. When the instance is created, GtkWidgetPrivate and GtkTextViewPrivate structures are also created
2. Initializes GObject (GInitiallyUnowned) part in the TfeTextView instance.
3. Initializes GtkWidget part (the first `priv`) in the TfeTextView instance and GtkWidgetPrivate structure.
4. Initializes GtkTextView part (the second `priv`) in the TfeTextView instance and GtkTextViewPrivate structure.
5. Initializes TfeTextView part (`file`) in the TfeTextView instance.
The step one through three is done by `g_object_init`, `gtk_widget_init` and `gtk_text_view_init`.
The step two through four is done by `g_object_init`, `gtk_widget_init` and `gtk_text_view_init`.
They are called by the system automatically and you don't need to care about them.
Step four is done by the function `tfe_text_view_init` in `tfetextview.c`.
@ -128,28 +151,31 @@ This function just initializes `tv->file` to be `NULL`.
## Functions and Classes
In Gtk, all objects derived from GObject have class and instance (except abstract object).
An instance is memory of C structure, which is described in the previous two subsections.
Instances are memory of C structure, which are described in the previous two subsections.
Each object can have more than one instance.
Those instances have the same structure.
An instance just keeps status of the instance.
Therefore, it is insufficient to define its behavior.
Instances just have data.
Therefore, it doesn't define object's behavior.
We need at least two things.
One is functions and the other is class.
One is functions and the other is class methods.
You've already seen many functions.
For example, `tfe_text_view_new` is a function to create a TfeTextView instance.
These functions are similar to public object methods in object oriented languages such as Java or Ruby.
Functions are public, which means that they are expected to be used by other objects.
For example,
Class comprises mainly pointers to functions.
Those functions are used by the object itself or its descendant objects.
- `TfeTextView *tfe_text_view_new (void);` is a function to create a TfeTextView instance.
- `GtkTextBuffer *gtk_text_view_get_buffer (GtkTextView *textview)` is a function to get a GtkTextBuffer from GtkTextView.
Functions are public, which means that they are expected to be used by other objects.
They are similar to public methods in object oriented languages.
Class (C structure) mainly consists of pointers to functions.
The functions are called class methods and used by the object itself or its descendant objects.
For example, GObject class is declared in `gobject.h` in GLib source files.
@@@include
class_gobject.c
@@@
I'd like to explain some of the members.
There's a pointer to the function `dispose` in line 23.
~~~C
@ -167,13 +193,13 @@ void (*finalize) (GObject *object);
Look at the declaration of `_GObjectClass` so that you would find that most of the members are pointers to functions.
- 11: A function pointed by `constructor` is called when the instance is generated. It completes the initialization of the instance.
- 23: A function pointed by `dispose` is called when the instance destructs itself.
- 13: A function pointed by `constructor` is called when the instance is generated. It completes the initialization of the instance.
- 25: A function pointed by `dispose` is called when the instance destructs itself.
Destruction process is divided into two phases.
The first one is called disposing.
In this phase, the instance releases all the references to other instances.
The second phase is finalizing.
- 24: A function pointed by `finalize` finishes the destruction process.
- 26: A function pointed by `finalize` finishes the destruction process.
- The other pointers point to functions which are called while the instance lives.
These functions are called class methods.
@ -182,25 +208,14 @@ But not open to the objects which are not the descendants.
## TfeTextView class
TfeTextView class is a structure and it includes all its ancestors' class in it.
TfeTextView class is a structure and it includes all its ancestors' classes in it.
Therefore, classes have similar hierarchy to instances.
~~~C
typedef _TfeTextView TfeTextView;
struct _TfeTextView {
GtkTextView parent;
GFile *file;
};
~~~
GObjectClass (GInitiallyUnownedClass) -- GtkWidgetClass -- GtkTextViewClass -- TfeTextViewClass
~~~
TfeTextView structure has GtkTextView type as the first member.
In the same way, GtkTextView has its parent type (GtkWidget) as the first member.
GtkWidget has its parent type (GtkInitiallyUnowned) as the first member.
The structure of GtkInitiallyUnowned is the same as GObject.
Therefore, TFeTextView includes GObject, GtkWidget and GtkTextView in itself.
GObject -- GInitiallyUnowned -- GtkWidget -- GtkTextView -- TfeTextView
The following is extracts from the source files (not exactly the same).
The following is extracted from the source codes (not exactly the same).
@@@include
classes.c
@ -208,11 +223,11 @@ classes.c
- 120-122: This three lines are generated by the macro `G_DECLARE_FINAL_TYPE`.
So, they are not written in either `tfe_text_view.h` or `tfe_text_view.c`.
- 3, 84, 121: Each derived class puts its parent class at the first member of its structure.
- 3, 84, 121: Each class has its parent class at the first member of its structure.
It is the same as instance structures.
- Class members in ancestors are open to the descendant class.
So, they can be changed in `tfe_text_view_class_init` function.
For example, the `dispose` pointer in GObjectClass will be overridden later in `tfe_text_view_class_init`.
For example, the `finalize` pointer in GObjectClass will be overridden later in `tfe_text_view_class_init`.
(Override is an object oriented programming terminology.
Override is rewriting ancestors' class methods in the descendant class.)
- Some class methods are often overridden.
@ -242,17 +257,15 @@ Then C destructs itself and finally the memories allocated to C is freed.
The idea above is based on an assumption that an object referred by nothing has reference count of zero.
When the reference count drops to zero, the object starts its destruction process.
The destruction process is split into two phases: disposing and finalizing.
In the disposing process, the object invokes the function pointed by `dispose` in its class to release all references to other objects.
In the finalizing process, it invokes the function pointed by `finalize` in its class to complete the destruction process.
These functions are also called handlers or methods.
In the disposing process, the object invokes the function pointed by `dispose` in its class to release all references to other instances.
After that, it invokes the function pointed by `finalize` in its class to complete the destruction process.
For example, dispose handler or dispose method.
In the destruction process of TfeTextView, the reference count of widgets related to TfeTextView is automatically decreased.
But GFile pointed by `tv->file` needs to decrease its reference count by one.
You must write the code in the dispose handler `tfe_text_view_dispose`.
In the destruction process, TfeTextView needs to unref the GFile pointed by `tv->file`.
You must write the dispose handler `tfe_text_view_dispose`.
@@@include
tfetextview/tfetextview.c tfe_text_view_dispose
tfetextview/tfetextview.c tfe_text_view_dispose
@@@
- 5,6: If `tv->file` points a GFile, decrease its reference count.
@ -262,7 +275,7 @@ In dispose handlers, we usually use `g_clear_object` rather than `g_object_unref
In the disposing process, the object uses the pointer in its class to call the handler.
Therefore, `tfe_text_view_dispose` needs to be registered in the class when the TfeTextView class is initialized.
The function `tfe_text_view_class_init` is the class initialization function and it is declared in the replacement produced by `G_DEFINE_TYPE` macro.
The function `tfe_text_view_class_init` is the class initialization function and it is declared in the `G_DEFINE_TYPE` macro expansion.
~~~C
static void
@ -290,10 +303,9 @@ Then it invokes its parent's dispose handler in line 8.
G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject);
~~~
`tfe_text_view_parent_class`,which is made by `G_DEFINE_TYPE` macro, is a pointer that points the parent object class.
A variable `tfe_text_view_parent_class`, which is made by `G_DEFINE_TYPE` macro, is a pointer that points the parent object class.
The variable `gobject` is a pointer to TfeTextView instance which is casted as a GObject instance.
Therefore, `G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose` points the handler `dh3` in the diagram above.
And `gobject` is a pointer to TfeTextView instance which is casted as a GObject instance.
`dh3` releases all the references to objects in the GtkTextView part (it is actually the private area pointed by `prev`) in TfeTextView instance.
The statement `G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject)` is the same as `dh3 (gobject)`, which means it releases all the reference to the other instances in the GtkTextViewPrivate in the TfeTextView instance.
After that, `dh3` calls `dh2`, and `dh2` calls `dh1`.
Finally all the references are released.

View file

@ -2,14 +2,14 @@
## Signals
In Gtk programming, each object is encapsulated.
And it is not recommended to use global variables because they tend to make the program complicated.
Each object is encapsulated in Gtk programming.
And it is not recommended to use global variables because they are prone to make the program complicated.
So, we need something to communicate between objects.
There are two ways to do so.
- Functions.
For example, `tb = gtk_text_view_get_buffer (tv)`.
The caller requests `tv` to give `tb`, which is a GtkTextBuffer instance connected to `tv` to the caller.
The caller requests `tv` to give `tb`, which is a GtkTextBuffer instance connected to `tv`.
- Signals.
For example, `activate` signal on GApplication object.
When the application is activated, the signal is emitted.
@ -27,7 +27,7 @@ The registration is done usually when the object class is initialized.
2. Signals are connected to handlers by `g_connect_signal` or its family functions.
The connection is usually done out of the object.
3. When Signals are emitted, the connected handlers are invoked.
Signal is emitted on the instance of the object.
Signals are emitted on the instance of the object.
## Signal registration
@ -39,8 +39,7 @@ This signal is emitted when `tv->file` is changed.
`tfe_text_view_open` function is not able to return the status because it uses GtkFileChooserDialog.
This signal is emitted instead of the return value of the function.
A static variable or array is used to store the signal ID.
A static array is used to register two or more signals.
A static variable or array is used to store signal ID.
~~~C
enum {
@ -60,10 +59,10 @@ tfetextview/tfetextview.c tfe_text_view_class_init
- 6-15: Registers "change-file" signal.
`g_signal_new` function is used.
The signal "change-file" has no default handler (object method handler).
You usually don't need to set a default handler.
If you need it, use `g_signal_new_class_handler` function.
See [GObject API Reference, g\_signal\_new\_class\_handler](https://docs.gtk.org/gobject/func.signal_new_class_handler.html) for further information.
The signal "change-file" has no default handler (object method handler) so the offset (the line 9) is set to zero.
You usually don't need a default handler.
If you need it, use `g_signal_new_class_handler` function instead of `g_signal_new`.
See [GObject API Reference](https://docs.gtk.org/gobject/func.signal_new_class_handler.html) for further information.
- The return value of `g_signal_new` is the signal id.
The type of signal id is guint, which is the same as unsigned int.
It is used in the function `g_signal_emit`.

View file

@ -1,30 +1,36 @@
# Functions in TfeTextView
In this section I will explain functions in TfeTextView object.
TfeTextView functions are described in this section.
## tfe.h and tfetextview.h
## tfetextview.h
`tfe.h` is a top header file and it includes `gtk.h` and all the header files.
C source files `tfeapplication.c` and `tfenotebook.c` include `tfe.h` at the beginning.
The header file `tfetextview.h` provides:
@@@include
tfe5/tfe.h
@@@
- The type of TfeTextView, which is `TFE_TYPE_TEXT_VIEW`.
- The expansion of `G_DECLARE_FINAL_TYPE` includes some useful macros.
- Constants for the `open-response` signal is defined..
- Public functions of `tfetextview.c` are declared.
`../tfetextview/tfetextview.h` is a header file which describes the public functions in `tfetextview.c`.
Therefore, Any programs use TfeTextView needs to include `tfetextview.h`.
@@@include
tfetextview//tfetextview.h
@@@
- 1,2,35: Thanks to these three lines, the following lines are included only once.
- 1,2,35: Thanks to these three lines, the following lines are included only once.
You can use `#pragma once` instead of them.
It is non-standard but widely used.
- 4: Includes gtk4 header files.
The header file `gtk4` also has the same mechanism to avoid including it multiple times.
- 6-7: These two lines define TfeTextView type, its class structure and some useful macros.
- `TfeTextView` and `TfeTextViewClass` are declared as typedef of C structures.
- You need to define a structure `_TfeTextView` later.
- The class structure `_TfeTextViewClass` is defined here. You don't need to define it by yourself.
- Convenience functions `TFE_TEXT_VIEW ()` for casting and `TFE_IS_TEXT_VIEW` for type check are defined.
- 9-15: A definition of the value of the parameter of "open-response" signal.
- 17-33: Declarations of public functions on TfeTextView.
## Functions to create TfeTextView instances
## Instance creation Functions
A TfeTextView instance is created with `tfe_text_view_new` or `tfe_text_view_new_with_file`.
@ -49,10 +55,13 @@ tfetextview/tfetextview.c tfe_text_view_new_with_file tfe_text_view_new
- 23-25: `tfe_text_view_new` function.
Just returns the value from the function `g_object_new` but casts it to the pointer to GtkWidget.
Initialization is done in `tfe_text_view_init` which is called in the process of `g_object_new` function.
The function `g_object_new` creates any instances of its descendant class.
The arguments are the type of the class, property list and NULL.
Null is the end mark of the property list.
TfeTextView "wrap-mode" property has GTK\_WRAP\_WORD\_CHAR as the default value.
- 1-21: `tfe_text_view_new_with_file` function.
- 3: `g_return_val_if_fail` is described in [GLib API Reference, g\_return\_val\_if\_fail](https://docs.gtk.org/glib/func.return_val_if_fail.html).
And also [GLib API Reference, Message Logging](https://docs.gtk.org/glib/logging.html).
- 3: `g_return_val_if_fail` is described in [GLib API Reference -- g\_return\_val\_if\_fail](https://docs.gtk.org/glib/func.return_val_if_fail.html).
And also [GLib API Reference -- Message Logging](https://docs.gtk.org/glib/logging.html).
It tests whether the argument `file` is a pointer to GFile.
If it's true, then the program goes on to the next line.
If it's false, then it returns NULL (the second argument) immediately.
@ -77,7 +86,7 @@ If the modification flag is FALSE, it doesn't need to save the contents.
- 20: Returns `tv`, which is a pointer to the newly created TfeTextView instance.
If an error happens, NULL is returned.
## Save and saveas functions
## Save related functions
Save and saveas functions write the contents in the GtkTextBuffer to a file.
@ -98,65 +107,118 @@ Then, the function changes `tv->file` and save the contents to the specified fil
If an error occurs, it is shown to the user through the message dialog.
The error is managed only in the TfeTextView and no information is notified to the caller.
### save\_file function
@@@include
tfetextview/tfetextview.c save_file saveas_dialog_response tfe_text_view_save tfe_text_view_saveas
tfetextview/tfetextview.c save_file
@@@
- 1-26: `save_file` function.
This function is called from `saveas_dialog_response` and `tfe_text_view_save`.
- The function `save_file` is called from `saveas_dialog_response` and `tfe_text_view_save`.
This function saves the contents of the buffer to the file given as an argument.
If error happens, it displays an error message.
So, a caller of this function don't need to take care of errors.
The class of this function is `static`.
Therefore, only functions in this file (`tfeTetview.c`) call this function.
Such static functions usally don't have `g_return_val_if_fail` function.
Therefore, only functions in this file (`tfetextview.c`) call this function.
Such static functions usually don't have `g_return_val_if_fail` function.
- 10-11: Gets the text contents from the buffer.
- 12-14: Saves the contents to the file.
If no error happens, set the modified flag to be FALSE.
- 12: The function `g_file_replace_contents` writes the contents to the file and returns the status (true = success/ false = fail).
It has many parameters, but some of them are almost always given the same values.
- GFile* file: GFile to which the contents are saved.
- const char* contents: contents to be saved. The string is owned by the caller.
- gsize length: the length of the contents
- const char* etag: entity tag. It is usually NULL.
- gboolean make_backup: true to make a backup if the file exists. false not to make it. the file will be overwritten.
- GFileCreateFlags flags: usually `G_FILE_CREATE_NONE` is fine.
- char** new_etag: new entity tag. It is usually NULL.
- GCancellable* cancellable: If a cancellable instance is set, the other thread can cancel this operation. it is usually NULL.
- GError** error: If error happens, GError will be set.
- 13,14: If no error happens, set the modified flag to be FALSE.
This means that the buffer is not modified since it has been saved.
And set the return status `stat` to be TRUE.
- 15-23: If it fails to save the contents, displays an error message.
- 16-18: Creates a message dialog with the error message.
- 19: Connects the "response" signal to `gtk_window_destroy`, so that the dialog disappears when a user clicked on the button.
- 20-21: Shows the window, frees `err` and set `stat` to be FLASE.
- 24: Frees `contents`.
- 25: Returns to the caller.
- 28-47: `saveas_dialog_response` function.
This is a signal handler for the "response" signal on GtkFileChooserDialog instance created by `tfe_text_view_saveas` function.
This handler analyzes the response and determines whether to save the contents.
- 34-45: If the response is `GTK_RESPONSE_ACCEPT`, the user has clicked on the `Save` button. So, it tries to save.
- 35: Gets the GFile `file` from GtkFileChooserDialog.
- 36-37: If it doesn't point GFile, it outputs an error message to the log.
- 38: Otherwise, it calls `save_file` to save the contents to the file.
- 39-42: If `save_file` has successfully saved the contents, `tv->file` is updated.
If the old GFile pointed by `tv->file` exists, it is freed in advance.
Emits "change-file" signal.
- 44: Unrefs `file`.
- 46: destroys the file chooser dialog.
- 49-64: `tfe_text_view_save` function.
- 51: `tfe_text_view_save` is public, i.e. it is open to the other files.
- 15-22: If it fails to save the contents, an error message will be displayed.
- 17-18: Creates a message dialog. The parameters are:
- GtkWindow* parent: transient parent window.
This allows window managers to keep the dialog on top of the parent window, or center the dialog over the parent window.
It is possible to give no parent window to the dialog.
However, it is encouraged to give parents to dialogs.
- GtkDialogFlags flags: GTK\_DIALOG\_MODAL for modal dialog. A modal dialog is usually fine.
- GtkMessageType type: GTK\_MESSAGE\_ERROR for error message. Other options are GTK\_MESSAGE\_INFO, GTK\_MESSAGE\_WARNING and so on.
- GtkButtonsType buttons: GTK\_BUTTON\_OK is used most often. Other option is GTK\_BUTTON\_YES\_NO, GTK\_BUTTON\_CANCEL and so on.
- const gchar* message_format: gchar is the same as char. This format is the same as printf. Arguments for the message format follow.
- 19: Connects the "response" signal to `gtk_window_destroy`, so that the dialog disappears when the user clicks on the button.
- 20: Shows the message dialog.
- 21: Frees `err` with `g_error_free` function.
- 23: Frees `contents`.
- 24: Returns to the caller.
### saveas\_dialog\_response function
@@@include
tfetextview/tfetextview.c saveas_dialog_response
@@@
- The function `saveas_dialog_response` is a signal handler for the "response" signal on GtkFileChooserDialog.
This handler analyzes the response and determines whether to save the contents or not.
- 7-25: If the response is `GTK_RESPONSE_ACCEPT`, the user has clicked on the `Save` button and the contents will be saved.
- 8: Gets the GFile `file` from the GtkFileChooserDialog.
- 9-10: If it doesn't point GFile, a warning message will be output to the log. This is not expected.
- 11: Otherwise, it calls `save_file` to save the contents to the file.
- 12-22: If `save_file` has successfully saved the contents, the following will be done.
- If `tv->file` is GFile and `file` is a different file, unref `tv->file`.
- If `tv->file` is GFile and `file` points the same file as `tv->file`, nothing needs to do.
Otherwise, `tv->file` is set to `file` and "change-file" signal is emitted.
- 22, 24: Unref `file`.
- 26: destroys the file chooser dialog.
### tfe\_text\_view\_save function
@@@include
tfetextview/tfetextview.c tfe_text_view_save
@@@
- The function `tfe_text_view_save` writes the contents to the `tv->file` file.
It calls `tfe_text_view_saveas` or `save_file`.
- 1-3: The function is public, i.e. it is open to the other objects.
So, it doesn't have `static` class.
Public functions should check the parameter type with `g_return_if_fail` function.
If `tv` is not a pointer to a TfeTextView instance, then it logs an error message and immediately returns.
This function is similar to `g_return_val_if_fail`, but no value is returned because `tfe_text_view_save` doesn't return a value.
- 53-54: Gets GtkTextBuffer instance and GtkWidget instance and assignes them to `tb` and`win` respectively.
- 56-57: If the buffer hasn't modified, then it doesn't need to save it.
So the function returns.
- 58-59: If `tv->file` is NULL, no file has given yet.
It calls `tfe_text_view_saveas` which prompts a user to select a file or specify a new file to save.
- 60-61: If `tv->file` doesn't point GFile, somethig bad has happened.
Logs an error message.
- 62-63: Calls `save_file` to save the contents to the file.
- 66-79: `tfe_text_view_saveas` function.
It shows GtkFileChooserDialog and prompts the user to choose a file.
- 73-76: Creates GtkFileChooserDialog.
The title is "Save file".
Transient parent of the dialog is `win`, which is the top-level window.
The action is save mode.
The buttons are Cancel and Save.
- 77: connects the "response" signal of the dialog and `saveas_dialog_response` handler.
- 78: Shows the dialog.
This function is similar to `g_return_val_if_fail`, but no value is returned because `tfe_text_view_save` doesn't return a value (void).
- 5-6: GtkTextBuffer `tb` and GtkWidget (GtkWindow) `win` are set.
The function `gtk_widget_get_ancestor (widget, type)` returns the first ancestor of the widget with type.
The type is a GType.
For example, the type of GtkWindow is `GTK_TYPE_WINDOW` and the one of TfeTextView is `TFE_TYPE_TEXT_VIEW`.
Be careful. The parent-child relationship here is the one for widgets, not classes.
The top level window may be a GtkApplicationWindow, but it depends on the application.
Because TfeTextView is a library, it can't determine the top level window type (GtkWindow or GtkApplicationWindow).
GtkWindow is a parent class of GtkApplication window so it is more general.
Therefore, TfeTextView takes GtkWindow as a top level window so that it can be used by any application.
- 8-9: If the buffer hasn't modified, it doesn't need to be saved.
- 10-11: If `tv->file` is NULL, which means no file has given yet, it calls `tfe_text_view_saveas` to prompt a user to select a file and save the contents.
- 12-13: If `tv->file` doesn't point GFile, an error message is logged out. It is not expected.
- 14-15: Otherwise, it calls `save_file` to save the contents to the file `tv->file`.
![Saveas process](../image/saveas.png){width=10.7cm height=5.16cm}
### tfe\_text\_view\_saveas function
@@@include
tfetextview/tfetextview.c tfe_text_view_saveas
@@@
- The function `tfe_text_view_saveas` shows GtkFileChooserDialog and prompts the user to choose a file and save the contents.
- 1-3: Check the type of `tv` because the caller may be other object. This function is public.
- 6: GtkWidget `win` is set to the top level window.
- 8-11: Creates GtkFileChooserDialog. It has at least four parameters.
- const char* title: title of the dialog shown at the bar.
- GtkWindow* parent: transient parent window.
- GtkFileChooserAction action: action is one of `GTK_FILE_CHOOSER_ACTION_OPEN`, `GTK_FILE_CHOOSER_ACTION_SAVE`
and `GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER`.
When you want to save a file, `GTK_FILE_CHOOSER_ACTION_SAVE` is the action.
- const char* first_button_text: the label text of the first button.
- type: response ID for the sirst button. response ID is one of the GtkReponseType.
See [GtkResponseType](https://docs.gtk.org/gtk4/enum.ResponseType.html).
- followed by pairs of button text and response ID...
- NULL is put at the end of the list.
- The GtkFileChooserDialog will have the title "Save file", transient parent `win`, save mode action, cancel and save button.
- 12: connects the "response" signal and `saveas_dialog_response` handler.
- 13: Shows the dialog.
When you use GtkFileChooserDialog, you need to divide the program into two parts.
One is a function which creates GtkFileChooserDialog and the other is a signal handler.
@ -164,9 +226,11 @@ The function just creates and shows GtkFileChooserDialog.
The rest is done by the handler.
It gets Gfile from GtkFileChooserDialog and saves the buffer to the file by calling `save_file`.
## Open function
![Saveas process](../image/saveas.png){width=10.7cm height=5.16cm}
Open function shows GtkFileChooserDialog to users and prompts them to choose a file.
## Open related functions
Open function shows GtkFileChooserDialog to a user and prompts them to choose a file.
Then it reads the file and puts the text into GtkTextBuffer.
~~~C
@ -175,10 +239,8 @@ void tfe_text_view_open (TfeTextView *tv, GtkWindow *win);
The parameter `win` is the top-level window.
It will be a transient parent window of GtkFileChooserDialog when the dialog is created.
This allows window managers to keep the dialog on top of the parent window, or center the dialog over the parent window.
It is possible to give no parent window to the dialog.
However, it is encouraged to give a parent window to dialog.
This function might be called just after `tv` has been created.
This function may be called just after `tv` has been created.
In that case, `tv` has not been incorporated into the widget hierarchy.
Therefore it is impossible to get the top-level window from `tv`.
That's why the function needs `win` parameter.
@ -186,38 +248,58 @@ That's why the function needs `win` parameter.
This function is usually called when the buffer of `tv` is empty.
However, even if the buffer is not empty, `tfe_text_view_open` doesn't treat it as an error.
If you want to revert the buffer, calling this function is appropriate.
Otherwise probably bad things will happen.
Open and read process is divided into two phases.
One is showing GtkFileChooserDialog and the other is its response handler.
The response handler gets the filename, reads the contents of the file and puts it into the GtkTextBuffer.
### open\_dialog\_response function
@@@include
tfetextview/tfetextview.c open_dialog_response tfe_text_view_open
tfetextview/tfetextview.c open_dialog_response
@@@
- 37-50: `tfe_text_view_open` function.
- 44-47: Creates GtkFileChooserDialog.
The title is "Open file".
Transient parent window is the top-level window of the application, which is given by the caller.
The action is open mode.
The buttons are Cancel and Open.
- 48: connects the "response" signal of the dialog and `open_dialog_response` signal handler.
- 49: Shows the dialog.
- 1-35: `open_dialog_response` signal handler.
- 10-11: If the response from GtkFileChooserDialog is not `GTK_RESPONSE_ACCEPT`, the user has clicked on the "Cancel" button or close button on the header bar.
- 2: The handler `open_dialog_response` has three parameters.
- GtkWidget *dialog: FileChooserDialog on which the "response" signal is emitted.
- gint response: response ID
- TfeTextView *tv: textview to put the contents to
- 10-11: If the response is not `GTK_RESPONSE_ACCEPT`, the user has clicked on the "Cancel" button or close button on the header bar.
Then, "open-response" signal is emitted.
The parameter of the signal is `TFE_OPEN_RESPONSE_CANCEL`.
- 12-14: Gets the pointer to the Gfile by `gtk_file_chooser_get_file`.
- 12-14: Gets the pointer to the GFile by `gtk_file_chooser_get_file`.
If it doesn't point GFile, maybe an error has occurred.
It is not expected, though.
Then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`.
- 15-23: If an error occurs at file reading, then it decreases the reference count of the Gfile, shows a message dialog to report the error to the user and emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`.
- 24-33: If the file has successfully been read, then the text is inserted to GtkTextBuffer, frees the temporary buffer pointed by `contents` and sets `tv->file` to point the file (no duplication is not necessary).
Then, it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_SUCCESS` and emits "change-file" signal.
- 34: destroys GtkFileCooserDialog.
- 15-22: If an error occurs when file reading, then it decreases the reference count of the GFile, shows a message dialog to report the error to the user and emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`.
- 23-38: If the file has been successfully read, the following is carried out. (It is not simple.)
- the text is inserted to GtkTextBuffer
- the temporary buffer `contents` is freed
- modify-bit is set to FALSE
- open-response signal is emitted with `TFE_OPEN_REPONSE_SUCCESS`
- If the file are changed, change-file signal is emitted
- If `tv->file` isn't NULL, `g_object_unref(tv->file)` is called
- `tv->file` is assigned with `file`
- 39: destroys GtkFileChooserDialog.
Now let's think about the whole process between the caller and TfeTextView.
It is shown in the following diagram and you would think that it is really complicated.
### tfe\_text\_view\_open function
@@@include
tfetextview/tfetextview.c tfe_text_view_open
@@@
- 3-4: Check the type of the arguments `tv` and `win`.
Public functions always need to check the arguments.
- 10-11: Creates GtkFileChooserDialog.
- The title is "Open file".
- Transient parent window is the top-level window `win`.
- The action is open mode.
- The buttons are Cancel and Open.
- 12: connects the "response" signal and the handler.
- 13: Shows the dialog.
The whole process between the caller and TfeTextView is shown in the following diagram.
It is really complicated.
Because signal is the only way for GtkFileChooserDialog to communicate with others.
In GTK 3, `gtk_dialog_run` function is available.
It simplifies the process.
However, in GTK 4, `gtk_dialog_run` is unavailable any more.
![Caller and TfeTextView](../image/open.png){width=12.405cm height=9.225cm}
@ -228,9 +310,10 @@ However, in GTK 4, `gtk_dialog_run` is unavailable any more.
5. The handler reads the file and inserts the text into GtkTextBuffer and emits a signal to inform the status as a response code.
6. The handler out of the TfeTextView receives the signal.
## Getting Gfile
## Getting GFile in TfeTextView
`gtk_text_view_get_file` is a simple function shown as follows.
You can get the GFile in a TfeTextView instance with `tfe_text_view_get_file`.
It is very simple.
@@@include
tfetextview/tfetextview.c tfe_text_view_get_file
@ -240,7 +323,7 @@ The important thing is to duplicate `tv->file`.
Otherwise, if the caller frees the GFile object, `tv->file` is no more guaranteed to point the GFile.
Another reason to use `g_file_dup` is that GFile isn't thread-safe.
If you use GFile in the different thread, the duplication is necessary.
See [Gio API Reference, g\_file\_dup](https://docs.gtk.org/gio/method.File.dup.html).
See [Gio API Reference -- g\_file\_dup](https://docs.gtk.org/gio/method.File.dup.html).
## The API document and source file of tfetextview.c
@ -248,13 +331,12 @@ See [Gio API Reference, g\_file\_dup](https://docs.gtk.org/gio/method.File.dup.h
Refer [API document of TfeTextView](../gfm/tfetextview_doc.md).
It is under the directory `src/tfetextview`.
@@@elif html
Refer [API document of TfeTextView](../html/tfetextview_doc.html).
Its original markdown file is under the directory `src/tfetextview`.
Refer [API document of TfeTextView](https://toshiocp.github.io/Gtk4-tutorial/tfetextview_doc.html).
The markdown file is under the directory `src/tfetextview`.
@@@elif latex
Refer API document of TfeTextView in the appendix.
Its original markdown file is under the directory `src/tfetextview`.
The markdown file is under the directory `src/tfetextview`.
@@@end
All the source files are listed in [Section 16](sec16.src.md).
You can find them under [src/tfe5](tfe5) and [src/tfetextview](tfetextview) directories.
You can find all the TfeTextView source codes under [src/tfetextview](tfetextview) directories.

View file

@ -16,7 +16,7 @@ This header file describes the public functions in `tfenotebook.c`.
If the name is `untitled` or `untitled` followed by digits, FileChooserDialog appears and a user can choose or specify a filename.
- 4-5: `notebook_page_close` closes the current page.
- 7-8: `notebook_page_open` shows a file chooser dialog and a user can choose a file. The file is inserted to a new page.
- 10-11: `notebook_page_new_with_file` creates a new page and the file given as an argument is read and inserted into the page.
- 10-11: `notebook_page_new_with_file` creates a new page and a file given as an argument is read and inserted into the page.
- 13-14: `notebook_page_new` creates a new empty page.
You probably find that the functions except `notebook_page_close` are higher level functions of
@ -40,29 +40,32 @@ Now let's look at the program of each function.
tfe5/tfenotebook.c get_untitled notebook_page_build notebook_page_new
@@@
- 27-38: `notebook_page_new` function.
- 29: `g_return_if_fail` is used to check the argument.
- 34: Creates TfeTextView object.
If it fails, it returns to the caller.
- 36: Creates filename, which is "Untitled", "Untitled1", ... .
- 26-38: `notebook_page_new` function.
- 28: `g_return_if_fail` is used to check the argument.
- 33-34: Creates TfeTextView object.
If it fails, no notebook page is created and the function returns to the caller.
- 35: Creates filename, which is "Untitled", "Untitled1", ... .
- 1-8: `get_untitled` function.
- 3: Static variable `c` is initialized at the first call of this function. After that `c` keeps its value unless it is changed explicitly.
- 4-7: Increases `c` by one and if it is zero then it returns "Untitled". If it is a positive integer then it returns "Untitled\<the integer\>", for example, "Untitled1", "Untitled2", and so on.
- 4-7: Increases `c` by one and if it is zero, it returns "Untitled". If it is a positive integer, it returns "Untitled\<the integer\>", for example, "Untitled1", "Untitled2", and so on.
The function `g_strdup_printf` creates a string and it should be freed by `g_free` when it becomes useless.
The caller of `get_untitled` is in charge of freeing the string.
- 37: calls `notebook_page_build` to build the contents of the page.
- 10- 25: `notebook_page_build` function.
- 36: calls `notebook_page_build` to build the contents of the page.
- 37: frees `filename`.
- 10- 24: `notebook_page_build` function.
A parameter with `const` qualifier doesn't change in the function.
It means that the argument `filename` is owned by the caller.
The caller needs to free it when it becomes useless.
- 12: Creates GtkScrolledWindow.
- 17: Sets the wrap mode of `tv` to GTK_WRAP_WORD_CHAR so that lines are broken between words or graphemes.
- 18: Inserts `tv` to GtkscrolledWindow as a child.
- 19-20: Creates GtkLabel, then appends `scr` and `lab` to the GtkNotebook instance `nb`.
- 21-22: Sets "tab-expand" property to TRUE.
- 17: Inserts `tv` to GtkscrolledWindow as a child.
- 18-19: Creates GtkLabel, then appends `scr` and `lab` to the GtkNotebook instance `nb`.
- 20-21: Sets "tab-expand" property to TRUE.
The function `g_object_set` sets properties on an object.
The object is any object derived from GObject.
In many cases, an object has its own function to set its properties, but sometimes not.
In that case, use `g_object_set` to set the property.
- 23: Sets the current page of `nb` to the newly created page.
- 24: Connects "change-file" signal and `file_changed_cb` handler.
- 22: Sets the current page to the newly created page.
- 23: Connects "change-file" signal and `file_changed_cb` handler.
## notebook\_page\_new\_with\_file
@ -73,7 +76,7 @@ tfe5/tfenotebook.c notebook_page_new_with_file
- 9-10: Calls `tfe_text_view_new_with_file`.
If the function returns NULL, an error has happend.
Then, it does nothing and returns.
- 11-12: Gets the filename and builds the contents of the page.
- 11-13: Gets the filename, builds the contents of the page and frees `filename`.
## notebook\_page\_open
@ -81,35 +84,70 @@ Then, it does nothing and returns.
tfe5/tfenotebook.c open_response notebook_page_open
@@@
- 16-26: `notebook_page_open` function.
- 22-23: Creates TfeTextView object.
- 18-28: `notebook_page_open` function.
- 24-25: Creates TfeTextView object.
If NULL is returned, an error has happened.
Then, it returns to the caller.
- 24: Connects the signal "open-response" and the handler `open_response`.
- 25: Calls `tfe_text_view_open`.
- 26: Connects the signal "open-response" and the handler `open_response`.
- 27: Calls `tfe_text_view_open`.
The "open-response" signal will be emitted later to inform the result of opening and reading a file.
- 1-14: `open_response` handler.
- 6-8: If the response code is NOT `TFE_OPEN_RESPONSE_SUCCESS` or `tfe_text_view_get_file` doesn't return the pointer to a GFile,
it has failed to open and read a new file.
- 1-16: `open_response` handler.
- 6-8: If the response code is not `TFE_OPEN_RESPONSE_SUCCESS`, it has failed to open and read a new file.
Then, what `notebook_page_open` did in advance need to be canceled.
The instance `tv` hasn't been a child widget of GtkScrolledWindow yet.
Such instance has floating reference.
Floating reference will be explained later in this subsection.
Floating reference will be explained later.
You need to call `g_object_ref_sink` first.
Then the floating reference is converted into an ordinary reference.
Now you call `g_object_unref` to decrease the reference count by one.
- 9-13: Otherwise, everything is okay.
Gets the filename, builds the contents of the page.
- 9-15: Otherwise, everything is okay.
Gets the filename, builds the contents of the page and frees `filename`.
## Floating reference
All the widgets are derived from GInitiallyUnowned.
When an instance of GInitiallyUnowned or its descendant is created, the instance has a floating reference.
The function `g_object_ref_sink` converts the floating reference into an ordinary reference.
If the instance doesn't have a floating reference, `g_object_ref_sink` simply increases the reference count by one.
GObject and GInitiallyUnowned are almost the same.
The difference is like this.
When an instance of GInitiallyUnowned is created, the instance has a floating reference and its reference count is zero.
On the other hand, when an instance of GObject (not GInitiallyUnowned) is created, no floating reference is given.
And the instance has a normal reference count instead of floating reference.
Their descendants inherits them, so every widget has a floating reference at first.
Non-widget class, for example, GtkTextBuffer is a direct sub class of GObject and it doesn't have floating reference.
Its reference count is one when it is created.
The function `g_object_ref_sink` converts the floating reference into an ordinary reference.
If the instance doesn't have a floating reference, `g_object_ref_sink` simply increases the reference count by one.
It is used when an widget is added to another widget as a child.
~~~
GtkTextView *tv = gtk_text_view_new (); // floating reference
GtkScrolledWindow *scr = gtk_scrolled_window_new ();
gtk_scrolled_window_set_child (scr, tv); // tv's reference count is one
~~~
When `tv` is added to `scr` as a child, `g_object_ref_sink` is used.
~~~
g_object_ref_sink (tv);
~~~
So, the floating reference is converted into an ordinary reference.
That is to say, floating reference is deleted, and reference count turns to one.
Thanks to this, the caller doesn't need to decrease tv's reference count.
If an Object\_A is not a descendant of GInitiallyUnowned, the program is like this:
~~~
Object_A *obj_a = object_a_new (); // reference count is one
GtkScrolledWindow *scr = gtk_scrolled_window_new ();
gtk_scrolled_window_set_child (scr, obj_a); // obj_a's reference count is two
// obj_a is referred by the caller (this program) and scrolled window
g_object_unref (obj_a); // obj_a's reference count is one because the caller no longer refers obj_a.
~~~
This example tells us that the caller needs to unref `obj_a`.
If you use `g_object_unref` to an instance that has a floating reference, you need to convert the floating reference to a normal reference in advance.
See [GObject Reference Manual](https://developer-old.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#gobject-The-Base-Object-Type.description) for further information.
See [GObject API reference](https://docs.gtk.org/gobject/floating-refs.html) for further information.
## notebook\_page\_close
@ -122,6 +160,7 @@ If the page is the only page the notebook has, then the function destroys the to
- 8-10: If the page is the only page the notebook has, it calls `gtk_window_destroy` to destroys the top-level window.
- 11-13: Otherwise, removes the current page.
The child widget (TfeTextView) is also destroyed.
## notebook\_page\_save
@ -142,7 +181,7 @@ This function gets the TfeTextView object belongs to the current page.
The function `file_changed_cb` is a handler connected to "change-file" signal.
If a file in a TfeTextView instance is changed, it emits this signal.
This handler changes the label of GtkNotebookPage.
This handler changes the label of the GtkNotebookPage.
@@@include
tfe5/tfenotebook.c file_changed_cb

View file

@ -121,7 +121,7 @@ textview {color: yellow; ...}
~~~
Class, ID and some other things can be applied to the selector like Web CSS.
Refer to [GTK 4 API Reference, CSS in Gtk](https://docs.gtk.org/gtk4/css-overview.html) for further information.
Refer to [GTK 4 API Reference -- CSS in Gtk](https://docs.gtk.org/gtk4/css-overview.html) for further information.
In line 30, the CSS is a string.
@ -256,9 +256,7 @@ In this file, just the source file names are modified from the prior version.
## source files
The [source files](tfe5) of the text editor `tfe` will be shown in the next section.
You can also download the files from the [repository](https://github.com/ToshioCP/Gtk4-tutorial).
You can download the files from the [repository](https://github.com/ToshioCP/Gtk4-tutorial).
There are two options.
- Use git and clone.

View file

@ -1,24 +1,19 @@
# tfe5 source files
# How to build tfe (text file editor)
## How to compile and execute the text editor 'tfe'.
First, source files are shown in the later subsections.
First, source files are in the [Gtk4-tutorila repository](https://github.com/ToshioCP/Gtk4-tutorial).
How to download them is written at the end of the [previous section](sec15.src.md).
The following is the instruction of compilation and execution.
- You need meson and ninja.
- Set environment variables if necessary.
If you have installed gtk4 from the source and you preferred the option `--prefix $HOME/local` (see [Section 2](sec2.src.md)), type `. env.sh` to set the environment variables.
~~~
$ . env.sh
~~~
- change your current directory to `src/tfe5` directory.
- type `meson _build` for configuration.
- type `ninja -C _build` for compilation.
- If you have installed gtk4 from the source, you need to set environment variables to suit your installation.
- Change your current directory to `src/tfe5` directory.
- Type `meson _build` for configuration.
- Type `ninja -C _build` for compilation.
Then the application `tfe` is built under the `_build` directory.
- type `_build/tfe` to execute it.
- Type `_build/tfe` to execute it.
Then the window appears.
There are four buttons, `New`, `Open`, `Save` and `Close`.
@ -33,63 +28,9 @@ Then the file is read and a new Notebook Page appears.
This is a very simple editor.
It is a good practice for you to add more features.
## meson.build
@@@include
tfe5/meson.build
@@@
## tfe.gresource.xml
@@@include
tfe5/tfe.gresource.xml
@@@
## tfe.ui
@@@include
tfe5/tfe.ui
@@@
## tfe.h
@@@include
tfe5/tfe.h
@@@
## tfeapplication.c
@@@include
tfe5/tfeapplication.c
@@@
## tfenotebook.h
@@@include
tfe5/tfenotebook.h
@@@
## tfenotebook.c
@@@include
tfe5/tfenotebook.c
@@@
## tfetextview.h
@@@include
tfetextview/tfetextview.h
@@@
## tfetextview.c
@@@include
tfetextview/tfetextview.c
@@@
## Total number of lines, words and characters
@@@shell
LANG=C wc tfe5/meson.build tfe5/tfeapplication.c tfe5/tfe.gresource.xml tfe5/tfe.h tfe5/tfenotebook.c tfe5/tfenotebook.h tfetextview/tfetextview.c tfetextview/tfetextview.h tfe5/tfe.ui
LANG=C wc tfe5/meson.build tfe5/tfeapplication.c tfe5/tfe.gresource.xml tfe5/tfenotebook.c tfe5/tfenotebook.h tfetextview/tfetextview.c tfetextview/tfetextview.h tfe5/tfe.ui
@@@

View file

@ -2,371 +2,40 @@
This section describes how to install GTK 4 into Linux distributions.
This tutorial is without any warranty.
If you want to install GTK 4 to your computer, do it at your own risk.
The information in this section is the one on April/27/2022.
The words 'at present' and/or 'now' in this section means 'April/27/2022'.
There are three possible way to install GTK 4.
There are two ways to install GTK 4.
- Install it from the distribution packages.
- Build it from the source file.
- Install a GNOME 40 distribution with the GNOME Boxes.
- Build it from the source files.
## Installation from the distribution packages
The first way is easy to install.
It is a recommended way.
I've installed GTK 4 packages in Ubuntu 21.04.
(Now, my Ubuntu version is 21.10).
The first way is the best and easiest way to install it.
I've installed GTK 4 packages (version 4.8.1) in Ubuntu 22.10.
~~~
$ sudo apt-get install libgtk-4-bin libgtk-4-common libgtk-4-dev libgtk-4-doc
$ sudo apt install libgtk-4-dev
~~~
Fedora, Arch, Debian and OpenSUSE are also possible.
See [Installing GTK from packages](https://www.gtk.org/docs/installations/linux#installing-gtk-from-packages).
It is important to install the developing package (libgtk-4-dev).
It includes C header files.
Otherwise, you can't compile any GTK 4 based programs.
Fedora, Debian, Arch, Gentoo and OpenSUSE also have GTK 4 packages.
Package information for Arch, Debian/Ubuntu and Fedra is described by [Installing GTK from packages](https://www.gtk.org/docs/installations/linux#installing-gtk-from-packages).
The following table shows the distributions which support GTK 4.
@@@table
|Distribution|version|GTK 4|GNOME 40|
|Distribution|version|GTK 4|GNOME|
|:-:|:-:|:-:|:-:|
|Fedora|36|4.4.2|GNOME 42|
|Ubuntu|22.04lts|4.4|GNOME 41(4.6.2)|
|Debian|bookworm(testing)|4.6.5|GNOME 42|
|Arch|rolling release|4.6.5|GNOME 42|
|Gentoo|rolling release|4.6.5|GNOME 42|
|OpenSUSE|Tumbleweed(rolling release)|4.6.5|GNOME 42|
|Fedora|37|4.8.2-2|GNOME 43|
|Ubuntu|22.10|4.8.1|GNOME 43.0|
|Debian|bookworm(testing)|4.8.2|GNOME 43|
|Arch|rolling release|4.8.2|GNOME 43|
|Gentoo|rolling release|4.8.2|GNOME 43|
|OpenSUSE|Tumbleweed(rolling release)|4.8.2|GNOME 43|
@@@
If you've installed GTK 4 from the packages, you don't need to read the rest of this section.
## Installation from the source file
If your operating system doesn't have GTK 4 packages, you need to build it from the source.
Or, if you want the latest version of GTK 4 (4.6.3), you also need to build it from the source.
I installed GTK 4 from the source in January 2021.
So, the following information is old, especially for the version of each software.
For the latest information, see [GTK API Reference, Building GTK](https://docs.gtk.org/gtk4/building.html).
### Prerequisites for GTK 4 installation
- Linux operating system. For example, Ubuntu 20.10 or 20.04LTS.
Other distributions might be OK.
- Packages for development such as gcc, meson, ninja, git, wget and so on.
- Dev package is necessary for each software below.
### Installation target
I installed GTK 4 under the directory `$HOME/local`.
This is a private user area.
If you want to install it in the system area, `/opt/gtk4` is one of good choices.
[GTK API Reference, Building GTK](https://docs.gtk.org/gtk4/building.html) gives an installation example to `/opt/gtk4`.
Don't install it to `/usr/local` which is the default.
It is used by Ubuntu applications which are not build on GTK 4.
Therefore, the risk is high and probably bad things will happen.
Actually I did it and I needed to reinstall Ubuntu.
### Installation to Ubuntu 20.10
Most of the necessary libraries are included by Ubuntu 20.10.
Therefore, they can be installed with `apt-get` command.
You don't need to install them from the source tarballs.
You can skip the subsections below about prerequisite library installation (GLib, Pango, GdkPixbuf and GTK-doc).
### GLib installation
If your Ubuntu is 20.04LTS, you need to install prerequisite libraries from the tarballs.
Check the version of your library and if it is lower than the necessary version, install it from the source.
For example,
~~~
$ pkg-config --modversion glib-2.0
2.64.6
~~~
The necessary version is 2.66.0 or higher.
Therefore, the example above shows that you need to install GLib.
I installed 2.67.1 which was the latest version at that time (January 2021).
Download GLib source files from the repository, then decompress and extract files.
$ wget https://download.gnome.org/sources/glib/2.67/glib-2.67.1.tar.xz
$ tar -Jxf glib-2.67.1.tar.xz
Some packages are required to build GLib.
You can find them if you run meson.
$ meson --prefix $HOME/local _build
Use apt-get and install the prerequisites.
For example,
$ sudo apt-get install -y libpcre2-dev libffi-dev
After that, compile GLib.
$ rm -rf _build
$ meson --prefix $HOME/local _build
$ ninja -C _build
$ ninja -C _build install
Set several environment variables so that the GLib libraries installed can be used by build tools.
Make a text file below and save it as `env.sh`
# compiler
CPPFLAGS="-I$HOME/local/include"
LDFLAGS="-L$HOME/local/lib"
PKG_CONFIG_PATH="$HOME/local/lib/pkgconfig:$HOME/local/lib/x86_64-linux-gnu/pkgconfig"
export CPPFLAGS LDFLAGS PKG_CONFIG_PATH
# linker
LD_LIBRARY_PATH="$HOME/local/lib/x86_64-linux-gnu/"
PATH="$HOME/local/bin:$PATH"
export LD_LIBRARY_PATH PATH
# gsetting
export GSETTINGS_SCHEMA_DIR=$HOME/local/share/glib-2.0/schemas
Then, use . (dot) or source command to include these commands to the current bash.
$ . env.sh
or
$ source env.sh
This command carries out the commands in `env.sh` and changes the environment variables above in the current shell.
### Pango installation
Download and untar.
$ wget https://download.gnome.org/sources/pango/1.48/pango-1.48.0.tar.xz
$ tar -Jxf pango-1.48.0.tar.xz
Try meson and check the required packages.
Install all the prerequisites.
Then, compile and install Pango.
$ meson --prefix $HOME/local _build
$ ninja -C _build
$ ninja -C _build install
It installs Pango-1.0.gir under `$HOME/local/share/gir-1.0`.
If you installed Pango without `--prefix` option, then it would be located at `/usr/local/share/gir-1.0`.
This directory (/usr/local/share) is used by applications.
They find the directory by the environment variable `XDG_DATA_DIRS`.
It is a text file which keep the list of 'share' directories like `/usr/share`, `usr/local/share` and so on.
Now `$HOME/local/share` needs to be added to `XDG_DATA_DIRS`, or error will occur in the later compilation.
$ export XDG_DATA_DIRS=$HOME/local/share:$XDG_DATA_DIRS
### GdkPixbuf and GTK-Doc installation
Download and untar.
$ wget https://download.gnome.org/sources/gdk-pixbuf/2.42/gdk-pixbuf-2.42.2.tar.xz
$ tar -Jxf gdk-pixbuf-2.42.2.tar.xz
$ wget https://download.gnome.org/sources/gtk-doc/1.33/gtk-doc-1.33.1.tar.xz
$ tar -Jxf gtk-doc-1.33.1.tar.xz
Same as before, install prerequisite packages, then compile and install them.
The installation of GTK-Doc put `gtk-doc.pc` under `$HOME/local/share/pkgconfig`.
This file is used by pkg-config, which is one of the build tools.
The directory needs to be added to the environment variable `PKG_CONFIG_PATH`
$ export PKG_CONFIG_PATH="$HOME/local/share/pkgconfig:$PKG_CONFIG_PATH"
### GTK 4 installation
If you want the latest development version of GTK 4, use git and clone the repository.
$ git clone https://gitlab.gnome.org/gnome/gtk.git
If you want a stable version of GTK 4, then download it from [GNOME source website](https://download.gnome.org/sources/gtk/).
The latest version is 4.3.1 (13/June/2021).
Compile and install it.
$ meson --prefix $HOME/local _build
$ ninja -C _build
$ ninja -C _build install
If you want to know more information, refer to [GTK 4 API Reference, Building GTK](https://docs.gtk.org/gtk4/building.html).
### Modify env.sh
Because environment variables disappear when you log out, you need to add them again.
Modify `env.sh`.
# compiler
CPPFLAGS="-I$HOME/local/include"
LDFLAGS="-L$HOME/local/lib"
PKG_CONFIG_PATH="$HOME/local/lib/pkgconfig:$HOME/local/lib/x86_64-linux-gnu/pkgconfig:
$HOME/local/share/pkgconfig"
export CPPFLAGS LDFLAGS PKG_CONFIG_PATH
# linker
LD_LIBRARY_PATH="$HOME/local/lib/x86_64-linux-gnu/"
PATH="$HOME/local/bin:$PATH"
export LD_LIBRARY_PATH PATH
# gir
XDG_DATA_DIRS=$HOME/local/share:$XDG_DATA_DIRS
export XDG_DATA_DIRS
# gsetting
export GSETTINGS_SCHEMA_DIR=$HOME/local/share/glib-2.0/schemas
# girepository-1.0
export GI_TYPELIB_PATH=$HOME/local/lib/x86_64-linux-gnu/girepository-1.0
Include this file by . (dot) command before using GTK 4 libraries.
You may think you can add them in your `.profile`.
But it's a wrong decision.
Never write them to your `.profile`.
The environment variables above are necessary only when you compile and run GTK 4 applications.
Otherwise it's not necessary.
If you changed the environment variables above and run GTK 3 applications, it probably causes serious damage.
### Compiling GTK 4 applications
Before you compile GTK 4 applications, define environment variables above.
$ . env.sh
After that you can compile them without anything.
For example, to compile `sample.c`, type the following.
$ gcc `pkg-config --cflags gtk4` sample.c `pkg-config --libs gtk4`
To know how to compile GTK 4 applications, refer to the section 3 (GtkApplication and GtkApplicationWindow) and after.
## Installing Fedora 34 with GNOME Boxes
The last part of this section is about GNOME 40 and GNOME Boxes.
GNOME 40 is a new version of GNOME desktop system.
And GTK 4 is installed in the distribution.
See [GNOME 40 website](https://forty.gnome.org/) first.
*However, GNOME 40 is not necessary to compile and run GTK 4 applications.*
There are seven choices at present.
- GNOME OS
- Arch Linux
- Gentoo Linux
- Fedora 36
- openSUSE Tumbleweed
- Ubuntu 22.04
- Debian bookworm
I've installed Fedora 34 with GNOME Boxes.
My OS was Ubuntu 21.04 at that time.
GNOME Boxes creates a virtual machine in Ubuntu and Fedora will be installed to that virtual machine.
The instruction is as follows.
1. Download Fedora 34 iso file.
There is an link at the end of [GNOME 40 website](https://forty.gnome.org/).
2. Install gnome-boxes with apt-get command.
~~~
$ sudo apt-get install gnome-boxes
~~~
3. Run GNOME Boxes.
4. Click on `+` button on the top left corner and launch a box creation wizard by clicking `Create a Virtual Machine ...`.
Then a dialog appears.
Click on `Operationg System Image File` and select the iso file you have downloaded.
5. Then, the Fedora's installer is executed.
Follow the instructions by the installer.
At the end of the installation, the installer instructs to reboot the system.
Click on the right of the title bar and select reboot or shutdown.
6. Your display is back to the initial window of GNOME Boxes, but there is a button `Fedora 34 Workstation` on the upper left of the window.
Click on the button then Fedora will be executed.
7. A setup dialog appears.
Setup Fedora according to the wizard.
Now you can use Fedora.
It includes GTK 4 libraries already.
But you need to install the GTK 4 development package.
Use `dnf` to install `gtk4.x86_64` package.
~~~
$ sudo dnf install gtk4.x86_64
~~~
### GTK 4 compilation test
You can test the GTK 4 development packages by compiling files which are based on GTK 4.
I've tried compiling `tfe` text editor, which is written in section 21.
1. Run Firefox.
2. Open this website \([Gtk4-Tutorial](https://github.com/ToshioCP/Gtk4-tutorial)\).
3. Click on the green button labeled `Code`.
4. Select `Download ZIP` and download the codes from the repository.
5. Unzip the file.
6. Change your current directory to `src/tfe7`.
7. Compile it.
~~~
$ meson _build
bash: meson: command not found...
Install package 'meson' to provide command 'meson'? [N/y] y
* Waiting in queue...
The following packages have to be installed:
meson-0.56.2-2.fc34.noarch High productivity build system
ninja-build-1.10.2-2.fc34.x86_64 Small build system with a focus on speed
vim-filesystem-2:8.2.2787-1.fc34.noarch VIM filesystem layout
Proceed with changes? [N/y] y
... ...
... ...
The Meson build system
Version: 0.56.2
... ...
... ...
Project name: tfe
Project version: undefined
C compiler for the host machine: cc (gcc 11.0.0 "cc (GCC) 11.0.0 20210210 (Red Hat 11.0.0-0)")
C linker for the host machine: cc ld.bfd 2.35.1-38
Host machine cpu family: x86_64
Host machine cpu: x86_64
Found pkg-config: /usr/bin/pkg-config (1.7.3)
Run-time dependency gtk4 found: YES 4.2.0
Found pkg-config: /usr/bin/pkg-config (1.7.3)
Program glib-compile-resources found: YES (/usr/bin/glib-compile-resources)
Program glib-compile-schemas found: YES (/usr/bin/glib-compile-schemas)
Program glib-compile-schemas found: YES (/usr/bin/glib-compile-schemas)
Build targets in project: 4
Found ninja-1.10.2 at /usr/bin/ninja
$ ninja -C _build
ninja: Entering directory `_build'
[12/12] Linking target tfe
$ ninja -C _build install
ninja: Entering directory `_build'
[0/1] Installing files.
Installing tfe to /usr/local/bin
Installation failed due to insufficient permissions.
Attempting to use polkit to gain elevated privileges...
Installing tfe to /usr/local/bin
Installing /home/<username>/Gtk4-tutorial-main/src/tfe7/com.github.ToshioCP.tfe.gschema.xml to /usr/local/share/glib-2.0/schemas
Running custom install script '/usr/bin/glib-compile-schemas /usr/local/share/glib-2.0/schemas/'
~~~
8. Execute it.
~~~
$ tfe
~~~
Then, the window of `tfe` text editor appears.
The compilation and execution have succeeded.
If you want to install a developing version of GTK 4, you need to build it from the source.
See [Compiling the GTK Libraries](https://docs.gtk.org/gtk4/building.html) secion in the GTK 4 API reference.

View file

@ -4,7 +4,7 @@
### GtkApplication and g\_application\_run
Usually people write programming code to make an application.
People write programming code to make an application.
What are applications?
Applications are software that runs using libraries, which includes the
OS, frameworks and so on.
@ -25,7 +25,7 @@ misc/pr1.c
@@@
The first line says that this program includes the header files of the Gtk libraries.
The function `main` above is a startup function in C language.
The function `main` is a startup function in C language.
The variable `app` is defined as a pointer to a GtkApplication instance.
The function `gtk_application_new` creates a GtkApplication instance and returns a pointer to the instance.
The GtkApplication instance is a C structure data in which the information about the application is stored.
@ -35,6 +35,16 @@ The function `g_application_run` runs an application that the instance defined.
Actually, `app` is not an application but a pointer to the instance of the application.
However, it is simple and short, and probably no confusion occurs.)
Here I used the word `instance`.
Instance, class and object are terminologies in Object Oriented Programming.
I use these words in the same way.
But, I will often use "object" instead of "instance" in this tutorial.
That means "object" and "instance" is the same.
Object is a bit ambiguous word.
In a broad sense, object has wider meaning than instance.
So, readers should be careful of the contexts to find the meaning of "object".
In many cases, object and instance are the same.
To compile this, the following command needs to be run.
The string `pr1.c` is the filename of the C source code above.
@ -71,7 +81,8 @@ So, I will explain that to you first.
A signal is emitted when something happens.
For example, a window is created, a window is destroyed and so on.
The signal "activate" is emitted when the application is activated, or started.
The signal "activate" is emitted when the application is activated.
(Activated is a bit different from started, but you can think the both are almost same so far.)
If the signal is connected to a function, which is called a signal handler or
simply handler, then the function is invoked when the signal emits.
@ -79,7 +90,7 @@ The flow is like this:
1. Something happens.
2. If it's related to a certain signal, then the signal is emitted.
3. If the signal as been connected to a handler, then the handler is invoked.
3. If the signal has been connected to a handler in advance, then the handler is invoked.
Signals are defined in objects.
For example, the "activate" signal belongs to the GApplication object, which is
@ -105,6 +116,7 @@ misc/pr2.c
@@@
First, we define the handler `app_activate` which simply displays a message.
The function `g_print` is defined in GLib and it's like a printf in the C standard library.
In the function `main`, we add `g_signal_connect` before `g_application_run`.
The function `g_signal_connect` has four arguments.
@ -113,14 +125,39 @@ The function `g_signal_connect` has four arguments.
3. A handler function (also called callback), which needs to be casted by `G_CALLBACK`.
4. Data to pass to the handler. If no data is necessary, NULL should be given.
You can find the description of each signal in the API reference manual.
For example, "activate" signal is in GApplication section in [GIO API Reference](https://docs.gtk.org/gio/signal.Application.activate.html).
The handler function is described in it.
It is described in the [GObject API Reference](https://docs.gtk.org/gobject/func.signal_connect.html).
Correctly, `g_signal_connect` is a macro (not a C function).
~~~c
#define g_signal_connect (
instance,
detailed_signal,
c_handler,
data
)
~~~
You can find the description of each signal in the API reference manual.
For example, "activate" signal is in [GApplication section](https://docs.gtk.org/gio/signal.Application.activate.html) in the GIO API Reference.
~~~c
void
activate (
GApplication* self,
gpointer user_data
)
~~~
This is a declaration of the "activate" signal handler.
You can use any name instead of "activate" in the declaration above.
The parameters are:
- self is an instance to which the signal belongs.
- user\_data is a data defined in the fourth argument of the `g_signal_connect` function.
If it is NULL, then you can ignore and left out the second parameter.
In addition, `g_signal_connect` is described in [GObject API Reference](https://docs.gtk.org/gobject/func.signal_connect.html).
API reference manual is very important.
You should see and understand it to write Gtk applications.
They are located in ['GTK Documentation'](https://docs.gtk.org/).
You should see and understand it.
Let's compile the source file above (`pr2.c`) and run it.
@ -207,19 +244,20 @@ By this definition, it returns a pointer to GtkWidget, not GtkWindow.
It actually creates a new GtkWindow instance (not GtkWidget) but returns a pointer to GtkWidget.
However,the pointer points the GtkWidget and at the same time it also points GtkWindow that contains GtkWidget in it.
If you want to use `win` as a pointer to the GtkWindow, you need to cast it.
If you want to use `win` as a pointer to a GtkWindow type instance, you need to cast it.
~~~C
(GtkWindow *) win
~~~
Or you can use `GTK_WINDOW` macro that performs a similar function.
It works, but isn't usually used.
Instead, `GTK_WINDOW` macro is used.
~~~C
GTK_WINDOW (win)
~~~
This is a recommended way.
The macro is recommended because it does not only cast but also check the type.
#### Connect it to GtkApplication.
@ -229,8 +267,7 @@ The function `gtk_window_set_application` is used to connect GtkWindow to GtkApp
gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
~~~
You need to cast `win` to GtkWindow and `app` to GtkApplication.
`GTK_WINDOW` and `GTK_APPLICATION` macro is appropriate for that.
You need to cast `win` to GtkWindow and `app` to GtkApplication with `GTK_WINDOW` and `GTK_APPLICATION` macro.
GtkApplication continues to run until the related window is destroyed.
If you didn't connect GtkWindow and GtkApplication, GtkApplication destroys itself immediately.
@ -246,7 +283,7 @@ But, there's an exception.
Top window (this term will be explained later) isn't visible when it is created.
So you need to use the function above to show the window.
Save the program as `pr3.c` and compile and run it.
Save the program as `pr3.c`, then compile and run it.
~~~
$ comp pr3
@ -262,8 +299,8 @@ Click on the close button then the window disappears and the program finishes.
### GtkApplicationWindow
GtkApplicationWindow is a child object of GtkWindow.
It has some extra functionality for better integration with GtkApplication.
It is recommended to use it instead of GtkWindow when you use GtkApplication.
It has some extra feature for better integration with GtkApplication.
It is recommended to use it as the top-level window of the application instead of GtkWindow.
Now rewrite the program and use GtkApplicationWindow.

View file

@ -32,6 +32,8 @@ cd misc; diff pr4.c lb1.c
This tells us:
- A signal handler `app_activate` doesn't have `user_data` parameter.
If the fourth argument of `g_signal_connect` is NULL, you can leave out `user_data`.
- The definition of a new variable `lab` is added.
- The title of the window is changed.
- A label is created and connected to the window as a child.
@ -53,11 +55,11 @@ An application can have more than one top-level window.
### GtkButton
The next widget to introduce is GtkButton.
The next widget is GtkButton.
It displays a button on the screen with a label or icon on it.
In this subsection, we will make a button with a label.
When the button is clicked, it emits a "clicked" signal.
The following program shows how to catch the signal to then do something.
The following program shows how to catch the signal and do something.
@@@include
misc/lb2.c
@ -66,7 +68,7 @@ misc/lb2.c
Look at the line 17 to 19.
First, it creates a GtkButton instance `btn` with a label "Click me".
Then, adds the button to the window `win` as a child.
Finally, connects a "clicked" signal of the button to a handler (function) `click_cb`.
Finally, connects a "clicked" signal of the button to the handler `click_cb`.
So, if `btn` is clicked, the function `click_cb` is invoked.
The suffix "cb" means "call back".
@ -80,7 +82,7 @@ Click the button (it is a large button, you can click everywhere in the window),
It shows the handler was invoked by clicking the button.
It's good that we make sure that the clicked signal was caught and the handler was invoked by using `g_print`.
However, using g_print is out of harmony with Gtk which is a GUI library.
However, using `g_print` is out of harmony with GTK, which is a GUI library.
So, we will change the handler.
The following code is `lb3.c`.
@ -96,15 +98,19 @@ cd misc; diff lb2.c lb3.c
The changes are:
- The function `g_print` in `lb2.c` was deleted and the two lines above are inserted instead.
- The label of `btn` is changed from "Click me" to "Quit".
- The function `g_print` in `lb2.c` was deleted and two lines are inserted.
- `click_cb` has the second parameter, which comes from the fourth argument of the `g_signal_connect` at the line 19.
One thing to be careful is the types are different between the second parameter of `click_cb` and the fourth argument of `g_signal_connect`.
The former is `GtkWindow *` and the latter is `GtkWidget *`.
The compiler doesn't complain because `g_signal_connect` uses gpointer (general type of pointer).
In this program the instance pointed by `win` is a GtkApplicationWindow object.
It is a descendant of GtkWindow and GtkWidget class, so both `GtkWindow *` and `GtkWidget *` are correct types for the instance.
- `gtk_destroy (win)` destroys the top-level window. Then the application quits.
- The label of `btn` is changed from "Click me" to "Close".
- The fourth argument of `g_signal_connect` is changed from `NULL` to `win`.
The most important change is the fourth argument of `g_signal_connect`.
This argument is described as "data to pass to handler" in the definition of `g_signal_connect` in [GObject API Reference](https://docs.gtk.org/gobject/func.signal_connect.html).
Therefore, `win` which is a pointer to GtkApplicationWindow is passed to the handler as a second parameter `user_data`.
The handler then casts it to a pointer to GtkWindow and calls `gtk_window_destroy` to destroy the top-level window.
The application then quits.
The most important change is the fourth argument of the `g_signal_connect`.
This argument is described as "data to pass to handler" in the definition of [`g_signal_connect`](https://docs.gtk.org/gobject/func.signal_connect.html).
### GtkBox
@ -123,8 +129,7 @@ After this, the Widgets are connected as the following diagram.
![Parent-child relationship](../image/box.png){width=7.725cm height=2.055cm}
The program `lb4.c` includes these widgets.
It is as follows.
The program `lb4.c` is as follows.
@@@include
misc/lb4.c
@ -134,16 +139,36 @@ Look at the function `app_activate`.
After the creation of a GtkApplicationWindow instance, a GtkBox instance is created.
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
gtk_box_set_homogeneous (GTK_BOX (box), TRUE);
~~~C
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
gtk_box_set_homogeneous (GTK_BOX (box), TRUE);
~~~
The first argument arranges the children of the box vertically.
The orientation constants are defined like this:
- GTK\_ORIENTATION\_VERTICAL: the children widgets are arranged vertically
- GTK\_ORIENTATION\_HORIZONTAL: the children widgets are arranged horizontally
The second argument is the size between the children.
The unit of the length is pixel.
The next function fills the box with the children, giving them the same space.
After that, two buttons `btn1` and `btn2` are created and the signal handlers are set.
Then, these two buttons are appended to the box.
@@@include
misc/lb4.c click1.cb
@@@
The function `gtk_button_get_lable` returns a text from the label.
The string is owned by the button and you can't modify or free it.
The `const` qualifier is necessary for the string `s`.
If you change the string, your compiler will give you a waring.
You always need to be careful with the const qualifier when you see the GTK 4 API reference.
![Screenshot of the box](../image/screenshot_lb4.png){width=6.3cm height=5.325cm}
The handler corresponds to `btn1` toggles its label.

View file

@ -16,16 +16,26 @@ Look at line 25.
A GtkTextView instance is created and its pointer is assigned to `tv`.
When the GtkTextView instance is created, a GtkTextBuffer instance is also created and connected to the GtkTextView automatically.
"GtkTextBuffer instance" will be referred to simply as "GtkTextBuffer" or "buffer".
In the next line, the pointer to the buffer is got and assigned to `tb`.
In the next line, the pointer to the buffer is assigned to `tb`.
Then, the text from line 10 to 20 is assigned to the buffer.
If the third argument of `gtk_text_buffer_set_text` is a positive integer, it is the length of the text.
It it is -1, the string terminates with NULL.
GtkTextView has a wrap mode.
When it is set to `GTK_WRAP_WORD_CHAR`, text wraps in between words, or if that is not enough, also between graphemes.
Wrap mode is written in [Gtk\_WrapMode](https://docs.gtk.org/gtk4/enum.WrapMode.html) in the GTK 4 API document.
In line 30, `tv` is added to `win` as a child.
Now compile and run it.
```
$ cd src/tfv
$ comp tfv1
$ ./a.out
```
![GtkTextView](../image/screenshot_tfv1.png){width=6.3cm height=5.325cm}
There's an I-beam pointer in the window.
@ -33,30 +43,31 @@ You can add or delete any characters on the GtkTextView,
and your changes are kept in the GtkTextBuffer.
If you add more characters beyond the limit of the window, the height increases and the window extends.
If the height gets bigger than the height of the display screen, you won't be
able to control the size of the window, and change it back to the original size.
This is a problem and shows that there is a bug in our program.
This can solve it by adding a GtkScrolledWindow between the GtkApplicationWindow and GtkTextView.
able to control the size of the window or change it back to the original size.
This is a problem, that is to say a bug.
This can be solved by adding a GtkScrolledWindow between the GtkApplicationWindow and GtkTextView.
### GtkScrolledWindow
What we need to do is:
- Create a GtkScrolledWindow and insert it as a child of the GtkApplicationWindow; and
- Create a GtkScrolledWindow and insert it as a child of the GtkApplicationWindow
- Insert the GtkTextView widget to the GtkScrolledWindow as a child.
Modify `tfv1.c` and save it as `tfv2.c`.
The difference between these two files is small.
There is only a few difference between these two files.
@@@shell
cd tfv; diff tfv1.c tfv2.c
@@@
Here is the complete code of `tfv2.c`.
The whole code of `tfv2.c` is as follows.
@@@include
tfv/tfv2.c
@@@
Compile and run it.
Notice how this time the window doesn't extend when you type a lot of characters,
it just scrolls and displays a slider.
Now, the window doesn't extend even if you type a lot of characters,
it just scrolls.

View file

@ -1,4 +1,4 @@
# String and memory management
# Strings and memory management
GtkTextView and GtkTextBuffer have functions that use string parameters or return a string.
The knowledge of strings and memory management is useful to understand how to use these functions.
@ -6,12 +6,11 @@ The knowledge of strings and memory management is useful to understand how to us
## String and memory
A String is an array of characters that is terminated with '\0'.
Strings are not a C type such as char, int, float or double,
but exist as a pointer to a character array. They behaves like a string type
which you may be familiar from other languages.
So, this pointer is often called 'a string'.
String is not a C type such as char, int, float or double, but a character array.
It behaves like a string in other languages.
So, the pointer is often called 'a string'.
In the following, `a` and `b` defined as character arrays, and are strings.
The following is a sample program.
~~~C
char a[10], *b;
@ -28,50 +27,50 @@ b = a;
/* *(++b) is 'e' */
~~~
The array `a` has `char` elements and the size of ten.
The first six elements are 'H', 'e', 'l', 'l', 'o' and '\0'.
This array represents the string "Hello".
The first five elements are character codes that correspond to the characters.
The sixth element is '\0', which is the same as zero,
and indicates that the string ends there.
The size of the array is 10, so 4 bytes aren't used, but that's OK,
they are just ignored.
An array `a` is defined as a `char` type array and its size is ten.
The first five elements are 'H', 'e', 'l', 'l', 'o'.
They are character codes.
For example, 'H' is the same as 0x48 or 72.
The sixth element is '\0', which is the same as zero, and indicates that the sequence of the data ends there.
The array represents the string "Hello".
The size of the array is 10, so 4 bytes aren't used.
But it's OK.
They are just ignored.
(If 'a' is defined out of functions or its class is static, they are assigned with zero.
Otherwise, that is to say, the class is auto or register, they are undefined.)
The variable 'b' is a pointer to a character.
Because `b` is assigned to be `a`, `a` and `b` point the same character ('H').
The variable `a` is defined as an array and it can't be changed.
It always point the top address of the array.
On the other hand, 'b' is a pointer, which is mutable, so `b` can be change.
It is then possible to write statements like `++b`, which means take the value in b (n address),
increase it by one, and store that back in `b`.
It is assigned with `a`, so `b` points the first element of `a` (character 'H').
The array `a` is immutable.
So `a=a+1` causes syntax error.
If a pointer is NULL, it points to nothing.
On the other hand, 'b' is a pointer type variable, which is mutable.
So, `++b`, which increases `b` by one, is allowed.
If a pointer is NULL, it points nothing.
So, the pointer is not a string.
A NULL string on the other hand will be a pointer which points to a location
that contains `\0`, which is a string of length 0 (or "").
Programs that use strings will include bugs if you aren't careful when using NULL pointers.
It is different from empty string.
Empty string is a pointer points `\0`.
Another annoying problem is the memory that a string is allocated.
There are four cases:
- The string is read only;
- The string is in static memory area;
- The string is in stack; and
- The string is in memory allocated from the heap area.
- The string is read only
- The string is in static memory area
- The string is in stack
- The string is in memory allocated from the heap area
## Read only string
A string literal in a C program is surrounded by double quotes and written as the following:
A string literal is surrounded by double quotes like this:
~~~C
char *s;
s = "Hello"
~~~
"Hello" is a string literal, and is stored in program memory.
A string literal is read only.
In the program above, `s` points the string literal.
"Hello" is a string literal, and is read only.
So, the following program is illegal.
~~~C
@ -81,25 +80,38 @@ So, the following program is illegal.
The result is undefined.
Probably a bad thing will happen, for example, a segmentation fault.
NOTE: The memory of the literal string is allocated when the program is
compiled. It is possible to view all the literal strings defined in your program
by using the `string` command.
NOTE: The memory of the literal string is allocated when the program is compiled.
It is possible to see the literal strings with `strings` command.
~~~
$ strings src/tvf/a.out
/lib64/ld-linux-x86-64.so.2
cN<5
... ... ...
... ... ...
Once upon a time, there was an old man who was called Taketori-no-Okina. It is a japanese word that means a man whose work is making bamboo baskets.
One day, he went into a mountain and found a shining bamboo. "What a mysterious bamboo it is!," he said. He cut it, then there was a small cute baby girl in it. The girl was shining faintly. He thought this baby girl is a gift from Heaven and took her home.
His wife was surprized at his story. They were very happy because they had no children.
... ... ...
... ... ...
~~~
It tells us that literal strings are embedded in program binary codes.
## Strings defined as arrays
If a string is defined as an array, it's in either stored in the static memory area or stack.
This depends on the class of the array.
If a string is defined as an array, it's stored in static memory area or stack.
It depends on the class of the array.
If the array's class is `static`, then it's placed in static memory area.
This allocation and memory address is fixed for the life of the program.
This area can be changed and is writable.
The allocated memory lives for the life of the program.
This area is writable.
If the array's class is `auto`, then it's placed in stack.
If the array's class is `auto`, it's placed in stack.
If the array is defined inside a function, its default class is `auto`.
The stack area will disappear when the function exits and returns to the caller.
The stack area will disappear when the function returns to the caller.
Arrays defined on the stack are writable.
~~~C
static char a[] = {'H', 'e', 'l', 'l', 'o', '\0'};
void
@ -114,30 +126,28 @@ print_strings (void) {
}
~~~
The array `a` is defined externally to a function and is global in its scope.
Such variables are placed in static memory area even if the `static` class is left out.
The compiler calculates the number of the elements in the right hand side (six),
and then creates code that allocates six bytes in the static memory area and copies the data to this memory.
The array `a` is defined out of functions.
It is placed in the static memory area even if the `static` class is left out.
The compiler calculates the number of the elements (six) and allocates six bytes in the static memory area.
Then, it copies "Hello" literal string data to the memory.
The array `b` is defined inside the function
so its class is `auto`.
The array `b` is defined inside the function, so its class is `auto`.
The compiler calculates the number of the elements in the string literal.
It has six elements as the zero termination character is also included.
The compiler creates code which allocates six bytes memory in the stack and copies the data to the memory.
It is six because it has '\0' terminator.
The compiler allocates six bytes in the stack and copies "Hello" litaral string to the stack memory.
Both `a` and `b` are writable.
The memory is managed by the executable program.
You don't need your program to allocate or free the memory for `a` and `b`.
The array `a` is created then the program is first run and remains for the life of the program.
The array `b` is created on the stack then the function is called, disappears when the function returns.
The memory is allocated and freed by the program automatically so you don't need to allocate or free.
The array `a` is alive during the program's life time.
The array `b` is alive when the function is called until the function returns to the caller.
## Strings in the heap area
You can also get, use and release memory from the heap area.
You can get, use and release memory from the heap area.
The standard C library provides `malloc` to get memory and `free` to put back memory.
GLib provides the functions `g_new` and `g_free` to do the same thing, with support for
some additional GLib functionality.
GLib provides the functions `g_new` and `g_free`.
They are similar to `malloc` and `free`.
~~~C
g_new (struct_type, n_struct)
@ -195,9 +205,9 @@ s = g_strdup ("Hello");
g_free (s);
~~~
The string literal "Hello" has 6 bytes because the string has '\0' at the end it.
The string literal "Hello" has 6 bytes because the string has '\0' at the end.
`g_strdup` gets 6 bytes from the heap area and copies the string to the memory.
`s` is assigned the top address of the memory.
`s` is assigned the start address of the memory.
`g_free` returns the memory to the heap area.
`g_strdup` is described in [GLib API Reference](https://docs.gtk.org/glib/func.strdup.html).
@ -205,27 +215,38 @@ The following is extracted from the reference.
> The returned string should be freed with `g_free()` when no longer needed.
The function reference will describe if the returned value needs to be freed.
If you forget to free the allocated memory it will remain allocated. Repeated use will cause
more memory to be allocated to the program, which will grow over time. This is called a memory leak,
and the only way to address this bug is to close the program (and restart it),
which will automatically release all of the programs memory back to the system.
If you forget to free the allocated memory it will remain until the program ends.
Repeated allocation and no freeing cause memory leak.
It is a bug and may bring a serious problem.
Some GLib functions return a string which mustn't be freed by the caller.
## const qualifier
~~~C
const char *
g_quark_to_string (GQuark quark);
~~~
This function returns `const char*` type.
The qualifier `const` means that the returned value is immutable.
The characters pointed by the returned value aren't be allowed to be changed or freed.
If a variable is qualified with `const`, the variable can't be assigned except during initialization.
A `const` qualified variable can be assigned to initialize it.
Once it is initialized, it is never allowed to change or free.
~~~C
const int x = 10; /* initialization is OK. */
x = 20; /* This is illegal because x is qualified with const */
~~~
If a function returns `const char*` type, the string can't be changed or freed.
If a function has a `const char *` type parameter, it ensures that the parameter is not changed in the function.
~~~C
// You never change or free the returned string.
const char*
gtk_label_get_text (
GtkLabel* self
)
// Str keeps itself during the function runs
void
gtk_label_set_text (
GtkLabel* self,
const char* str
)
~~~

View file

@ -4,51 +4,40 @@
### G\_APPLICATION\_HANDLES\_OPEN flag
The GtkTextView, GtkTextBuffer and GtkScrolledWindow widgets have given us a minimum editor
in the previous section.
We will now add a function to read a file and rework the program into a file viewer.
There are many ways to implement the function and
because this is a tutorial for beginners, we'll take the easiest one.
We made a very simple editor in the previous section with GtkTextView, GtkTextBuffer and GtkScrolledWindow.
We will add file-reading ability to the program and improve it to a file viewer.
When the program starts, we will give the filename to open as an argument.
The easiest way to give a filename is to use a command line argument.
$ ./a.out filename
It will open the file and insert its contents into the GtkTextBuffer.
The program will open the file and insert its contents into the GtkTextBuffer.
To do this, we need to know how GtkApplication (or GApplication) recognizes arguments.
This is described in the [GIO API Reference, Application](https://docs.gtk.org/gio/class.Application.html).
This is described in the [GIO API Reference -- Application](https://docs.gtk.org/gio/class.Application.html).
When GtkApplication is created, a flag (with the type GApplicationFlags) is provided as an argument.
When GtkApplication is created, a flag (GApplicationFlags) is given as an argument.
~~~C
GtkApplication *
gtk_application_new (const gchar *application_id, GApplicationFlags flags);
~~~
This tutorial explains only two flags, `G_APPLICATION_FLAGS_NONE` and `G_APPLICATION_HANDLES_OPEN`.
This tutorial explains only two flags, `G_APPLICATION_DEFAULT_FLAGS` and `G_APPLICATION_HANDLES_OPEN`.
(`G_APPLICATION_FLAGS_NONE` was used instead of `G_APPLICATION_DEFAULT_FLAGS` before GIO version2.73.3 (GLib 2.73.3 5/Aug/2022).
Some GTK 4 applications still use `G_APPLICATION_FLAGS_NONE`.
But now it is deprecated and `G_APPLICATION_DEFAULT_FLAGS` is recommended.)
If you want to handle command line arguments, the `G_APPLICATION_HANDLES_COMMAND_LINE` flag is what you need.
How to use the new application method is described in [GIO API Reference, g\_application\_run](https://docs.gtk.org/gio/method.Application.run.html),
and the flag is described in the [GIO API Reference, ApplicationFlags](https://docs.gtk.org/gio/flags.ApplicationFlags.html).
~~~
GApplicationFlags' Members
For further information, see [GIO API Reference -- ApplicationFlags](https://docs.gtk.org/gio/flags.ApplicationFlags.html) and
[GIO API Reference -- g\_application\_run](https://docs.gtk.org/gio/method.Application.run.html).
G_APPLICATION_FLAGS_NONE Default. (No argument allowed)
... ... ...
G_APPLICATION_HANDLES_OPEN This application handles opening files (in the primary instance).
... ... ...
~~~
There are ten flags in total, but we only need two of them so far.
We've already used `G_APPLICATION_FLAGS_NONE`, as
it is the simplest option, and no arguments are allowed.
If you provide arguments when running the application, an error will occur.
We've already used `G_APPLICATION_DEFAULT_FLAGS`, as it is the simplest option, and no command line arguments are allowed.
If you give arguments, an error will occur.
The flag `G_APPLICATION_HANDLES_OPEN` is the second simplest option.
It allows arguments but only files.
The application assumes all the arguments are filenames and we will use this flag when creating
our GtkApplication.
The application assumes all the arguments are filenames.
~~~C
app = gtk_application_new ("com.github.ToshioCP.tfv3", G_APPLICATION_HANDLES_OPEN);
@ -56,7 +45,7 @@ app = gtk_application_new ("com.github.ToshioCP.tfv3", G_APPLICATION_HANDLES_OPE
### open signal
Now, when the application starts, two signals can be emitted.
When `G_APPLICATION_HANDLES_OPEN` flag is given to the application, two signals are available.
- activate signal --- This signal is emitted when there's no argument.
- open signal --- This signal is emitted when there is at least one argument.
@ -64,37 +53,38 @@ Now, when the application starts, two signals can be emitted.
The handler of the "open" signal is defined as follows.
~~~C
void user_function (GApplication *application,
gpointer files,
gint n_files,
gchar *hint,
gpointer user_data)
void
open (
GApplication* self,
gpointer files,
gint n_files,
gchar* hint,
gpointer user_data
)
~~~
The parameters are:
- `application` --- the application (usually GtkApplication)
- `self` --- the application instance (usually GtkApplication)
- `files` --- an array of GFiles. [array length=n\_files] [element-type GFile]
- `n_files` --- the number of the elements of `files`
- `hint` --- a hint provided by the calling instance (usually it can be ignored)
- `user_data` --- user data set when the signal handler was connected.
How to read a specified file (GFile) will be described next.
## Making a file viewer
### What is a file viewer?
A file viewer is a program that displays the text file that is given as an argument.
A file viewer is a program that displays text files.
Our file viewer will work as follows.
- When arguments are given, it treats the first argument as a filename and opens it.
- If opening the file succeeds, it reads the contents of the file and inserts it to GtkTextBuffer and then shows the window.
- If it fails to open the file, it will show an error message and quit.
- If there's no argument, it will shows an error message and quit.
- If there are two or more arguments, the second one and any others are ignored.
- When arguments are given, it recognizes the first argument as a filename and opens it.
- The second argument and after are ignored.
- If there's no argument, it shows an error message and quit.
- If it successfully opens the file, it reads the contents of the file, inserts them to GtkTextBuffer and shows the window.
- If it fails to open the file, it shows an error message and quit.
The program which does this is shown below.
The program is shown below.
@@@include
tfv/tfv3.c
@ -108,25 +98,29 @@ Then compile and run it.
![File viewer](../image/screenshot_tfv3.png){width=6.3cm height=5.325cm}
Let's explain how the program `tfv3.c` works.
First, the function `main` has only two changes from the previous version.
The function `main` has only two changes from the previous version.
- `G_APPLICATION_FLAGS_NONE` is replaced by `G_APPLICATION_HANDLES_OPEN`; and
- `g_signal_connect (app, "open", G_CALLBACK (on_open), NULL)` is added.
- `G_APPLICATION_DEFAULT_FLAGS` is replaced by `G_APPLICATION_HANDLES_OPEN`
- `g_signal_connect (app, "open", G_CALLBACK (app_open), NULL)` is added.
Next, the handler `app_activate` is added and is very simple.
It just outputs the error message and
the application quits immediately because no window is created.
When the flag `G_APPLICATION_HANDLES_OPEN` is given to `gtk_application_new` function, the application behaves like this:
The main functionality is the in the handler `app_open`. It
- If the application is run without command line arguments, it emits "activate" signal when it is activated.
- If the application is run with command line arguments, it emits "open" signal when it is activated.
- Creates GtkApplicationWindow, GtkScrolledWindow, GtkTextView and GtkTextBuffer and connects them together;
- Sets wrap mode to `GTK_WRAP_WORD_CHAR` in GtktextView;
- Sets GtkTextView to non-editable because the program isn't an editor but only a viewer;
- Reads the file and inserts the text into GtkTextBuffer (this will be explained in detail later); and
- If the file is not opened then outputs an error message and destroys the window. This makes the application quit.
The handler `app_activate` becomes very simple.
It just outputs the error message and return to the caller.
Then the application quits immediately because no window is created.
The following is the important file reading part of the program and is shown again below.
The main work is done in the handler `app_open`.
- Creates GtkApplicationWindow, GtkScrolledWindow, GtkTextView and GtkTextBuffer and connects them together
- Sets wrap mode to `GTK_WRAP_WORD_CHAR` in GtktextView
- Sets GtkTextView to non-editable because the program isn't an editor but only a viewer
- Reads the file and inserts the text into GtkTextBuffer (this will be explained later)
- If the file is not opened, outputs an error message and destroys the window. This makes the application quit.
The following is the file reading part of the program again.
~~~C
if (g_file_load_contents (files[0], NULL, &contents, &length, NULL, NULL)) {
@ -139,39 +133,41 @@ if (g_file_load_contents (files[0], NULL, &contents, &length, NULL, NULL)) {
gtk_widget_show (win);
} else {
if ((filename = g_file_get_path (files[0])) != NULL) {
g_print ("No such file: %s.\n", filename);
g_printerr ("No such file: %s.\n", filename);
g_free (filename);
}
} else
g_printerr ("File can't be opened.\n");
gtk_window_destroy (GTK_WINDOW (win));
}
~~~
The function `g_file_load_contents` loads the file contents into a buffer,
which is automatically allocated and sets `contents` to point that buffer.
The length of the buffer is set to `length`.
It returns `TRUE` if the file's contents are successfully loaded and `FALSE` if an error occurs.
The function `g_file_load_contents` loads the file contents into a temporary buffer,
which is automatically allocated and sets `contents` to point the buffer.
The length of the buffer is assigned to `length`.
It returns `TRUE` if the file's contents are successfully loaded or `FALSE` if an error occurs.
If you want to know the details about g\_file\_load\_contents, see [g file load contents](https://docs.gtk.org/gio/method.File.load_contents.html).
If this function succeeds, it inserts the contents into GtkTextBuffer,
frees the buffer pointed by `contents`, sets the title of the window,
If it has successfully read the file, it inserts the contents into GtkTextBuffer,
frees the temporary buffer pointed by `contents`, sets the title of the window,
frees the memories pointed by `filename` and then shows the window.
If it fails, it outputs an error message and destroys the window, causing the program to quit.
If it fails, it outputs an error message and destroys the window and finally make the program quit.
## GtkNotebook
GtkNotebook is a container widget that uses tabs and contains multiple children.
The child that is displayed depends on which tab has been selected.
GtkNotebook is a container widget that contains multiple widgets with tabs.
It shows only one child at a time.
Another child will be shown when its tab is clicked.
![GtkNotebook](../image/screenshot_gtk_notebook.png){width=13.2cm height=5.325cm}
Looking at the screenshots above,
the left one is the window at the startup.
It shows the file `pr1.c` and the filename is in the left tab.
After clicking on the right tab, the contents of the file `tfv1.c` are shown instead.
This is shown in the right screenshot.
The left image is the window at the startup.
The file `pr1.c` is shown and its filename is in the left tab.
After clicking on the right tab, the contents of the file `tfv1.c` is shown.
The right image is the screenshot.
The GtkNotebook widget is inserted as a child of GtkApplicationWindow and contains a GtkScrolledWindow
for each file that is being displayed.
The code to do this is given in `tfv4.c` and is:
The following is `tfv4.c`.
It has GtkNoteBook widget.
It is inserted as a child of GtkApplicationWindow and contains multiple GtkScrolledWindow.
@@@include
tfv/tfv4.c
@ -180,34 +176,26 @@ tfv/tfv4.c
Most of the changes are in the function `app_open`.
The numbers at the left of the following items are line numbers in the source code.
- 11-13: Variables `nb`, `lab` and `nbp` are defined and will point to a new GtkNotebook, GtkLabel and GtkNotebookPage widget respectively.
- 11-13: Variables `nb`, `lab` and `nbp` are defined. They point GtkNotebook, GtkLabel and GtkNotebookPage respectively.
- 23: The window's title is set to "file viewer".
- 25: The size of the window is set to maximum because a big window is appropriate for file viewers.
- 27-28 GtkNotebook is created and inserted to the GtkApplicationWindow as a child.
- 30-59 For-loop. Each loop corresponds to a filename argument, and `files[i]` is GFile object containing the i-th argument.
- 32-37 GtkScrollledWindow, GtkTextView are created and GtkTextBuffer found from the new GtkTextView.
GtkTextView is connected to GtkScrolledWindow as a child.
Each file gets their own copy of these widgets, so they are created inside the for-loop.
- 39-40 inserts the contents of the file into GtkTextBuffer and frees the memory pointed by `contents`.
- 41-43: Gets the filename and creates GtkLabel with the filename and then frees `filename`.
- 44-45: If `filename` is NULL, creates GtkLabel with the empty string.
- 46: Appends GtkScrolledWindow as a page, with the tab GtkLabel, to GtkNotebook.
At this time a GtkNoteBookPage widget is created automatically.
The GtkScrolledWindow widget is connected to the GtkNotebookPage.
Therefore, the structure is like this:
~~~
GtkNotebook -- GtkNotebookPage -- GtkScrolledWindow
~~~
- 47: Gets GtkNotebookPage widget and sets `nbp` to point to this GtkNotebookPage.
- 48: GtkNotebookPage has a property set called "tab-expand".
- 24: The default size of the window is 600x400.
- 26-27 GtkNotebook is created and inserted to the GtkApplicationWindow as a child.
- 29-58 For-loop. The variable `files[i]` points i-th GFile, which is created by the GtkApplication from the i-th command line argument.
- 31-36 GtkScrollledWindow, GtkTextView are created. GtkTextBuffer is got from the GtkTextView.
The GtkTextView is connected to the GtkScrolledWindow as a child.
- 38-39 inserts the contents of the file into GtkTextBuffer and frees the memory pointed by `contents`.
- 40-42: If the filename is taken from the GFile, GtkLabel is created with the filename. The string `filename` is freed..
- 43-44: If it fails to take the filename, empty string GtkLabel is created.
- 45-46: Appends a GtkScrolledWindow to the GtkNotebook as a child.
And the GtkLabel is set as the child's tab.
At the same time, a GtkNoteBookPage is created automatically.
The function `gtk_notebook_get_page` returns the GtkNotebookPage of the child (GtkScrolledWindow).
- 47: GtkNotebookPage has "tab-expand" property.
If it is set to TRUE then the tab expands horizontally as long as possible.
If it is FALSE, then the width of the tab is determined by the size of the label.
`g_object_set` is a general function to set properties in objects.
See [GObject API Reference, g\_object\_set](https://docs.gtk.org/gobject/method.Object.set.html).
- 49-51: If the file cannot be read, "No such file" message is displayed and the `filename` buffer is freed.
- 52-53: If `filename` is NULL, the "No valid file is given" message is outputted.
- 55-58: If at least one file was read, then the number of GtkNotebookPage is greater than zero.
If it's true, it shows the window.
If it's false, it destroys the window, which causes the program to quit.
`g_object_set` is a general function to set properties of objects.
See [GObject API Reference -- g\_object\_set](https://docs.gtk.org/gobject/method.Object.set.html).
- 48-50: If it fails to read the file and a filename is taken from the GFile, "No such file" message is displayed. The `filename` is freed.
- 51-52: If `filename` is NULL, the "No valid file is given" message is displayed.
- 54-57: If at least one page exists, the window is shown.
Otherwise, the window is destroyed and the application quits.

View file

@ -1,60 +1,62 @@
# Defining a child object
# Defining a final class
## A very simple editor
In the previous section we made a very simple file viewer.
We made a very simple file viewer in the previous section.
Now we go on to rewrite it and turn it into very simple editor.
Its source file is in tfe1.c (text file editor 1).
Its source file is `tfe1.c` (text file editor 1) under `tfe` directory.
GtkTextView has a feature for editing multiple lines. Therefore, we don't need to
write the program from scratch, we just add two things to the file viewer:
GtkTextView is a multi-line editor.
So, we don't need to write the editor from scratch.
We just add two things to the file viewer:
- Memory to store a pointer to the GFile instance.
- A function to write the file.
- Pointers to GFile instances.
- A text-save function.
There are a couple of ways to store the details of GFile.
There are a couple of ways to store the pointers.
- Use global variables; or
- Make a child object, which can extend the instance memory for the GFile object.
- Use global variables
- Make a child class of GtkTextView and its each instance holds a pointer to the GFile instance.
Using global variables is easy to implement.
Define a sufficient size array of pointers to GFile.
Define a sufficient size pointer array to GFile.
For example,
~~~C
GFile *f[20];
~~~
The variable `f[i]` corresponds to the file associated to the i-th GtkNotebookPage.
There are however two problems with this.
The first concerns the size of the array.
If a user gives too many arguments (more than 20 in the example above), it is impossible to store the additional pointers to the GFile instances.
The second is the increasing difficulty for maintenance of the program.
We have a small program so far,
but however, if you continue developing it, the size of the program will grow.
Generally speaking, the bigger the program size, the more difficult it is to keep track of and maintain global variables. Global variables can be used and changed anywhere throughout the entire program.
The variable `f[i]` corresponds to the file associated with the i-th GtkNotebookPage.
Making a child object is a good idea in terms of maintenance.
One thing you need to be careful of is the difference between "child object" and "child widget".
Here we are describing a "child object".
A child object includes, and expands on its parent object, as
a child object derives everything from the parent object.
However, There are two problems.
The first is the size of the array.
If a user gives too many arguments (more than 20 in the example above), it is impossible to store all the pointers to the GFile instances.
The second is difficulty to maintain the program.
We have a small program so far.
But, the more you develop the program, the bigger its size grows.
Generally speaking, it is very difficult to maintain global variables in a big program.
When you check the global variable, you need to check all the codes that use the variable.
Making a child class is a good idea in terms of maintenance.
And we prefer it rather than a global variable.
Be careful that we are thinking about "child class", not "child widget".
Child class and child widget are totally different.
Class is a term of GObject system.
If you are not familiar with GObject, see:
- [GObject API reference](https://docs.gtk.org/gobject/)
- [GObject tutorial for beginners](https://toshiocp.github.io/Gobject-tutorial/)
A child class inherits everything from the parent and, in addition, extends its performance.
We will define TfeTextView as a child class of GtkTextView.
It has everything that GtkTextView has and adds a pointer to a GFile.
![Child object of GtkTextView](../image/child.png){width=9.675cm height=4.89cm}
We will define TfeTextView as a child object of GtkTextView.
It has everything that GtkTextView has.
Specifically, TfeTextView has a GtkTextbuffer which corresponds to the GtkTextView inside TfeTextView.
The additional important thing is that TfeTextView can also keep an additional pointer to GFile.
## How to define a child class of GtkTextView
In general, this is how GObjects work. Understanding the general theory about Gobject's is difficult,
particularly for beginners.
So, I will just show you the way how to write the code and avoid the theoretical side.
If you want to know about GObject system, refer to another [tutorial](https://github.com/ToshioCP/Gobject-tutorial).
## How to define a child object of GtkTextView
Let's define the TfeTextView object, which is a child object of GtkTextView.
You need to know GObject system convention.
First, look at the program below.
~~~C
@ -93,55 +95,55 @@ tfe_text_view_new (void) {
}
~~~
If you are curious about the background theory of this program, that's good,
because knowing the theory is very important if you want to program GTK applications.
Look at [GObject API Reference](https://docs.gtk.org/gobject/).
All you need is described there,
or refer to [GObject tutorial](https://github.com/ToshioCP/Gobject-tutorial).
It's a tough journey especially for beginners so for now, you don't need to know about this difficult theory.
It is enough to just remember the instructions below.
- TfeTextView is divided into two parts.
Tfe and TextView.
Tfe is called the prefix, namespace or module.
TextView is called the object.
Tfe is called prefix or namespace.
TextView is called object.
- There are three differnet identifier patterns.
TfeTextView (camel case), tfe\_text\_view (this is used to write functions) and TFE\_TEXT\_VIEW (This is used to cast a pointer to point TfeTextView type).
TfeTextView (camel case), tfe\_text\_view (this is used for functions) and TFE\_TEXT\_VIEW (This is used to cast a object to TfeTextView).
- First, define TFE\_TYPE\_TEXT\_VIEW macro as tfe\_text\_view\_get\_type ().
The name is always (prefix)\_TYPE\_(object) and the letters are upper case.
And the replacement text is always (prefix)\_(object)\_get\_type () and the letters are lower case.
- Next, use G\_DECLARE\_FINAL\_TYPE macro.
The arguments are the child object name in camel case, lower case with underscore, prefix (upper case), object (upper case with underscore) and parent object name (camel case).
This definition is put before G\_DECLARE\_FINAL\_TYPE macro.
- The arguments of G\_DECLARE\_FINAL\_TYPE macro are the child class name in camel case, lower case with underscore, prefix (upper case), object (upper case with underscore) and parent class name (camel case).
The following two C structure is declared in the expansion of the macro.
- `typedef struct _TfeTextView TfeTextView`
- `typedef struct {GtkTextViewClass parent_class; } TfeTextViewClass;`
- These declaration tells us that TfeTextView and TfeTextViewClass are C structures.
"TfeTextView" has two meanings, class name and C structure name.
The C structure TfeTextView is called object.
Similarly, TfeTextViewClass is called class.
- Declare the structure \_TfeTextView.
The underscore is necessary.
The first member is the parent object.
The first member is the parent object (C structure).
Notice this is not a pointer but the object itself.
The second member and after are members of the child object.
TfeTextView structure has a pointer to a GFile instance as a member.
- Use G\_DEFINE\_TYPE macro.
- G\_DEFINE\_TYPE macro.
The arguments are the child object name in camel case, lower case with underscore and parent object type (prefix)\_TYPE\_(module).
- Define instance init function (tfe\_text\_view\_init).
Usually you don't need to do anything.
- Define class init function (tfe\_text\_view\_class\_init).
You don't need to do anything in this object.
- Write function codes you want to add (tfe\_text\_view\_set\_file and tfe\_text\_view\_get\_file).
`tv` is a pointer to the TfeTextView object instance which is a C-structure declared with the tag \_TfeTextView.
So, the structure has a member `file` as a pointer to a GFile instance.
`tv->file = f` is an assignment of `f` to a member `file` of the structure pointed by `tv`.
This is an example how to use the extended memory in a child widget.
- Write a function to create an instance.
This macro is mainly used to register the new class to the type system.
Type system is a base system of GObject.
Every class has its own type.
The types of GObject, GtkWidget and TfeTextView are G\_TYPE\_OBJECT, GTK\_TYPE\_WIDGET and TFE\_TYPE\_TEXT\_VIEW respectively.
Such type (for example, TFE\_TYPE\_TEXT\_VIEW) is a macro and it is expanded to a function (tfe\_text\_view\_get\_type()).
It returns a integer which is unique among all GObject system classes.
- Instance init function (tfe\_text\_view\_init) is called when the instance is created.
It is the same as a constructor in other object oriented languages.
- Class init function (tfe\_text\_view\_class\_init) is called when the class is created.
- Two functions tfe\_text\_view\_set\_file and tfe\_text\_view\_get\_file are public functions.
Public functions are open and you can call them anywhere.
They are the same as public method in other object oriented languages.
`tv` is a pointer to the TfeTextView object (C structure).
It has a member `file` and it is pointed by `tv->file`.
- TfeTextView instance creation function is `tfe_text_view_new`.
Its name is (prefix)\_(object)\_new.
If the parent object function needs parameters, this function also need them.
You sometimes might want to add some parameters.
It's your choice.
Use g\_object\_new function to create the instance.
The arguments are (prefix)\_TYPE\_(object), a list to initialize properties and NULL.
In this code no property needs to be initialized.
It uses g\_object\_new function to create the instance.
The arguments are (prefix)\_TYPE\_(object), a list to initialize properties and NULL.
NULL is the end mark of the property list.
No property is initialized here.
And the return value is casted to GtkWidget.
This program is not perfect.
It has some problems.
It will be modified later.
This program shows the outline how to define a child class.
## Close-request signal
@ -154,10 +156,9 @@ After you finish editing, you exit the editor.
The editor updates files just before the window closes.
GtkWindow emits the "close-request" signal before it closes.
We connect the signal and the handler `before_close`.
A handler is a C function.
When a function is connected to a certain signal, we call it a handler.
The function `before_close` is invoked when the signal "close-request" is emitted.
We will connect the signal and the handler `before_close`.
(A handler is a C function which is connected to a signal.)
The function `before_close` is called when the signal "close-request" is emitted.
~~~C
g_signal_connect (win, "close-request", G_CALLBACK (before_close), NULL);
@ -173,36 +174,46 @@ tfe/tfe1.c before_close
The numbers on the left of items are line numbers in the source code.
- 15: Gets the number of pages `nb` has.
- 16-29: For loop with regard to the index to each pages.
- 17-19: Gets GtkScrolledWindow, TfeTextView and a pointer to GFile.
The pointer was stored when `app_open` handler had run. It will be shown later.
- 20-22: Gets GtkTextBuffer and contents. `start_iter` and `end_iter` are iterators of the buffer.
I don't want to explain them now because it would take a lot of time.
Just remember these lines for the present.
- 23-27: Writes the contents to the file.
If it fails, it outputs an error message.
- 28: Frees `contents`.
- 14: The number of pages of `nb` is assigned to `n`.
- 15-31: For loop with regard to the index to each pages.
- 16-18: `scr`, `tv` and `file` is assigned pointers to the GtkScrolledWindow, TfeTextView and GFile.
The GFile of TfeTextView was stored when `app_open` handler was called. It will be shown later.
- 19-21: `tb` is assigned the GtkTextBuffer of the TfeTextView.
The buffer is accessed with iterators.
Iterators points somewhere in the buffer.
The function `gtk_text_buffer_get_bounds` assigns the start and end of the buffer to `start_iter` and `end_iter` respectively.
Then the function `gtk_text_buffer_get_text` returns the text between `start_iter` and `end_iter`, which is the whole text in the buffer.
- 22-28: The text is saved to the file.
If it fails, error messages are displayed.
- 29: `contents` are freed.
- 30: GFile is useless. `g_object_unref` decreases the reference count of the GFile.
Reference count will be explained in the later section.
The reference count will be zero in this program and the GFile instance will destroy itself.
## Source code of tfe1.c
The following is the complete source code of `tfe1.c`.
The following is the whole source code of `tfe1.c`.
@@@include
tfe/tfe1.c
@@@
- 107: Sets the pointer to GFile into TfeTextView.
`files[i]` is a pointer to GFile structure.
It will be freed by the system. So you need to copy it.
`g_file_dup` duplicates the given GFile structure.
- 123: Connects "close-request" signal and `before_close` handler.
The fourth argument is called user data and it is given to the signal handler.
- 110: The GFile pointer of the TfeTextView is set with `files[i]`, which is a GFile created with the command line argument.
But the GFile will be destroyed by the system later.
So it needs to be copied before the assignment.
`g_file_dup` duplicates the GFile.
- 126: The "close-request" signal is connected to `before_close` handler.
The fourth argument is called "user data" and it will be the second argument of the signal handler.
So, `nb` is given to `before_close` as the second argument.
Now compile and run it.
There's a sample file in the directory `tfe`.
Type `./a.out taketori.txt`.
Now it's time to compile and run.
~~~
$ cd src/tfe
$ comp tfe1
$ ./a.out taketori.txt`.
~~~
Modify the contents and close the window.
Make sure that the file is modified.

View file

@ -1,13 +1,12 @@
# The User Interface (UI) file and GtkBuilder
# GtkBuilder and UI file
## New, Open and Save button
In the last section we made the almost simplest editor possible.
It reads files in the `app_open` function at start-up and writes them out when closing the window.
It works but is not very good.
We made very simple editor in the previous section.
It reads files at the start and writes them out at the end of the program.
It works, but is not so good.
It would be better if we had "New", "Open", "Save" and "Close" buttons.
This section describes how to put those buttons into the window.
Signals and handlers will be explained later.
![Screenshot of the file editor](../image/screenshot_tfe2.png){width=9.3cm height=6.825cm}
@ -18,7 +17,7 @@ The function `app_open` in the source code `tfe2.c` is as follows.
tfe/tfe2.c app_open
@@@
The aim is to build the widgets of the main application window.
The function `app_open` builds the widgets in the main application window.
- 25-27: Creates a GtkApplicationWindow instance and sets the title and default size.
- 29-30: Creates a GtkBox instance `boxv`.
@ -33,13 +32,12 @@ The other label `dmy2` has hexpand property which is set to be TRUE.
This makes the label expands horizontally as long as possible.
- 41-44: Creates four buttons.
- 46-52: Appends these GtkLabel and GtkButton to `boxh`.
- 54-57: Creates a GtkNotebook instance and sets hexpand and vexpand properties TRUE.
- 54-57: Creates a GtkNotebook instance and sets hexpand and vexpand properties to be TRUE.
This makes it expand horizontally and vertically as big as possible.
It is appended to `boxv` as the second child.
The number of lines to build the widgets is 33(=57-25+1).
We also needed many additional variables (`boxv`, `boxh`, `dmy1`, ...),
most of which weren't necessary, except for building the widgets.
The number of widget-build lines is 33(=57-25+1).
We also needed many variables (`boxv`, `boxh`, `dmy1`, ...) and most of them used only for building the widgets.
Are there any good solution to reduce these work?
Gtk provides GtkBuilder.
@ -48,32 +46,30 @@ It reduces this cumbersome work.
## The UI File
First, let's look at the UI file `tfe3.ui` that is used to define the widget structure.
Look at the UI file `tfe3.ui` that defines widget structure.
@@@include
tfe/tfe3.ui
@@@
The structure of this file is XML.
Constructs that begin with `<` and end with `>` are called tags.
The is a XML file.
Tags begin with `<` and end with `>`.
There are two types of tags, the start tag and the end tag.
For example, `<interface>` is a start tag and `</interface>` is an end tag.
The UI file begins and ends with interface tags.
Some tags, for example object tags, can have a class and id attributes in their start tag.
- 1: The first line is XML declaration.
It specifies that the version of XML is 1.0 and the encoding is UTF-8.
Even if the line is left out, GtkBuilder builds objects from the ui file.
But ui files must use UTF-8 encoding, or GtkBuilder can't recognize it and a fatal error occurs.
- 3-6: An object with `GtkApplicationWindow` class and `win` id is defined.
- 1: XML declaration.
It specifies that the XML version is 1.0 and the encoding is UTF-8.
- 3-6: An object tag with `GtkApplicationWindow` class and `win` id.
This is the top level window.
And the three properties of the window are defined.
`title` property is "file editor", `default-width` property is 600 and `default-height` property is 400.
- 7: child tag means a child of the widget above.
For example, line 7 tells us that GtkBox object which id is "boxv" is a child widget of `win`.
The `title` property is "file editor", `default-width` property is 600 and `default-height` property is 400.
- 7: child tag means a child widget.
For example, line 7 tells us that GtkBox object with "boxv" id attribute is a child widget of `win`.
Compare this ui file and the lines 25-57 in the source code of `app_open` function.
Those two describe the same structure of widgets.
Compare this ui file and the lines 25-57 in the `tfe2.c` source code.
Both builds the same window with its descendant widgets.
You can check the ui file with `gtk4-builder-tool`.
@ -90,7 +86,7 @@ It is a good idea to check your ui file before compiling.
## GtkBuilder
GtkBuilder builds widgets based on the ui file.
GtkBuilder builds widgets based on a ui file.
~~~C
GtkBuilder *build;
@ -105,7 +101,7 @@ The function `gtk_builder_new_from_file` reads the file given as an argument.
Then, it builds the widgets and creates GtkBuilder object.
The function `gtk_builder_get_object (build, "win")` returns the pointer to the widget `win`, which is the id in the ui file.
All the widgets are connected based on the parent-children relationship described in the ui file.
We only need `win` and `nb` for the program after this, so we don't need to take out any other widgets.
We only need `win` and `nb` for the program below.
This reduces lines in the C source file.
@@@shell
@ -123,12 +119,11 @@ tfe/tfe3.c app_open
@@@
The whole source code of `tfe3.c` is stored in [src/tfe](tfe) directory.
If you want to see it, click the link above.
### Using ui string
GtkBuilder can build widgets using string.
Use the function `gtk_builder_new_from_string` instead of `gtk_builder_new_from_file`.
GtkBuilder can build widgets with string.
Use `gtk_builder_new_from_string` instead of `gtk_builder_new_from_file`.
~~~C
char *uistring;
@ -146,19 +141,19 @@ uistring =
... ... ...
"</interface>";
build = gtk_builder_new_from_stringfile (uistring);
build = gtk_builder_new_from_string (uistring, -1);
~~~
This method has an advantage and disadvantage.
The advantage is that the ui string is written in the source code.
So ui file is not necessary on runtime.
So, no ui file is needed on runtime.
The disadvantage is that writing C string is a bit bothersome because of the double quotes.
If you want to use this method, you should write a script that transforms ui file into C-string.
- Add backslash before each double quote.
- Add double quotes at the left and right of the string in each line.
### Using Gresource
### Gresource
Using Gresource is similar to using string.
But Gresource is compressed binary data, not text data.
@ -176,9 +171,10 @@ tfe/tfe3.gresource.xml
- 2: `gresources` tag can include multiple gresources (gresource tags).
However, this xml has only one gresource.
- 3: The gresource has a prefix `/com/github/ToshioCP/tfe3`.
- 4: The gresource has `tfe3.ui`.
And it is pointed by `/com/github/ToshioCP/tfe3/tfe3.ui` because it needs prefix.
If you want to add more files, then insert them between line 4 and 5.
- 4: The name of the gresource is `tfe3.ui`.
The resource will be pointed with `/com/github/ToshioCP/tfe3/tfe3.ui` by GtkBuilder.
The pattern is "prefix" + "name".
If you want to add more files, insert them between line 4 and 5.
Save this xml text to `tfe3.gresource.xml`.
The gresource compiler `glib-compile-resources` shows its usage with the argument `--help`.
@ -189,7 +185,9 @@ LANG=C glib-compile-resources --help
Now run the compiler.
$ glib-compile-resources tfe3.gresource.xml --target=resources.c --generate-source
~~~
$ glib-compile-resources tfe3.gresource.xml --target=resources.c --generate-source
~~~
Then a C source file `resources.c` is generated.
Modify `tfe3.c` and save it as `tfe3_r.c`.
@ -203,5 +201,10 @@ build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe3/tfe3.ui");
... ... ...
~~~
The function `gtk_builder_new_from_resource` builds widgets from a resource.
Then, compile and run it.
The window appears and it is the same as the screenshot at the beginning of this page.
A window appears and it is the same as the screenshot at the beginning of this page.
Generally, resource is the best way for C language.
If you use other languages like Ruby, string is better than resource.

View file

@ -1,4 +1,4 @@
Once upon a time, there was an old man who was called Taketori-no-Okina. It is a japanese word that means a man whose work is making bamboo baskets.
One day, he went into a mountain and found a shining bamboo. "What a mysterious bamboo it is!," he said. He cut it, then there was a small cute baby girl in it. The girl was shining faintly. He thought this baby girl is a gift from Heaven and took her home.
His wife was surprized at his tale. They were very happy because they had no children.
His wife was surprized at his story. They were very happy because they had no children.

View file

@ -1,6 +1,6 @@
#include <gtk/gtk.h>
/* Define TfeTextView Widget which is the child object of GtkTextView */
/* Define TfeTextView Widget which is the child class of GtkTextView */
#define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
@ -15,6 +15,7 @@ G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
static void
tfe_text_view_init (TfeTextView *tv) {
tv->file = NULL;
}
static void
@ -23,7 +24,7 @@ tfe_text_view_class_init (TfeTextViewClass *class) {
void
tfe_text_view_set_file (TfeTextView *tv, GFile *f) {
tv -> file = f;
tv->file = f;
}
GFile *
@ -39,8 +40,7 @@ tfe_text_view_new (void) {
/* ---------- end of the definition of TfeTextView ---------- */
static gboolean
before_close (GtkWindow *win, gpointer user_data) {
GtkWidget *nb = GTK_WIDGET (user_data);
before_close (GtkWindow *win, GtkWidget *nb) {
GtkWidget *scr;
GtkWidget *tv;
GFile *file;
@ -61,22 +61,25 @@ before_close (GtkWindow *win, gpointer user_data) {
gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
if (! g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, NULL)) {
pathname = g_file_get_path (file);
g_print ("ERROR : Can't save %s.", pathname);
g_free (pathname);
if ((pathname = g_file_get_path (file)) != NULL) {
g_printerr ("Can't save %s.", pathname);
g_free (pathname);
} else
g_printerr ("Pathname not exist.\n");
}
g_free (contents);
g_object_unref (file);
}
return FALSE;
}
static void
app_activate (GApplication *app, gpointer user_data) {
app_activate (GApplication *app) {
g_print ("You need to give filenames as arguments.\n");
}
static void
app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint) {
GtkWidget *win;
GtkWidget *nb;
GtkWidget *lab;
@ -91,7 +94,7 @@ app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer
win = gtk_application_window_new (GTK_APPLICATION (app));
gtk_window_set_title (GTK_WINDOW (win), "file editor");
gtk_window_maximize (GTK_WINDOW (win));
gtk_window_set_default_size (GTK_WINDOW (win), 600, 400);
nb = gtk_notebook_new ();
gtk_window_set_child (GTK_WINDOW (win), nb);

View file

@ -39,12 +39,12 @@ tfe_text_view_new (void) {
/* ---------- end of the definition of TfeTextView ---------- */
static void
app_activate (GApplication *app, gpointer user_data) {
app_activate (GApplication *app) {
g_print ("You need a filename argument.\n");
}
static void
app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint) {
GtkWidget *win;
GtkWidget *nb;
GtkWidget *lab;
@ -142,4 +142,3 @@ main (int argc, char **argv) {
g_object_unref (app);
return stat;
}

View file

@ -39,12 +39,12 @@ tfe_text_view_new (void) {
/* ---------- end of the definition of TfeTextView ---------- */
static void
app_activate (GApplication *app, gpointer user_data) {
app_activate (GApplication *app) {
g_print ("You need a filename argument.\n");
}
static void
app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint) {
GtkWidget *win;
GtkWidget *nb;
GtkWidget *lab;

View file

@ -39,12 +39,12 @@ tfe_text_view_new (void) {
/* ---------- end of the definition of TfeTextView ---------- */
static void
app_activate (GApplication *app, gpointer user_data) {
app_activate (GApplication *app) {
g_print ("You need a filename argument.\n");
}
static void
app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint) {
GtkWidget *win;
GtkWidget *nb;
GtkWidget *lab;

View file

@ -2,8 +2,10 @@ require 'rake/clean'
targetfile = "tfe"
srcfiles = FileList["tfe.c", "tfetextview.c", "resources.c"]
uifile = "tfe.ui"
rscfile = srcfiles[2]
objfiles = srcfiles.gsub(/.c$/, '.o')
objfiles = srcfiles.ext(".o")
gresource_xml = "tfe.gresource.xml"
CLEAN.include(targetfile, objfiles, rscfile)
@ -14,12 +16,12 @@ file targetfile => objfiles do |t|
end
objfiles.each do |obj|
src = obj.gsub(/.o$/,'.c')
src = obj.ext(".c")
file obj => src do |t|
sh "gcc -c -o #{t.name} `pkg-config --cflags gtk4` #{t.source}"
end
end
file rscfile => ["tfe.gresource.xml", "tfe.ui"] do |t|
sh "glib-compile-resources #{t.prerequisites[0]} --target=#{t.name} --generate-source"
file rscfile => uifile do |t|
sh "glib-compile-resources #{gresource_xml} --target=#{t.name} --generate-source"
end

View file

@ -2,12 +2,12 @@
#include "tfetextview.h"
static void
app_activate (GApplication *app, gpointer user_data) {
app_activate (GApplication *app) {
g_print ("You need a filename argument.\n");
}
static void
app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint) {
GtkWidget *win;
GtkWidget *nb;
GtkWidget *lab;

View file

@ -1,4 +0,0 @@
#include <gtk/gtk.h>
#include "../tfetextview/tfetextview.h"
#include "tfenotebook.h"

View file

@ -1,4 +1,7 @@
#include "tfe.h"
#include <gtk/gtk.h>
#include "../tfetextview/tfetextview.h"
#include "tfenotebook.h"
static void
open_cb (GtkNotebook *nb) {
@ -96,4 +99,3 @@ main (int argc, char **argv) {
g_object_unref (app);
return stat;
}

View file

@ -1,7 +1,9 @@
#include "tfe.h"
#include <gtk/gtk.h>
#include "../tfetextview/tfetextview.h"
/* The returned string should be freed with g_free() when no longer needed. */
static gchar*
static char*
get_untitled () {
static int c = -1;
if (++c == 0)
@ -67,14 +69,14 @@ notebook_page_close (GtkNotebook *nb) {
}
}
// filename is owned by the caller.
static void
notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) {
notebook_page_build (GtkNotebook *nb, GtkWidget *tv, const char *filename) {
GtkWidget *scr = gtk_scrolled_window_new ();
GtkNotebookPage *nbp;
GtkWidget *lab;
int i;
gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
lab = gtk_label_new (filename);
i = gtk_notebook_append_page (nb, scr, lab);
@ -89,13 +91,15 @@ open_response (TfeTextView *tv, int response, GtkNotebook *nb) {
GFile *file;
char *filename;
if (response != TFE_OPEN_RESPONSE_SUCCESS || ! G_IS_FILE (file = tfe_text_view_get_file (tv))) {
if (response != TFE_OPEN_RESPONSE_SUCCESS) {
g_object_ref_sink (tv);
g_object_unref (tv);
}else {
file = tfe_text_view_get_file (tv);
filename = g_file_get_basename (file);
g_object_unref (file);
notebook_page_build (nb, GTK_WIDGET (tv), filename);
g_free (filename);
}
}
@ -123,6 +127,7 @@ notebook_page_new_with_file (GtkNotebook *nb, GFile *file) {
return; /* read error */
filename = g_file_get_basename (file);
notebook_page_build (nb, tv, filename);
g_free (filename);
}
void
@ -136,5 +141,5 @@ notebook_page_new (GtkNotebook *nb) {
return;
filename = get_untitled ();
notebook_page_build (nb, tv, filename);
g_free (filename);
}

View file

@ -0,0 +1,9 @@
project ('test', 'c')
gtk4dep = dependency ('gtk4')
sourcefiles_open = ['test_open.c', '../tfetextview.c']
executable ('test_open', sourcefiles_open, dependencies: gtk4dep, install: false)
sourcefiles_save = ['test_save.c', '../tfetextview.c']
executable ('test_save', sourcefiles_save, dependencies: gtk4dep, install: false)

View file

@ -0,0 +1,76 @@
#include <gtk/gtk.h>
#include "../tfetextview.h"
#define APPLICATION_ID "com.github.ToshioCP.testopen"
static void
open_response_cb (TfeTextView *tv, int response) {
GFile *file = tfe_text_view_get_file (TFE_TEXT_VIEW (tv));
char *filename = file ? g_file_get_basename (file) : NULL;
g_printerr ("New file is %s.\n", filename);
switch (response) {
case TFE_OPEN_RESPONSE_SUCCESS:
g_printerr ("Response is SUCCESS.\n");
break;
case TFE_OPEN_RESPONSE_CANCEL:
g_printerr ("Response is CANCEL.\n");
break;
case TFE_OPEN_RESPONSE_ERROR:
g_printerr ("Response is ERROR.\n");
break;
default:
g_printerr ("Unexpected response.\n");
}
}
static void
change_file_cb (TfeTextView *tv, int response) {
g_printerr ("File is changed.\n");
}
/* ----- activate, open, startup handlers ----- */
static void
app_activate (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
GtkWidget *win = gtk_application_window_new (app);
GtkWidget *scr = gtk_scrolled_window_new ();
GtkWidget *tv = tfe_text_view_new ();
gtk_window_set_default_size (GTK_WINDOW (win), 800, 600);
gtk_window_set_child (GTK_WINDOW (win), scr);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
// tfe_text_view_open
// It is assumed that FileChooserDialog works correctly.
// The user (1) selects a file and clicks on "open" or (2) clicks on "cancel".
// - (1) && (tv->file == NULL) => 1. buffer is set with file contents, 2. tv->file is updated,
// 3. response signal with SUCCESS, 4. file-change signal
// - (1) && (G_IS_FILE (tv->file)) => 1. buffer is set with file contents, 2. tv->file is updated,
// 3. response signal with SUCCESS, (4. file-change signal if file changed)
// - (2) => response signal with CANCEL
// The test program outputs logs with regards to above and the user confirms the behavior.
// The test doesn't assert.
gtk_widget_show (GTK_WIDGET (win));
g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response_cb), NULL);
g_signal_connect (TFE_TEXT_VIEW (tv), "change-file", G_CALLBACK (change_file_cb), NULL);
// open twice
tfe_text_view_open (TFE_TEXT_VIEW (tv), GTK_WINDOW (win));
tfe_text_view_open (TFE_TEXT_VIEW (tv), GTK_WINDOW (win));
}
/* ----- main ----- */
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}

View file

@ -0,0 +1,106 @@
#include <stdio.h>
#include <gtk/gtk.h>
#include "../tfetextview.h"
#define APPLICATION_ID "com.github.ToshioCP.testtfetextview"
// NOTICE!! -- Don't choose any existing file to save in FileChooserDialog (except 'yesy.txt')
// senario:
// setup: create test.txt
// create tv (TfeTextView) with test.txt => gtk_text_view_get_text => is the file correct?
// change the buffer with gtk_buffer_insert => save => check the file's contents
// saveas => is change-file signal emitted?
// teardown: remove test.txt
static GFile *
test_create_file (const char *filename, const char *sample) {
FILE *fp = fopen (filename, "w");
fprintf (fp, "%s", sample);
fclose (fp);
return g_file_new_for_path (filename);
}
static char *
test_read_file (const char *filename) {
FILE *fp = fopen (filename, "r");
int c;
int length;
char *s, *t;
// get file size (length)
for (length=0; (c = fgetc (fp)) != EOF; ++length)
;
t = s = g_new (char, length + 1);
fseek (fp, 0, SEEK_SET); // reset the file pointer
while ((c = fgetc (fp)) != EOF)
*t++ = (char) c;
*t = '\0';
fclose (fp);
return s;
}
static void
change_file_cb (TfeTextView *tv, int response) {
g_printerr ("File is changed.\n");
}
/* ----- activate, open, startup handlers ----- */
static void
app_activate (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
GtkWidget *win = gtk_application_window_new (app);
GtkWidget *scr = gtk_scrolled_window_new ();
GtkWidget *tv;
char *s;
gtk_window_set_default_size (GTK_WINDOW (win), 800, 600);
gtk_window_set_child (GTK_WINDOW (win), scr);
// test for tfe_text_view_new_with_file and tfe_text_view_get_file
GFile *file = test_create_file ("test.txt", "I wasn't the outdoor type in my childhood.\n");
tv = tfe_text_view_new_with_file (file);
g_object_ref_sink (tv);
GFile *file2 = tfe_text_view_get_file (TFE_TEXT_VIEW (tv));
if (! g_file_equal (file, file2)) {
g_printerr ("tfe_text_view_get_file didn't return the expected GFile.\n");
g_printerr ("Expected: %s, actual: %s", g_file_get_basename (file), g_file_get_basename(file2));
}
g_object_unref (file2);
// tfe_text_view_save
GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
GtkTextIter iter;
g_signal_connect (TFE_TEXT_VIEW (tv), "change-file", G_CALLBACK (change_file_cb), NULL);
gtk_text_buffer_get_end_iter (tb, &iter);
gtk_text_buffer_insert (tb, &iter, "My mom always got me to play outside.\n", -1);
tfe_text_view_save (TFE_TEXT_VIEW (tv));
s = test_read_file ("test.txt");
char *expected_s = "I wasn't the outdoor type in my childhood.\n"
"My mom always got me to play outside.\n";
if (strcmp (expected_s, s) != 0) {
g_printerr ("tfe_text_view_save didn't work.\n");
g_printerr ("Expected:\n%s\nactual:\n%s\n", expected_s, s);
}
g_free (s);
// tfe_text_view_saveas
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
gtk_widget_show (win);
tfe_text_view_saveas (TFE_TEXT_VIEW (tv));
}
/* ----- main ----- */
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}

View file

@ -73,24 +73,23 @@ static gboolean
save_file (GFile *file, GtkTextBuffer *tb, GtkWindow *win) {
GtkTextIter start_iter;
GtkTextIter end_iter;
gchar *contents;
char *contents;
gboolean stat;
GtkWidget *message_dialog;
GError *err = NULL;
gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
if (g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err)) {
stat = g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err);
if (stat)
gtk_text_buffer_set_modified (tb, FALSE);
stat = TRUE;
} else {
else {
// Because error message is displayed here, the caller of 'save_file' doesn't need to do anything about error.
message_dialog = gtk_message_dialog_new (win, GTK_DIALOG_MODAL,
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
"%s.\n", err->message);
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s.", err->message);
g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
gtk_widget_show (message_dialog);
g_error_free (err);
stat = FALSE;
}
g_free (contents);
return stat;
@ -107,10 +106,17 @@ saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
if (! G_IS_FILE (file))
g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile.\n");
else if (save_file(file, tb, GTK_WINDOW (win))) {
if (G_IS_FILE (tv->file))
// The following is complicated. The comments here will help your understanding
// G_IS_FILE(tv->file) && tv->file == file => nothing to do
// G_IS_FILE(tv->file) && tv->file != file => unref(tv->file), tv->file=file, signal emit
// tv->file==NULL => tv->file=file, signal emit
if (G_IS_FILE (tv->file) && (! g_file_equal (tv->file, file)))
g_object_unref (tv->file);
tv->file = file;
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
if (! (G_IS_FILE (tv->file) && g_file_equal (tv->file, file))) {
tv->file = file; // The ownership of 'file' moves to TfeTextView.
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
}
g_object_unref (file);
} else
g_object_unref (file);
}
@ -128,7 +134,7 @@ tfe_text_view_save (TfeTextView *tv) {
return; /* no need to save it */
else if (tv->file == NULL)
tfe_text_view_saveas (tv);
else if (! G_IS_FILE (tv->file))
else if (! G_IS_FILE (tv->file)) // Unexpected error
g_error ("TfeTextView: The pointer tv->file isn't NULL nor GFile.\n");
else
save_file (tv->file, tb, GTK_WINDOW (win));
@ -188,8 +194,7 @@ open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
} else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
g_object_unref (file);
message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
"%s.\n", err->message);
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", err->message);
g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
gtk_widget_show (message_dialog);
g_error_free (err);
@ -197,12 +202,18 @@ open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
} else {
gtk_text_buffer_set_text (tb, contents, length);
g_free (contents);
gtk_text_buffer_set_modified (tb, FALSE);
// G_IS_FILE(tv->file) && tv->file == file => unref(tv->file), tv->file=file, emit response with SUCCESS
// G_IS_FILE(tv->file) && tv->file != file => unref(tv->file), tv->file=file, emit response with SUCCESS, emit change-file
// tv->file==NULL => tv->file=file, emit response with SUCCESS, emit change-file
// The order is important. If you unref tv->file first, you can't compare tv->file and file anymore.
// And open-response signal is emitted after new tv->file is set. Or the handler can't catch the new file.
if (! (G_IS_FILE (tv->file) && g_file_equal (tv->file, file)))
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
if (G_IS_FILE (tv->file))
g_object_unref (tv->file);
tv->file = file;
gtk_text_buffer_set_modified (tb, FALSE);
tv->file = file; // The ownership of 'file' moves to TfeTextView
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
}
gtk_window_destroy (GTK_WINDOW (dialog));
}
@ -210,20 +221,19 @@ open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
void
tfe_text_view_open (TfeTextView *tv, GtkWindow *win) {
g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
g_return_if_fail (GTK_IS_WINDOW (win));
// 'win' is used for a transient window of the GtkFileChooserDialog.
// It can be NULL.
g_return_if_fail (GTK_IS_WINDOW (win) || win == NULL);
GtkWidget *dialog;
dialog = gtk_file_chooser_dialog_new ("Open file", win, GTK_FILE_CHOOSER_ACTION_OPEN,
"Cancel", GTK_RESPONSE_CANCEL,
"Open", GTK_RESPONSE_ACCEPT,
NULL);
"Cancel", GTK_RESPONSE_CANCEL, "Open", GTK_RESPONSE_ACCEPT, NULL);
g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
gtk_widget_show (dialog);
}
GtkWidget *
tfe_text_view_new (void) {
return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, "wrap-mode", GTK_WRAP_WORD_CHAR, NULL));
}

View file

@ -1,6 +1,6 @@
# TfeTextView API reference
TfeTextView -- Child object of GtkTextView. It holds GFile which the contents of GtkTextBuffer correponds to.
TfeTextView -- Child object of GtkTextView. It is connected to a certain file.
## Functions
- GFile *[tfe_text_view_get_file ()](#tfe_text_view_get_file)
@ -39,8 +39,8 @@ GObject
## Description
TfeTextView holds GFile which the contents of GtkTextBuffer corresponds to.
File manipulation functions are added to this object.
TfeTextView holds GFile corresponds to the contents of GtkTextBuffer.
It has some file manipulation functions.
## Functions
@ -67,7 +67,7 @@ tfe_text_view_open (TfeTextView *tv, GtkWidget *win);
Just shows a GtkFileChooserDialog so that a user can choose a file to read.
This function doesn't do any I/O operations.
They are done by the signal handler connected to the `response` signal emitted by GtkFileChooserDialog.
Therefore the caller can't know the I/O status directly from the function.
Therefore the caller can't know the I/O status directly from `tfe_text_view_open`.
Instead, the status is informed by `open-response` signal.
The caller needs to set a handler to this signal in advance.
@ -83,9 +83,9 @@ void
tfe_text_view_save (TfeTextView *tv);
~~~
Saves the contents of a TfeTextView to a file.
Saves the contents of the TfeTextView to a file.
If `tv` holds a GFile, it is used.
Otherwise, this function shows GtkFileChosserDialog so that a user can choose a file to save.
Otherwise, this function shows GtkFileChooserDialog so that the user can choose a file to save.
Parameters
@ -99,7 +99,7 @@ tfe_text_view_saveas (TfeTextView *tv);
~~~
Saves the content of a TfeTextView to a file.
This function shows GtkFileChosserDialog so that a user can choose a file to save.
This function shows GtkFileChooserDialog so that a user can choose a file to save.
Parameters
@ -174,6 +174,13 @@ Members:
- TFE_OPEN_RESPONSE_CANCEL: Reading file is canceled by the user.
- TFE_OPEN_RESPONSE_ERROR: An error happened during the opening or reading process.
## Properties
### wrap-mode
The property "wrap-mode" belongs to GtkTextView.
TfeTextView inherits it and the value is set to GTK\_WRAP\_WORD\_CHAR as a default.
## Signals
### change-file

View file

@ -1,7 +1,7 @@
#include <gtk/gtk.h>
static void
app_activate (GApplication *app, gpointer user_data) {
app_activate (GApplication *app) {
GtkWidget *win;
GtkWidget *tv;
GtkTextBuffer *tb;
@ -15,7 +15,7 @@ app_activate (GApplication *app, gpointer user_data) {
"He cut it, then there was a small cute baby girl in it. "
"The girl was shining faintly. "
"He thought this baby girl is a gift from Heaven and took her home.\n"
"His wife was surprized at his tale. "
"His wife was surprized at his story. "
"They were very happy because they had no children. "
;
win = gtk_application_window_new (GTK_APPLICATION (app));
@ -37,7 +37,7 @@ main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.tfv1", G_APPLICATION_FLAGS_NONE);
app = gtk_application_new ("com.github.ToshioCP.tfv1", G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
stat = g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);

View file

@ -1,7 +1,7 @@
#include <gtk/gtk.h>
static void
app_activate (GApplication *app, gpointer user_data) {
app_activate (GApplication *app) {
GtkWidget *win;
GtkWidget *scr;
GtkWidget *tv;
@ -16,7 +16,7 @@ app_activate (GApplication *app, gpointer user_data) {
"He cut it, then there was a small cute baby girl in it. "
"The girl was shining faintly. "
"He thought this baby girl is a gift from Heaven and took her home.\n"
"His wife was surprized at his tale. "
"His wife was surprized at his story. "
"They were very happy because they had no children. "
;
win = gtk_application_window_new (GTK_APPLICATION (app));
@ -41,7 +41,7 @@ main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.tfv2", G_APPLICATION_FLAGS_NONE);
app = gtk_application_new ("com.github.ToshioCP.tfv2", G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
stat = g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);

View file

@ -1,12 +1,12 @@
#include <gtk/gtk.h>
static void
app_activate (GApplication *app, gpointer user_data) {
g_print ("You need a filename argument.\n");
app_activate (GApplication *app) {
g_printerr ("You need a filename argument.\n");
}
static void
app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
app_open (GApplication *app, GFile ** files, int n_files, char *hint) {
GtkWidget *win;
GtkWidget *scr;
GtkWidget *tv;
@ -37,9 +37,10 @@ app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer
gtk_widget_show (win);
} else {
if ((filename = g_file_get_path (files[0])) != NULL) {
g_print ("No such file: %s.\n", filename);
g_printerr ("No such file: %s.\n", filename);
g_free (filename);
}
} else
g_printerr ("File can't be opened.\n");
gtk_window_destroy (GTK_WINDOW (win));
}
}

View file

@ -1,12 +1,12 @@
#include <gtk/gtk.h>
static void
app_activate (GApplication *app, gpointer user_data) {
g_print ("You need a filename argument.\n");
app_activate (GApplication *app) {
g_printerr ("You need a filename argument.\n");
}
static void
app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint) {
GtkWidget *win;
GtkWidget *nb;
GtkWidget *lab;
@ -21,8 +21,7 @@ app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer
win = gtk_application_window_new (GTK_APPLICATION (app));
gtk_window_set_title (GTK_WINDOW (win), "file viewer");
gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
gtk_window_maximize (GTK_WINDOW (win));
gtk_window_set_default_size (GTK_WINDOW (win), 600, 400);
nb = gtk_notebook_new ();
gtk_window_set_child (GTK_WINDOW (win), nb);
@ -47,10 +46,10 @@ app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer
nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
g_object_set (nbp, "tab-expand", TRUE, NULL);
} else if ((filename = g_file_get_path (files[i])) != NULL) {
g_print ("No such file: %s.\n", filename);
g_printerr ("No such file: %s.\n", filename);
g_free (filename);
} else
g_print ("No valid file is given\n");
g_printerr ("No valid file is given\n");
}
if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0)
gtk_widget_show (win);