Section 30 is added. It is about GtkSignalListItemFactory.

This commit is contained in:
Toshio Sekiya 2023-01-31 19:04:06 +09:00
parent ae15d4abbd
commit 95320ca6fb
17 changed files with 1652 additions and 6 deletions

View file

@ -82,3 +82,4 @@ There is a document \("[How to build GTK 4 Tutorial](gfm/Readme_for_developers.m
1. [GtkGridView and activate signal](gfm/sec27.md)
1. [GtkExpression](gfm/sec28.md)
1. [GtkColumnView](gfm/sec29.md)
1. [GtkSignalListItemFactory](gfm/sec30.md)

BIN
docs/image/listeditor.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View file

@ -190,6 +190,7 @@ gsettings</a></li>
<li><a href="sec27.html">GtkGridView and activate signal</a></li>
<li><a href="sec28.html">GtkExpression</a></li>
<li><a href="sec29.html">GtkColumnView</a></li>
<li><a href="sec30.html">GtkSignalListItemFactory</a></li>
</ol>
<p>This website uses <a
href="https://getbootstrap.jp/">Bootstrap</a>.</p>

View file

@ -820,7 +820,7 @@ class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span i
<span id="cb13-10"><a href="#cb13-10"></a> GtkCssProvider <span class="op">*</span>provider<span class="op">;</span></span>
<span id="cb13-11"><a href="#cb13-11"></a><span class="op">};</span></span>
<span id="cb13-12"><a href="#cb13-12"></a></span>
<span id="cb13-13"><a href="#cb13-13"></a>G_DEFINE_TYPE <span class="op">(</span>TfeApplication<span class="op">,</span> tfe_application<span class="op">,</span> GTK_TYPE_APPLICATION<span class="op">);</span></span>
<span id="cb13-13"><a href="#cb13-13"></a>G_DEFINE_TYPE <span class="op">(</span>TfeApplication<span class="op">,</span> tfe_application<span class="op">,</span> GTK_TYPE_APPLICATION<span class="op">)</span></span>
<span id="cb13-14"><a href="#cb13-14"></a></span>
<span id="cb13-15"><a href="#cb13-15"></a><span class="co">/* gsettings changed::font signal handler */</span></span>
<span id="cb13-16"><a href="#cb13-16"></a><span class="dt">static</span> <span class="dt">void</span></span>

View file

@ -103,7 +103,10 @@
<a class="nav-link" href="sec28.html">Prev: section28</a>
</li>
<li class="nav-item">
<a class="nav-link" href="sec30.html">Next: section30</a>
</li>
</ul>
</div>
</div>

429
docs/sec30.html Normal file
View file

@ -0,0 +1,429 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<title>GTK 4 tutorial</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
pre{overflow: visible;}
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::after
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
div.sourceCode { margin: 10px; padding: 16px 10px 8px 10px; border: 2px solid silver; background-color: ghostwhite; overflow-x:scroll}
pre:not(.sourceCode) { margin: 10px; padding: 16px 10px 8px 10px; border: 2px solid silver; background-color: ghostwhite; overflow-x:scroll}
table {margin-left: auto; margin-right: auto; border-collapse: collapse; border: 1px solid;}
th {padding: 2px 6px; border: 1px solid; background-color: ghostwhite;}
td {padding: 2px 6px; border: 1px solid;}
img {display: block; margin-left: auto; margin-right: auto;}
figcaption {text-align: center;}
</style>
</head>
<body style="padding-top: 70px;">
<div class="container">
<nav class="navbar fixed-top navbar-expand-lg navbar-dark bg-primary">
<div class="container-fluid">
<span class="navbar-brand">Gtk4 tutorial</span>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="index.html">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="sec29.html">Prev: section29</a>
</li>
</ul>
</div>
</div>
</nav>
<h1 id="gtksignallistitemfactory">GtkSignalListItemFactory</h1>
<h2
id="gtksignallistitemfactory-and-gtkbulderlistitemfactory">GtkSignalListItemFactory
and GtkBulderListItemFactory</h2>
<p>GtkBuilderlistItemFactory is convenient when GtkListView just shows
the contents of a list. Its binding direction is always from an item of
a list to a child of GtkListItem.</p>
<p>When it comes to dynamic connection, its not enough. For example,
you want to edit the contents of a list. You set a child of GtkListItem
to a GtkText instance so a user can edit a text with it. You need to
bind an item in the list with the buffer of the GtkText. The direction
is opposite from the one with GtkBuilderListItemFactory. It is from the
GtkText instance to the item in the list. You can implement this with
GtkSignalListItemFactory, which is more flexible than
GtkBuilderListItemFactory.</p>
<p>Two things are shown in this section.</p>
<ul>
<li>Binding from a child of a GtkListItem instance to an item of a
list.</li>
<li>Access a child of GtkListItem dynamically. This direction is the
same as the one with GtkBulderListItemFactory. But
GtkBulderListItemFactory uses GtkExpression from the item property of
the GtkListItem. So, it updates its child widget only when the item
property changes. In this example the child reflects the change in the
same item in the list dynamically.</li>
</ul>
<p>This section shows just a part of the source file
<code>listeditor.c</code>. If you want to see the whole codes, see
<code>src/listeditor</code> directory of the <a
href="https://github.com/ToshioCP/Gtk4-tutorial">Gtk4 tutorial
repository</a>.</p>
<h2 id="a-list-editor">A list editor</h2>
<p>The sample program is a list editor and data of the list are strings.
Its the same as a line editor. It reads a text file line by line. Each
line is an item of the list. The list is displayed with GtkColumnView.
There are two columns. The one is a button, which makes the line be a
current line. If the line is the current line, the button is colored
with red. The other is a string which is the contents of the
corresponding item of the list.</p>
<figure>
<img src="image/listeditor.png" alt="List editor" />
<figcaption aria-hidden="true">List editor</figcaption>
</figure>
<p>The source files are located at <code>src/listeditor</code>
directory. You can compile end execute it as follows.</p>
<ul>
<li>Download the program from the <a
href="https://github.com/ToshioCP/Gtk4-tutorial">repository</a>.</li>
<li>Change your current directory to <code>src/listeditor</code>.</li>
<li>Type the following on your commandline.</li>
</ul>
<pre><code>$ meson _build
$ ninja -C _build
$ _build/listeditor</code></pre>
<ul>
<li>Append button: appends a line after the current line, or at the last
line if no current line exists.</li>
<li>Insert button: inserts a line before the current line.</li>
<li>Remove button: removes a current line.</li>
<li>Read button: reads a file.</li>
<li>Write button: writes the contents to a file.</li>
<li>close button: close the contents.</li>
<li>quit button: quit the application.</li>
<li>Button on the select column: makes the line current.</li>
<li>String column: GtkText. You can edit a string in the field.</li>
</ul>
<p>The current line number (zero-based) is shown at the left of the tool
bar. The file name is shown at the right of the write button.</p>
<h2 id="connect-a-gtktext-instance-and-an-item-in-the-list">Connect a
GtkText instance and an item in the list</h2>
<p>The second column (GtkColumnViewColumn) sets its factory property to
GtkSignalListItemFactory. It uses three signals setup, bind and unbind.
The following is their sgnal handlers.</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>setup2_cb <span class="op">(</span>GtkListItemFactory <span class="op">*</span>factory<span class="op">,</span> GtkListItem <span class="op">*</span>listitem<span class="op">)</span> <span class="op">{</span></span>
<span id="cb2-3"><a href="#cb2-3"></a> GtkWidget <span class="op">*</span>text <span class="op">=</span> gtk_text_new <span class="op">();</span></span>
<span id="cb2-4"><a href="#cb2-4"></a> gtk_list_item_set_child <span class="op">(</span>listitem<span class="op">,</span> GTK_WIDGET <span class="op">(</span>text<span class="op">));</span></span>
<span id="cb2-5"><a href="#cb2-5"></a><span class="op">}</span></span>
<span id="cb2-6"><a href="#cb2-6"></a></span>
<span id="cb2-7"><a href="#cb2-7"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb2-8"><a href="#cb2-8"></a>bind2_cb <span class="op">(</span>GtkListItemFactory <span class="op">*</span>factory<span class="op">,</span> GtkListItem <span class="op">*</span>listitem<span class="op">)</span> <span class="op">{</span></span>
<span id="cb2-9"><a href="#cb2-9"></a> GtkWidget <span class="op">*</span>text <span class="op">=</span> gtk_list_item_get_child <span class="op">(</span>listitem<span class="op">);</span></span>
<span id="cb2-10"><a href="#cb2-10"></a> LeData <span class="op">*</span>data <span class="op">=</span> LE_DATA <span class="op">(</span>gtk_list_item_get_item <span class="op">(</span>listitem<span class="op">));</span></span>
<span id="cb2-11"><a href="#cb2-11"></a> GtkEntryBuffer <span class="op">*</span>buffer<span class="op">;</span></span>
<span id="cb2-12"><a href="#cb2-12"></a> GBinding <span class="op">*</span>bind<span class="op">;</span></span>
<span id="cb2-13"><a href="#cb2-13"></a></span>
<span id="cb2-14"><a href="#cb2-14"></a> buffer <span class="op">=</span> gtk_text_get_buffer <span class="op">(</span>GTK_TEXT <span class="op">(</span>text<span class="op">));</span></span>
<span id="cb2-15"><a href="#cb2-15"></a> gtk_entry_buffer_set_text <span class="op">(</span>buffer<span class="op">,</span> le_data_look_string <span class="op">(</span>data<span class="op">),</span> <span class="op">-</span><span class="dv">1</span><span class="op">);</span></span>
<span id="cb2-16"><a href="#cb2-16"></a> bind <span class="op">=</span> g_object_bind_property <span class="op">(</span>buffer<span class="op">,</span> <span class="st">&quot;text&quot;</span><span class="op">,</span> data<span class="op">,</span> <span class="st">&quot;string&quot;</span><span class="op">,</span> G_BINDING_DEFAULT<span class="op">);</span></span>
<span id="cb2-17"><a href="#cb2-17"></a> g_object_set_data <span class="op">(</span>G_OBJECT <span class="op">(</span>listitem<span class="op">),</span> <span class="st">&quot;bind&quot;</span><span class="op">,</span> bind<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">static</span> <span class="dt">void</span></span>
<span id="cb2-21"><a href="#cb2-21"></a>unbind2_cb <span class="op">(</span>GtkListItemFactory <span class="op">*</span>factory<span class="op">,</span> GtkListItem <span class="op">*</span>listitem<span class="op">)</span> <span class="op">{</span></span>
<span id="cb2-22"><a href="#cb2-22"></a> GBinding <span class="op">*</span>bind <span class="op">=</span> G_BINDING <span class="op">(</span>g_object_get_data <span class="op">(</span>G_OBJECT <span class="op">(</span>listitem<span class="op">),</span> <span class="st">&quot;bind&quot;</span><span class="op">));</span></span>
<span id="cb2-23"><a href="#cb2-23"></a></span>
<span id="cb2-24"><a href="#cb2-24"></a> g_binding_unbind<span class="op">(</span>bind<span class="op">);</span></span>
<span id="cb2-25"><a href="#cb2-25"></a> g_object_set_data <span class="op">(</span>G_OBJECT <span class="op">(</span>listitem<span class="op">),</span> <span class="st">&quot;bind&quot;</span><span class="op">,</span> NULL<span class="op">);</span></span>
<span id="cb2-26"><a href="#cb2-26"></a><span class="op">}</span></span></code></pre></div>
<ul>
<li>1-5: <code>setup2_cb</code> is a setup signal handler on the
GtkSignalListItemFactory. This factory is inserted to the factory
property of the second GtkColumnViewColumn. Te handler just creates a
GtkText instance and sets the child of <code>listitem</code> to it. The
instance will be destroyed automatically when the <code>listitem</code>
is destroyed. So, teardown signal handler isnt necessary.</li>
<li>7-18: <code>bind2_cb</code> is a bind signal handler. It is called
when the <code>listitem</code> is bound to an item in the list. The list
items are LeData instances. LeData is defined in the file
<code>listeditor.c</code> (the C source file of the list editor). It is
a child class of GObject and has two data. The one is
<code>listitem</code> which points a GtkListItem instance when they are
connected. If no GtkListItem is connected, it is NULL. The other is
<code>string</code> which is a content of the line.
<ul>
<li>9-10: <code>text</code> is a child of the <code>listitem</code> and
it is a GtkText instance. <code>data</code> is an item pointed by the
<code>listitem</code>. It is a LeData instance.</li>
<li>14: Gets the buffer of the <code>text</code>.</li>
<li>15: Sets the text of the buffer to
<code>le_data_look_string (data)</code>. le_data_look_string returns the
string of the data and the ownership of the string is taken by the
<code>data</code>. So, the caller dont need to free the string.</li>
<li>16: <code>g_object_bind_property</code> binds a property and another
object property. This line binds the “text” property of the
<code>buffer</code> (source) and the “string” property of the
<code>data</code> (destination). It is a uni-directional binding
(<code>G_BINDING_DEFAULT</code>). When a user changes the GtkText text,
the same string is immediately put into the <code>data</code>. The
function returns a GBinding instance. This binding is different from
bindings of GtkExpression. This binding needs the existence of the two
properties.</li>
<li>17: GObjec has a table. The key is a string (or GQuark) and the
value is a gpointer (pointer to any type). The function
<code>g_object_set_data</code> sets the association from the key to the
value. This line sets the association from “bind” to <code>bind</code>
instance on the <code>listitem</code> instance. It makes possible for
the “unbind” handler to get the <code>bind</code> instance.</li>
</ul></li>
<li>20-26: <code>unbind2_cb</code> is a unbind signal handler.
<ul>
<li>22: Retrieves the <code>bind</code> instance from the table in the
<code>listitem</code> instance.</li>
<li>24: Unbind the binding.</li>
<li>25: Removes the value corresponds to the “bind” key.</li>
</ul></li>
</ul>
<p>This technique is not so complicated. You can use it when you make a
cell editable application.</p>
<h2 id="change-the-cell-of-gtkcolumnview-dynamically">Change the cell of
GtkColumnView dynamically</h2>
<p>Next topic is to change the GtkColumnView (or GtkListView) cells
dynamically. The example changes the color of the buttons, which are
children of GtkListItem instances, as the current line position
moves.</p>
<p>The line editor has the current position of the list.</p>
<ul>
<li>At first, no line is current.</li>
<li>When a line is appended or inserted, the line is current.</li>
<li>When the current line is deleted, no line will be current.</li>
<li>When a button in the first column of GtkColumnView is clicked, the
line will be current.</li>
</ul>
<p>The button of the current line is colored with red and otherwise
white.</p>
<p>The current line has no relationship to GtkSingleSelection object.
GtkSingleSelection selects a line on the display. The current line
doesnt need to be on the display. It is possible to be on the line out
of the Window (GtkScrolledWindow). Actually, the program doesnt use
GtkSingleSelection.</p>
<p>It is necessary to know the corresponding GtkListItem instance from
the item in the list. It is the opposite direction from
<code>gtk_list_item_get_item</code> function. To accomplish this, we set
a <code>listitem</code> element of LeData to point the corresponding
GtkListItem instance. Therefore, items (LeData) in the list always know
the GtkListItem. If theres no GtkListItem binded to the item, NULL is
assigned.</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="dt">void</span></span>
<span id="cb3-2"><a href="#cb3-2"></a>select_cb <span class="op">(</span>GtkButton <span class="op">*</span>btn<span class="op">,</span> GtkListItem <span class="op">*</span>listitem<span class="op">)</span> <span class="op">{</span></span>
<span id="cb3-3"><a href="#cb3-3"></a> LeWindow <span class="op">*</span>win <span class="op">=</span> LE_WINDOW <span class="op">(</span>gtk_widget_get_ancestor <span class="op">(</span>GTK_WIDGET <span class="op">(</span>btn<span class="op">),</span> LE_TYPE_WINDOW<span class="op">));</span></span>
<span id="cb3-4"><a href="#cb3-4"></a></span>
<span id="cb3-5"><a href="#cb3-5"></a> update_current <span class="op">(</span>win<span class="op">,</span> gtk_list_item_get_position <span class="op">(</span>listitem<span class="op">));</span></span>
<span id="cb3-6"><a href="#cb3-6"></a><span class="op">}</span></span>
<span id="cb3-7"><a href="#cb3-7"></a></span>
<span id="cb3-8"><a href="#cb3-8"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb3-9"><a href="#cb3-9"></a>setup1_cb <span class="op">(</span>GtkListItemFactory <span class="op">*</span>factory<span class="op">,</span> GtkListItem <span class="op">*</span>listitem<span class="op">)</span> <span class="op">{</span></span>
<span id="cb3-10"><a href="#cb3-10"></a> GtkWidget <span class="op">*</span>button <span class="op">=</span> gtk_button_new <span class="op">();</span></span>
<span id="cb3-11"><a href="#cb3-11"></a> gtk_list_item_set_child <span class="op">(</span>listitem<span class="op">,</span> button<span class="op">);</span></span>
<span id="cb3-12"><a href="#cb3-12"></a> g_signal_connect <span class="op">(</span>button<span class="op">,</span> <span class="st">&quot;clicked&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>select_cb<span class="op">),</span> listitem<span class="op">);</span></span>
<span id="cb3-13"><a href="#cb3-13"></a><span class="op">}</span></span>
<span id="cb3-14"><a href="#cb3-14"></a></span>
<span id="cb3-15"><a href="#cb3-15"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb3-16"><a href="#cb3-16"></a>bind1_cb <span class="op">(</span>GtkListItemFactory <span class="op">*</span>factory<span class="op">,</span> GtkListItem <span class="op">*</span>listitem<span class="op">)</span> <span class="op">{</span></span>
<span id="cb3-17"><a href="#cb3-17"></a> LeData <span class="op">*</span>data <span class="op">=</span> LE_DATA <span class="op">(</span>gtk_list_item_get_item <span class="op">(</span>listitem<span class="op">));</span></span>
<span id="cb3-18"><a href="#cb3-18"></a> <span class="cf">if</span> <span class="op">(</span>data<span class="op">)</span></span>
<span id="cb3-19"><a href="#cb3-19"></a> le_data_set_listitem <span class="op">(</span>data<span class="op">,</span> listitem<span class="op">);</span></span>
<span id="cb3-20"><a href="#cb3-20"></a><span class="op">}</span></span>
<span id="cb3-21"><a href="#cb3-21"></a></span>
<span id="cb3-22"><a href="#cb3-22"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb3-23"><a href="#cb3-23"></a>unbind1_cb <span class="op">(</span>GtkListItemFactory <span class="op">*</span>factory<span class="op">,</span> GtkListItem <span class="op">*</span>listitem<span class="op">)</span> <span class="op">{</span></span>
<span id="cb3-24"><a href="#cb3-24"></a> LeData <span class="op">*</span>data <span class="op">=</span> LE_DATA <span class="op">(</span>gtk_list_item_get_item <span class="op">(</span>listitem<span class="op">));</span></span>
<span id="cb3-25"><a href="#cb3-25"></a> <span class="cf">if</span> <span class="op">(</span>data<span class="op">)</span></span>
<span id="cb3-26"><a href="#cb3-26"></a> le_data_set_listitem <span class="op">(</span>data<span class="op">,</span> NULL<span class="op">);</span></span>
<span id="cb3-27"><a href="#cb3-27"></a><span class="op">}</span></span></code></pre></div>
<ul>
<li>8-13: <code>setup1_cb</code> is a setup signal handler on the
GtkSignalListItemFactory. This factory is inserted to the factory
property of the first GtkColumnViewColumn. It sets the child of
<code>listitem</code> to a newly created GtkButton instance. The
“clicked” signal on the button is connected to the handler
<code>select_cb</code>. When the listitem is destroyed, the child
(GtkButton) is also destroyed. At the same time, the connection of the
signal and handler is also destroyed. So, you dont need teardown signal
handler.</li>
<li>1-6: <code>select_cb</code> is a “clicked” signal handler. LeWindow
is defined in <code>listeditor.c</code>. Its a child class of
GtkApplicationWindow. The handler just calls the
<code>update_current</code> function. The function will be explained
later.</li>
<li>15-20: <code>bind1_cb</code> is a bind signal handler. It sets the
“listitem” element of the item (LeData) to point the
<code>listitem</code> (GtkListItem instance). It makes the item possible
to find the corresponding GtkListItem instance.</li>
<li>22-27: <code>unbind1_cb</code> is an unbind signal handler. It
removes the <code>listitem</code> instance from the “listitem” element
of the item. The element becomes NULL, which tells no GtkListItem is
bound. When referring GtkListItem, it needs to check the “listitem”
element whether it points a GtkListItem or not (NULL). Otherwise bad
things will happen.</li>
</ul>
<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>update_current <span class="op">(</span>LeWindow <span class="op">*</span>win<span class="op">,</span> <span class="dt">int</span> new<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-3"><a href="#cb4-3"></a> <span class="dt">char</span> <span class="op">*</span>s<span class="op">;</span></span>
<span id="cb4-4"><a href="#cb4-4"></a> LeData <span class="op">*</span>data<span class="op">;</span></span>
<span id="cb4-5"><a href="#cb4-5"></a> GtkListItem <span class="op">*</span>listitem<span class="op">;</span></span>
<span id="cb4-6"><a href="#cb4-6"></a> GtkButton <span class="op">*</span>button<span class="op">;</span></span>
<span id="cb4-7"><a href="#cb4-7"></a> <span class="dt">const</span> <span class="dt">char</span> <span class="op">*</span>non_current<span class="op">[</span><span class="dv">1</span><span class="op">]</span> <span class="op">=</span> <span class="op">{</span>NULL<span class="op">};</span></span>
<span id="cb4-8"><a href="#cb4-8"></a> <span class="dt">const</span> <span class="dt">char</span> <span class="op">*</span>current<span class="op">[</span><span class="dv">2</span><span class="op">]</span> <span class="op">=</span> <span class="op">{</span><span class="st">&quot;current&quot;</span><span class="op">,</span> NULL<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>new <span class="op">&gt;=</span> <span class="dv">0</span><span class="op">)</span></span>
<span id="cb4-11"><a href="#cb4-11"></a> s <span class="op">=</span> g_strdup_printf <span class="op">(</span><span class="st">&quot;%d&quot;</span><span class="op">,</span> new<span class="op">);</span></span>
<span id="cb4-12"><a href="#cb4-12"></a> <span class="cf">else</span></span>
<span id="cb4-13"><a href="#cb4-13"></a> s <span class="op">=</span> <span class="st">&quot;&quot;</span><span class="op">;</span></span>
<span id="cb4-14"><a href="#cb4-14"></a> gtk_label_set_text <span class="op">(</span>GTK_LABEL <span class="op">(</span>win<span class="op">-&gt;</span>position_label<span class="op">),</span> s<span class="op">);</span></span>
<span id="cb4-15"><a href="#cb4-15"></a> <span class="cf">if</span> <span class="op">(*</span>s<span class="op">)</span> <span class="co">// s isn&#39;t an empty string</span></span>
<span id="cb4-16"><a href="#cb4-16"></a> g_free <span class="op">(</span>s<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="cf">if</span> <span class="op">(</span>win<span class="op">-&gt;</span>position <span class="op">&gt;=</span><span class="dv">0</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-19"><a href="#cb4-19"></a> data <span class="op">=</span> LE_DATA <span class="op">(</span>g_list_model_get_item <span class="op">(</span>G_LIST_MODEL <span class="op">(</span>win<span class="op">-&gt;</span>liststore<span class="op">),</span> win<span class="op">-&gt;</span>position<span class="op">));</span></span>
<span id="cb4-20"><a href="#cb4-20"></a> <span class="cf">if</span> <span class="op">((</span>listitem <span class="op">=</span> le_data_get_listitem <span class="op">(</span>data<span class="op">))</span> <span class="op">!=</span> NULL<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-21"><a href="#cb4-21"></a> button <span class="op">=</span> GTK_BUTTON <span class="op">(</span>gtk_list_item_get_child <span class="op">(</span>listitem<span class="op">));</span></span>
<span id="cb4-22"><a href="#cb4-22"></a> gtk_widget_set_css_classes <span class="op">(</span>GTK_WIDGET <span class="op">(</span>button<span class="op">),</span> non_current<span class="op">);</span></span>
<span id="cb4-23"><a href="#cb4-23"></a> g_object_unref <span class="op">(</span>listitem<span class="op">);</span></span>
<span id="cb4-24"><a href="#cb4-24"></a> <span class="op">}</span></span>
<span id="cb4-25"><a href="#cb4-25"></a> g_object_unref <span class="op">(</span>data<span class="op">);</span></span>
<span id="cb4-26"><a href="#cb4-26"></a> <span class="op">}</span></span>
<span id="cb4-27"><a href="#cb4-27"></a> win<span class="op">-&gt;</span>position <span class="op">=</span> new<span class="op">;</span></span>
<span id="cb4-28"><a href="#cb4-28"></a> <span class="cf">if</span> <span class="op">(</span>win<span class="op">-&gt;</span>position <span class="op">&gt;=</span><span class="dv">0</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-29"><a href="#cb4-29"></a> data <span class="op">=</span> LE_DATA <span class="op">(</span>g_list_model_get_item <span class="op">(</span>G_LIST_MODEL <span class="op">(</span>win<span class="op">-&gt;</span>liststore<span class="op">),</span> win<span class="op">-&gt;</span>position<span class="op">));</span></span>
<span id="cb4-30"><a href="#cb4-30"></a> <span class="cf">if</span> <span class="op">((</span>listitem <span class="op">=</span> le_data_get_listitem <span class="op">(</span>data<span class="op">))</span> <span class="op">!=</span> NULL<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-31"><a href="#cb4-31"></a> button <span class="op">=</span> GTK_BUTTON <span class="op">(</span>gtk_list_item_get_child <span class="op">(</span>listitem<span class="op">));</span></span>
<span id="cb4-32"><a href="#cb4-32"></a> gtk_widget_set_css_classes <span class="op">(</span>GTK_WIDGET <span class="op">(</span>button<span class="op">),</span> current<span class="op">);</span></span>
<span id="cb4-33"><a href="#cb4-33"></a> g_object_unref <span class="op">(</span>listitem<span class="op">);</span></span>
<span id="cb4-34"><a href="#cb4-34"></a> <span class="op">}</span></span>
<span id="cb4-35"><a href="#cb4-35"></a> g_object_unref <span class="op">(</span>data<span class="op">);</span></span>
<span id="cb4-36"><a href="#cb4-36"></a> <span class="op">}</span></span>
<span id="cb4-37"><a href="#cb4-37"></a><span class="op">}</span></span></code></pre></div>
<p>The function <code>update_current</code> does several things.</p>
<ul>
<li>It has two parameters. The first one is <code>win</code>, which is
an instance of LeWindow class. It has some elements.
<ul>
<li>win-&gt;position: an Integer. it is the current position. If no
current line exists, it is -1.</li>
<li>win-&gt;position_label: GtkLabel. It shows the current
position.</li>
</ul></li>
<li>The second parameter is <code>new</code>, which is the new current
position. At the beginning of the function, win-&gt;position points the
old position.</li>
<li>10-16: Update the text of GtkLabel.</li>
<li>18-26: If the old position (win-&gt;position) is not negative, the
current line exists. It gets a GtkListItem instance via the item
(LeData) of the list. And it gets the GtkButton instance which is the
child of the GtkListItem. It clears the “css-classes” property of the
button.</li>
<li>27: Updates win-&gt;position.</li>
<li>28-36: If the new position is not negative (Its possible to be
negative when the current line has been removed), the current line
exists. It sets the “css-classes” property of the button to
<code>{"current", NULL}</code>. It is a NULL-terminated array of
strings. Each string is a CSS class. Now the button has “current” style
class.</li>
</ul>
<p>The color of buttons are determined by the “background” CSS style.
The following CSS is applied to the default GdkDisplay in advance (in
the startup handler of the application).</p>
<div class="sourceCode" id="cb5"><pre
class="sourceCode css"><code class="sourceCode css"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a>columnview listview row button<span class="fu">.current</span> {<span class="kw">background</span>: <span class="cn">red</span><span class="op">;</span>}</span></code></pre></div>
<p>The selectors “columnview listview row” is needed before “button”
selector. Otherwise the buttons in the GtkColumnview wont be found. The
button selector has “current” class. So, the only “current” class button
is colored with red. Other buttons are not colored, which means they are
white.</p>
<h2
id="gtk_widget_dispose_template-function">Gtk_widget_dispose_template
function</h2>
<p>The function <code>gtk_widget_dispose_template</code> clears the
template children for the given widget. This is the opposite of
<code>gtk_widget_init_template()</code>. It is a new function of GTK 4.8
version. If your GTK version is lower than 4.8, you need to modify the
program.</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>
</html>

View file

@ -713,7 +713,7 @@ It defines the application and supports:
10 GtkCssProvider *provider;
11 };
12
13 G_DEFINE_TYPE (TfeApplication, tfe_application, GTK_TYPE_APPLICATION);
13 G_DEFINE_TYPE (TfeApplication, tfe_application, GTK_TYPE_APPLICATION)
14
15 /* gsettings changed::font signal handler */
16 static void

View file

@ -1,4 +1,4 @@
Up: [README.md](../README.md), Prev: [Section 28](sec28.md)
Up: [README.md](../README.md), Prev: [Section 28](sec28.md), Next: [Section 30](sec30.md)
# GtkColumnView
@ -471,4 +471,4 @@ If you click the header of another column, then the whole lists are sorted by th
GtkColumnView is very useful and it can manage very big GListModel.
It is possible to use it for file list, application list, database frontend and so on.
Up: [README.md](../README.md), Prev: [Section 28](sec28.md)
Up: [README.md](../README.md), Prev: [Section 28](sec28.md), Next: [Section 30](sec30.md)

303
gfm/sec30.md Normal file
View file

@ -0,0 +1,303 @@
Up: [README.md](../README.md), Prev: [Section 29](sec29.md)
# GtkSignalListItemFactory
## GtkSignalListItemFactory and GtkBulderListItemFactory
GtkBuilderlistItemFactory is convenient when GtkListView just shows the contents of a list.
Its binding direction is always from an item of a list to a child of GtkListItem.
When it comes to dynamic connection, it's not enough.
For example, you want to edit the contents of a list.
You set a child of GtkListItem to a GtkText instance so a user can edit a text with it.
You need to bind an item in the list with the buffer of the GtkText.
The direction is opposite from the one with GtkBuilderListItemFactory.
It is from the GtkText instance to the item in the list.
You can implement this with GtkSignalListItemFactory, which is more flexible than GtkBuilderListItemFactory.
Two things are shown in this section.
- Binding from a child of a GtkListItem instance to an item of a list.
- Access a child of GtkListItem dynamically.
This direction is the same as the one with GtkBulderListItemFactory.
But GtkBulderListItemFactory uses GtkExpression from the item property of the GtkListItem.
So, it updates its child widget only when the item property changes.
In this example the child reflects the change in the same item in the list dynamically.
This section shows just a part of the source file `listeditor.c`.
If you want to see the whole codes, see `src/listeditor` directory of the [Gtk4 tutorial repository](https://github.com/ToshioCP/Gtk4-tutorial).
## A list editor
The sample program is a list editor and data of the list are strings.
It's the same as a line editor.
It reads a text file line by line.
Each line is an item of the list.
The list is displayed with GtkColumnView.
There are two columns.
The one is a button, which makes the line be a current line.
If the line is the current line, the button is colored with red.
The other is a string which is the contents of the corresponding item of the list.
![List editor](../image/listeditor.png)
The source files are located at `src/listeditor` directory.
You can compile end execute it as follows.
- Download the program from the [repository](https://github.com/ToshioCP/Gtk4-tutorial).
- Change your current directory to `src/listeditor`.
- Type the following on your commandline.
~~~
$ meson _build
$ ninja -C _build
$ _build/listeditor
~~~
- Append button: appends a line after the current line, or at the last line if no current line exists.
- Insert button: inserts a line before the current line.
- Remove button: removes a current line.
- Read button: reads a file.
- Write button: writes the contents to a file.
- close button: close the contents.
- quit button: quit the application.
- Button on the select column: makes the line current.
- String column: GtkText. You can edit a string in the field.
The current line number (zero-based) is shown at the left of the tool bar.
The file name is shown at the right of the write button.
## Connect a GtkText instance and an item in the list
The second column (GtkColumnViewColumn) sets its factory property to GtkSignalListItemFactory.
It uses three signals setup, bind and unbind.
The following is their sgnal handlers.
~~~C
1 static void
2 setup2_cb (GtkListItemFactory *factory, GtkListItem *listitem) {
3 GtkWidget *text = gtk_text_new ();
4 gtk_list_item_set_child (listitem, GTK_WIDGET (text));
5 }
6
7 static void
8 bind2_cb (GtkListItemFactory *factory, GtkListItem *listitem) {
9 GtkWidget *text = gtk_list_item_get_child (listitem);
10 LeData *data = LE_DATA (gtk_list_item_get_item (listitem));
11 GtkEntryBuffer *buffer;
12 GBinding *bind;
13
14 buffer = gtk_text_get_buffer (GTK_TEXT (text));
15 gtk_entry_buffer_set_text (buffer, le_data_look_string (data), -1);
16 bind = g_object_bind_property (buffer, "text", data, "string", G_BINDING_DEFAULT);
17 g_object_set_data (G_OBJECT (listitem), "bind", bind);
18 }
19
20 static void
21 unbind2_cb (GtkListItemFactory *factory, GtkListItem *listitem) {
22 GBinding *bind = G_BINDING (g_object_get_data (G_OBJECT (listitem), "bind"));
23
24 g_binding_unbind(bind);
25 g_object_set_data (G_OBJECT (listitem), "bind", NULL);
26 }
~~~
- 1-5: `setup2_cb` is a setup signal handler on the GtkSignalListItemFactory.
This factory is inserted to the factory property of the second GtkColumnViewColumn.
Te handler just creates a GtkText instance and sets the child of `listitem` to it.
The instance will be destroyed automatically when the `listitem` is destroyed.
So, teardown signal handler isn't necessary.
- 7-18: `bind2_cb` is a bind signal handler.
It is called when the `listitem` is bound to an item in the list.
The list items are LeData instances.
LeData is defined in the file `listeditor.c` (the C source file of the list editor).
It is a child class of GObject and has two data.
The one is `listitem` which points a GtkListItem instance when they are connected.
If no GtkListItem is connected, it is NULL.
The other is `string` which is a content of the line.
- 9-10: `text` is a child of the `listitem` and it is a GtkText instance.
`data` is an item pointed by the `listitem`. It is a LeData instance.
- 14: Gets the buffer of the `text`.
- 15: Sets the text of the buffer to `le_data_look_string (data)`.
le\_data\_look\_string returns the string of the data and the ownership of the string is taken by the `data`.
So, the caller don't need to free the string.
- 16: `g_object_bind_property` binds a property and another object property.
This line binds the "text" property of the `buffer` (source) and the "string" property of the `data` (destination).
It is a uni-directional binding (`G_BINDING_DEFAULT`).
When a user changes the GtkText text, the same string is immediately put into the `data`.
The function returns a GBinding instance.
This binding is different from bindings of GtkExpression.
This binding needs the existence of the two properties.
- 17: GObjec has a table.
The key is a string (or GQuark) and the value is a gpointer (pointer to any type).
The function `g_object_set_data` sets the association from the key to the value.
This line sets the association from "bind" to `bind` instance on the `listitem` instance.
It makes possible for the "unbind" handler to get the `bind` instance.
- 20-26: `unbind2_cb` is a unbind signal handler.
- 22: Retrieves the `bind` instance from the table in the `listitem` instance.
- 24: Unbind the binding.
- 25: Removes the value corresponds to the "bind" key.
This technique is not so complicated.
You can use it when you make a cell editable application.
## Change the cell of GtkColumnView dynamically
Next topic is to change the GtkColumnView (or GtkListView) cells dynamically.
The example changes the color of the buttons, which are children of GtkListItem instances, as the current line position moves.
The line editor has the current position of the list.
- At first, no line is current.
- When a line is appended or inserted, the line is current.
- When the current line is deleted, no line will be current.
- When a button in the first column of GtkColumnView is clicked, the line will be current.
The button of the current line is colored with red and otherwise white.
The current line has no relationship to GtkSingleSelection object.
GtkSingleSelection selects a line on the display.
The current line doesn't need to be on the display.
It is possible to be on the line out of the Window (GtkScrolledWindow).
Actually, the program doesn't use GtkSingleSelection.
It is necessary to know the corresponding GtkListItem instance from the item in the list.
It is the opposite direction from `gtk_list_item_get_item` function.
To accomplish this, we set a `listitem` element of LeData to point the corresponding GtkListItem instance.
Therefore, items (LeData) in the list always know the GtkListItem.
If there's no GtkListItem binded to the item, NULL is assigned.
~~~C
1 void
2 select_cb (GtkButton *btn, GtkListItem *listitem) {
3 LeWindow *win = LE_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (btn), LE_TYPE_WINDOW));
4
5 update_current (win, gtk_list_item_get_position (listitem));
6 }
7
8 static void
9 setup1_cb (GtkListItemFactory *factory, GtkListItem *listitem) {
10 GtkWidget *button = gtk_button_new ();
11 gtk_list_item_set_child (listitem, button);
12 g_signal_connect (button, "clicked", G_CALLBACK (select_cb), listitem);
13 }
14
15 static void
16 bind1_cb (GtkListItemFactory *factory, GtkListItem *listitem) {
17 LeData *data = LE_DATA (gtk_list_item_get_item (listitem));
18 if (data)
19 le_data_set_listitem (data, listitem);
20 }
21
22 static void
23 unbind1_cb (GtkListItemFactory *factory, GtkListItem *listitem) {
24 LeData *data = LE_DATA (gtk_list_item_get_item (listitem));
25 if (data)
26 le_data_set_listitem (data, NULL);
27 }
~~~
- 8-13: `setup1_cb` is a setup signal handler on the GtkSignalListItemFactory.
This factory is inserted to the factory property of the first GtkColumnViewColumn.
It sets the child of `listitem` to a newly created GtkButton instance.
The "clicked" signal on the button is connected to the handler `select_cb`.
When the listitem is destroyed, the child (GtkButton) is also destroyed.
At the same time, the connection of the signal and handler is also destroyed.
So, you don't need teardown signal handler.
- 1-6: `select_cb` is a "clicked" signal handler.
LeWindow is defined in `listeditor.c`.
It's a child class of GtkApplicationWindow.
The handler just calls the `update_current` function.
The function will be explained later.
- 15-20: `bind1_cb` is a bind signal handler.
It sets the "listitem" element of the item (LeData) to point the `listitem` (GtkListItem instance).
It makes the item possible to find the corresponding GtkListItem instance.
- 22-27: `unbind1_cb` is an unbind signal handler.
It removes the `listitem` instance from the "listitem" element of the item.
The element becomes NULL, which tells no GtkListItem is bound.
When referring GtkListItem, it needs to check the "listitem" element whether it points a GtkListItem or not (NULL).
Otherwise bad things will happen.
~~~C
1 static void
2 update_current (LeWindow *win, int new) {
3 char *s;
4 LeData *data;
5 GtkListItem *listitem;
6 GtkButton *button;
7 const char *non_current[1] = {NULL};
8 const char *current[2] = {"current", NULL};
9
10 if (new >= 0)
11 s = g_strdup_printf ("%d", new);
12 else
13 s = "";
14 gtk_label_set_text (GTK_LABEL (win->position_label), s);
15 if (*s) // s isn't an empty string
16 g_free (s);
17
18 if (win->position >=0) {
19 data = LE_DATA (g_list_model_get_item (G_LIST_MODEL (win->liststore), win->position));
20 if ((listitem = le_data_get_listitem (data)) != NULL) {
21 button = GTK_BUTTON (gtk_list_item_get_child (listitem));
22 gtk_widget_set_css_classes (GTK_WIDGET (button), non_current);
23 g_object_unref (listitem);
24 }
25 g_object_unref (data);
26 }
27 win->position = new;
28 if (win->position >=0) {
29 data = LE_DATA (g_list_model_get_item (G_LIST_MODEL (win->liststore), win->position));
30 if ((listitem = le_data_get_listitem (data)) != NULL) {
31 button = GTK_BUTTON (gtk_list_item_get_child (listitem));
32 gtk_widget_set_css_classes (GTK_WIDGET (button), current);
33 g_object_unref (listitem);
34 }
35 g_object_unref (data);
36 }
37 }
~~~
The function `update_current` does several things.
- It has two parameters.
The first one is `win`, which is an instance of LeWindow class.
It has some elements.
- win->position: an Integer. it is the current position. If no current line exists, it is -1.
- win->position_label: GtkLabel. It shows the current position.
- The second parameter is `new`, which is the new current position.
At the beginning of the function, win->position points the old position.
- 10-16: Update the text of GtkLabel.
- 18-26: If the old position (win->position) is not negative, the current line exists.
It gets a GtkListItem instance via the item (LeData) of the list.
And it gets the GtkButton instance which is the child of the GtkListItem.
It clears the "css-classes" property of the button.
- 27: Updates win->position.
- 28-36: If the new position is not negative (It's possible to be negative when the current line has been removed), the current line exists.
It sets the "css-classes" property of the button to `{"current", NULL}`.
It is a NULL-terminated array of strings.
Each string is a CSS class.
Now the button has "current" style class.
The color of buttons are determined by the "background" CSS style.
The following CSS is applied to the default GdkDisplay in advance (in the startup handler of the application).
~~~css
columnview listview row button.current {background: red;}
~~~
The selectors "columnview listview row" is needed before "button" selector.
Otherwise the buttons in the GtkColumnview won't be found.
The button selector has "current" class.
So, the only "current" class button is colored with red.
Other buttons are not colored, which means they are white.
## Gtk\_widget\_dispose\_template function
The function `gtk_widget_dispose_template` clears the template children for the given widget.
This is the opposite of `gtk_widget_init_template()`.
It is a new function of GTK 4.8 version.
If your GTK version is lower than 4.8, you need to modify the program.
Up: [README.md](../README.md), Prev: [Section 29](sec29.md)

BIN
image/listeditor.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View file

@ -0,0 +1,17 @@
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [],
"compilerPath": "/usr/bin/gcc",
"cStandard": "gnu17",
"cppStandard": "gnu++17",
"intelliSenseMode": "linux-gcc-x64",
"compileCommands": "${workspaceFolder}/_build/compile_commands.json"
}
],
"version": 4
}

530
src/listeditor/listeditor.c Normal file
View file

@ -0,0 +1,530 @@
#include <gtk/gtk.h>
#define LE_TYPE_DATA (le_data_get_type ())
G_DECLARE_FINAL_TYPE (LeData, le_data, LE, DATA, GObject)
enum {
PROP_0,
PROP_STRING,
N_PROPERTIES
};
static GParamSpec *ledata_properties[N_PROPERTIES] = {NULL, };
typedef struct _LeData {
GObject parent;
GtkListItem *listitem;
char *string;
} LeData;
G_DEFINE_TYPE(LeData, le_data, G_TYPE_OBJECT)
static void
le_data_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) {
LeData *self = LE_DATA (object);
if (property_id == PROP_STRING)
self->string = g_strdup (g_value_get_string (value));
else
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
le_data_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) {
LeData *self = LE_DATA (object);
if (property_id == PROP_STRING)
g_value_set_string (value, g_strdup (self->string));
else
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
le_data_init (LeData *self) {
self->listitem = NULL;
self->string = NULL;
}
static void
le_data_dispose (GObject *object) {
LeData *self = LE_DATA (object);
if (self->listitem)
g_clear_object (&self->listitem);
G_OBJECT_CLASS (le_data_parent_class)->dispose (object);
}
static void
le_data_finalize (GObject *object) {
LeData *self = LE_DATA (object);
if (self->string)
g_free (self->string);
G_OBJECT_CLASS (le_data_parent_class)->finalize (object);
}
static void
le_data_class_init (LeDataClass *class) {
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
gobject_class->finalize = le_data_dispose;
gobject_class->finalize = le_data_finalize;
gobject_class->set_property = le_data_set_property;
gobject_class->get_property = le_data_get_property;
ledata_properties[PROP_STRING] = g_param_spec_string ("string", "string", "string", "", G_PARAM_READWRITE);
g_object_class_install_properties (gobject_class,N_PROPERTIES, ledata_properties);
}
/* setter and getter */
void
le_data_set_listitem (LeData *self, GtkListItem *listitem) {
g_return_if_fail (GTK_IS_LIST_ITEM (listitem) || listitem == NULL);
if (self->listitem)
g_object_unref (self->listitem);
self->listitem = listitem ? g_object_ref (listitem) : NULL;
}
void
le_data_set_string (LeData *self, const char *string) {
if (self->string)
g_free (self->string);
self->string = g_strdup (string);
}
void
le_data_take_string (LeData *self, char *string) {
if (self->string)
g_free (self->string);
self->string = string;
}
GtkListItem *
le_data_get_listitem (LeData *self) {
return g_object_ref (self->listitem);
}
GtkListItem *
le_data_look_listitem (LeData *self) {
return self->listitem;
}
char *
le_data_get_string (LeData *self) {
return g_strdup (self->string);
}
const char *
le_data_look_string (LeData *self) {
return self->string;
}
LeData *
le_data_new_with_data (GtkListItem *listitem, const char *string) {
g_return_val_if_fail (GTK_IS_LIST_ITEM (listitem) || listitem == NULL, NULL);
LeData *data;
data = LE_DATA (g_object_new (LE_TYPE_DATA, NULL));
data->listitem = listitem ? g_object_ref (listitem) : NULL;
data->string = g_strdup (string);
return data;
}
LeData *
le_data_new (void) {
return LE_DATA (g_object_new (LE_TYPE_DATA, NULL));
}
/* ----- definition of LeWindow ----- */
#define LE_TYPE_WINDOW (le_window_get_type ())
G_DECLARE_FINAL_TYPE (LeWindow, le_window, LE, WINDOW, GtkApplicationWindow)
typedef struct _LeWindow {
GtkApplicationWindow parent;
int position; /* current position */
GFile *file;
GtkWidget *position_label;
GtkWidget *filename;
GtkWidget *columnview;
GListStore *liststore;
} LeWindow;
G_DEFINE_TYPE (LeWindow, le_window, GTK_TYPE_APPLICATION_WINDOW)
static void
update_current (LeWindow *win, int new) {
char *s;
LeData *data;
GtkListItem *listitem;
GtkButton *button;
const char *non_current[1] = {NULL};
const char *current[2] = {"current", NULL};
if (new >= 0)
s = g_strdup_printf ("%d", new);
else
s = "";
gtk_label_set_text (GTK_LABEL (win->position_label), s);
if (*s) // s isn't an empty string
g_free (s);
if (win->position >=0) {
data = LE_DATA (g_list_model_get_item (G_LIST_MODEL (win->liststore), win->position));
if ((listitem = le_data_get_listitem (data)) != NULL) {
button = GTK_BUTTON (gtk_list_item_get_child (listitem));
gtk_widget_set_css_classes (GTK_WIDGET (button), non_current);
g_object_unref (listitem);
}
g_object_unref (data);
}
win->position = new;
if (win->position >=0) {
data = LE_DATA (g_list_model_get_item (G_LIST_MODEL (win->liststore), win->position));
if ((listitem = le_data_get_listitem (data)) != NULL) {
button = GTK_BUTTON (gtk_list_item_get_child (listitem));
gtk_widget_set_css_classes (GTK_WIDGET (button), current);
g_object_unref (listitem);
}
g_object_unref (data);
}
}
/* ----- Button "clicled" signal handlers ----- */
static void
app_cb (GtkButton *btn, LeWindow *win) {
LeData *data;
data = le_data_new_with_data (NULL, "");
if (win->position >= 0) {
g_list_store_insert (win->liststore, win->position + 1, data);
update_current (win, win->position + 1);
} else {
g_list_store_append (win->liststore, data);
update_current (win, g_list_model_get_n_items (G_LIST_MODEL (win->liststore)) - 1);
}
g_object_unref (data);
}
static void
ins_cb (GtkButton *btn, LeWindow *win) {
LeData *data;
data = le_data_new_with_data (NULL, "");
if (win->position >= 0) {
g_list_store_insert (win->liststore, win->position, data);
win->position += 1;
update_current (win, win->position - 1);
}
g_object_unref (data);
}
static void
rm_cb (GtkButton *btn, LeWindow *win) {
if (win->position >= 0) {
g_list_store_remove (win->liststore, win->position);
win->position = -1;
update_current (win, -1);
}
}
static void
open_dialog_response(GtkWidget *dialog, gint response, LeWindow *win) {
GFile *file;
char *contents;
char *s;
gsize length;
GFileInputStream *stream;
GDataInputStream *dstream;
GError *err = NULL;
LeData *data;
if (response == GTK_RESPONSE_ACCEPT
&& G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)))
&& g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) {
if (! (stream = g_file_read (file, NULL, &err))) {
g_warning ("%s\n", err->message);
g_error_free (err);
return;
}
dstream = g_data_input_stream_new (G_INPUT_STREAM (stream));
g_object_unref (stream);
while ((contents = g_data_input_stream_read_line_utf8 (dstream, &length, NULL, &err)) != NULL) {
data = le_data_new_with_data (NULL, contents);
g_free (contents);
g_list_store_append (win->liststore, data);
g_object_unref (data);
}
if (err) {
g_warning ("%s\n", err->message);
if (g_list_model_get_n_items(G_LIST_MODEL (win->liststore)) > 0)
g_list_store_remove_all (win->liststore);
return;
} else if (! g_input_stream_close (G_INPUT_STREAM (dstream), NULL, &err)) { /* EOF */
g_warning ("%s\n", err->message);
g_error_free (err);
return;
}
win->file = file; /* win->file is NULL (has already checked) and it take the ownership of 'file' */
s = g_file_get_basename (file);
gtk_label_set_text (GTK_LABEL (win->filename), s);
g_free (s);
update_current (win, -1);
}
gtk_window_destroy (GTK_WINDOW (dialog));
}
static void
read_cb (GtkButton *btn, LeWindow *win) {
GtkWidget *dialog;
if (win->file || g_list_model_get_n_items (G_LIST_MODEL (win->liststore)) > 0)
return;
dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN,
"Cancel", GTK_RESPONSE_CANCEL,
"Open", GTK_RESPONSE_ACCEPT,
NULL);
g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), win);
gtk_window_present (GTK_WINDOW (dialog));
}
static void
write_data (LeWindow *win) {
GFileOutputStream *ostream;
gssize size;
gsize length;
LeData *data;
int i, n_items;
GError *err = NULL;
if (! (ostream = g_file_replace (win->file, NULL, TRUE, G_FILE_CREATE_NONE, NULL, &err))) {
g_warning ("%s\n", err->message);
g_error_free (err);
return;
}
n_items = g_list_model_get_n_items (G_LIST_MODEL (win->liststore));
for (i=0; i<n_items; ++i) {
data = LE_DATA (g_list_model_get_item (G_LIST_MODEL (win->liststore), i));
length = (gsize) strlen (le_data_look_string (data));
size = g_output_stream_write (G_OUTPUT_STREAM (ostream), le_data_look_string (data), length, NULL, &err);
g_object_unref (data);
if (size < 0) {
g_warning ("%s\n", err->message);
g_error_free (err);
break;
}
size = g_output_stream_write (G_OUTPUT_STREAM (ostream), "\n", 1, NULL, &err);
if (size < 0) {
g_warning ("%s\n", err->message);
g_error_free (err);
break;
}
}
if (! (g_output_stream_close (G_OUTPUT_STREAM (ostream), NULL, &err))) {
g_warning ("%s\n", err->message);
g_error_free (err);
}
}
static void
saveas_dialog_response (GtkWidget *dialog, gint response, LeWindow *win) {
GFile *file;
char *s;
if (response == GTK_RESPONSE_ACCEPT) {
file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
if (G_IS_FILE (file)) {
win->file = file; /* the ownership is taken by win */
s = g_file_get_basename (file);
gtk_label_set_text (GTK_LABEL (win->filename), s);
g_free (s);
write_data (win);
}
else {
g_warning ("gtk_file_chooser_get_file returns non GFile.\n");
}
}
gtk_window_destroy (GTK_WINDOW (dialog));
}
static void
show_saveas_dialog (LeWindow *win) {
GtkWidget *dialog;
dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
"Cancel", GTK_RESPONSE_CANCEL,
"Save", GTK_RESPONSE_ACCEPT,
NULL);
g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), win);
gtk_widget_show (dialog);
}
static void
write_cb (GtkButton *btn, LeWindow *win) {
if (g_list_model_get_n_items (G_LIST_MODEL (win->liststore)) == 0)
return;
if (win->file)
write_data (win);
else
show_saveas_dialog (win);
}
static void
close_cb (GtkButton *btn, LeWindow *win) {
if (g_list_model_get_n_items (G_LIST_MODEL (win->liststore)) > 0)
g_list_store_remove_all (win->liststore);
if (win->file) {
g_clear_object (&win->file);
gtk_label_set_text (GTK_LABEL (win->filename), "");
}
win->position = -1;
gtk_label_set_text (GTK_LABEL (win->position_label), "");
}
static void
quit_cb (GtkButton *btn, LeWindow *win) {
GtkApplication *app = gtk_window_get_application (GTK_WINDOW (win));
g_application_quit (G_APPLICATION (app));
}
void
select_cb (GtkButton *btn, GtkListItem *listitem) {
LeWindow *win = LE_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (btn), LE_TYPE_WINDOW));
update_current (win, gtk_list_item_get_position (listitem));
}
/* ----- Handlers on GtkSignalListItemFacory ----- */
static void
setup1_cb (GtkListItemFactory *factory, GtkListItem *listitem) {
GtkWidget *button = gtk_button_new ();
gtk_list_item_set_child (listitem, button);
g_signal_connect (button, "clicked", G_CALLBACK (select_cb), listitem);
}
static void
bind1_cb (GtkListItemFactory *factory, GtkListItem *listitem) {
LeData *data = LE_DATA (gtk_list_item_get_item (listitem));
if (data)
le_data_set_listitem (data, listitem);
}
static void
unbind1_cb (GtkListItemFactory *factory, GtkListItem *listitem) {
LeData *data = LE_DATA (gtk_list_item_get_item (listitem));
if (data)
le_data_set_listitem (data, NULL);
}
static void
setup2_cb (GtkListItemFactory *factory, GtkListItem *listitem) {
GtkWidget *text = gtk_text_new ();
gtk_list_item_set_child (listitem, GTK_WIDGET (text));
}
static void
bind2_cb (GtkListItemFactory *factory, GtkListItem *listitem) {
GtkWidget *text = gtk_list_item_get_child (listitem);
LeData *data = LE_DATA (gtk_list_item_get_item (listitem));
GtkEntryBuffer *buffer;
GBinding *bind;
buffer = gtk_text_get_buffer (GTK_TEXT (text));
gtk_entry_buffer_set_text (buffer, le_data_look_string (data), -1);
bind = g_object_bind_property (buffer, "text", data, "string", G_BINDING_DEFAULT);
g_object_set_data (G_OBJECT (listitem), "bind", bind);
}
static void
unbind2_cb (GtkListItemFactory *factory, GtkListItem *listitem) {
GBinding *bind = G_BINDING (g_object_get_data (G_OBJECT (listitem), "bind"));
g_binding_unbind(bind);
g_object_set_data (G_OBJECT (listitem), "bind", NULL);
}
static void
le_window_init (LeWindow *win) {
gtk_widget_init_template (GTK_WIDGET (win));
win->position = -1;
win->file =NULL;
}
static void
le_window_dispose (GObject *object) {
LeWindow *win = LE_WINDOW (object);
/* this function is available since GTK 4.8 */
gtk_widget_dispose_template (GTK_WIDGET (win), LE_TYPE_WINDOW);
/* chain to the parent */
G_OBJECT_CLASS (le_window_parent_class)->dispose (object);
}
static void
le_window_class_init (LeWindowClass *class) {
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
gobject_class->dispose = le_window_dispose;
gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class), "/com/github/ToshioCP/listeditor/listeditor.ui");
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), LeWindow, position_label);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), LeWindow, filename);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), LeWindow, columnview);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), LeWindow, liststore);
/* The followint macros are available since GTK 4.8 */
gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), app_cb);
gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), ins_cb);
gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), rm_cb);
gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), read_cb);
gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), write_cb);
gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), close_cb);
gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), quit_cb);
gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), setup1_cb);
gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), bind1_cb);
gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), unbind1_cb);
gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), setup2_cb);
gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), bind2_cb);
gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), unbind2_cb);
}
GtkWidget *
le_window_new (GtkApplication *app) {
g_return_val_if_fail (GTK_IS_APPLICATION (app), NULL);
return GTK_WIDGET (g_object_new (LE_TYPE_WINDOW, "application", app, NULL));
}
/* ----- activate, startup handlers ----- */
static void
app_activate (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
gtk_window_present (gtk_application_get_active_window(app));
}
static void
app_startup (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
GtkCssProvider *provider;
le_window_new (app);
provider = gtk_css_provider_new ();
gtk_css_provider_load_from_data (provider, "text:focus {border: 1px solid gray;} columnview listview row button.current {background: red;}", -1);
gtk_style_context_add_provider_for_display (gdk_display_get_default (),
GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
g_object_unref (provider);
}
#define APPLICATION_ID "com.github.ToshioCP.listeditor"
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/com/github/ToshioCP/listeditor">
<file>listeditor.ui</file>
</gresource>
</gresources>

View file

@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="LeWindow" parent="GtkApplicationWindow">
<property name="title">list editor</property>
<property name="default-width">600</property>
<property name="default-height">400</property>
<child>
<object class="GtkBox">
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<child>
<object class="GtkBox">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<child>
<object class="GtkLabel">
<property name="width-chars">10</property>
</object>
</child>
<child>
<object class="GtkLabel" id="position_label">
<property name="width-chars">8</property>
</object>
</child>
<child>
<object class="GtkButton">
<property name="label">Append</property>
<signal name="clicked" handler="app_cb" swapped="false" object="LeWindow"></signal>
</object>
</child>
<child>
<object class="GtkButton">
<property name="label">Insert</property>
<signal name="clicked" handler="ins_cb" swapped="false" object="LeWindow"></signal>
</object>
</child>
<child>
<object class="GtkButton">
<property name="label">Remove</property>
<signal name="clicked" handler="rm_cb" swapped="false" object="LeWindow"></signal>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="width-chars">10</property>
</object>
</child>
<child>
<object class="GtkButton">
<property name="label">Read</property>
<signal name="clicked" handler="read_cb" swapped="false" object="LeWindow"></signal>
</object>
</child>
<child>
<object class="GtkButton">
<property name="label">Write</property>
<signal name="clicked" handler="write_cb" swapped="false" object="LeWindow"></signal>
</object>
</child>
<child>
<object class="GtkLabel" id="filename">
<property name="width-chars">10</property>
<property name='ellipsize'>PANGO_ELLIPSIZE_END</property>"
</object>
</child>
<child>
<object class="GtkButton">
<property name="label">Close</property>
<signal name="clicked" handler="close_cb" swapped="false" object="LeWindow"></signal>
</object>
</child>
<child>
<object class="GtkButton">
<property name="label">Quit</property>
<signal name="clicked" handler="quit_cb" swapped="false" object="LeWindow"></signal>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="hexpand">TRUE</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="hexpand">TRUE</property>
<property name="vexpand">TRUE</property>
<child>
<object class="GtkColumnView" id="columnview">
<property name="hexpand">TRUE</property>
<property name="vexpand">TRUE</property>
<property name="focusable">FALSE</property>
<property name="model">
<object class="GtkNoSelection">
<property name="model">
<object class="GListStore" id="liststore">
<property name="item-type">LeData</property>
</object>
</property>
</object>
</property>
<child>
<object class="GtkColumnViewColumn">
<property name="title">Select</property>
<property name="factory">
<object class="GtkSignalListItemFactory">
<signal name="setup" handler="setup1_cb"></signal>
<signal name="bind" handler="bind1_cb"></signal>
<signal name="unbind" handler="unbind1_cb"></signal>
</object>
</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn">
<property name="title">String</property>
<property name="factory">
<object class="GtkSignalListItemFactory">
<signal name="setup" handler="setup2_cb"></signal>
<signal name="bind" handler="bind2_cb"></signal>
<signal name="unbind" handler="unbind2_cb"></signal>
</object>
</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>

View file

@ -0,0 +1,11 @@
project('csv', 'c')
glibdep = dependency('glib-2.0', version: '>=2.68.0')
gtkdep = dependency('gtk4', version: '>=4.8')
gnome=import('gnome')
resources = gnome.compile_resources('resources','listeditor.gresource.xml')
#sourcefiles=files('listeditor.c', 'tcsvapplication.c')
executable('listeditor', ['listeditor.c'], resources, dependencies: gtkdep, export_dynamic: true, install: false)

212
src/sec30.src.md Normal file
View file

@ -0,0 +1,212 @@
# GtkSignalListItemFactory
## GtkSignalListItemFactory and GtkBulderListItemFactory
GtkBuilderlistItemFactory is convenient when GtkListView just shows the contents of a list.
Its binding direction is always from an item of a list to a child of GtkListItem.
When it comes to dynamic connection, it's not enough.
For example, you want to edit the contents of a list.
You set a child of GtkListItem to a GtkText instance so a user can edit a text with it.
You need to bind an item in the list with the buffer of the GtkText.
The direction is opposite from the one with GtkBuilderListItemFactory.
It is from the GtkText instance to the item in the list.
You can implement this with GtkSignalListItemFactory, which is more flexible than GtkBuilderListItemFactory.
Two things are shown in this section.
- Binding from a child of a GtkListItem instance to an item of a list.
- Access a child of GtkListItem dynamically.
This direction is the same as the one with GtkBulderListItemFactory.
But GtkBulderListItemFactory uses GtkExpression from the item property of the GtkListItem.
So, it updates its child widget only when the item property changes.
In this example the child reflects the change in the same item in the list dynamically.
This section shows just a part of the source file `listeditor.c`.
If you want to see the whole codes, see `src/listeditor` directory of the [Gtk4 tutorial repository](https://github.com/ToshioCP/Gtk4-tutorial).
## A list editor
The sample program is a list editor and data of the list are strings.
It's the same as a line editor.
It reads a text file line by line.
Each line is an item of the list.
The list is displayed with GtkColumnView.
There are two columns.
The one is a button, which makes the line be a current line.
If the line is the current line, the button is colored with red.
The other is a string which is the contents of the corresponding item of the list.
![List editor](../image/listeditor.png){width=12cm height=9cm}
The source files are located at `src/listeditor` directory.
You can compile end execute it as follows.
- Download the program from the [repository](https://github.com/ToshioCP/Gtk4-tutorial).
- Change your current directory to `src/listeditor`.
- Type the following on your commandline.
~~~
$ meson _build
$ ninja -C _build
$ _build/listeditor
~~~
- Append button: appends a line after the current line, or at the last line if no current line exists.
- Insert button: inserts a line before the current line.
- Remove button: removes a current line.
- Read button: reads a file.
- Write button: writes the contents to a file.
- close button: close the contents.
- quit button: quit the application.
- Button on the select column: makes the line current.
- String column: GtkText. You can edit a string in the field.
The current line number (zero-based) is shown at the left of the tool bar.
The file name is shown at the right of the write button.
## Connect a GtkText instance and an item in the list
The second column (GtkColumnViewColumn) sets its factory property to GtkSignalListItemFactory.
It uses three signals setup, bind and unbind.
The following is their sgnal handlers.
@@@include
listeditor/listeditor.c setup2_cb bind2_cb unbind2_cb
@@@
- 1-5: `setup2_cb` is a setup signal handler on the GtkSignalListItemFactory.
This factory is inserted to the factory property of the second GtkColumnViewColumn.
Te handler just creates a GtkText instance and sets the child of `listitem` to it.
The instance will be destroyed automatically when the `listitem` is destroyed.
So, teardown signal handler isn't necessary.
- 7-18: `bind2_cb` is a bind signal handler.
It is called when the `listitem` is bound to an item in the list.
The list items are LeData instances.
LeData is defined in the file `listeditor.c` (the C source file of the list editor).
It is a child class of GObject and has two data.
The one is `listitem` which points a GtkListItem instance when they are connected.
If no GtkListItem is connected, it is NULL.
The other is `string` which is a content of the line.
- 9-10: `text` is a child of the `listitem` and it is a GtkText instance.
`data` is an item pointed by the `listitem`. It is a LeData instance.
- 14: Gets the buffer of the `text`.
- 15: Sets the text of the buffer to `le_data_look_string (data)`.
le\_data\_look\_string returns the string of the data and the ownership of the string is taken by the `data`.
So, the caller don't need to free the string.
- 16: `g_object_bind_property` binds a property and another object property.
This line binds the "text" property of the `buffer` (source) and the "string" property of the `data` (destination).
It is a uni-directional binding (`G_BINDING_DEFAULT`).
When a user changes the GtkText text, the same string is immediately put into the `data`.
The function returns a GBinding instance.
This binding is different from bindings of GtkExpression.
This binding needs the existence of the two properties.
- 17: GObjec has a table.
The key is a string (or GQuark) and the value is a gpointer (pointer to any type).
The function `g_object_set_data` sets the association from the key to the value.
This line sets the association from "bind" to `bind` instance on the `listitem` instance.
It makes possible for the "unbind" handler to get the `bind` instance.
- 20-26: `unbind2_cb` is a unbind signal handler.
- 22: Retrieves the `bind` instance from the table in the `listitem` instance.
- 24: Unbind the binding.
- 25: Removes the value corresponds to the "bind" key.
This technique is not so complicated.
You can use it when you make a cell editable application.
## Change the cell of GtkColumnView dynamically
Next topic is to change the GtkColumnView (or GtkListView) cells dynamically.
The example changes the color of the buttons, which are children of GtkListItem instances, as the current line position moves.
The line editor has the current position of the list.
- At first, no line is current.
- When a line is appended or inserted, the line is current.
- When the current line is deleted, no line will be current.
- When a button in the first column of GtkColumnView is clicked, the line will be current.
The button of the current line is colored with red and otherwise white.
The current line has no relationship to GtkSingleSelection object.
GtkSingleSelection selects a line on the display.
The current line doesn't need to be on the display.
It is possible to be on the line out of the Window (GtkScrolledWindow).
Actually, the program doesn't use GtkSingleSelection.
It is necessary to know the corresponding GtkListItem instance from the item in the list.
It is the opposite direction from `gtk_list_item_get_item` function.
To accomplish this, we set a `listitem` element of LeData to point the corresponding GtkListItem instance.
Therefore, items (LeData) in the list always know the GtkListItem.
If there's no GtkListItem binded to the item, NULL is assigned.
@@@include
listeditor/listeditor.c select_cb setup1_cb bind1_cb unbind1_cb
@@@
- 8-13: `setup1_cb` is a setup signal handler on the GtkSignalListItemFactory.
This factory is inserted to the factory property of the first GtkColumnViewColumn.
It sets the child of `listitem` to a newly created GtkButton instance.
The "clicked" signal on the button is connected to the handler `select_cb`.
When the listitem is destroyed, the child (GtkButton) is also destroyed.
At the same time, the connection of the signal and handler is also destroyed.
So, you don't need teardown signal handler.
- 1-6: `select_cb` is a "clicked" signal handler.
LeWindow is defined in `listeditor.c`.
It's a child class of GtkApplicationWindow.
The handler just calls the `update_current` function.
The function will be explained later.
- 15-20: `bind1_cb` is a bind signal handler.
It sets the "listitem" element of the item (LeData) to point the `listitem` (GtkListItem instance).
It makes the item possible to find the corresponding GtkListItem instance.
- 22-27: `unbind1_cb` is an unbind signal handler.
It removes the `listitem` instance from the "listitem" element of the item.
The element becomes NULL, which tells no GtkListItem is bound.
When referring GtkListItem, it needs to check the "listitem" element whether it points a GtkListItem or not (NULL).
Otherwise bad things will happen.
@@@include
listeditor/listeditor.c update_current
@@@
The function `update_current` does several things.
- It has two parameters.
The first one is `win`, which is an instance of LeWindow class.
It has some elements.
- win->position: an Integer. it is the current position. If no current line exists, it is -1.
- win->position_label: GtkLabel. It shows the current position.
- The second parameter is `new`, which is the new current position.
At the beginning of the function, win->position points the old position.
- 10-16: Update the text of GtkLabel.
- 18-26: If the old position (win->position) is not negative, the current line exists.
It gets a GtkListItem instance via the item (LeData) of the list.
And it gets the GtkButton instance which is the child of the GtkListItem.
It clears the "css-classes" property of the button.
- 27: Updates win->position.
- 28-36: If the new position is not negative (It's possible to be negative when the current line has been removed), the current line exists.
It sets the "css-classes" property of the button to `{"current", NULL}`.
It is a NULL-terminated array of strings.
Each string is a CSS class.
Now the button has "current" style class.
The color of buttons are determined by the "background" CSS style.
The following CSS is applied to the default GdkDisplay in advance (in the startup handler of the application).
~~~css
columnview listview row button.current {background: red;}
~~~
The selectors "columnview listview row" is needed before "button" selector.
Otherwise the buttons in the GtkColumnview won't be found.
The button selector has "current" class.
So, the only "current" class button is colored with red.
Other buttons are not colored, which means they are white.
## Gtk\_widget\_dispose\_template function
The function `gtk_widget_dispose_template` clears the template children for the given widget.
This is the opposite of `gtk_widget_init_template()`.
It is a new function of GTK 4.8 version.
If your GTK version is lower than 4.8, you need to modify the program.

View file

@ -10,7 +10,7 @@ struct _TfeApplication {
GtkCssProvider *provider;
};
G_DEFINE_TYPE (TfeApplication, tfe_application, GTK_TYPE_APPLICATION);
G_DEFINE_TYPE (TfeApplication, tfe_application, GTK_TYPE_APPLICATION)
/* gsettings changed::font signal handler */
static void