mirror of
https://github.com/ToshioCP/Gtk4-tutorial.git
synced 2025-01-30 20:34:23 +01:00
Upgrade tfe. It's in src/tfe7. Insert sec20.
This commit is contained in:
parent
2b2c26a1ca
commit
bb632f024d
28 changed files with 2851 additions and 751 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -16,6 +16,7 @@ src/tfe4/_build
|
||||||
src/tfe5/_build
|
src/tfe5/_build
|
||||||
src/tfe5/hello.txt
|
src/tfe5/hello.txt
|
||||||
src/tfe6/_build
|
src/tfe6/_build
|
||||||
|
src/tfe7/_build
|
||||||
src/menu/a.out
|
src/menu/a.out
|
||||||
src/color/_build
|
src/color/_build
|
||||||
src/turtle/_build
|
src/turtle/_build
|
||||||
|
|
|
@ -32,6 +32,7 @@ You can read it without download.
|
||||||
1. [Menu and action](gfm/sec16.md)
|
1. [Menu and action](gfm/sec16.md)
|
||||||
1. [Stateful action](gfm/sec17.md)
|
1. [Stateful action](gfm/sec17.md)
|
||||||
1. [Ui file for menu and action entries](gfm/sec18.md)
|
1. [Ui file for menu and action entries](gfm/sec18.md)
|
||||||
1. [Upgrade text file editor](gfm/sec19.md)
|
1. [GtkMenuButton, accelerators, font, pango and gsettings](gfm/sec19.md)
|
||||||
1. [GtkDrawingArea and Cairo](gfm/sec20.md)
|
1. [Template XML](gfm/sec20.md)
|
||||||
1. [Combine GtkDrawingArea and TfeTextView](gfm/sec21.md)
|
1. [GtkDrawingArea and Cairo](gfm/sec21.md)
|
||||||
|
1. [Combine GtkDrawingArea and TfeTextView](gfm/sec22.md)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
Up: [Readme.md](../Readme.md), Prev: [Section 18](sec18.md), Next: [Section 20](sec20.md)
|
Up: [Readme.md](../Readme.md), Prev: [Section 18](sec18.md), Next: [Section 20](sec20.md)
|
||||||
|
|
||||||
# Upgrade text file editor
|
# GtkMenuButton, accelerators, font, pango and gsettings
|
||||||
|
|
||||||
Traditional menu structure is fine.
|
Traditional menu structure is fine.
|
||||||
However, Buttons or menu items we often use are not so many.
|
However, Buttons or menu items we often use are not so many.
|
||||||
|
|
994
gfm/sec20.md
994
gfm/sec20.md
File diff suppressed because it is too large
Load diff
508
gfm/sec21.md
508
gfm/sec21.md
|
@ -1,373 +1,201 @@
|
||||||
Up: [Readme.md](../Readme.md), Prev: [Section 20](sec20.md)
|
Up: [Readme.md](../Readme.md), Prev: [Section 20](sec20.md), Next: [Section 22](sec22.md)
|
||||||
|
|
||||||
# Combine GtkDrawingArea and TfeTextView
|
# GtkDrawingArea and Cairo
|
||||||
|
|
||||||
Now, we will make a new application which has GtkDrawingArea and TfeTextView in it.
|
If you want to draw dynamically, like an image window of gimp graphics editor, GtkDrawingArea widget is the most suitable widget.
|
||||||
Its name is "color".
|
You can draw or redraw an image in this widget freely.
|
||||||
If you write a color in TfeTextView and click on the `run` button, then the color of GtkDrawingArea changes to the color given by you.
|
It is called custom drawing.
|
||||||
|
|
||||||
![color](../image/color.png)
|
GtkDrawingArea provides a cairo context so users can draw images by cairo functions.
|
||||||
|
In this section, I will explain:
|
||||||
|
|
||||||
The following colors are available.
|
1. Cairo, but briefly.
|
||||||
|
2. GtkDrawingArea with very simple example.
|
||||||
|
|
||||||
- white
|
## Cairo
|
||||||
- black
|
|
||||||
- red
|
|
||||||
- green
|
|
||||||
- blue
|
|
||||||
|
|
||||||
In addition the following two options are also available.
|
Cairo is a two dimensional graphics library.
|
||||||
|
First, you need to know surface, source, mask, destination, cairo context and transformation.
|
||||||
|
|
||||||
- light: Make the color of the drawing area lighter.
|
- Surface represents an image.
|
||||||
- dark: Make the color of the drawing area darker.
|
It is like a canvas.
|
||||||
|
We can draw shapes and images with different colors on surfaces.
|
||||||
|
- Source pattern, or simply source, is a kind of paint, which will be transferred to destination surface by cairo functions.
|
||||||
|
- Mask is image mask used in the transference.
|
||||||
|
- Destination is a target surface.
|
||||||
|
- Cairo context manages the transference from source to destination through mask with its functions.
|
||||||
|
For example, `cairo_stroke` is a function to draw a path to the destination by the transference.
|
||||||
|
- Transformation is applied before the transfer completes.
|
||||||
|
The transformation is called affine, which is a mathematics terminology, and represented by matrix multiplication and vector addition.
|
||||||
|
Scaling, rotation, reflection, shearing and translation are examples of affine transformation.
|
||||||
|
In this section, we don't use it.
|
||||||
|
That means we only use identity transformation.
|
||||||
|
Therefore, the coordinate in source and mask is the same as the coordinate in destination.
|
||||||
|
|
||||||
This application can only do very simple things.
|
![Stroke a rectangle](../image/cairo.png)
|
||||||
However, it tells us that if we add powerful parser to it, we will be able to make it more efficient.
|
|
||||||
I want to show it to you in the later section by making a turtle graphics language like Logo program language.
|
|
||||||
|
|
||||||
In this section, we focus on how to bind the two objects.
|
The instruction is as follows:
|
||||||
|
|
||||||
## Color.ui and color.gresource.xml
|
1. Create a surface.
|
||||||
|
This will be a destination.
|
||||||
|
2. Create a cairo context with the surface and the surface will be the destination of the context.
|
||||||
|
3. Create a source pattern within the context.
|
||||||
|
4. Create paths, which are lines, rectangles, arcs, texts or more complicated shapes, to generate a mask.
|
||||||
|
5. Use drawing operator such as `cairo_stroke` to transfer the paint in the source to the destination.
|
||||||
|
6. Save the destination surface to a file if necessary.
|
||||||
|
|
||||||
First, We need to make the ui file of the widgets.
|
Here's a simple example code that draws a small square and save it as a png file.
|
||||||
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.
|
|
||||||
|
|
||||||
~~~xml
|
~~~C
|
||||||
1 <?xml version="1.0" encoding="UTF-8"?>
|
1 #include <cairo.h>
|
||||||
2 <interface>
|
2
|
||||||
3 <object class="GtkApplicationWindow" id="win">
|
3 int
|
||||||
4 <property name="title">color changer</property>
|
4 main (int argc, char **argv)
|
||||||
5 <property name="default-width">600</property>
|
5 {
|
||||||
6 <property name="default-height">400</property>
|
6 cairo_surface_t *surface;
|
||||||
7 <child>
|
7 cairo_t *cr;
|
||||||
8 <object class="GtkBox" id="boxv">
|
8 int width = 100;
|
||||||
9 <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
9 int height = 100;
|
||||||
10 <child>
|
10
|
||||||
11 <object class="GtkBox" id="boxh1">
|
11 /* Generate surface and cairo */
|
||||||
12 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
12 surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
|
||||||
13 <child>
|
13 cr = cairo_create (surface);
|
||||||
14 <object class="GtkLabel" id="dmy1">
|
14
|
||||||
15 <property name="width-chars">10</property>
|
15 /* Drawing starts here. */
|
||||||
16 </object>
|
16 /* Paint the background white */
|
||||||
17 </child>
|
17 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
|
||||||
18 <child>
|
18 cairo_paint (cr);
|
||||||
19 <object class="GtkButton" id="btnr">
|
19 /* Draw a black rectangle */
|
||||||
20 <property name="label">Run</property>
|
20 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
|
||||||
21 <signal name="clicked" handler="run_cb"></signal>
|
21 cairo_set_line_width (cr, 2.0);
|
||||||
22 </object>
|
22 cairo_rectangle (cr, width/2.0 - 20.0, height/2.0 - 20.0, 40.0, 40.0);
|
||||||
23 </child>
|
23 cairo_stroke (cr);
|
||||||
24 <child>
|
24
|
||||||
25 <object class="GtkButton" id="btno">
|
25 /* Write the surface to a png file and clean up cairo and surface. */
|
||||||
26 <property name="label">Open</property>
|
26 cairo_surface_write_to_png (surface, "rectangle.png");
|
||||||
27 <signal name="clicked" handler="open_cb"></signal>
|
27 cairo_destroy (cr);
|
||||||
28 </object>
|
28 cairo_surface_destroy (surface);
|
||||||
29 </child>
|
29
|
||||||
30 <child>
|
30 return 0;
|
||||||
31 <object class="GtkLabel" id="dmy2">
|
31 }
|
||||||
32 <property name="hexpand">TRUE</property>
|
|
||||||
33 </object>
|
|
||||||
34 </child>
|
|
||||||
35 <child>
|
|
||||||
36 <object class="GtkButton" id="btns">
|
|
||||||
37 <property name="label">Save</property>
|
|
||||||
38 <signal name="clicked" handler="save_cb"></signal>
|
|
||||||
39 </object>
|
|
||||||
40 </child>
|
|
||||||
41 <child>
|
|
||||||
42 <object class="GtkButton" id="btnc">
|
|
||||||
43 <property name="label">Close</property>
|
|
||||||
44 <signal name="clicked" handler="close_cb"></signal>
|
|
||||||
45 </object>
|
|
||||||
46 </child>
|
|
||||||
47 <child>
|
|
||||||
48 <object class="GtkLabel" id="dmy3">
|
|
||||||
49 <property name="width-chars">10</property>
|
|
||||||
50 </object>
|
|
||||||
51 </child>
|
|
||||||
52 </object>
|
|
||||||
53 </child>
|
|
||||||
54 <child>
|
|
||||||
55 <object class="GtkBox" id="boxh2">
|
|
||||||
56 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
|
||||||
57 <property name="homogeneous">TRUE</property>
|
|
||||||
58 <child>
|
|
||||||
59 <object class="GtkScrolledWindow" id="scr">
|
|
||||||
60 <property name="hexpand">TRUE</property>
|
|
||||||
61 <property name="vexpand">TRUE</property>
|
|
||||||
62 <child>
|
|
||||||
63 <object class="TfeTextView" id="tv">
|
|
||||||
64 <property name="wrap-mode">GTK_WRAP_WORD_CHAR</property>
|
|
||||||
65 </object>
|
|
||||||
66 </child>
|
|
||||||
67 </object>
|
|
||||||
68 </child>
|
|
||||||
69 <child>
|
|
||||||
70 <object class="GtkDrawingArea" id="da">
|
|
||||||
71 <property name="hexpand">TRUE</property>
|
|
||||||
72 <property name="vexpand">TRUE</property>
|
|
||||||
73 </object>
|
|
||||||
74 </child>
|
|
||||||
75 </object>
|
|
||||||
76 </child>
|
|
||||||
77 </object>
|
|
||||||
78 </child>
|
|
||||||
79 </object>
|
|
||||||
80 </interface>
|
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
- 10-53: This part describes the tool bar which has four buttons, `Run`, `Open`, `Save` and `Close`.
|
- 1: Includes the header file of cairo.
|
||||||
This is similar to the toolbar of tfe text editor in [Section 8](sec8.md).
|
- 12: `cairo_image_surface_create` creates an image surface.
|
||||||
There are two differences.
|
`CAIRO_FORMAT_RGB24` is a constant which means that each pixel has red, green and blue data.
|
||||||
`Run` button replaces `New` button.
|
Each data has 8 bit quantity.
|
||||||
A signal element is added to each button object.
|
Modern displays have this type of color depth.
|
||||||
It has "name" attribute which is a signal name and "handler" attribute which is the name of its signal handler function.
|
Width and height are pixels and given as integers.
|
||||||
Options "-WI, --export-dynamic" CFLAG is necessary when you compile the application.
|
- 13: Creates cairo context.
|
||||||
You can achieve this by adding "export_dynamic: true" argument to executable function in `meson.build`.
|
The surface given as an argument will be the destination of the context.
|
||||||
And be careful that the handler must be defined without 'static' class.
|
- 17: `cairo_set_source_rgb` creates a source pattern, which is a solid white paint.
|
||||||
- 54-76: Puts GtkScrolledWindow and GtkDrawingArea into GtkBox.
|
The second to fourth argument is red, green and blue color depth respectively.
|
||||||
GtkBox has "homogeneous property" with TRUE value, so the two children have the same width in the box.
|
Their type is float and the values are between zero and one.
|
||||||
TfeTextView is a child of GtkScrolledWindow.
|
(0,0,0) is black and (1,1,1) is white.
|
||||||
|
- 18: `cairo_paint` copies everywhere in the source to destination.
|
||||||
|
The destination is filled with white pixels by this command.
|
||||||
|
- 20: Sets the source color to black.
|
||||||
|
- 21: `cairo_set_line_width` set the width of lines.
|
||||||
|
In this case, the line width is set to two pixels.
|
||||||
|
(It is because the transformation is identity.
|
||||||
|
If the transformation isn't identity, for example scaling with the factor three, the actual width in destination will be six (2x3=6) pixels.)
|
||||||
|
- 22: Draws a rectangle (square).
|
||||||
|
The top-left coordinate is (width/2.0-20.0, height/2.0-20.0) and the width and height have the same length 40.0.
|
||||||
|
- 23: `cairo_stroke` transfer the source to destination through the rectangle in mask.
|
||||||
|
- 26: Outputs the image to a png file `rectangle.png`.
|
||||||
|
- 27: Destroys the context. At the same time the source is destroyed.
|
||||||
|
- 28: Destroys the destination surface.
|
||||||
|
|
||||||
The xml file for the resource compiler is almost same as before.
|
To compile this, type the following.
|
||||||
Just substitute "color" for "tfe".
|
|
||||||
|
|
||||||
~~~xml
|
$ gcc `pkg-config --cflags cairo` cairo.c `pkg-config --libs cairo`
|
||||||
1 <?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
2 <gresources>
|
|
||||||
3 <gresource prefix="/com/github/ToshioCP/color">
|
|
||||||
4 <file>color.ui</file>
|
|
||||||
5 </gresource>
|
|
||||||
6 </gresources>
|
|
||||||
~~~
|
|
||||||
|
|
||||||
## Tfetextview.h, tfetextview.c and color.h
|
![rectangle.png](../src/misc/rectangle.png)
|
||||||
|
|
||||||
First two files are the same as before.
|
There are lots of documentations in [Cairo's website](https://www.cairographics.org/).
|
||||||
Color.h just includes tfetextview.h.
|
If you aren't familiar with cairo, it is strongly recommended to read the [tutorial](https://www.cairographics.org/tutorial/) in the website.
|
||||||
|
|
||||||
|
## GtkDrawingArea
|
||||||
|
|
||||||
|
The following is a very simple example.
|
||||||
|
|
||||||
~~~C
|
~~~C
|
||||||
1 #include <gtk/gtk.h>
|
1 #include <gtk/gtk.h>
|
||||||
2
|
2
|
||||||
3 #include "../tfetextview/tfetextview.h"
|
3 static void
|
||||||
|
4 draw_function (GtkDrawingArea *area, cairo_t *cr, int width, int height, gpointer user_data) {
|
||||||
|
5 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); /* whilte */
|
||||||
|
6 cairo_paint (cr);
|
||||||
|
7 cairo_set_line_width (cr, 2.0);
|
||||||
|
8 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
|
||||||
|
9 cairo_rectangle (cr, width/2.0 - 20.0, height/2.0 - 20.0, 40.0, 40.0);
|
||||||
|
10 cairo_stroke (cr);
|
||||||
|
11 }
|
||||||
|
12
|
||||||
|
13 static void
|
||||||
|
14 on_activate (GApplication *app, gpointer user_data) {
|
||||||
|
15 GtkWidget *win = gtk_application_window_new (GTK_APPLICATION (app));
|
||||||
|
16 GtkWidget *area = gtk_drawing_area_new ();
|
||||||
|
17
|
||||||
|
18 gtk_window_set_title (GTK_WINDOW (win), "da1");
|
||||||
|
19 /* Set initial size of width and height */
|
||||||
|
20 gtk_drawing_area_set_content_width (GTK_DRAWING_AREA (area), 100);
|
||||||
|
21 gtk_drawing_area_set_content_height (GTK_DRAWING_AREA (area), 100);
|
||||||
|
22 gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (area), draw_function, NULL, NULL);
|
||||||
|
23 gtk_window_set_child (GTK_WINDOW (win), area);
|
||||||
|
24
|
||||||
|
25 gtk_widget_show (win);
|
||||||
|
26 }
|
||||||
|
27
|
||||||
|
28 int
|
||||||
|
29 main (int argc, char **argv) {
|
||||||
|
30 GtkApplication *app;
|
||||||
|
31 int stat;
|
||||||
|
32
|
||||||
|
33 app = gtk_application_new ("com.github.ToshioCP.da1", G_APPLICATION_FLAGS_NONE);
|
||||||
|
34 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
|
||||||
|
35 stat =g_application_run (G_APPLICATION (app), argc, argv);
|
||||||
|
36 g_object_unref (app);
|
||||||
|
37 return stat;
|
||||||
|
38 }
|
||||||
|
39
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
## Colorapplication.c
|
The function `main` is almost same as before.
|
||||||
|
The two functions `on_activate` and `draw_function` is important in this example.
|
||||||
|
|
||||||
This is the main file.
|
- 16: Generates a GtkDrawingArea object.
|
||||||
It deals with:
|
- 20,21: Sets the width and height of the contents of the GtkDrawingArea widget.
|
||||||
|
These width and height is the size of the destination surface of the cairo context provided by the widget.
|
||||||
|
- 22: 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.
|
||||||
|
|
||||||
- Building widgets by GtkBuilder.
|
The drawing function has five parameters.
|
||||||
- Seting a drawing function of GtkDrawingArea.
|
|
||||||
And connecting a handler to "resize" signal on GtkDrawingArea.
|
|
||||||
- Implementing each call back functions.
|
|
||||||
Particularly, `Run` signal handler is the point in this program.
|
|
||||||
|
|
||||||
The following is `colorapplication.c`.
|
void drawing_function (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height,
|
||||||
|
gpointer user_data);
|
||||||
|
|
||||||
~~~C
|
The first parameter is the GtkDrawingArea widget which calls the drawing function.
|
||||||
1 #include "color.h"
|
However, you can't change any properties, for example `content-width` or `content-height`, in this function.
|
||||||
2
|
The second parameter is a cairo context given by the widget.
|
||||||
3 static GtkWidget *win;
|
The destination surface of the context is connected to the contents of the widget.
|
||||||
4 static GtkWidget *tv;
|
What you draw to this surface will appear in the widget on the screen.
|
||||||
5 static GtkWidget *da;
|
The third and fourth parameters are the size of the destination surface.
|
||||||
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 }
|
|
||||||
41
|
|
||||||
42 void
|
|
||||||
43 run_cb (GtkWidget *btnr) {
|
|
||||||
44 run ();
|
|
||||||
45 gtk_widget_queue_draw (GTK_WIDGET (da));
|
|
||||||
46 }
|
|
||||||
47
|
|
||||||
48 void
|
|
||||||
49 open_cb (GtkWidget *btno) {
|
|
||||||
50 tfe_text_view_open (TFE_TEXT_VIEW (tv), win);
|
|
||||||
51 }
|
|
||||||
52
|
|
||||||
53 void
|
|
||||||
54 save_cb (GtkWidget *btns) {
|
|
||||||
55 tfe_text_view_save (TFE_TEXT_VIEW (tv));
|
|
||||||
56 }
|
|
||||||
57
|
|
||||||
58 void
|
|
||||||
59 close_cb (GtkWidget *btnc) {
|
|
||||||
60 if (surface)
|
|
||||||
61 cairo_surface_destroy (surface);
|
|
||||||
62 gtk_window_destroy (GTK_WINDOW (win));
|
|
||||||
63 }
|
|
||||||
64
|
|
||||||
65 static void
|
|
||||||
66 resize_cb (GtkDrawingArea *drawing_area, int width, int height, gpointer user_data) {
|
|
||||||
67 if (surface)
|
|
||||||
68 cairo_surface_destroy (surface);
|
|
||||||
69 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
|
|
||||||
70 run ();
|
|
||||||
71 }
|
|
||||||
72
|
|
||||||
73 static void
|
|
||||||
74 draw_func (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height, gpointer user_data) {
|
|
||||||
75 if (surface) {
|
|
||||||
76 cairo_set_source_surface (cr, surface, 0, 0);
|
|
||||||
77 cairo_paint (cr);
|
|
||||||
78 }
|
|
||||||
79 }
|
|
||||||
80
|
|
||||||
81 static void
|
|
||||||
82 activate (GApplication *application) {
|
|
||||||
83 gtk_widget_show (win);
|
|
||||||
84 }
|
|
||||||
85
|
|
||||||
86 static void
|
|
||||||
87 startup (GApplication *application) {
|
|
||||||
88 GtkApplication *app = GTK_APPLICATION (application);
|
|
||||||
89 GtkBuilder *build;
|
|
||||||
90
|
|
||||||
91 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/color/color.ui");
|
|
||||||
92 win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
|
|
||||||
93 gtk_window_set_application (GTK_WINDOW (win), app);
|
|
||||||
94 tv = GTK_WIDGET (gtk_builder_get_object (build, "tv"));
|
|
||||||
95 da = GTK_WIDGET (gtk_builder_get_object (build, "da"));
|
|
||||||
96 g_object_unref(build);
|
|
||||||
97 g_signal_connect (GTK_DRAWING_AREA (da), "resize", G_CALLBACK (resize_cb), NULL);
|
|
||||||
98 gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (da), draw_func, NULL, NULL);
|
|
||||||
99
|
|
||||||
100 GdkDisplay *display;
|
|
||||||
101
|
|
||||||
102 display = gtk_widget_get_display (GTK_WIDGET (win));
|
|
||||||
103 GtkCssProvider *provider = gtk_css_provider_new ();
|
|
||||||
104 gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1);
|
|
||||||
105 gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
|
|
||||||
106 }
|
|
||||||
107
|
|
||||||
108 int
|
|
||||||
109 main (int argc, char **argv) {
|
|
||||||
110 GtkApplication *app;
|
|
||||||
111 int stat;
|
|
||||||
112
|
|
||||||
113 app = gtk_application_new ("com.github.ToshioCP.color", G_APPLICATION_FLAGS_NONE);
|
|
||||||
114
|
|
||||||
115 g_signal_connect (app, "startup", G_CALLBACK (startup), NULL);
|
|
||||||
116 g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
|
|
||||||
117
|
|
||||||
118 stat =g_application_run (G_APPLICATION (app), argc, argv);
|
|
||||||
119 g_object_unref (app);
|
|
||||||
120 return stat;
|
|
||||||
121 }
|
|
||||||
122
|
|
||||||
~~~
|
|
||||||
|
|
||||||
- 108-121: The function `main` is almost same as before but there are some differences.
|
- 3-11: The drawing function.
|
||||||
The application ID is "com.github.ToshioCP.color".
|
- 4-5: Sets the source to be white and paint the destination white.
|
||||||
`G_APPLICATION_FLAGS_NONE` is specified so no open signal handler is necessary.
|
- 7: Sets the line width to be 2.
|
||||||
- 86-106: Startup handler.
|
- 8: Sets the source to be black.
|
||||||
- 91-96: Builds widgets.
|
- 9: Adds a rectangle to the mask.
|
||||||
The pointers of the top window, TfeTextView and GtkDrawingArea objects are stored to static variables `win`, `tv` and `da` respectively.
|
- 10: Draws the rectangle with black color to the destination.
|
||||||
This is because these objects are often used in handlers.
|
|
||||||
They never be rewritten so they're thread safe.
|
|
||||||
- 97: connects "resize" signal and the handler.
|
|
||||||
- 98: sets the drawing function.
|
|
||||||
- 81-84: Activates handler, which just shows the widgets.
|
|
||||||
- 73-79: The drawing function.
|
|
||||||
It just copies `surface` to destination.
|
|
||||||
- 65-71: Resize handler.
|
|
||||||
Re-creates the surface to fit the width and height of the drawing area and paints by calling the function `run`.
|
|
||||||
- 58-63: Closes the handler.
|
|
||||||
It destroys `surface` if it exists.
|
|
||||||
Then it destroys the top window and quits the application.
|
|
||||||
- 48-56: Open and save handler.
|
|
||||||
They just call the corresponding functions of TfeTextView.
|
|
||||||
- 42-46: Run handler.
|
|
||||||
It calls run function to paint the surface.
|
|
||||||
After that `gtk_widget_queue_draw` is called.
|
|
||||||
This fhunction adds the widget (GtkDrawingArea) to the queue to be redrawn.
|
|
||||||
It is important to know that the drawing function is called when 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 repaint of `surface` is not automatically notified to gtk.
|
|
||||||
Therefore, you need to call `gtk_widget_queue_draw` to redraw the widget.
|
|
||||||
- 9-40: 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.
|
|
||||||
|
|
||||||
## Meson.build
|
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 every moment the window is resized.
|
||||||
|
|
||||||
This file is almost same as before.
|
![Square in the window](../image/da1.png)
|
||||||
An argument "export_dynamic: true" is added to executable function.
|
|
||||||
|
|
||||||
~~~meson
|
|
||||||
1 project('color', 'c')
|
|
||||||
2
|
|
||||||
3 gtkdep = dependency('gtk4')
|
|
||||||
4
|
|
||||||
5 gnome=import('gnome')
|
|
||||||
6 resources = gnome.compile_resources('resources','color.gresource.xml')
|
|
||||||
7
|
|
||||||
8 sourcefiles=files('colorapplication.c', '../tfetextview/tfetextview.c')
|
|
||||||
9
|
|
||||||
10 executable('color', sourcefiles, resources, dependencies: gtkdep, export_dynamic: true)
|
|
||||||
~~~
|
|
||||||
|
|
||||||
## Compile and execute it
|
Up: [Readme.md](../Readme.md), Prev: [Section 20](sec20.md), Next: [Section 22](sec22.md)
|
||||||
|
|
||||||
First you need to export some variables (refer to [Section 2](sec2.md)).
|
|
||||||
|
|
||||||
$ . env.sh
|
|
||||||
|
|
||||||
Then type the following to compile it.
|
|
||||||
|
|
||||||
$ meson _build
|
|
||||||
$ ninja -C _build
|
|
||||||
|
|
||||||
The application is made in `_build` directory.
|
|
||||||
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.
|
|
||||||
Make sure the color of GtkDrawingArea changes.
|
|
||||||
|
|
||||||
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 application by yourself.
|
|
||||||
|
|
||||||
Up: [Readme.md](../Readme.md), Prev: [Section 20](sec20.md)
|
|
||||||
|
|
373
gfm/sec22.md
Normal file
373
gfm/sec22.md
Normal file
|
@ -0,0 +1,373 @@
|
||||||
|
Up: [Readme.md](../Readme.md), Prev: [Section 21](sec21.md)
|
||||||
|
|
||||||
|
# Combine GtkDrawingArea and TfeTextView
|
||||||
|
|
||||||
|
Now, we will make a new application which has GtkDrawingArea and TfeTextView in it.
|
||||||
|
Its name is "color".
|
||||||
|
If you write a color in TfeTextView and click on the `run` button, then the color of GtkDrawingArea changes to the color given by you.
|
||||||
|
|
||||||
|
![color](../image/color.png)
|
||||||
|
|
||||||
|
The following colors are available.
|
||||||
|
|
||||||
|
- white
|
||||||
|
- black
|
||||||
|
- red
|
||||||
|
- green
|
||||||
|
- blue
|
||||||
|
|
||||||
|
In addition the following two options are also available.
|
||||||
|
|
||||||
|
- light: Make the color of the drawing area lighter.
|
||||||
|
- dark: Make the color of the drawing area darker.
|
||||||
|
|
||||||
|
This application can only do very simple things.
|
||||||
|
However, it tells us that if we add powerful parser to it, we will be able to make it more efficient.
|
||||||
|
I want to show it to you in the later section by making a turtle graphics language like Logo program language.
|
||||||
|
|
||||||
|
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.
|
||||||
|
The ui file is as follows.
|
||||||
|
|
||||||
|
~~~xml
|
||||||
|
1 <?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
2 <interface>
|
||||||
|
3 <object class="GtkApplicationWindow" id="win">
|
||||||
|
4 <property name="title">color changer</property>
|
||||||
|
5 <property name="default-width">600</property>
|
||||||
|
6 <property name="default-height">400</property>
|
||||||
|
7 <child>
|
||||||
|
8 <object class="GtkBox" id="boxv">
|
||||||
|
9 <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
||||||
|
10 <child>
|
||||||
|
11 <object class="GtkBox" id="boxh1">
|
||||||
|
12 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
||||||
|
13 <child>
|
||||||
|
14 <object class="GtkLabel" id="dmy1">
|
||||||
|
15 <property name="width-chars">10</property>
|
||||||
|
16 </object>
|
||||||
|
17 </child>
|
||||||
|
18 <child>
|
||||||
|
19 <object class="GtkButton" id="btnr">
|
||||||
|
20 <property name="label">Run</property>
|
||||||
|
21 <signal name="clicked" handler="run_cb"></signal>
|
||||||
|
22 </object>
|
||||||
|
23 </child>
|
||||||
|
24 <child>
|
||||||
|
25 <object class="GtkButton" id="btno">
|
||||||
|
26 <property name="label">Open</property>
|
||||||
|
27 <signal name="clicked" handler="open_cb"></signal>
|
||||||
|
28 </object>
|
||||||
|
29 </child>
|
||||||
|
30 <child>
|
||||||
|
31 <object class="GtkLabel" id="dmy2">
|
||||||
|
32 <property name="hexpand">TRUE</property>
|
||||||
|
33 </object>
|
||||||
|
34 </child>
|
||||||
|
35 <child>
|
||||||
|
36 <object class="GtkButton" id="btns">
|
||||||
|
37 <property name="label">Save</property>
|
||||||
|
38 <signal name="clicked" handler="save_cb"></signal>
|
||||||
|
39 </object>
|
||||||
|
40 </child>
|
||||||
|
41 <child>
|
||||||
|
42 <object class="GtkButton" id="btnc">
|
||||||
|
43 <property name="label">Close</property>
|
||||||
|
44 <signal name="clicked" handler="close_cb"></signal>
|
||||||
|
45 </object>
|
||||||
|
46 </child>
|
||||||
|
47 <child>
|
||||||
|
48 <object class="GtkLabel" id="dmy3">
|
||||||
|
49 <property name="width-chars">10</property>
|
||||||
|
50 </object>
|
||||||
|
51 </child>
|
||||||
|
52 </object>
|
||||||
|
53 </child>
|
||||||
|
54 <child>
|
||||||
|
55 <object class="GtkBox" id="boxh2">
|
||||||
|
56 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
||||||
|
57 <property name="homogeneous">TRUE</property>
|
||||||
|
58 <child>
|
||||||
|
59 <object class="GtkScrolledWindow" id="scr">
|
||||||
|
60 <property name="hexpand">TRUE</property>
|
||||||
|
61 <property name="vexpand">TRUE</property>
|
||||||
|
62 <child>
|
||||||
|
63 <object class="TfeTextView" id="tv">
|
||||||
|
64 <property name="wrap-mode">GTK_WRAP_WORD_CHAR</property>
|
||||||
|
65 </object>
|
||||||
|
66 </child>
|
||||||
|
67 </object>
|
||||||
|
68 </child>
|
||||||
|
69 <child>
|
||||||
|
70 <object class="GtkDrawingArea" id="da">
|
||||||
|
71 <property name="hexpand">TRUE</property>
|
||||||
|
72 <property name="vexpand">TRUE</property>
|
||||||
|
73 </object>
|
||||||
|
74 </child>
|
||||||
|
75 </object>
|
||||||
|
76 </child>
|
||||||
|
77 </object>
|
||||||
|
78 </child>
|
||||||
|
79 </object>
|
||||||
|
80 </interface>
|
||||||
|
~~~
|
||||||
|
|
||||||
|
- 10-53: This part describes the tool bar which has four buttons, `Run`, `Open`, `Save` and `Close`.
|
||||||
|
This is similar to the toolbar of tfe text editor in [Section 8](sec8.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.
|
||||||
|
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`.
|
||||||
|
And be careful that the handler must be defined without 'static' class.
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
The xml file for the resource compiler is almost same as before.
|
||||||
|
Just substitute "color" for "tfe".
|
||||||
|
|
||||||
|
~~~xml
|
||||||
|
1 <?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
2 <gresources>
|
||||||
|
3 <gresource prefix="/com/github/ToshioCP/color">
|
||||||
|
4 <file>color.ui</file>
|
||||||
|
5 </gresource>
|
||||||
|
6 </gresources>
|
||||||
|
~~~
|
||||||
|
|
||||||
|
## Tfetextview.h, tfetextview.c and color.h
|
||||||
|
|
||||||
|
First two files are the same as before.
|
||||||
|
Color.h just includes tfetextview.h.
|
||||||
|
|
||||||
|
~~~C
|
||||||
|
1 #include <gtk/gtk.h>
|
||||||
|
2
|
||||||
|
3 #include "../tfetextview/tfetextview.h"
|
||||||
|
~~~
|
||||||
|
|
||||||
|
## Colorapplication.c
|
||||||
|
|
||||||
|
This is the main file.
|
||||||
|
It deals with:
|
||||||
|
|
||||||
|
- Building widgets by GtkBuilder.
|
||||||
|
- Seting a drawing function of GtkDrawingArea.
|
||||||
|
And connecting a handler to "resize" signal on GtkDrawingArea.
|
||||||
|
- Implementing each call back functions.
|
||||||
|
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 }
|
||||||
|
41
|
||||||
|
42 void
|
||||||
|
43 run_cb (GtkWidget *btnr) {
|
||||||
|
44 run ();
|
||||||
|
45 gtk_widget_queue_draw (GTK_WIDGET (da));
|
||||||
|
46 }
|
||||||
|
47
|
||||||
|
48 void
|
||||||
|
49 open_cb (GtkWidget *btno) {
|
||||||
|
50 tfe_text_view_open (TFE_TEXT_VIEW (tv), win);
|
||||||
|
51 }
|
||||||
|
52
|
||||||
|
53 void
|
||||||
|
54 save_cb (GtkWidget *btns) {
|
||||||
|
55 tfe_text_view_save (TFE_TEXT_VIEW (tv));
|
||||||
|
56 }
|
||||||
|
57
|
||||||
|
58 void
|
||||||
|
59 close_cb (GtkWidget *btnc) {
|
||||||
|
60 if (surface)
|
||||||
|
61 cairo_surface_destroy (surface);
|
||||||
|
62 gtk_window_destroy (GTK_WINDOW (win));
|
||||||
|
63 }
|
||||||
|
64
|
||||||
|
65 static void
|
||||||
|
66 resize_cb (GtkDrawingArea *drawing_area, int width, int height, gpointer user_data) {
|
||||||
|
67 if (surface)
|
||||||
|
68 cairo_surface_destroy (surface);
|
||||||
|
69 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
|
||||||
|
70 run ();
|
||||||
|
71 }
|
||||||
|
72
|
||||||
|
73 static void
|
||||||
|
74 draw_func (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height, gpointer user_data) {
|
||||||
|
75 if (surface) {
|
||||||
|
76 cairo_set_source_surface (cr, surface, 0, 0);
|
||||||
|
77 cairo_paint (cr);
|
||||||
|
78 }
|
||||||
|
79 }
|
||||||
|
80
|
||||||
|
81 static void
|
||||||
|
82 activate (GApplication *application) {
|
||||||
|
83 gtk_widget_show (win);
|
||||||
|
84 }
|
||||||
|
85
|
||||||
|
86 static void
|
||||||
|
87 startup (GApplication *application) {
|
||||||
|
88 GtkApplication *app = GTK_APPLICATION (application);
|
||||||
|
89 GtkBuilder *build;
|
||||||
|
90
|
||||||
|
91 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/color/color.ui");
|
||||||
|
92 win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
|
||||||
|
93 gtk_window_set_application (GTK_WINDOW (win), app);
|
||||||
|
94 tv = GTK_WIDGET (gtk_builder_get_object (build, "tv"));
|
||||||
|
95 da = GTK_WIDGET (gtk_builder_get_object (build, "da"));
|
||||||
|
96 g_object_unref(build);
|
||||||
|
97 g_signal_connect (GTK_DRAWING_AREA (da), "resize", G_CALLBACK (resize_cb), NULL);
|
||||||
|
98 gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (da), draw_func, NULL, NULL);
|
||||||
|
99
|
||||||
|
100 GdkDisplay *display;
|
||||||
|
101
|
||||||
|
102 display = gtk_widget_get_display (GTK_WIDGET (win));
|
||||||
|
103 GtkCssProvider *provider = gtk_css_provider_new ();
|
||||||
|
104 gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1);
|
||||||
|
105 gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
|
||||||
|
106 }
|
||||||
|
107
|
||||||
|
108 int
|
||||||
|
109 main (int argc, char **argv) {
|
||||||
|
110 GtkApplication *app;
|
||||||
|
111 int stat;
|
||||||
|
112
|
||||||
|
113 app = gtk_application_new ("com.github.ToshioCP.color", G_APPLICATION_FLAGS_NONE);
|
||||||
|
114
|
||||||
|
115 g_signal_connect (app, "startup", G_CALLBACK (startup), NULL);
|
||||||
|
116 g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
|
||||||
|
117
|
||||||
|
118 stat =g_application_run (G_APPLICATION (app), argc, argv);
|
||||||
|
119 g_object_unref (app);
|
||||||
|
120 return stat;
|
||||||
|
121 }
|
||||||
|
122
|
||||||
|
~~~
|
||||||
|
|
||||||
|
- 108-121: 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.
|
||||||
|
- 86-106: Startup handler.
|
||||||
|
- 91-96: 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.
|
||||||
|
- 97: connects "resize" signal and the handler.
|
||||||
|
- 98: sets the drawing function.
|
||||||
|
- 81-84: Activates handler, which just shows the widgets.
|
||||||
|
- 73-79: The drawing function.
|
||||||
|
It just copies `surface` to destination.
|
||||||
|
- 65-71: Resize handler.
|
||||||
|
Re-creates the surface to fit the width and height of the drawing area and paints by calling the function `run`.
|
||||||
|
- 58-63: Closes the handler.
|
||||||
|
It destroys `surface` if it exists.
|
||||||
|
Then it destroys the top window and quits the application.
|
||||||
|
- 48-56: Open and save handler.
|
||||||
|
They just call the corresponding functions of TfeTextView.
|
||||||
|
- 42-46: Run handler.
|
||||||
|
It calls run function to paint the surface.
|
||||||
|
After that `gtk_widget_queue_draw` is called.
|
||||||
|
This fhunction adds the widget (GtkDrawingArea) to the queue to be redrawn.
|
||||||
|
It is important to know that the drawing function is called when 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 repaint of `surface` is not automatically notified to gtk.
|
||||||
|
Therefore, you need to call `gtk_widget_queue_draw` to redraw the widget.
|
||||||
|
- 9-40: 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.
|
||||||
|
|
||||||
|
## Meson.build
|
||||||
|
|
||||||
|
This file is almost same as before.
|
||||||
|
An argument "export_dynamic: true" is added to executable function.
|
||||||
|
|
||||||
|
~~~meson
|
||||||
|
1 project('color', 'c')
|
||||||
|
2
|
||||||
|
3 gtkdep = dependency('gtk4')
|
||||||
|
4
|
||||||
|
5 gnome=import('gnome')
|
||||||
|
6 resources = gnome.compile_resources('resources','color.gresource.xml')
|
||||||
|
7
|
||||||
|
8 sourcefiles=files('colorapplication.c', '../tfetextview/tfetextview.c')
|
||||||
|
9
|
||||||
|
10 executable('color', sourcefiles, resources, dependencies: gtkdep, export_dynamic: true)
|
||||||
|
~~~
|
||||||
|
|
||||||
|
## Compile and execute it
|
||||||
|
|
||||||
|
First you need to export some variables (refer to [Section 2](sec2.md)).
|
||||||
|
|
||||||
|
$ . env.sh
|
||||||
|
|
||||||
|
Then type the following to compile it.
|
||||||
|
|
||||||
|
$ meson _build
|
||||||
|
$ ninja -C _build
|
||||||
|
|
||||||
|
The application is made in `_build` directory.
|
||||||
|
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.
|
||||||
|
Make sure the color of GtkDrawingArea changes.
|
||||||
|
|
||||||
|
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 application by yourself.
|
||||||
|
|
||||||
|
Up: [Readme.md](../Readme.md), Prev: [Section 21](sec21.md)
|
|
@ -1,4 +1,4 @@
|
||||||
# Upgrade text file editor
|
# GtkMenuButton, accelerators, font, pango and gsettings
|
||||||
|
|
||||||
Traditional menu structure is fine.
|
Traditional menu structure is fine.
|
||||||
However, Buttons or menu items we often use are not so many.
|
However, Buttons or menu items we often use are not so many.
|
||||||
|
|
357
src/sec20.src.md
357
src/sec20.src.md
|
@ -1,129 +1,280 @@
|
||||||
# GtkDrawingArea and Cairo
|
# Template XML
|
||||||
|
|
||||||
If you want to draw dynamically, like an image window of gimp graphics editor, GtkDrawingArea widget is the most suitable widget.
|
The tfe program in the previous section is not so good because many things are crammed into `tfepplication.c`.
|
||||||
You can draw or redraw an image in this widget freely.
|
Many static variables in `tfepplication.c` shows that.
|
||||||
It is called custom drawing.
|
|
||||||
|
|
||||||
GtkDrawingArea provides a cairo context so users can draw images by cairo functions.
|
~~~C
|
||||||
In this section, I will explain:
|
static GtkDialog *pref;
|
||||||
|
static GtkFontButton *fontbtn;
|
||||||
|
static GSettings *settings;
|
||||||
|
static GtkDialog *alert;
|
||||||
|
static GtkLabel *lb_alert;
|
||||||
|
static GtkButton *btn_accept;
|
||||||
|
|
||||||
1. Cairo, but briefly.
|
static gulong pref_close_request_handler_id = 0;
|
||||||
2. GtkDrawingArea with very simple example.
|
static gulong alert_close_request_handler_id = 0;
|
||||||
|
static gboolean is_quit;
|
||||||
|
~~~
|
||||||
|
|
||||||
## Cairo
|
Generally, if there are many global or static variables in the program, it is not a good program.
|
||||||
|
Such programs are difficult to maintain.
|
||||||
|
|
||||||
Cairo is a two dimensional graphics library.
|
The file `tfeapplication.c` should be divided into several files.
|
||||||
First, you need to know surface, source, mask, destination, cairo context and transformation.
|
|
||||||
|
|
||||||
- Surface represents an image.
|
- `tfeapplication.c` only has codes related to GtkApplication.
|
||||||
It is like a canvas.
|
- A file about GtkApplicationWindow
|
||||||
We can draw shapes and images with different colors on surfaces.
|
- A file about a preference dialog
|
||||||
- Source pattern, or simply source, is a kind of paint, which will be transferred to destination surface by cairo functions.
|
- A file about an alert dialog
|
||||||
- Mask is image mask used in the transference.
|
|
||||||
- Destination is a target surface.
|
|
||||||
- Cairo context manages the transference from source to destination through mask with its functions.
|
|
||||||
For example, `cairo_stroke` is a function to draw a path to the destination by the transference.
|
|
||||||
- Transformation is applied before the transfer completes.
|
|
||||||
The transformation is called affine, which is a mathematics terminology, and represented by matrix multiplication and vector addition.
|
|
||||||
Scaling, rotation, reflection, shearing and translation are examples of affine transformation.
|
|
||||||
In this section, we don't use it.
|
|
||||||
That means we only use identity transformation.
|
|
||||||
Therefore, the coordinate in source and mask is the same as the coordinate in destination.
|
|
||||||
|
|
||||||
![Stroke a rectangle](../image/cairo.png){width=9.0cm height=6.0cm}
|
The preference dialog is defined by a ui file.
|
||||||
|
And it has GtkBox, GtkLabel and GtkFontButton in it.
|
||||||
|
Such widget is called composite widget.
|
||||||
|
Composite widget is a child object of the parent widget.
|
||||||
|
For example, the preference composite widget is a child object of GtkDialog.
|
||||||
|
Composite widget can be built from template XML.
|
||||||
|
Next subsection shows how to build a preference dialog.
|
||||||
|
|
||||||
The instruction is as follows:
|
## Preference dialog
|
||||||
|
|
||||||
1. Create a surface.
|
First, write a template XML file.
|
||||||
This will be a destination.
|
|
||||||
2. Create a cairo context with the surface and the surface will be the destination of the context.
|
|
||||||
3. Create a source pattern within the context.
|
|
||||||
4. Create paths, which are lines, rectangles, arcs, texts or more complicated shapes, to generate a mask.
|
|
||||||
5. Use drawing operator such as `cairo_stroke` to transfer the paint in the source to the destination.
|
|
||||||
6. Save the destination surface to a file if necessary.
|
|
||||||
|
|
||||||
Here's a simple example code that draws a small square and save it as a png file.
|
|
||||||
|
|
||||||
@@@include
|
@@@include
|
||||||
misc/cairo.c
|
tfe7/tfepref.ui
|
||||||
@@@
|
@@@
|
||||||
|
|
||||||
- 1: Includes the header file of cairo.
|
- 3: Template tag specifies a composite widget.
|
||||||
- 12: `cairo_image_surface_create` creates an image surface.
|
The value of a class attribute is the object name of the composite widget.
|
||||||
`CAIRO_FORMAT_RGB24` is a constant which means that each pixel has red, green and blue data.
|
This XML file names the object "TfePref".
|
||||||
Each data has 8 bit quantity.
|
It is defined in a C source file and it will be shown later.
|
||||||
Modern displays have this type of color depth.
|
A parent attribute specifies the direct parent object of the composite widget.
|
||||||
Width and height are pixels and given as integers.
|
`TfePref` is a child object of `GtkDialog`.
|
||||||
- 13: Creates cairo context.
|
Therefore the value of the attribute is "GtkDialog".
|
||||||
The surface given as an argument will be the destination of the context.
|
A parent attribute is optional but it is recommended to specify.
|
||||||
- 17: `cairo_set_source_rgb` creates a source pattern, which is a solid white paint.
|
|
||||||
The second to fourth argument is red, green and blue color depth respectively.
|
|
||||||
Their type is float and the values are between zero and one.
|
|
||||||
(0,0,0) is black and (1,1,1) is white.
|
|
||||||
- 18: `cairo_paint` copies everywhere in the source to destination.
|
|
||||||
The destination is filled with white pixels by this command.
|
|
||||||
- 20: Sets the source color to black.
|
|
||||||
- 21: `cairo_set_line_width` set the width of lines.
|
|
||||||
In this case, the line width is set to two pixels.
|
|
||||||
(It is because the transformation is identity.
|
|
||||||
If the transformation isn't identity, for example scaling with the factor three, the actual width in destination will be six (2x3=6) pixels.)
|
|
||||||
- 22: Draws a rectangle (square).
|
|
||||||
The top-left coordinate is (width/2.0-20.0, height/2.0-20.0) and the width and height have the same length 40.0.
|
|
||||||
- 23: `cairo_stroke` transfer the source to destination through the rectangle in mask.
|
|
||||||
- 26: Outputs the image to a png file `rectangle.png`.
|
|
||||||
- 27: Destroys the context. At the same time the source is destroyed.
|
|
||||||
- 28: Destroys the destination surface.
|
|
||||||
|
|
||||||
To compile this, type the following.
|
Other lines are the same as before.
|
||||||
|
The object `TfePref` is defined in `tfepref.h` and `tfepref.c`.
|
||||||
$ gcc `pkg-config --cflags cairo` cairo.c `pkg-config --libs cairo`
|
|
||||||
|
|
||||||
![rectangle.png](misc/rectangle.png)
|
|
||||||
|
|
||||||
There are lots of documentations in [Cairo's website](https://www.cairographics.org/).
|
|
||||||
If you aren't familiar with cairo, it is strongly recommended to read the [tutorial](https://www.cairographics.org/tutorial/) in the website.
|
|
||||||
|
|
||||||
## GtkDrawingArea
|
|
||||||
|
|
||||||
The following is a very simple example.
|
|
||||||
|
|
||||||
@@@include
|
@@@include
|
||||||
misc/da1.c
|
tfe7/tfepref.h
|
||||||
@@@
|
@@@
|
||||||
|
|
||||||
The function `main` is almost same as before.
|
- 6-7: When you define a new object, you need to write these two lines.
|
||||||
The two functions `on_activate` and `draw_function` is important in this example.
|
Refer to [Section 7](sec7.src.md).
|
||||||
|
- 9-10: `tfe_pref_new` generates a new TfePref object.
|
||||||
|
It has a parameter which the object use as a transient parent to show the dialog.
|
||||||
|
|
||||||
- 16: Generates a GtkDrawingArea object.
|
@@@include
|
||||||
- 20,21: Sets the width and height of the contents of the GtkDrawingArea widget.
|
tfe7/tfepref.c
|
||||||
These width and height is the size of the destination surface of the cairo context provided by the widget.
|
@@@
|
||||||
- 22: 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.
|
|
||||||
|
|
||||||
The drawing function has five parameters.
|
- 3-8: The structure of an instance of this object.
|
||||||
|
It has two variables, settings and fontbtn.
|
||||||
|
- 10: G\_DEFINE\_TYPE macro generates lines to register the type.
|
||||||
|
- 12-18: dispose handler.
|
||||||
|
This handler is called when this object is finalizing.
|
||||||
|
The process has two stages, disposing and finalizing.
|
||||||
|
When disposing, the object releases all the objects it has had.
|
||||||
|
TfePref object holds a GSetting object.
|
||||||
|
It is released in line 16.
|
||||||
|
After that parents dispose handler is called in line 17.
|
||||||
|
Refer to [Section 10](sec10.src.md).
|
||||||
|
- 27-34: Class initialization function.
|
||||||
|
This is called in the class generation process.
|
||||||
|
- 31: Set the dispose handler.
|
||||||
|
- 32: `gtk_widget_class_set_template_from_resource` function associates the description in the XML file with the widget.
|
||||||
|
At this moment no object is generated.
|
||||||
|
It just make the class to know the structure of the object.
|
||||||
|
That's why the top level tag is not an object but template in the XML file.
|
||||||
|
- 33: `gtk_widget_class_bind_template_child` function binds a private variable of the object with a child object in the template.
|
||||||
|
This function is a macro.
|
||||||
|
The name of the private variable (in the line 7) and the id (in the line 24) in the XML file must be the same.
|
||||||
|
In the program above, the name is `fontbtn`.
|
||||||
|
The pointer to the object will be assigned to the variable when an instance is generated.
|
||||||
|
- 20-25: Instance initialization function.
|
||||||
|
- 22: Initializes the template of this object.
|
||||||
|
The template has been made during the class initialization process.
|
||||||
|
Now it is implemented to the instance.
|
||||||
|
- 23: Create GSettings object with the id `com.github.ToshioCP.tfe`.
|
||||||
|
- 24: Bind the font key in the GSettings object and the font property in the GtkFontButton.
|
||||||
|
|
||||||
void drawing_function (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height,
|
- 36-39: The function `tfe_pref_new` creates an instance of TfePref.
|
||||||
gpointer user_data);
|
The parameter `win` is a transient parent.
|
||||||
|
|
||||||
The first parameter is the GtkDrawingArea widget which calls the drawing function.
|
Now, It is very simple to use this dialog.
|
||||||
However, you can't change any properties, for example `content-width` or `content-height`, in this function.
|
A caller just creates this object and shows it.
|
||||||
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.
|
|
||||||
|
|
||||||
- 3-11: The drawing function.
|
~~~C
|
||||||
- 4-5: Sets the source to be white and paint the destination white.
|
TfePref *pref;
|
||||||
- 7: Sets the line width to be 2.
|
pref = tfe_pref_new (win) /* win is the top level window */
|
||||||
- 8: Sets the source to be black.
|
gtk_widget_show (GTK_WINDOW (win));
|
||||||
- 9: Adds a rectangle to the mask.
|
~~~
|
||||||
- 10: Draws the rectangle with black color to the destination.
|
|
||||||
|
|
||||||
Compile and run it, then a window with a black rectangle (square) appears.
|
This instance is automatically destroyed when a user clicks on the close button.
|
||||||
Try resizing the window.
|
That's all.
|
||||||
The square always appears at the center of the window because the drawing function is invoked every moment the window is resized.
|
If you want to show the dialog again, just create and show it.
|
||||||
|
|
||||||
![Square in the window](../image/da1.png)
|
## Alert dialog
|
||||||
|
|
||||||
|
It is almost same as preference dialog.
|
||||||
|
|
||||||
|
Its XML file is:
|
||||||
|
|
||||||
|
@@@include
|
||||||
|
tfe7/tfealert.ui
|
||||||
|
@@@
|
||||||
|
|
||||||
|
The header file is:
|
||||||
|
|
||||||
|
@@@include
|
||||||
|
tfe7/tfealert.h
|
||||||
|
@@@
|
||||||
|
|
||||||
|
There are three public functions.
|
||||||
|
The functions `tfe_alert_set_message` and `tfe_alert_set_button_label` sets the label and button name of the alert dialog.
|
||||||
|
For example, if you want to show an alert that the user tries to close without saving the content, set them like:
|
||||||
|
|
||||||
|
~~~C
|
||||||
|
tfe_alert_set_message (alert, "Are you really close without saving?"); /* alert points to a TfeAlert object */
|
||||||
|
tfe_alert_set_button_label (alert, "Close");
|
||||||
|
~~~
|
||||||
|
|
||||||
|
The function `tfe_alert_new` creates a TfeAlert dialog.
|
||||||
|
|
||||||
|
The C source file is:
|
||||||
|
|
||||||
|
@@@include
|
||||||
|
tfe7/tfealert.c
|
||||||
|
@@@
|
||||||
|
|
||||||
|
The program is almost same as `tfepref.c`.
|
||||||
|
|
||||||
|
The instruction how to use this object is as follows.
|
||||||
|
|
||||||
|
1. Write a "response" signal handler.
|
||||||
|
2. Create a TfeAlert object.
|
||||||
|
3. Connect "response" signal to a handler
|
||||||
|
4. Show the dialog
|
||||||
|
5. In the signal handler do something along the response-id.
|
||||||
|
Then destroy the dialog.
|
||||||
|
|
||||||
|
## Top level window
|
||||||
|
|
||||||
|
In the same way, create a child object of GtkApplicationWindow.
|
||||||
|
The object name is "TfeWindow".
|
||||||
|
|
||||||
|
@@@include
|
||||||
|
tfe7/tfewindow.ui
|
||||||
|
@@@
|
||||||
|
|
||||||
|
This XML file is the same as before except template tag.
|
||||||
|
|
||||||
|
@@@include
|
||||||
|
tfe7/tfewindow.h
|
||||||
|
@@@
|
||||||
|
|
||||||
|
There are three public functions.
|
||||||
|
The function `tfe_window_notebook_page_new` creates a new notebook page.
|
||||||
|
This is a wrapper function of `notebook_page_new`.
|
||||||
|
It is called by GtkApplication object.
|
||||||
|
The function `tfe_window_notebook_page_new_with_files` creates notebook pages with a contents read from the given files.
|
||||||
|
The function `tfe_window_new` creates a TfeWindow instance.
|
||||||
|
|
||||||
|
@@@include
|
||||||
|
tfe7/tfewindow.c
|
||||||
|
@@@
|
||||||
|
|
||||||
|
- 20-32: `alert_response_cb` is a call back function of the "response" signal of TfeAlert dialog.
|
||||||
|
This is the same as before except `gtk_window_destroy(GTK_WINDOW (win))` is used instead of `tfe_application_quit`.
|
||||||
|
- 34-60: Handlers of Button clicked signal.
|
||||||
|
- 62-123: Handlers of action activated signal.
|
||||||
|
The `user_data` is a pointer to TfeWindow instance.
|
||||||
|
- 125-135: A handler of "changed::font" signal of GSettings object.
|
||||||
|
- 132: Gets the font from GSettings data.
|
||||||
|
- 133: Gets a PangoFontDescription from the font.
|
||||||
|
In the previous version, the program gets the font description from the GtkFontButton.
|
||||||
|
The button data and GSettings data are the same.
|
||||||
|
Therefore, the data got here is the same as the data in the GtkFontButton.
|
||||||
|
In addition, we don't need to worry about the preference dialog is alive or not thanks to the GSettings.
|
||||||
|
- 134: Sets CSS on the display with the font description.
|
||||||
|
- 137-152: Public functions.
|
||||||
|
- 155-161: Dispose handler.
|
||||||
|
The GSettings object needs to be released.
|
||||||
|
- 163-191: Object initialize function.
|
||||||
|
- 168: Generates a composite widget with the template.
|
||||||
|
- 170-173: Insert menu to the menu button.
|
||||||
|
- 175-176: Creates a GSettings object with the id.
|
||||||
|
Connects "changed::font" signal to the handler `changed_font_cb`.
|
||||||
|
This signal emits when the GSettings data is changed.
|
||||||
|
The second part "font" of the signal name "changed::font" is called details.
|
||||||
|
Signals can have details.
|
||||||
|
If a GSettings object has more than one key, "changed" signal emits only if the key which has the same name as the detail changes its value.
|
||||||
|
For example, Suppose a GSettings object has three keys "a", "b" and "c".
|
||||||
|
- "changed::a" is emitted when the value of "a" is changed. It isn't emitted when the value of "b" or "c" is changed.
|
||||||
|
- "changed::b" is emitted when the value of "b" is changed. It isn't emitted when the value of "a" or "c" is changed.
|
||||||
|
- "changed::c" is emitted when the value of "c" is changed. It isn't emitted when the value of "a" or "b" is changed.
|
||||||
|
In this version of tfe, there is only one key ("font").
|
||||||
|
So, even if the signal doesn't have a detail, the result is the same.
|
||||||
|
But in the future version, it will probably need details.
|
||||||
|
- 178-188: Creates actions.
|
||||||
|
- 190: Sets CSS font.
|
||||||
|
- 193-207: Class initialization function.
|
||||||
|
- 197: Sets the dispose handler.
|
||||||
|
- 198: Sets the composite widget template
|
||||||
|
- 199-203: Binds private variable with child objects in the template.
|
||||||
|
- 204-206: Binds signal handlers with signal tags in the template.
|
||||||
|
- 209-212: `tfe_window_new`.
|
||||||
|
This function creates TfeWindow instance.
|
||||||
|
|
||||||
|
## GtkApplication
|
||||||
|
|
||||||
|
The file `tfeapplication.c` is now very simple.
|
||||||
|
|
||||||
|
@@@include
|
||||||
|
tfe7/tfeapplication.c
|
||||||
|
@@@
|
||||||
|
|
||||||
|
- 3-11: Activate signal handler.
|
||||||
|
It uses `tfe_window_notebook_page_new` instead of `notebook_page_new`.
|
||||||
|
- 13-20: Open signal handler.
|
||||||
|
Thanks to `tfe_window_notebook_page_new_with_files`, this handler becomes very simple.
|
||||||
|
- 22-46: Startup signal handler.
|
||||||
|
Most of the task is moved to TfeWindow, the remaining task is creating a window and setting accelerations.
|
||||||
|
- 49-63: A function main.
|
||||||
|
|
||||||
|
## Other files
|
||||||
|
|
||||||
|
Resource XML file.
|
||||||
|
|
||||||
|
@@@include
|
||||||
|
tfe7/tfe.gresource.xml
|
||||||
|
@@@
|
||||||
|
|
||||||
|
GSchema XML file
|
||||||
|
|
||||||
|
@@@include
|
||||||
|
tfe7/com.github.ToshioCP.tfe.gschema.xml
|
||||||
|
@@@
|
||||||
|
|
||||||
|
Meson.build
|
||||||
|
|
||||||
|
@@@include
|
||||||
|
tfe7/meson.build
|
||||||
|
@@@
|
||||||
|
|
||||||
|
## Compiling and installation.
|
||||||
|
|
||||||
|
~~~
|
||||||
|
$ meson --prefix=$HOME/local _build
|
||||||
|
$ ninja -C _build
|
||||||
|
$ ninja -C _build install
|
||||||
|
===
|
||||||
|
|
||||||
|
Source files are in [src/tfe7](tfe7) directory.
|
||||||
|
|
||||||
|
We made a very small text editor.
|
||||||
|
You can add features to this editor.
|
||||||
|
When you add a new feature, care about the structure of the program.
|
||||||
|
Maybe you need to divide a file into several files like this section.
|
||||||
|
It isn't good to put many things into one file.
|
||||||
|
And it is important to think about the relationship between source files and widget structures.
|
||||||
|
It is appropriate that they correspond to each other in many cases.
|
||||||
|
|
228
src/sec21.src.md
228
src/sec21.src.md
|
@ -1,153 +1,129 @@
|
||||||
# Combine GtkDrawingArea and TfeTextView
|
# GtkDrawingArea and Cairo
|
||||||
|
|
||||||
Now, we will make a new application which has GtkDrawingArea and TfeTextView in it.
|
If you want to draw dynamically, like an image window of gimp graphics editor, GtkDrawingArea widget is the most suitable widget.
|
||||||
Its name is "color".
|
You can draw or redraw an image in this widget freely.
|
||||||
If you write a color in TfeTextView and click on the `run` button, then the color of GtkDrawingArea changes to the color given by you.
|
It is called custom drawing.
|
||||||
|
|
||||||
![color](../image/color.png){width=7.0cm height=5.13cm}
|
GtkDrawingArea provides a cairo context so users can draw images by cairo functions.
|
||||||
|
In this section, I will explain:
|
||||||
|
|
||||||
The following colors are available.
|
1. Cairo, but briefly.
|
||||||
|
2. GtkDrawingArea with very simple example.
|
||||||
|
|
||||||
- white
|
## Cairo
|
||||||
- black
|
|
||||||
- red
|
|
||||||
- green
|
|
||||||
- blue
|
|
||||||
|
|
||||||
In addition the following two options are also available.
|
Cairo is a two dimensional graphics library.
|
||||||
|
First, you need to know surface, source, mask, destination, cairo context and transformation.
|
||||||
|
|
||||||
- light: Make the color of the drawing area lighter.
|
- Surface represents an image.
|
||||||
- dark: Make the color of the drawing area darker.
|
It is like a canvas.
|
||||||
|
We can draw shapes and images with different colors on surfaces.
|
||||||
|
- Source pattern, or simply source, is a kind of paint, which will be transferred to destination surface by cairo functions.
|
||||||
|
- Mask is image mask used in the transference.
|
||||||
|
- Destination is a target surface.
|
||||||
|
- Cairo context manages the transference from source to destination through mask with its functions.
|
||||||
|
For example, `cairo_stroke` is a function to draw a path to the destination by the transference.
|
||||||
|
- Transformation is applied before the transfer completes.
|
||||||
|
The transformation is called affine, which is a mathematics terminology, and represented by matrix multiplication and vector addition.
|
||||||
|
Scaling, rotation, reflection, shearing and translation are examples of affine transformation.
|
||||||
|
In this section, we don't use it.
|
||||||
|
That means we only use identity transformation.
|
||||||
|
Therefore, the coordinate in source and mask is the same as the coordinate in destination.
|
||||||
|
|
||||||
This application can only do very simple things.
|
![Stroke a rectangle](../image/cairo.png){width=9.0cm height=6.0cm}
|
||||||
However, it tells us that if we add powerful parser to it, we will be able to make it more efficient.
|
|
||||||
I want to show it to you in the later section by making a turtle graphics language like Logo program language.
|
|
||||||
|
|
||||||
In this section, we focus on how to bind the two objects.
|
The instruction is as follows:
|
||||||
|
|
||||||
## Color.ui and color.gresource.xml
|
1. Create a surface.
|
||||||
|
This will be a destination.
|
||||||
|
2. Create a cairo context with the surface and the surface will be the destination of the context.
|
||||||
|
3. Create a source pattern within the context.
|
||||||
|
4. Create paths, which are lines, rectangles, arcs, texts or more complicated shapes, to generate a mask.
|
||||||
|
5. Use drawing operator such as `cairo_stroke` to transfer the paint in the source to the destination.
|
||||||
|
6. Save the destination surface to a file if necessary.
|
||||||
|
|
||||||
First, We need to make the ui file of the widgets.
|
Here's a simple example code that draws a small square and save it as a png file.
|
||||||
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.
|
|
||||||
|
|
||||||
@@@include
|
@@@include
|
||||||
color/color.ui
|
misc/cairo.c
|
||||||
@@@
|
@@@
|
||||||
|
|
||||||
- 10-53: This part describes the tool bar which has four buttons, `Run`, `Open`, `Save` and `Close`.
|
- 1: Includes the header file of cairo.
|
||||||
This is similar to the toolbar of tfe text editor in [Section 8](sec8.src.md).
|
- 12: `cairo_image_surface_create` creates an image surface.
|
||||||
There are two differences.
|
`CAIRO_FORMAT_RGB24` is a constant which means that each pixel has red, green and blue data.
|
||||||
`Run` button replaces `New` button.
|
Each data has 8 bit quantity.
|
||||||
A signal element is added to each button object.
|
Modern displays have this type of color depth.
|
||||||
It has "name" attribute which is a signal name and "handler" attribute which is the name of its signal handler function.
|
Width and height are pixels and given as integers.
|
||||||
Options "-WI, --export-dynamic" CFLAG is necessary when you compile the application.
|
- 13: Creates cairo context.
|
||||||
You can achieve this by adding "export_dynamic: true" argument to executable function in `meson.build`.
|
The surface given as an argument will be the destination of the context.
|
||||||
And be careful that the handler must be defined without 'static' class.
|
- 17: `cairo_set_source_rgb` creates a source pattern, which is a solid white paint.
|
||||||
- 54-76: Puts GtkScrolledWindow and GtkDrawingArea into GtkBox.
|
The second to fourth argument is red, green and blue color depth respectively.
|
||||||
GtkBox has "homogeneous property" with TRUE value, so the two children have the same width in the box.
|
Their type is float and the values are between zero and one.
|
||||||
TfeTextView is a child of GtkScrolledWindow.
|
(0,0,0) is black and (1,1,1) is white.
|
||||||
|
- 18: `cairo_paint` copies everywhere in the source to destination.
|
||||||
|
The destination is filled with white pixels by this command.
|
||||||
|
- 20: Sets the source color to black.
|
||||||
|
- 21: `cairo_set_line_width` set the width of lines.
|
||||||
|
In this case, the line width is set to two pixels.
|
||||||
|
(It is because the transformation is identity.
|
||||||
|
If the transformation isn't identity, for example scaling with the factor three, the actual width in destination will be six (2x3=6) pixels.)
|
||||||
|
- 22: Draws a rectangle (square).
|
||||||
|
The top-left coordinate is (width/2.0-20.0, height/2.0-20.0) and the width and height have the same length 40.0.
|
||||||
|
- 23: `cairo_stroke` transfer the source to destination through the rectangle in mask.
|
||||||
|
- 26: Outputs the image to a png file `rectangle.png`.
|
||||||
|
- 27: Destroys the context. At the same time the source is destroyed.
|
||||||
|
- 28: Destroys the destination surface.
|
||||||
|
|
||||||
The xml file for the resource compiler is almost same as before.
|
To compile this, type the following.
|
||||||
Just substitute "color" for "tfe".
|
|
||||||
|
$ gcc `pkg-config --cflags cairo` cairo.c `pkg-config --libs cairo`
|
||||||
|
|
||||||
|
![rectangle.png](misc/rectangle.png)
|
||||||
|
|
||||||
|
There are lots of documentations in [Cairo's website](https://www.cairographics.org/).
|
||||||
|
If you aren't familiar with cairo, it is strongly recommended to read the [tutorial](https://www.cairographics.org/tutorial/) in the website.
|
||||||
|
|
||||||
|
## GtkDrawingArea
|
||||||
|
|
||||||
|
The following is a very simple example.
|
||||||
|
|
||||||
@@@include
|
@@@include
|
||||||
color/color.gresource.xml
|
misc/da1.c
|
||||||
@@@
|
@@@
|
||||||
|
|
||||||
## Tfetextview.h, tfetextview.c and color.h
|
The function `main` is almost same as before.
|
||||||
|
The two functions `on_activate` and `draw_function` is important in this example.
|
||||||
|
|
||||||
First two files are the same as before.
|
- 16: Generates a GtkDrawingArea object.
|
||||||
Color.h just includes tfetextview.h.
|
- 20,21: Sets the width and height of the contents of the GtkDrawingArea widget.
|
||||||
|
These width and height is the size of the destination surface of the cairo context provided by the widget.
|
||||||
|
- 22: 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.
|
||||||
|
|
||||||
@@@include
|
The drawing function has five parameters.
|
||||||
color/color.h
|
|
||||||
@@@
|
|
||||||
|
|
||||||
## Colorapplication.c
|
void drawing_function (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height,
|
||||||
|
gpointer user_data);
|
||||||
|
|
||||||
This is the main file.
|
The first parameter is the GtkDrawingArea widget which calls the drawing function.
|
||||||
It deals with:
|
However, you can't change any properties, for example `content-width` or `content-height`, in this function.
|
||||||
|
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.
|
||||||
|
|
||||||
- Building widgets by GtkBuilder.
|
- 3-11: The drawing function.
|
||||||
- Seting a drawing function of GtkDrawingArea.
|
- 4-5: Sets the source to be white and paint the destination white.
|
||||||
And connecting a handler to "resize" signal on GtkDrawingArea.
|
- 7: Sets the line width to be 2.
|
||||||
- Implementing each call back functions.
|
- 8: Sets the source to be black.
|
||||||
Particularly, `Run` signal handler is the point in this program.
|
- 9: Adds a rectangle to the mask.
|
||||||
|
- 10: Draws the rectangle with black color to the destination.
|
||||||
|
|
||||||
The following is `colorapplication.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 every moment the window is resized.
|
||||||
|
|
||||||
@@@include
|
![Square in the window](../image/da1.png)
|
||||||
color/colorapplication.c
|
|
||||||
@@@
|
|
||||||
|
|
||||||
- 108-121: 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.
|
|
||||||
- 86-106: Startup handler.
|
|
||||||
- 91-96: 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.
|
|
||||||
- 97: connects "resize" signal and the handler.
|
|
||||||
- 98: sets the drawing function.
|
|
||||||
- 81-84: Activates handler, which just shows the widgets.
|
|
||||||
- 73-79: The drawing function.
|
|
||||||
It just copies `surface` to destination.
|
|
||||||
- 65-71: Resize handler.
|
|
||||||
Re-creates the surface to fit the width and height of the drawing area and paints by calling the function `run`.
|
|
||||||
- 58-63: Closes the handler.
|
|
||||||
It destroys `surface` if it exists.
|
|
||||||
Then it destroys the top window and quits the application.
|
|
||||||
- 48-56: Open and save handler.
|
|
||||||
They just call the corresponding functions of TfeTextView.
|
|
||||||
- 42-46: Run handler.
|
|
||||||
It calls run function to paint the surface.
|
|
||||||
After that `gtk_widget_queue_draw` is called.
|
|
||||||
This fhunction adds the widget (GtkDrawingArea) to the queue to be redrawn.
|
|
||||||
It is important to know that the drawing function is called when 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 repaint of `surface` is not automatically notified to gtk.
|
|
||||||
Therefore, you need to call `gtk_widget_queue_draw` to redraw the widget.
|
|
||||||
- 9-40: 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.
|
|
||||||
|
|
||||||
## Meson.build
|
|
||||||
|
|
||||||
This file is almost same as before.
|
|
||||||
An argument "export_dynamic: true" is added to executable function.
|
|
||||||
|
|
||||||
@@@include
|
|
||||||
color/meson.build
|
|
||||||
@@@
|
|
||||||
|
|
||||||
## Compile and execute it
|
|
||||||
|
|
||||||
First you need to export some variables (refer to [Section 2](sec2.src.md)).
|
|
||||||
|
|
||||||
$ . env.sh
|
|
||||||
|
|
||||||
Then type the following to compile it.
|
|
||||||
|
|
||||||
$ meson _build
|
|
||||||
$ ninja -C _build
|
|
||||||
|
|
||||||
The application is made in `_build` directory.
|
|
||||||
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.
|
|
||||||
Make sure the color of GtkDrawingArea changes.
|
|
||||||
|
|
||||||
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 application by yourself.
|
|
||||||
|
|
153
src/sec22.src.md
Normal file
153
src/sec22.src.md
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
# Combine GtkDrawingArea and TfeTextView
|
||||||
|
|
||||||
|
Now, we will make a new application which has GtkDrawingArea and TfeTextView in it.
|
||||||
|
Its name is "color".
|
||||||
|
If you write a color in TfeTextView and click on the `run` button, then the color of GtkDrawingArea changes to the color given by you.
|
||||||
|
|
||||||
|
![color](../image/color.png){width=7.0cm height=5.13cm}
|
||||||
|
|
||||||
|
The following colors are available.
|
||||||
|
|
||||||
|
- white
|
||||||
|
- black
|
||||||
|
- red
|
||||||
|
- green
|
||||||
|
- blue
|
||||||
|
|
||||||
|
In addition the following two options are also available.
|
||||||
|
|
||||||
|
- light: Make the color of the drawing area lighter.
|
||||||
|
- dark: Make the color of the drawing area darker.
|
||||||
|
|
||||||
|
This application can only do very simple things.
|
||||||
|
However, it tells us that if we add powerful parser to it, we will be able to make it more efficient.
|
||||||
|
I want to show it to you in the later section by making a turtle graphics language like Logo program language.
|
||||||
|
|
||||||
|
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.
|
||||||
|
The ui file is as follows.
|
||||||
|
|
||||||
|
@@@include
|
||||||
|
color/color.ui
|
||||||
|
@@@
|
||||||
|
|
||||||
|
- 10-53: This part describes the tool bar which has four buttons, `Run`, `Open`, `Save` and `Close`.
|
||||||
|
This is similar to the toolbar of tfe text editor in [Section 8](sec8.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.
|
||||||
|
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`.
|
||||||
|
And be careful that the handler must be defined without 'static' class.
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
The xml file for the resource compiler is almost same as before.
|
||||||
|
Just substitute "color" for "tfe".
|
||||||
|
|
||||||
|
@@@include
|
||||||
|
color/color.gresource.xml
|
||||||
|
@@@
|
||||||
|
|
||||||
|
## Tfetextview.h, tfetextview.c and color.h
|
||||||
|
|
||||||
|
First two files are the same as before.
|
||||||
|
Color.h just includes tfetextview.h.
|
||||||
|
|
||||||
|
@@@include
|
||||||
|
color/color.h
|
||||||
|
@@@
|
||||||
|
|
||||||
|
## Colorapplication.c
|
||||||
|
|
||||||
|
This is the main file.
|
||||||
|
It deals with:
|
||||||
|
|
||||||
|
- Building widgets by GtkBuilder.
|
||||||
|
- Seting a drawing function of GtkDrawingArea.
|
||||||
|
And connecting a handler to "resize" signal on GtkDrawingArea.
|
||||||
|
- Implementing each call back functions.
|
||||||
|
Particularly, `Run` signal handler is the point in this program.
|
||||||
|
|
||||||
|
The following is `colorapplication.c`.
|
||||||
|
|
||||||
|
@@@include
|
||||||
|
color/colorapplication.c
|
||||||
|
@@@
|
||||||
|
|
||||||
|
- 108-121: 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.
|
||||||
|
- 86-106: Startup handler.
|
||||||
|
- 91-96: 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.
|
||||||
|
- 97: connects "resize" signal and the handler.
|
||||||
|
- 98: sets the drawing function.
|
||||||
|
- 81-84: Activates handler, which just shows the widgets.
|
||||||
|
- 73-79: The drawing function.
|
||||||
|
It just copies `surface` to destination.
|
||||||
|
- 65-71: Resize handler.
|
||||||
|
Re-creates the surface to fit the width and height of the drawing area and paints by calling the function `run`.
|
||||||
|
- 58-63: Closes the handler.
|
||||||
|
It destroys `surface` if it exists.
|
||||||
|
Then it destroys the top window and quits the application.
|
||||||
|
- 48-56: Open and save handler.
|
||||||
|
They just call the corresponding functions of TfeTextView.
|
||||||
|
- 42-46: Run handler.
|
||||||
|
It calls run function to paint the surface.
|
||||||
|
After that `gtk_widget_queue_draw` is called.
|
||||||
|
This fhunction adds the widget (GtkDrawingArea) to the queue to be redrawn.
|
||||||
|
It is important to know that the drawing function is called when 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 repaint of `surface` is not automatically notified to gtk.
|
||||||
|
Therefore, you need to call `gtk_widget_queue_draw` to redraw the widget.
|
||||||
|
- 9-40: 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.
|
||||||
|
|
||||||
|
## Meson.build
|
||||||
|
|
||||||
|
This file is almost same as before.
|
||||||
|
An argument "export_dynamic: true" is added to executable function.
|
||||||
|
|
||||||
|
@@@include
|
||||||
|
color/meson.build
|
||||||
|
@@@
|
||||||
|
|
||||||
|
## Compile and execute it
|
||||||
|
|
||||||
|
First you need to export some variables (refer to [Section 2](sec2.src.md)).
|
||||||
|
|
||||||
|
$ . env.sh
|
||||||
|
|
||||||
|
Then type the following to compile it.
|
||||||
|
|
||||||
|
$ meson _build
|
||||||
|
$ ninja -C _build
|
||||||
|
|
||||||
|
The application is made in `_build` directory.
|
||||||
|
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.
|
||||||
|
Make sure the color of GtkDrawingArea changes.
|
||||||
|
|
||||||
|
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 application by yourself.
|
10
src/tfe7/com.github.ToshioCP.tfe.gschema.xml
Normal file
10
src/tfe7/com.github.ToshioCP.tfe.gschema.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<schemalist>
|
||||||
|
<schema path="/com/github/ToshioCP/tfe/" id="com.github.ToshioCP.tfe">
|
||||||
|
<key name="font" type="s">
|
||||||
|
<default>'Monospace 12'</default>
|
||||||
|
<summary>Font</summary>
|
||||||
|
<description>The font to be used for textview.</description>
|
||||||
|
</key>
|
||||||
|
</schema>
|
||||||
|
</schemalist>
|
92
src/tfe7/css.c
Normal file
92
src/tfe7/css.c
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
/* css.h */
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
void
|
||||||
|
set_css_for_display (GtkWindow *win, char *css) {
|
||||||
|
GdkDisplay *display;
|
||||||
|
|
||||||
|
display = gtk_widget_get_display (GTK_WIDGET (win));
|
||||||
|
GtkCssProvider *provider = gtk_css_provider_new ();
|
||||||
|
gtk_css_provider_load_from_data (provider, css, -1);
|
||||||
|
gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
set_font_for_display (GtkWindow *win, const char *fontfamily, const char *fontstyle, const char *fontweight, int fontsize) {
|
||||||
|
char *textview_css;
|
||||||
|
|
||||||
|
textview_css = g_strdup_printf ("textview {padding: 10px; font-family: \"%s\"; font-style: %s; font-weight: %s; font-size: %dpt;}",
|
||||||
|
fontfamily, fontstyle, fontweight, fontsize);
|
||||||
|
set_css_for_display (win, textview_css);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
set_font_for_display_with_pango_font_desc (GtkWindow *win, PangoFontDescription *pango_font_desc) {
|
||||||
|
int pango_style;
|
||||||
|
int pango_weight;
|
||||||
|
const char *family;
|
||||||
|
const char *style;
|
||||||
|
const char *weight;
|
||||||
|
int fontsize;
|
||||||
|
|
||||||
|
family = pango_font_description_get_family (pango_font_desc);
|
||||||
|
pango_style = pango_font_description_get_style (pango_font_desc);
|
||||||
|
switch (pango_style) {
|
||||||
|
case PANGO_STYLE_NORMAL:
|
||||||
|
style = "normal";
|
||||||
|
break;
|
||||||
|
case PANGO_STYLE_ITALIC:
|
||||||
|
style = "italic";
|
||||||
|
break;
|
||||||
|
case PANGO_STYLE_OBLIQUE:
|
||||||
|
style = "oblique";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
style = "normal";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pango_weight = pango_font_description_get_weight (pango_font_desc);
|
||||||
|
switch (pango_weight) {
|
||||||
|
case PANGO_WEIGHT_THIN:
|
||||||
|
weight = "100";
|
||||||
|
break;
|
||||||
|
case PANGO_WEIGHT_ULTRALIGHT:
|
||||||
|
weight = "200";
|
||||||
|
break;
|
||||||
|
case PANGO_WEIGHT_LIGHT:
|
||||||
|
weight = "300";
|
||||||
|
break;
|
||||||
|
case PANGO_WEIGHT_SEMILIGHT:
|
||||||
|
weight = "350";
|
||||||
|
break;
|
||||||
|
case PANGO_WEIGHT_BOOK:
|
||||||
|
weight = "380";
|
||||||
|
break;
|
||||||
|
case PANGO_WEIGHT_NORMAL:
|
||||||
|
weight = "400"; /* or "normal" */
|
||||||
|
break;
|
||||||
|
case PANGO_WEIGHT_MEDIUM:
|
||||||
|
weight = "500";
|
||||||
|
break;
|
||||||
|
case PANGO_WEIGHT_SEMIBOLD:
|
||||||
|
weight = "600";
|
||||||
|
break;
|
||||||
|
case PANGO_WEIGHT_BOLD:
|
||||||
|
weight = "700"; /* or "bold" */
|
||||||
|
break;
|
||||||
|
case PANGO_WEIGHT_ULTRABOLD:
|
||||||
|
weight = "800";
|
||||||
|
break;
|
||||||
|
case PANGO_WEIGHT_HEAVY:
|
||||||
|
weight = "900";
|
||||||
|
break;
|
||||||
|
case PANGO_WEIGHT_ULTRAHEAVY:
|
||||||
|
weight = "900"; /* In PangoWeight definition, the weight is 1000. But CSS allows the weight below 900. */
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
weight = "normal";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
fontsize = pango_font_description_get_size (pango_font_desc) / PANGO_SCALE;
|
||||||
|
set_font_for_display (win, family, style, weight, fontsize);
|
||||||
|
}
|
14
src/tfe7/css.h
Normal file
14
src/tfe7/css.h
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
#ifndef __TFE_CSS_H__
|
||||||
|
#define __TFE_CSS_H__
|
||||||
|
|
||||||
|
void
|
||||||
|
set_css (GtkWindow *win, char *css);
|
||||||
|
|
||||||
|
void
|
||||||
|
set_font_for_display (GtkWindow *win, const char *fontfamily, const char *fontstyle, const char *fontweight, int size);
|
||||||
|
|
||||||
|
void
|
||||||
|
set_font_for_display_with_pango_font_desc (GtkWindow *win, PangoFontDescription *pango_font_desc);
|
||||||
|
|
||||||
|
#endif /* __TFE_CSS_H__ */
|
||||||
|
|
27
src/tfe7/menu.ui
Executable file
27
src/tfe7/menu.ui
Executable file
|
@ -0,0 +1,27 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface>
|
||||||
|
<menu id="menu">
|
||||||
|
<section>
|
||||||
|
<item>
|
||||||
|
<attribute name="label">New</attribute>
|
||||||
|
<attribute name="action">win.new</attribute>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label">Save As…</attribute>
|
||||||
|
<attribute name="action">win.saveas</attribute>
|
||||||
|
</item>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<item>
|
||||||
|
<attribute name="label">Preference</attribute>
|
||||||
|
<attribute name="action">win.pref</attribute>
|
||||||
|
</item>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<item>
|
||||||
|
<attribute name="label">Quit</attribute>
|
||||||
|
<attribute name="action">win.close-all</attribute>
|
||||||
|
</item>
|
||||||
|
</section>
|
||||||
|
</menu>
|
||||||
|
</interface>
|
16
src/tfe7/meson.build
Normal file
16
src/tfe7/meson.build
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
project('tfe', 'c')
|
||||||
|
|
||||||
|
gtkdep = dependency('gtk4')
|
||||||
|
|
||||||
|
gnome=import('gnome')
|
||||||
|
resources = gnome.compile_resources('resources','tfe.gresource.xml')
|
||||||
|
gnome.compile_schemas(build_by_default: true, depend_files: 'com.github.ToshioCP.tfe.gschema.xml')
|
||||||
|
|
||||||
|
sourcefiles=files('tfeapplication.c', 'tfewindow.c', 'tfenotebook.c', 'tfepref.c', 'tfealert.c', 'css.c', '../tfetextview/tfetextview.c')
|
||||||
|
|
||||||
|
executable('tfe', sourcefiles, resources, dependencies: gtkdep, export_dynamic: true, install: true)
|
||||||
|
|
||||||
|
schema_dir = get_option('prefix') / get_option('datadir') / 'glib-2.0/schemas/'
|
||||||
|
install_data('com.github.ToshioCP.tfe.gschema.xml', install_dir: schema_dir)
|
||||||
|
meson.add_install_script('glib-compile-schemas', schema_dir)
|
||||||
|
|
9
src/tfe7/tfe.gresource.xml
Normal file
9
src/tfe7/tfe.gresource.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<gresources>
|
||||||
|
<gresource prefix="/com/github/ToshioCP/tfe">
|
||||||
|
<file>tfewindow.ui</file>
|
||||||
|
<file>tfepref.ui</file>
|
||||||
|
<file>tfealert.ui</file>
|
||||||
|
<file>menu.ui</file>
|
||||||
|
</gresource>
|
||||||
|
</gresources>
|
38
src/tfe7/tfealert.c
Normal file
38
src/tfe7/tfealert.c
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
#include "tfealert.h"
|
||||||
|
|
||||||
|
struct _TfeAlert
|
||||||
|
{
|
||||||
|
GtkDialog parent;
|
||||||
|
GtkLabel *lb_alert;
|
||||||
|
GtkButton *btn_accept;
|
||||||
|
};
|
||||||
|
|
||||||
|
G_DEFINE_TYPE (TfeAlert, tfe_alert, GTK_TYPE_DIALOG);
|
||||||
|
|
||||||
|
void
|
||||||
|
tfe_alert_set_message (TfeAlert *alert, const char *message) {
|
||||||
|
gtk_label_set_text (alert->lb_alert, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
tfe_alert_set_button_label (TfeAlert *alert, const char *label) {
|
||||||
|
gtk_button_set_label (alert->btn_accept, label);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
tfe_alert_init (TfeAlert *alert) {
|
||||||
|
gtk_widget_init_template (GTK_WIDGET (alert));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
tfe_alert_class_init (TfeAlertClass *class) {
|
||||||
|
gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class), "/com/github/ToshioCP/tfe/tfealert.ui");
|
||||||
|
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeAlert, lb_alert);
|
||||||
|
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeAlert, btn_accept);
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkWidget *
|
||||||
|
tfe_alert_new (GtkWindow *win) {
|
||||||
|
return GTK_WIDGET (g_object_new (TFE_TYPE_ALERT, "transient-for", win, NULL));
|
||||||
|
}
|
||||||
|
|
19
src/tfe7/tfealert.h
Normal file
19
src/tfe7/tfealert.h
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
#ifndef __TFE_ALERT_H__
|
||||||
|
#define __TFE_ALERT_H__
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
#define TFE_TYPE_ALERT tfe_alert_get_type ()
|
||||||
|
G_DECLARE_FINAL_TYPE (TfeAlert, tfe_alert, TFE, ALERT, GtkDialog)
|
||||||
|
|
||||||
|
void
|
||||||
|
tfe_alert_set_message (TfeAlert *alert, const char *message);
|
||||||
|
|
||||||
|
void
|
||||||
|
tfe_alert_set_button_label (TfeAlert *alert, const char *label);
|
||||||
|
|
||||||
|
GtkWidget *
|
||||||
|
tfe_alert_new (GtkWindow *win);
|
||||||
|
|
||||||
|
#endif /* __TFE_ALERT_H__ */
|
||||||
|
|
47
src/tfe7/tfealert.ui
Normal file
47
src/tfe7/tfealert.ui
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface>
|
||||||
|
<template class="TfeAlert" parent="GtkDialog">
|
||||||
|
<property name="title">Are you sure?</property>
|
||||||
|
<property name="resizable">FALSE</property>
|
||||||
|
<property name="modal">TRUE</property>
|
||||||
|
<child internal-child="content_area">
|
||||||
|
<object class="GtkBox">
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
||||||
|
<property name="spacing">12</property>
|
||||||
|
<property name="margin-start">12</property>
|
||||||
|
<property name="margin-end">12</property>
|
||||||
|
<property name="margin-top">12</property>
|
||||||
|
<property name="margin-bottom">12</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage">
|
||||||
|
<property name="icon-name">dialog-warning</property>
|
||||||
|
<property name="icon-size">GTK_ICON_SIZE_LARGE</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="lb_alert">
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child type="action">
|
||||||
|
<object class="GtkButton" id="btn_cancel">
|
||||||
|
<property name="label">Cancel</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child type="action">
|
||||||
|
<object class="GtkButton" id="btn_accept">
|
||||||
|
<property name="label">Close</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<action-widgets>
|
||||||
|
<action-widget response="cancel" default="true">btn_cancel</action-widget>
|
||||||
|
<action-widget response="accept">btn_accept</action-widget>
|
||||||
|
</action-widgets>
|
||||||
|
</template>
|
||||||
|
</interface>
|
||||||
|
|
64
src/tfe7/tfeapplication.c
Normal file
64
src/tfe7/tfeapplication.c
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
#include "tfewindow.h"
|
||||||
|
|
||||||
|
/* ----- activate, open, startup handlers ----- */
|
||||||
|
static void
|
||||||
|
tfe_activate (GApplication *application) {
|
||||||
|
GtkApplication *app = GTK_APPLICATION (application);
|
||||||
|
GtkWidget *win = GTK_WIDGET (gtk_application_get_active_window (app));
|
||||||
|
|
||||||
|
tfe_window_notebook_page_new (win);
|
||||||
|
gtk_widget_show (GTK_WIDGET (win));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
tfe_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) {
|
||||||
|
GtkApplication *app = GTK_APPLICATION (application);
|
||||||
|
GtkWidget *win = GTK_WIDGET (gtk_application_get_active_window (app));
|
||||||
|
|
||||||
|
tfe_window_notebook_page_new_with_files (win, files, n_files);
|
||||||
|
gtk_widget_show (win);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
tfe_startup (GApplication *application) {
|
||||||
|
GtkApplication *app = GTK_APPLICATION (application);
|
||||||
|
GtkBuilder *build;
|
||||||
|
TfeWindow *win;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
win = tfe_window_new (app);
|
||||||
|
|
||||||
|
/* ----- accelerator ----- */
|
||||||
|
struct {
|
||||||
|
const char *action;
|
||||||
|
const char *accels[2];
|
||||||
|
} action_accels[] = {
|
||||||
|
{ "win.open", { "<Control>o", NULL } },
|
||||||
|
{ "win.save", { "<Control>s", NULL } },
|
||||||
|
{ "win.close", { "<Control>w", NULL } },
|
||||||
|
{ "win.new", { "<Control>n", NULL } },
|
||||||
|
{ "win.saveas", { "<Shift><Control>s", NULL } },
|
||||||
|
{ "win.close-all", { "<Control>q", NULL } },
|
||||||
|
};
|
||||||
|
|
||||||
|
for (i = 0; i < G_N_ELEMENTS(action_accels); i++)
|
||||||
|
gtk_application_set_accels_for_action(GTK_APPLICATION(app), action_accels[i].action, action_accels[i].accels);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----- main ----- */
|
||||||
|
int
|
||||||
|
main (int argc, char **argv) {
|
||||||
|
GtkApplication *app;
|
||||||
|
int stat;
|
||||||
|
|
||||||
|
app = gtk_application_new ("com.github.ToshioCP.tfe", G_APPLICATION_HANDLES_OPEN);
|
||||||
|
|
||||||
|
g_signal_connect (app, "startup", G_CALLBACK (tfe_startup), NULL);
|
||||||
|
g_signal_connect (app, "activate", G_CALLBACK (tfe_activate), NULL);
|
||||||
|
g_signal_connect (app, "open", G_CALLBACK (tfe_open), NULL);
|
||||||
|
|
||||||
|
stat =g_application_run (G_APPLICATION (app), argc, argv);
|
||||||
|
g_object_unref (app);
|
||||||
|
return stat;
|
||||||
|
}
|
||||||
|
|
211
src/tfe7/tfenotebook.c
Normal file
211
src/tfe7/tfenotebook.c
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
#include "tfenotebook.h"
|
||||||
|
#include "../tfetextview/tfetextview.h"
|
||||||
|
|
||||||
|
/* The returned string should be freed with g_free() when no longer needed. */
|
||||||
|
static gchar*
|
||||||
|
get_untitled () {
|
||||||
|
static int c = -1;
|
||||||
|
if (++c == 0)
|
||||||
|
return g_strdup_printf("Untitled");
|
||||||
|
else
|
||||||
|
return g_strdup_printf ("Untitled%u", c);
|
||||||
|
}
|
||||||
|
|
||||||
|
static TfeTextView *
|
||||||
|
get_current_textview (GtkNotebook *nb) {
|
||||||
|
int i;
|
||||||
|
GtkWidget *scr;
|
||||||
|
GtkWidget *tv;
|
||||||
|
|
||||||
|
i = gtk_notebook_get_current_page (nb);
|
||||||
|
scr = gtk_notebook_get_nth_page (nb, i);
|
||||||
|
tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
|
||||||
|
return TFE_TEXT_VIEW (tv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
file_changed_cb (TfeTextView *tv) {
|
||||||
|
GtkWidget *nb = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_NOTEBOOK);
|
||||||
|
GtkWidget *scr;
|
||||||
|
GtkWidget *label;
|
||||||
|
GFile *file;
|
||||||
|
char *filename;
|
||||||
|
|
||||||
|
if (! GTK_IS_NOTEBOOK (nb)) /* tv not connected to nb yet */
|
||||||
|
return;
|
||||||
|
file = tfe_text_view_get_file (tv);
|
||||||
|
scr = gtk_widget_get_parent (GTK_WIDGET (tv));
|
||||||
|
if (G_IS_FILE (file)) {
|
||||||
|
filename = g_file_get_basename (file);
|
||||||
|
g_object_unref (file);
|
||||||
|
} else
|
||||||
|
filename = get_untitled ();
|
||||||
|
label = gtk_label_new (filename);
|
||||||
|
gtk_notebook_set_tab_label (GTK_NOTEBOOK (nb), scr, label);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
modified_changed_cb (GtkTextBuffer *tb, gpointer user_data) {
|
||||||
|
TfeTextView *tv = TFE_TEXT_VIEW (user_data);
|
||||||
|
GtkWidget *scr = gtk_widget_get_parent (GTK_WIDGET (tv));
|
||||||
|
GtkWidget *nb = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_NOTEBOOK);
|
||||||
|
GtkWidget *label;
|
||||||
|
const char *filename;
|
||||||
|
char *text;
|
||||||
|
|
||||||
|
if (! GTK_IS_NOTEBOOK (nb)) /* tv not connected to nb yet */
|
||||||
|
return;
|
||||||
|
else if (gtk_text_buffer_get_modified (tb)) {
|
||||||
|
filename = gtk_notebook_get_tab_label_text (GTK_NOTEBOOK (nb), scr);
|
||||||
|
text = g_strdup_printf ("*%s", filename);
|
||||||
|
label = gtk_label_new (text);
|
||||||
|
gtk_notebook_set_tab_label (GTK_NOTEBOOK (nb), scr, label);
|
||||||
|
} else
|
||||||
|
file_changed_cb (tv);
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
has_saved (GtkNotebook *nb) {
|
||||||
|
g_return_val_if_fail (GTK_IS_NOTEBOOK (nb), false);
|
||||||
|
|
||||||
|
TfeTextView *tv;
|
||||||
|
GtkTextBuffer *tb;
|
||||||
|
|
||||||
|
tv = get_current_textview (nb);
|
||||||
|
tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
||||||
|
if (gtk_text_buffer_get_modified (tb))
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
has_saved_all (GtkNotebook *nb) {
|
||||||
|
g_return_val_if_fail (GTK_IS_NOTEBOOK (nb), false);
|
||||||
|
|
||||||
|
int i, n;
|
||||||
|
GtkWidget *scr;
|
||||||
|
GtkWidget *tv;
|
||||||
|
GtkTextBuffer *tb;
|
||||||
|
|
||||||
|
n = gtk_notebook_get_n_pages (nb);
|
||||||
|
for (i = 0; i < n; ++i) {
|
||||||
|
scr = gtk_notebook_get_nth_page (nb, i);
|
||||||
|
tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
|
||||||
|
tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
||||||
|
if (gtk_text_buffer_get_modified (tb))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
notebook_page_save (GtkNotebook *nb) {
|
||||||
|
g_return_if_fail(GTK_IS_NOTEBOOK (nb));
|
||||||
|
|
||||||
|
TfeTextView *tv;
|
||||||
|
|
||||||
|
tv = get_current_textview (nb);
|
||||||
|
tfe_text_view_save (TFE_TEXT_VIEW (tv));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
notebook_page_saveas (GtkNotebook *nb) {
|
||||||
|
g_return_if_fail(GTK_IS_NOTEBOOK (nb));
|
||||||
|
|
||||||
|
TfeTextView *tv;
|
||||||
|
|
||||||
|
tv = get_current_textview (nb);
|
||||||
|
tfe_text_view_saveas (TFE_TEXT_VIEW (tv));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
notebook_page_close (GtkNotebook *nb) {
|
||||||
|
g_return_if_fail(GTK_IS_NOTEBOOK (nb));
|
||||||
|
|
||||||
|
GtkWidget *win;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (gtk_notebook_get_n_pages (nb) == 1) {
|
||||||
|
win = gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW);
|
||||||
|
gtk_window_destroy (GTK_WINDOW (win));
|
||||||
|
} else {
|
||||||
|
i = gtk_notebook_get_current_page (nb);
|
||||||
|
gtk_notebook_remove_page (GTK_NOTEBOOK (nb), i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) {
|
||||||
|
GtkWidget *scr = gtk_scrolled_window_new ();
|
||||||
|
GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
||||||
|
GtkNotebookPage *nbp;
|
||||||
|
GtkWidget *lab;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
|
||||||
|
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
|
||||||
|
lab = gtk_label_new (filename);
|
||||||
|
i = gtk_notebook_append_page (nb, scr, lab);
|
||||||
|
nbp = gtk_notebook_get_page (nb, scr);
|
||||||
|
g_object_set (nbp, "tab-expand", TRUE, NULL);
|
||||||
|
gtk_notebook_set_current_page (nb, i);
|
||||||
|
g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed_cb), NULL);
|
||||||
|
g_signal_connect (tb, "modified-changed", G_CALLBACK (modified_changed_cb), tv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
open_response (TfeTextView *tv, int response, GtkNotebook *nb) {
|
||||||
|
GFile *file;
|
||||||
|
char *filename;
|
||||||
|
|
||||||
|
if (response != TFE_OPEN_RESPONSE_SUCCESS) {
|
||||||
|
g_object_ref_sink (tv);
|
||||||
|
g_object_unref (tv);
|
||||||
|
}else if (! G_IS_FILE (file = tfe_text_view_get_file (tv))) {
|
||||||
|
g_object_ref_sink (tv);
|
||||||
|
g_object_unref (tv);
|
||||||
|
}else {
|
||||||
|
filename = g_file_get_basename (file);
|
||||||
|
g_object_unref (file);
|
||||||
|
notebook_page_build (nb, GTK_WIDGET (tv), filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
notebook_page_open (GtkNotebook *nb) {
|
||||||
|
g_return_if_fail(GTK_IS_NOTEBOOK (nb));
|
||||||
|
|
||||||
|
GtkWidget *tv;
|
||||||
|
|
||||||
|
tv = tfe_text_view_new ();
|
||||||
|
g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
|
||||||
|
tfe_text_view_open (TFE_TEXT_VIEW (tv), gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
notebook_page_new_with_file (GtkNotebook *nb, GFile *file) {
|
||||||
|
g_return_if_fail(GTK_IS_NOTEBOOK (nb));
|
||||||
|
g_return_if_fail(G_IS_FILE (file));
|
||||||
|
|
||||||
|
GtkWidget *tv;
|
||||||
|
char *filename;
|
||||||
|
|
||||||
|
if ((tv = tfe_text_view_new_with_file (file)) == NULL)
|
||||||
|
return; /* read error */
|
||||||
|
filename = g_file_get_basename (file);
|
||||||
|
notebook_page_build (nb, tv, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
notebook_page_new (GtkNotebook *nb) {
|
||||||
|
g_return_if_fail(GTK_IS_NOTEBOOK (nb));
|
||||||
|
|
||||||
|
GtkWidget *tv;
|
||||||
|
char *filename;
|
||||||
|
|
||||||
|
tv = tfe_text_view_new ();
|
||||||
|
filename = get_untitled ();
|
||||||
|
notebook_page_build (nb, tv, filename);
|
||||||
|
}
|
||||||
|
|
31
src/tfe7/tfenotebook.h
Normal file
31
src/tfe7/tfenotebook.h
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
#ifndef __TFE_NOTEBOOK_H__
|
||||||
|
#define __TFE_NOTEBOOK_H__
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
has_saved (GtkNotebook *nb);
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
has_saved_all (GtkNotebook *nb);
|
||||||
|
|
||||||
|
void
|
||||||
|
notebook_page_save(GtkNotebook *nb);
|
||||||
|
|
||||||
|
void
|
||||||
|
notebook_page_saveas(GtkNotebook *nb);
|
||||||
|
|
||||||
|
void
|
||||||
|
notebook_page_close (GtkNotebook *nb);
|
||||||
|
|
||||||
|
void
|
||||||
|
notebook_page_open (GtkNotebook *nb);
|
||||||
|
|
||||||
|
void
|
||||||
|
notebook_page_new_with_file (GtkNotebook *nb, GFile *file);
|
||||||
|
|
||||||
|
void
|
||||||
|
notebook_page_new (GtkNotebook *nb);
|
||||||
|
|
||||||
|
#endif /* __TFE_NOTEBOOK_H__ */
|
||||||
|
|
40
src/tfe7/tfepref.c
Normal file
40
src/tfe7/tfepref.c
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
#include "tfepref.h"
|
||||||
|
|
||||||
|
struct _TfePref
|
||||||
|
{
|
||||||
|
GtkDialog parent;
|
||||||
|
GSettings *settings;
|
||||||
|
GtkFontButton *fontbtn;
|
||||||
|
};
|
||||||
|
|
||||||
|
G_DEFINE_TYPE (TfePref, tfe_pref, GTK_TYPE_DIALOG);
|
||||||
|
|
||||||
|
static void
|
||||||
|
tfe_pref_dispose (GObject *gobject) {
|
||||||
|
TfePref *pref = TFE_PREF (gobject);
|
||||||
|
|
||||||
|
g_clear_object (&pref->settings);
|
||||||
|
G_OBJECT_CLASS (tfe_pref_parent_class)->dispose (gobject);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
tfe_pref_init (TfePref *pref) {
|
||||||
|
gtk_widget_init_template (GTK_WIDGET (pref));
|
||||||
|
pref->settings = g_settings_new ("com.github.ToshioCP.tfe");
|
||||||
|
g_settings_bind (pref->settings, "font", pref->fontbtn, "font", G_SETTINGS_BIND_DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
tfe_pref_class_init (TfePrefClass *class) {
|
||||||
|
GObjectClass *object_class = G_OBJECT_CLASS (class);
|
||||||
|
|
||||||
|
object_class->dispose = tfe_pref_dispose;
|
||||||
|
gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class), "/com/github/ToshioCP/tfe/tfepref.ui");
|
||||||
|
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfePref, fontbtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkWidget *
|
||||||
|
tfe_pref_new (GtkWindow *win) {
|
||||||
|
return GTK_WIDGET (g_object_new (TFE_TYPE_PREF, "transient-for", win, NULL));
|
||||||
|
}
|
||||||
|
|
13
src/tfe7/tfepref.h
Normal file
13
src/tfe7/tfepref.h
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#ifndef __TFE_PREF_H__
|
||||||
|
#define __TFE_PREF_H__
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
#define TFE_TYPE_PREF tfe_pref_get_type ()
|
||||||
|
G_DECLARE_FINAL_TYPE (TfePref, tfe_pref, TFE, PREF, GtkDialog)
|
||||||
|
|
||||||
|
GtkWidget *
|
||||||
|
tfe_pref_new (GtkWindow *win);
|
||||||
|
|
||||||
|
#endif /* __TFE_PREF_H__ */
|
||||||
|
|
33
src/tfe7/tfepref.ui
Normal file
33
src/tfe7/tfepref.ui
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface>
|
||||||
|
<template class="TfePref" parent="GtkDialog">
|
||||||
|
<property name="title">Preferences</property>
|
||||||
|
<property name="resizable">FALSE</property>
|
||||||
|
<property name="modal">TRUE</property>
|
||||||
|
<child internal-child="content_area">
|
||||||
|
<object class="GtkBox" id="content_area">
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="pref_boxh">
|
||||||
|
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
||||||
|
<property name="spacing">12</property>
|
||||||
|
<property name="margin-start">12</property>
|
||||||
|
<property name="margin-end">12</property>
|
||||||
|
<property name="margin-top">12</property>
|
||||||
|
<property name="margin-bottom">12</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="fontlabel">
|
||||||
|
<property name="label">Font:</property>
|
||||||
|
<property name="xalign">1</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkFontButton" id="fontbtn">
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</template>
|
||||||
|
</interface>
|
||||||
|
|
213
src/tfe7/tfewindow.c
Normal file
213
src/tfe7/tfewindow.c
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
#include "tfewindow.h"
|
||||||
|
#include "tfenotebook.h"
|
||||||
|
#include "tfepref.h"
|
||||||
|
#include "tfealert.h"
|
||||||
|
#include "css.h"
|
||||||
|
|
||||||
|
struct _TfeWindow {
|
||||||
|
GtkApplicationWindow parent;
|
||||||
|
GtkButton *btno;
|
||||||
|
GtkButton *btns;
|
||||||
|
GtkButton *btnc;
|
||||||
|
GtkMenuButton *btnm;
|
||||||
|
GtkNotebook *nb;
|
||||||
|
GSettings *settings;
|
||||||
|
gboolean is_quit;
|
||||||
|
};
|
||||||
|
|
||||||
|
G_DEFINE_TYPE (TfeWindow, tfe_window, GTK_TYPE_APPLICATION_WINDOW);
|
||||||
|
|
||||||
|
/* alert response signal handler */
|
||||||
|
static void
|
||||||
|
alert_response_cb (GtkDialog *alert, int response_id, gpointer user_data) {
|
||||||
|
TfeWindow *win = TFE_WINDOW (user_data);
|
||||||
|
|
||||||
|
if (response_id == GTK_RESPONSE_ACCEPT) {
|
||||||
|
if (win->is_quit)
|
||||||
|
gtk_window_destroy(GTK_WINDOW (win));
|
||||||
|
else
|
||||||
|
notebook_page_close (win->nb);
|
||||||
|
}
|
||||||
|
gtk_window_destroy (GTK_WINDOW (alert));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----- button handlers ----- */
|
||||||
|
void
|
||||||
|
open_cb (GtkNotebook *nb) {
|
||||||
|
notebook_page_open (nb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
save_cb (GtkNotebook *nb) {
|
||||||
|
notebook_page_save (nb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
close_cb (GtkNotebook *nb) {
|
||||||
|
TfeAlert *alert;
|
||||||
|
TfeWindow *win = TFE_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (nb), TFE_TYPE_WINDOW));
|
||||||
|
|
||||||
|
if (has_saved (nb))
|
||||||
|
notebook_page_close (nb);
|
||||||
|
else {
|
||||||
|
win->is_quit = false;
|
||||||
|
alert = TFE_ALERT (tfe_alert_new (GTK_WINDOW (win)));
|
||||||
|
tfe_alert_set_message (alert, "Contents aren't saved yet.\nAre you sure to close?");
|
||||||
|
tfe_alert_set_button_label (alert, "Close");
|
||||||
|
g_signal_connect (GTK_DIALOG (alert), "response", G_CALLBACK (alert_response_cb), win);
|
||||||
|
gtk_widget_show (GTK_WIDGET (alert));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----- action activated handlers ----- */
|
||||||
|
static void
|
||||||
|
open_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
|
||||||
|
TfeWindow *win = TFE_WINDOW (user_data);
|
||||||
|
|
||||||
|
open_cb (GTK_NOTEBOOK (win->nb));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
save_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
|
||||||
|
TfeWindow *win = TFE_WINDOW (user_data);
|
||||||
|
|
||||||
|
save_cb (GTK_NOTEBOOK (win->nb));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
close_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
|
||||||
|
TfeWindow *win = TFE_WINDOW (user_data);
|
||||||
|
|
||||||
|
close_cb (GTK_NOTEBOOK (win->nb));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
new_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
|
||||||
|
TfeWindow *win = TFE_WINDOW (user_data);
|
||||||
|
|
||||||
|
notebook_page_new (GTK_NOTEBOOK (win->nb));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
saveas_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
|
||||||
|
TfeWindow *win = TFE_WINDOW (user_data);
|
||||||
|
|
||||||
|
notebook_page_saveas (GTK_NOTEBOOK (win->nb));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
pref_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
|
||||||
|
TfeWindow *win = TFE_WINDOW (user_data);
|
||||||
|
GtkWidget *pref;
|
||||||
|
|
||||||
|
pref = tfe_pref_new (GTK_WINDOW (win));
|
||||||
|
gtk_widget_show (pref);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
quit_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
|
||||||
|
TfeWindow *win = TFE_WINDOW (user_data);
|
||||||
|
|
||||||
|
TfeAlert *alert;
|
||||||
|
|
||||||
|
if (has_saved_all (GTK_NOTEBOOK (win->nb)))
|
||||||
|
gtk_window_destroy (GTK_WINDOW (win));
|
||||||
|
else {
|
||||||
|
win->is_quit = true;
|
||||||
|
alert = TFE_ALERT (tfe_alert_new (GTK_WINDOW (win)));
|
||||||
|
tfe_alert_set_message (alert, "Contents aren't saved yet.\nAre you sure to quit?");
|
||||||
|
tfe_alert_set_button_label (alert, "Quit");
|
||||||
|
g_signal_connect (GTK_DIALOG (alert), "response", G_CALLBACK (alert_response_cb), win);
|
||||||
|
gtk_widget_show (GTK_WIDGET (alert));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* gsettings changed::font signal handler */
|
||||||
|
static void
|
||||||
|
changed_font_cb (GSettings *settings, char *key, gpointer user_data) {
|
||||||
|
GtkWindow *win = GTK_WINDOW (user_data);
|
||||||
|
const char *font;
|
||||||
|
PangoFontDescription *pango_font_desc;
|
||||||
|
|
||||||
|
font = g_settings_get_string (settings, "font");
|
||||||
|
pango_font_desc = pango_font_description_from_string (font);
|
||||||
|
set_font_for_display_with_pango_font_desc (win, pango_font_desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- public functions --- */
|
||||||
|
|
||||||
|
void
|
||||||
|
tfe_window_notebook_page_new (TfeWindow *win) {
|
||||||
|
notebook_page_new (win->nb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
tfe_window_notebook_page_new_with_files (TfeWindow *win, GFile **files, int n_files) {
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < n_files; i++)
|
||||||
|
notebook_page_new_with_file (win->nb, files[i]);
|
||||||
|
if (gtk_notebook_get_n_pages (win->nb) == 0)
|
||||||
|
notebook_page_new (win->nb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- TfeWindow object construction/destruction --- */
|
||||||
|
static void
|
||||||
|
tfe_window_dispose (GObject *gobject) {
|
||||||
|
TfeWindow *window = TFE_WINDOW (gobject);
|
||||||
|
|
||||||
|
g_clear_object (&window->settings);
|
||||||
|
G_OBJECT_CLASS (tfe_window_parent_class)->dispose (gobject);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
tfe_window_init (TfeWindow *win) {
|
||||||
|
GtkBuilder *build;
|
||||||
|
GMenuModel *menu;
|
||||||
|
|
||||||
|
gtk_widget_init_template (GTK_WIDGET (win));
|
||||||
|
|
||||||
|
build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe/menu.ui");
|
||||||
|
menu = G_MENU_MODEL (gtk_builder_get_object (build, "menu"));
|
||||||
|
gtk_menu_button_set_menu_model (win->btnm, menu);
|
||||||
|
g_object_unref(build);
|
||||||
|
|
||||||
|
win->settings = g_settings_new ("com.github.ToshioCP.tfe");
|
||||||
|
g_signal_connect (win->settings, "changed::font", G_CALLBACK (changed_font_cb), win);
|
||||||
|
|
||||||
|
/* ----- action ----- */
|
||||||
|
const GActionEntry win_entries[] = {
|
||||||
|
{ "open", open_activated, NULL, NULL, NULL },
|
||||||
|
{ "save", save_activated, NULL, NULL, NULL },
|
||||||
|
{ "close", close_activated, NULL, NULL, NULL },
|
||||||
|
{ "new", new_activated, NULL, NULL, NULL },
|
||||||
|
{ "saveas", saveas_activated, NULL, NULL, NULL },
|
||||||
|
{ "pref", pref_activated, NULL, NULL, NULL },
|
||||||
|
{ "close-all", quit_activated, NULL, NULL, NULL }
|
||||||
|
};
|
||||||
|
g_action_map_add_action_entries (G_ACTION_MAP (win), win_entries, G_N_ELEMENTS (win_entries), win);
|
||||||
|
|
||||||
|
changed_font_cb(win->settings, "font", win);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
tfe_window_class_init (TfeWindowClass *class) {
|
||||||
|
GObjectClass *object_class = G_OBJECT_CLASS (class);
|
||||||
|
|
||||||
|
object_class->dispose = tfe_window_dispose;
|
||||||
|
gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class), "/com/github/ToshioCP/tfe/tfewindow.ui");
|
||||||
|
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeWindow, btno);
|
||||||
|
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeWindow, btns);
|
||||||
|
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeWindow, btnc);
|
||||||
|
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeWindow, btnm);
|
||||||
|
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeWindow, nb);
|
||||||
|
gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), open_cb);
|
||||||
|
gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), save_cb);
|
||||||
|
gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), close_cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkWidget *
|
||||||
|
tfe_window_new (GtkApplication *app) {
|
||||||
|
return GTK_WIDGET (g_object_new (TFE_TYPE_WINDOW, "application", app, NULL));
|
||||||
|
}
|
||||||
|
|
19
src/tfe7/tfewindow.h
Normal file
19
src/tfe7/tfewindow.h
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
#ifndef __TFE_WINDOW_H__
|
||||||
|
#define __TFE_WINDOW_H__
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
#define TFE_TYPE_WINDOW tfe_window_get_type ()
|
||||||
|
G_DECLARE_FINAL_TYPE (TfeWindow, tfe_window, TFE, WINDOW, GtkApplicationWindow)
|
||||||
|
|
||||||
|
void
|
||||||
|
tfe_window_notebook_page_new (TfeWindow *win);
|
||||||
|
|
||||||
|
void
|
||||||
|
tfe_window_notebook_page_new_with_files (TfeWindow *win, GFile **files, int n_files);
|
||||||
|
|
||||||
|
GtkWidget *
|
||||||
|
tfe_window_new (GtkApplication *app);
|
||||||
|
|
||||||
|
#endif /* __TFE_WINDOW_H__ */
|
||||||
|
|
65
src/tfe7/tfewindow.ui
Normal file
65
src/tfe7/tfewindow.ui
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface>
|
||||||
|
<template class="TfeWindow" parent="GtkApplicationWindow">
|
||||||
|
<property name="title">file editor</property>
|
||||||
|
<property name="default-width">600</property>
|
||||||
|
<property name="default-height">400</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="boxv">
|
||||||
|
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="boxh">
|
||||||
|
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="dmy1">
|
||||||
|
<property name="width-chars">10</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="btno">
|
||||||
|
<property name="label">Open</property>
|
||||||
|
<signal name="clicked" handler="open_cb" swapped="TRUE" object="nb"></signal>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="btns">
|
||||||
|
<property name="label">Save</property>
|
||||||
|
<signal name="clicked" handler="save_cb" swapped="TRUE" object="nb"></signal>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="dmy2">
|
||||||
|
<property name="hexpand">TRUE</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="btnc">
|
||||||
|
<property name="label">Close</property>
|
||||||
|
<signal name="clicked" handler="close_cb" swapped="TRUE" object="nb"></signal>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkMenuButton" id="btnm">
|
||||||
|
<property name="direction">down</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="dmy3">
|
||||||
|
<property name="width-chars">10</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkNotebook" id="nb">
|
||||||
|
<property name="scrollable">TRUE</property>
|
||||||
|
<property name="hexpand">TRUE</property>
|
||||||
|
<property name="vexpand">TRUE</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</template>
|
||||||
|
</interface>
|
||||||
|
|
Loading…
Add table
Reference in a new issue