Gtk4-tutorial/docs/sec7.html
2022-04-24 12:13:40 +09:00

366 lines
30 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

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

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<title>Gtk4 tutorial</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
pre{overflow: visible;}
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::after
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
div.sourceCode { margin: 10px; padding: 16px 10px 8px 10px; border: 2px solid silver; background-color: ghostwhite; overflow-x:scroll}
pre:not(.sourceCode) { margin: 10px; padding: 16px 10px 8px 10px; border: 2px solid silver; background-color: ghostwhite; overflow-x:scroll}
table {margin-left: auto; margin-right: auto; border-collapse: collapse; border: 1px solid;}
th {padding: 2px 6px; border: 1px solid; background-color: ghostwhite;}
td {padding: 2px 6px; border: 1px solid;}
img {display: block; margin-left: auto; margin-right: auto;}
figcaption {text-align: center;}
</style>
</head>
<body style="padding-top: 70px;">
<div class="container">
<nav class="navbar fixed-top navbar-expand-lg navbar-dark bg-primary">
<div class="container-fluid">
<span class="navbar-brand">Gtk4 tutorial</span>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="index.html">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="sec6.html">Prev: section6</a>
</li>
<li class="nav-item">
<a class="nav-link" href="sec8.html">Next: section8</a>
</li>
</ul>
</div>
</div>
</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>
<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>
<h3 id="open-signal-1">open signal</h3>
<p>Now, when the application starts, two signals can be emitted.</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>
</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>
<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>
</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>
<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>
</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>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>
</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>
<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>
</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>
<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>
</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>
<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>
<figure>
<img src="image/screenshot_gtk_notebook.png" alt="" /><figcaption>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>
<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>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>
</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>
</body>
</html>