Section 22, 24 and 25 are updated.

This commit is contained in:
Toshio Sekiya 2023-01-05 20:41:02 +09:00
parent d4317f1140
commit a8cf7176df
23 changed files with 2408 additions and 1655 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/image/turtle_snow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

View file

@ -119,19 +119,18 @@ 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">
<li>Cairo, but only briefly; and</li>
<li>Cairo, but only briefly</li>
<li>GtkDrawingArea, with a very simple example.</li>
</ol>
<h2 id="cairo">Cairo</h2>
<p>Cairo is a set of two dimensional graphical drawing functions (or
graphics library). There is a lot of documentation on <a
graphics library). There are a lot of documents on <a
href="https://www.cairographics.org/">Cairos website</a>. If you arent
familiar with Cairo, it is worth reading their <a
familiar with Cairo, it is worth reading the <a
href="https://www.cairographics.org/tutorial/">tutorial</a>.</p>
<p>The following is a gentle introduction to the Cairo library and how
to use it. Firstly, in order to use Cairo you need to know about
surfaces, sources, masks, destinations, cairo context and
transformations.</p>
<p>The following is an introduction to the Cairo library and how to use
it. First, you need to know about surfaces, sources, masks,
destinations, cairo context and transformations.</p>
<ul>
<li>A surface represents an image. It is like a canvas. We can draw
shapes and images with different colors on surfaces.</li>
@ -169,7 +168,8 @@ the paint in the source to the destination.</li>
<li>Save the destination surface to a file if necessary.</li>
</ol>
<p>Heres a simple example program that draws a small square and saves
it as a png file.</p>
it as a png file. The path of the file is
<code>src/misc/cairo.c</code>.</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb1-1"><a href="#cb1-1"></a><span class="pp">#include </span><span class="im">&lt;cairo.h&gt;</span></span>
<span id="cb1-2"><a href="#cb1-2"></a></span>
@ -222,10 +222,10 @@ 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
the destination of the context.</li>
<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 argument
are red, green and blue color values respectively, and they are of type
float. The values are between zero (0.0) and one (1.0), with black being
given by (0.0,0.0,0.0) and white by (1.0,1.0,1.0).</li>
which in this case is a solid white paint. The second to fourth
arguments are red, green and blue color values respectively, and they
are of type float. The values are between zero (0.0) and one (1.0), with
black being given by (0.0,0.0,0.0) and white by (1.0,1.0,1.0).</li>
<li>19: <code>cairo_paint</code> copies everywhere in the source to
destination. The destination is filled with white pixels with this
command.</li>
@ -244,14 +244,15 @@ through the rectangle in the mask.</li>
destroyed.</li>
<li>29: Destroys the surface.</li>
</ul>
<p>To compile this, type the following.</p>
<p>To compile this, change your current directory to
<code>src/misc</code> and type the following.</p>
<pre><code>$ gcc `pkg-config --cflags cairo` cairo.c `pkg-config --libs cairo`</code></pre>
<figure>
<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>
for more details.</p>
for further information.</p>
<h2 id="gtkdrawingarea">GtkDrawingArea</h2>
<p>The following is a very simple example.</p>
<div class="sourceCode" id="cb3"><pre
@ -302,15 +303,15 @@ class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span i
functions <code>app_activate</code> and <code>draw_function</code> are
important in this example.</p>
<ul>
<li>18: Creates a GtkDrawingArea instance; and</li>
<li>21: Sets a drawing function of the widget. GtkDrawingArea widget
<li>22: Creates a GtkDrawingArea instance.</li>
<li>25: Sets a drawing function of the widget. GtkDrawingArea widget
uses the 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. Then, the whole window
needs to be redrawn. For the information of
<code>gtk_drawing_area_set_draw_func</code>, see <a
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>
</ul>
<p>The drawing function has five parameters.</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> drawing_function <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></span>
@ -322,20 +323,20 @@ cairo context given by the widget. The destination surface of the
context is connected to the contents of the widget. What you draw to
this surface will appear in the widget on the screen. The third and
fourth parameters are the size of the destination surface. Now, look at
the program example again.</p>
the program again.</p>
<ul>
<li>3-13: The drawing function.</li>
<li>3-17: The drawing function.</li>
<li>7-8: Sets the source to be white and paint the destination
white.</li>
<li>9: Sets the line width to be 2.</li>
<li>10: Sets the source to be black.</li>
<li>11: Adds a rectangle to the mask.</li>
<li>12: Draws the rectangle with black color to the destination.</li>
<li>11-15: Adds a rectangle to the mask.</li>
<li>16: Draws the rectangle with black color to the destination.</li>
</ul>
<p>Compile and run it, then a window with a black rectangle (square)
appears. Try resizing the window. The square always appears at the
center of the window because the drawing function is invoked each time
the window is resized.</p>
<p>The program is src/misc/da1.c. Compile and run it, then a window with
a black rectangle (square) appears. Try resizing the window. The square
always appears at the center of the window because the drawing function
is invoked each time the window is resized.</p>
<figure>
<img src="image/da1.png" alt="Square in the window" />
<figcaption aria-hidden="true">Square in the window</figcaption>

View file

@ -121,7 +121,7 @@ of GtkDrawingArea changes to the color given by you.</p>
<img src="image/color.png" alt="color" />
<figcaption aria-hidden="true">color</figcaption>
</figure>
<p>The following colors are available.</p>
<p>The following colors are available. (without new line charactor)</p>
<ul>
<li>white</li>
<li>black</li>
@ -141,10 +141,9 @@ 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. The image in the
previous subsection gives us the structure of the widgets. Title bar,
four buttons in the tool bar and two widgets textview and drawing area.
The ui file is as follows.</p>
<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
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>
@ -227,21 +226,22 @@ class="sourceCode numberSource xml numberLines"><code class="sourceCode xml"><sp
<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: This part is the tool bar which has four buttons,
<code>Run</code>, <code>Open</code>, <code>Save</code> and
<code>Close</code>. This is similar to the toolbar of tfe text editor in
<a href="sec9.html">Section 9</a>. There are two differences.
<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 function. Options “-WI, export-dynamic” CFLAG is necessary when
you compile the application. You can achieve this by adding
“export_dynamic: true” argument to executable function in
<code>meson.build</code>. And be careful that the handler must be
defined without static class.</li>
<li>54-76: Puts GtkScrolledWindow and GtkDrawingArea into GtkBox. 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>
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>
@ -252,216 +252,278 @@ class="sourceCode numberSource xml numberLines"><code class="sourceCode xml"><sp
<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-5"><a href="#cb2-5"></a> &lt;/<span class="kw">gresource</span>&gt;</span>
<span id="cb2-6"><a href="#cb2-6"></a>&lt;/<span class="kw">gresources</span>&gt;</span></code></pre></div>
<h2 id="tfetextview.h-tfetextview.c-and-color.h">Tfetextview.h,
tfetextview.c and color.h</h2>
<p>First two files are the same as before. Color.h just includes
tfetextview.h.</p>
<h2 id="drawing-function-and-surface">Drawing function and surface</h2>
<p>The main point of this program is a drawing function.</p>
<div class="sourceCode" id="cb3"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb3-1"><a href="#cb3-1"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<span id="cb3-2"><a href="#cb3-2"></a></span>
<span id="cb3-3"><a href="#cb3-3"></a><span class="pp">#include </span><span class="im">&quot;../tfetextview/tfetextview.h&quot;</span></span></code></pre></div>
<h2 id="colorapplication.c">Colorapplication.c</h2>
<p>This is the main file. It deals with:</p>
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="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="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="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="cb3-5"><a href="#cb3-5"></a> cairo_paint <span class="op">(</span>cr<span class="op">);</span></span>
<span id="cb3-6"><a href="#cb3-6"></a> <span class="op">}</span></span>
<span id="cb3-7"><a href="#cb3-7"></a><span class="op">}</span></span></code></pre></div>
<p>The <code>surface</code> variable in line 3 is a static variable.</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="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>The drawing function just copies the <code>surface</code> to its own
surface with the <code>cairo_paint</code> function. The surface (pointed
by the static variable <code>surface</code>) is built by the
<code>run</code> function.</p>
<div class="sourceCode" id="cb5"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb5-1"><a href="#cb5-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb5-2"><a href="#cb5-2"></a>run <span class="op">(</span><span class="dt">void</span><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-4"><a href="#cb5-4"></a> GtkTextIter start_iter<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-6"><a href="#cb5-6"></a> <span class="dt">char</span> <span class="op">*</span>contents<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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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>
<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>
<li>Building widgets by GtkBuilder.</li>
<li>Setting a drawing function of GtkDrawingArea. And connecting a
handler to “resize” signal on GtkDrawingArea.</li>
<li>Implementing each call back functions. Particularly,
<code>Run</code> signal handler is the point in this program.</li>
<li>9-10: Gets the string in the GtkTextBuffer and inserts it to
<code>contents</code>.</li>
<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>
<li>The drawing area is realized (it appears on the display).</li>
<li>It is changed (resized) while realized</li>
</ul>
<p>So, the first surface is created when it is realized.</p>
<h2 id="colorapplication.c">Colorapplication.c</h2>
<p>This is the main file.</p>
<ul>
<li>Builds widgets by GtkBuilder.</li>
<li>Sets a drawing function for GtkDrawingArea. And connects a handler
to the “resize” signal on the GtkDrawingArea instance.</li>
<li>Implements each call back function. Particularly, <code>Run</code>
signal handler is the point in this program.</li>
</ul>
<p>The following is <code>colorapplication.c</code>.</p>
<div class="sourceCode" id="cb4"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb4-1"><a href="#cb4-1"></a><span class="pp">#include </span><span class="im">&quot;color.h&quot;</span></span>
<span id="cb4-2"><a href="#cb4-2"></a></span>
<span id="cb4-3"><a href="#cb4-3"></a><span class="dt">static</span> GtkWidget <span class="op">*</span>win<span class="op">;</span></span>
<span id="cb4-4"><a href="#cb4-4"></a><span class="dt">static</span> GtkWidget <span class="op">*</span>tv<span class="op">;</span></span>
<span id="cb4-5"><a href="#cb4-5"></a><span class="dt">static</span> GtkWidget <span class="op">*</span>da<span class="op">;</span></span>
<span id="cb4-6"><a href="#cb4-6"></a></span>
<span id="cb4-7"><a href="#cb4-7"></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="cb4-8"><a href="#cb4-8"></a></span>
<span id="cb4-9"><a href="#cb4-9"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb4-10"><a href="#cb4-10"></a>run <span class="op">(</span><span class="dt">void</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-11"><a href="#cb4-11"></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="cb4-12"><a href="#cb4-12"></a> GtkTextIter start_iter<span class="op">;</span></span>
<span id="cb4-13"><a href="#cb4-13"></a> GtkTextIter end_iter<span class="op">;</span></span>
<span id="cb4-14"><a href="#cb4-14"></a> <span class="dt">char</span> <span class="op">*</span>contents<span class="op">;</span></span>
<span id="cb4-15"><a href="#cb4-15"></a> cairo_t <span class="op">*</span>cr<span class="op">;</span></span>
<span id="cb4-16"><a href="#cb4-16"></a></span>
<span id="cb4-17"><a href="#cb4-17"></a> gtk_text_buffer_get_bounds <span class="op">(</span>tb<span class="op">,</span> <span class="op">&amp;</span>start_iter<span class="op">,</span> <span class="op">&amp;</span>end_iter<span class="op">);</span></span>
<span id="cb4-18"><a href="#cb4-18"></a> contents <span class="op">=</span> gtk_text_buffer_get_text <span class="op">(</span>tb<span class="op">,</span> <span class="op">&amp;</span>start_iter<span class="op">,</span> <span class="op">&amp;</span>end_iter<span class="op">,</span> FALSE<span class="op">);</span></span>
<span id="cb4-19"><a href="#cb4-19"></a> <span class="cf">if</span> <span class="op">(</span>surface<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-20"><a href="#cb4-20"></a> cr <span class="op">=</span> cairo_create <span class="op">(</span>surface<span class="op">);</span></span>
<span id="cb4-21"><a href="#cb4-21"></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="cb4-22"><a href="#cb4-22"></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="cb4-23"><a href="#cb4-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;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="cb4-24"><a href="#cb4-24"></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="cb4-25"><a href="#cb4-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;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="cb4-26"><a href="#cb4-26"></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="cb4-27"><a href="#cb4-27"></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="cb4-28"><a href="#cb4-28"></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="cb4-29"><a href="#cb4-29"></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="cb4-30"><a href="#cb4-30"></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="cb4-31"><a href="#cb4-31"></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="cb4-32"><a href="#cb4-32"></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="cb4-33"><a href="#cb4-33"></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="cb4-34"><a href="#cb4-34"></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="cb4-35"><a href="#cb4-35"></a> <span class="cf">else</span></span>
<span id="cb4-36"><a href="#cb4-36"></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="cb4-37"><a href="#cb4-37"></a> cairo_paint <span class="op">(</span>cr<span class="op">);</span></span>
<span id="cb4-38"><a href="#cb4-38"></a> cairo_destroy <span class="op">(</span>cr<span class="op">);</span></span>
<span id="cb4-39"><a href="#cb4-39"></a> <span class="op">}</span></span>
<span id="cb4-40"><a href="#cb4-40"></a> g_free <span class="op">(</span>contents<span class="op">);</span></span>
<span id="cb4-41"><a href="#cb4-41"></a><span class="op">}</span></span>
<span id="cb4-42"><a href="#cb4-42"></a></span>
<span id="cb4-43"><a href="#cb4-43"></a><span class="dt">void</span></span>
<span id="cb4-44"><a href="#cb4-44"></a>run_cb <span class="op">(</span>GtkWidget <span class="op">*</span>btnr<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-45"><a href="#cb4-45"></a> run <span class="op">();</span></span>
<span id="cb4-46"><a href="#cb4-46"></a> gtk_widget_queue_draw <span class="op">(</span>GTK_WIDGET <span class="op">(</span>da<span class="op">));</span></span>
<span id="cb4-47"><a href="#cb4-47"></a><span class="op">}</span></span>
<span id="cb4-48"><a href="#cb4-48"></a></span>
<span id="cb4-49"><a href="#cb4-49"></a><span class="dt">void</span></span>
<span id="cb4-50"><a href="#cb4-50"></a>open_cb <span class="op">(</span>GtkWidget <span class="op">*</span>btno<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-51"><a href="#cb4-51"></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="cb4-52"><a href="#cb4-52"></a><span class="op">}</span></span>
<span id="cb4-53"><a href="#cb4-53"></a></span>
<span id="cb4-54"><a href="#cb4-54"></a><span class="dt">void</span></span>
<span id="cb4-55"><a href="#cb4-55"></a>save_cb <span class="op">(</span>GtkWidget <span class="op">*</span>btns<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-56"><a href="#cb4-56"></a> tfe_text_view_save <span class="op">(</span>TFE_TEXT_VIEW <span class="op">(</span>tv<span class="op">));</span></span>
<span id="cb4-57"><a href="#cb4-57"></a><span class="op">}</span></span>
<span id="cb4-58"><a href="#cb4-58"></a></span>
<span id="cb4-59"><a href="#cb4-59"></a><span class="dt">void</span></span>
<span id="cb4-60"><a href="#cb4-60"></a>close_cb <span class="op">(</span>GtkWidget <span class="op">*</span>btnc<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-61"><a href="#cb4-61"></a> <span class="cf">if</span> <span class="op">(</span>surface<span class="op">)</span></span>
<span id="cb4-62"><a href="#cb4-62"></a> cairo_surface_destroy <span class="op">(</span>surface<span class="op">);</span></span>
<span id="cb4-63"><a href="#cb4-63"></a> gtk_window_destroy <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">));</span></span>
<span id="cb4-64"><a href="#cb4-64"></a><span class="op">}</span></span>
<span id="cb4-65"><a href="#cb4-65"></a></span>
<span id="cb4-66"><a href="#cb4-66"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb4-67"><a href="#cb4-67"></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="cb4-68"><a href="#cb4-68"></a> <span class="cf">if</span> <span class="op">(</span>surface<span class="op">)</span></span>
<span id="cb4-69"><a href="#cb4-69"></a> cairo_surface_destroy <span class="op">(</span>surface<span class="op">);</span></span>
<span id="cb4-70"><a href="#cb4-70"></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="cb4-71"><a href="#cb4-71"></a> run <span class="op">();</span></span>
<span id="cb4-72"><a href="#cb4-72"></a><span class="op">}</span></span>
<span id="cb4-73"><a href="#cb4-73"></a></span>
<span id="cb4-74"><a href="#cb4-74"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb4-75"><a href="#cb4-75"></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="cb4-76"><a href="#cb4-76"></a> <span class="cf">if</span> <span class="op">(</span>surface<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-77"><a href="#cb4-77"></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="cb4-78"><a href="#cb4-78"></a> cairo_paint <span class="op">(</span>cr<span class="op">);</span></span>
<span id="cb4-79"><a href="#cb4-79"></a> <span class="op">}</span></span>
<span id="cb4-80"><a href="#cb4-80"></a><span class="op">}</span></span>
<span id="cb4-81"><a href="#cb4-81"></a></span>
<span id="cb4-82"><a href="#cb4-82"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb4-83"><a href="#cb4-83"></a>app_activate <span class="op">(</span>GApplication <span class="op">*</span>application<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-84"><a href="#cb4-84"></a> gtk_widget_show <span class="op">(</span>win<span class="op">);</span></span>
<span id="cb4-85"><a href="#cb4-85"></a><span class="op">}</span></span>
<span id="cb4-86"><a href="#cb4-86"></a></span>
<span id="cb4-87"><a href="#cb4-87"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb4-88"><a href="#cb4-88"></a>app_startup <span class="op">(</span>GApplication <span class="op">*</span>application<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-89"><a href="#cb4-89"></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="cb4-90"><a href="#cb4-90"></a> GtkBuilder <span class="op">*</span>build<span class="op">;</span></span>
<span id="cb4-91"><a href="#cb4-91"></a></span>
<span id="cb4-92"><a href="#cb4-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="cb4-93"><a href="#cb4-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="cb4-94"><a href="#cb4-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="cb4-95"><a href="#cb4-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="cb4-96"><a href="#cb4-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="cb4-97"><a href="#cb4-97"></a> g_object_unref<span class="op">(</span>build<span class="op">);</span></span>
<span id="cb4-98"><a href="#cb4-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="cb4-99"><a href="#cb4-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="cb4-100"><a href="#cb4-100"></a></span>
<span id="cb4-101"><a href="#cb4-101"></a>GdkDisplay <span class="op">*</span>display<span class="op">;</span></span>
<span id="cb4-102"><a href="#cb4-102"></a></span>
<span id="cb4-103"><a href="#cb4-103"></a> display <span class="op">=</span> gtk_widget_get_display <span class="op">(</span>GTK_WIDGET <span class="op">(</span>win<span class="op">));</span></span>
<span id="cb4-104"><a href="#cb4-104"></a> GtkCssProvider <span class="op">*</span>provider <span class="op">=</span> gtk_css_provider_new <span class="op">();</span></span>
<span id="cb4-105"><a href="#cb4-105"></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="cb4-106"><a href="#cb4-106"></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="cb4-107"><a href="#cb4-107"></a><span class="op">}</span></span>
<span id="cb4-108"><a href="#cb4-108"></a></span>
<span id="cb4-109"><a href="#cb4-109"></a><span class="pp">#define APPLICATION_ID &quot;com.github.ToshioCP.color&quot;</span></span>
<span id="cb4-110"><a href="#cb4-110"></a></span>
<span id="cb4-111"><a href="#cb4-111"></a><span class="dt">int</span></span>
<span id="cb4-112"><a href="#cb4-112"></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-113"><a href="#cb4-113"></a> GtkApplication <span class="op">*</span>app<span class="op">;</span></span>
<span id="cb4-114"><a href="#cb4-114"></a> <span class="dt">int</span> stat<span class="op">;</span></span>
<span id="cb4-115"><a href="#cb4-115"></a></span>
<span id="cb4-116"><a href="#cb4-116"></a> app <span class="op">=</span> gtk_application_new <span class="op">(</span>APPLICATION_ID<span class="op">,</span> G_APPLICATION_FLAGS_NONE<span class="op">);</span></span>
<span id="cb4-117"><a href="#cb4-117"></a></span>
<span id="cb4-118"><a href="#cb4-118"></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-119"><a href="#cb4-119"></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-120"><a href="#cb4-120"></a></span>
<span id="cb4-121"><a href="#cb4-121"></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-122"><a href="#cb4-122"></a> g_object_unref <span class="op">(</span>app<span class="op">);</span></span>
<span id="cb4-123"><a href="#cb4-123"></a> <span class="cf">return</span> stat<span class="op">;</span></span>
<span id="cb4-124"><a href="#cb4-124"></a><span class="op">}</span></span></code></pre></div>
<div class="sourceCode" id="cb8"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb8-1"><a href="#cb8-1"></a><span class="pp">#include </span><span class="im">&lt;gtk/gtk.h&gt;</span></span>
<span id="cb8-2"><a href="#cb8-2"></a><span class="pp">#include </span><span class="im">&quot;../tfetextview/tfetextview.h&quot;</span></span>
<span id="cb8-3"><a href="#cb8-3"></a></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-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-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-7"><a href="#cb8-7"></a></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-9"><a href="#cb8-9"></a></span>
<span id="cb8-10"><a href="#cb8-10"></a><span class="dt">static</span> <span class="dt">void</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-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-13"><a href="#cb8-13"></a> GtkTextIter start_iter<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-15"><a href="#cb8-15"></a> <span class="dt">char</span> <span class="op">*</span>contents<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-17"><a href="#cb8-17"></a></span>
<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>
<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>
<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>
<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>
<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>
<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>
<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>109-124: The function <code>main</code> is almost same as before but
there are some differences. The application ID is
“com.github.ToshioCP.color”. <code>G_APPLICATION_FLAGS_NONE</code> is
specified so no open signal handler is necessary.</li>
<li>87-107: Startup handler.</li>
<li>92-97: Builds widgets. The pointers of the top window, TfeTextView
and GtkDrawingArea objects are stored to static variables
<code>win</code>, <code>tv</code> and <code>da</code> respectively. This
is because these objects are often used in handlers. They never be
rewritten so theyre thread safe.</li>
<li>98: connects “resize” signal and the handler.</li>
<li>99: sets the drawing function.</li>
<li>82-85: Activate handler, which just shows the widgets.</li>
<li>74-80: The drawing function. It just copies <code>surface</code> to
destination.</li>
<li>66-72: Resize handler. Re-creates the surface to fit its width and
height for the drawing area and paints by calling the function
<code>run</code>.</li>
<li>59-64: Close handler. It destroys <code>surface</code> if it exists.
Then it destroys the top-level window and quits the application.</li>
<li>49-57: Open and save handler. They just call the corresponding
functions of TfeTextView.</li>
<li>43-47: Run handler. It calls run function to paint the surface.
After that <code>gtk_widget_queue_draw</code> is called. This function
adds the widget (GtkDrawingArea) to the queue to be redrawn. It is
important to know that the window is redrawn whenever it is necessary.
For example, when another window is moved and uncovers part of the
widget, or when the window containing it is resized. But repainting
<code>surface</code> is not automatically notified to gtk. Therefore,
you need to call <code>gtk_widget_queue_draw</code> to redraw the
widget.</li>
<li>9-41: Run function paints the surface. First, it gets the contents
of GtkTextBuffer. Then it compares it to “red”, “green” and so on. If it
matches the color, then the surface is painted the color. If it matches
“light” or “dark”, then the color of the surface is lightened or
darkened respectively. Alpha channel is used.</li>
<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="cb5"><pre
class="sourceCode numberSource numberLines"><code class="sourceCode"><span id="cb5-1"><a href="#cb5-1"></a>project(&#39;color&#39;, &#39;c&#39;)</span>
<span id="cb5-2"><a href="#cb5-2"></a></span>
<span id="cb5-3"><a href="#cb5-3"></a>gtkdep = dependency(&#39;gtk4&#39;)</span>
<span id="cb5-4"><a href="#cb5-4"></a></span>
<span id="cb5-5"><a href="#cb5-5"></a>gnome=import(&#39;gnome&#39;)</span>
<span id="cb5-6"><a href="#cb5-6"></a>resources = gnome.compile_resources(&#39;resources&#39;,&#39;color.gresource.xml&#39;)</span>
<span id="cb5-7"><a href="#cb5-7"></a></span>
<span id="cb5-8"><a href="#cb5-8"></a>sourcefiles=files(&#39;colorapplication.c&#39;, &#39;../tfetextview/tfetextview.c&#39;)</span>
<span id="cb5-9"><a href="#cb5-9"></a></span>
<span id="cb5-10"><a href="#cb5-10"></a>executable(&#39;color&#39;, sourcefiles, resources, dependencies: gtkdep, export_dynamic: true)</span></code></pre></div>
<h2 id="compile-and-execute-it">Compile and execute it</h2>
<p>First you need to export some variables (refer to <a
href="sec2.html">Section 2</a>) if youve installed GTK 4 from the
source. If youve installed GTK 4 from the distribution packages, you
dont need to do this.</p>
<pre><code>$ . env.sh</code></pre>
<p>Then type the following to compile it.</p>
<div class="sourceCode" id="cb9"><pre
class="sourceCode numberSource numberLines"><code class="sourceCode"><span id="cb9-1"><a href="#cb9-1"></a>project(&#39;color&#39;, &#39;c&#39;)</span>
<span id="cb9-2"><a href="#cb9-2"></a></span>
<span id="cb9-3"><a href="#cb9-3"></a>gtkdep = dependency(&#39;gtk4&#39;)</span>
<span id="cb9-4"><a href="#cb9-4"></a></span>
<span id="cb9-5"><a href="#cb9-5"></a>gnome=import(&#39;gnome&#39;)</span>
<span id="cb9-6"><a href="#cb9-6"></a>resources = gnome.compile_resources(&#39;resources&#39;,&#39;color.gresource.xml&#39;)</span>
<span id="cb9-7"><a href="#cb9-7"></a></span>
<span id="cb9-8"><a href="#cb9-8"></a>sourcefiles=files(&#39;colorapplication.c&#39;, &#39;../tfetextview/tfetextview.c&#39;)</span>
<span id="cb9-9"><a href="#cb9-9"></a></span>
<span id="cb9-10"><a href="#cb9-10"></a>executable(&#39;color&#39;, sourcefiles, resources, dependencies: gtkdep, export_dynamic: true)</span></code></pre></div>
<h2 id="build-and-try">Build and try</h2>
<p>Type the following to compile the program.</p>
<pre><code>$ meson _build
$ ninja -C _build</code></pre>
<p>The application is made in <code>_build</code> directory. Type the
following to execute it.</p>
<pre><code>$ _build/color</code></pre>
<p>Type “red”, “green”, “blue”, “white”, black”, “light” or “dark” in
the TfeTextView. Then, click on <code>Run</code> button. Make sure the
color of GtkDrawingArea changes.</p>
the TfeTextView. No new line charactor is needed. Then, click on the
<code>Run</code> button. Make sure the color of GtkDrawingArea
changes.</p>
<p>In this program TfeTextView is used to change the color. You can use
buttons or menus instead of textview. Probably it is more appropriate.
Using textview is unnatural. It is a good practice to make such

File diff suppressed because it is too large Load diff

View file

@ -9,17 +9,17 @@ This is called custom drawing.
GtkDrawingArea provides a cairo drawing context so users can draw images by using cairo functions.
In this section, I will explain:
1. Cairo, but only briefly; and
1. Cairo, but only briefly
2. GtkDrawingArea, with a very simple example.
## Cairo
Cairo is a set of two dimensional graphical drawing functions (or graphics library).
There is a lot of documentation on [Cairo's website](https://www.cairographics.org/).
If you aren't familiar with Cairo, it is worth reading their [tutorial](https://www.cairographics.org/tutorial/).
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/).
The following is a gentle introduction to the Cairo library and how to use it.
Firstly, in order to use Cairo you need to know about surfaces, sources, masks, destinations, cairo context and transformations.
The following is an introduction to the Cairo library and how to use it.
First, you need to know about surfaces, sources, masks, destinations, cairo context and transformations.
- A surface represents an image.
It is like a canvas.
@ -50,6 +50,7 @@ This will be the destination.
6. Save the destination surface to a file if necessary.
Here's a simple example program that draws a small square and saves it as a png file.
The path of the file is `src/misc/cairo.c`.
~~~C
1 #include <cairo.h>
@ -103,7 +104,7 @@ Width and height are in pixels and given as integers.
- 14: Creates cairo 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.
The second to fourth argument 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. The values are between zero (0.0) and one (1.0), with
black being given by (0.0,0.0,0.0) and white by (1.0,1.0,1.0).
- 19: `cairo_paint` copies everywhere in the source to destination.
@ -120,13 +121,13 @@ The square is located at the center.
- 28: Destroys the context. At the same time the source is destroyed.
- 29: Destroys the surface.
To compile this, 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`
![rectangle.png](../image/rectangle.png)
See the [Cairo's website](https://www.cairographics.org/) for more details.
See the [Cairo's website](https://www.cairographics.org/) for further information.
## GtkDrawingArea
@ -181,12 +182,12 @@ The following is a very simple example.
The function `main` is almost same as before.
The two functions `app_activate` and `draw_function` are important in this example.
- 18: Creates a GtkDrawingArea instance; and
- 21: Sets a drawing function of the widget.
- 22: Creates a GtkDrawingArea instance.
- 25: Sets a drawing function of the widget.
GtkDrawingArea widget uses the 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.
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).
The drawing function has five parameters.
@ -201,15 +202,16 @@ The second parameter is a cairo context given by the widget.
The destination surface of the context is connected to the contents of the widget.
What you draw to this surface will appear in the widget on the screen.
The third and fourth parameters are the size of the destination surface.
Now, look at the program example again.
Now, look at the program again.
- 3-13: The drawing function.
- 3-17: The drawing function.
- 7-8: Sets the source to be white and paint the destination white.
- 9: Sets the line width to be 2.
- 10: Sets the source to be black.
- 11: Adds a rectangle to the mask.
- 12: Draws the rectangle with black color to the destination.
- 11-15: Adds a rectangle to the mask.
- 16: Draws the rectangle with black color to the destination.
The program is [src/misc/da1.c](../src/misc/da1.c).
Compile and run it, then a window with a black rectangle (square) appears.
Try resizing the window.
The square always appears at the center of the window because the drawing function is invoked each time the window is resized.

View file

@ -9,6 +9,7 @@ If you write a name of a color in TfeTextView and click on the `run` button, the
![color](../image/color.png)
The following colors are available.
(without new line charactor)
- white
- black
@ -30,8 +31,7 @@ In this section, we focus on how to bind the two objects.
## Color.ui and color.gresource.xml
First, We need to make the ui file of the widgets.
The image in the previous subsection gives us the structure of the widgets.
Title bar, four buttons in the tool bar and two widgets textview and drawing area.
Title bar, four buttons in the tool bar, textview and drawing area.
The ui file is as follows.
~~~xml
@ -117,16 +117,16 @@ The ui file is as follows.
80 </interface>
~~~
- 10-53: This part is the tool bar which has four buttons, `Run`, `Open`, `Save` and `Close`.
This is similar to the toolbar of tfe text editor in [Section 9](sec9.md).
- 10-53: The horizontal box `boxh1` makes a tool bar which has four buttons, `Run`, `Open`, `Save` and `Close`.
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 function.
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 executable function in `meson.build`.
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: Puts GtkScrolledWindow and GtkDrawingArea into GtkBox.
- 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.
@ -142,121 +142,209 @@ Just substitute "color" for "tfe".
6 </gresources>
~~~
## Tfetextview.h, tfetextview.c and color.h
## Drawing function and surface
First two files are the same as before.
Color.h just includes tfetextview.h.
The main point of this program is a drawing function.
~~~C
1 #include <gtk/gtk.h>
2
3 #include "../tfetextview/tfetextview.h"
1 static void
2 draw_func (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height, gpointer user_data) {
3 if (surface) {
4 cairo_set_source_surface (cr, surface, 0, 0);
5 cairo_paint (cr);
6 }
7 }
~~~
The `surface` variable in line 3 is a static variable.
~~~C
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
1 static void
2 resize_cb (GtkDrawingArea *drawing_area, int width, int height, gpointer user_data) {
3 if (surface)
4 cairo_surface_destroy (surface);
5 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
6 run ();
7 }
~~~
If the variable `surface` 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 `surface`.
The function `run` is called and the surface is colored.
The signal is emitted when:
- 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.
It deals with:
- Building widgets by GtkBuilder.
- Setting a drawing function of GtkDrawingArea.
And connecting a handler to "resize" signal on GtkDrawingArea.
- Implementing each call back functions.
- 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`.
~~~C
1 #include "color.h"
2
3 static GtkWidget *win;
4 static GtkWidget *tv;
5 static GtkWidget *da;
6
7 static cairo_surface_t *surface = NULL;
8
9 static void
10 run (void) {
11 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
12 GtkTextIter start_iter;
13 GtkTextIter end_iter;
14 char *contents;
15 cairo_t *cr;
16
17 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
18 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
19 if (surface) {
20 cr = cairo_create (surface);
21 if (g_strcmp0 ("red", contents) == 0)
22 cairo_set_source_rgb (cr, 1, 0, 0);
23 else if (g_strcmp0 ("green", contents) == 0)
24 cairo_set_source_rgb (cr, 0, 1, 0);
25 else if (g_strcmp0 ("blue", contents) == 0)
26 cairo_set_source_rgb (cr, 0, 0, 1);
27 else if (g_strcmp0 ("white", contents) == 0)
28 cairo_set_source_rgb (cr, 1, 1, 1);
29 else if (g_strcmp0 ("black", contents) == 0)
30 cairo_set_source_rgb (cr, 0, 0, 0);
31 else if (g_strcmp0 ("light", contents) == 0)
32 cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
33 else if (g_strcmp0 ("dark", contents) == 0)
34 cairo_set_source_rgba (cr, 0, 0, 0, 0.5);
35 else
36 cairo_set_source_surface (cr, surface, 0, 0);
37 cairo_paint (cr);
38 cairo_destroy (cr);
39 }
40 g_free (contents);
41 }
42
43 void
44 run_cb (GtkWidget *btnr) {
45 run ();
46 gtk_widget_queue_draw (GTK_WIDGET (da));
47 }
48
49 void
50 open_cb (GtkWidget *btno) {
51 tfe_text_view_open (TFE_TEXT_VIEW (tv), GTK_WINDOW (win));
52 }
53
54 void
55 save_cb (GtkWidget *btns) {
56 tfe_text_view_save (TFE_TEXT_VIEW (tv));
57 }
58
59 void
60 close_cb (GtkWidget *btnc) {
61 if (surface)
62 cairo_surface_destroy (surface);
63 gtk_window_destroy (GTK_WINDOW (win));
64 }
65
66 static void
67 resize_cb (GtkDrawingArea *drawing_area, int width, int height, gpointer user_data) {
68 if (surface)
69 cairo_surface_destroy (surface);
70 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
71 run ();
72 }
73
74 static void
75 draw_func (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height, gpointer user_data) {
76 if (surface) {
77 cairo_set_source_surface (cr, surface, 0, 0);
78 cairo_paint (cr);
1 #include <gtk/gtk.h>
2 #include "../tfetextview/tfetextview.h"
3
4 static GtkWidget *win;
5 static GtkWidget *tv;
6 static GtkWidget *da;
7
8 static cairo_surface_t *surface = NULL;
9
10 static void
11 run (void) {
12 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
13 GtkTextIter start_iter;
14 GtkTextIter end_iter;
15 char *contents;
16 cairo_t *cr;
17
18 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
19 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
20 if (surface) {
21 cr = cairo_create (surface);
22 if (g_strcmp0 ("red", contents) == 0)
23 cairo_set_source_rgb (cr, 1, 0, 0);
24 else if (g_strcmp0 ("green", contents) == 0)
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
82 static void
83 app_activate (GApplication *application) {
84 gtk_widget_show (win);
85 }
86
87 static void
88 app_startup (GApplication *application) {
89 GtkApplication *app = GTK_APPLICATION (application);
90 GtkBuilder *build;
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"));
@ -267,67 +355,58 @@ The following is `colorapplication.c`.
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 GdkDisplay *display;
102
103 display = gtk_widget_get_display (GTK_WIDGET (win));
104 GtkCssProvider *provider = gtk_css_provider_new ();
105 gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1);
106 gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
107 }
108
109 #define APPLICATION_ID "com.github.ToshioCP.color"
110
111 int
112 main (int argc, char **argv) {
113 GtkApplication *app;
114 int stat;
115
116 app = gtk_application_new (APPLICATION_ID, G_APPLICATION_FLAGS_NONE);
117
118 g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
119 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
120
121 stat =g_application_run (G_APPLICATION (app), argc, argv);
122 g_object_unref (app);
123 return stat;
124 }
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
~~~
- 109-124: The function `main` is almost same as before but there are some differences.
The application ID is "com.github.ToshioCP.color".
`G_APPLICATION_FLAGS_NONE` is specified so no open signal handler is necessary.
- 87-107: Startup handler.
- 92-97: Builds widgets.
The pointers of the top window, TfeTextView and GtkDrawingArea objects are stored to static variables `win`, `tv` and `da` respectively.
This is because these objects are often used in handlers.
They never be rewritten so they're thread safe.
- 98: connects "resize" signal and the handler.
- 99: sets the drawing function.
- 82-85: Activate handler, which just shows the widgets.
- 74-80: The drawing function.
It just copies `surface` to destination.
- 66-72: Resize handler.
Re-creates the surface to fit its width and height for the drawing area and paints by calling the function `run`.
- 59-64: Close handler.
It destroys `surface` if it exists.
Then it destroys the top-level window and quits the application.
- 49-57: Open and save handler.
They just call the corresponding functions of TfeTextView.
- 43-47: Run handler.
It calls run function to paint the surface.
After that `gtk_widget_queue_draw` is called.
This function adds the widget (GtkDrawingArea) to the queue to be redrawn.
It is important to know that the window is redrawn whenever it is necessary.
For example, when another window is moved and uncovers part of the widget, or when the window containing it is resized.
But repainting `surface` is not automatically notified to gtk.
Therefore, you need to call `gtk_widget_queue_draw` to redraw the widget.
- 9-41: Run function paints the surface.
First, it gets the contents of GtkTextBuffer.
Then it compares it to "red", "green" and so on.
If it matches the color, then the surface is painted the color.
If it matches "light" or "dark", then the color of the surface is lightened or darkened respectively.
Alpha channel is used.
- 4-8: Win, tv, da and surface are defined as static variables.
- 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
@ -347,14 +426,9 @@ An argument "export_dynamic: true" is added to executable function.
10 executable('color', sourcefiles, resources, dependencies: gtkdep, export_dynamic: true)
~~~
## Compile and execute it
## Build and try
First you need to export some variables (refer to [Section 2](sec2.md)) if you've installed GTK 4 from the source.
If you've installed GTK 4 from the distribution packages, you don't need to do this.
$ . env.sh
Then type the following to compile it.
Type the following to compile the program.
$ meson _build
$ ninja -C _build
@ -365,7 +439,8 @@ Type the following to execute it.
$ _build/color
Type "red", "green", "blue", "white", black", "light" or "dark" in the TfeTextView.
Then, click on `Run` button.
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.

View file

@ -1,37 +1,38 @@
Up: [Readme.md](../Readme.md), Prev: [Section 24](sec24.md), Next: [Section 26](sec26.md)
Up: [README.md](../README.md), Prev: [Section 24](sec24.md), Next: [Section 26](sec26.md)
# Tiny turtle graphics interpreter
A program `turtle` is an example with the combination of TfeTextView and GtkDrawingArea objects.
It is a very small interpreter but it provides a tool to draw fractal curves.
The following diagram is a Koch curve, which is a famous example of fractal curves.
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.
![Koch curve](../src/turtle/image/turtle_koch.png)
The following is a snow-crystal-shaped curve.
It is composed of six Koch curves.
![Snow](../image/turtle_snow.png)
This program uses flex and bison.
Flex is a lexical analyzer.
Bison is a parser generator.
These two programs are similar to lex and yacc which are proprietary software developed in Bell Laboratory.
However, flex and bison are open source software.
I will write about how to use those software, but they are not topics about gtk.
So, readers can skip that part of this sections.
I will write about how to use those software, but they are not topics about GTK 4.
So, readers can skip this section.
## How to use turtle
The documentation of turtle is [here](turtle_doc.md).
The turtle document is [here](turtle_doc.md).
I'll show you a simple example.
~~~
fc (1,0,0) # Foreground color is red, rgb = (1,0,0).
pd # Pen down.
rp (4) { # Repeat four times.
fd 100 # Go forward by 100 pixels.
tr 90 # Turn right by 90 degrees.
fd 100
tr 90
fd 100
tr 90
fd 100
tr 90
}
~~~
1. Compile and install `turtle` (See the documentation above).
@ -41,8 +42,8 @@ Then, run `turtle`.
The side of the square is 100 pixels long.
In the same way, you can draw other curves.
The documentation above shows some fractal curves such as tree, snow and square-koch.
The source code in turtle language is located at [src/turtle/example](../src/turtle/example) directory.
The turtle document includes some fractal curves such as tree, snow and square-koch.
The source codes are located at [src/turtle/example](../src/turtle/example) directory.
You can read these files into `turtle` editor by clicking on the `Open` button.
## Combination of TfeTextView and GtkDrawingArea objects
@ -55,10 +56,9 @@ It is similar to `color` program in the previous section.
3. The parser reads the program and generates tree-structured data.
4. The interpriter reads the data and executes it step by step.
And it draws shapes on a surface.
The surface is different from the surface of the GtkDrawingArea widget.
The surface isn't the one in the GtkDrawingArea widget.
5. The widget is added to the queue.
It will be redrawn with the drawing function.
The function just copies the surface, which is drawn by the interpreter, into the surface of the GtkDrawingArea.
It will be redrawn with the drawing function, which just copies the surface into the one in the GtkDrawingArea.
![Parser, interpreter and drawing function](../image/turtle.png)
@ -74,10 +74,10 @@ So the handler of "clicked" signal of the `Run` button prevents from reentering.
5 GtkTextIter end_iter;
6 char *contents;
7 int stat;
8 static gboolean busy = FALSE;
8 static gboolean busy = FALSE; /* initialized only once */
9
10 /* yyparse() and run() are NOT thread safe. */
11 /* The variable busy avoids reentry. */
11 /* The variable busy avoids reentrance. */
12 if (busy)
13 return;
14 busy = TRUE;
@ -98,16 +98,18 @@ So the handler of "clicked" signal of the `Run` button prevents from reentering.
29
30 static void
31 resize_cb (GtkDrawingArea *drawing_area, int width, int height, gpointer user_data) {
32 if (surface)
33 cairo_surface_destroy (surface);
34 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
35 }
32
33 if (surface)
34 cairo_surface_destroy (surface);
35 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
36 run_cb (NULL); // NULL is a fake (run button).
37 }
~~~
- 8-13: The static value `busy` holds a status of the interpreter.
If it is `TRUE`, the interpreter is running and it is not possible to call the interpreter again because it's not a re-entrant program.
If it is `FALSE`, it is safe to call the interpreter.
- 14: Now it is about to call the interpreter so it changes `busy` to TRUE.
- 14: Changes `busy` to TRUE to avoid reentrance.
- 15-16: Gets the contents of `tb`.
- 17: The variable `surface` is a static variable.
It points to a `cairo_surface_t` instance.
@ -116,17 +118,17 @@ Therefore, `surface` isn't NULL usually.
But if it is NULL, the interpreter won't be called.
- 18: Initializes lexical analyzer.
- 19: Calls parser.
Parser analyzes the program codes syntactically and generate a tree structured data.
Parser analyzes the program codes syntactically and generates a tree structured data.
- 20-22: If the parser successfully parsed, it calls `run` (runtime routine).
- 23: finalizes the lexical analyzer.
- 25: frees `contents`.
- 26: Adds the drawing area widget to the queue to draw.
- 27: The interpreter program has finished so `busy` is now changed to FALSE.
- 29-34: A handler of "resized" signal.
If `surface` isn't NULL, it destroys the old surface.
Then it creates a new surface.
- 30-37: A "resized" signal handler.
If the `surface` isn't NULL, it is destroyed.
A new surface is created.
Its size is the same as the surface of the GtkDrawingArea instance.
Run\_cb is called to redraw the shape on the drawing area.
Other part of `turtleapplication.c` is almost same as the codes of `colorapplication.c` in the previous section.
The codes of `turtleapplication.c` is in the [turtle directory](../src/turtle).
@ -215,7 +217,7 @@ The source files are:
- flex source file => `turtle.lex`
- bison source file => `turtle.y`
- C header file => `turtle.h`, `turtle_lex.h`
- C header file => `turtle_lex.h`
- C source file => `turtleapplication.c`
- other files => `turtle.ui`, `turtle.gresources.xml` and `meson.build`
@ -225,12 +227,13 @@ The compilation process is a bit complicated.
It also generates `resources.h`.
2. bison compiles `turtle.y` to `turtle_parser.c` and generates `turtle_parser.h`
3. flex compiles `turtle.lex` to `turtle_lex.c`.
4. gcc compiles `application.c`, `resources.c`, `turtle_parser.c` and `turtle_lex.c` with `turtle.h`, `turtle_lex.h`, `resources.h` and `turtle_parser.h`.
4. gcc compiles `application.c`, `resources.c`, `turtle_parser.c` and `turtle_lex.c` with `turtle_lex.h`, `resources.h` and `turtle_parser.h`.
It generates an executable file `turtle`.
![compile process](../image/turtle_compile_process.png)
Meson controls the process and the instruction is described in `meson.build`.
Meson controls the process.
The instruction is described in `meson.build`.
~~~meson
1 project('turtle', 'c')
@ -261,15 +264,13 @@ This program uses trigonometric functions.
They are defined in the math library, but the library is optional.
So, it is necessary to include it by `#include <math.h>` and also link the library with the linker.
- 6: Gets gtk4 library.
- 8: Gets gnome module.
Module is a system provided by meson.
See [Meson build system website, GNUME module](https://mesonbuild.com/Gnome-module.html#gnome-module) for further information.
- 8: Gets gnome module.See [Meson build system website -- GNUME module](https://mesonbuild.com/Gnome-module.html#gnome-module) for further information.
- 9: Compiles ui file to C source file according to the XML file `turtle.gresource.xml`.
- 11: Gets flex.
- 12: Gets bison.
- 13: Compiles `turtle.y` to `turtle_parser.c` and `turtle_parser.h` by bison.
The function `custom_target` creates a custom top level target.
See [Meson build system website, custom target](https://mesonbuild.com/Reference-manual.html#custom_target) for further information.
See [Meson build system website -- custom target](https://mesonbuild.com/Reference-manual.html#custom_target) for further information.
- 14: Compiles `turtle.lex` to `turtle_lex.c` by flex.
- 16: Specifies C source files.
- 18: Compiles C source files including generated files by glib-compile-resources, bison and flex.
@ -309,79 +310,81 @@ Turtle.lex isn't a big program.
1 %top{
2 #include <string.h>
3 #include <stdlib.h>
4 #include "turtle.h"
5
6 static int nline = 1;
7 static int ncolumn = 1;
8 static void get_location (char *text);
9
10 /* Dinamically allocated memories are added to the single list. They will be freed in the finalize function. */
11 extern GSList *list;
12 }
13
14 %option noyywrap
15
16 REAL_NUMBER (0|[1-9][0-9]*)(\.[0-9]+)?
17 IDENTIFIER [a-zA-Z][a-zA-Z0-9]*
18 %%
19 /* rules */
20 #.* ; /* comment. Be careful. Dot symbol (.) matches any character but new line. */
21 [ ] ncolumn++;
22 \t ncolumn += 8; /* assume that tab is 8 spaces. */
23 \n nline++; ncolumn = 1;
24 /* reserved keywords */
25 pu get_location (yytext); return PU; /* pen up */
26 pd get_location (yytext); return PD; /* pen down */
27 pw get_location (yytext); return PW; /* pen width = line width */
28 fd get_location (yytext); return FD; /* forward */
29 tr get_location (yytext); return TR; /* turn right */
30 bc get_location (yytext); return BC; /* background color */
31 fc get_location (yytext); return FC; /* foreground color */
32 dp get_location (yytext); return DP; /* define procedure */
33 if get_location (yytext); return IF; /* if statement */
34 rt get_location (yytext); return RT; /* return statement */
35 rs get_location (yytext); return RS; /* reset the status */
36 /* constant */
37 {REAL_NUMBER} get_location (yytext); yylval.NUM = atof (yytext); return NUM;
38 /* identifier */
39 {IDENTIFIER} { get_location (yytext); yylval.ID = g_strdup(yytext);
40 list = g_slist_prepend (list, yylval.ID);
41 return ID;
42 }
43 "=" get_location (yytext); return '=';
44 ">" get_location (yytext); return '>';
45 "<" get_location (yytext); return '<';
46 "+" get_location (yytext); return '+';
47 "-" get_location (yytext); return '-';
48 "*" get_location (yytext); return '*';
49 "/" get_location (yytext); return '/';
50 "(" get_location (yytext); return '(';
51 ")" get_location (yytext); return ')';
52 "{" get_location (yytext); return '{';
53 "}" get_location (yytext); return '}';
54 "," get_location (yytext); return ',';
55 . ncolumn++; return YYUNDEF;
56 %%
57
58 static void
59 get_location (char *text) {
60 yylloc.first_line = yylloc.last_line = nline;
61 yylloc.first_column = ncolumn;
62 yylloc.last_column = (ncolumn += strlen(text)) - 1;
63 }
64
65 static YY_BUFFER_STATE state;
66
67 void
68 init_flex (const char *text) {
69 state = yy_scan_string (text);
70 }
71
72 void
73 finalize_flex (void) {
74 yy_delete_buffer (state);
75 }
76
4 #include <glib.h>
5 #include "turtle_parser.h"
6
7 static int nline = 1;
8 static int ncolumn = 1;
9 static void get_location (char *text);
10
11 /* Dinamically allocated memories are added to the single list. They will be freed in the finalize function. */
12 extern GSList *list;
13 }
14
15 %option noyywrap
16
17 REAL_NUMBER (0|[1-9][0-9]*)(\.[0-9]+)?
18 IDENTIFIER [a-zA-Z][a-zA-Z0-9]*
19 %%
20 /* rules */
21 #.* ; /* comment. Be careful. Dot symbol (.) matches any character but new line. */
22 [ ] ncolumn++;
23 \t ncolumn += 8; /* assume that tab is 8 spaces. */
24 \n nline++; ncolumn = 1;
25 /* reserved keywords */
26 pu get_location (yytext); return PU; /* pen up */
27 pd get_location (yytext); return PD; /* pen down */
28 pw get_location (yytext); return PW; /* pen width = line width */
29 fd get_location (yytext); return FD; /* forward */
30 tr get_location (yytext); return TR; /* turn right */
31 tl get_location (yytext); return TL; /* turn left ver 0.5 */
32 bc get_location (yytext); return BC; /* background color */
33 fc get_location (yytext); return FC; /* foreground color */
34 dp get_location (yytext); return DP; /* define procedure */
35 if get_location (yytext); return IF; /* if statement */
36 rt get_location (yytext); return RT; /* return statement */
37 rs get_location (yytext); return RS; /* reset the status */
38 rp get_location (yytext); return RP; /* repeat ver 0.5 */
39 /* constant */
40 {REAL_NUMBER} get_location (yytext); yylval.NUM = atof (yytext); return NUM;
41 /* identifier */
42 {IDENTIFIER} { get_location (yytext); yylval.ID = g_strdup(yytext);
43 list = g_slist_prepend (list, yylval.ID);
44 return ID;
45 }
46 "=" get_location (yytext); return '=';
47 ">" get_location (yytext); return '>';
48 "<" get_location (yytext); return '<';
49 "+" get_location (yytext); return '+';
50 "-" get_location (yytext); return '-';
51 "*" get_location (yytext); return '*';
52 "/" get_location (yytext); return '/';
53 "(" get_location (yytext); return '(';
54 ")" get_location (yytext); return ')';
55 "{" get_location (yytext); return '{';
56 "}" get_location (yytext); return '}';
57 "," get_location (yytext); return ',';
58 . ncolumn++; return YYUNDEF;
59 %%
60
61 static void
62 get_location (char *text) {
63 yylloc.first_line = yylloc.last_line = nline;
64 yylloc.first_column = ncolumn;
65 yylloc.last_column = (ncolumn += strlen(text)) - 1;
66 }
67
68 static YY_BUFFER_STATE state;
69
70 void
71 init_flex (const char *text) {
72 state = yy_scan_string (text);
73 }
74
75 void
76 finalize_flex (void) {
77 yy_delete_buffer (state);
78 }
~~~
The file consists of three sections which are separated by "%%" (line 18 and 56).
@ -391,17 +394,17 @@ They are definitions, rules and user code sections.
- 1-12: Lines between "%top{" and "}" are C source codes.
They will be copied to the top of the generated C source file.
- 2-3: The function `strlen`, in line 62, is defined in `string.h`
The function `atof`, in line 37, is defined in `stdlib.h`.
- 6-8: The current input position is pointed by `nline` and `ncolumn`.
The function `get_location` (line 58-63) sets `yylloc`to point the start and end point of `yytext` in the buffer.
- 2-3: The function `strlen`, in line 65, is defined in `string.h`
The function `atof`, in line 40, is defined in `stdlib.h`.
- 7-9: The current input position is pointed by `nline` and `ncolumn`.
The function `get_location` (line 61-66) sets `yylloc`to point the start and end point of `yytext` in the buffer.
This function is declared here so that it can be called before the function is defined.
- 11: GSlist is used to keep allocated memories.
- 14: This option (`%option noyywrap`) must be specified when you have only single source file to the scanner. Refer to "9 The Generated Scanner" in the flex documentation in your distribution for further information.
- 12: GSlist is used to keep allocated memories.
- 15: This option (`%option noyywrap`) must be specified when you have only single source file to the scanner. Refer to "9 The Generated Scanner" in the flex documentation in your distribution for further information.
(The documentation is not on the internet.)
- 16-17: `REAL_NUMBER` and `IDENTIFIER` are names.
- 17-18: `REAL_NUMBER` and `IDENTIFIER` are names.
A name begins with a letter or an underscore followed by zero or more letters, digits, underscores (`_`) or dashes (`-`).
They are followed by regular expressions which are their definition.
They are followed by regular expressions which are their definitions.
They will be used in rules section and will expand to the definition.
You can leave out such definitions here and use regular expressions in rules section directly.
@ -413,64 +416,66 @@ The patterns are regular expressions or names surrounded by braces.
The names must be defined in the definitions section.
The definition of the regular expression is written in the flex documentation.
For example, line 37 is a rule.
For example, line 40 is a rule.
- `{REAL_NUMBER}` is a pattern
- `get_location (yytext); yylval.NUM = atof (yytext); return NUM;` is an action.
`{REAL_NUMBER}` is defined in the 16th line, so it expands to `(0|[1-9][0-9]*)(\.[0-9]+)?`.
`{REAL_NUMBER}` is defined in the line 17, so it expands to `(0|[1-9][0-9]*)(\.[0-9]+)?`.
This regular expression matches numbers like `0`, `12` and `1.5`.
If the input is a number, it matches the pattern in line 37.
If an input is a number, it matches the pattern in line 40.
Then the matched text is assigned to `yytext` and corresponding action is executed.
A function `get_location` changes the location variables.
A function `get_location` changes the location variables to the position at the text.
It assigns `atof (yytext)`, which is double sized number converted from `yytext`, to `yylval.NUM` and return `NUM`.
`NUM` is an integer defined by `turtle.y`.
`NUM` is a token kind and it represents integer.
It is defined in `turtle.y`.
The scanner generated by flex and C compiler has `yylex` function.
The scanner generated by flex has `yylex` function.
If `yylex` is called and the input is "123.4", then it works as follows.
1. A string "123.4" matches `{REAL_NUMBER}`.
2. Update the location variable `ncolumn` and `yylloc`with `get_location`.
3. `atof` converts the string "123.4" to double type number 123.4.
3. The function `atof` converts the string "123.4" to double type number 123.4.
4. It is assigned to `yylval.NUM`.
5. `yylex` returns `NUM` to the caller.
Then the caller knows the input is `NUM` (number), and its value is 123.4.
Then the caller knows the input is a number (`NUM`), and its value is 123.4.
- 19-55: Rules section.
- 20: The symbol `.` (dot) matches any character except newline.
- 20-58: Rules section.
- 21: The symbol `.` (dot) matches any character except newline.
Therefore, a comment begins `#` followed by any characters except newline.
No action happens.
- 21: White space just increases a variable `ncolumn` by one.
- 22: Tab is assumed to be equal to eight spaces.
- 23: New line increases a variable `nline` by one and resets `ncolumn`.
- 25-35: Keywords just updates the location variables `ncolumn` and `yylloc`, and return the codes of the keywords.
- 37: Real number constant.
- 38: `IDENTIFIER` is defined in line 17.
- 22: White space just increases the variable `ncolumn` by one.
- 23: Tab is assumed to be equal to eight spaces.
- 24: New line increases a variable `nline` by one and resets `ncolumn`.
- 26-38: Keywords just updates the location variables `ncolumn` and `yylloc`, and return the token kinds of the keywords.
- 40: Real number constant.
- 42: `IDENTIFIER` is defined in line 18.
The location variables are updated and the name of the identifier is assigned to `yylval.ID`.
The memory of the name is allocated by the function `g_strdup`.
The memory is registered to the list (GSlist type list).
The memory will be freed after the runtime routine finishes.
Returns `ID`.
- 43-54: Symbols just update the location variable and return the codes.
The code is the same as the symbol itself.
- 55: If the input doesn't match above patterns, then it is error.
Returns `YYUNDEF`.
A token kind `ID` is returned.
- 46-56: Symbols just update the location variable and return the token kinds.
The token kind is the same as the symbol itself.
- 58: If the input doesn't match the patterns, then it is an error.
A special token kind `YYUNDEF` is returned.
### User code section
This section is just copied to C source file.
- 58-63: A function `get_location`.
- 61-66: A function `get_location`.
The location of the input is recorded to `nline` and `ncolumn`.
A variable `yylloc` is referred by the parser.
It is a C structure and has four members, `first_line`, `first_column`, `last_line` and `last_column`.
They point the start and end of the current input text.
- 65: `YY_BUFFER_STATE` is a pointer points the input buffer.
- 67-70: `init_flex` is called by `run_cb` signal handler, which is called when `Run` button is clicked on.
`run_cb` calls `init_flex` with one argument which is the copy of the content of GtkTextBuffer.
`yy_scan_string` sets the input buffer to read from the text.
- 72-75: `finalize_flex` is called after runtime routine finishes.
- 68: `YY_BUFFER_STATE` is a pointer points the input buffer.
- 70-73: A function `init_flex` is called by `run_cb` which is a "clicked" signal handler on the `Run` button.
It has one string type parameter.
The caller assigns it with the content of the GtkTextBuffer instance.
A function `yy_scan_string` sets the input buffer for the scanner.
- 75-78: A function `finalize_flex` is called after runtime routine finishes.
It deletes the input buffer.
## Turtle.y
@ -480,8 +485,8 @@ So I will explain the key points and leave out other less important parts.
### What does bison do?
Bison creates C source file from bison source file.
Bison source file is a text file.
Bison creates C source file of a parser from a bison source file.
The bison source file is a text file.
A parser analyzes a program source code according to its grammar.
Suppose here is a turtle source file.
@ -522,7 +527,7 @@ So, the parser gets items in the following table whenever it calls `yylex`.
Bison source code specifies the grammar rules of turtle language.
For example, `fc (1,0,0)` is called primary procedure.
A procedure is like a void type function in C source code.
A procedure is like a void type C function.
It doesn't return any values.
Programmers can define their own procedures.
On the other hand, `fc` is a built-in procedure.
@ -540,7 +545,8 @@ This means:
- expression is ID or NUM.
The description above is called BNF (Backus-Naur form).
More precisely, it is similar to BNF.
Precisely speaking, it is not exactly the same as BNF.
But the difference is small.
The first line is:
@ -550,7 +556,7 @@ FC '(' NUM ',' NUM ',' NUM ')';
The parser analyzes the turtle source code and if the input matches the definition above, the parser recognizes it as a primary procedure.
The grammar of turtle is described in the [document](turtle_doc.md).
The grammar of turtle is described in the [Turtle manual](https://toshiocp.github.io/Gtk4-tutorial/turtle_doc.html).
The following is an extract from the document.
~~~
@ -570,12 +576,14 @@ primary_procedure:
| PW expression
| FD expression
| TR expression
| TL expression
| BC '(' expression ',' expression ',' expression ')'
| FC '(' expression ',' expression ',' expression ')'
| ID '=' expression
| IF '(' expression ')' '{' primary_procedure_list '}'
| RT
| RS
| RP '(' expression ')' '{' primary_procedure_list '}'
| ID '(' ')'
| ID '(' argument_list ')'
;
@ -622,12 +630,12 @@ The grammar rule defines `program` first.
The definition is recursive.
- `statement` is program.
- `statement statement` is `program statemet`.
- `statement statement` is `program statement`.
Therefore, it is program.
- `statement statement statement` is `program statemet`.
- `statement statement statement` is `program statement`.
Therefore, it is program.
You can find that a list of statements is program like this.
You can find that a sequence of statements is program like this.
`program` and `statement` aren't tokens.
They don't appear in the input.
@ -723,7 +731,17 @@ The following is an extract from `turtle.y`.
#include <stdarg.h>
#include <setjmp.h>
#include <math.h>
#include "turtle.h"
#include <glib.h>
#include <cairo.h>
#include "turtle_parser.h"
/* The following line defines 'debug' so that debug information is printed out during the run time. */
/* However it makes the program slow. */
/* If you want to debug on, uncomment the line. */
/* #define debug 1 */
extern cairo_surface_t *surface;
/* error reporting */
static void yyerror (char const *s) { /* for syntax error */
@ -768,7 +786,7 @@ The header file is read by the scanner C source file and other files.
}
~~~
- `yylex` is shared by parser implementation file and scanner file.
- `yylex` is shared by the parser implementation file and scanner file.
- `yyparse` and `run` is called by `run_cb` in `turtleapplication.c`.
- `node_t` is the type of the semantic value of nterms.
The header file defines `YYSTYPE`, which is the semantic value type, with all the token and nterm value types.
@ -826,12 +844,14 @@ It also specifies some directives.
%token PW
%token FD
%token TR
%token TL
%token BC
%token FC
%token DP
%token IF
%token RT
%token RS
%token RP
/* constant */
%token <double> NUM
/* identirier */
@ -950,8 +970,9 @@ Be careful.
The operator `=` above is an assignment.
Assignment is not expression in turtle language.
It is primary_procedure.
But if `=` appears in an expression, it is a logical operater, not an assignment.
But if `=` appears in an expression, it is a logical operator, not an assignment.
The logical equal '`=`' usually used in the conditional expression, for example, in `if` statement.
(Turtle language uses '=' instead of '==' in C language).
### Grammar rules
@ -984,22 +1005,22 @@ expression:
;
~~~
- `program` is `statement`.
- The first two lines tell that `program` is `statement`.
- Whenever `statement` is reduced to `program`, an action `node_top=$$=$1;` is executed.
- `node_top` is a static variable.
It points the top node of the tree.
- `$$` is a semantic value of the result, which is `program` in the second line of the example above.
The semantic value of `program` is a pointer to `node_t` type structure.
It was defined in the declaration section.
- `$1` is a semantic value of the first component, which is `statement`.
The semantic value of `statement` is also a pointer to `node_t`.
- `statement` is `primary_procedure`.
- A symbol `$$` is a semantic value of the result.
For example, `$$` in line 2 is the semantic value of `program`.
It is a pointer to a `node_t` type structure.
- `$1` is a semantic value of the first component.
For example, `$1` in line 2 is the semantic value of `statement`.
It is also a pointer to `node_t`.
- The next rule is that `statement` is `primary_procedure`.
There's no action specified.
Then, the default action is executed.
It is ` $$ = $1`.
- `primary_procedure` is `FD` followed by expression.
Then, the default action `$$ = $1` is executed.
- The next rule is that `primary_procedure` is `FD` followed by expression.
The action calls `tree1` and assigns its return value to `$$`.
`tree1` makes a tree node.
The function `tree1` makes a tree node.
The tree node has type and union of three pointers to children nodes, string or double.
~~~
node --+-- type
@ -1009,7 +1030,7 @@ node --+-- type
+---double value
~~~
- `tree1` assigns the four arguments to type, child1, child2 and child3 members.
- `expression` is `NUM`.
- The last rule is that `expression` is `NUM`.
- `tree2` makes a tree node. The paremeters of `tree2` are a type and a semantic value.
Suppose the parser reads the following program.
@ -1062,6 +1083,7 @@ primary_procedure:
| PW expression { $$ = tree1 (N_PW, $2, NULL, NULL); }
| FD expression { $$ = tree1 (N_FD, $2, NULL, NULL); }
| TR expression { $$ = tree1 (N_TR, $2, NULL, NULL); }
| TL expression { $$ = tree1 (N_TL, $2, NULL, NULL); } /* ver 0.5 */
| BC '(' expression ',' expression ',' expression ')' { $$ = tree1 (N_BC, $3, $5, $7); }
| FC '(' expression ',' expression ',' expression ')' { $$ = tree1 (N_FC, $3, $5, $7); }
/* assignment */
@ -1070,6 +1092,7 @@ primary_procedure:
| IF '(' expression ')' '{' primary_procedure_list '}' { $$ = tree1 (N_IF, $3, $6, NULL); }
| RT { $$ = tree1 (N_RT, NULL, NULL, NULL); }
| RS { $$ = tree1 (N_RS, NULL, NULL, NULL); }
| RP '(' expression ')' '{' primary_procedure_list '}' { $$ = tree1 (N_RP, $3, $6, NULL); }
/* user defined procedure call */
| ID '(' ')' { $$ = tree1 (N_procedure_call, tree3 (N_ID, $1), NULL, NULL); }
| ID '(' argument_list ')' { $$ = tree1 (N_procedure_call, tree3 (N_ID, $1), $3, NULL); }
@ -1219,7 +1242,7 @@ init_table (void) {
~~~
`init_table` initializes the table.
This must be called before any registrations.
This must be called before registrations.
There are five functions to access the table,
@ -1347,8 +1370,8 @@ The runtime routine stores the name `drawline` and the node of the procedure to
- The second line calls the procedure.
First, it looks for the procedure in the symbol table and gets its node.
Then it searches the node for the parameters and gets `angle` and `distance`.
- It pushes ("distance", 100.0) to the stack.
- It pushes ("angle", 90.0) to the stack.
- It pushes ("distance", 100.0) to the stack.
- It pushes (NULL, 2.0) to the stack.
The number 2.0 is the number of parameters (or arguments).
It is used when the procedure returns.
@ -1356,8 +1379,11 @@ It is used when the procedure returns.
The following diagram shows the structure of the stack.
First, `procedure 1` is called.
The procedure has two parameters.
In the `procedure 1`, another procedure `procedure 2`, which has one parameter, is called.
And in the `procedure 2`, `procedure 3`, which has three parameters, is called.
In the `procedure 1`, another procedure `procedure 2` is called.
It has one parameter.
In the `procedure 2`, another procedure `procedure 3` is called.
It has three parameters.
These three procedures are nested.
Programs push data to a stack from a low address memory to a high address memory.
In the following diagram, the lowest address is at the top and the highest address is at the bottom.
@ -1948,6 +1974,6 @@ However, the following information is very useful (but old).
- Source code of a language, for example, ruby.
Lately, lots of source codes are in the internet.
Maybe reading source codes are the most useful for programmers.
Maybe reading source codes is the most useful for programmers.
Up: [Readme.md](../Readme.md), Prev: [Section 24](sec24.md), Next: [Section 26](sec26.md)
Up: [README.md](../README.md), Prev: [Section 24](sec24.md), Next: [Section 26](sec26.md)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

BIN
image/turtle_snow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

View file

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

View file

@ -1,4 +1,5 @@
#include "color.h"
#include <gtk/gtk.h>
#include "../tfetextview/tfetextview.h"
static GtkWidget *win;
static GtkWidget *tv;
@ -58,8 +59,6 @@ save_cb (GtkWidget *btns) {
void
close_cb (GtkWidget *btnc) {
if (surface)
cairo_surface_destroy (surface);
gtk_window_destroy (GTK_WINDOW (win));
}
@ -81,13 +80,14 @@ draw_func (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height, gpo
static void
app_activate (GApplication *application) {
gtk_widget_show (win);
gtk_window_present (GTK_WINDOW (win));
}
static void
app_startup (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
GtkBuilder *build;
GdkDisplay *display;
build = gtk_builder_new_from_resource ("/com/github/ToshioCP/color/color.ui");
win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
@ -98,14 +98,18 @@ app_startup (GApplication *application) {
g_signal_connect (GTK_DRAWING_AREA (da), "resize", G_CALLBACK (resize_cb), NULL);
gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (da), draw_func, NULL, NULL);
GdkDisplay *display;
display = gtk_widget_get_display (GTK_WIDGET (win));
display = gdk_display_get_default ();
GtkCssProvider *provider = gtk_css_provider_new ();
gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1);
gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
}
static void
app_shutdown (GApplication *application) {
if (surface)
cairo_surface_destroy (surface);
}
#define APPLICATION_ID "com.github.ToshioCP.color"
int
@ -113,9 +117,10 @@ main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new (APPLICATION_ID, G_APPLICATION_FLAGS_NONE);
app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
g_signal_connect (app, "shutdown", G_CALLBACK (app_shutdown), NULL);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);

View file

@ -7,17 +7,17 @@ This is called custom drawing.
GtkDrawingArea provides a cairo drawing context so users can draw images by using cairo functions.
In this section, I will explain:
1. Cairo, but only briefly; and
1. Cairo, but only briefly
2. GtkDrawingArea, with a very simple example.
## Cairo
Cairo is a set of two dimensional graphical drawing functions (or graphics library).
There is a lot of documentation on [Cairo's website](https://www.cairographics.org/).
If you aren't familiar with Cairo, it is worth reading their [tutorial](https://www.cairographics.org/tutorial/).
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/).
The following is a gentle introduction to the Cairo library and how to use it.
Firstly, in order to use Cairo you need to know about surfaces, sources, masks, destinations, cairo context and transformations.
The following is an introduction to the Cairo library and how to use it.
First, you need to know about surfaces, sources, masks, destinations, cairo context and transformations.
- A surface represents an image.
It is like a canvas.
@ -48,6 +48,7 @@ This will be the destination.
6. Save the destination surface to a file if necessary.
Here's a simple example program that draws a small square and saves it as a png file.
The path of the file is `src/misc/cairo.c`.
@@@include
misc/cairo.c
@ -66,7 +67,7 @@ Width and height are in pixels and given as integers.
- 14: Creates cairo 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.
The second to fourth argument 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. The values are between zero (0.0) and one (1.0), with
black being given by (0.0,0.0,0.0) and white by (1.0,1.0,1.0).
- 19: `cairo_paint` copies everywhere in the source to destination.
@ -83,13 +84,13 @@ The square is located at the center.
- 28: Destroys the context. At the same time the source is destroyed.
- 29: Destroys the surface.
To compile this, 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`
![rectangle.png](../image/rectangle.png)
See the [Cairo's website](https://www.cairographics.org/) for more details.
See the [Cairo's website](https://www.cairographics.org/) for further information.
## GtkDrawingArea
@ -102,12 +103,12 @@ misc/da1.c
The function `main` is almost same as before.
The two functions `app_activate` and `draw_function` are important in this example.
- 18: Creates a GtkDrawingArea instance; and
- 21: Sets a drawing function of the widget.
- 22: Creates a GtkDrawingArea instance.
- 25: Sets a drawing function of the widget.
GtkDrawingArea widget uses the 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.
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).
The drawing function has five parameters.
@ -122,15 +123,16 @@ The second parameter is a cairo context given by the widget.
The destination surface of the context is connected to the contents of the widget.
What you draw to this surface will appear in the widget on the screen.
The third and fourth parameters are the size of the destination surface.
Now, look at the program example again.
Now, look at the program again.
- 3-13: The drawing function.
- 3-17: The drawing function.
- 7-8: Sets the source to be white and paint the destination white.
- 9: Sets the line width to be 2.
- 10: Sets the source to be black.
- 11: Adds a rectangle to the mask.
- 12: Draws the rectangle with black color to the destination.
- 11-15: Adds a rectangle to the mask.
- 16: Draws the rectangle with black color to the destination.
The program is [src/misc/da1.c](misc/da1.c).
Compile and run it, then a window with a black rectangle (square) appears.
Try resizing the window.
The square always appears at the center of the window because the drawing function is invoked each time the window is resized.

View file

@ -7,6 +7,7 @@ If you write a name of a color in TfeTextView and click on the `run` button, the
![color](../image/color.png){width=7.0cm height=5.13cm}
The following colors are available.
(without new line charactor)
- white
- black
@ -28,24 +29,23 @@ In this section, we focus on how to bind the two objects.
## Color.ui and color.gresource.xml
First, We need to make the ui file of the widgets.
The image in the previous subsection gives us the structure of the widgets.
Title bar, four buttons in the tool bar and two widgets textview and drawing area.
Title bar, four buttons in the tool bar, textview and drawing area.
The ui file is as follows.
@@@include
color/color.ui
@@@
- 10-53: This part is the tool bar which has four buttons, `Run`, `Open`, `Save` and `Close`.
This is similar to the toolbar of tfe text editor in [Section 9](sec9.src.md).
- 10-53: The horizontal box `boxh1` makes a tool bar which has four buttons, `Run`, `Open`, `Save` and `Close`.
This is similar to the `tfe` text editor in [Section 9](sec9.src.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 function.
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 executable function in `meson.build`.
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: Puts GtkScrolledWindow and GtkDrawingArea into GtkBox.
- 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.
@ -56,24 +56,70 @@ Just substitute "color" for "tfe".
color/color.gresource.xml
@@@
## Tfetextview.h, tfetextview.c and color.h
## Drawing function and surface
First two files are the same as before.
Color.h just includes tfetextview.h.
The main point of this program is a drawing function.
@@@include
color/color.h
color/colorapplication.c draw_func
@@@
The `surface` variable in line 3 is a static variable.
~~~C
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.
@@@include
color/colorapplication.c run
@@@
- 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.
@@@include
color/colorapplication.c resize_cb
@@@
If the variable `surface` 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 `surface`.
The function `run` is called and the surface is colored.
The signal is emitted when:
- 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.
It deals with:
- Building widgets by GtkBuilder.
- Setting a drawing function of GtkDrawingArea.
And connecting a handler to "resize" signal on GtkDrawingArea.
- Implementing each call back functions.
- 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`.
@ -82,40 +128,26 @@ The following is `colorapplication.c`.
color/colorapplication.c
@@@
- 109-124: The function `main` is almost same as before but there are some differences.
The application ID is "com.github.ToshioCP.color".
`G_APPLICATION_FLAGS_NONE` is specified so no open signal handler is necessary.
- 87-107: Startup handler.
- 92-97: Builds widgets.
The pointers of the top window, TfeTextView and GtkDrawingArea objects are stored to static variables `win`, `tv` and `da` respectively.
This is because these objects are often used in handlers.
They never be rewritten so they're thread safe.
- 98: connects "resize" signal and the handler.
- 99: sets the drawing function.
- 82-85: Activate handler, which just shows the widgets.
- 74-80: The drawing function.
It just copies `surface` to destination.
- 66-72: Resize handler.
Re-creates the surface to fit its width and height for the drawing area and paints by calling the function `run`.
- 59-64: Close handler.
It destroys `surface` if it exists.
Then it destroys the top-level window and quits the application.
- 49-57: Open and save handler.
They just call the corresponding functions of TfeTextView.
- 43-47: Run handler.
It calls run function to paint the surface.
After that `gtk_widget_queue_draw` is called.
This function adds the widget (GtkDrawingArea) to the queue to be redrawn.
It is important to know that the window is redrawn whenever it is necessary.
For example, when another window is moved and uncovers part of the widget, or when the window containing it is resized.
But repainting `surface` is not automatically notified to gtk.
Therefore, you need to call `gtk_widget_queue_draw` to redraw the widget.
- 9-41: Run function paints the surface.
First, it gets the contents of GtkTextBuffer.
Then it compares it to "red", "green" and so on.
If it matches the color, then the surface is painted the color.
If it matches "light" or "dark", then the color of the surface is lightened or darkened respectively.
Alpha channel is used.
- 4-8: Win, tv, da and surface are defined as static variables.
- 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
@ -126,14 +158,9 @@ An argument "export_dynamic: true" is added to executable function.
color/meson.build
@@@
## Compile and execute it
## Build and try
First you need to export some variables (refer to [Section 2](sec2.src.md)) if you've installed GTK 4 from the source.
If you've installed GTK 4 from the distribution packages, you don't need to do this.
$ . env.sh
Then type the following to compile it.
Type the following to compile the program.
$ meson _build
$ ninja -C _build
@ -144,7 +171,8 @@ Type the following to execute it.
$ _build/color
Type "red", "green", "blue", "white", black", "light" or "dark" in the TfeTextView.
Then, click on `Run` button.
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.

View file

@ -1,41 +1,42 @@
# Tiny turtle graphics interpreter
A program `turtle` is an example with the combination of TfeTextView and GtkDrawingArea objects.
It is a very small interpreter but it provides a tool to draw fractal curves.
The following diagram is a Koch curve, which is a famous example of fractal curves.
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.
![Koch curve](turtle/image/turtle_koch.png){width=8cm height=5.11cm}
The following is a snow-crystal-shaped curve.
It is composed of six Koch curves.
![Snow](../image/turtle_snow.png){width=8cm height=5.11cm}
This program uses flex and bison.
Flex is a lexical analyzer.
Bison is a parser generator.
These two programs are similar to lex and yacc which are proprietary software developed in Bell Laboratory.
However, flex and bison are open source software.
I will write about how to use those software, but they are not topics about gtk.
So, readers can skip that part of this sections.
I will write about how to use those software, but they are not topics about GTK 4.
So, readers can skip this section.
## How to use turtle
@@@if gfm
The documentation of turtle is [here](turtle/turtle_doc.src.md).
The turtle document is [here](turtle/turtle_doc.src.md).
@@@elif html
The documentation of turtle is [here](turtle/turtle_doc.src.md).
The turtle document is [here](https://toshiocp.github.io/Gtk4-tutorial/turtle_doc.html).
@@@elif latex
The documentation of turtle is in the appendix.
The turtle document is in the appendix.
@@@end
I'll show you a simple example.
~~~
fc (1,0,0) # Foreground color is red, rgb = (1,0,0).
pd # Pen down.
rp (4) { # Repeat four times.
fd 100 # Go forward by 100 pixels.
tr 90 # Turn right by 90 degrees.
fd 100
tr 90
fd 100
tr 90
fd 100
tr 90
}
~~~
1. Compile and install `turtle` (See the documentation above).
@ -45,8 +46,8 @@ Then, run `turtle`.
The side of the square is 100 pixels long.
In the same way, you can draw other curves.
The documentation above shows some fractal curves such as tree, snow and square-koch.
The source code in turtle language is located at [src/turtle/example](turtle/example) directory.
The turtle document includes some fractal curves such as tree, snow and square-koch.
The source codes are located at [src/turtle/example](turtle/example) directory.
You can read these files into `turtle` editor by clicking on the `Open` button.
## Combination of TfeTextView and GtkDrawingArea objects
@ -59,10 +60,9 @@ It is similar to `color` program in the previous section.
3. The parser reads the program and generates tree-structured data.
4. The interpriter reads the data and executes it step by step.
And it draws shapes on a surface.
The surface is different from the surface of the GtkDrawingArea widget.
The surface isn't the one in the GtkDrawingArea widget.
5. The widget is added to the queue.
It will be redrawn with the drawing function.
The function just copies the surface, which is drawn by the interpreter, into the surface of the GtkDrawingArea.
It will be redrawn with the drawing function, which just copies the surface into the one in the GtkDrawingArea.
![Parser, interpreter and drawing function](../image/turtle.png)
@ -77,7 +77,7 @@ turtle/turtleapplication.c run_cb resize_cb
- 8-13: The static value `busy` holds a status of the interpreter.
If it is `TRUE`, the interpreter is running and it is not possible to call the interpreter again because it's not a re-entrant program.
If it is `FALSE`, it is safe to call the interpreter.
- 14: Now it is about to call the interpreter so it changes `busy` to TRUE.
- 14: Changes `busy` to TRUE to avoid reentrance.
- 15-16: Gets the contents of `tb`.
- 17: The variable `surface` is a static variable.
It points to a `cairo_surface_t` instance.
@ -86,17 +86,17 @@ Therefore, `surface` isn't NULL usually.
But if it is NULL, the interpreter won't be called.
- 18: Initializes lexical analyzer.
- 19: Calls parser.
Parser analyzes the program codes syntactically and generate a tree structured data.
Parser analyzes the program codes syntactically and generates a tree structured data.
- 20-22: If the parser successfully parsed, it calls `run` (runtime routine).
- 23: finalizes the lexical analyzer.
- 25: frees `contents`.
- 26: Adds the drawing area widget to the queue to draw.
- 27: The interpreter program has finished so `busy` is now changed to FALSE.
- 29-34: A handler of "resized" signal.
If `surface` isn't NULL, it destroys the old surface.
Then it creates a new surface.
- 30-37: A "resized" signal handler.
If the `surface` isn't NULL, it is destroyed.
A new surface is created.
Its size is the same as the surface of the GtkDrawingArea instance.
Run\_cb is called to redraw the shape on the drawing area.
Other part of `turtleapplication.c` is almost same as the codes of `colorapplication.c` in the previous section.
The codes of `turtleapplication.c` is in the [turtle directory](turtle).
@ -185,7 +185,7 @@ The source files are:
- flex source file => `turtle.lex`
- bison source file => `turtle.y`
- C header file => `turtle.h`, `turtle_lex.h`
- C header file => `turtle_lex.h`
- C source file => `turtleapplication.c`
- other files => `turtle.ui`, `turtle.gresources.xml` and `meson.build`
@ -195,12 +195,13 @@ The compilation process is a bit complicated.
It also generates `resources.h`.
2. bison compiles `turtle.y` to `turtle_parser.c` and generates `turtle_parser.h`
3. flex compiles `turtle.lex` to `turtle_lex.c`.
4. gcc compiles `application.c`, `resources.c`, `turtle_parser.c` and `turtle_lex.c` with `turtle.h`, `turtle_lex.h`, `resources.h` and `turtle_parser.h`.
4. gcc compiles `application.c`, `resources.c`, `turtle_parser.c` and `turtle_lex.c` with `turtle_lex.h`, `resources.h` and `turtle_parser.h`.
It generates an executable file `turtle`.
![compile process](../image/turtle_compile_process.png){width=12cm height=9cm}
Meson controls the process and the instruction is described in `meson.build`.
Meson controls the process.
The instruction is described in `meson.build`.
@@@include
turtle/meson.build
@ -213,15 +214,13 @@ This program uses trigonometric functions.
They are defined in the math library, but the library is optional.
So, it is necessary to include it by `#include <math.h>` and also link the library with the linker.
- 6: Gets gtk4 library.
- 8: Gets gnome module.
Module is a system provided by meson.
See [Meson build system website, GNUME module](https://mesonbuild.com/Gnome-module.html#gnome-module) for further information.
- 8: Gets gnome module.See [Meson build system website -- GNUME module](https://mesonbuild.com/Gnome-module.html#gnome-module) for further information.
- 9: Compiles ui file to C source file according to the XML file `turtle.gresource.xml`.
- 11: Gets flex.
- 12: Gets bison.
- 13: Compiles `turtle.y` to `turtle_parser.c` and `turtle_parser.h` by bison.
The function `custom_target` creates a custom top level target.
See [Meson build system website, custom target](https://mesonbuild.com/Reference-manual.html#custom_target) for further information.
See [Meson build system website -- custom target](https://mesonbuild.com/Reference-manual.html#custom_target) for further information.
- 14: Compiles `turtle.lex` to `turtle_lex.c` by flex.
- 16: Specifies C source files.
- 18: Compiles C source files including generated files by glib-compile-resources, bison and flex.
@ -268,17 +267,17 @@ They are definitions, rules and user code sections.
- 1-12: Lines between "%top{" and "}" are C source codes.
They will be copied to the top of the generated C source file.
- 2-3: The function `strlen`, in line 62, is defined in `string.h`
The function `atof`, in line 37, is defined in `stdlib.h`.
- 6-8: The current input position is pointed by `nline` and `ncolumn`.
The function `get_location` (line 58-63) sets `yylloc`to point the start and end point of `yytext` in the buffer.
- 2-3: The function `strlen`, in line 65, is defined in `string.h`
The function `atof`, in line 40, is defined in `stdlib.h`.
- 7-9: The current input position is pointed by `nline` and `ncolumn`.
The function `get_location` (line 61-66) sets `yylloc`to point the start and end point of `yytext` in the buffer.
This function is declared here so that it can be called before the function is defined.
- 11: GSlist is used to keep allocated memories.
- 14: This option (`%option noyywrap`) must be specified when you have only single source file to the scanner. Refer to "9 The Generated Scanner" in the flex documentation in your distribution for further information.
- 12: GSlist is used to keep allocated memories.
- 15: This option (`%option noyywrap`) must be specified when you have only single source file to the scanner. Refer to "9 The Generated Scanner" in the flex documentation in your distribution for further information.
(The documentation is not on the internet.)
- 16-17: `REAL_NUMBER` and `IDENTIFIER` are names.
- 17-18: `REAL_NUMBER` and `IDENTIFIER` are names.
A name begins with a letter or an underscore followed by zero or more letters, digits, underscores (`_`) or dashes (`-`).
They are followed by regular expressions which are their definition.
They are followed by regular expressions which are their definitions.
They will be used in rules section and will expand to the definition.
You can leave out such definitions here and use regular expressions in rules section directly.
@ -290,64 +289,66 @@ The patterns are regular expressions or names surrounded by braces.
The names must be defined in the definitions section.
The definition of the regular expression is written in the flex documentation.
For example, line 37 is a rule.
For example, line 40 is a rule.
- `{REAL_NUMBER}` is a pattern
- `get_location (yytext); yylval.NUM = atof (yytext); return NUM;` is an action.
`{REAL_NUMBER}` is defined in the 16th line, so it expands to `(0|[1-9][0-9]*)(\.[0-9]+)?`.
`{REAL_NUMBER}` is defined in the line 17, so it expands to `(0|[1-9][0-9]*)(\.[0-9]+)?`.
This regular expression matches numbers like `0`, `12` and `1.5`.
If the input is a number, it matches the pattern in line 37.
If an input is a number, it matches the pattern in line 40.
Then the matched text is assigned to `yytext` and corresponding action is executed.
A function `get_location` changes the location variables.
A function `get_location` changes the location variables to the position at the text.
It assigns `atof (yytext)`, which is double sized number converted from `yytext`, to `yylval.NUM` and return `NUM`.
`NUM` is an integer defined by `turtle.y`.
`NUM` is a token kind and it represents integer.
It is defined in `turtle.y`.
The scanner generated by flex and C compiler has `yylex` function.
The scanner generated by flex has `yylex` function.
If `yylex` is called and the input is "123.4", then it works as follows.
1. A string "123.4" matches `{REAL_NUMBER}`.
2. Update the location variable `ncolumn` and `yylloc`with `get_location`.
3. `atof` converts the string "123.4" to double type number 123.4.
3. The function `atof` converts the string "123.4" to double type number 123.4.
4. It is assigned to `yylval.NUM`.
5. `yylex` returns `NUM` to the caller.
Then the caller knows the input is `NUM` (number), and its value is 123.4.
Then the caller knows the input is a number (`NUM`), and its value is 123.4.
- 19-55: Rules section.
- 20: The symbol `.` (dot) matches any character except newline.
- 20-58: Rules section.
- 21: The symbol `.` (dot) matches any character except newline.
Therefore, a comment begins `#` followed by any characters except newline.
No action happens.
- 21: White space just increases a variable `ncolumn` by one.
- 22: Tab is assumed to be equal to eight spaces.
- 23: New line increases a variable `nline` by one and resets `ncolumn`.
- 25-35: Keywords just updates the location variables `ncolumn` and `yylloc`, and return the codes of the keywords.
- 37: Real number constant.
- 38: `IDENTIFIER` is defined in line 17.
- 22: White space just increases the variable `ncolumn` by one.
- 23: Tab is assumed to be equal to eight spaces.
- 24: New line increases a variable `nline` by one and resets `ncolumn`.
- 26-38: Keywords just updates the location variables `ncolumn` and `yylloc`, and return the token kinds of the keywords.
- 40: Real number constant.
- 42: `IDENTIFIER` is defined in line 18.
The location variables are updated and the name of the identifier is assigned to `yylval.ID`.
The memory of the name is allocated by the function `g_strdup`.
The memory is registered to the list (GSlist type list).
The memory will be freed after the runtime routine finishes.
Returns `ID`.
- 43-54: Symbols just update the location variable and return the codes.
The code is the same as the symbol itself.
- 55: If the input doesn't match above patterns, then it is error.
Returns `YYUNDEF`.
A token kind `ID` is returned.
- 46-56: Symbols just update the location variable and return the token kinds.
The token kind is the same as the symbol itself.
- 58: If the input doesn't match the patterns, then it is an error.
A special token kind `YYUNDEF` is returned.
### User code section
This section is just copied to C source file.
- 58-63: A function `get_location`.
- 61-66: A function `get_location`.
The location of the input is recorded to `nline` and `ncolumn`.
A variable `yylloc` is referred by the parser.
It is a C structure and has four members, `first_line`, `first_column`, `last_line` and `last_column`.
They point the start and end of the current input text.
- 65: `YY_BUFFER_STATE` is a pointer points the input buffer.
- 67-70: `init_flex` is called by `run_cb` signal handler, which is called when `Run` button is clicked on.
`run_cb` calls `init_flex` with one argument which is the copy of the content of GtkTextBuffer.
`yy_scan_string` sets the input buffer to read from the text.
- 72-75: `finalize_flex` is called after runtime routine finishes.
- 68: `YY_BUFFER_STATE` is a pointer points the input buffer.
- 70-73: A function `init_flex` is called by `run_cb` which is a "clicked" signal handler on the `Run` button.
It has one string type parameter.
The caller assigns it with the content of the GtkTextBuffer instance.
A function `yy_scan_string` sets the input buffer for the scanner.
- 75-78: A function `finalize_flex` is called after runtime routine finishes.
It deletes the input buffer.
## Turtle.y
@ -357,8 +358,8 @@ So I will explain the key points and leave out other less important parts.
### What does bison do?
Bison creates C source file from bison source file.
Bison source file is a text file.
Bison creates C source file of a parser from a bison source file.
The bison source file is a text file.
A parser analyzes a program source code according to its grammar.
Suppose here is a turtle source file.
@ -399,7 +400,7 @@ So, the parser gets items in the following table whenever it calls `yylex`.
Bison source code specifies the grammar rules of turtle language.
For example, `fc (1,0,0)` is called primary procedure.
A procedure is like a void type function in C source code.
A procedure is like a void type C function.
It doesn't return any values.
Programmers can define their own procedures.
On the other hand, `fc` is a built-in procedure.
@ -417,7 +418,8 @@ This means:
- expression is ID or NUM.
The description above is called BNF (Backus-Naur form).
More precisely, it is similar to BNF.
Precisely speaking, it is not exactly the same as BNF.
But the difference is small.
The first line is:
@ -427,7 +429,7 @@ FC '(' NUM ',' NUM ',' NUM ')';
The parser analyzes the turtle source code and if the input matches the definition above, the parser recognizes it as a primary procedure.
The grammar of turtle is described in the [document](turtle/turtle_doc.src.md).
The grammar of turtle is described in the [Turtle manual](https://toshiocp.github.io/Gtk4-tutorial/turtle_doc.html).
The following is an extract from the document.
~~~
@ -447,12 +449,14 @@ primary_procedure:
| PW expression
| FD expression
| TR expression
| TL expression
| BC '(' expression ',' expression ',' expression ')'
| FC '(' expression ',' expression ',' expression ')'
| ID '=' expression
| IF '(' expression ')' '{' primary_procedure_list '}'
| RT
| RS
| RP '(' expression ')' '{' primary_procedure_list '}'
| ID '(' ')'
| ID '(' argument_list ')'
;
@ -499,12 +503,12 @@ The grammar rule defines `program` first.
The definition is recursive.
- `statement` is program.
- `statement statement` is `program statemet`.
- `statement statement` is `program statement`.
Therefore, it is program.
- `statement statement statement` is `program statemet`.
- `statement statement statement` is `program statement`.
Therefore, it is program.
You can find that a list of statements is program like this.
You can find that a sequence of statements is program like this.
`program` and `statement` aren't tokens.
They don't appear in the input.
@ -606,7 +610,17 @@ The following is an extract from `turtle.y`.
#include <stdarg.h>
#include <setjmp.h>
#include <math.h>
#include "turtle.h"
#include <glib.h>
#include <cairo.h>
#include "turtle_parser.h"
/* The following line defines 'debug' so that debug information is printed out during the run time. */
/* However it makes the program slow. */
/* If you want to debug on, uncomment the line. */
/* #define debug 1 */
extern cairo_surface_t *surface;
/* error reporting */
static void yyerror (char const *s) { /* for syntax error */
@ -657,7 +671,7 @@ The header file is read by the scanner C source file and other files.
}
~~~
- `yylex` is shared by parser implementation file and scanner file.
- `yylex` is shared by the parser implementation file and scanner file.
- `yyparse` and `run` is called by `run_cb` in `turtleapplication.c`.
- `node_t` is the type of the semantic value of nterms.
The header file defines `YYSTYPE`, which is the semantic value type, with all the token and nterm value types.
@ -715,12 +729,14 @@ It also specifies some directives.
%token PW
%token FD
%token TR
%token TL
%token BC
%token FC
%token DP
%token IF
%token RT
%token RS
%token RP
/* constant */
%token <double> NUM
/* identirier */
@ -839,8 +855,9 @@ Be careful.
The operator `=` above is an assignment.
Assignment is not expression in turtle language.
It is primary_procedure.
But if `=` appears in an expression, it is a logical operater, not an assignment.
But if `=` appears in an expression, it is a logical operator, not an assignment.
The logical equal '`=`' usually used in the conditional expression, for example, in `if` statement.
(Turtle language uses '=' instead of '==' in C language).
### Grammar rules
@ -873,22 +890,22 @@ expression:
;
~~~
- `program` is `statement`.
- The first two lines tell that `program` is `statement`.
- Whenever `statement` is reduced to `program`, an action `node_top=$$=$1;` is executed.
- `node_top` is a static variable.
It points the top node of the tree.
- `$$` is a semantic value of the result, which is `program` in the second line of the example above.
The semantic value of `program` is a pointer to `node_t` type structure.
It was defined in the declaration section.
- `$1` is a semantic value of the first component, which is `statement`.
The semantic value of `statement` is also a pointer to `node_t`.
- `statement` is `primary_procedure`.
- A symbol `$$` is a semantic value of the result.
For example, `$$` in line 2 is the semantic value of `program`.
It is a pointer to a `node_t` type structure.
- `$1` is a semantic value of the first component.
For example, `$1` in line 2 is the semantic value of `statement`.
It is also a pointer to `node_t`.
- The next rule is that `statement` is `primary_procedure`.
There's no action specified.
Then, the default action is executed.
It is ` $$ = $1`.
- `primary_procedure` is `FD` followed by expression.
Then, the default action `$$ = $1` is executed.
- The next rule is that `primary_procedure` is `FD` followed by expression.
The action calls `tree1` and assigns its return value to `$$`.
`tree1` makes a tree node.
The function `tree1` makes a tree node.
The tree node has type and union of three pointers to children nodes, string or double.
~~~
node --+-- type
@ -898,7 +915,7 @@ node --+-- type
+---double value
~~~
- `tree1` assigns the four arguments to type, child1, child2 and child3 members.
- `expression` is `NUM`.
- The last rule is that `expression` is `NUM`.
- `tree2` makes a tree node. The paremeters of `tree2` are a type and a semantic value.
Suppose the parser reads the following program.
@ -957,6 +974,7 @@ primary_procedure:
| PW expression { $$ = tree1 (N_PW, $2, NULL, NULL); }
| FD expression { $$ = tree1 (N_FD, $2, NULL, NULL); }
| TR expression { $$ = tree1 (N_TR, $2, NULL, NULL); }
| TL expression { $$ = tree1 (N_TL, $2, NULL, NULL); } /* ver 0.5 */
| BC '(' expression ',' expression ',' expression ')' { $$ = tree1 (N_BC, $3, $5, $7); }
| FC '(' expression ',' expression ',' expression ')' { $$ = tree1 (N_FC, $3, $5, $7); }
/* assignment */
@ -965,6 +983,7 @@ primary_procedure:
| IF '(' expression ')' '{' primary_procedure_list '}' { $$ = tree1 (N_IF, $3, $6, NULL); }
| RT { $$ = tree1 (N_RT, NULL, NULL, NULL); }
| RS { $$ = tree1 (N_RS, NULL, NULL, NULL); }
| RP '(' expression ')' '{' primary_procedure_list '}' { $$ = tree1 (N_RP, $3, $6, NULL); }
/* user defined procedure call */
| ID '(' ')' { $$ = tree1 (N_procedure_call, tree3 (N_ID, $1), NULL, NULL); }
| ID '(' argument_list ')' { $$ = tree1 (N_procedure_call, tree3 (N_ID, $1), $3, NULL); }
@ -1114,7 +1133,7 @@ init_table (void) {
~~~
`init_table` initializes the table.
This must be called before any registrations.
This must be called before registrations.
There are five functions to access the table,
@ -1242,8 +1261,8 @@ The runtime routine stores the name `drawline` and the node of the procedure to
- The second line calls the procedure.
First, it looks for the procedure in the symbol table and gets its node.
Then it searches the node for the parameters and gets `angle` and `distance`.
- It pushes ("distance", 100.0) to the stack.
- It pushes ("angle", 90.0) to the stack.
- It pushes ("distance", 100.0) to the stack.
- It pushes (NULL, 2.0) to the stack.
The number 2.0 is the number of parameters (or arguments).
It is used when the procedure returns.
@ -1251,8 +1270,11 @@ It is used when the procedure returns.
The following diagram shows the structure of the stack.
First, `procedure 1` is called.
The procedure has two parameters.
In the `procedure 1`, another procedure `procedure 2`, which has one parameter, is called.
And in the `procedure 2`, `procedure 3`, which has three parameters, is called.
In the `procedure 1`, another procedure `procedure 2` is called.
It has one parameter.
In the `procedure 2`, another procedure `procedure 3` is called.
It has three parameters.
These three procedures are nested.
Programs push data to a stack from a low address memory to a high address memory.
In the following diagram, the lowest address is at the top and the highest address is at the bottom.
@ -1843,4 +1865,4 @@ However, the following information is very useful (but old).
- Source code of a language, for example, ruby.
Lately, lots of source codes are in the internet.
Maybe reading source codes are the most useful for programmers.
Maybe reading source codes is the most useful for programmers.

View file

@ -1,12 +1,8 @@
dp sq (side) {
fd side
tr 90
fd side
tr 90
fd side
tr 90
rp (4) {
fd side
tr 90
}
}
sq (100)

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

View file

@ -1,13 +0,0 @@
#include <gtk/gtk.h>
#include "../tfetextview/tfetextview.h"
#include "turtle_lex.h"
#include "turtle_parser.h"
/* The following line defines 'debug' so that debug information is printed during the run time. */
/* However it makes the program slow. */
/* If you don't want to see such information, remove the line. */
/*#define debug 1*/
extern cairo_surface_t *surface;

View file

@ -1,7 +1,8 @@
%top{
#include <string.h>
#include <stdlib.h>
#include "turtle.h"
#include <glib.h>
#include "turtle_parser.h"
static int nline = 1;
static int ncolumn = 1;
@ -27,12 +28,14 @@ pd get_location (yytext); return PD; /* pen down */
pw get_location (yytext); return PW; /* pen width = line width */
fd get_location (yytext); return FD; /* forward */
tr get_location (yytext); return TR; /* turn right */
tl get_location (yytext); return TL; /* turn left ver 0.5 */
bc get_location (yytext); return BC; /* background color */
fc get_location (yytext); return FC; /* foreground color */
dp get_location (yytext); return DP; /* define procedure */
if get_location (yytext); return IF; /* if statement */
rt get_location (yytext); return RT; /* return statement */
rs get_location (yytext); return RS; /* reset the status */
rp get_location (yytext); return RP; /* repeat ver 0.5 */
/* constant */
{REAL_NUMBER} get_location (yytext); yylval.NUM = atof (yytext); return NUM;
/* identifier */
@ -73,4 +76,3 @@ void
finalize_flex (void) {
yy_delete_buffer (state);
}

View file

@ -12,7 +12,17 @@
#include <stdarg.h>
#include <setjmp.h>
#include <math.h>
#include "turtle.h"
#include <glib.h>
#include <cairo.h>
#include "turtle_parser.h"
/* The following line defines 'debug' so that debug information is printed out during the run time. */
/* However it makes the program slow. */
/* If you want to debug on, uncomment the line. */
/* #define debug 1 */
extern cairo_surface_t *surface;
/* error reporting */
static void yyerror (char const *s) { /* for syntax error */
@ -25,6 +35,7 @@
N_PW,
N_FD,
N_TR,
N_TL, /* Turn Left version 0.5 */
N_BC,
N_FC,
N_DP,
@ -32,6 +43,7 @@
N_IF,
N_RT,
N_RS,
N_RP, /* Repeat version 0.5 */
N_NUM,
N_ID,
N_program,
@ -59,6 +71,7 @@
"N_PW",
"N_FD",
"N_TR",
"N_TL", /* ver o.5 */
"N_BC",
"N_FC",
"N_DP",
@ -66,6 +79,7 @@
"N_IF",
"N_RT",
"N_RS",
"N_RP", /* ver 0.5 */
"N_NUM",
"N_ID",
"N_program",
@ -132,12 +146,14 @@
%token PW
%token FD
%token TR
%token TL /* ver 0.5 */
%token BC
%token FC
%token DP
%token IF
%token RT
%token RS
%token RP /* ver 0.5 */
/* constant */
%token <double> NUM
/* identirier */
@ -180,6 +196,7 @@ primary_procedure:
| PW expression { $$ = tree1 (N_PW, $2, NULL, NULL); }
| FD expression { $$ = tree1 (N_FD, $2, NULL, NULL); }
| TR expression { $$ = tree1 (N_TR, $2, NULL, NULL); }
| TL expression { $$ = tree1 (N_TL, $2, NULL, NULL); } /* ver 0.5 */
| BC '(' expression ',' expression ',' expression ')' { $$ = tree1 (N_BC, $3, $5, $7); }
| FC '(' expression ',' expression ',' expression ')' { $$ = tree1 (N_FC, $3, $5, $7); }
/* assignment */
@ -188,6 +205,7 @@ primary_procedure:
| IF '(' expression ')' '{' primary_procedure_list '}' { $$ = tree1 (N_IF, $3, $6, NULL); }
| RT { $$ = tree1 (N_RT, NULL, NULL, NULL); }
| RS { $$ = tree1 (N_RS, NULL, NULL, NULL); }
| RP '(' expression ')' '{' primary_procedure_list '}' { $$ = tree1 (N_RP, $3, $6, NULL); }
/* user defined procedure call */
| ID '(' ')' { $$ = tree1 (N_procedure_call, tree3 (N_ID, $1), NULL, NULL); }
| ID '(' argument_list ')' { $$ = tree1 (N_procedure_call, tree3 (N_ID, $1), $3, NULL); }
@ -614,6 +632,7 @@ execute (node_t *node) {
double d, x, y;
char *name;
int n, i;
int counter; /* ver 0.5, for repeat procedure */
if (node == NULL)
runtime_error ("Node is NULL.\n");
@ -677,6 +696,11 @@ g_print ("fd: New Y coordinate is %f.\n", cur_y);
for (; angle < 0; angle += 360.0);
for (; angle>360; angle -= 360.0);
break;
case N_TL: /* ver 0.5 */
angle += eval (child1(node));
for (; angle < 0; angle += 360.0);
for (; angle>360; angle -= 360.0);
break;
case N_BC:
bc.red = eval (child1(node));
bc.green = eval (child2(node));
@ -722,6 +746,15 @@ g_print ("fc: Foreground color is (%f, %f, %f).\n", fc.red, fc.green, fc.blue);
fc.red = 0.0; fc.green = 0.0; fc.blue = 0.0;
/* To change background color, use bc. */
break;
case N_RP: /* ver 0.5 */
counter = (int) eval (child1(node));
if (counter < 0)
runtime_error ("Repeat number %d is negative.\n", counter);
if (counter > 100)
runtime_error ("Repeat number %d is too big.\n", counter);
for (i=0; i<counter; ++i)
execute (child2(node));
break;
case N_procedure_call:
name = name(child1(node));
node_t *proc = proc_lookup (name);

View file

@ -1,4 +1,7 @@
#include "turtle.h"
#include <gtk/gtk.h>
#include "../tfetextview/tfetextview.h"
#include "turtle_lex.h"
#include "turtle_parser.h"
static GtkWidget *win;
static GtkWidget *tv;
@ -13,10 +16,10 @@ run_cb (GtkWidget *btnr) {
GtkTextIter end_iter;
char *contents;
int stat;
static gboolean busy = FALSE;
static gboolean busy = FALSE; /* initialized only once */
/* yyparse() and run() are NOT thread safe. */
/* The variable busy avoids reentry. */
/* The variable busy avoids reentrance. */
if (busy)
return;
busy = TRUE;
@ -79,9 +82,11 @@ show_filename (TfeTextView *tv) {
static void
resize_cb (GtkDrawingArea *drawing_area, int width, int height, gpointer user_data) {
if (surface)
cairo_surface_destroy (surface);
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
run_cb (NULL); // NULL is a fake (run button).
}
static void
@ -128,7 +133,7 @@ main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new (APPLICATION_ID, G_APPLICATION_FLAGS_NONE);
app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
@ -137,4 +142,3 @@ main (int argc, char **argv) {
g_object_unref (app);
return stat;
}

1
src/turtle/version.txt Normal file
View file

@ -0,0 +1 @@
0.5