Update section 24 to 27

This commit is contained in:
Toshio Sekiya 2023-07-23 11:49:22 +09:00
parent 8aba1da141
commit 251f60910f
31 changed files with 1489 additions and 1011 deletions

View file

@ -85,7 +85,7 @@ In short,
1. [Pango, CSS and Application](gfm/sec23.md) 1. [Pango, CSS and Application](gfm/sec23.md)
1. [GtkDrawingArea and Cairo](gfm/sec24.md) 1. [GtkDrawingArea and Cairo](gfm/sec24.md)
1. [Periodic Events](gfm/sec25.md) 1. [Periodic Events](gfm/sec25.md)
1. [Combine GtkDrawingArea and TfeTextView](gfm/sec26.md) 1. [Custom drawing](gfm/sec26.md)
1. [Tiny turtle graphics interpreter](gfm/sec27.md) 1. [Tiny turtle graphics interpreter](gfm/sec27.md)
1. [GtkListView](gfm/sec28.md) 1. [GtkListView](gfm/sec28.md)
1. [GtkGridView and activate signal](gfm/sec29.md) 1. [GtkGridView and activate signal](gfm/sec29.md)

BIN
docs/image/cd0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

BIN
docs/image/cd1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

BIN
docs/image/cd2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

BIN
docs/image/rect.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View file

@ -190,7 +190,7 @@ destruction</a></li>
<li><a href="sec23.html">Pango, CSS and Application</a></li> <li><a href="sec23.html">Pango, CSS and Application</a></li>
<li><a href="sec24.html">GtkDrawingArea and Cairo</a></li> <li><a href="sec24.html">GtkDrawingArea and Cairo</a></li>
<li><a href="sec25.html">Periodic Events</a></li> <li><a href="sec25.html">Periodic Events</a></li>
<li><a href="sec26.html">Combine GtkDrawingArea and TfeTextView</a></li> <li><a href="sec26.html">Custom drawing</a></li>
<li><a href="sec27.html">Tiny turtle graphics interpreter</a></li> <li><a href="sec27.html">Tiny turtle graphics interpreter</a></li>
<li><a href="sec28.html">GtkListView</a></li> <li><a href="sec28.html">GtkListView</a></li>
<li><a href="sec29.html">GtkGridView and activate signal</a></li> <li><a href="sec29.html">GtkGridView and activate signal</a></li>

View file

@ -112,28 +112,22 @@
</div> </div>
</nav> </nav>
<h1 id="gtkdrawingarea-and-cairo">GtkDrawingArea and Cairo</h1> <h1 id="gtkdrawingarea-and-cairo">GtkDrawingArea and Cairo</h1>
<p>This section and following sections are <em>not</em> updated yet and <p>If you want to draw shapes or paint images dynamically on the screen,
the programs were checked on the older GTK version than 4.10. They will use the GtkDrawingArea widget.</p>
be updated in the near future.</p> <p>GtkDrawingArea provides a cairo drawing context. You can draw images
<p>If you want to draw dynamically on the screen, like an image window with cairo library functions. This section describes:</p>
of gimp graphics editor, the GtkDrawingArea widget is the most suitable
widget. You can freely draw or redraw an image in this widget. This is
called custom drawing.</p>
<p>GtkDrawingArea provides a cairo drawing context so users can draw
images by using cairo functions. In this section, I will explain:</p>
<ol type="1"> <ol type="1">
<li>Cairo, but only briefly</li> <li>Cairo, but briefly</li>
<li>GtkDrawingArea, with a very simple example.</li> <li>GtkDrawingArea, with a very simple example.</li>
</ol> </ol>
<h2 id="cairo">Cairo</h2> <h2 id="cairo">Cairo</h2>
<p>Cairo is a set of two dimensional graphical drawing functions (or <p>Cairo is a drawing library for two dimensional graphics. There are a
graphics library). There are a lot of documents on <a lot of documents on <a href="https://www.cairographics.org/">Cairos
href="https://www.cairographics.org/">Cairos website</a>. If you arent website</a>. If you arent familiar with Cairo, it is worth reading the
familiar with Cairo, it is worth reading the <a <a href="https://www.cairographics.org/tutorial/">tutorial</a>.</p>
href="https://www.cairographics.org/tutorial/">tutorial</a>.</p> <p>The following is an introduction to the Cairo library. First, you
<p>The following is an introduction to the Cairo library and how to use need to know surfaces, sources, masks, destinations, cairo context and
it. First, you need to know about surfaces, sources, masks, transformations.</p>
destinations, cairo context and transformations.</p>
<ul> <ul>
<li>A surface represents an image. It is like a canvas. We can draw <li>A surface represents an image. It is like a canvas. We can draw
shapes and images with different colors on surfaces.</li> shapes and images with different colors on surfaces.</li>
@ -147,8 +141,8 @@ is a function to draw a path to the destination by the transfer.</li>
<li>A transformation can be applied before the transfer completes. The <li>A transformation can be applied before the transfer completes. The
transformation which is applied is called affine, which is a transformation which is applied is called affine, which is a
mathematical term meaning transofrmations that preserve straight lines. mathematical term meaning transofrmations that preserve straight lines.
Scaling, rotating, reflecting, shearing and translating are all examples Scaling, rotating, reflecting, shearing and translating are examples of
of affine transformations. They are mathematically represented by matrix affine transformations. They are mathematically represented by matrix
multiplication and vector addition. In this section we dont use it, multiplication and vector addition. In this section we dont use it,
instead we will only use the identity transformation. This means that instead we will only use the identity transformation. This means that
the coordinates in the source and mask are the same as the coordinates the coordinates in the source and mask are the same as the coordinates
@ -225,35 +219,32 @@ color depth. Width and height are in pixels and given as integers.</li>
<li>14: Creates cairo context. The surface given as an argument will be <li>14: Creates cairo context. The surface given as an argument will be
the destination of the context.</li> the destination of the context.</li>
<li>18: <code>cairo_set_source_rgb</code> creates a source pattern, <li>18: <code>cairo_set_source_rgb</code> creates a source pattern,
which in this case is a solid white paint. The second to fourth which is a solid white paint. The second to fourth arguments are red,
arguments are red, green and blue color values respectively, and they green and blue color values respectively, and they are of type float.
are of type float. The values are between zero (0.0) and one (1.0), with The values are between zero (0.0) and one (1.0). Black is (0.0,0.0,0.0)
black being given by (0.0,0.0,0.0) and white by (1.0,1.0,1.0).</li> and white is (1.0,1.0,1.0).</li>
<li>19: <code>cairo_paint</code> copies everywhere in the source to <li>19: <code>cairo_paint</code> copies everywhere in the source to
destination. The destination is filled with white pixels with this destination. The destination is filled with white pixels with this
command.</li> command.</li>
<li>21: Sets the source color to black.</li> <li>21: Sets the source color to black.</li>
<li>22: <code>cairo_set_line_width</code> set the width of lines. In <li>22: <code>cairo_set_line_width</code> sets the width of lines. In
this case, the line width is set to be two pixels and will end up that this case, the line width is set to be two pixels and will end up that
same size. (It is because the transformation is identity. If the same size. (It is because the transformation is identity. If the
transformation isnt identity, for example scaling with the factor transformation isnt identity, for example scaling with the factor
three, the actual width in destination will be six (2x3=6) pixels.)</li> three, the actual width in destination will be six (2x3=6) pixels.)</li>
<li>23: Draws a rectangle (square) on the mask. The square is located at <li>23: Draws a rectangle (square) on the mask. The square is located at
the center.</li> the center.</li>
<li>24: <code>cairo_stroke</code> transfer the source to destination <li>24: <code>cairo_stroke</code> transfers the source to destination
through the rectangle in the mask.</li> through the rectangle in the mask.</li>
<li>27: Outputs the image to a png file <code>rectangle.png</code>.</li> <li>31: Outputs the image to a png file <code>rectangle.png</code>.</li>
<li>28: Destroys the context. At the same time the source is <li>32: Destroys the context. At the same time the source is
destroyed.</li> destroyed.</li>
<li>29: Destroys the surface.</li> <li>33: Destroys the surface.</li>
</ul> </ul>
<p>To compile this, change your current directory to <p>To compile this, change your current directory to
<code>src/misc</code> and type the following.</p> <code>src/misc</code> and type the following.</p>
<pre><code>$ gcc `pkg-config --cflags cairo` cairo.c `pkg-config --libs cairo`</code></pre> <pre><code>$ gcc `pkg-config --cflags cairo` cairo.c `pkg-config --libs cairo`</code></pre>
<figure> <p>s <img src="image/rectangle.png" alt="rectangle.png" /></p>
<img src="image/rectangle.png" alt="rectangle.png" />
<figcaption aria-hidden="true">rectangle.png</figcaption>
</figure>
<p>See the <a href="https://www.cairographics.org/">Cairos website</a> <p>See the <a href="https://www.cairographics.org/">Cairos website</a>
for further information.</p> for further information.</p>
<h2 id="gtkdrawingarea">GtkDrawingArea</h2> <h2 id="gtkdrawingarea">GtkDrawingArea</h2>
@ -308,10 +299,10 @@ important in this example.</p>
<ul> <ul>
<li>22: Creates a GtkDrawingArea instance.</li> <li>22: Creates a GtkDrawingArea instance.</li>
<li>25: Sets a drawing function of the widget. GtkDrawingArea widget <li>25: Sets a drawing function of the widget. GtkDrawingArea widget
uses the function to draw the contents of itself whenever its necessary. uses the function <code>draw_function</code> to draw the contents of
For example, when a user drag a mouse pointer and resize a top-level itself whenever its necessary. For example, when a user drag a mouse
window, GtkDrawingArea also changes the size. Then, the whole window pointer and resize a top-level window, GtkDrawingArea also changes the
needs to be redrawn. For the information of size. Then, the whole window needs to be redrawn. For the information of
<code>gtk_drawing_area_set_draw_func</code>, see <a <code>gtk_drawing_area_set_draw_func</code>, see <a
href="https://docs.gtk.org/gtk4/method.DrawingArea.set_draw_func.html">Gtk href="https://docs.gtk.org/gtk4/method.DrawingArea.set_draw_func.html">Gtk
API Reference gtk_drawing_area_set_draw_func</a>.</li> API Reference gtk_drawing_area_set_draw_func</a>.</li>

View file

@ -279,7 +279,7 @@ class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span i
<span id="cb2-13"><a href="#cb2-13"></a></span> <span id="cb2-13"><a href="#cb2-13"></a></span>
<span id="cb2-14"><a href="#cb2-14"></a> gtk_drawing_area_set_draw_func<span class="op">(</span>GTK_DRAWING_AREA <span class="op">(</span>clock<span class="op">),</span> draw_clock<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">);</span></span> <span id="cb2-14"><a href="#cb2-14"></a> gtk_drawing_area_set_draw_func<span class="op">(</span>GTK_DRAWING_AREA <span class="op">(</span>clock<span class="op">),</span> draw_clock<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">);</span></span>
<span id="cb2-15"><a href="#cb2-15"></a> g_timeout_add<span class="op">(</span><span class="dv">1000</span><span class="op">,</span> <span class="op">(</span>GSourceFunc<span class="op">)</span> time_handler<span class="op">,</span> <span class="op">(</span>gpointer<span class="op">)</span> clock<span class="op">);</span></span> <span id="cb2-15"><a href="#cb2-15"></a> g_timeout_add<span class="op">(</span><span class="dv">1000</span><span class="op">,</span> <span class="op">(</span>GSourceFunc<span class="op">)</span> time_handler<span class="op">,</span> <span class="op">(</span>gpointer<span class="op">)</span> clock<span class="op">);</span></span>
<span id="cb2-16"><a href="#cb2-16"></a> gtk_widget_show<span class="op">(</span>win<span class="op">);</span></span> <span id="cb2-16"><a href="#cb2-16"></a> gtk_window_present<span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">));</span></span>
<span id="cb2-17"><a href="#cb2-17"></a></span> <span id="cb2-17"><a href="#cb2-17"></a></span>
<span id="cb2-18"><a href="#cb2-18"></a><span class="op">}</span></span></code></pre></div> <span id="cb2-18"><a href="#cb2-18"></a><span class="op">}</span></span></code></pre></div>
<p>Our <code>time_handler()</code> function is very simple, as it just <p>Our <code>time_handler()</code> function is very simple, as it just
@ -448,7 +448,7 @@ class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span i
<span id="cb4-143"><a href="#cb4-143"></a></span> <span id="cb4-143"><a href="#cb4-143"></a></span>
<span id="cb4-144"><a href="#cb4-144"></a> gtk_drawing_area_set_draw_func<span class="op">(</span>GTK_DRAWING_AREA <span class="op">(</span>clock<span class="op">),</span> draw_clock<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">);</span></span> <span id="cb4-144"><a href="#cb4-144"></a> gtk_drawing_area_set_draw_func<span class="op">(</span>GTK_DRAWING_AREA <span class="op">(</span>clock<span class="op">),</span> draw_clock<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">);</span></span>
<span id="cb4-145"><a href="#cb4-145"></a> g_timeout_add<span class="op">(</span><span class="dv">1000</span><span class="op">,</span> <span class="op">(</span>GSourceFunc<span class="op">)</span> time_handler<span class="op">,</span> <span class="op">(</span>gpointer<span class="op">)</span> clock<span class="op">);</span></span> <span id="cb4-145"><a href="#cb4-145"></a> g_timeout_add<span class="op">(</span><span class="dv">1000</span><span class="op">,</span> <span class="op">(</span>GSourceFunc<span class="op">)</span> time_handler<span class="op">,</span> <span class="op">(</span>gpointer<span class="op">)</span> clock<span class="op">);</span></span>
<span id="cb4-146"><a href="#cb4-146"></a> gtk_widget_show<span class="op">(</span>win<span class="op">);</span></span> <span id="cb4-146"><a href="#cb4-146"></a> gtk_window_present<span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">));</span></span>
<span id="cb4-147"><a href="#cb4-147"></a></span> <span id="cb4-147"><a href="#cb4-147"></a></span>
<span id="cb4-148"><a href="#cb4-148"></a><span class="op">}</span></span> <span id="cb4-148"><a href="#cb4-148"></a><span class="op">}</span></span>
<span id="cb4-149"><a href="#cb4-149"></a></span> <span id="cb4-149"><a href="#cb4-149"></a></span>

View file

@ -111,423 +111,354 @@
</div> </div>
</div> </div>
</nav> </nav>
<h1 id="combine-gtkdrawingarea-and-tfetextview">Combine GtkDrawingArea <h1 id="custom-drawing">Custom drawing</h1>
and TfeTextView</h1> <p>Custom drawing is to draw shapes dynamically. This section shows an
<p>Now, we will make a new application which has GtkDrawingArea and example of custom drawing. You can draw rectangles by dragging the
TfeTextView in it. Its name is “color”. If you write a name of a color mouse.</p>
in TfeTextView and click on the <code>run</code> button, then the color <p>Down the button.</p>
of GtkDrawingArea changes to the color given by you.</p>
<figure> <figure>
<img src="image/color.png" alt="color" /> <img src="image/cd0.png" alt="down the button" />
<figcaption aria-hidden="true">color</figcaption> <figcaption aria-hidden="true">down the button</figcaption>
</figure> </figure>
<p>The following colors are available. (without new line charactor)</p> <p>Move the mouse</p>
<figure>
<img src="image/cd1.png" alt="Move the mouse" />
<figcaption aria-hidden="true">Move the mouse</figcaption>
</figure>
<p>Up the button.</p>
<figure>
<img src="image/cd2.png" alt="Up the button" />
<figcaption aria-hidden="true">Up the button</figcaption>
</figure>
<p>The programs are at <code>src/custom_drawing</code> directory.
Download the <a
href="https://github.com/ToshioCP/Gtk4-tutorial">repository</a> and see
the directory. There are four files.</p>
<ul> <ul>
<li>white</li> <li>meson.build</li>
<li>black</li> <li>rect.c</li>
<li>red</li> <li>rect.gresource.xml</li>
<li>green</li> <li>rect.ui</li>
<li>blue</li>
</ul> </ul>
<p>In addition the following two options are also available.</p> <h2 id="rect.gresource.xml">rect.gresource.xml</h2>
<ul> <p>This file describes a ui file to compile. The compiler
<li>light: Make the color of the drawing area lighter.</li> glib-compile-resources uses it.</p>
<li>dark: Make the color of the drawing area darker.</li>
</ul>
<p>This application can only do very simple things. However, it tells us
that if we add powerful parser to it, we will be able to make it more
efficient. I want to show it to you in the later section by making a
turtle graphics language like Logo program language.</p>
<p>In this section, we focus on how to bind the two objects.</p>
<h2 id="color.ui-and-color.gresource.xml">Color.ui and
color.gresource.xml</h2>
<p>First, We need to make the ui file of the widgets. Title bar, four
buttons in the tool bar, textview and drawing area. The ui file is as
follows.</p>
<div class="sourceCode" id="cb1"><pre <div class="sourceCode" id="cb1"><pre
class="sourceCode numberSource xml numberLines"><code class="sourceCode xml"><span id="cb1-1"><a href="#cb1-1"></a><span class="fu">&lt;?xml</span><span class="ot"> version=</span><span class="st">&quot;1.0&quot;</span><span class="ot"> encoding=</span><span class="st">&quot;UTF-8&quot;</span><span class="fu">?&gt;</span></span> class="sourceCode numberSource xml numberLines"><code class="sourceCode xml"><span id="cb1-1"><a href="#cb1-1"></a><span class="fu">&lt;?xml</span><span class="ot"> version=</span><span class="st">&quot;1.0&quot;</span><span class="ot"> encoding=</span><span class="st">&quot;UTF-8&quot;</span><span class="fu">?&gt;</span></span>
<span id="cb1-2"><a href="#cb1-2"></a>&lt;<span class="kw">interface</span>&gt;</span> <span id="cb1-2"><a href="#cb1-2"></a>&lt;<span class="kw">gresources</span>&gt;</span>
<span id="cb1-3"><a href="#cb1-3"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkApplicationWindow&quot;</span><span class="ot"> id=</span><span class="st">&quot;win&quot;</span>&gt;</span> <span id="cb1-3"><a href="#cb1-3"></a> &lt;<span class="kw">gresource</span><span class="ot"> prefix=</span><span class="st">&quot;/com/github/ToshioCP/rect&quot;</span>&gt;</span>
<span id="cb1-4"><a href="#cb1-4"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;title&quot;</span>&gt;color changer&lt;/<span class="kw">property</span>&gt;</span> <span id="cb1-4"><a href="#cb1-4"></a> &lt;<span class="kw">file</span>&gt;rect.ui&lt;/<span class="kw">file</span>&gt;</span>
<span id="cb1-5"><a href="#cb1-5"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;default-width&quot;</span>&gt;600&lt;/<span class="kw">property</span>&gt;</span> <span id="cb1-5"><a href="#cb1-5"></a> &lt;/<span class="kw">gresource</span>&gt;</span>
<span id="cb1-6"><a href="#cb1-6"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;default-height&quot;</span>&gt;400&lt;/<span class="kw">property</span>&gt;</span> <span id="cb1-6"><a href="#cb1-6"></a>&lt;/<span class="kw">gresources</span>&gt;</span></code></pre></div>
<span id="cb1-7"><a href="#cb1-7"></a> &lt;<span class="kw">child</span>&gt;</span> <p>The prefix is <code>/com/github/ToshioCP/rect</code> and the file is
<span id="cb1-8"><a href="#cb1-8"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkBox&quot;</span><span class="ot"> id=</span><span class="st">&quot;boxv&quot;</span>&gt;</span> <code>rect.ui</code>. Therefore, GtkBuilder reads the resource from
<span id="cb1-9"><a href="#cb1-9"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;orientation&quot;</span>&gt;GTK_ORIENTATION_VERTICAL&lt;/<span class="kw">property</span>&gt;</span> <code>/com/github/ToshioCP/rect/rect.ui</code>.</p>
<span id="cb1-10"><a href="#cb1-10"></a> &lt;<span class="kw">child</span>&gt;</span> <h2 id="rect.ui">rect.ui</h2>
<span id="cb1-11"><a href="#cb1-11"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkBox&quot;</span><span class="ot"> id=</span><span class="st">&quot;boxh1&quot;</span>&gt;</span> <p>The following is the ui file that defines the widgets. There are two
<span id="cb1-12"><a href="#cb1-12"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;orientation&quot;</span>&gt;GTK_ORIENTATION_HORIZONTAL&lt;/<span class="kw">property</span>&gt;</span> widgets which are GtkApplicationWindow and GtkDrawingArea. The ids are
<span id="cb1-13"><a href="#cb1-13"></a> &lt;<span class="kw">child</span>&gt;</span> <code>win</code> and <code>da</code> respectively.</p>
<span id="cb1-14"><a href="#cb1-14"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkLabel&quot;</span><span class="ot"> id=</span><span class="st">&quot;dmy1&quot;</span>&gt;</span>
<span id="cb1-15"><a href="#cb1-15"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;width-chars&quot;</span>&gt;10&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb1-16"><a href="#cb1-16"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb1-17"><a href="#cb1-17"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb1-18"><a href="#cb1-18"></a> &lt;<span class="kw">child</span>&gt;</span>
<span id="cb1-19"><a href="#cb1-19"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkButton&quot;</span><span class="ot"> id=</span><span class="st">&quot;btnr&quot;</span>&gt;</span>
<span id="cb1-20"><a href="#cb1-20"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;label&quot;</span>&gt;Run&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb1-21"><a href="#cb1-21"></a> &lt;<span class="kw">signal</span><span class="ot"> name=</span><span class="st">&quot;clicked&quot;</span><span class="ot"> handler=</span><span class="st">&quot;run_cb&quot;</span>&gt;&lt;/<span class="kw">signal</span>&gt;</span>
<span id="cb1-22"><a href="#cb1-22"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb1-23"><a href="#cb1-23"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb1-24"><a href="#cb1-24"></a> &lt;<span class="kw">child</span>&gt;</span>
<span id="cb1-25"><a href="#cb1-25"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkButton&quot;</span><span class="ot"> id=</span><span class="st">&quot;btno&quot;</span>&gt;</span>
<span id="cb1-26"><a href="#cb1-26"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;label&quot;</span>&gt;Open&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb1-27"><a href="#cb1-27"></a> &lt;<span class="kw">signal</span><span class="ot"> name=</span><span class="st">&quot;clicked&quot;</span><span class="ot"> handler=</span><span class="st">&quot;open_cb&quot;</span>&gt;&lt;/<span class="kw">signal</span>&gt;</span>
<span id="cb1-28"><a href="#cb1-28"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb1-29"><a href="#cb1-29"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb1-30"><a href="#cb1-30"></a> &lt;<span class="kw">child</span>&gt;</span>
<span id="cb1-31"><a href="#cb1-31"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkLabel&quot;</span><span class="ot"> id=</span><span class="st">&quot;dmy2&quot;</span>&gt;</span>
<span id="cb1-32"><a href="#cb1-32"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;hexpand&quot;</span>&gt;TRUE&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb1-33"><a href="#cb1-33"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb1-34"><a href="#cb1-34"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb1-35"><a href="#cb1-35"></a> &lt;<span class="kw">child</span>&gt;</span>
<span id="cb1-36"><a href="#cb1-36"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkButton&quot;</span><span class="ot"> id=</span><span class="st">&quot;btns&quot;</span>&gt;</span>
<span id="cb1-37"><a href="#cb1-37"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;label&quot;</span>&gt;Save&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb1-38"><a href="#cb1-38"></a> &lt;<span class="kw">signal</span><span class="ot"> name=</span><span class="st">&quot;clicked&quot;</span><span class="ot"> handler=</span><span class="st">&quot;save_cb&quot;</span>&gt;&lt;/<span class="kw">signal</span>&gt;</span>
<span id="cb1-39"><a href="#cb1-39"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb1-40"><a href="#cb1-40"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb1-41"><a href="#cb1-41"></a> &lt;<span class="kw">child</span>&gt;</span>
<span id="cb1-42"><a href="#cb1-42"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkButton&quot;</span><span class="ot"> id=</span><span class="st">&quot;btnc&quot;</span>&gt;</span>
<span id="cb1-43"><a href="#cb1-43"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;label&quot;</span>&gt;Close&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb1-44"><a href="#cb1-44"></a> &lt;<span class="kw">signal</span><span class="ot"> name=</span><span class="st">&quot;clicked&quot;</span><span class="ot"> handler=</span><span class="st">&quot;close_cb&quot;</span>&gt;&lt;/<span class="kw">signal</span>&gt;</span>
<span id="cb1-45"><a href="#cb1-45"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb1-46"><a href="#cb1-46"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb1-47"><a href="#cb1-47"></a> &lt;<span class="kw">child</span>&gt;</span>
<span id="cb1-48"><a href="#cb1-48"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkLabel&quot;</span><span class="ot"> id=</span><span class="st">&quot;dmy3&quot;</span>&gt;</span>
<span id="cb1-49"><a href="#cb1-49"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;width-chars&quot;</span>&gt;10&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb1-50"><a href="#cb1-50"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb1-51"><a href="#cb1-51"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb1-52"><a href="#cb1-52"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb1-53"><a href="#cb1-53"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb1-54"><a href="#cb1-54"></a> &lt;<span class="kw">child</span>&gt;</span>
<span id="cb1-55"><a href="#cb1-55"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkBox&quot;</span><span class="ot"> id=</span><span class="st">&quot;boxh2&quot;</span>&gt;</span>
<span id="cb1-56"><a href="#cb1-56"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;orientation&quot;</span>&gt;GTK_ORIENTATION_HORIZONTAL&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb1-57"><a href="#cb1-57"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;homogeneous&quot;</span>&gt;TRUE&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb1-58"><a href="#cb1-58"></a> &lt;<span class="kw">child</span>&gt;</span>
<span id="cb1-59"><a href="#cb1-59"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkScrolledWindow&quot;</span><span class="ot"> id=</span><span class="st">&quot;scr&quot;</span>&gt;</span>
<span id="cb1-60"><a href="#cb1-60"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;hexpand&quot;</span>&gt;TRUE&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb1-61"><a href="#cb1-61"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;vexpand&quot;</span>&gt;TRUE&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb1-62"><a href="#cb1-62"></a> &lt;<span class="kw">child</span>&gt;</span>
<span id="cb1-63"><a href="#cb1-63"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;TfeTextView&quot;</span><span class="ot"> id=</span><span class="st">&quot;tv&quot;</span>&gt;</span>
<span id="cb1-64"><a href="#cb1-64"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;wrap-mode&quot;</span>&gt;GTK_WRAP_WORD_CHAR&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb1-65"><a href="#cb1-65"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb1-66"><a href="#cb1-66"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb1-67"><a href="#cb1-67"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb1-68"><a href="#cb1-68"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb1-69"><a href="#cb1-69"></a> &lt;<span class="kw">child</span>&gt;</span>
<span id="cb1-70"><a href="#cb1-70"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkDrawingArea&quot;</span><span class="ot"> id=</span><span class="st">&quot;da&quot;</span>&gt;</span>
<span id="cb1-71"><a href="#cb1-71"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;hexpand&quot;</span>&gt;TRUE&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb1-72"><a href="#cb1-72"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;vexpand&quot;</span>&gt;TRUE&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb1-73"><a href="#cb1-73"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb1-74"><a href="#cb1-74"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb1-75"><a href="#cb1-75"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb1-76"><a href="#cb1-76"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb1-77"><a href="#cb1-77"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb1-78"><a href="#cb1-78"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb1-79"><a href="#cb1-79"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb1-80"><a href="#cb1-80"></a>&lt;/<span class="kw">interface</span>&gt;</span></code></pre></div>
<ul>
<li>10-53: The horizontal box <code>boxh1</code> makes a tool bar which
has four buttons, <code>Run</code>, <code>Open</code>, <code>Save</code>
and <code>Close</code>. This is similar to the <code>tfe</code> text
editor in <a href="sec9.html">Section 9</a>. There are two differences.
<code>Run</code> button replaces <code>New</code> button. A signal
element is added to each button object. It has “name” attribute which is
a signal name and “handler” attribute which is the name of its signal
handler. Options “-WI, export-dynamic” CFLAG is necessary when you
compile the application. You can achieve this by adding “export_dynamic:
true” argument to the executable function in <code>meson.build</code>.
And be careful that the handler must be defined without static
class.</li>
<li>54-76: The horizontal box <code>boxh2</code> includes
GtkScrolledWindow and GtkDrawingArea. GtkBox has “homogeneous property”
with TRUE value, so the two children have the same width in the box.
TfeTextView is a child of GtkScrolledWindow.</li>
</ul>
<p>The xml file for the resource compiler is almost same as before. Just
substitute “color” for “tfe”.</p>
<div class="sourceCode" id="cb2"><pre <div class="sourceCode" id="cb2"><pre
class="sourceCode numberSource xml numberLines"><code class="sourceCode xml"><span id="cb2-1"><a href="#cb2-1"></a><span class="fu">&lt;?xml</span><span class="ot"> version=</span><span class="st">&quot;1.0&quot;</span><span class="ot"> encoding=</span><span class="st">&quot;UTF-8&quot;</span><span class="fu">?&gt;</span></span> class="sourceCode numberSource xml numberLines"><code class="sourceCode xml"><span id="cb2-1"><a href="#cb2-1"></a><span class="fu">&lt;?xml</span><span class="ot"> version=</span><span class="st">&quot;1.0&quot;</span><span class="ot"> encoding=</span><span class="st">&quot;UTF-8&quot;</span><span class="fu">?&gt;</span></span>
<span id="cb2-2"><a href="#cb2-2"></a>&lt;<span class="kw">gresources</span>&gt;</span> <span id="cb2-2"><a href="#cb2-2"></a>&lt;<span class="kw">interface</span>&gt;</span>
<span id="cb2-3"><a href="#cb2-3"></a> &lt;<span class="kw">gresource</span><span class="ot"> prefix=</span><span class="st">&quot;/com/github/ToshioCP/color&quot;</span>&gt;</span> <span id="cb2-3"><a href="#cb2-3"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkApplicationWindow&quot;</span><span class="ot"> id=</span><span class="st">&quot;win&quot;</span>&gt;</span>
<span id="cb2-4"><a href="#cb2-4"></a> &lt;<span class="kw">file</span>&gt;color.ui&lt;/<span class="kw">file</span>&gt;</span> <span id="cb2-4"><a href="#cb2-4"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;default-width&quot;</span>&gt;800&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb2-5"><a href="#cb2-5"></a> &lt;/<span class="kw">gresource</span>&gt;</span> <span id="cb2-5"><a href="#cb2-5"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;default-height&quot;</span>&gt;600&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb2-6"><a href="#cb2-6"></a>&lt;/<span class="kw">gresources</span>&gt;</span></code></pre></div> <span id="cb2-6"><a href="#cb2-6"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;resizable&quot;</span>&gt;FALSE&lt;/<span class="kw">property</span>&gt;</span>
<h2 id="drawing-function-and-surface">Drawing function and surface</h2> <span id="cb2-7"><a href="#cb2-7"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;title&quot;</span>&gt;Custom drawing&lt;/<span class="kw">property</span>&gt;</span>
<p>The main point of this program is a drawing function.</p> <span id="cb2-8"><a href="#cb2-8"></a> &lt;<span class="kw">child</span>&gt;</span>
<div class="sourceCode" id="cb3"><pre <span id="cb2-9"><a href="#cb2-9"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkDrawingArea&quot;</span><span class="ot"> id=</span><span class="st">&quot;da&quot;</span>&gt;</span>
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb3-1"><a href="#cb3-1"></a><span class="dt">static</span> <span class="dt">void</span></span> <span id="cb2-10"><a href="#cb2-10"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;hexpand&quot;</span>&gt;TRUE&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb3-2"><a href="#cb3-2"></a>draw_func <span class="op">(</span>GtkDrawingArea <span class="op">*</span>drawing_area<span class="op">,</span> cairo_t <span class="op">*</span>cr<span class="op">,</span> <span class="dt">int</span> width<span class="op">,</span> <span class="dt">int</span> height<span class="op">,</span> gpointer user_data<span class="op">)</span> <span class="op">{</span></span> <span id="cb2-11"><a href="#cb2-11"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;vexpand&quot;</span>&gt;TRUE&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb3-3"><a href="#cb3-3"></a> <span class="cf">if</span> <span class="op">(</span>surface<span class="op">)</span> <span class="op">{</span></span> <span id="cb2-12"><a href="#cb2-12"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb3-4"><a href="#cb3-4"></a> cairo_set_source_surface <span class="op">(</span>cr<span class="op">,</span> surface<span class="op">,</span> <span class="dv">0</span><span class="op">,</span> <span class="dv">0</span><span class="op">);</span></span> <span id="cb2-13"><a href="#cb2-13"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb3-5"><a href="#cb3-5"></a> cairo_paint <span class="op">(</span>cr<span class="op">);</span></span> <span id="cb2-14"><a href="#cb2-14"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb3-6"><a href="#cb3-6"></a> <span class="op">}</span></span> <span id="cb2-15"><a href="#cb2-15"></a>&lt;/<span class="kw">interface</span>&gt;</span></code></pre></div>
<span id="cb3-7"><a href="#cb3-7"></a><span class="op">}</span></span></code></pre></div> <h2 id="rect.c">rect.c</h2>
<p>The <code>surface</code> variable in line 3 is a static variable.</p> <h3 id="gtkapplication">GtkApplication</h3>
<div class="sourceCode" id="cb4"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="dt">static</span> cairo_surface_t <span class="op">*</span>surface <span class="op">=</span> NULL<span class="op">;</span></span></code></pre></div> <p>This program uses GtkApplication. The application ID is
<p>The drawing function just copies the <code>surface</code> to its own <code>com.github.ToshioCP.rect</code>.</p>
surface with the <code>cairo_paint</code> function. The surface (pointed <div class="sourceCode" id="cb3"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#define APPLICATION_ID &quot;com.github.ToshioCP.rect&quot;</span></span></code></pre></div>
by the static variable <code>surface</code>) is built by the <p>See <a
<code>run</code> function.</p> href="https://developer.gnome.org/documentation/tutorials/application-id.html">GNOME
Developer Documentation</a> for further information.</p>
<p>The function <code>main</code> is called at the beginning of the
application.</p>
<div class="sourceCode" id="cb4"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb4-1"><a href="#cb4-1"></a><span class="dt">int</span></span>
<span id="cb4-2"><a href="#cb4-2"></a>main <span class="op">(</span><span class="dt">int</span> argc<span class="op">,</span> <span class="dt">char</span> <span class="op">**</span>argv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-3"><a href="#cb4-3"></a> GtkApplication <span class="op">*</span>app<span class="op">;</span></span>
<span id="cb4-4"><a href="#cb4-4"></a> <span class="dt">int</span> stat<span class="op">;</span></span>
<span id="cb4-5"><a href="#cb4-5"></a></span>
<span id="cb4-6"><a href="#cb4-6"></a> app <span class="op">=</span> gtk_application_new <span class="op">(</span>APPLICATION_ID<span class="op">,</span> G_APPLICATION_HANDLES_OPEN<span class="op">);</span></span>
<span id="cb4-7"><a href="#cb4-7"></a> g_signal_connect <span class="op">(</span>app<span class="op">,</span> <span class="st">&quot;startup&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>app_startup<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb4-8"><a href="#cb4-8"></a> g_signal_connect <span class="op">(</span>app<span class="op">,</span> <span class="st">&quot;activate&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>app_activate<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb4-9"><a href="#cb4-9"></a> g_signal_connect <span class="op">(</span>app<span class="op">,</span> <span class="st">&quot;shutdown&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>app_shutdown<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb4-10"><a href="#cb4-10"></a> stat <span class="op">=</span>g_application_run <span class="op">(</span>G_APPLICATION <span class="op">(</span>app<span class="op">),</span> argc<span class="op">,</span> argv<span class="op">);</span></span>
<span id="cb4-11"><a href="#cb4-11"></a> g_object_unref <span class="op">(</span>app<span class="op">);</span></span>
<span id="cb4-12"><a href="#cb4-12"></a> <span class="cf">return</span> stat<span class="op">;</span></span>
<span id="cb4-13"><a href="#cb4-13"></a><span class="op">}</span></span></code></pre></div>
<p>It connects three signals and handlers.</p>
<ul>
<li>startup: It is emitted after the application is registered to the
system.</li>
<li>activate: It is emitted when the application is activated.</li>
<li>shutdown: It is emitted just before the application quits.</li>
</ul>
<div class="sourceCode" id="cb5"><pre <div class="sourceCode" id="cb5"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb5-1"><a href="#cb5-1"></a><span class="dt">static</span> <span class="dt">void</span></span> class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb5-1"><a href="#cb5-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb5-2"><a href="#cb5-2"></a>run <span class="op">(</span><span class="dt">void</span><span class="op">)</span> <span class="op">{</span></span> <span id="cb5-2"><a href="#cb5-2"></a>app_startup <span class="op">(</span>GApplication <span class="op">*</span>application<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-3"><a href="#cb5-3"></a> GtkTextBuffer <span class="op">*</span>tb <span class="op">=</span> gtk_text_view_get_buffer <span class="op">(</span>GTK_TEXT_VIEW <span class="op">(</span>tv<span class="op">));</span></span> <span id="cb5-3"><a href="#cb5-3"></a> GtkApplication <span class="op">*</span>app <span class="op">=</span> GTK_APPLICATION <span class="op">(</span>application<span class="op">);</span></span>
<span id="cb5-4"><a href="#cb5-4"></a> GtkTextIter start_iter<span class="op">;</span></span> <span id="cb5-4"><a href="#cb5-4"></a> GtkBuilder <span class="op">*</span>build<span class="op">;</span></span>
<span id="cb5-5"><a href="#cb5-5"></a> GtkTextIter end_iter<span class="op">;</span></span> <span id="cb5-5"><a href="#cb5-5"></a> GtkWindow <span class="op">*</span>win<span class="op">;</span></span>
<span id="cb5-6"><a href="#cb5-6"></a> <span class="dt">char</span> <span class="op">*</span>contents<span class="op">;</span></span> <span id="cb5-6"><a href="#cb5-6"></a> GtkDrawingArea <span class="op">*</span>da<span class="op">;</span></span>
<span id="cb5-7"><a href="#cb5-7"></a> cairo_t <span class="op">*</span>cr<span class="op">;</span></span> <span id="cb5-7"><a href="#cb5-7"></a> GtkGesture <span class="op">*</span>drag<span class="op">;</span></span>
<span id="cb5-8"><a href="#cb5-8"></a></span> <span id="cb5-8"><a href="#cb5-8"></a></span>
<span id="cb5-9"><a href="#cb5-9"></a> gtk_text_buffer_get_bounds <span class="op">(</span>tb<span class="op">,</span> <span class="op">&amp;</span>start_iter<span class="op">,</span> <span class="op">&amp;</span>end_iter<span class="op">);</span></span> <span id="cb5-9"><a href="#cb5-9"></a> build <span class="op">=</span> gtk_builder_new_from_resource <span class="op">(</span><span class="st">&quot;/com/github/ToshioCP/rect/rect.ui&quot;</span><span class="op">);</span></span>
<span id="cb5-10"><a href="#cb5-10"></a> contents <span class="op">=</span> gtk_text_buffer_get_text <span class="op">(</span>tb<span class="op">,</span> <span class="op">&amp;</span>start_iter<span class="op">,</span> <span class="op">&amp;</span>end_iter<span class="op">,</span> FALSE<span class="op">);</span></span> <span id="cb5-10"><a href="#cb5-10"></a> win <span class="op">=</span> GTK_WINDOW <span class="op">(</span>gtk_builder_get_object <span class="op">(</span>build<span class="op">,</span> <span class="st">&quot;win&quot;</span><span class="op">));</span></span>
<span id="cb5-11"><a href="#cb5-11"></a> <span class="cf">if</span> <span class="op">(</span>surface<span class="op">)</span> <span class="op">{</span></span> <span id="cb5-11"><a href="#cb5-11"></a> da <span class="op">=</span> GTK_DRAWING_AREA <span class="op">(</span>gtk_builder_get_object <span class="op">(</span>build<span class="op">,</span> <span class="st">&quot;da&quot;</span><span class="op">));</span></span>
<span id="cb5-12"><a href="#cb5-12"></a> cr <span class="op">=</span> cairo_create <span class="op">(</span>surface<span class="op">);</span></span> <span id="cb5-12"><a href="#cb5-12"></a> gtk_window_set_application <span class="op">(</span>win<span class="op">,</span> app<span class="op">);</span></span>
<span id="cb5-13"><a href="#cb5-13"></a> <span class="cf">if</span> <span class="op">(</span>g_strcmp0 <span class="op">(</span><span class="st">&quot;red&quot;</span><span class="op">,</span> contents<span class="op">)</span> <span class="op">==</span> <span class="dv">0</span><span class="op">)</span></span> <span id="cb5-13"><a href="#cb5-13"></a> g_object_unref <span class="op">(</span>build<span class="op">);</span></span>
<span id="cb5-14"><a href="#cb5-14"></a> cairo_set_source_rgb <span class="op">(</span>cr<span class="op">,</span> <span class="dv">1</span><span class="op">,</span> <span class="dv">0</span><span class="op">,</span> <span class="dv">0</span><span class="op">);</span></span> <span id="cb5-14"><a href="#cb5-14"></a></span>
<span id="cb5-15"><a href="#cb5-15"></a> <span class="cf">else</span> <span class="cf">if</span> <span class="op">(</span>g_strcmp0 <span class="op">(</span><span class="st">&quot;green&quot;</span><span class="op">,</span> contents<span class="op">)</span> <span class="op">==</span> <span class="dv">0</span><span class="op">)</span></span> <span id="cb5-15"><a href="#cb5-15"></a> gtk_drawing_area_set_draw_func <span class="op">(</span>da<span class="op">,</span> draw_cb<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">);</span></span>
<span id="cb5-16"><a href="#cb5-16"></a> cairo_set_source_rgb <span class="op">(</span>cr<span class="op">,</span> <span class="dv">0</span><span class="op">,</span> <span class="dv">1</span><span class="op">,</span> <span class="dv">0</span><span class="op">);</span></span> <span id="cb5-16"><a href="#cb5-16"></a> g_signal_connect_after <span class="op">(</span>da<span class="op">,</span> <span class="st">&quot;resize&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>resize_cb<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb5-17"><a href="#cb5-17"></a> <span class="cf">else</span> <span class="cf">if</span> <span class="op">(</span>g_strcmp0 <span class="op">(</span><span class="st">&quot;blue&quot;</span><span class="op">,</span> contents<span class="op">)</span> <span class="op">==</span> <span class="dv">0</span><span class="op">)</span></span> <span id="cb5-17"><a href="#cb5-17"></a></span>
<span id="cb5-18"><a href="#cb5-18"></a> cairo_set_source_rgb <span class="op">(</span>cr<span class="op">,</span> <span class="dv">0</span><span class="op">,</span> <span class="dv">0</span><span class="op">,</span> <span class="dv">1</span><span class="op">);</span></span> <span id="cb5-18"><a href="#cb5-18"></a> drag <span class="op">=</span> gtk_gesture_drag_new <span class="op">();</span></span>
<span id="cb5-19"><a href="#cb5-19"></a> <span class="cf">else</span> <span class="cf">if</span> <span class="op">(</span>g_strcmp0 <span class="op">(</span><span class="st">&quot;white&quot;</span><span class="op">,</span> contents<span class="op">)</span> <span class="op">==</span> <span class="dv">0</span><span class="op">)</span></span> <span id="cb5-19"><a href="#cb5-19"></a> gtk_gesture_single_set_button <span class="op">(</span>GTK_GESTURE_SINGLE <span class="op">(</span>drag<span class="op">),</span> GDK_BUTTON_PRIMARY<span class="op">);</span></span>
<span id="cb5-20"><a href="#cb5-20"></a> cairo_set_source_rgb <span class="op">(</span>cr<span class="op">,</span> <span class="dv">1</span><span class="op">,</span> <span class="dv">1</span><span class="op">,</span> <span class="dv">1</span><span class="op">);</span></span> <span id="cb5-20"><a href="#cb5-20"></a> gtk_widget_add_controller <span class="op">(</span>GTK_WIDGET <span class="op">(</span>da<span class="op">),</span> GTK_EVENT_CONTROLLER <span class="op">(</span>drag<span class="op">));</span></span>
<span id="cb5-21"><a href="#cb5-21"></a> <span class="cf">else</span> <span class="cf">if</span> <span class="op">(</span>g_strcmp0 <span class="op">(</span><span class="st">&quot;black&quot;</span><span class="op">,</span> contents<span class="op">)</span> <span class="op">==</span> <span class="dv">0</span><span class="op">)</span></span> <span id="cb5-21"><a href="#cb5-21"></a> g_signal_connect <span class="op">(</span>drag<span class="op">,</span> <span class="st">&quot;drag-begin&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>drag_begin<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb5-22"><a href="#cb5-22"></a> cairo_set_source_rgb <span class="op">(</span>cr<span class="op">,</span> <span class="dv">0</span><span class="op">,</span> <span class="dv">0</span><span class="op">,</span> <span class="dv">0</span><span class="op">);</span></span> <span id="cb5-22"><a href="#cb5-22"></a> g_signal_connect <span class="op">(</span>drag<span class="op">,</span> <span class="st">&quot;drag-update&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>drag_update<span class="op">),</span> da<span class="op">);</span></span>
<span id="cb5-23"><a href="#cb5-23"></a> <span class="cf">else</span> <span class="cf">if</span> <span class="op">(</span>g_strcmp0 <span class="op">(</span><span class="st">&quot;light&quot;</span><span class="op">,</span> contents<span class="op">)</span> <span class="op">==</span> <span class="dv">0</span><span class="op">)</span></span> <span id="cb5-23"><a href="#cb5-23"></a> g_signal_connect <span class="op">(</span>drag<span class="op">,</span> <span class="st">&quot;drag-end&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>drag_end<span class="op">),</span> da<span class="op">);</span></span>
<span id="cb5-24"><a href="#cb5-24"></a> cairo_set_source_rgba <span class="op">(</span>cr<span class="op">,</span> <span class="dv">1</span><span class="op">,</span> <span class="dv">1</span><span class="op">,</span> <span class="dv">1</span><span class="op">,</span> <span class="fl">0.5</span><span class="op">);</span></span> <span id="cb5-24"><a href="#cb5-24"></a><span class="op">}</span></span></code></pre></div>
<span id="cb5-25"><a href="#cb5-25"></a> <span class="cf">else</span> <span class="cf">if</span> <span class="op">(</span>g_strcmp0 <span class="op">(</span><span class="st">&quot;dark&quot;</span><span class="op">,</span> contents<span class="op">)</span> <span class="op">==</span> <span class="dv">0</span><span class="op">)</span></span> <p>The startup handler does three things.</p>
<span id="cb5-26"><a href="#cb5-26"></a> cairo_set_source_rgba <span class="op">(</span>cr<span class="op">,</span> <span class="dv">0</span><span class="op">,</span> <span class="dv">0</span><span class="op">,</span> <span class="dv">0</span><span class="op">,</span> <span class="fl">0.5</span><span class="op">);</span></span>
<span id="cb5-27"><a href="#cb5-27"></a> <span class="cf">else</span></span>
<span id="cb5-28"><a href="#cb5-28"></a> cairo_set_source_surface <span class="op">(</span>cr<span class="op">,</span> surface<span class="op">,</span> <span class="dv">0</span><span class="op">,</span> <span class="dv">0</span><span class="op">);</span></span>
<span id="cb5-29"><a href="#cb5-29"></a> cairo_paint <span class="op">(</span>cr<span class="op">);</span></span>
<span id="cb5-30"><a href="#cb5-30"></a> cairo_destroy <span class="op">(</span>cr<span class="op">);</span></span>
<span id="cb5-31"><a href="#cb5-31"></a> <span class="op">}</span></span>
<span id="cb5-32"><a href="#cb5-32"></a> g_free <span class="op">(</span>contents<span class="op">);</span></span>
<span id="cb5-33"><a href="#cb5-33"></a><span class="op">}</span></span></code></pre></div>
<ul> <ul>
<li>9-10: Gets the string in the GtkTextBuffer and inserts it to <li>Builds the widgets.</li>
<code>contents</code>.</li> <li>Initializes the GtkDrawingArea instance.
<li>11: If the variable <code>surface</code> points a surface instance,
it is painted as follows.</li>
<li>12- 30: The source is set based on the string <code>contents</code>
and copied to the surface with <code>cairo_paint</code>.</li>
<li>24,26: Alpha channel is used in “light” and “dark” procedure.</li>
</ul>
<p>The drawing area just reflects the <code>surface</code>. But one
problem is resizing. If a user resizes the main window, the drawing area
is also resized. It makes size difference between the surface and the
drawing area. So, the surface needs to be resized to fit the drawing
area.</p>
<p>It is accomplished by connecting the “resize” signal on the drawing
area to a handler.</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a>g_signal_connect <span class="op">(</span>GTK_DRAWING_AREA <span class="op">(</span>da<span class="op">),</span> <span class="st">&quot;resize&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>resize_cb<span class="op">),</span> NULL<span class="op">);</span></span></code></pre></div>
<p>The handler is as follows.</p>
<div class="sourceCode" id="cb7"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb7-1"><a href="#cb7-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb7-2"><a href="#cb7-2"></a>resize_cb <span class="op">(</span>GtkDrawingArea <span class="op">*</span>drawing_area<span class="op">,</span> <span class="dt">int</span> width<span class="op">,</span> <span class="dt">int</span> height<span class="op">,</span> gpointer user_data<span class="op">)</span> <span class="op">{</span></span>
<span id="cb7-3"><a href="#cb7-3"></a> <span class="cf">if</span> <span class="op">(</span>surface<span class="op">)</span></span>
<span id="cb7-4"><a href="#cb7-4"></a> cairo_surface_destroy <span class="op">(</span>surface<span class="op">);</span></span>
<span id="cb7-5"><a href="#cb7-5"></a> surface <span class="op">=</span> cairo_image_surface_create <span class="op">(</span>CAIRO_FORMAT_ARGB32<span class="op">,</span> width<span class="op">,</span> height<span class="op">);</span></span>
<span id="cb7-6"><a href="#cb7-6"></a> run <span class="op">();</span></span>
<span id="cb7-7"><a href="#cb7-7"></a><span class="op">}</span></span></code></pre></div>
<p>If the variable <code>surface</code> sets a surface instance, it is
destroyed. A new surface is created and its size fits the drawing area.
The surface is assigned to the variable <code>surface</code>. The
function <code>run</code> is called and the surface is colored.</p>
<p>The signal is emitted when:</p>
<ul> <ul>
<li>The drawing area is realized (it appears on the display).</li> <li>Sets the drawing function</li>
<li>It is changed (resized) while realized</li> <li>Connects the “resize” signal and the handler.</li>
</ul></li>
<li>Creates the GtkGestureDrag instance and initializes it. Gesture will
be explained in this section later.</li>
</ul> </ul>
<p>So, the first surface is created when it is realized.</p> <div class="sourceCode" id="cb6"><pre
<h2 id="colorapplication.c">Colorapplication.c</h2> class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb6-1"><a href="#cb6-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<p>This is the main file.</p> <span id="cb6-2"><a href="#cb6-2"></a>app_activate <span class="op">(</span>GApplication <span class="op">*</span>application<span class="op">)</span> <span class="op">{</span></span>
<span id="cb6-3"><a href="#cb6-3"></a> GtkApplication <span class="op">*</span>app <span class="op">=</span> GTK_APPLICATION <span class="op">(</span>application<span class="op">);</span></span>
<span id="cb6-4"><a href="#cb6-4"></a> GtkWindow <span class="op">*</span>win<span class="op">;</span></span>
<span id="cb6-5"><a href="#cb6-5"></a></span>
<span id="cb6-6"><a href="#cb6-6"></a> win <span class="op">=</span> gtk_application_get_active_window <span class="op">(</span>app<span class="op">);</span></span>
<span id="cb6-7"><a href="#cb6-7"></a> gtk_window_present <span class="op">(</span>win<span class="op">);</span></span>
<span id="cb6-8"><a href="#cb6-8"></a><span class="op">}</span></span></code></pre></div>
<p>The activate handler just shows the window.</p>
<h3 id="gtkdrawingarea">GtkDrawingArea</h3>
<p>The program has two cairo surfaces and they are pointed by the global
variables.</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode C"><code class="sourceCode c"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="dt">static</span> cairo_surface_t <span class="op">*</span>surface <span class="op">=</span> NULL<span class="op">;</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a><span class="dt">static</span> cairo_surface_t <span class="op">*</span>surface_save <span class="op">=</span> NULL<span class="op">;</span></span></code></pre></div>
<p>The drawing process is as follows.</p>
<ul> <ul>
<li>Builds widgets by GtkBuilder.</li> <li>Creates an image on <code>surface</code>.</li>
<li>Sets a drawing function for GtkDrawingArea. And connects a handler <li>Copies <code>surface</code> to the cairo surface of the
to the “resize” signal on the GtkDrawingArea instance.</li> GtkDrawingArea.</li>
<li>Implements each call back function. Particularly, <code>Run</code> <li>Calls <code>gtk_widget_queue_draw (da)</code> to draw it if
signal handler is the point in this program.</li> necessary.</li>
</ul> </ul>
<p>The following is <code>colorapplication.c</code>.</p> <p>They are created in the “resize” signal handler.</p>
<div class="sourceCode" id="cb8"><pre <div class="sourceCode" id="cb8"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb8-1"><a href="#cb8-1"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span> class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb8-1"><a href="#cb8-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb8-2"><a href="#cb8-2"></a><span class="pp">#include </span><span class="im">&quot;../tfetextview/tfetextview.h&quot;</span></span> <span id="cb8-2"><a href="#cb8-2"></a>resize_cb <span class="op">(</span>GtkWidget <span class="op">*</span>widget<span class="op">,</span> <span class="dt">int</span> width<span class="op">,</span> <span class="dt">int</span> height<span class="op">,</span> gpointer user_data<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-3"><a href="#cb8-3"></a></span> <span id="cb8-3"><a href="#cb8-3"></a> cairo_t <span class="op">*</span>cr<span class="op">;</span></span>
<span id="cb8-4"><a href="#cb8-4"></a><span class="dt">static</span> GtkWidget <span class="op">*</span>win<span class="op">;</span></span> <span id="cb8-4"><a href="#cb8-4"></a></span>
<span id="cb8-5"><a href="#cb8-5"></a><span class="dt">static</span> GtkWidget <span class="op">*</span>tv<span class="op">;</span></span> <span id="cb8-5"><a href="#cb8-5"></a> <span class="cf">if</span> <span class="op">(</span>surface<span class="op">)</span></span>
<span id="cb8-6"><a href="#cb8-6"></a><span class="dt">static</span> GtkWidget <span class="op">*</span>da<span class="op">;</span></span> <span id="cb8-6"><a href="#cb8-6"></a> cairo_surface_destroy <span class="op">(</span>surface<span class="op">);</span></span>
<span id="cb8-7"><a href="#cb8-7"></a></span> <span id="cb8-7"><a href="#cb8-7"></a> surface <span class="op">=</span> cairo_image_surface_create <span class="op">(</span>CAIRO_FORMAT_RGB24<span class="op">,</span> width<span class="op">,</span> height<span class="op">);</span></span>
<span id="cb8-8"><a href="#cb8-8"></a><span class="dt">static</span> cairo_surface_t <span class="op">*</span>surface <span class="op">=</span> NULL<span class="op">;</span></span> <span id="cb8-8"><a href="#cb8-8"></a> <span class="cf">if</span> <span class="op">(</span>surface_save<span class="op">)</span></span>
<span id="cb8-9"><a href="#cb8-9"></a></span> <span id="cb8-9"><a href="#cb8-9"></a> cairo_surface_destroy <span class="op">(</span>surface_save<span class="op">);</span></span>
<span id="cb8-10"><a href="#cb8-10"></a><span class="dt">static</span> <span class="dt">void</span></span> <span id="cb8-10"><a href="#cb8-10"></a> surface_save <span class="op">=</span> cairo_image_surface_create <span class="op">(</span>CAIRO_FORMAT_RGB24<span class="op">,</span> width<span class="op">,</span> height<span class="op">);</span></span>
<span id="cb8-11"><a href="#cb8-11"></a>run <span class="op">(</span><span class="dt">void</span><span class="op">)</span> <span class="op">{</span></span> <span id="cb8-11"><a href="#cb8-11"></a> <span class="co">/* Paint the surface white. It is the background color. */</span></span>
<span id="cb8-12"><a href="#cb8-12"></a> GtkTextBuffer <span class="op">*</span>tb <span class="op">=</span> gtk_text_view_get_buffer <span class="op">(</span>GTK_TEXT_VIEW <span class="op">(</span>tv<span class="op">));</span></span> <span id="cb8-12"><a href="#cb8-12"></a> cr <span class="op">=</span> cairo_create <span class="op">(</span>surface<span class="op">);</span></span>
<span id="cb8-13"><a href="#cb8-13"></a> GtkTextIter start_iter<span class="op">;</span></span> <span id="cb8-13"><a href="#cb8-13"></a> cairo_set_source_rgb <span class="op">(</span>cr<span class="op">,</span> <span class="fl">1.0</span><span class="op">,</span> <span class="fl">1.0</span><span class="op">,</span> <span class="fl">1.0</span><span class="op">);</span></span>
<span id="cb8-14"><a href="#cb8-14"></a> GtkTextIter end_iter<span class="op">;</span></span> <span id="cb8-14"><a href="#cb8-14"></a> cairo_paint <span class="op">(</span>cr<span class="op">);</span></span>
<span id="cb8-15"><a href="#cb8-15"></a> <span class="dt">char</span> <span class="op">*</span>contents<span class="op">;</span></span> <span id="cb8-15"><a href="#cb8-15"></a> cairo_destroy <span class="op">(</span>cr<span class="op">);</span></span>
<span id="cb8-16"><a href="#cb8-16"></a> cairo_t <span class="op">*</span>cr<span class="op">;</span></span> <span id="cb8-16"><a href="#cb8-16"></a><span class="op">}</span></span></code></pre></div>
<span id="cb8-17"><a href="#cb8-17"></a></span> <p>This callback is called when the GtkDrawingArea is shown. It is the
<span id="cb8-18"><a href="#cb8-18"></a> gtk_text_buffer_get_bounds <span class="op">(</span>tb<span class="op">,</span> <span class="op">&amp;</span>start_iter<span class="op">,</span> <span class="op">&amp;</span>end_iter<span class="op">);</span></span> only call because the window is not resizable.</p>
<span id="cb8-19"><a href="#cb8-19"></a> contents <span class="op">=</span> gtk_text_buffer_get_text <span class="op">(</span>tb<span class="op">,</span> <span class="op">&amp;</span>start_iter<span class="op">,</span> <span class="op">&amp;</span>end_iter<span class="op">,</span> FALSE<span class="op">);</span></span> <p>It creates image surfaces for <code>surface</code> and
<span id="cb8-20"><a href="#cb8-20"></a> <span class="cf">if</span> <span class="op">(</span>surface<span class="op">)</span> <span class="op">{</span></span> <code>surface_save</code>. The <code>surface</code> surface is painted
<span id="cb8-21"><a href="#cb8-21"></a> cr <span class="op">=</span> cairo_create <span class="op">(</span>surface<span class="op">);</span></span> white, which is the background color.</p>
<span id="cb8-22"><a href="#cb8-22"></a> <span class="cf">if</span> <span class="op">(</span>g_strcmp0 <span class="op">(</span><span class="st">&quot;red&quot;</span><span class="op">,</span> contents<span class="op">)</span> <span class="op">==</span> <span class="dv">0</span><span class="op">)</span></span> <p>The drawing function copies <code>surface</code> to the
<span id="cb8-23"><a href="#cb8-23"></a> cairo_set_source_rgb <span class="op">(</span>cr<span class="op">,</span> <span class="dv">1</span><span class="op">,</span> <span class="dv">0</span><span class="op">,</span> <span class="dv">0</span><span class="op">);</span></span> GtkDrawingArea surface.</p>
<span id="cb8-24"><a href="#cb8-24"></a> <span class="cf">else</span> <span class="cf">if</span> <span class="op">(</span>g_strcmp0 <span class="op">(</span><span class="st">&quot;green&quot;</span><span class="op">,</span> contents<span class="op">)</span> <span class="op">==</span> <span class="dv">0</span><span class="op">)</span></span>
<span id="cb8-25"><a href="#cb8-25"></a> cairo_set_source_rgb <span class="op">(</span>cr<span class="op">,</span> <span class="dv">0</span><span class="op">,</span> <span class="dv">1</span><span class="op">,</span> <span class="dv">0</span><span class="op">);</span></span>
<span id="cb8-26"><a href="#cb8-26"></a> <span class="cf">else</span> <span class="cf">if</span> <span class="op">(</span>g_strcmp0 <span class="op">(</span><span class="st">&quot;blue&quot;</span><span class="op">,</span> contents<span class="op">)</span> <span class="op">==</span> <span class="dv">0</span><span class="op">)</span></span>
<span id="cb8-27"><a href="#cb8-27"></a> cairo_set_source_rgb <span class="op">(</span>cr<span class="op">,</span> <span class="dv">0</span><span class="op">,</span> <span class="dv">0</span><span class="op">,</span> <span class="dv">1</span><span class="op">);</span></span>
<span id="cb8-28"><a href="#cb8-28"></a> <span class="cf">else</span> <span class="cf">if</span> <span class="op">(</span>g_strcmp0 <span class="op">(</span><span class="st">&quot;white&quot;</span><span class="op">,</span> contents<span class="op">)</span> <span class="op">==</span> <span class="dv">0</span><span class="op">)</span></span>
<span id="cb8-29"><a href="#cb8-29"></a> cairo_set_source_rgb <span class="op">(</span>cr<span class="op">,</span> <span class="dv">1</span><span class="op">,</span> <span class="dv">1</span><span class="op">,</span> <span class="dv">1</span><span class="op">);</span></span>
<span id="cb8-30"><a href="#cb8-30"></a> <span class="cf">else</span> <span class="cf">if</span> <span class="op">(</span>g_strcmp0 <span class="op">(</span><span class="st">&quot;black&quot;</span><span class="op">,</span> contents<span class="op">)</span> <span class="op">==</span> <span class="dv">0</span><span class="op">)</span></span>
<span id="cb8-31"><a href="#cb8-31"></a> cairo_set_source_rgb <span class="op">(</span>cr<span class="op">,</span> <span class="dv">0</span><span class="op">,</span> <span class="dv">0</span><span class="op">,</span> <span class="dv">0</span><span class="op">);</span></span>
<span id="cb8-32"><a href="#cb8-32"></a> <span class="cf">else</span> <span class="cf">if</span> <span class="op">(</span>g_strcmp0 <span class="op">(</span><span class="st">&quot;light&quot;</span><span class="op">,</span> contents<span class="op">)</span> <span class="op">==</span> <span class="dv">0</span><span class="op">)</span></span>
<span id="cb8-33"><a href="#cb8-33"></a> cairo_set_source_rgba <span class="op">(</span>cr<span class="op">,</span> <span class="dv">1</span><span class="op">,</span> <span class="dv">1</span><span class="op">,</span> <span class="dv">1</span><span class="op">,</span> <span class="fl">0.5</span><span class="op">);</span></span>
<span id="cb8-34"><a href="#cb8-34"></a> <span class="cf">else</span> <span class="cf">if</span> <span class="op">(</span>g_strcmp0 <span class="op">(</span><span class="st">&quot;dark&quot;</span><span class="op">,</span> contents<span class="op">)</span> <span class="op">==</span> <span class="dv">0</span><span class="op">)</span></span>
<span id="cb8-35"><a href="#cb8-35"></a> cairo_set_source_rgba <span class="op">(</span>cr<span class="op">,</span> <span class="dv">0</span><span class="op">,</span> <span class="dv">0</span><span class="op">,</span> <span class="dv">0</span><span class="op">,</span> <span class="fl">0.5</span><span class="op">);</span></span>
<span id="cb8-36"><a href="#cb8-36"></a> <span class="cf">else</span></span>
<span id="cb8-37"><a href="#cb8-37"></a> cairo_set_source_surface <span class="op">(</span>cr<span class="op">,</span> surface<span class="op">,</span> <span class="dv">0</span><span class="op">,</span> <span class="dv">0</span><span class="op">);</span></span>
<span id="cb8-38"><a href="#cb8-38"></a> cairo_paint <span class="op">(</span>cr<span class="op">);</span></span>
<span id="cb8-39"><a href="#cb8-39"></a> cairo_destroy <span class="op">(</span>cr<span class="op">);</span></span>
<span id="cb8-40"><a href="#cb8-40"></a> <span class="op">}</span></span>
<span id="cb8-41"><a href="#cb8-41"></a> g_free <span class="op">(</span>contents<span class="op">);</span></span>
<span id="cb8-42"><a href="#cb8-42"></a><span class="op">}</span></span>
<span id="cb8-43"><a href="#cb8-43"></a></span>
<span id="cb8-44"><a href="#cb8-44"></a><span class="dt">void</span></span>
<span id="cb8-45"><a href="#cb8-45"></a>run_cb <span class="op">(</span>GtkWidget <span class="op">*</span>btnr<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-46"><a href="#cb8-46"></a> run <span class="op">();</span></span>
<span id="cb8-47"><a href="#cb8-47"></a> gtk_widget_queue_draw <span class="op">(</span>GTK_WIDGET <span class="op">(</span>da<span class="op">));</span></span>
<span id="cb8-48"><a href="#cb8-48"></a><span class="op">}</span></span>
<span id="cb8-49"><a href="#cb8-49"></a></span>
<span id="cb8-50"><a href="#cb8-50"></a><span class="dt">void</span></span>
<span id="cb8-51"><a href="#cb8-51"></a>open_cb <span class="op">(</span>GtkWidget <span class="op">*</span>btno<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-52"><a href="#cb8-52"></a> tfe_text_view_open <span class="op">(</span>TFE_TEXT_VIEW <span class="op">(</span>tv<span class="op">),</span> GTK_WINDOW <span class="op">(</span>win<span class="op">));</span></span>
<span id="cb8-53"><a href="#cb8-53"></a><span class="op">}</span></span>
<span id="cb8-54"><a href="#cb8-54"></a></span>
<span id="cb8-55"><a href="#cb8-55"></a><span class="dt">void</span></span>
<span id="cb8-56"><a href="#cb8-56"></a>save_cb <span class="op">(</span>GtkWidget <span class="op">*</span>btns<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-57"><a href="#cb8-57"></a> tfe_text_view_save <span class="op">(</span>TFE_TEXT_VIEW <span class="op">(</span>tv<span class="op">));</span></span>
<span id="cb8-58"><a href="#cb8-58"></a><span class="op">}</span></span>
<span id="cb8-59"><a href="#cb8-59"></a></span>
<span id="cb8-60"><a href="#cb8-60"></a><span class="dt">void</span></span>
<span id="cb8-61"><a href="#cb8-61"></a>close_cb <span class="op">(</span>GtkWidget <span class="op">*</span>btnc<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-62"><a href="#cb8-62"></a> gtk_window_destroy <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">));</span></span>
<span id="cb8-63"><a href="#cb8-63"></a><span class="op">}</span></span>
<span id="cb8-64"><a href="#cb8-64"></a></span>
<span id="cb8-65"><a href="#cb8-65"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb8-66"><a href="#cb8-66"></a>resize_cb <span class="op">(</span>GtkDrawingArea <span class="op">*</span>drawing_area<span class="op">,</span> <span class="dt">int</span> width<span class="op">,</span> <span class="dt">int</span> height<span class="op">,</span> gpointer user_data<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-67"><a href="#cb8-67"></a> <span class="cf">if</span> <span class="op">(</span>surface<span class="op">)</span></span>
<span id="cb8-68"><a href="#cb8-68"></a> cairo_surface_destroy <span class="op">(</span>surface<span class="op">);</span></span>
<span id="cb8-69"><a href="#cb8-69"></a> surface <span class="op">=</span> cairo_image_surface_create <span class="op">(</span>CAIRO_FORMAT_ARGB32<span class="op">,</span> width<span class="op">,</span> height<span class="op">);</span></span>
<span id="cb8-70"><a href="#cb8-70"></a> run <span class="op">();</span></span>
<span id="cb8-71"><a href="#cb8-71"></a><span class="op">}</span></span>
<span id="cb8-72"><a href="#cb8-72"></a></span>
<span id="cb8-73"><a href="#cb8-73"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb8-74"><a href="#cb8-74"></a>draw_func <span class="op">(</span>GtkDrawingArea <span class="op">*</span>drawing_area<span class="op">,</span> cairo_t <span class="op">*</span>cr<span class="op">,</span> <span class="dt">int</span> width<span class="op">,</span> <span class="dt">int</span> height<span class="op">,</span> gpointer user_data<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-75"><a href="#cb8-75"></a> <span class="cf">if</span> <span class="op">(</span>surface<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-76"><a href="#cb8-76"></a> cairo_set_source_surface <span class="op">(</span>cr<span class="op">,</span> surface<span class="op">,</span> <span class="dv">0</span><span class="op">,</span> <span class="dv">0</span><span class="op">);</span></span>
<span id="cb8-77"><a href="#cb8-77"></a> cairo_paint <span class="op">(</span>cr<span class="op">);</span></span>
<span id="cb8-78"><a href="#cb8-78"></a> <span class="op">}</span></span>
<span id="cb8-79"><a href="#cb8-79"></a><span class="op">}</span></span>
<span id="cb8-80"><a href="#cb8-80"></a></span>
<span id="cb8-81"><a href="#cb8-81"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb8-82"><a href="#cb8-82"></a>app_activate <span class="op">(</span>GApplication <span class="op">*</span>application<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-83"><a href="#cb8-83"></a> gtk_window_present <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">));</span></span>
<span id="cb8-84"><a href="#cb8-84"></a><span class="op">}</span></span>
<span id="cb8-85"><a href="#cb8-85"></a></span>
<span id="cb8-86"><a href="#cb8-86"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb8-87"><a href="#cb8-87"></a>app_startup <span class="op">(</span>GApplication <span class="op">*</span>application<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-88"><a href="#cb8-88"></a> GtkApplication <span class="op">*</span>app <span class="op">=</span> GTK_APPLICATION <span class="op">(</span>application<span class="op">);</span></span>
<span id="cb8-89"><a href="#cb8-89"></a> GtkBuilder <span class="op">*</span>build<span class="op">;</span></span>
<span id="cb8-90"><a href="#cb8-90"></a> GdkDisplay <span class="op">*</span>display<span class="op">;</span></span>
<span id="cb8-91"><a href="#cb8-91"></a></span>
<span id="cb8-92"><a href="#cb8-92"></a> build <span class="op">=</span> gtk_builder_new_from_resource <span class="op">(</span><span class="st">&quot;/com/github/ToshioCP/color/color.ui&quot;</span><span class="op">);</span></span>
<span id="cb8-93"><a href="#cb8-93"></a> win <span class="op">=</span> GTK_WIDGET <span class="op">(</span>gtk_builder_get_object <span class="op">(</span>build<span class="op">,</span> <span class="st">&quot;win&quot;</span><span class="op">));</span></span>
<span id="cb8-94"><a href="#cb8-94"></a> gtk_window_set_application <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">),</span> app<span class="op">);</span></span>
<span id="cb8-95"><a href="#cb8-95"></a> tv <span class="op">=</span> GTK_WIDGET <span class="op">(</span>gtk_builder_get_object <span class="op">(</span>build<span class="op">,</span> <span class="st">&quot;tv&quot;</span><span class="op">));</span></span>
<span id="cb8-96"><a href="#cb8-96"></a> da <span class="op">=</span> GTK_WIDGET <span class="op">(</span>gtk_builder_get_object <span class="op">(</span>build<span class="op">,</span> <span class="st">&quot;da&quot;</span><span class="op">));</span></span>
<span id="cb8-97"><a href="#cb8-97"></a> g_object_unref<span class="op">(</span>build<span class="op">);</span></span>
<span id="cb8-98"><a href="#cb8-98"></a> g_signal_connect <span class="op">(</span>GTK_DRAWING_AREA <span class="op">(</span>da<span class="op">),</span> <span class="st">&quot;resize&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>resize_cb<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb8-99"><a href="#cb8-99"></a> gtk_drawing_area_set_draw_func <span class="op">(</span>GTK_DRAWING_AREA <span class="op">(</span>da<span class="op">),</span> draw_func<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">);</span></span>
<span id="cb8-100"><a href="#cb8-100"></a></span>
<span id="cb8-101"><a href="#cb8-101"></a> display <span class="op">=</span> gdk_display_get_default <span class="op">();</span></span>
<span id="cb8-102"><a href="#cb8-102"></a> GtkCssProvider <span class="op">*</span>provider <span class="op">=</span> gtk_css_provider_new <span class="op">();</span></span>
<span id="cb8-103"><a href="#cb8-103"></a> gtk_css_provider_load_from_data <span class="op">(</span>provider<span class="op">,</span> <span class="st">&quot;textview {padding: 10px; font-family: monospace; font-size: 12pt;}&quot;</span><span class="op">,</span> <span class="op">-</span><span class="dv">1</span><span class="op">);</span></span>
<span id="cb8-104"><a href="#cb8-104"></a> gtk_style_context_add_provider_for_display <span class="op">(</span>display<span class="op">,</span> GTK_STYLE_PROVIDER <span class="op">(</span>provider<span class="op">),</span> GTK_STYLE_PROVIDER_PRIORITY_USER<span class="op">);</span></span>
<span id="cb8-105"><a href="#cb8-105"></a><span class="op">}</span></span>
<span id="cb8-106"><a href="#cb8-106"></a></span>
<span id="cb8-107"><a href="#cb8-107"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb8-108"><a href="#cb8-108"></a>app_shutdown <span class="op">(</span>GApplication <span class="op">*</span>application<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-109"><a href="#cb8-109"></a> <span class="cf">if</span> <span class="op">(</span>surface<span class="op">)</span></span>
<span id="cb8-110"><a href="#cb8-110"></a> cairo_surface_destroy <span class="op">(</span>surface<span class="op">);</span></span>
<span id="cb8-111"><a href="#cb8-111"></a><span class="op">}</span></span>
<span id="cb8-112"><a href="#cb8-112"></a></span>
<span id="cb8-113"><a href="#cb8-113"></a><span class="pp">#define APPLICATION_ID &quot;com.github.ToshioCP.color&quot;</span></span>
<span id="cb8-114"><a href="#cb8-114"></a></span>
<span id="cb8-115"><a href="#cb8-115"></a><span class="dt">int</span></span>
<span id="cb8-116"><a href="#cb8-116"></a>main <span class="op">(</span><span class="dt">int</span> argc<span class="op">,</span> <span class="dt">char</span> <span class="op">**</span>argv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-117"><a href="#cb8-117"></a> GtkApplication <span class="op">*</span>app<span class="op">;</span></span>
<span id="cb8-118"><a href="#cb8-118"></a> <span class="dt">int</span> stat<span class="op">;</span></span>
<span id="cb8-119"><a href="#cb8-119"></a></span>
<span id="cb8-120"><a href="#cb8-120"></a> app <span class="op">=</span> gtk_application_new <span class="op">(</span>APPLICATION_ID<span class="op">,</span> G_APPLICATION_DEFAULT_FLAGS<span class="op">);</span></span>
<span id="cb8-121"><a href="#cb8-121"></a></span>
<span id="cb8-122"><a href="#cb8-122"></a> g_signal_connect <span class="op">(</span>app<span class="op">,</span> <span class="st">&quot;startup&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>app_startup<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb8-123"><a href="#cb8-123"></a> g_signal_connect <span class="op">(</span>app<span class="op">,</span> <span class="st">&quot;shutdown&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>app_shutdown<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb8-124"><a href="#cb8-124"></a> g_signal_connect <span class="op">(</span>app<span class="op">,</span> <span class="st">&quot;activate&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>app_activate<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb8-125"><a href="#cb8-125"></a></span>
<span id="cb8-126"><a href="#cb8-126"></a> stat <span class="op">=</span>g_application_run <span class="op">(</span>G_APPLICATION <span class="op">(</span>app<span class="op">),</span> argc<span class="op">,</span> argv<span class="op">);</span></span>
<span id="cb8-127"><a href="#cb8-127"></a> g_object_unref <span class="op">(</span>app<span class="op">);</span></span>
<span id="cb8-128"><a href="#cb8-128"></a> <span class="cf">return</span> stat<span class="op">;</span></span>
<span id="cb8-129"><a href="#cb8-129"></a><span class="op">}</span></span></code></pre></div>
<ul>
<li>4-8: Win, tv, da and surface are defined as static variables.</li>
<li>10-42: Run function.</li>
<li>44-63: Handlers for button signals.</li>
<li>65-71: Resize handler.</li>
<li>73-79: Drawing function.</li>
<li>81-84: Application activate handler. It just shows the main
window.</li>
<li>86-105: Application startup handler.</li>
<li>92- 97: It builds widgets according to the ui resource. The static
variables win, tv and da are assigned instances.</li>
<li>98: Connects “resize” signal and a handler.</li>
<li>99: Drawing function is set.</li>
<li>101-104: CSS for textview padding is set.</li>
<li>107-111: Application shutdown handler. If there exists a surface
instance, it will be destroyed.</li>
<li>116-129: A function <code>main</code>. It creates a new application
instance. And connects three signals startup, shutdown and activate to
their handlers. It runs the application. It releases the reference to
the application and returns with <code>stat</code> value.</li>
</ul>
<h2 id="meson.build">Meson.build</h2>
<p>This file is almost same as before. An argument “export_dynamic:
true” is added to executable function.</p>
<div class="sourceCode" id="cb9"><pre <div class="sourceCode" id="cb9"><pre
class="sourceCode numberSource numberLines"><code class="sourceCode"><span id="cb9-1"><a href="#cb9-1"></a>project(&#39;color&#39;, &#39;c&#39;)</span> class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb9-1"><a href="#cb9-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb9-2"><a href="#cb9-2"></a></span> <span id="cb9-2"><a href="#cb9-2"></a>draw_cb <span class="op">(</span>GtkDrawingArea <span class="op">*</span>da<span class="op">,</span> cairo_t <span class="op">*</span>cr<span class="op">,</span> <span class="dt">int</span> width<span class="op">,</span> <span class="dt">int</span> height<span class="op">,</span> gpointer user_data<span class="op">)</span> <span class="op">{</span></span>
<span id="cb9-3"><a href="#cb9-3"></a>gtkdep = dependency(&#39;gtk4&#39;)</span> <span id="cb9-3"><a href="#cb9-3"></a> <span class="cf">if</span> <span class="op">(</span>surface<span class="op">)</span> <span class="op">{</span></span>
<span id="cb9-4"><a href="#cb9-4"></a></span> <span id="cb9-4"><a href="#cb9-4"></a> cairo_set_source_surface <span class="op">(</span>cr<span class="op">,</span> surface<span class="op">,</span> <span class="fl">0.0</span><span class="op">,</span> <span class="fl">0.0</span><span class="op">);</span></span>
<span id="cb9-5"><a href="#cb9-5"></a>gnome=import(&#39;gnome&#39;)</span> <span id="cb9-5"><a href="#cb9-5"></a> cairo_paint <span class="op">(</span>cr<span class="op">);</span></span>
<span id="cb9-6"><a href="#cb9-6"></a>resources = gnome.compile_resources(&#39;resources&#39;,&#39;color.gresource.xml&#39;)</span> <span id="cb9-6"><a href="#cb9-6"></a> <span class="op">}</span></span>
<span id="cb9-7"><a href="#cb9-7"></a></span> <span id="cb9-7"><a href="#cb9-7"></a><span class="op">}</span></span></code></pre></div>
<span id="cb9-8"><a href="#cb9-8"></a>sourcefiles=files(&#39;colorapplication.c&#39;, &#39;../tfetextview/tfetextview.c&#39;)</span> <p>This function is called by the system when it needs to redraw the
<span id="cb9-9"><a href="#cb9-9"></a></span> drawing area.</p>
<span id="cb9-10"><a href="#cb9-10"></a>executable(&#39;color&#39;, sourcefiles, resources, dependencies: gtkdep, export_dynamic: true)</span></code></pre></div> <p>Two surfaces <code>surface</code> and <code>surface_save</code> are
<h2 id="build-and-try">Build and try</h2> destroyed before the application quits.</p>
<p>Type the following to compile the program.</p> <div class="sourceCode" id="cb10"><pre
<pre><code>$ meson _build class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb10-1"><a href="#cb10-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
$ ninja -C _build</code></pre> <span id="cb10-2"><a href="#cb10-2"></a>app_shutdown <span class="op">(</span>GApplication <span class="op">*</span>application<span class="op">)</span> <span class="op">{</span></span>
<p>The application is made in <code>_build</code> directory. Type the <span id="cb10-3"><a href="#cb10-3"></a> <span class="cf">if</span> <span class="op">(</span>surface<span class="op">)</span></span>
following to execute it.</p> <span id="cb10-4"><a href="#cb10-4"></a> cairo_surface_destroy <span class="op">(</span>surface<span class="op">);</span></span>
<pre><code>$ _build/color</code></pre> <span id="cb10-5"><a href="#cb10-5"></a> <span class="cf">if</span> <span class="op">(</span>surface_save<span class="op">)</span></span>
<p>Type “red”, “green”, “blue”, “white”, black”, “light” or “dark” in <span id="cb10-6"><a href="#cb10-6"></a> cairo_surface_destroy <span class="op">(</span>surface_save<span class="op">);</span></span>
the TfeTextView. No new line charactor is needed. Then, click on the <span id="cb10-7"><a href="#cb10-7"></a><span class="op">}</span></span></code></pre></div>
<code>Run</code> button. Make sure the color of GtkDrawingArea <h3 id="gtkgesturedrag">GtkGestureDrag</h3>
changes.</p> <p>Gesture class is used to recognize human gestures such as click,
<p>In this program TfeTextView is used to change the color. You can use drag, pan, swipe and so on. It is a subclass of GtkEventController.
buttons or menus instead of textview. Probably it is more appropriate. GtkGesture class is abstract and there are several implementations.</p>
Using textview is unnatural. It is a good practice to make such <ul>
application by yourself.</p> <li>GtkGestureClick</li>
<li>GtkGestureDrag</li>
<li>GtkGesturePan</li>
<li>GtkGestureSwipe</li>
<li>other implementations</li>
</ul>
<p>The program <code>rect.c</code> uses GtkGestureDrag. It is the
implementation for drags. The parent-child relationship is as
follows.</p>
<pre><code>GObject -- GtkEventController -- GtkGesture -- GtkGestureSingle -- GtkGestureDrag</code></pre>
<p>GtkGestureSingle is a subclass of GtkGesture and optimized for
singe-touch and mouse gestures.</p>
<p>A GtkGestureDrag instance is created and initialized in the startup
signal handler in <code>rect.c</code>. See line 18 to 23 in the
following.</p>
<div class="sourceCode" id="cb12"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb12-1"><a href="#cb12-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb12-2"><a href="#cb12-2"></a>app_startup <span class="op">(</span>GApplication <span class="op">*</span>application<span class="op">)</span> <span class="op">{</span></span>
<span id="cb12-3"><a href="#cb12-3"></a> GtkApplication <span class="op">*</span>app <span class="op">=</span> GTK_APPLICATION <span class="op">(</span>application<span class="op">);</span></span>
<span id="cb12-4"><a href="#cb12-4"></a> GtkBuilder <span class="op">*</span>build<span class="op">;</span></span>
<span id="cb12-5"><a href="#cb12-5"></a> GtkWindow <span class="op">*</span>win<span class="op">;</span></span>
<span id="cb12-6"><a href="#cb12-6"></a> GtkDrawingArea <span class="op">*</span>da<span class="op">;</span></span>
<span id="cb12-7"><a href="#cb12-7"></a> GtkGesture <span class="op">*</span>drag<span class="op">;</span></span>
<span id="cb12-8"><a href="#cb12-8"></a></span>
<span id="cb12-9"><a href="#cb12-9"></a> build <span class="op">=</span> gtk_builder_new_from_resource <span class="op">(</span><span class="st">&quot;/com/github/ToshioCP/rect/rect.ui&quot;</span><span class="op">);</span></span>
<span id="cb12-10"><a href="#cb12-10"></a> win <span class="op">=</span> GTK_WINDOW <span class="op">(</span>gtk_builder_get_object <span class="op">(</span>build<span class="op">,</span> <span class="st">&quot;win&quot;</span><span class="op">));</span></span>
<span id="cb12-11"><a href="#cb12-11"></a> da <span class="op">=</span> GTK_DRAWING_AREA <span class="op">(</span>gtk_builder_get_object <span class="op">(</span>build<span class="op">,</span> <span class="st">&quot;da&quot;</span><span class="op">));</span></span>
<span id="cb12-12"><a href="#cb12-12"></a> gtk_window_set_application <span class="op">(</span>win<span class="op">,</span> app<span class="op">);</span></span>
<span id="cb12-13"><a href="#cb12-13"></a> g_object_unref <span class="op">(</span>build<span class="op">);</span></span>
<span id="cb12-14"><a href="#cb12-14"></a></span>
<span id="cb12-15"><a href="#cb12-15"></a> gtk_drawing_area_set_draw_func <span class="op">(</span>da<span class="op">,</span> draw_cb<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">);</span></span>
<span id="cb12-16"><a href="#cb12-16"></a> g_signal_connect_after <span class="op">(</span>da<span class="op">,</span> <span class="st">&quot;resize&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>resize_cb<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb12-17"><a href="#cb12-17"></a></span>
<span id="cb12-18"><a href="#cb12-18"></a> drag <span class="op">=</span> gtk_gesture_drag_new <span class="op">();</span></span>
<span id="cb12-19"><a href="#cb12-19"></a> gtk_gesture_single_set_button <span class="op">(</span>GTK_GESTURE_SINGLE <span class="op">(</span>drag<span class="op">),</span> GDK_BUTTON_PRIMARY<span class="op">);</span></span>
<span id="cb12-20"><a href="#cb12-20"></a> gtk_widget_add_controller <span class="op">(</span>GTK_WIDGET <span class="op">(</span>da<span class="op">),</span> GTK_EVENT_CONTROLLER <span class="op">(</span>drag<span class="op">));</span></span>
<span id="cb12-21"><a href="#cb12-21"></a> g_signal_connect <span class="op">(</span>drag<span class="op">,</span> <span class="st">&quot;drag-begin&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>drag_begin<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb12-22"><a href="#cb12-22"></a> g_signal_connect <span class="op">(</span>drag<span class="op">,</span> <span class="st">&quot;drag-update&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>drag_update<span class="op">),</span> da<span class="op">);</span></span>
<span id="cb12-23"><a href="#cb12-23"></a> g_signal_connect <span class="op">(</span>drag<span class="op">,</span> <span class="st">&quot;drag-end&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>drag_end<span class="op">),</span> da<span class="op">);</span></span>
<span id="cb12-24"><a href="#cb12-24"></a><span class="op">}</span></span></code></pre></div>
<ul>
<li>The function <code>gtk_gesture_drag_new</code> creates a new
GtkGestureDrag instance.</li>
<li>The function <code>gtk_gesture_single_set_button</code> sets the
button number to listen to. The constant <code>GDK_BUTTON_PRIMARY</code>
is the left button of a mouse.</li>
<li>The function <code>gtk_widget_add_controller</code> adds an event
controller, gestures are descendants of the event controller, to a
widget.</li>
<li>Three signals and handlers are connected.
<ul>
<li>drag-begin: Emitted when dragging starts.</li>
<li>drag-update: Emitted when the dragging point moves.</li>
<li>drag-end: Emitted when the dragging ends.</li>
</ul></li>
</ul>
<p>The process during the drag is as follows.</p>
<ul>
<li>start: save the surface and start points</li>
<li>update: restore the surface and draw a thin rectangle between the
start point and the current point of the mouse</li>
<li>end: restore the surface and draw a thick rectangle between the
start and end points.</li>
</ul>
<p>We need two global variables for the start point.</p>
<div class="sourceCode" id="cb13"><pre class="sourceCode C"><code class="sourceCode c"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a><span class="dt">static</span> <span class="dt">double</span> start_x<span class="op">;</span></span>
<span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a><span class="dt">static</span> <span class="dt">double</span> start_y<span class="op">;</span></span></code></pre></div>
<p>The following is the handler for the “drag-begin” signal.</p>
<div class="sourceCode" id="cb14"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb14-1"><a href="#cb14-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb14-2"><a href="#cb14-2"></a>copy_surface <span class="op">(</span>cairo_surface_t <span class="op">*</span>src<span class="op">,</span> cairo_surface_t <span class="op">*</span>dst<span class="op">)</span> <span class="op">{</span></span>
<span id="cb14-3"><a href="#cb14-3"></a> <span class="cf">if</span> <span class="op">(!</span>src <span class="op">||</span> <span class="op">!</span>dst<span class="op">)</span></span>
<span id="cb14-4"><a href="#cb14-4"></a> <span class="cf">return</span><span class="op">;</span></span>
<span id="cb14-5"><a href="#cb14-5"></a> cairo_t <span class="op">*</span>cr <span class="op">=</span> cairo_create <span class="op">(</span>dst<span class="op">);</span></span>
<span id="cb14-6"><a href="#cb14-6"></a> cairo_set_source_surface <span class="op">(</span>cr<span class="op">,</span> src<span class="op">,</span> <span class="fl">0.0</span><span class="op">,</span> <span class="fl">0.0</span><span class="op">);</span></span>
<span id="cb14-7"><a href="#cb14-7"></a> cairo_paint <span class="op">(</span>cr<span class="op">);</span></span>
<span id="cb14-8"><a href="#cb14-8"></a> cairo_destroy <span class="op">(</span>cr<span class="op">);</span></span>
<span id="cb14-9"><a href="#cb14-9"></a><span class="op">}</span></span>
<span id="cb14-10"><a href="#cb14-10"></a></span>
<span id="cb14-11"><a href="#cb14-11"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb14-12"><a href="#cb14-12"></a>drag_begin <span class="op">(</span>GtkGestureDrag <span class="op">*</span>gesture<span class="op">,</span> <span class="dt">double</span> x<span class="op">,</span> <span class="dt">double</span> y<span class="op">,</span> gpointer user_data<span class="op">)</span> <span class="op">{</span></span>
<span id="cb14-13"><a href="#cb14-13"></a> <span class="co">// save the surface and record (x, y)</span></span>
<span id="cb14-14"><a href="#cb14-14"></a> copy_surface <span class="op">(</span>surface<span class="op">,</span> surface_save<span class="op">);</span></span>
<span id="cb14-15"><a href="#cb14-15"></a> start_x <span class="op">=</span> x<span class="op">;</span></span>
<span id="cb14-16"><a href="#cb14-16"></a> start_y <span class="op">=</span> y<span class="op">;</span></span>
<span id="cb14-17"><a href="#cb14-17"></a><span class="op">}</span></span></code></pre></div>
<ul>
<li>Copies <code>surface</code> to <code>surface_save</code>, which is
an image just before the dragging.</li>
<li>Stores the points to <code>start_x</code> and
<code>start_y</code>.</li>
</ul>
<div class="sourceCode" id="cb15"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb15-1"><a href="#cb15-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb15-2"><a href="#cb15-2"></a>drag_update <span class="op">(</span>GtkGestureDrag <span class="op">*</span>gesture<span class="op">,</span> <span class="dt">double</span> offset_x<span class="op">,</span> <span class="dt">double</span> offset_y<span class="op">,</span> gpointer user_data<span class="op">)</span> <span class="op">{</span></span>
<span id="cb15-3"><a href="#cb15-3"></a> GtkWidget <span class="op">*</span>da <span class="op">=</span> GTK_WIDGET <span class="op">(</span>user_data<span class="op">);</span></span>
<span id="cb15-4"><a href="#cb15-4"></a> cairo_t <span class="op">*</span>cr<span class="op">;</span></span>
<span id="cb15-5"><a href="#cb15-5"></a> </span>
<span id="cb15-6"><a href="#cb15-6"></a> copy_surface <span class="op">(</span>surface_save<span class="op">,</span> surface<span class="op">);</span></span>
<span id="cb15-7"><a href="#cb15-7"></a> cr <span class="op">=</span> cairo_create <span class="op">(</span>surface<span class="op">);</span></span>
<span id="cb15-8"><a href="#cb15-8"></a> cairo_rectangle <span class="op">(</span>cr<span class="op">,</span> start_x<span class="op">,</span> start_y<span class="op">,</span> offset_x<span class="op">,</span> offset_y<span class="op">);</span></span>
<span id="cb15-9"><a href="#cb15-9"></a> cairo_set_line_width <span class="op">(</span>cr<span class="op">,</span> <span class="fl">1.0</span><span class="op">);</span></span>
<span id="cb15-10"><a href="#cb15-10"></a> cairo_stroke <span class="op">(</span>cr<span class="op">);</span></span>
<span id="cb15-11"><a href="#cb15-11"></a> cairo_destroy <span class="op">(</span>cr<span class="op">);</span></span>
<span id="cb15-12"><a href="#cb15-12"></a> gtk_widget_queue_draw <span class="op">(</span>da<span class="op">);</span></span>
<span id="cb15-13"><a href="#cb15-13"></a><span class="op">}</span></span></code></pre></div>
<ul>
<li>Restores <code>surface</code> from <code>surface_save</code>.</li>
<li>Draws a rectangle with thin lines.</li>
<li>Calls <code>gtk_widget_queue_draw</code> to add the GtkDrawingArea
to the queue to redraw.</li>
</ul>
<div class="sourceCode" id="cb16"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb16-1"><a href="#cb16-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb16-2"><a href="#cb16-2"></a>drag_end <span class="op">(</span>GtkGestureDrag <span class="op">*</span>gesture<span class="op">,</span> <span class="dt">double</span> offset_x<span class="op">,</span> <span class="dt">double</span> offset_y<span class="op">,</span> gpointer user_data<span class="op">)</span> <span class="op">{</span></span>
<span id="cb16-3"><a href="#cb16-3"></a> GtkWidget <span class="op">*</span>da <span class="op">=</span> GTK_WIDGET <span class="op">(</span>user_data<span class="op">);</span></span>
<span id="cb16-4"><a href="#cb16-4"></a> cairo_t <span class="op">*</span>cr<span class="op">;</span></span>
<span id="cb16-5"><a href="#cb16-5"></a> </span>
<span id="cb16-6"><a href="#cb16-6"></a> copy_surface <span class="op">(</span>surface_save<span class="op">,</span> surface<span class="op">);</span></span>
<span id="cb16-7"><a href="#cb16-7"></a> cr <span class="op">=</span> cairo_create <span class="op">(</span>surface<span class="op">);</span></span>
<span id="cb16-8"><a href="#cb16-8"></a> cairo_rectangle <span class="op">(</span>cr<span class="op">,</span> start_x<span class="op">,</span> start_y<span class="op">,</span> offset_x<span class="op">,</span> offset_y<span class="op">);</span></span>
<span id="cb16-9"><a href="#cb16-9"></a> cairo_set_line_width <span class="op">(</span>cr<span class="op">,</span> <span class="fl">6.0</span><span class="op">);</span></span>
<span id="cb16-10"><a href="#cb16-10"></a> cairo_stroke <span class="op">(</span>cr<span class="op">);</span></span>
<span id="cb16-11"><a href="#cb16-11"></a> cairo_destroy <span class="op">(</span>cr<span class="op">);</span></span>
<span id="cb16-12"><a href="#cb16-12"></a> gtk_widget_queue_draw <span class="op">(</span>da<span class="op">);</span></span>
<span id="cb16-13"><a href="#cb16-13"></a><span class="op">}</span></span></code></pre></div>
<ul>
<li>Restores <code>surface</code> from <code>surface_save</code>.</li>
<li>Draws a rectangle with thick lines.</li>
<li>Calls <code>gtk_widget_queue_draw</code> to add the GtkDrawingArea
to the queue to redraw.</li>
</ul>
<h2 id="build-and-run">Build and run</h2>
<p>Download the <a
href="https://github.com/ToshioCP/Gtk4-tutorial">repository</a>. Change
your current directory to <code>src/custom_drawing</code>. Run meson and
ninja to build the program. Type <code>_build/rect</code> to run the
program. Try to draw rectangles.</p>
<pre><code>$ cd src/custom_drawing
$ meson setup _build
$ ninja -C _build
$ _build/rect</code></pre>
<figure>
<img src="image/rect.png" alt="The screen of rect program" />
<figcaption aria-hidden="true">The screen of rect program</figcaption>
</figure>
</div> </div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script> <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> </body>

View file

@ -116,7 +116,7 @@ interpreter</h1>
<p>A program <code>turtle</code> is an example with the combination of <p>A program <code>turtle</code> is an example with the combination of
TfeTextView and GtkDrawingArea objects. It is a very small interpreter TfeTextView and GtkDrawingArea objects. It is a very small interpreter
but you can draw fractal curves with it. The following diagram is a Koch but you can draw fractal curves with it. The following diagram is a Koch
curve, which is one of famous fractal curves.</p> curve, which is one of the famous fractal curves.</p>
<figure> <figure>
<img src="image/turtle_koch.png" alt="Koch curve" /> <img src="image/turtle_koch.png" alt="Koch curve" />
<figcaption aria-hidden="true">Koch curve</figcaption> <figcaption aria-hidden="true">Koch curve</figcaption>

View file

@ -2,27 +2,23 @@ Up: [README.md](../README.md), Prev: [Section 23](sec23.md), Next: [Section 25]
# GtkDrawingArea and Cairo # GtkDrawingArea and Cairo
This section and following sections are *not* updated yet and the programs were checked on the older GTK version than 4.10. If you want to draw shapes or paint images dynamically on the screen, use the GtkDrawingArea widget.
They will be updated in the near future.
If you want to draw dynamically on the screen, like an image window of gimp graphics editor, the GtkDrawingArea widget is the most suitable widget. GtkDrawingArea provides a cairo drawing context.
You can freely draw or redraw an image in this widget. You can draw images with cairo library functions.
This is called custom drawing. This section describes:
GtkDrawingArea provides a cairo drawing context so users can draw images by using cairo functions. 1. Cairo, but briefly
In this section, I will explain:
1. Cairo, but only briefly
2. GtkDrawingArea, with a very simple example. 2. GtkDrawingArea, with a very simple example.
## Cairo ## Cairo
Cairo is a set of two dimensional graphical drawing functions (or graphics library). Cairo is a drawing library for two dimensional graphics.
There are a lot of documents on [Cairo's website](https://www.cairographics.org/). There are a lot of documents on [Cairo's website](https://www.cairographics.org/).
If you aren't familiar with Cairo, it is worth reading the [tutorial](https://www.cairographics.org/tutorial/). If you aren't familiar with Cairo, it is worth reading the [tutorial](https://www.cairographics.org/tutorial/).
The following is an introduction to the Cairo library and how to use it. The following is an introduction to the Cairo library.
First, you need to know about surfaces, sources, masks, destinations, cairo context and transformations. First, you need to know surfaces, sources, masks, destinations, cairo context and transformations.
- A surface represents an image. - A surface represents an image.
It is like a canvas. It is like a canvas.
@ -35,7 +31,7 @@ For example, `cairo_stroke` is a function to draw a path to the destination by t
- A transformation can be applied before the transfer completes. - A transformation can be applied before the transfer completes.
The transformation which is applied is called affine, which is a mathematical term meaning transofrmations The transformation which is applied is called affine, which is a mathematical term meaning transofrmations
that preserve straight lines. that preserve straight lines.
Scaling, rotating, reflecting, shearing and translating are all examples of affine transformations. Scaling, rotating, reflecting, shearing and translating are examples of affine transformations.
They are mathematically represented by matrix multiplication and vector addition. They are mathematically represented by matrix multiplication and vector addition.
In this section we don't use it, instead we will only use the identity transformation. In this section we don't use it, instead we will only use the identity transformation.
This means that the coordinates in the source and mask are the same as the coordinates in destination. This means that the coordinates in the source and mask are the same as the coordinates in destination.
@ -106,28 +102,30 @@ Modern displays have this type of color depth.
Width and height are in pixels and given as integers. Width and height are in pixels and given as integers.
- 14: Creates cairo context. - 14: Creates cairo context.
The surface given as an argument will be the destination of the context. The surface given as an argument will be the destination of the context.
- 18: `cairo_set_source_rgb` creates a source pattern, which in this case is a solid white paint. - 18: `cairo_set_source_rgb` creates a source pattern, which is a solid white paint.
The second to fourth arguments are red, green and blue color values respectively, and they are The second to fourth arguments are red, green and blue color values respectively, and they are of type float.
of type float. The values are between zero (0.0) and one (1.0), with The values are between zero (0.0) and one (1.0).
black being given by (0.0,0.0,0.0) and white by (1.0,1.0,1.0). Black is (0.0,0.0,0.0) and white is (1.0,1.0,1.0).
- 19: `cairo_paint` copies everywhere in the source to destination. - 19: `cairo_paint` copies everywhere in the source to destination.
The destination is filled with white pixels with this command. The destination is filled with white pixels with this command.
- 21: Sets the source color to black. - 21: Sets the source color to black.
- 22: `cairo_set_line_width` set the width of lines. - 22: `cairo_set_line_width` sets the width of lines.
In this case, the line width is set to be two pixels and will end up that same size. In this case, the line width is set to be two pixels and will end up that same size.
(It is because the transformation is identity. (It is because the transformation is identity.
If the transformation isn't identity, for example scaling with the factor three, the actual width in destination will be six (2x3=6) pixels.) If the transformation isn't identity, for example scaling with the factor three, the actual width in destination will be six (2x3=6) pixels.)
- 23: Draws a rectangle (square) on the mask. - 23: Draws a rectangle (square) on the mask.
The square is located at the center. The square is located at the center.
- 24: `cairo_stroke` transfer the source to destination through the rectangle in the mask. - 24: `cairo_stroke` transfers the source to destination through the rectangle in the mask.
- 27: Outputs the image to a png file `rectangle.png`. - 31: Outputs the image to a png file `rectangle.png`.
- 28: Destroys the context. At the same time the source is destroyed. - 32: Destroys the context. At the same time the source is destroyed.
- 29: Destroys the surface. - 33: Destroys the surface.
To compile this, change your current directory to `src/misc` and type the following. To compile this, change your current directory to `src/misc` and type the following.
$ gcc `pkg-config --cflags cairo` cairo.c `pkg-config --libs cairo` ```
$ gcc `pkg-config --cflags cairo` cairo.c `pkg-config --libs cairo`
```
s
![rectangle.png](../image/rectangle.png) ![rectangle.png](../image/rectangle.png)
See the [Cairo's website](https://www.cairographics.org/) for further information. See the [Cairo's website](https://www.cairographics.org/) for further information.
@ -187,7 +185,7 @@ The two functions `app_activate` and `draw_function` are important in this examp
- 22: Creates a GtkDrawingArea instance. - 22: Creates a GtkDrawingArea instance.
- 25: Sets a drawing function of the widget. - 25: Sets a drawing function of the widget.
GtkDrawingArea widget uses the function to draw the contents of itself whenever its necessary. GtkDrawingArea widget uses the function `draw_function` to draw the contents of itself whenever its necessary.
For example, when a user drag a mouse pointer and resize a top-level window, GtkDrawingArea also changes the size. For example, when a user drag a mouse pointer and resize a top-level window, GtkDrawingArea also changes the size.
Then, the whole window needs to be redrawn. Then, the whole window needs to be redrawn.
For the information of `gtk_drawing_area_set_draw_func`, see [Gtk API Reference -- gtk\_drawing\_area\_set\_draw\_func](https://docs.gtk.org/gtk4/method.DrawingArea.set_draw_func.html). For the information of `gtk_drawing_area_set_draw_func`, see [Gtk API Reference -- gtk\_drawing\_area\_set\_draw\_func](https://docs.gtk.org/gtk4/method.DrawingArea.set_draw_func.html).

View file

@ -173,7 +173,7 @@ every second (or 1000ms).
13 13
14 gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA (clock), draw_clock, NULL, NULL); 14 gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA (clock), draw_clock, NULL, NULL);
15 g_timeout_add(1000, (GSourceFunc) time_handler, (gpointer) clock); 15 g_timeout_add(1000, (GSourceFunc) time_handler, (gpointer) clock);
16 gtk_widget_show(win); 16 gtk_window_present(GTK_WINDOW (win));
17 17
18 } 18 }
~~~ ~~~
@ -349,7 +349,7 @@ You can find the source files in the `tfc` directory. it can be compiled with `.
143 143
144 gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA (clock), draw_clock, NULL, NULL); 144 gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA (clock), draw_clock, NULL, NULL);
145 g_timeout_add(1000, (GSourceFunc) time_handler, (gpointer) clock); 145 g_timeout_add(1000, (GSourceFunc) time_handler, (gpointer) clock);
146 gtk_widget_show(win); 146 gtk_window_present(GTK_WINDOW (win));
147 147
148 } 148 }
149 149

View file

@ -1,452 +1,382 @@
Up: [README.md](../README.md), Prev: [Section 25](sec25.md), Next: [Section 27](sec27.md) Up: [README.md](../README.md), Prev: [Section 25](sec25.md), Next: [Section 27](sec27.md)
# Combine GtkDrawingArea and TfeTextView # Custom drawing
Now, we will make a new application which has GtkDrawingArea and TfeTextView in it. Custom drawing is to draw shapes dynamically.
Its name is "color". This section shows an example of custom drawing.
If you write a name of a color in TfeTextView and click on the `run` button, then the color of GtkDrawingArea changes to the color given by you. You can draw rectangles by dragging the mouse.
![color](../image/color.png) Down the button.
The following colors are available. ![down the button](../image/cd0.png)
(without new line charactor)
- white Move the mouse
- black
- red
- green
- blue
In addition the following two options are also available. ![Move the mouse](../image/cd1.png)
- light: Make the color of the drawing area lighter. Up the button.
- dark: Make the color of the drawing area darker.
This application can only do very simple things. ![Up the button](../image/cd2.png)
However, it tells us that if we add powerful parser to it, we will be able to make it more efficient.
I want to show it to you in the later section by making a turtle graphics language like Logo program language.
In this section, we focus on how to bind the two objects. The programs are at `src/custom_drawing` directory.
Download the [repository](https://github.com/ToshioCP/Gtk4-tutorial) and see the directory.
There are four files.
## Color.ui and color.gresource.xml - meson.build
- rect.c
- rect.gresource.xml
- rect.ui
First, We need to make the ui file of the widgets. ## rect.gresource.xml
Title bar, four buttons in the tool bar, textview and drawing area.
The ui file is as follows. This file describes a ui file to compile.
The compiler glib-compile-resources uses it.
~~~xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <gresources>
3 <gresource prefix="/com/github/ToshioCP/rect">
4 <file>rect.ui</file>
5 </gresource>
6 </gresources>
~~~
The prefix is `/com/github/ToshioCP/rect` and the file is `rect.ui`.
Therefore, GtkBuilder reads the resource from `/com/github/ToshioCP/rect/rect.ui`.
## rect.ui
The following is the ui file that defines the widgets.
There are two widgets which are GtkApplicationWindow and GtkDrawingArea.
The ids are `win` and `da` respectively.
~~~xml ~~~xml
1 <?xml version="1.0" encoding="UTF-8"?> 1 <?xml version="1.0" encoding="UTF-8"?>
2 <interface> 2 <interface>
3 <object class="GtkApplicationWindow" id="win"> 3 <object class="GtkApplicationWindow" id="win">
4 <property name="title">color changer</property> 4 <property name="default-width">800</property>
5 <property name="default-width">600</property> 5 <property name="default-height">600</property>
6 <property name="default-height">400</property> 6 <property name="resizable">FALSE</property>
7 <child> 7 <property name="title">Custom drawing</property>
8 <object class="GtkBox" id="boxv"> 8 <child>
9 <property name="orientation">GTK_ORIENTATION_VERTICAL</property> 9 <object class="GtkDrawingArea" id="da">
10 <child> 10 <property name="hexpand">TRUE</property>
11 <object class="GtkBox" id="boxh1"> 11 <property name="vexpand">TRUE</property>
12 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property> 12 </object>
13 <child> 13 </child>
14 <object class="GtkLabel" id="dmy1"> 14 </object>
15 <property name="width-chars">10</property> 15 </interface>
16 </object> 16
17 </child>
18 <child>
19 <object class="GtkButton" id="btnr">
20 <property name="label">Run</property>
21 <signal name="clicked" handler="run_cb"></signal>
22 </object>
23 </child>
24 <child>
25 <object class="GtkButton" id="btno">
26 <property name="label">Open</property>
27 <signal name="clicked" handler="open_cb"></signal>
28 </object>
29 </child>
30 <child>
31 <object class="GtkLabel" id="dmy2">
32 <property name="hexpand">TRUE</property>
33 </object>
34 </child>
35 <child>
36 <object class="GtkButton" id="btns">
37 <property name="label">Save</property>
38 <signal name="clicked" handler="save_cb"></signal>
39 </object>
40 </child>
41 <child>
42 <object class="GtkButton" id="btnc">
43 <property name="label">Close</property>
44 <signal name="clicked" handler="close_cb"></signal>
45 </object>
46 </child>
47 <child>
48 <object class="GtkLabel" id="dmy3">
49 <property name="width-chars">10</property>
50 </object>
51 </child>
52 </object>
53 </child>
54 <child>
55 <object class="GtkBox" id="boxh2">
56 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
57 <property name="homogeneous">TRUE</property>
58 <child>
59 <object class="GtkScrolledWindow" id="scr">
60 <property name="hexpand">TRUE</property>
61 <property name="vexpand">TRUE</property>
62 <child>
63 <object class="TfeTextView" id="tv">
64 <property name="wrap-mode">GTK_WRAP_WORD_CHAR</property>
65 </object>
66 </child>
67 </object>
68 </child>
69 <child>
70 <object class="GtkDrawingArea" id="da">
71 <property name="hexpand">TRUE</property>
72 <property name="vexpand">TRUE</property>
73 </object>
74 </child>
75 </object>
76 </child>
77 </object>
78 </child>
79 </object>
80 </interface>
~~~ ~~~
- 10-53: The horizontal box `boxh1` makes a tool bar which has four buttons, `Run`, `Open`, `Save` and `Close`. ## rect.c
This is similar to the `tfe` text editor in [Section 9](sec9.md).
There are two differences.
`Run` button replaces `New` button.
A signal element is added to each button object.
It has "name" attribute which is a signal name and "handler" attribute which is the name of its signal handler.
Options "-WI, --export-dynamic" CFLAG is necessary when you compile the application.
You can achieve this by adding "export_dynamic: true" argument to the executable function in `meson.build`.
And be careful that the handler must be defined without 'static' class.
- 54-76: The horizontal box `boxh2` includes GtkScrolledWindow and GtkDrawingArea.
GtkBox has "homogeneous property" with TRUE value, so the two children have the same width in the box.
TfeTextView is a child of GtkScrolledWindow.
The xml file for the resource compiler is almost same as before. ### GtkApplication
Just substitute "color" for "tfe".
~~~xml This program uses GtkApplication.
1 <?xml version="1.0" encoding="UTF-8"?> The application ID is `com.github.ToshioCP.rect`.
2 <gresources>
3 <gresource prefix="/com/github/ToshioCP/color"> ```c
4 <file>color.ui</file> #define APPLICATION_ID "com.github.ToshioCP.rect"
5 </gresource> ```
6 </gresources>
See [GNOME Developer Documentation](https://developer.gnome.org/documentation/tutorials/application-id.html) for further information.
The function `main` is called at the beginning of the application.
~~~C
1 int
2 main (int argc, char **argv) {
3 GtkApplication *app;
4 int stat;
5
6 app = gtk_application_new (APPLICATION_ID, G_APPLICATION_HANDLES_OPEN);
7 g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
8 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
9 g_signal_connect (app, "shutdown", G_CALLBACK (app_shutdown), NULL);
10 stat =g_application_run (G_APPLICATION (app), argc, argv);
11 g_object_unref (app);
12 return stat;
13 }
~~~ ~~~
## Drawing function and surface It connects three signals and handlers.
The main point of this program is a drawing function. - startup: It is emitted after the application is registered to the system.
- activate: It is emitted when the application is activated.
- shutdown: It is emitted just before the application quits.
~~~C
1 static void
2 app_startup (GApplication *application) {
3 GtkApplication *app = GTK_APPLICATION (application);
4 GtkBuilder *build;
5 GtkWindow *win;
6 GtkDrawingArea *da;
7 GtkGesture *drag;
8
9 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/rect/rect.ui");
10 win = GTK_WINDOW (gtk_builder_get_object (build, "win"));
11 da = GTK_DRAWING_AREA (gtk_builder_get_object (build, "da"));
12 gtk_window_set_application (win, app);
13 g_object_unref (build);
14
15 gtk_drawing_area_set_draw_func (da, draw_cb, NULL, NULL);
16 g_signal_connect_after (da, "resize", G_CALLBACK (resize_cb), NULL);
17
18 drag = gtk_gesture_drag_new ();
19 gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (drag), GDK_BUTTON_PRIMARY);
20 gtk_widget_add_controller (GTK_WIDGET (da), GTK_EVENT_CONTROLLER (drag));
21 g_signal_connect (drag, "drag-begin", G_CALLBACK (drag_begin), NULL);
22 g_signal_connect (drag, "drag-update", G_CALLBACK (drag_update), da);
23 g_signal_connect (drag, "drag-end", G_CALLBACK (drag_end), da);
24 }
~~~
The startup handler does three things.
- Builds the widgets.
- Initializes the GtkDrawingArea instance.
- Sets the drawing function
- Connects the "resize" signal and the handler.
- Creates the GtkGestureDrag instance and initializes it.
Gesture will be explained in this section later.
~~~C ~~~C
1 static void 1 static void
2 draw_func (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height, gpointer user_data) { 2 app_activate (GApplication *application) {
3 GtkApplication *app = GTK_APPLICATION (application);
4 GtkWindow *win;
5
6 win = gtk_application_get_active_window (app);
7 gtk_window_present (win);
8 }
~~~
The activate handler just shows the window.
### GtkDrawingArea
The program has two cairo surfaces and they are pointed by the global variables.
```C
static cairo_surface_t *surface = NULL;
static cairo_surface_t *surface_save = NULL;
```
The drawing process is as follows.
- Creates an image on `surface`.
- Copies `surface` to the cairo surface of the GtkDrawingArea.
- Calls ` gtk_widget_queue_draw (da)` to draw it if necessary.
They are created in the "resize" signal handler.
~~~C
1 static void
2 resize_cb (GtkWidget *widget, int width, int height, gpointer user_data) {
3 cairo_t *cr;
4
5 if (surface)
6 cairo_surface_destroy (surface);
7 surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
8 if (surface_save)
9 cairo_surface_destroy (surface_save);
10 surface_save = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
11 /* Paint the surface white. It is the background color. */
12 cr = cairo_create (surface);
13 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
14 cairo_paint (cr);
15 cairo_destroy (cr);
16 }
~~~
This callback is called when the GtkDrawingArea is shown.
It is the only call because the window is not resizable.
It creates image surfaces for `surface` and `surface_save`.
The `surface` surface is painted white, which is the background color.
The drawing function copies `surface` to the GtkDrawingArea surface.
~~~C
1 static void
2 draw_cb (GtkDrawingArea *da, cairo_t *cr, int width, int height, gpointer user_data) {
3 if (surface) { 3 if (surface) {
4 cairo_set_source_surface (cr, surface, 0, 0); 4 cairo_set_source_surface (cr, surface, 0.0, 0.0);
5 cairo_paint (cr); 5 cairo_paint (cr);
6 } 6 }
7 } 7 }
~~~ ~~~
The `surface` variable in line 3 is a static variable. This function is called by the system when it needs to redraw the drawing area.
~~~C Two surfaces `surface` and `surface_save` are destroyed before the application quits.
static cairo_surface_t *surface = NULL;
~~~
The drawing function just copies the `surface` to its own surface with the `cairo_paint` function.
The surface (pointed by the static variable `surface`) is built by the `run` function.
~~~C
1 static void
2 run (void) {
3 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
4 GtkTextIter start_iter;
5 GtkTextIter end_iter;
6 char *contents;
7 cairo_t *cr;
8
9 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
10 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
11 if (surface) {
12 cr = cairo_create (surface);
13 if (g_strcmp0 ("red", contents) == 0)
14 cairo_set_source_rgb (cr, 1, 0, 0);
15 else if (g_strcmp0 ("green", contents) == 0)
16 cairo_set_source_rgb (cr, 0, 1, 0);
17 else if (g_strcmp0 ("blue", contents) == 0)
18 cairo_set_source_rgb (cr, 0, 0, 1);
19 else if (g_strcmp0 ("white", contents) == 0)
20 cairo_set_source_rgb (cr, 1, 1, 1);
21 else if (g_strcmp0 ("black", contents) == 0)
22 cairo_set_source_rgb (cr, 0, 0, 0);
23 else if (g_strcmp0 ("light", contents) == 0)
24 cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
25 else if (g_strcmp0 ("dark", contents) == 0)
26 cairo_set_source_rgba (cr, 0, 0, 0, 0.5);
27 else
28 cairo_set_source_surface (cr, surface, 0, 0);
29 cairo_paint (cr);
30 cairo_destroy (cr);
31 }
32 g_free (contents);
33 }
~~~
- 9-10: Gets the string in the GtkTextBuffer and inserts it to `contents`.
- 11: If the variable `surface` points a surface instance, it is painted as follows.
- 12- 30: The source is set based on the string `contents` and copied to the surface with `cairo_paint`.
- 24,26: Alpha channel is used in "light" and "dark" procedure.
The drawing area just reflects the `surface`.
But one problem is resizing.
If a user resizes the main window, the drawing area is also resized.
It makes size difference between the surface and the drawing area.
So, the surface needs to be resized to fit the drawing area.
It is accomplished by connecting the "resize" signal on the drawing area to a handler.
~~~C
g_signal_connect (GTK_DRAWING_AREA (da), "resize", G_CALLBACK (resize_cb), NULL);
~~~
The handler is as follows.
~~~C ~~~C
1 static void 1 static void
2 resize_cb (GtkDrawingArea *drawing_area, int width, int height, gpointer user_data) { 2 app_shutdown (GApplication *application) {
3 if (surface) 3 if (surface)
4 cairo_surface_destroy (surface); 4 cairo_surface_destroy (surface);
5 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); 5 if (surface_save)
6 run (); 6 cairo_surface_destroy (surface_save);
7 } 7 }
~~~ ~~~
If the variable `surface` sets a surface instance, it is destroyed. ### GtkGestureDrag
A new surface is created and its size fits the drawing area.
The surface is assigned to the variable `surface`.
The function `run` is called and the surface is colored.
The signal is emitted when: Gesture class is used to recognize human gestures such as click, drag, pan, swipe and so on.
It is a subclass of GtkEventController.
GtkGesture class is abstract and there are several implementations.
- The drawing area is realized (it appears on the display). - GtkGestureClick
- It is changed (resized) while realized - GtkGestureDrag
- GtkGesturePan
- GtkGestureSwipe
- other implementations
So, the first surface is created when it is realized. The program `rect.c` uses GtkGestureDrag.
It is the implementation for drags.
The parent-child relationship is as follows.
## Colorapplication.c ```
GObject -- GtkEventController -- GtkGesture -- GtkGestureSingle -- GtkGestureDrag
```
This is the main file. GtkGestureSingle is a subclass of GtkGesture and optimized for singe-touch and mouse gestures.
- Builds widgets by GtkBuilder. A GtkGestureDrag instance is created and initialized in the startup signal handler in `rect.c`.
- Sets a drawing function for GtkDrawingArea. See line 18 to 23 in the following.
And connects a handler to the "resize" signal on the GtkDrawingArea instance.
- Implements each call back function.
Particularly, `Run` signal handler is the point in this program.
The following is `colorapplication.c`.
~~~C ~~~C
1 #include <gtk/gtk.h> 1 static void
2 #include "../tfetextview/tfetextview.h" 2 app_startup (GApplication *application) {
3 3 GtkApplication *app = GTK_APPLICATION (application);
4 static GtkWidget *win; 4 GtkBuilder *build;
5 static GtkWidget *tv; 5 GtkWindow *win;
6 static GtkWidget *da; 6 GtkDrawingArea *da;
7 7 GtkGesture *drag;
8 static cairo_surface_t *surface = NULL; 8
9 9 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/rect/rect.ui");
10 static void 10 win = GTK_WINDOW (gtk_builder_get_object (build, "win"));
11 run (void) { 11 da = GTK_DRAWING_AREA (gtk_builder_get_object (build, "da"));
12 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); 12 gtk_window_set_application (win, app);
13 GtkTextIter start_iter; 13 g_object_unref (build);
14 GtkTextIter end_iter; 14
15 char *contents; 15 gtk_drawing_area_set_draw_func (da, draw_cb, NULL, NULL);
16 cairo_t *cr; 16 g_signal_connect_after (da, "resize", G_CALLBACK (resize_cb), NULL);
17 17
18 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter); 18 drag = gtk_gesture_drag_new ();
19 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE); 19 gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (drag), GDK_BUTTON_PRIMARY);
20 if (surface) { 20 gtk_widget_add_controller (GTK_WIDGET (da), GTK_EVENT_CONTROLLER (drag));
21 cr = cairo_create (surface); 21 g_signal_connect (drag, "drag-begin", G_CALLBACK (drag_begin), NULL);
22 if (g_strcmp0 ("red", contents) == 0) 22 g_signal_connect (drag, "drag-update", G_CALLBACK (drag_update), da);
23 cairo_set_source_rgb (cr, 1, 0, 0); 23 g_signal_connect (drag, "drag-end", G_CALLBACK (drag_end), da);
24 else if (g_strcmp0 ("green", contents) == 0) 24 }
25 cairo_set_source_rgb (cr, 0, 1, 0);
26 else if (g_strcmp0 ("blue", contents) == 0)
27 cairo_set_source_rgb (cr, 0, 0, 1);
28 else if (g_strcmp0 ("white", contents) == 0)
29 cairo_set_source_rgb (cr, 1, 1, 1);
30 else if (g_strcmp0 ("black", contents) == 0)
31 cairo_set_source_rgb (cr, 0, 0, 0);
32 else if (g_strcmp0 ("light", contents) == 0)
33 cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
34 else if (g_strcmp0 ("dark", contents) == 0)
35 cairo_set_source_rgba (cr, 0, 0, 0, 0.5);
36 else
37 cairo_set_source_surface (cr, surface, 0, 0);
38 cairo_paint (cr);
39 cairo_destroy (cr);
40 }
41 g_free (contents);
42 }
43
44 void
45 run_cb (GtkWidget *btnr) {
46 run ();
47 gtk_widget_queue_draw (GTK_WIDGET (da));
48 }
49
50 void
51 open_cb (GtkWidget *btno) {
52 tfe_text_view_open (TFE_TEXT_VIEW (tv), GTK_WINDOW (win));
53 }
54
55 void
56 save_cb (GtkWidget *btns) {
57 tfe_text_view_save (TFE_TEXT_VIEW (tv));
58 }
59
60 void
61 close_cb (GtkWidget *btnc) {
62 gtk_window_destroy (GTK_WINDOW (win));
63 }
64
65 static void
66 resize_cb (GtkDrawingArea *drawing_area, int width, int height, gpointer user_data) {
67 if (surface)
68 cairo_surface_destroy (surface);
69 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
70 run ();
71 }
72
73 static void
74 draw_func (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height, gpointer user_data) {
75 if (surface) {
76 cairo_set_source_surface (cr, surface, 0, 0);
77 cairo_paint (cr);
78 }
79 }
80
81 static void
82 app_activate (GApplication *application) {
83 gtk_window_present (GTK_WINDOW (win));
84 }
85
86 static void
87 app_startup (GApplication *application) {
88 GtkApplication *app = GTK_APPLICATION (application);
89 GtkBuilder *build;
90 GdkDisplay *display;
91
92 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/color/color.ui");
93 win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
94 gtk_window_set_application (GTK_WINDOW (win), app);
95 tv = GTK_WIDGET (gtk_builder_get_object (build, "tv"));
96 da = GTK_WIDGET (gtk_builder_get_object (build, "da"));
97 g_object_unref(build);
98 g_signal_connect (GTK_DRAWING_AREA (da), "resize", G_CALLBACK (resize_cb), NULL);
99 gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (da), draw_func, NULL, NULL);
100
101 display = gdk_display_get_default ();
102 GtkCssProvider *provider = gtk_css_provider_new ();
103 gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1);
104 gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
105 }
106
107 static void
108 app_shutdown (GApplication *application) {
109 if (surface)
110 cairo_surface_destroy (surface);
111 }
112
113 #define APPLICATION_ID "com.github.ToshioCP.color"
114
115 int
116 main (int argc, char **argv) {
117 GtkApplication *app;
118 int stat;
119
120 app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS);
121
122 g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
123 g_signal_connect (app, "shutdown", G_CALLBACK (app_shutdown), NULL);
124 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
125
126 stat =g_application_run (G_APPLICATION (app), argc, argv);
127 g_object_unref (app);
128 return stat;
129 }
130
~~~ ~~~
- 4-8: Win, tv, da and surface are defined as static variables. - The function `gtk_gesture_drag_new` creates a new GtkGestureDrag instance.
- 10-42: Run function. - The function `gtk_gesture_single_set_button` sets the button number to listen to.
- 44-63: Handlers for button signals. The constant `GDK_BUTTON_PRIMARY` is the left button of a mouse.
- 65-71: Resize handler. - The function `gtk_widget_add_controller` adds an event controller, gestures are descendants of the event controller, to a widget.
- 73-79: Drawing function. - Three signals and handlers are connected.
- 81-84: Application activate handler. - drag-begin: Emitted when dragging starts.
It just shows the main window. - drag-update: Emitted when the dragging point moves.
- 86-105: Application startup handler. - drag-end: Emitted when the dragging ends.
- 92- 97: It builds widgets according to the ui resource.
The static variables win, tv and da are assigned instances.
- 98: Connects "resize" signal and a handler.
- 99: Drawing function is set.
- 101-104: CSS for textview padding is set.
- 107-111: Application shutdown handler.
If there exists a surface instance, it will be destroyed.
- 116-129: A function `main`.
It creates a new application instance.
And connects three signals startup, shutdown and activate to their handlers.
It runs the application.
It releases the reference to the application and returns with `stat` value.
## Meson.build The process during the drag is as follows.
This file is almost same as before. - start: save the surface and start points
An argument "export_dynamic: true" is added to executable function. - update: restore the surface and draw a thin rectangle between the start point and the current point of the mouse
- end: restore the surface and draw a thick rectangle between the start and end points.
~~~meson We need two global variables for the start point.
1 project('color', 'c')
2 ```C
3 gtkdep = dependency('gtk4') static double start_x;
4 static double start_y;
5 gnome=import('gnome') ```
6 resources = gnome.compile_resources('resources','color.gresource.xml')
7 The following is the handler for the "drag-begin" signal.
8 sourcefiles=files('colorapplication.c', '../tfetextview/tfetextview.c')
9 ~~~C
10 executable('color', sourcefiles, resources, dependencies: gtkdep, export_dynamic: true) 1 static void
2 copy_surface (cairo_surface_t *src, cairo_surface_t *dst) {
3 if (!src || !dst)
4 return;
5 cairo_t *cr = cairo_create (dst);
6 cairo_set_source_surface (cr, src, 0.0, 0.0);
7 cairo_paint (cr);
8 cairo_destroy (cr);
9 }
10
11 static void
12 drag_begin (GtkGestureDrag *gesture, double x, double y, gpointer user_data) {
13 // save the surface and record (x, y)
14 copy_surface (surface, surface_save);
15 start_x = x;
16 start_y = y;
17 }
~~~ ~~~
## Build and try - Copies `surface` to `surface_save`, which is an image just before the dragging.
- Stores the points to `start_x` and `start_y`.
Type the following to compile the program. ~~~C
1 static void
2 drag_update (GtkGestureDrag *gesture, double offset_x, double offset_y, gpointer user_data) {
3 GtkWidget *da = GTK_WIDGET (user_data);
4 cairo_t *cr;
5
6 copy_surface (surface_save, surface);
7 cr = cairo_create (surface);
8 cairo_rectangle (cr, start_x, start_y, offset_x, offset_y);
9 cairo_set_line_width (cr, 1.0);
10 cairo_stroke (cr);
11 cairo_destroy (cr);
12 gtk_widget_queue_draw (da);
13 }
~~~
$ meson _build - Restores `surface` from `surface_save`.
$ ninja -C _build - Draws a rectangle with thin lines.
- Calls `gtk_widget_queue_draw` to add the GtkDrawingArea to the queue to redraw.
The application is made in `_build` directory. ~~~C
Type the following to execute it. 1 static void
2 drag_end (GtkGestureDrag *gesture, double offset_x, double offset_y, gpointer user_data) {
3 GtkWidget *da = GTK_WIDGET (user_data);
4 cairo_t *cr;
5
6 copy_surface (surface_save, surface);
7 cr = cairo_create (surface);
8 cairo_rectangle (cr, start_x, start_y, offset_x, offset_y);
9 cairo_set_line_width (cr, 6.0);
10 cairo_stroke (cr);
11 cairo_destroy (cr);
12 gtk_widget_queue_draw (da);
13 }
~~~
$ _build/color - Restores `surface` from `surface_save`.
- Draws a rectangle with thick lines.
- Calls `gtk_widget_queue_draw` to add the GtkDrawingArea to the queue to redraw.
Type "red", "green", "blue", "white", black", "light" or "dark" in the TfeTextView. ## Build and run
No new line charactor is needed.
Then, click on the `Run` button.
Make sure the color of GtkDrawingArea changes.
In this program TfeTextView is used to change the color. Download the [repository](https://github.com/ToshioCP/Gtk4-tutorial).
You can use buttons or menus instead of textview. Change your current directory to `src/custom_drawing`.
Probably it is more appropriate. Run meson and ninja to build the program.
Using textview is unnatural. Type `_build/rect` to run the program.
It is a good practice to make such application by yourself. Try to draw rectangles.
```
$ cd src/custom_drawing
$ meson setup _build
$ ninja -C _build
$ _build/rect
```
![The screen of rect program](../image/rect.png)
Up: [README.md](../README.md), Prev: [Section 25](sec25.md), Next: [Section 27](sec27.md) Up: [README.md](../README.md), Prev: [Section 25](sec25.md), Next: [Section 27](sec27.md)

View file

@ -4,7 +4,7 @@ Up: [README.md](../README.md), Prev: [Section 26](sec26.md), Next: [Section 28]
A program `turtle` is an example with the combination of TfeTextView and GtkDrawingArea objects. A program `turtle` is an example with the combination of TfeTextView and GtkDrawingArea objects.
It is a very small interpreter but you can draw fractal curves with it. It is a very small interpreter but you can draw fractal curves with it.
The following diagram is a Koch curve, which is one of famous fractal curves. The following diagram is a Koch curve, which is one of the famous fractal curves.
![Koch curve](../src/turtle/image/turtle_koch.png) ![Koch curve](../src/turtle/image/turtle_koch.png)

BIN
image/cd0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

BIN
image/cd1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

BIN
image/cd2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

BIN
image/rect.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View file

@ -0,0 +1,8 @@
project('rect', 'c')
gtkdep = dependency('gtk4')
gnome = import('gnome')
resources = gnome.compile_resources('resources','rect.gresource.xml')
executable(meson.project_name(), 'rect.c', resources, dependencies: gtkdep, export_dynamic: true, install: false)

137
src/custom_drawing/rect.c Normal file
View file

@ -0,0 +1,137 @@
#include <gtk/gtk.h>
static cairo_surface_t *surface = NULL;
static cairo_surface_t *surface_save = NULL;
static double start_x;
static double start_y;
static void
copy_surface (cairo_surface_t *src, cairo_surface_t *dst) {
if (!src || !dst)
return;
cairo_t *cr = cairo_create (dst);
cairo_set_source_surface (cr, src, 0.0, 0.0);
cairo_paint (cr);
cairo_destroy (cr);
}
/* This callback is called when the GtkDrawingArea is shown. */
/* It is the only call because the window is not resizable. */
static void
resize_cb (GtkWidget *widget, int width, int height, gpointer user_data) {
cairo_t *cr;
if (surface)
cairo_surface_destroy (surface);
surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
if (surface_save)
cairo_surface_destroy (surface_save);
surface_save = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
/* Paint the surface white. It is the background color. */
cr = cairo_create (surface);
cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
cairo_paint (cr);
cairo_destroy (cr);
}
static void
draw_cb (GtkDrawingArea *da, cairo_t *cr, int width, int height, gpointer user_data) {
if (surface) {
cairo_set_source_surface (cr, surface, 0.0, 0.0);
cairo_paint (cr);
}
}
static void
drag_begin (GtkGestureDrag *gesture, double x, double y, gpointer user_data) {
// save the surface and record (x, y)
copy_surface (surface, surface_save);
start_x = x;
start_y = y;
}
static void
drag_update (GtkGestureDrag *gesture, double offset_x, double offset_y, gpointer user_data) {
GtkWidget *da = GTK_WIDGET (user_data);
cairo_t *cr;
copy_surface (surface_save, surface);
cr = cairo_create (surface);
cairo_rectangle (cr, start_x, start_y, offset_x, offset_y);
cairo_set_line_width (cr, 1.0);
cairo_stroke (cr);
cairo_destroy (cr);
gtk_widget_queue_draw (da);
}
static void
drag_end (GtkGestureDrag *gesture, double offset_x, double offset_y, gpointer user_data) {
GtkWidget *da = GTK_WIDGET (user_data);
cairo_t *cr;
copy_surface (surface_save, surface);
cr = cairo_create (surface);
cairo_rectangle (cr, start_x, start_y, offset_x, offset_y);
cairo_set_line_width (cr, 6.0);
cairo_stroke (cr);
cairo_destroy (cr);
gtk_widget_queue_draw (da);
}
static void
app_activate (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
GtkWindow *win;
win = gtk_application_get_active_window (app);
gtk_window_present (win);
}
static void
app_shutdown (GApplication *application) {
if (surface)
cairo_surface_destroy (surface);
if (surface_save)
cairo_surface_destroy (surface_save);
}
static void
app_startup (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
GtkBuilder *build;
GtkWindow *win;
GtkDrawingArea *da;
GtkGesture *drag;
build = gtk_builder_new_from_resource ("/com/github/ToshioCP/rect/rect.ui");
win = GTK_WINDOW (gtk_builder_get_object (build, "win"));
da = GTK_DRAWING_AREA (gtk_builder_get_object (build, "da"));
gtk_window_set_application (win, app);
g_object_unref (build);
gtk_drawing_area_set_draw_func (da, draw_cb, NULL, NULL);
g_signal_connect_after (da, "resize", G_CALLBACK (resize_cb), NULL);
drag = gtk_gesture_drag_new ();
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (drag), GDK_BUTTON_PRIMARY);
gtk_widget_add_controller (GTK_WIDGET (da), GTK_EVENT_CONTROLLER (drag));
g_signal_connect (drag, "drag-begin", G_CALLBACK (drag_begin), NULL);
g_signal_connect (drag, "drag-update", G_CALLBACK (drag_update), da);
g_signal_connect (drag, "drag-end", G_CALLBACK (drag_end), da);
}
#define APPLICATION_ID "com.github.ToshioCP.rect"
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new (APPLICATION_ID, G_APPLICATION_HANDLES_OPEN);
g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
g_signal_connect (app, "shutdown", G_CALLBACK (app_shutdown), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}

View file

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

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkApplicationWindow" id="win">
<property name="default-width">800</property>
<property name="default-height">600</property>
<property name="resizable">FALSE</property>
<property name="title">Custom drawing</property>
<child>
<object class="GtkDrawingArea" id="da">
<property name="hexpand">TRUE</property>
<property name="vexpand">TRUE</property>
</object>
</child>
</object>
</interface>

86
src/dnd/dnd.c Normal file
View file

@ -0,0 +1,86 @@
#include <gtk/gtk.h>
static GtkCssProvider *provider = NULL;
static const char *format = "label {padding: 20px;} label#red {background: red;} "
"label#green {background: green;} label#blue {background: blue;} "
"label#canvas {color: %s; font-weight: bold; font-size: 72pt;}";
static gboolean
drop_cb (GtkDropTarget* self, const GValue* value, gdouble x, gdouble y, gpointer user_data) {
char *s;
s = g_strdup_printf (format, g_value_get_string (value));
gtk_css_provider_load_from_data (provider, s, -1);
g_free (s);
return TRUE;
}
static void
app_activate (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
GtkWindow *win;
win = gtk_application_get_active_window (app);
gtk_window_present (win);
}
static void
app_startup (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
GtkBuilder *build;
GtkWindow *win;
GtkLabel *src_labels[3];
int i;
GtkLabel *canvas;
GtkDragSource *src;
GdkContentProvider* content;
GtkDropTarget *tgt;
GdkDisplay *display;
char *s;
build = gtk_builder_new_from_resource ("/com/github/ToshioCP/dnd/dnd.ui");
win = GTK_WINDOW (gtk_builder_get_object (build, "win"));
src_labels[0] = GTK_LABEL (gtk_builder_get_object (build, "red"));
src_labels[1] = GTK_LABEL (gtk_builder_get_object (build, "green"));
src_labels[2] = GTK_LABEL (gtk_builder_get_object (build, "blue"));
canvas = GTK_LABEL (gtk_builder_get_object (build, "canvas"));
gtk_window_set_application (win, app);
g_object_unref (build);
for (i=0; i<3; ++i) {
src = gtk_drag_source_new ();
content = gdk_content_provider_new_typed (G_TYPE_STRING, gtk_widget_get_name (GTK_WIDGET (src_labels[i])));
gtk_drag_source_set_content (src, content);
g_object_unref (content);
gtk_widget_add_controller (GTK_WIDGET (src_labels[i]), GTK_EVENT_CONTROLLER (src)); // The ownership of src is taken by the instance.
}
tgt = gtk_drop_target_new (G_TYPE_STRING, GDK_ACTION_COPY);
g_signal_connect (tgt, "drop", G_CALLBACK (drop_cb), NULL);
gtk_widget_add_controller (GTK_WIDGET (canvas), GTK_EVENT_CONTROLLER (tgt)); // The ownership of tgt is taken by the instance.
provider = gtk_css_provider_new ();
s = g_strdup_printf (format, "black");
gtk_css_provider_load_from_data (provider, s, -1);
g_free (s);
display = gdk_display_get_default ();
gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
g_object_unref (provider); // The provider is still alive because the display owns it.
}
#define APPLICATION_ID "com.github.ToshioCP.dnd"
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new (APPLICATION_ID, G_APPLICATION_HANDLES_OPEN);
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/dnd">
<file>dnd.ui</file>
</gresource>
</gresources>

54
src/dnd/dnd.ui Normal file
View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkApplicationWindow" id="win">
<property name="default-width">800</property>
<property name="default-height">600</property>
<property name="resizable">FALSE</property>
<property name="title">Drag and Drop</property>
<child>
<object class="GtkBox">
<property name="hexpand">TRUE</property>
<property name="vexpand">TRUE</property>
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<property name="spacing">5</property>
<child>
<object class="GtkBox">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<property name="homogeneous">TRUE</property>
<child>
<object class="GtkLabel" id="red">
<property name="label">RED</property>
<property name="justify">GTK_JUSTIFY_CENTER</property>
<property name="name">red</property>
</object>
</child>
<child>
<object class="GtkLabel" id="green">
<property name="label">GREEN</property>
<property name="justify">GTK_JUSTIFY_CENTER</property>
<property name="name">green</property>
</object>
</child>
<child>
<object class="GtkLabel" id="blue">
<property name="label">BLUE</property>
<property name="justify">GTK_JUSTIFY_CENTER</property>
<property name="name">blue</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLabel" id="canvas">
<property name="label">CANVAS</property>
<property name="justify">GTK_JUSTIFY_CENTER</property>
<property name="name">canvas</property>
<property name="hexpand">TRUE</property>
<property name="vexpand">TRUE</property>
</object>
</child>
</object>
</child>
</object>
</interface>

8
src/dnd/meson.build Normal file
View file

@ -0,0 +1,8 @@
project('dnd', 'c')
gtkdep = dependency('gtk4')
gnome = import('gnome')
resources = gnome.compile_resources('resources','dnd.gresource.xml')
executable(meson.project_name(), 'dnd.c', resources, dependencies: gtkdep, export_dynamic: true, install: false)

View file

@ -1,26 +1,22 @@
# GtkDrawingArea and Cairo # GtkDrawingArea and Cairo
This section and following sections are *not* updated yet and the programs were checked on the older GTK version than 4.10. If you want to draw shapes or paint images dynamically on the screen, use the GtkDrawingArea widget.
They will be updated in the near future.
If you want to draw dynamically on the screen, like an image window of gimp graphics editor, the GtkDrawingArea widget is the most suitable widget. GtkDrawingArea provides a cairo drawing context.
You can freely draw or redraw an image in this widget. You can draw images with cairo library functions.
This is called custom drawing. This section describes:
GtkDrawingArea provides a cairo drawing context so users can draw images by using cairo functions. 1. Cairo, but briefly
In this section, I will explain:
1. Cairo, but only briefly
2. GtkDrawingArea, with a very simple example. 2. GtkDrawingArea, with a very simple example.
## Cairo ## Cairo
Cairo is a set of two dimensional graphical drawing functions (or graphics library). Cairo is a drawing library for two dimensional graphics.
There are a lot of documents on [Cairo's website](https://www.cairographics.org/). There are a lot of documents on [Cairo's website](https://www.cairographics.org/).
If you aren't familiar with Cairo, it is worth reading the [tutorial](https://www.cairographics.org/tutorial/). If you aren't familiar with Cairo, it is worth reading the [tutorial](https://www.cairographics.org/tutorial/).
The following is an introduction to the Cairo library and how to use it. The following is an introduction to the Cairo library.
First, you need to know about surfaces, sources, masks, destinations, cairo context and transformations. First, you need to know surfaces, sources, masks, destinations, cairo context and transformations.
- A surface represents an image. - A surface represents an image.
It is like a canvas. It is like a canvas.
@ -33,7 +29,7 @@ For example, `cairo_stroke` is a function to draw a path to the destination by t
- A transformation can be applied before the transfer completes. - A transformation can be applied before the transfer completes.
The transformation which is applied is called affine, which is a mathematical term meaning transofrmations The transformation which is applied is called affine, which is a mathematical term meaning transofrmations
that preserve straight lines. that preserve straight lines.
Scaling, rotating, reflecting, shearing and translating are all examples of affine transformations. Scaling, rotating, reflecting, shearing and translating are examples of affine transformations.
They are mathematically represented by matrix multiplication and vector addition. They are mathematically represented by matrix multiplication and vector addition.
In this section we don't use it, instead we will only use the identity transformation. In this section we don't use it, instead we will only use the identity transformation.
This means that the coordinates in the source and mask are the same as the coordinates in destination. This means that the coordinates in the source and mask are the same as the coordinates in destination.
@ -69,28 +65,30 @@ Modern displays have this type of color depth.
Width and height are in pixels and given as integers. Width and height are in pixels and given as integers.
- 14: Creates cairo context. - 14: Creates cairo context.
The surface given as an argument will be the destination of the context. The surface given as an argument will be the destination of the context.
- 18: `cairo_set_source_rgb` creates a source pattern, which in this case is a solid white paint. - 18: `cairo_set_source_rgb` creates a source pattern, which is a solid white paint.
The second to fourth arguments are red, green and blue color values respectively, and they are The second to fourth arguments are red, green and blue color values respectively, and they are of type float.
of type float. The values are between zero (0.0) and one (1.0), with The values are between zero (0.0) and one (1.0).
black being given by (0.0,0.0,0.0) and white by (1.0,1.0,1.0). Black is (0.0,0.0,0.0) and white is (1.0,1.0,1.0).
- 19: `cairo_paint` copies everywhere in the source to destination. - 19: `cairo_paint` copies everywhere in the source to destination.
The destination is filled with white pixels with this command. The destination is filled with white pixels with this command.
- 21: Sets the source color to black. - 21: Sets the source color to black.
- 22: `cairo_set_line_width` set the width of lines. - 22: `cairo_set_line_width` sets the width of lines.
In this case, the line width is set to be two pixels and will end up that same size. In this case, the line width is set to be two pixels and will end up that same size.
(It is because the transformation is identity. (It is because the transformation is identity.
If the transformation isn't identity, for example scaling with the factor three, the actual width in destination will be six (2x3=6) pixels.) If the transformation isn't identity, for example scaling with the factor three, the actual width in destination will be six (2x3=6) pixels.)
- 23: Draws a rectangle (square) on the mask. - 23: Draws a rectangle (square) on the mask.
The square is located at the center. The square is located at the center.
- 24: `cairo_stroke` transfer the source to destination through the rectangle in the mask. - 24: `cairo_stroke` transfers the source to destination through the rectangle in the mask.
- 27: Outputs the image to a png file `rectangle.png`. - 31: Outputs the image to a png file `rectangle.png`.
- 28: Destroys the context. At the same time the source is destroyed. - 32: Destroys the context. At the same time the source is destroyed.
- 29: Destroys the surface. - 33: Destroys the surface.
To compile this, change your current directory to `src/misc` and type the following. To compile this, change your current directory to `src/misc` and type the following.
$ gcc `pkg-config --cflags cairo` cairo.c `pkg-config --libs cairo` ```
$ gcc `pkg-config --cflags cairo` cairo.c `pkg-config --libs cairo`
```
s
![rectangle.png](../image/rectangle.png) ![rectangle.png](../image/rectangle.png)
See the [Cairo's website](https://www.cairographics.org/) for further information. See the [Cairo's website](https://www.cairographics.org/) for further information.
@ -108,7 +106,7 @@ The two functions `app_activate` and `draw_function` are important in this examp
- 22: Creates a GtkDrawingArea instance. - 22: Creates a GtkDrawingArea instance.
- 25: Sets a drawing function of the widget. - 25: Sets a drawing function of the widget.
GtkDrawingArea widget uses the function to draw the contents of itself whenever its necessary. GtkDrawingArea widget uses the function `draw_function` to draw the contents of itself whenever its necessary.
For example, when a user drag a mouse pointer and resize a top-level window, GtkDrawingArea also changes the size. For example, when a user drag a mouse pointer and resize a top-level window, GtkDrawingArea also changes the size.
Then, the whole window needs to be redrawn. Then, the whole window needs to be redrawn.
For the information of `gtk_drawing_area_set_draw_func`, see [Gtk API Reference -- gtk\_drawing\_area\_set\_draw\_func](https://docs.gtk.org/gtk4/method.DrawingArea.set_draw_func.html). For the information of `gtk_drawing_area_set_draw_func`, see [Gtk API Reference -- gtk\_drawing\_area\_set\_draw\_func](https://docs.gtk.org/gtk4/method.DrawingArea.set_draw_func.html).

View file

@ -1,182 +1,240 @@
# Combine GtkDrawingArea and TfeTextView # Custom drawing
Now, we will make a new application which has GtkDrawingArea and TfeTextView in it. Custom drawing is to draw shapes dynamically.
Its name is "color". This section shows an example of custom drawing.
If you write a name of a color in TfeTextView and click on the `run` button, then the color of GtkDrawingArea changes to the color given by you. You can draw rectangles by dragging the mouse.
![color](../image/color.png){width=7.0cm height=5.13cm} Down the button.
The following colors are available. ![down the button](../image/cd0.png){width=6cm height=4.83cm}
(without new line charactor)
- white Move the mouse
- black
- red
- green
- blue
In addition the following two options are also available. ![Move the mouse](../image/cd1.png){width=6cm height=4.83cm}
- light: Make the color of the drawing area lighter. Up the button.
- dark: Make the color of the drawing area darker.
This application can only do very simple things. ![Up the button](../image/cd2.png){width=6cm height=4.83cm}
However, it tells us that if we add powerful parser to it, we will be able to make it more efficient.
I want to show it to you in the later section by making a turtle graphics language like Logo program language.
In this section, we focus on how to bind the two objects. The programs are at `src/custom_drawing` directory.
Download the [repository](https://github.com/ToshioCP/Gtk4-tutorial) and see the directory.
There are four files.
## Color.ui and color.gresource.xml - meson.build
- rect.c
- rect.gresource.xml
- rect.ui
First, We need to make the ui file of the widgets. ## rect.gresource.xml
Title bar, four buttons in the tool bar, textview and drawing area.
The ui file is as follows. This file describes a ui file to compile.
The compiler glib-compile-resources uses it.
@@@include @@@include
color/color.ui custom_drawing/rect.gresource.xml
@@@ @@@
- 10-53: The horizontal box `boxh1` makes a tool bar which has four buttons, `Run`, `Open`, `Save` and `Close`. The prefix is `/com/github/ToshioCP/rect` and the file is `rect.ui`.
This is similar to the `tfe` text editor in [Section 9](sec9.src.md). Therefore, GtkBuilder reads the resource from `/com/github/ToshioCP/rect/rect.ui`.
There are two differences.
`Run` button replaces `New` button.
A signal element is added to each button object.
It has "name" attribute which is a signal name and "handler" attribute which is the name of its signal handler.
Options "-WI, --export-dynamic" CFLAG is necessary when you compile the application.
You can achieve this by adding "export_dynamic: true" argument to the executable function in `meson.build`.
And be careful that the handler must be defined without 'static' class.
- 54-76: The horizontal box `boxh2` includes GtkScrolledWindow and GtkDrawingArea.
GtkBox has "homogeneous property" with TRUE value, so the two children have the same width in the box.
TfeTextView is a child of GtkScrolledWindow.
The xml file for the resource compiler is almost same as before. ## rect.ui
Just substitute "color" for "tfe".
The following is the ui file that defines the widgets.
There are two widgets which are GtkApplicationWindow and GtkDrawingArea.
The ids are `win` and `da` respectively.
@@@include @@@include
color/color.gresource.xml custom_drawing/rect.ui
@@@ @@@
## Drawing function and surface ## rect.c
The main point of this program is a drawing function. ### GtkApplication
This program uses GtkApplication.
The application ID is `com.github.ToshioCP.rect`.
```c
#define APPLICATION_ID "com.github.ToshioCP.rect"
```
See [GNOME Developer Documentation](https://developer.gnome.org/documentation/tutorials/application-id.html) for further information.
The function `main` is called at the beginning of the application.
@@@include @@@include
color/colorapplication.c draw_func custom_drawing/rect.c main
@@@ @@@
The `surface` variable in line 3 is a static variable. It connects three signals and handlers.
~~~C - startup: It is emitted after the application is registered to the system.
- activate: It is emitted when the application is activated.
- shutdown: It is emitted just before the application quits.
@@@include
custom_drawing/rect.c app_startup
@@@
The startup handler does three things.
- Builds the widgets.
- Initializes the GtkDrawingArea instance.
- Sets the drawing function
- Connects the "resize" signal and the handler.
- Creates the GtkGestureDrag instance and initializes it.
Gesture will be explained in this section later.
@@@include
custom_drawing/rect.c app_activate
@@@
The activate handler just shows the window.
### GtkDrawingArea
The program has two cairo surfaces and they are pointed by the global variables.
@@@if gfm
```C
static cairo_surface_t *surface = NULL; static cairo_surface_t *surface = NULL;
~~~ static cairo_surface_t *surface_save = NULL;
```
@@@else
```{.C}
static cairo_surface_t *surface = NULL;
static cairo_surface_t *surface_save = NULL;
```
@@@end
The drawing function just copies the `surface` to its own surface with the `cairo_paint` function. The drawing process is as follows.
The surface (pointed by the static variable `surface`) is built by the `run` function.
- Creates an image on `surface`.
- Copies `surface` to the cairo surface of the GtkDrawingArea.
- Calls ` gtk_widget_queue_draw (da)` to draw it if necessary.
They are created in the "resize" signal handler.
@@@include @@@include
color/colorapplication.c run custom_drawing/rect.c resize_cb
@@@ @@@
- 9-10: Gets the string in the GtkTextBuffer and inserts it to `contents`. This callback is called when the GtkDrawingArea is shown.
- 11: If the variable `surface` points a surface instance, it is painted as follows. It is the only call because the window is not resizable.
- 12- 30: The source is set based on the string `contents` and copied to the surface with `cairo_paint`.
- 24,26: Alpha channel is used in "light" and "dark" procedure.
The drawing area just reflects the `surface`. It creates image surfaces for `surface` and `surface_save`.
But one problem is resizing. The `surface` surface is painted white, which is the background color.
If a user resizes the main window, the drawing area is also resized.
It makes size difference between the surface and the drawing area.
So, the surface needs to be resized to fit the drawing area.
It is accomplished by connecting the "resize" signal on the drawing area to a handler. The drawing function copies `surface` to the GtkDrawingArea surface.
~~~C
g_signal_connect (GTK_DRAWING_AREA (da), "resize", G_CALLBACK (resize_cb), NULL);
~~~
The handler is as follows.
@@@include @@@include
color/colorapplication.c resize_cb custom_drawing/rect.c draw_cb
@@@ @@@
If the variable `surface` sets a surface instance, it is destroyed. This function is called by the system when it needs to redraw the drawing area.
A new surface is created and its size fits the drawing area.
The surface is assigned to the variable `surface`.
The function `run` is called and the surface is colored.
The signal is emitted when: Two surfaces `surface` and `surface_save` are destroyed before the application quits.
- The drawing area is realized (it appears on the display).
- It is changed (resized) while realized
So, the first surface is created when it is realized.
## Colorapplication.c
This is the main file.
- Builds widgets by GtkBuilder.
- Sets a drawing function for GtkDrawingArea.
And connects a handler to the "resize" signal on the GtkDrawingArea instance.
- Implements each call back function.
Particularly, `Run` signal handler is the point in this program.
The following is `colorapplication.c`.
@@@include @@@include
color/colorapplication.c custom_drawing/rect.c app_shutdown
@@@ @@@
- 4-8: Win, tv, da and surface are defined as static variables. ### GtkGestureDrag
- 10-42: Run function.
- 44-63: Handlers for button signals.
- 65-71: Resize handler.
- 73-79: Drawing function.
- 81-84: Application activate handler.
It just shows the main window.
- 86-105: Application startup handler.
- 92- 97: It builds widgets according to the ui resource.
The static variables win, tv and da are assigned instances.
- 98: Connects "resize" signal and a handler.
- 99: Drawing function is set.
- 101-104: CSS for textview padding is set.
- 107-111: Application shutdown handler.
If there exists a surface instance, it will be destroyed.
- 116-129: A function `main`.
It creates a new application instance.
And connects three signals startup, shutdown and activate to their handlers.
It runs the application.
It releases the reference to the application and returns with `stat` value.
## Meson.build Gesture class is used to recognize human gestures such as click, drag, pan, swipe and so on.
It is a subclass of GtkEventController.
GtkGesture class is abstract and there are several implementations.
This file is almost same as before. - GtkGestureClick
An argument "export_dynamic: true" is added to executable function. - GtkGestureDrag
- GtkGesturePan
- GtkGestureSwipe
- other implementations
The program `rect.c` uses GtkGestureDrag.
It is the implementation for drags.
The parent-child relationship is as follows.
```
GObject -- GtkEventController -- GtkGesture -- GtkGestureSingle -- GtkGestureDrag
```
GtkGestureSingle is a subclass of GtkGesture and optimized for singe-touch and mouse gestures.
A GtkGestureDrag instance is created and initialized in the startup signal handler in `rect.c`.
See line 18 to 23 in the following.
@@@include @@@include
color/meson.build custom_drawing/rect.c app_startup
@@@ @@@
## Build and try - The function `gtk_gesture_drag_new` creates a new GtkGestureDrag instance.
- The function `gtk_gesture_single_set_button` sets the button number to listen to.
The constant `GDK_BUTTON_PRIMARY` is the left button of a mouse.
- The function `gtk_widget_add_controller` adds an event controller, gestures are descendants of the event controller, to a widget.
- Three signals and handlers are connected.
- drag-begin: Emitted when dragging starts.
- drag-update: Emitted when the dragging point moves.
- drag-end: Emitted when the dragging ends.
Type the following to compile the program. The process during the drag is as follows.
$ meson _build - start: save the surface and start points
$ ninja -C _build - update: restore the surface and draw a thin rectangle between the start point and the current point of the mouse
- end: restore the surface and draw a thick rectangle between the start and end points.
The application is made in `_build` directory. We need two global variables for the start point.
Type the following to execute it.
$ _build/color @@@if gfm
```C
static double start_x;
static double start_y;
```
@@@else
```{.C}
static double start_x;
static double start_y;
```
@@@end
Type "red", "green", "blue", "white", black", "light" or "dark" in the TfeTextView. The following is the handler for the "drag-begin" signal.
No new line charactor is needed.
Then, click on the `Run` button.
Make sure the color of GtkDrawingArea changes.
In this program TfeTextView is used to change the color. @@@include
You can use buttons or menus instead of textview. custom_drawing/rect.c copy_surface drag_begin
Probably it is more appropriate. @@@
Using textview is unnatural.
It is a good practice to make such application by yourself. - Copies `surface` to `surface_save`, which is an image just before the dragging.
- Stores the points to `start_x` and `start_y`.
@@@include
custom_drawing/rect.c drag_update
@@@
- Restores `surface` from `surface_save`.
- Draws a rectangle with thin lines.
- Calls `gtk_widget_queue_draw` to add the GtkDrawingArea to the queue to redraw.
@@@include
custom_drawing/rect.c drag_end
@@@
- Restores `surface` from `surface_save`.
- Draws a rectangle with thick lines.
- Calls `gtk_widget_queue_draw` to add the GtkDrawingArea to the queue to redraw.
## Build and run
Download the [repository](https://github.com/ToshioCP/Gtk4-tutorial).
Change your current directory to `src/custom_drawing`.
Run meson and ninja to build the program.
Type `_build/rect` to run the program.
Try to draw rectangles.
```
$ cd src/custom_drawing
$ meson setup _build
$ ninja -C _build
$ _build/rect
```
![The screen of rect program](../image/rect.png){width=12.4cm height=10cm}

View file

@ -2,7 +2,7 @@
A program `turtle` is an example with the combination of TfeTextView and GtkDrawingArea objects. A program `turtle` is an example with the combination of TfeTextView and GtkDrawingArea objects.
It is a very small interpreter but you can draw fractal curves with it. It is a very small interpreter but you can draw fractal curves with it.
The following diagram is a Koch curve, which is one of famous fractal curves. The following diagram is a Koch curve, which is one of the famous fractal curves.
![Koch curve](turtle/image/turtle_koch.png){width=8cm height=5.11cm} ![Koch curve](turtle/image/turtle_koch.png){width=8cm height=5.11cm}

View file

@ -143,7 +143,7 @@ app_activate (GApplication *app, gpointer user_data) {
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA (clock), draw_clock, NULL, NULL); gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA (clock), draw_clock, NULL, NULL);
g_timeout_add(1000, (GSourceFunc) time_handler, (gpointer) clock); g_timeout_add(1000, (GSourceFunc) time_handler, (gpointer) clock);
gtk_widget_show(win); gtk_window_present(GTK_WINDOW (win));
} }

251
src/tfc/tfc.gresource.c Normal file
View file

@ -0,0 +1,251 @@
#include <gio/gio.h>
#if defined (__ELF__) && ( __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 6))
# define SECTION __attribute__ ((section (".gresource.tfc"), aligned (8)))
#else
# define SECTION
#endif
static const SECTION union { const guint8 data[739]; const double alignment; void * const ptr;} tfc_resource_data = {
"\107\126\141\162\151\141\156\164\000\000\000\000\000\000\000\000"
"\030\000\000\000\310\000\000\000\000\000\000\050\006\000\000\000"
"\000\000\000\000\002\000\000\000\002\000\000\000\003\000\000\000"
"\003\000\000\000\005\000\000\000\130\345\125\000\004\000\000\000"
"\310\000\000\000\004\000\114\000\314\000\000\000\320\000\000\000"
"\224\135\334\227\003\000\000\000\320\000\000\000\007\000\114\000"
"\330\000\000\000\334\000\000\000\324\265\002\000\377\377\377\377"
"\334\000\000\000\001\000\114\000\340\000\000\000\344\000\000\000"
"\302\257\211\013\002\000\000\000\344\000\000\000\004\000\114\000"
"\350\000\000\000\354\000\000\000\214\044\075\224\001\000\000\000"
"\354\000\000\000\011\000\114\000\370\000\000\000\374\000\000\000"
"\141\320\165\220\000\000\000\000\374\000\000\000\006\000\166\000"
"\010\001\000\000\342\002\000\000\164\146\143\057\005\000\000\000"
"\147\151\164\150\165\142\057\000\004\000\000\000\057\000\000\000"
"\003\000\000\000\143\157\155\057\001\000\000\000\124\157\163\150"
"\151\157\103\120\057\000\000\000\000\000\000\000\164\146\143\056"
"\165\151\000\000\000\000\000\000\312\001\000\000\000\000\000\000"
"\074\077\170\155\154\040\166\145\162\163\151\157\156\075\042\061"
"\056\060\042\040\145\156\143\157\144\151\156\147\075\042\125\124"
"\106\055\070\042\077\076\012\074\151\156\164\145\162\146\141\143"
"\145\076\012\040\040\074\157\142\152\145\143\164\040\143\154\141"
"\163\163\075\042\107\164\153\101\160\160\154\151\143\141\164\151"
"\157\156\127\151\156\144\157\167\042\040\151\144\075\042\167\151"
"\156\042\076\012\040\040\040\040\074\160\162\157\160\145\162\164"
"\171\040\156\141\155\145\075\042\164\151\164\154\145\042\076\103"
"\154\157\143\153\074\057\160\162\157\160\145\162\164\171\076\012"
"\040\040\040\040\074\160\162\157\160\145\162\164\171\040\156\141"
"\155\145\075\042\144\145\146\141\165\154\164\055\167\151\144\164"
"\150\042\076\062\060\060\074\057\160\162\157\160\145\162\164\171"
"\076\012\040\040\040\040\074\160\162\157\160\145\162\164\171\040"
"\156\141\155\145\075\042\144\145\146\141\165\154\164\055\150\145"
"\151\147\150\164\042\076\062\060\060\074\057\160\162\157\160\145"
"\162\164\171\076\012\040\040\040\040\074\143\150\151\154\144\076"
"\012\040\040\040\040\040\040\074\157\142\152\145\143\164\040\143"
"\154\141\163\163\075\042\107\164\153\104\162\141\167\151\156\147"
"\101\162\145\141\042\040\151\144\075\042\143\154\157\143\153\042"
"\076\012\040\040\040\040\040\040\040\040\074\160\162\157\160\145"
"\162\164\171\040\156\141\155\145\075\042\150\145\170\160\141\156"
"\144\042\076\124\122\125\105\074\057\160\162\157\160\145\162\164"
"\171\076\012\040\040\040\040\040\040\040\040\074\160\162\157\160"
"\145\162\164\171\040\156\141\155\145\075\042\166\145\170\160\141"
"\156\144\042\076\124\122\125\105\074\057\160\162\157\160\145\162"
"\164\171\076\012\040\040\040\040\040\040\074\057\157\142\152\145"
"\143\164\076\012\040\040\040\040\074\057\143\150\151\154\144\076"
"\012\040\040\074\057\157\142\152\145\143\164\076\012\074\057\151"
"\156\164\145\162\146\141\143\145\076\012\000\000\050\165\165\141"
"\171\051" };
static GStaticResource static_resource = { tfc_resource_data.data, sizeof (tfc_resource_data.data) - 1 /* nul terminator */, NULL, NULL, NULL };
G_MODULE_EXPORT
GResource *tfc_get_resource (void);
GResource *tfc_get_resource (void)
{
return g_static_resource_get_resource (&static_resource);
}
/* GLIB - Library of useful routines for C programming
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
/*
* Modified by the GLib Team and others 1997-2000. See the AUTHORS
* file for a list of people on the GLib Team. See the ChangeLog
* files for a list of changes. These files are distributed with
* GLib at ftp://ftp.gtk.org/pub/gtk/.
*/
#ifndef __G_CONSTRUCTOR_H__
#define __G_CONSTRUCTOR_H__
/*
If G_HAS_CONSTRUCTORS is true then the compiler support *both* constructors and
destructors, in a usable way, including e.g. on library unload. If not you're on
your own.
Some compilers need #pragma to handle this, which does not work with macros,
so the way you need to use this is (for constructors):
#ifdef G_DEFINE_CONSTRUCTOR_NEEDS_PRAGMA
#pragma G_DEFINE_CONSTRUCTOR_PRAGMA_ARGS(my_constructor)
#endif
G_DEFINE_CONSTRUCTOR(my_constructor)
static void my_constructor(void) {
...
}
*/
#ifndef __GTK_DOC_IGNORE__
#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 7)
#define G_HAS_CONSTRUCTORS 1
#define G_DEFINE_CONSTRUCTOR(_func) static void __attribute__((constructor)) _func (void);
#define G_DEFINE_DESTRUCTOR(_func) static void __attribute__((destructor)) _func (void);
#elif defined (_MSC_VER) && (_MSC_VER >= 1500)
/* Visual studio 2008 and later has _Pragma */
/*
* Only try to include gslist.h if not already included via glib.h,
* so that items using gconstructor.h outside of GLib (such as
* GResources) continue to build properly.
*/
#ifndef __G_LIB_H__
#include "gslist.h"
#endif
#include <stdlib.h>
#define G_HAS_CONSTRUCTORS 1
/* We do some weird things to avoid the constructors being optimized
* away on VS2015 if WholeProgramOptimization is enabled. First we
* make a reference to the array from the wrapper to make sure its
* references. Then we use a pragma to make sure the wrapper function
* symbol is always included at the link stage. Also, the symbols
* need to be extern (but not dllexport), even though they are not
* really used from another object file.
*/
/* We need to account for differences between the mangling of symbols
* for x86 and x64/ARM/ARM64 programs, as symbols on x86 are prefixed
* with an underscore but symbols on x64/ARM/ARM64 are not.
*/
#ifdef _M_IX86
#define G_MSVC_SYMBOL_PREFIX "_"
#else
#define G_MSVC_SYMBOL_PREFIX ""
#endif
#define G_DEFINE_CONSTRUCTOR(_func) G_MSVC_CTOR (_func, G_MSVC_SYMBOL_PREFIX)
#define G_DEFINE_DESTRUCTOR(_func) G_MSVC_DTOR (_func, G_MSVC_SYMBOL_PREFIX)
#define G_MSVC_CTOR(_func,_sym_prefix) \
static void _func(void); \
extern int (* _array ## _func)(void); \
int _func ## _wrapper(void) { _func(); g_slist_find (NULL, _array ## _func); return 0; } \
__pragma(comment(linker,"/include:" _sym_prefix # _func "_wrapper")) \
__pragma(section(".CRT$XCU",read)) \
__declspec(allocate(".CRT$XCU")) int (* _array ## _func)(void) = _func ## _wrapper;
#define G_MSVC_DTOR(_func,_sym_prefix) \
static void _func(void); \
extern int (* _array ## _func)(void); \
int _func ## _constructor(void) { atexit (_func); g_slist_find (NULL, _array ## _func); return 0; } \
__pragma(comment(linker,"/include:" _sym_prefix # _func "_constructor")) \
__pragma(section(".CRT$XCU",read)) \
__declspec(allocate(".CRT$XCU")) int (* _array ## _func)(void) = _func ## _constructor;
#elif defined (_MSC_VER)
#define G_HAS_CONSTRUCTORS 1
/* Pre Visual studio 2008 must use #pragma section */
#define G_DEFINE_CONSTRUCTOR_NEEDS_PRAGMA 1
#define G_DEFINE_DESTRUCTOR_NEEDS_PRAGMA 1
#define G_DEFINE_CONSTRUCTOR_PRAGMA_ARGS(_func) \
section(".CRT$XCU",read)
#define G_DEFINE_CONSTRUCTOR(_func) \
static void _func(void); \
static int _func ## _wrapper(void) { _func(); return 0; } \
__declspec(allocate(".CRT$XCU")) static int (*p)(void) = _func ## _wrapper;
#define G_DEFINE_DESTRUCTOR_PRAGMA_ARGS(_func) \
section(".CRT$XCU",read)
#define G_DEFINE_DESTRUCTOR(_func) \
static void _func(void); \
static int _func ## _constructor(void) { atexit (_func); return 0; } \
__declspec(allocate(".CRT$XCU")) static int (* _array ## _func)(void) = _func ## _constructor;
#elif defined(__SUNPRO_C)
/* This is not tested, but i believe it should work, based on:
* http://opensource.apple.com/source/OpenSSL098/OpenSSL098-35/src/fips/fips_premain.c
*/
#define G_HAS_CONSTRUCTORS 1
#define G_DEFINE_CONSTRUCTOR_NEEDS_PRAGMA 1
#define G_DEFINE_DESTRUCTOR_NEEDS_PRAGMA 1
#define G_DEFINE_CONSTRUCTOR_PRAGMA_ARGS(_func) \
init(_func)
#define G_DEFINE_CONSTRUCTOR(_func) \
static void _func(void);
#define G_DEFINE_DESTRUCTOR_PRAGMA_ARGS(_func) \
fini(_func)
#define G_DEFINE_DESTRUCTOR(_func) \
static void _func(void);
#else
/* constructors not supported for this compiler */
#endif
#endif /* __GTK_DOC_IGNORE__ */
#endif /* __G_CONSTRUCTOR_H__ */
#ifdef G_HAS_CONSTRUCTORS
#ifdef G_DEFINE_CONSTRUCTOR_NEEDS_PRAGMA
#pragma G_DEFINE_CONSTRUCTOR_PRAGMA_ARGS(tfcresource_constructor)
#endif
G_DEFINE_CONSTRUCTOR(tfcresource_constructor)
#ifdef G_DEFINE_DESTRUCTOR_NEEDS_PRAGMA
#pragma G_DEFINE_DESTRUCTOR_PRAGMA_ARGS(tfcresource_destructor)
#endif
G_DEFINE_DESTRUCTOR(tfcresource_destructor)
#else
#warning "Constructor not supported on this compiler, linking in resources will not work"
#endif
static void tfcresource_constructor (void)
{
g_static_resource_init (&static_resource);
}
static void tfcresource_destructor (void)
{
g_static_resource_fini (&static_resource);
}