Add tfe6 and insert new section19. Bug fixed.

This commit is contained in:
Toshio Sekiya 2021-02-17 23:59:50 +09:00
parent 62d2fe46ba
commit 8e788b0749
49 changed files with 4880 additions and 1844 deletions

2
.gitignore vendored
View file

@ -12,8 +12,10 @@ src/tfv/a.out
src/tfe/a.out src/tfe/a.out
src/tfe/hello.txt src/tfe/hello.txt
src/tfe/resources.c src/tfe/resources.c
src/tfe4/_build
src/tfe5/_build src/tfe5/_build
src/tfe5/hello.txt src/tfe5/hello.txt
src/tfe6/_build
src/menu/a.out src/menu/a.out
src/color/_build src/color/_build
src/turtle/_build src/turtle/_build

View file

@ -32,5 +32,6 @@ 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. [GtkDrawingArea and Cairo](gfm/sec19.md) 1. [Upgrade text file editor](gfm/sec19.md)
1. [Combine GtkDrawingArea and TfeTextView](gfm/sec20.md) 1. [GtkDrawingArea and Cairo](gfm/sec20.md)
1. [Combine GtkDrawingArea and TfeTextView](gfm/sec21.md)

View file

@ -372,7 +372,7 @@ Then C destructs itself and finally the memories allocated to C is freed.
The idea above is based on an assumption that an object referred by nothing has reference count of zero. The idea above is based on an assumption that an object referred by nothing has reference count of zero.
When the reference count drops to zero, the object starts its destruction process. When the reference count drops to zero, the object starts its destruction process.
The destruction process is spitted into two phases: disposing and finalizing. The destruction process is split into two phases: disposing and finalizing.
In the disposing process, the object invokes the function pointed by `dispose` in its class to release all references to other objects. In the disposing process, the object invokes the function pointed by `dispose` in its class to release all references to other objects.
In the finalizing process, it invokes the function pointed by `finalize` in its class to complete the destruction process. In the finalizing process, it invokes the function pointed by `finalize` in its class to complete the destruction process.
These functions are also called handlers or methods. These functions are also called handlers or methods.

View file

@ -158,73 +158,57 @@ The error is managed only in the TfeTextView instance and no information is noti
2 saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) { 2 saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
3 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); 3 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
4 GFile *file; 4 GFile *file;
5 5 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
6 if (response == GTK_RESPONSE_ACCEPT) { 6
7 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); 7 gtk_window_destroy (GTK_WINDOW (dialog));
8 if (G_IS_FILE(file)) { 8 if (response == GTK_RESPONSE_ACCEPT) {
9 if (G_IS_FILE (tv->file)) 9 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
10 g_object_unref (tv->file); 10 if (! G_IS_FILE (file))
11 tv->file = file; 11 g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n");
12 gtk_text_buffer_set_modified (tb, TRUE); 12 else {
13 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0); 13 save_file(file, tb, GTK_WINDOW (win));
14 tfe_text_view_save (TFE_TEXT_VIEW (tv)); 14 if (G_IS_FILE (tv->file))
15 } 15 g_object_unref (tv->file);
16 } 16 tv->file = file;
17 gtk_window_destroy (GTK_WINDOW (dialog)); 17 gtk_text_buffer_set_modified (tb, FALSE);
18 } 18 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
19 19 }
20 void 20 }
21 tfe_text_view_save (TfeTextView *tv) { 21 }
22 g_return_if_fail (TFE_IS_TEXT_VIEW (tv)); 22
23 23 void
24 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); 24 tfe_text_view_save (TfeTextView *tv) {
25 GtkTextIter start_iter; 25 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
26 GtkTextIter end_iter; 26
27 gchar *contents; 27 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
28 GtkWidget *message_dialog; 28 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
29 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW); 29
30 GError *err = NULL; 30 if (! gtk_text_buffer_get_modified (tb))
31 31 return; /* no need to save it */
32 if (! gtk_text_buffer_get_modified (tb)) 32 else if (tv->file == NULL)
33 return; /* no need to save it */ 33 tfe_text_view_saveas (tv);
34 else if (tv->file == NULL) 34 else if (! G_IS_FILE (tv->file))
35 tfe_text_view_saveas (tv); 35 g_error ("TfeTextView: The pointer in this object isn't NULL nor GFile object.\n");
36 else { 36 else {
37 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter); 37 if (save_file (tv->file, tb, GTK_WINDOW (win)))
38 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE); 38 gtk_text_buffer_set_modified (tb, FALSE);
39 if (g_file_replace_contents (tv->file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err)) 39 }
40 gtk_text_buffer_set_modified (tb, FALSE); 40 }
41 else { 41
42 /* It is possible that tv->file is broken or you don't have permission to write. */ 42 void
43 /* It is a good idea to set tv->file to NULL. */ 43 tfe_text_view_saveas (TfeTextView *tv) {
44 if (G_IS_FILE (tv->file)) 44 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
45 g_object_unref (tv->file); 45
46 tv->file =NULL; 46 GtkWidget *dialog;
47 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0); 47 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
48 message_dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL, 48
49 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, 49 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
50 "%s.\n", err->message); 50 "Cancel", GTK_RESPONSE_CANCEL,
51 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL); 51 "Save", GTK_RESPONSE_ACCEPT,
52 gtk_widget_show (message_dialog); 52 NULL);
53 g_error_free (err); 53 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
54 } 54 gtk_widget_show (dialog);
55 } 55 }
56 }
57
58 void
59 tfe_text_view_saveas (TfeTextView *tv) {
60 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
61
62 GtkWidget *dialog;
63 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
64
65 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
66 "Cancel", GTK_RESPONSE_CANCEL,
67 "Save", GTK_RESPONSE_ACCEPT,
68 NULL);
69 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
70 gtk_widget_show (dialog);
71 }
~~~ ~~~
- 20-56: `Tfe_text_view_save` function. - 20-56: `Tfe_text_view_save` function.
@ -300,45 +284,46 @@ Otherwise probably bad things will happen.
9 9
10 if (response != GTK_RESPONSE_ACCEPT) 10 if (response != GTK_RESPONSE_ACCEPT)
11 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL); 11 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
12 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)))) 12 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)))) {
13 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR); 13 g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n");
14 else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */ 14 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
15 if (G_IS_FILE (file)) 15 } else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
16 g_object_unref (file); 16 if (G_IS_FILE (file))
17 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL, 17 g_object_unref (file);
18 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, 18 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
19 "%s.\n", err->message); 19 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
20 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL); 20 "%s.\n", err->message);
21 gtk_widget_show (message_dialog); 21 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
22 g_error_free (err); 22 gtk_widget_show (message_dialog);
23 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR); 23 g_error_free (err);
24 } else { 24 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
25 gtk_text_buffer_set_text (tb, contents, length); 25 } else {
26 g_free (contents); 26 gtk_text_buffer_set_text (tb, contents, length);
27 if (G_IS_FILE (tv->file)) 27 g_free (contents);
28 g_object_unref (tv->file); 28 if (G_IS_FILE (tv->file))
29 tv->file = file; 29 g_object_unref (tv->file);
30 gtk_text_buffer_set_modified (tb, FALSE); 30 tv->file = file;
31 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS); 31 gtk_text_buffer_set_modified (tb, FALSE);
32 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0); 32 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
33 } 33 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
34 gtk_window_destroy (GTK_WINDOW (dialog)); 34 }
35 } 35 gtk_window_destroy (GTK_WINDOW (dialog));
36 36 }
37 void 37
38 tfe_text_view_open (TfeTextView *tv, GtkWidget *win) { 38 void
39 g_return_if_fail (TFE_IS_TEXT_VIEW (tv)); 39 tfe_text_view_open (TfeTextView *tv, GtkWidget *win) {
40 g_return_if_fail (GTK_IS_WINDOW (win)); 40 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
41 41 g_return_if_fail (GTK_IS_WINDOW (win));
42 GtkWidget *dialog; 42
43 43 GtkWidget *dialog;
44 dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN, 44
45 "Cancel", GTK_RESPONSE_CANCEL, 45 dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN,
46 "Open", GTK_RESPONSE_ACCEPT, 46 "Cancel", GTK_RESPONSE_CANCEL,
47 NULL); 47 "Open", GTK_RESPONSE_ACCEPT,
48 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv); 48 NULL);
49 gtk_widget_show (dialog); 49 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
50 } 50 gtk_widget_show (dialog);
51 }
~~~ ~~~
- 37-50: `tfe_text_view_open` function. - 37-50: `tfe_text_view_open` function.
@ -384,8 +369,11 @@ However, in Gtk4, `gtk_dialog_run`is unavailable any more.
2 tfe_text_view_get_file (TfeTextView *tv) { 2 tfe_text_view_get_file (TfeTextView *tv) {
3 g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL); 3 g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);
4 4
5 return g_file_dup (tv->file); 5 if (G_IS_FILE (tv->file))
6 } 6 return g_file_dup (tv->file);
7 else
8 return NULL;
9 }
~~~ ~~~
The important thing is to duplicate `tv->file`. The important thing is to duplicate `tv->file`.

View file

@ -4,7 +4,7 @@ Up: [Readme.md](../Readme.md), Prev: [Section 12](sec12.md), Next: [Section 14]
GtkNotebook is a very important object in the text file editor `tfe`. GtkNotebook is a very important object in the text file editor `tfe`.
It connects the application and TfeTextView objects. It connects the application and TfeTextView objects.
A set of functions related to GtkNotebook are declared in `tfenotebook.h`. A set of public functions related to GtkNotebook are declared in `tfenotebook.h`.
The word "tfenotebook" is used only in filenames. The word "tfenotebook" is used only in filenames.
There's no "TfeNotebook" object. There's no "TfeNotebook" object.
@ -13,30 +13,34 @@ There's no "TfeNotebook" object.
2 notebook_page_save(GtkNotebook *nb); 2 notebook_page_save(GtkNotebook *nb);
3 3
4 void 4 void
5 notebook_page_open (GtkNotebook *nb); 5 notebook_page_close (GtkNotebook *nb);
6 6
7 void 7 void
8 notebook_page_new_with_file (GtkNotebook *nb, GFile *file); 8 notebook_page_open (GtkNotebook *nb);
9 9
10 void 10 void
11 notebook_page_new (GtkNotebook *nb); 11 notebook_page_new_with_file (GtkNotebook *nb, GFile *file);
12 12
13 void
14 notebook_page_new (GtkNotebook *nb);
15
~~~ ~~~
This header file describes the public functions in `tfenotebook.c`. This header file describes the public functions in `tfenotebook.c`.
- 10-11: The function `notebook_page_new` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView to the page. - 1-2: `notebook_page_save` saves the current page to the file of which the name specified in the tab.
- 7-8: The function `notebook_page_new_with_file` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView to the page. A file is read and inserted into GtkTextBuffer. If the name is `untitled` or `untitled` followed by digits, FileChooserDialog appears and a user can choose or specify a filename.
The GFile `file` is copied and inserted to the TfeTextView object. - 4-5: `notebook_page_close` closes the current page.
- 4-5: `notebook_page_open` shows a file chooser dialog. Then, user chooses a file and the file is inserted into GtkTextBuffer. - 7-8: `notebook_page_open` shows a file chooser dialog and a user can choose a file. The file is inserted to a new page.
- 1-2: `notebook_page_save` saves the contents in GtkTextBuffer into the file, which is got from the TfeTextView. - 10-11: `notebook_page_new_with_file` generates a new page and the file give as an argument is read and inserted into the page.
- 13-14: `notebook_page_new` generates a new empty page.
You probably find that the functions above are higher level functions of You probably find that the functions except `notebook_page_close` are higher level functions of
- `tfe_text_view_new`
- `tfe_text_view_new_with_file`
- `tef_text_view_open`
- `tfe_text_view_save` - `tfe_text_view_save`
- `tef_text_view_open`
- `tfe_text_view_new_with_file`
- `tfe_text_view_new`
respectively. respectively.
@ -44,7 +48,7 @@ There are two layers.
One of them is `tfe_text_view ...`, which is the lower level layer. One of them is `tfe_text_view ...`, which is the lower level layer.
The other is `note_book ...`, which is the higher level layer. The other is `note_book ...`, which is the higher level layer.
Now let's look at each program of the functions. Now let's look at the program of each function.
## notebook\_page\_new ## notebook\_page\_new
@ -60,57 +64,56 @@ Now let's look at each program of the functions.
9 9
10 static void 10 static void
11 notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) { 11 notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) {
12 GtkWidget *scr; 12 GtkWidget *scr = gtk_scrolled_window_new ();
13 GtkNotebookPage *nbp; 13 GtkNotebookPage *nbp;
14 GtkWidget *lab; 14 GtkWidget *lab;
15 gint i; 15 int i;
16 scr = gtk_scrolled_window_new (); 16
17 17 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
18 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR); 18 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
19 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv); 19 lab = gtk_label_new (filename);
20 lab = gtk_label_new (filename); 20 i = gtk_notebook_append_page (nb, scr, lab);
21 i = gtk_notebook_append_page (nb, scr, lab); 21 nbp = gtk_notebook_get_page (nb, scr);
22 nbp = gtk_notebook_get_page (nb, scr); 22 g_object_set (nbp, "tab-expand", TRUE, NULL);
23 g_object_set (nbp, "tab-expand", TRUE, NULL); 23 gtk_notebook_set_current_page (nb, i);
24 gtk_notebook_set_current_page (nb, i); 24 g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed_cb), nb);
25 g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed), nb); 25 }
26 } 26
27 27 void
28 void 28 notebook_page_new (GtkNotebook *nb) {
29 notebook_page_new (GtkNotebook *nb) { 29 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
30 g_return_if_fail(GTK_IS_NOTEBOOK (nb)); 30
31 31 GtkWidget *tv;
32 GtkWidget *tv; 32 char *filename;
33 char *filename; 33
34 34 tv = tfe_text_view_new ();
35 tv = tfe_text_view_new (); 35 filename = get_untitled ();
36 filename = get_untitled (); 36 notebook_page_build (nb, tv, filename);
37 notebook_page_build (nb, tv, filename); 37 }
38 }
~~~ ~~~
- 28-38: `notebook_page_new` function. - 27-37: `notebook_page_new` function.
- 30: `g_return_if_fail` is used to check the argument. - 29: `g_return_if_fail` is used to check the argument.
- 35: Generates TfeTextView object. - 34: Generates TfeTextView object.
- 36: Generates filename, which is "Untitled", "Untitled2", ... . - 35: Generates filename, which is "Untitled", "Untitled1", ... .
- 1-8: `get_untitled` function. - 1-8: `get_untitled` function.
- 3: Static variable `c` is initialized at the first call of this function. After that `c` keeps its value except it is changed explicitly. - 3: Static variable `c` is initialized at the first call of this function. After that `c` keeps its value unless it is changed explicitly.
- 4-7: Increases `c` by one and if it is zero then it returns "Untitled". If it is a positive integer then the it returns "Untitled\<the integer\>", for example, "Untitled1", "Untitled2", and so on. - 4-7: Increases `c` by one and if it is zero then it returns "Untitled". If it is a positive integer then it returns "Untitled\<the integer\>", for example, "Untitled1", "Untitled2", and so on.
The function `g_strdup_printf` generates a string and it should be freed by `g_free` when it becomes useless. The function `g_strdup_printf` generates a string and it should be freed by `g_free` when it becomes useless.
The caller of `get_untitled` is in charge of freeing the string. The caller of `get_untitled` is in charge of freeing the string.
- 37: calls `notebook_page_build` to build the contents of the page. - 36: calls `notebook_page_build` to build the contents of the page.
- 10- 26: `notebook_page_build` function. - 10- 25: `notebook_page_build` function.
- 16: Generates GtkScrolledWindow. - 12: Generates GtkScrolledWindow.
- 18: Sets the wrap mode of `tv` to GTK_WRAP_WORD_CHAR so that lines are broken between words or graphemes. - 17: Sets the wrap mode of `tv` to GTK_WRAP_WORD_CHAR so that lines are broken between words or graphemes.
- 19: Inserts `tv` to GtkscrolledWindow as a child. - 18: Inserts `tv` to GtkscrolledWindow as a child.
- 20-21: Generates GtkLabel, then appends it to GtkNotebookPage. - 19-20: Generates GtkLabel, then appends it to GtkNotebookPage.
- 22-23: Sets "tab-expand" property to TRUE. - 21-22: Sets "tab-expand" property to TRUE.
The function g\_object\_set sets properties on an object. The function g\_object\_set sets properties on an object.
The object is any object derived from GObject. The object is any object derived from GObject.
In many cases, an object has its own function to set its properties, but sometimes not. In many cases, an object has its own function to set its properties, but sometimes not.
In that case, use g\_object\_set to set the property. In that case, use g\_object\_set to set the property.
- 24: Sets the current page of `nb` to `i` which is the number of the GtkNotebookPage above. - 23: Sets the current page of `nb` to the newly generated page.
- 25: Connects "change-file" signal and `file_changed` handler. - 24: Connects "change-file" signal and `file_changed_cb` handler.
## notebook\_page\_new\_with\_file ## notebook\_page\_new\_with\_file
@ -139,7 +142,7 @@ The return value NULL means that an error has happened.
~~~C ~~~C
1 static void 1 static void
2 open_response (TfeTextView *tv, gint response, GtkNotebook *nb) { 2 open_response (TfeTextView *tv, int response, GtkNotebook *nb) {
3 GFile *file; 3 GFile *file;
4 char *filename; 4 char *filename;
5 5
@ -181,63 +184,106 @@ Such object has floating reference.
You need to call `g_object_ref_sink` first. You need to call `g_object_ref_sink` first.
Then the floating reference is converted into an ordinary reference. Then the floating reference is converted into an ordinary reference.
Now you call `g_object_unref` to decrease the reference count by one. Now you call `g_object_unref` to decrease the reference count by one.
- 9-11: If `tfe_text_view_get_file` returns a pointer not to point GFile, then something bad happens. - 9-11: If `tfe_text_view_get_file` returns a pointer not to point GFile, it means that an error has happened.
Sink and unref `tv`. Sink and unref `tv`.
- 12-16: Otherwise, everything is okay. - 12-16: Otherwise, everything is okay.
Gets the filename, builds the contents of the page. Gets the filename, builds the contents of the page.
## notebook\_page\_save ## notebook\_page\_close
~~~C ~~~C
1 void 1 void
2 notebook_page_save(GtkNotebook *nb) { 2 notebook_page_close (GtkNotebook *nb) {
3 gint i; 3 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
4
5 GtkWidget *win;
6 int i;
7
8 if (gtk_notebook_get_n_pages (nb) == 1) {
9 win = gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW);
10 gtk_window_destroy(GTK_WINDOW (win));
11 } else {
12 i = gtk_notebook_get_current_page (nb);
13 gtk_notebook_remove_page (GTK_NOTEBOOK (nb), i);
14 }
15 }
~~~
This function closes the current page.
If the page is the only page the notebook has, then the function destroys the top window and quits the application.
- 8-10: If the page is the only page the notebook has, it calls gtk\_window\_destroy to destroys the top window.
- 11-13: Otherwise, removes the current page.
## notebook\_page\_save
~~~C
1 static TfeTextView *
2 get_current_textview (GtkNotebook *nb) {
3 int i;
4 GtkWidget *scr; 4 GtkWidget *scr;
5 GtkWidget *tv; 5 GtkWidget *tv;
6 6
7 i = gtk_notebook_get_current_page (nb); 7 i = gtk_notebook_get_current_page (nb);
8 scr = gtk_notebook_get_nth_page (nb, i); 8 scr = gtk_notebook_get_nth_page (nb, i);
9 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr)); 9 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
10 tfe_text_view_save (TFE_TEXT_VIEW (tv)); 10 return TFE_TEXT_VIEW (tv);
11 } 11 }
12
13 void
14 notebook_page_save (GtkNotebook *nb) {
15 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
16
17 TfeTextView *tv;
18
19 tv = get_current_textview (nb);
20 tfe_text_view_save (TFE_TEXT_VIEW (tv));
21 }
~~~ ~~~
- 7-9: Get TfeTextView belongs to the current notebook page. - 13-21: `notebook_page_save`.
- 10: Call `tfe_text_view_save`. - 19: Gets TfeTextView belongs to the current page.
- 20: Calls `tfe_text_view_save`.
- 1-11: `get_current_textview`.
This function gets the TfeTextView object belongs to the current page.
- 7: Gets the page number of the current page.
- 8: Gets the child object `scr`, which is GtkScrolledWindow object, of the current page.
- 9-10: Gets the child object of `scr`, which is TfeTextView object, and return it.
## file\_changed handler ## file\_changed\_cb handler
The function `file_changed` is a handler connected to "change-file" signal. The function `file_changed_cb` is a handler connected to "change-file" signal.
If the file in TfeTextView is changed, it emits this signal. If the file in TfeTextView is changed, it emits this signal.
This handler changes the label of GtkNotebookPage. This handler changes the label of GtkNotebookPage.
~~~C ~~~C
1 static void 1 static void
2 file_changed (TfeTextView *tv, GtkNotebook *nb) { 2 file_changed_cb (TfeTextView *tv, GtkNotebook *nb) {
3 GFile *file; 3 GtkWidget *scr;
4 char *filename; 4 GtkWidget *label;
5 GtkWidget *scr; 5 GFile *file;
6 GtkWidget *label; 6 char *filename;
7 7
8 file = tfe_text_view_get_file (tv); 8 file = tfe_text_view_get_file (tv);
9 scr = gtk_widget_get_parent (GTK_WIDGET (tv)); 9 scr = gtk_widget_get_parent (GTK_WIDGET (tv));
10 if (G_IS_FILE (file)) 10 if (G_IS_FILE (file)) {
11 filename = g_file_get_basename (file); 11 filename = g_file_get_basename (file);
12 else 12 g_object_unref (file);
13 filename = get_untitled (); 13 } else
14 label = gtk_label_new (filename); 14 filename = get_untitled ();
15 gtk_notebook_set_tab_label (nb, scr, label); 15 label = gtk_label_new (filename);
16 g_object_unref (file); 16 gtk_notebook_set_tab_label (nb, scr, label);
17 g_free (filename); 17 }
18 }
~~~ ~~~
- 8: Gets GFile from TfeTextView. - 8: Gets GFile from TfeTextView.
- 9: Gets GkScrolledWindow which is the parent widget of `tv`. - 9: Gets GkScrolledWindow which is the parent widget of `tv`.
- 10-13: If `file` points GFile, then assigns the filename of the GFile into `filename`. - 10-12: If `file` points GFile, then assigns the filename of the GFile into `filename`.
Otherwise (file is NULL), assigns untitled string to `filename`. Then, unref the GFile object `file`.
- 14-15: Generates a label with the filename and inserts it into GtkNotebookPage. - 13-14: Otherwise (file is NULL), assigns untitled string to `filename`.
- 16-17: Unrefs `file` and frees `filename`. - 15-16: Generates a label with the filename and inserts it into GtkNotebookPage.
The string `filename` is used in the GtkLabel object.
You mustn't free it.
Up: [Readme.md](../Readme.md), Prev: [Section 12](sec12.md), Next: [Section 14](sec14.md) Up: [Readme.md](../Readme.md), Prev: [Section 12](sec12.md), Next: [Section 14](sec14.md)

View file

@ -3,12 +3,12 @@ Up: [Readme.md](../Readme.md), Prev: [Section 13](sec13.md), Next: [Section 15]
# tfeapplication.c # tfeapplication.c
`tfeapplication.c` includes all the code other than `tfetxtview.c` and `tfenotebook.c`. `tfeapplication.c` includes all the code other than `tfetxtview.c` and `tfenotebook.c`.
It does following things. It does:
- Application support, mainly handling command line arguments. - Application support, mainly handling command line arguments.
- Build widgets using ui file. - Builds widgets using ui file.
- Connect button signals and their handlers. - Connects button signals and their handlers.
- Manage CSS. - Manages CSS.
## main ## main
@ -53,9 +53,9 @@ The handler is as follows.
1 static void 1 static void
2 tfe_startup (GApplication *application) { 2 tfe_startup (GApplication *application) {
3 GtkApplication *app = GTK_APPLICATION (application); 3 GtkApplication *app = GTK_APPLICATION (application);
4 GtkApplicationWindow *win; 4 GtkBuilder *build;
5 GtkNotebook *nb; 5 GtkApplicationWindow *win;
6 GtkBuilder *build; 6 GtkNotebook *nb;
7 GtkButton *btno; 7 GtkButton *btno;
8 GtkButton *btnn; 8 GtkButton *btnn;
9 GtkButton *btns; 9 GtkButton *btns;
@ -69,10 +69,10 @@ The handler is as follows.
17 btnn = GTK_BUTTON (gtk_builder_get_object (build, "btnn")); 17 btnn = GTK_BUTTON (gtk_builder_get_object (build, "btnn"));
18 btns = GTK_BUTTON (gtk_builder_get_object (build, "btns")); 18 btns = GTK_BUTTON (gtk_builder_get_object (build, "btns"));
19 btnc = GTK_BUTTON (gtk_builder_get_object (build, "btnc")); 19 btnc = GTK_BUTTON (gtk_builder_get_object (build, "btnc"));
20 g_signal_connect (btno, "clicked", G_CALLBACK (open_clicked), nb); 20 g_signal_connect_swapped (btno, "clicked", G_CALLBACK (open_cb), nb);
21 g_signal_connect (btnn, "clicked", G_CALLBACK (new_clicked), nb); 21 g_signal_connect_swapped (btnn, "clicked", G_CALLBACK (new_cb), nb);
22 g_signal_connect (btns, "clicked", G_CALLBACK (save_clicked), nb); 22 g_signal_connect_swapped (btns, "clicked", G_CALLBACK (save_cb), nb);
23 g_signal_connect (btnc, "clicked", G_CALLBACK (close_clicked), nb); 23 g_signal_connect_swapped (btnc, "clicked", G_CALLBACK (close_cb), nb);
24 g_object_unref(build); 24 g_object_unref(build);
25 25
26 GdkDisplay *display; 26 GdkDisplay *display;
@ -119,7 +119,8 @@ If you want to set style to GtkTextView, substitute "textview" for the selector.
textview {color: yellow; ...} textview {color: yellow; ...}
~~~ ~~~
Class, ID and some other things can be applied to the selector like Web CSS. Refer GTK4 API reference for further information. Class, ID and some other things can be applied to the selector like Web CSS.
Refer [GTK4 API reference](https://gnome.pages.gitlab.gnome.org/gtk/gtk/theming.html) for further information.
In line 30, the CSS is a string. In line 30, the CSS is a string.
@ -128,11 +129,10 @@ textview {padding: 10px; font-family: monospace; font-size: 12pt;}
~~~ ~~~
- padding is a space between the border and contents. - padding is a space between the border and contents.
This space makes the text easier to read. This space makes the textview easier to read.
- font-family is a name of font. - font-family is a name of font.
"monospace" is one of the generic family font keywords. "monospace" is one of the generic family font keywords.
- font-size is set to 12pt. - font-size is set to 12pt.
It is a bit large, but easy on the eyes especially for elderly people.
### GtkStyleContext, GtkCSSProvider and GdkDisplay ### GtkStyleContext, GtkCSSProvider and GdkDisplay
@ -154,6 +154,8 @@ Look at the source file of `startup` handler again.
It is possible to add the provider to the context of GtkTextView instead of GdkDiplay. It is possible to add the provider to the context of GtkTextView instead of GdkDiplay.
To do so, rewrite `tfe_text_view_new`. To do so, rewrite `tfe_text_view_new`.
First, get the GtkStyleContext object of a TfeTextView object.
Then adds the CSS provider to the context.
~~~C ~~~C
GtkWidget * GtkWidget *
@ -184,46 +186,36 @@ They just generate a new GtkNotebookPage.
1 static void 1 static void
2 tfe_activate (GApplication *application) { 2 tfe_activate (GApplication *application) {
3 GtkApplication *app = GTK_APPLICATION (application); 3 GtkApplication *app = GTK_APPLICATION (application);
4 GtkWidget *win; 4 GtkWidget *win = GTK_WIDGET (gtk_application_get_active_window (app));
5 GtkWidget *boxv; 5 GtkWidget *boxv = gtk_window_get_child (GTK_WINDOW (win));
6 GtkNotebook *nb; 6 GtkNotebook *nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
7 7
8 win = GTK_WIDGET (gtk_application_get_active_window (app)); 8 notebook_page_new (nb);
9 boxv = gtk_window_get_child (GTK_WINDOW (win)); 9 gtk_widget_show (GTK_WIDGET (win));
10 nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv)); 10 }
11 11
12 notebook_page_new (nb); 12 static void
13 gtk_widget_show (GTK_WIDGET (win)); 13 tfe_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) {
14 } 14 GtkApplication *app = GTK_APPLICATION (application);
15 15 GtkWidget *win = GTK_WIDGET (gtk_application_get_active_window (app));
16 static void 16 GtkWidget *boxv = gtk_window_get_child (GTK_WINDOW (win));
17 tfe_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) { 17 GtkNotebook *nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
18 GtkApplication *app = GTK_APPLICATION (application); 18 int i;
19 GtkWidget *win; 19
20 GtkWidget *boxv; 20 for (i = 0; i < n_files; i++)
21 GtkNotebook *nb; 21 notebook_page_new_with_file (nb, files[i]);
22 int i; 22 if (gtk_notebook_get_n_pages (nb) == 0)
23 23 notebook_page_new (nb);
24 win = GTK_WIDGET (gtk_application_get_active_window (app)); 24 gtk_widget_show (win);
25 boxv = gtk_window_get_child (GTK_WINDOW (win)); 25 }
26 nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
27
28 for (i = 0; i < n_files; i++)
29 notebook_page_new_with_file (nb, files[i]);
30 if (gtk_notebook_get_n_pages (nb) == 0)
31 notebook_page_new (nb);
32 gtk_widget_show (win);
33 }
~~~ ~~~
- 1-14: `tfe_activate`. - 1-11: `tfe_activate`.
- 8-10: Gets GtkNotebook object. - 8-10: Generates a new page and shows the window.
- 12-13: Generates a new GtkNotebookPage and show the window. - 12-25: `tfe_open`.
- 16-33: `tfe_open`. - 20-21: Generates notebook pages with files.
- 24-26: Gets GtkNotebook object. - 22-23: If no page has generated, maybe because of read error, then it generates a empty page.
- 28-29: Generates GtkNotebookPage with files. - 24: Shows the window.
- 30-31: If opening and reading file failed and no GtkNotebookPage has generated, then it generates a empty page.
- 32: Shows the window.
These codes have become really simple thanks to tfenotebook.c and tfetextview.c. These codes have become really simple thanks to tfenotebook.c and tfetextview.c.
@ -265,43 +257,27 @@ The second instance immediately quits so shell prompt soon appears.
~~~C ~~~C
1 static void 1 static void
2 open_clicked (GtkWidget *btno, GtkNotebook *nb) { 2 open_cb (GtkNotebook *nb) {
3 notebook_page_open (nb); 3 notebook_page_open (nb);
4 } 4 }
5 5
6 static void 6 static void
7 new_clicked (GtkWidget *btnn, GtkNotebook *nb) { 7 new_cb (GtkNotebook *nb) {
8 notebook_page_new (nb); 8 notebook_page_new (nb);
9 } 9 }
10 10
11 static void 11 static void
12 save_clicked (GtkWidget *btns, GtkNotebook *nb) { 12 save_cb (GtkNotebook *nb) {
13 notebook_page_save (nb); 13 notebook_page_save (nb);
14 } 14 }
15 15
16 static void 16 static void
17 close_clicked (GtkWidget *btnc, GtkNotebook *nb) { 17 close_cb (GtkNotebook *nb) {
18 GtkWidget *win; 18 notebook_page_close (GTK_NOTEBOOK (nb));
19 GtkWidget *boxv; 19 }
20 gint i;
21
22 if (gtk_notebook_get_n_pages (nb) == 1) {
23 boxv = gtk_widget_get_parent (GTK_WIDGET (nb));
24 win = gtk_widget_get_parent (boxv);
25 gtk_window_destroy (GTK_WINDOW (win));
26 } else {
27 i = gtk_notebook_get_current_page (nb);
28 gtk_notebook_remove_page (GTK_NOTEBOOK (nb), i);
29 }
30 }
~~~ ~~~
`open_clicked`, `new_clicked` and `save_clicked` just call corresponding notebook page functions. `open_cb`, `new_cb`, `save_cb` and `close_cb` just call corresponding notebook page functions.
`close_clicked` is a bit complicated.
- 22-25: If there's only one page, we need to close the top level window and quit the application.
First, get the top level window and call `gtk_window_destroy`.
- 26-28: Otherwise, it removes the current page.
## meson.build ## meson.build
@ -318,13 +294,13 @@ First, get the top level window and call `gtk_window_destroy`.
10 executable('tfe', sourcefiles, resources, dependencies: gtkdep) 10 executable('tfe', sourcefiles, resources, dependencies: gtkdep)
~~~ ~~~
In this file, just the source file names are modified. In this file, just the source file names are modified from the prior version.
## source files ## source files
The [source files](https://github.com/ToshioCP/Gtk4-tutorial/tree/main/src/tfe5) of the text editor `tfe` will be shown in the next section. The [source files](https://github.com/ToshioCP/Gtk4-tutorial/tree/main/src/tfe5) of the text editor `tfe` will be shown in the next section.
You can download the files. You can also download the files from the [repository](https://github.com/ToshioCP/Gtk4-tutorial).
There are two options. There are two options.
- Use git and clone. - Use git and clone.
@ -335,6 +311,6 @@ If you use git, run the terminal and type the following.
$ git clone https://github.com/ToshioCP/Gtk4-tutorial.git $ git clone https://github.com/ToshioCP/Gtk4-tutorial.git
The source files are under `/src/tfe5` directory. The source files are under [`/src/tfe5`](../src/tfe5) directory.
Up: [Readme.md](../Readme.md), Prev: [Section 13](sec13.md), Next: [Section 15](sec15.md) Up: [Readme.md](../Readme.md), Prev: [Section 13](sec13.md), Next: [Section 15](sec15.md)

View file

@ -60,70 +60,67 @@ It is a good practice for you to add more features.
## tfe.ui ## tfe.ui
~~~xml ~~~xml
1 <interface> 1 <?xml version="1.0" encoding="UTF-8"?>
2 <object class="GtkApplicationWindow" id="win"> 2 <interface>
3 <property name="title">file editor</property> 3 <object class="GtkApplicationWindow" id="win">
4 <property name="default-width">600</property> 4 <property name="title">file editor</property>
5 <property name="default-height">400</property> 5 <property name="default-width">600</property>
6 <child> 6 <property name="default-height">400</property>
7 <object class="GtkBox" id="boxv"> 7 <child>
8 <property name="orientation">GTK_ORIENTATION_VERTICAL</property> 8 <object class="GtkBox" id="boxv">
9 <child> 9 <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
10 <object class="GtkBox" id="boxh"> 10 <child>
11 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property> 11 <object class="GtkBox" id="boxh">
12 <child> 12 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
13 <object class="GtkLabel" id="dmy1"> 13 <child>
14 <property name="width-chars">10</property> 14 <object class="GtkLabel" id="dmy1">
15 </object> 15 <property name="width-chars">10</property>
16 </child> 16 </object>
17 <child> 17 </child>
18 <object class="GtkButton" id="btnn"> 18 <child>
19 <property name="label">_New</property> 19 <object class="GtkButton" id="btnn">
20 <property name="use-underline">TRUE</property> 20 <property name="label">New</property>
21 </object> 21 </object>
22 </child> 22 </child>
23 <child> 23 <child>
24 <object class="GtkButton" id="btno"> 24 <object class="GtkButton" id="btno">
25 <property name="label">_Open</property> 25 <property name="label">Open</property>
26 <property name="use-underline">TRUE</property> 26 </object>
27 </object> 27 </child>
28 </child> 28 <child>
29 <child> 29 <object class="GtkLabel" id="dmy2">
30 <object class="GtkLabel" id="dmy2"> 30 <property name="hexpand">TRUE</property>
31 <property name="hexpand">TRUE</property> 31 </object>
32 </object> 32 </child>
33 </child> 33 <child>
34 <child> 34 <object class="GtkButton" id="btns">
35 <object class="GtkButton" id="btns"> 35 <property name="label">Save</property>
36 <property name="label">_Save</property> 36 </object>
37 <property name="use-underline">TRUE</property> 37 </child>
38 </object> 38 <child>
39 </child> 39 <object class="GtkButton" id="btnc">
40 <child> 40 <property name="label">Close</property>
41 <object class="GtkButton" id="btnc"> 41 </object>
42 <property name="label">_Close</property> 42 </child>
43 <property name="use-underline">TRUE</property> 43 <child>
44 </object> 44 <object class="GtkLabel" id="dmy3">
45 </child> 45 <property name="width-chars">10</property>
46 <child> 46 </object>
47 <object class="GtkLabel" id="dmy3"> 47 </child>
48 <property name="width-chars">10</property> 48 </object>
49 </object> 49 </child>
50 </child> 50 <child>
51 </object> 51 <object class="GtkNotebook" id="nb">
52 </child> 52 <property name="scrollable">TRUE</property>
53 <child> 53 <property name="hexpand">TRUE</property>
54 <object class="GtkNotebook" id="nb"> 54 <property name="vexpand">TRUE</property>
55 <property name="scrollable">TRUE</property> 55 </object>
56 <property name="hexpand">TRUE</property> 56 </child>
57 <property name="vexpand">TRUE</property> 57 </object>
58 </object> 58 </child>
59 </child> 59 </object>
60 </object> 60 </interface>
61 </child> 61
62 </object>
63 </interface>
64
~~~ ~~~
## tfe.h ## tfe.h
@ -138,123 +135,103 @@ It is a good practice for you to add more features.
## tfeapplication.c ## tfeapplication.c
~~~C ~~~C
1 #include "tfe.h" 1 #include "tfe.h"
2 2
3 static void 3 static void
4 open_clicked (GtkWidget *btno, GtkNotebook *nb) { 4 open_cb (GtkNotebook *nb) {
5 notebook_page_open (nb); 5 notebook_page_open (nb);
6 } 6 }
7 7
8 static void 8 static void
9 new_clicked (GtkWidget *btnn, GtkNotebook *nb) { 9 new_cb (GtkNotebook *nb) {
10 notebook_page_new (nb); 10 notebook_page_new (nb);
11 } 11 }
12 12
13 static void 13 static void
14 save_clicked (GtkWidget *btns, GtkNotebook *nb) { 14 save_cb (GtkNotebook *nb) {
15 notebook_page_save (nb); 15 notebook_page_save (nb);
16 } 16 }
17 17
18 static void 18 static void
19 close_clicked (GtkWidget *btnc, GtkNotebook *nb) { 19 close_cb (GtkNotebook *nb) {
20 GtkWidget *win; 20 notebook_page_close (GTK_NOTEBOOK (nb));
21 GtkWidget *boxv; 21 }
22 gint i; 22
23 23 static void
24 if (gtk_notebook_get_n_pages (nb) == 1) { 24 tfe_activate (GApplication *application) {
25 boxv = gtk_widget_get_parent (GTK_WIDGET (nb)); 25 GtkApplication *app = GTK_APPLICATION (application);
26 win = gtk_widget_get_parent (boxv); 26 GtkWidget *win = GTK_WIDGET (gtk_application_get_active_window (app));
27 gtk_window_destroy (GTK_WINDOW (win)); 27 GtkWidget *boxv = gtk_window_get_child (GTK_WINDOW (win));
28 } else { 28 GtkNotebook *nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
29 i = gtk_notebook_get_current_page (nb); 29
30 gtk_notebook_remove_page (GTK_NOTEBOOK (nb), i); 30 notebook_page_new (nb);
31 } 31 gtk_widget_show (GTK_WIDGET (win));
32 } 32 }
33 33
34 static void 34 static void
35 tfe_activate (GApplication *application) { 35 tfe_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) {
36 GtkApplication *app = GTK_APPLICATION (application); 36 GtkApplication *app = GTK_APPLICATION (application);
37 GtkWidget *win; 37 GtkWidget *win = GTK_WIDGET (gtk_application_get_active_window (app));
38 GtkWidget *boxv; 38 GtkWidget *boxv = gtk_window_get_child (GTK_WINDOW (win));
39 GtkNotebook *nb; 39 GtkNotebook *nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
40 40 int i;
41 win = GTK_WIDGET (gtk_application_get_active_window (app)); 41
42 boxv = gtk_window_get_child (GTK_WINDOW (win)); 42 for (i = 0; i < n_files; i++)
43 nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv)); 43 notebook_page_new_with_file (nb, files[i]);
44 44 if (gtk_notebook_get_n_pages (nb) == 0)
45 notebook_page_new (nb); 45 notebook_page_new (nb);
46 gtk_widget_show (GTK_WIDGET (win)); 46 gtk_widget_show (win);
47 } 47 }
48 48
49 static void 49 static void
50 tfe_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) { 50 tfe_startup (GApplication *application) {
51 GtkApplication *app = GTK_APPLICATION (application); 51 GtkApplication *app = GTK_APPLICATION (application);
52 GtkWidget *win; 52 GtkBuilder *build;
53 GtkWidget *boxv; 53 GtkApplicationWindow *win;
54 GtkNotebook *nb; 54 GtkNotebook *nb;
55 int i; 55 GtkButton *btno;
56 56 GtkButton *btnn;
57 win = GTK_WIDGET (gtk_application_get_active_window (app)); 57 GtkButton *btns;
58 boxv = gtk_window_get_child (GTK_WINDOW (win)); 58 GtkButton *btnc;
59 nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv)); 59
60 60 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe/tfe.ui");
61 for (i = 0; i < n_files; i++) 61 win = GTK_APPLICATION_WINDOW (gtk_builder_get_object (build, "win"));
62 notebook_page_new_with_file (nb, files[i]); 62 nb = GTK_NOTEBOOK (gtk_builder_get_object (build, "nb"));
63 if (gtk_notebook_get_n_pages (nb) == 0) 63 gtk_window_set_application (GTK_WINDOW (win), app);
64 notebook_page_new (nb); 64 btno = GTK_BUTTON (gtk_builder_get_object (build, "btno"));
65 gtk_widget_show (win); 65 btnn = GTK_BUTTON (gtk_builder_get_object (build, "btnn"));
66 } 66 btns = GTK_BUTTON (gtk_builder_get_object (build, "btns"));
67 67 btnc = GTK_BUTTON (gtk_builder_get_object (build, "btnc"));
68 68 g_signal_connect_swapped (btno, "clicked", G_CALLBACK (open_cb), nb);
69 static void 69 g_signal_connect_swapped (btnn, "clicked", G_CALLBACK (new_cb), nb);
70 tfe_startup (GApplication *application) { 70 g_signal_connect_swapped (btns, "clicked", G_CALLBACK (save_cb), nb);
71 GtkApplication *app = GTK_APPLICATION (application); 71 g_signal_connect_swapped (btnc, "clicked", G_CALLBACK (close_cb), nb);
72 GtkApplicationWindow *win; 72 g_object_unref(build);
73 GtkNotebook *nb; 73
74 GtkBuilder *build; 74 GdkDisplay *display;
75 GtkButton *btno; 75
76 GtkButton *btnn; 76 display = gtk_widget_get_display (GTK_WIDGET (win));
77 GtkButton *btns; 77 GtkCssProvider *provider = gtk_css_provider_new ();
78 GtkButton *btnc; 78 gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1);
79 79 gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
80 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe/tfe.ui"); 80 }
81 win = GTK_APPLICATION_WINDOW (gtk_builder_get_object (build, "win")); 81
82 nb = GTK_NOTEBOOK (gtk_builder_get_object (build, "nb")); 82 int
83 gtk_window_set_application (GTK_WINDOW (win), app); 83 main (int argc, char **argv) {
84 btno = GTK_BUTTON (gtk_builder_get_object (build, "btno")); 84 GtkApplication *app;
85 btnn = GTK_BUTTON (gtk_builder_get_object (build, "btnn")); 85 int stat;
86 btns = GTK_BUTTON (gtk_builder_get_object (build, "btns")); 86
87 btnc = GTK_BUTTON (gtk_builder_get_object (build, "btnc")); 87 app = gtk_application_new ("com.github.ToshioCP.tfe", G_APPLICATION_HANDLES_OPEN);
88 g_signal_connect (btno, "clicked", G_CALLBACK (open_clicked), nb); 88
89 g_signal_connect (btnn, "clicked", G_CALLBACK (new_clicked), nb); 89 g_signal_connect (app, "startup", G_CALLBACK (tfe_startup), NULL);
90 g_signal_connect (btns, "clicked", G_CALLBACK (save_clicked), nb); 90 g_signal_connect (app, "activate", G_CALLBACK (tfe_activate), NULL);
91 g_signal_connect (btnc, "clicked", G_CALLBACK (close_clicked), nb); 91 g_signal_connect (app, "open", G_CALLBACK (tfe_open), NULL);
92 g_object_unref(build); 92
93 93 stat =g_application_run (G_APPLICATION (app), argc, argv);
94 GdkDisplay *display; 94 g_object_unref (app);
95 95 return stat;
96 display = gtk_widget_get_display (GTK_WIDGET (win)); 96 }
97 GtkCssProvider *provider = gtk_css_provider_new (); 97
98 gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1);
99 gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
100 }
101
102 int
103 main (int argc, char **argv) {
104 GtkApplication *app;
105 int stat;
106
107 app = gtk_application_new ("com.github.ToshioCP.tfe", G_APPLICATION_HANDLES_OPEN);
108
109 g_signal_connect (app, "startup", G_CALLBACK (tfe_startup), NULL);
110 g_signal_connect (app, "activate", G_CALLBACK (tfe_activate), NULL);
111 g_signal_connect (app, "open", G_CALLBACK (tfe_open), NULL);
112
113 stat =g_application_run (G_APPLICATION (app), argc, argv);
114 g_object_unref (app);
115 return stat;
116 }
117
~~~ ~~~
## tfenotebook.h ## tfenotebook.h
@ -264,14 +241,17 @@ It is a good practice for you to add more features.
2 notebook_page_save(GtkNotebook *nb); 2 notebook_page_save(GtkNotebook *nb);
3 3
4 void 4 void
5 notebook_page_open (GtkNotebook *nb); 5 notebook_page_close (GtkNotebook *nb);
6 6
7 void 7 void
8 notebook_page_new_with_file (GtkNotebook *nb, GFile *file); 8 notebook_page_open (GtkNotebook *nb);
9 9
10 void 10 void
11 notebook_page_new (GtkNotebook *nb); 11 notebook_page_new_with_file (GtkNotebook *nb, GFile *file);
12 12
13 void
14 notebook_page_new (GtkNotebook *nb);
15
~~~ ~~~
## tfenotebook.c ## tfenotebook.c
@ -289,111 +269,134 @@ It is a good practice for you to add more features.
10 return g_strdup_printf ("Untitled%u", c); 10 return g_strdup_printf ("Untitled%u", c);
11 } 11 }
12 12
13 static void 13 static TfeTextView *
14 file_changed (TfeTextView *tv, GtkNotebook *nb) { 14 get_current_textview (GtkNotebook *nb) {
15 GFile *file; 15 int i;
16 char *filename; 16 GtkWidget *scr;
17 GtkWidget *scr; 17 GtkWidget *tv;
18 GtkWidget *label; 18
19 19 i = gtk_notebook_get_current_page (nb);
20 file = tfe_text_view_get_file (tv); 20 scr = gtk_notebook_get_nth_page (nb, i);
21 scr = gtk_widget_get_parent (GTK_WIDGET (tv)); 21 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
22 if (G_IS_FILE (file)) 22 return TFE_TEXT_VIEW (tv);
23 filename = g_file_get_basename (file); 23 }
24 else 24
25 filename = get_untitled (); 25 static void
26 label = gtk_label_new (filename); 26 file_changed_cb (TfeTextView *tv, GtkNotebook *nb) {
27 gtk_notebook_set_tab_label (nb, scr, label); 27 GtkWidget *scr;
28 g_object_unref (file); 28 GtkWidget *label;
29 g_free (filename); 29 GFile *file;
30 } 30 char *filename;
31 31
32 /* Save the contents in the current page */ 32 file = tfe_text_view_get_file (tv);
33 void 33 scr = gtk_widget_get_parent (GTK_WIDGET (tv));
34 notebook_page_save(GtkNotebook *nb) { 34 if (G_IS_FILE (file)) {
35 gint i; 35 filename = g_file_get_basename (file);
36 GtkWidget *scr; 36 g_object_unref (file);
37 GtkWidget *tv; 37 } else
38 38 filename = get_untitled ();
39 i = gtk_notebook_get_current_page (nb); 39 label = gtk_label_new (filename);
40 scr = gtk_notebook_get_nth_page (nb, i); 40 gtk_notebook_set_tab_label (nb, scr, label);
41 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr)); 41 }
42 tfe_text_view_save (TFE_TEXT_VIEW (tv)); 42
43 } 43 void
44 44 notebook_page_save (GtkNotebook *nb) {
45 static void 45 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
46 notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) { 46
47 GtkWidget *scr; 47 TfeTextView *tv;
48 GtkNotebookPage *nbp; 48
49 GtkWidget *lab; 49 tv = get_current_textview (nb);
50 gint i; 50 tfe_text_view_save (TFE_TEXT_VIEW (tv));
51 scr = gtk_scrolled_window_new (); 51 }
52 52
53 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR); 53 void
54 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv); 54 notebook_page_close (GtkNotebook *nb) {
55 lab = gtk_label_new (filename); 55 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
56 i = gtk_notebook_append_page (nb, scr, lab); 56
57 nbp = gtk_notebook_get_page (nb, scr); 57 GtkWidget *win;
58 g_object_set (nbp, "tab-expand", TRUE, NULL); 58 int i;
59 gtk_notebook_set_current_page (nb, i); 59
60 g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed), nb); 60 if (gtk_notebook_get_n_pages (nb) == 1) {
61 } 61 win = gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW);
62 62 gtk_window_destroy(GTK_WINDOW (win));
63 static void 63 } else {
64 open_response (TfeTextView *tv, gint response, GtkNotebook *nb) { 64 i = gtk_notebook_get_current_page (nb);
65 GFile *file; 65 gtk_notebook_remove_page (GTK_NOTEBOOK (nb), i);
66 char *filename; 66 }
67 67 }
68 if (response != TFE_OPEN_RESPONSE_SUCCESS) { 68
69 g_object_ref_sink (tv); 69 static void
70 g_object_unref (tv); 70 notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) {
71 }else if (! G_IS_FILE (file = tfe_text_view_get_file (tv))) { 71 GtkWidget *scr = gtk_scrolled_window_new ();
72 g_object_ref_sink (tv); 72 GtkNotebookPage *nbp;
73 g_object_unref (tv); 73 GtkWidget *lab;
74 }else { 74 int i;
75 filename = g_file_get_basename (file); 75
76 g_object_unref (file); 76 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
77 notebook_page_build (nb, GTK_WIDGET (tv), filename); 77 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
78 } 78 lab = gtk_label_new (filename);
79 } 79 i = gtk_notebook_append_page (nb, scr, lab);
80 80 nbp = gtk_notebook_get_page (nb, scr);
81 void 81 g_object_set (nbp, "tab-expand", TRUE, NULL);
82 notebook_page_open (GtkNotebook *nb) { 82 gtk_notebook_set_current_page (nb, i);
83 g_return_if_fail(GTK_IS_NOTEBOOK (nb)); 83 g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed_cb), nb);
84 84 }
85 GtkWidget *tv; 85
86 86 static void
87 tv = tfe_text_view_new (); 87 open_response (TfeTextView *tv, int response, GtkNotebook *nb) {
88 g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb); 88 GFile *file;
89 tfe_text_view_open (TFE_TEXT_VIEW (tv), gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW)); 89 char *filename;
90 } 90
91 91 if (response != TFE_OPEN_RESPONSE_SUCCESS) {
92 void 92 g_object_ref_sink (tv);
93 notebook_page_new_with_file (GtkNotebook *nb, GFile *file) { 93 g_object_unref (tv);
94 g_return_if_fail(GTK_IS_NOTEBOOK (nb)); 94 }else if (! G_IS_FILE (file = tfe_text_view_get_file (tv))) {
95 g_return_if_fail(G_IS_FILE (file)); 95 g_object_ref_sink (tv);
96 96 g_object_unref (tv);
97 GtkWidget *tv; 97 }else {
98 char *filename; 98 filename = g_file_get_basename (file);
99 99 g_object_unref (file);
100 if ((tv = tfe_text_view_new_with_file (file)) == NULL) 100 notebook_page_build (nb, GTK_WIDGET (tv), filename);
101 return; /* read error */ 101 }
102 filename = g_file_get_basename (file); 102 }
103 notebook_page_build (nb, tv, filename); 103
104 } 104 void
105 105 notebook_page_open (GtkNotebook *nb) {
106 void 106 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
107 notebook_page_new (GtkNotebook *nb) { 107
108 g_return_if_fail(GTK_IS_NOTEBOOK (nb)); 108 GtkWidget *tv;
109 109
110 GtkWidget *tv; 110 tv = tfe_text_view_new ();
111 char *filename; 111 g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
112 112 tfe_text_view_open (TFE_TEXT_VIEW (tv), gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW));
113 tv = tfe_text_view_new (); 113 }
114 filename = get_untitled (); 114
115 notebook_page_build (nb, tv, filename); 115 void
116 } 116 notebook_page_new_with_file (GtkNotebook *nb, GFile *file) {
117 117 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
118 g_return_if_fail(G_IS_FILE (file));
119
120 GtkWidget *tv;
121 char *filename;
122
123 if ((tv = tfe_text_view_new_with_file (file)) == NULL)
124 return; /* read error */
125 filename = g_file_get_basename (file);
126 notebook_page_build (nb, tv, filename);
127 }
128
129 void
130 notebook_page_new (GtkNotebook *nb) {
131 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
132
133 GtkWidget *tv;
134 char *filename;
135
136 tv = tfe_text_view_new ();
137 filename = get_untitled ();
138 notebook_page_build (nb, tv, filename);
139 }
140
~~~ ~~~
## tfetextview.h ## tfetextview.h
@ -505,157 +508,170 @@ It is a good practice for you to add more features.
64 tfe_text_view_get_file (TfeTextView *tv) { 64 tfe_text_view_get_file (TfeTextView *tv) {
65 g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL); 65 g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);
66 66
67 return g_file_dup (tv->file); 67 if (G_IS_FILE (tv->file))
68 } 68 return g_file_dup (tv->file);
69 69 else
70 static void 70 return NULL;
71 open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) { 71 }
72 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); 72
73 GFile *file; 73 static gboolean
74 char *contents; 74 save_file (GFile *file, GtkTextBuffer *tb, GtkWindow *win) {
75 gsize length; 75 GtkTextIter start_iter;
76 GtkWidget *message_dialog; 76 GtkTextIter end_iter;
77 GError *err = NULL; 77 gchar *contents;
78 78 GtkWidget *message_dialog;
79 if (response != GTK_RESPONSE_ACCEPT) 79 GError *err = NULL;
80 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL); 80
81 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)))) 81 /* This function doesn't check G_IS_FILE (file). The caller should check it. */
82 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR); 82 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
83 else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */ 83 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
84 if (G_IS_FILE (file)) 84 if (g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err)) {
85 g_object_unref (file); 85 gtk_text_buffer_set_modified (tb, FALSE);
86 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL, 86 return TRUE;
87 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, 87 } else {
88 "%s.\n", err->message); 88 message_dialog = gtk_message_dialog_new (win, GTK_DIALOG_MODAL,
89 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL); 89 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
90 gtk_widget_show (message_dialog); 90 "%s.\n", err->message);
91 g_error_free (err); 91 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
92 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR); 92 gtk_widget_show (message_dialog);
93 } else { 93 g_error_free (err);
94 gtk_text_buffer_set_text (tb, contents, length); 94 return FALSE;
95 g_free (contents); 95 }
96 if (G_IS_FILE (tv->file)) 96 }
97 g_object_unref (tv->file); 97
98 tv->file = file; 98 static void
99 gtk_text_buffer_set_modified (tb, FALSE); 99 saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
100 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS); 100 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
101 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0); 101 GFile *file;
102 } 102 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
103 gtk_window_destroy (GTK_WINDOW (dialog)); 103
104 } 104 gtk_window_destroy (GTK_WINDOW (dialog));
105 105 if (response == GTK_RESPONSE_ACCEPT) {
106 void 106 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
107 tfe_text_view_open (TfeTextView *tv, GtkWidget *win) { 107 if (! G_IS_FILE (file))
108 g_return_if_fail (TFE_IS_TEXT_VIEW (tv)); 108 g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n");
109 g_return_if_fail (GTK_IS_WINDOW (win)); 109 else {
110 110 save_file(file, tb, GTK_WINDOW (win));
111 GtkWidget *dialog; 111 if (G_IS_FILE (tv->file))
112 112 g_object_unref (tv->file);
113 dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN, 113 tv->file = file;
114 "Cancel", GTK_RESPONSE_CANCEL, 114 gtk_text_buffer_set_modified (tb, FALSE);
115 "Open", GTK_RESPONSE_ACCEPT, 115 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
116 NULL); 116 }
117 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv); 117 }
118 gtk_widget_show (dialog); 118 }
119 } 119
120 120 void
121 static void 121 tfe_text_view_save (TfeTextView *tv) {
122 saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) { 122 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
123 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); 123
124 GFile *file; 124 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
125 125 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
126 if (response == GTK_RESPONSE_ACCEPT) { 126
127 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); 127 if (! gtk_text_buffer_get_modified (tb))
128 if (G_IS_FILE(file)) { 128 return; /* no need to save it */
129 if (G_IS_FILE (tv->file)) 129 else if (tv->file == NULL)
130 g_object_unref (tv->file); 130 tfe_text_view_saveas (tv);
131 tv->file = file; 131 else if (! G_IS_FILE (tv->file))
132 gtk_text_buffer_set_modified (tb, TRUE); 132 g_error ("TfeTextView: The pointer in this object isn't NULL nor GFile object.\n");
133 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0); 133 else {
134 tfe_text_view_save (TFE_TEXT_VIEW (tv)); 134 if (save_file (tv->file, tb, GTK_WINDOW (win)))
135 } 135 gtk_text_buffer_set_modified (tb, FALSE);
136 } 136 }
137 gtk_window_destroy (GTK_WINDOW (dialog)); 137 }
138 } 138
139 139 void
140 void 140 tfe_text_view_saveas (TfeTextView *tv) {
141 tfe_text_view_save (TfeTextView *tv) { 141 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
142 g_return_if_fail (TFE_IS_TEXT_VIEW (tv)); 142
143 143 GtkWidget *dialog;
144 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); 144 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
145 GtkTextIter start_iter; 145
146 GtkTextIter end_iter; 146 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
147 gchar *contents; 147 "Cancel", GTK_RESPONSE_CANCEL,
148 GtkWidget *message_dialog; 148 "Save", GTK_RESPONSE_ACCEPT,
149 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW); 149 NULL);
150 GError *err = NULL; 150 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
151 151 gtk_widget_show (dialog);
152 if (! gtk_text_buffer_get_modified (tb)) 152 }
153 return; /* no need to save it */ 153
154 else if (tv->file == NULL) 154 GtkWidget *
155 tfe_text_view_saveas (tv); 155 tfe_text_view_new_with_file (GFile *file) {
156 else { 156 g_return_val_if_fail (G_IS_FILE (file), NULL);
157 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter); 157
158 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE); 158 GtkWidget *tv;
159 if (g_file_replace_contents (tv->file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err)) 159 GtkTextBuffer *tb;
160 gtk_text_buffer_set_modified (tb, FALSE); 160 char *contents;
161 else { 161 gsize length;
162 /* It is possible that tv->file is broken or you don't have permission to write. */ 162
163 /* It is a good idea to set tv->file to NULL. */ 163 if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */
164 if (G_IS_FILE (tv->file)) 164 return NULL;
165 g_object_unref (tv->file); 165
166 tv->file =NULL; 166 tv = tfe_text_view_new();
167 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0); 167 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
168 message_dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL, 168 gtk_text_buffer_set_text (tb, contents, length);
169 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, 169 g_free (contents);
170 "%s.\n", err->message); 170 TFE_TEXT_VIEW (tv)->file = g_file_dup (file);
171 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL); 171 return tv;
172 gtk_widget_show (message_dialog); 172 }
173 g_error_free (err); 173
174 } 174 static void
175 } 175 open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
176 } 176 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
177 177 GFile *file;
178 void 178 char *contents;
179 tfe_text_view_saveas (TfeTextView *tv) { 179 gsize length;
180 g_return_if_fail (TFE_IS_TEXT_VIEW (tv)); 180 GtkWidget *message_dialog;
181 181 GError *err = NULL;
182 GtkWidget *dialog; 182
183 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW); 183 if (response != GTK_RESPONSE_ACCEPT)
184 184 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
185 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE, 185 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)))) {
186 "Cancel", GTK_RESPONSE_CANCEL, 186 g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n");
187 "Save", GTK_RESPONSE_ACCEPT, 187 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
188 NULL); 188 } else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
189 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv); 189 if (G_IS_FILE (file))
190 gtk_widget_show (dialog); 190 g_object_unref (file);
191 } 191 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
192 192 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
193 GtkWidget * 193 "%s.\n", err->message);
194 tfe_text_view_new_with_file (GFile *file) { 194 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
195 g_return_val_if_fail (G_IS_FILE (file), NULL); 195 gtk_widget_show (message_dialog);
196 196 g_error_free (err);
197 GtkWidget *tv; 197 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
198 GtkTextBuffer *tb; 198 } else {
199 char *contents; 199 gtk_text_buffer_set_text (tb, contents, length);
200 gsize length; 200 g_free (contents);
201 201 if (G_IS_FILE (tv->file))
202 if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */ 202 g_object_unref (tv->file);
203 return NULL; 203 tv->file = file;
204 204 gtk_text_buffer_set_modified (tb, FALSE);
205 tv = tfe_text_view_new(); 205 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
206 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); 206 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
207 gtk_text_buffer_set_text (tb, contents, length); 207 }
208 g_free (contents); 208 gtk_window_destroy (GTK_WINDOW (dialog));
209 TFE_TEXT_VIEW (tv)->file = g_file_dup (file); 209 }
210 return tv; 210
211 } 211 void
212 212 tfe_text_view_open (TfeTextView *tv, GtkWidget *win) {
213 GtkWidget * 213 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
214 tfe_text_view_new (void) { 214 g_return_if_fail (GTK_IS_WINDOW (win));
215 return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL)); 215
216 } 216 GtkWidget *dialog;
217 217
218 dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN,
219 "Cancel", GTK_RESPONSE_CANCEL,
220 "Open", GTK_RESPONSE_ACCEPT,
221 NULL);
222 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
223 gtk_widget_show (dialog);
224 }
225
226 GtkWidget *
227 tfe_text_view_new (void) {
228 return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
229 }
230
~~~ ~~~
## Total number of lines, words and characters ## Total number of lines, words and characters
@ -663,15 +679,15 @@ It is a good practice for you to add more features.
~~~ ~~~
$ LANG=C wc tfe5/meson.build tfe5/tfeapplication.c tfe5/tfe.gresource.xml tfe5/tfe.h tfe5/tfenotebook.c tfe5/tfenotebook.h tfetextview/tfetextview.c tfetextview/tfetextview.h tfe5/tfe.ui $ LANG=C wc tfe5/meson.build tfe5/tfeapplication.c tfe5/tfe.gresource.xml tfe5/tfe.h tfe5/tfenotebook.c tfe5/tfenotebook.h tfetextview/tfetextview.c tfetextview/tfetextview.h tfe5/tfe.ui
10 17 294 tfe5/meson.build 10 17 294 tfe5/meson.build
117 348 3576 tfe5/tfeapplication.c 97 301 3159 tfe5/tfeapplication.c
6 9 153 tfe5/tfe.gresource.xml 6 9 153 tfe5/tfe.gresource.xml
4 6 87 tfe5/tfe.h 4 6 87 tfe5/tfe.h
117 325 3064 tfe5/tfenotebook.c 140 373 3580 tfe5/tfenotebook.c
12 17 196 tfe5/tfenotebook.h 15 21 241 tfe5/tfenotebook.h
217 637 7725 tfetextview/tfetextview.c 230 686 8144 tfetextview/tfetextview.c
35 60 701 tfetextview/tfetextview.h 35 60 701 tfetextview/tfetextview.h
64 105 2266 tfe5/tfe.ui 61 100 2073 tfe5/tfe.ui
582 1524 18062 total 598 1573 18432 total
~~~ ~~~

View file

@ -58,7 +58,7 @@ Some menu items have a link to another GMenu.
There are two types of links, submenu and section. There are two types of links, submenu and section.
GMenuItem can be inserted, appended or prepended to GMenu. GMenuItem can be inserted, appended or prepended to GMenu.
When it is inserted, all of the attribute and link values of the item are copied and used to form a new item within the menu. When it is inserted, all of the attributes and link values of the item are copied and used to form a new item within the menu.
The GMenuItem itself is not really inserted. The GMenuItem itself is not really inserted.
Therefore, after the insertion, GMenuItem is useless and it should be freed. Therefore, after the insertion, GMenuItem is useless and it should be freed.
The same goes for appending or prepending. The same goes for appending or prepending.

File diff suppressed because it is too large Load diff

View file

@ -164,9 +164,11 @@ Modify `env.sh`.
Include this file by . (dot) command before using gtk4 libraries. Include this file by . (dot) command before using gtk4 libraries.
You may think you can add them in your `.profile`. You may think you can add them in your `.profile`.
I think the environment variables above are necessary only when you compile gtk4 applications. But it's a wrong decision.
And it's not necessary except the case above and it might cause some bad things. Never write them to your `.profile`.
Therefore, I recommend you not to write them to your `.profile`. The environment variables above are necessary only when you compile and run gtk4 applications.
Otherwise it's not necessary.
If you changed the environment variables above and run gtk3 applications, it probably causes serious damage.
## Compiling gtk4 applications ## Compiling gtk4 applications

View file

@ -1,374 +1,201 @@
Up: [Readme.md](../Readme.md), Prev: [Section 19](sec19.md) Up: [Readme.md](../Readme.md), Prev: [Section 19](sec19.md), Next: [Section 21](sec21.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
1 <interface>
2 <object class="GtkApplicationWindow" id="win">
3 <property name="title">color changer</property>
4 <property name="default-width">600</property>
5 <property name="default-height">400</property>
6 <child>
7 <object class="GtkBox" id="boxv">
8 <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
9 <child>
10 <object class="GtkBox" id="boxh1">
11 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
12 <child>
13 <object class="GtkLabel" id="dmy1">
14 <property name="width-chars">10</property>
15 </object>
16 </child>
17 <child>
18 <object class="GtkButton" id="btnr">
19 <property name="label">Run</property>
20 <signal name="clicked" handler="run_cb"></signal>
21
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>
81
~~~
- 9-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.
Signal element are 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 ~~~C
1 #include <gtk/gtk.h> 1 #include <cairo.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 2
3 gtkdep = dependency('gtk4') 3 int
4 4 main (int argc, char **argv)
5 gnome=import('gnome') 5 {
6 resources = gnome.compile_resources('resources','color.gresource.xml') 6 cairo_surface_t *surface;
7 7 cairo_t *cr;
8 sourcefiles=files('colorapplication.c', '../tfetextview/tfetextview.c') 8 int width = 100;
9 9 int height = 100;
10 executable('color', sourcefiles, resources, dependencies: gtkdep, export_dynamic: true) 10
11 /* Generate surface and cairo */
12 surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
13 cr = cairo_create (surface);
14
15 /* Drawing starts here. */
16 /* Paint the background white */
17 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
18 cairo_paint (cr);
19 /* Draw a black rectangle */
20 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
21 cairo_set_line_width (cr, 2.0);
22 cairo_rectangle (cr, width/2.0 - 20.0, height/2.0 - 20.0, 40.0, 40.0);
23 cairo_stroke (cr);
24
25 /* Write the surface to a png file and clean up cairo and surface. */
26 cairo_surface_write_to_png (surface, "rectangle.png");
27 cairo_destroy (cr);
28 cairo_surface_destroy (surface);
29
30 return 0;
31 }
~~~ ~~~
## Compile and execute it - 1: Includes the header file of cairo.
- 12: `cairo_image_surface_create` creates an image surface.
`CAIRO_FORMAT_RGB24` is a constant which means that each pixel has red, green and blue data.
Each data has 8 bit quantity.
Modern displays have this type of color depth.
Width and height are pixels and given as integers.
- 13: Creates cairo context.
The surface given as an argument will be the destination of the context.
- 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.
First you need to export some variables (refer to [Section 2](sec2.md)). To compile this, type the following.
$ . env.sh $ gcc `pkg-config --cflags cairo` cairo.c `pkg-config --libs cairo`
Then type the following to compile it. ![rectangle.png](../src/misc/rectangle.png)
$ meson _build There are lots of documentations in [Cairo's website](https://www.cairographics.org/).
$ ninja -C _build If you aren't familiar with cairo, it is strongly recommended to read the [tutorial](https://www.cairographics.org/tutorial/) in the website.
The application is made in `_build` directory. ## GtkDrawingArea
Type the following to execute it.
$ _build/color The following is a very simple example.
Type "red", "green", "blue", "white", black", "light" or "dark" in the TfeTextView. ~~~C
Then, click on `Run` button. 1 #include <gtk/gtk.h>
Make sure the color of GtkDrawingArea changes. 2
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
~~~
In this program TfeTextView is used to change the color. The function `main` is almost same as before.
You can use buttons or menus instead of textview. The two functions `on_activate` and `draw_function` is important in this example.
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 19](sec19.md) - 16: Generates a GtkDrawingArea object.
- 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.
The drawing function has five parameters.
void drawing_function (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height,
gpointer user_data);
The first parameter is the GtkDrawingArea widget which calls the drawing function.
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.
- 3-11: The drawing function.
- 4-5: Sets the source to be white and paint the destination white.
- 7: Sets the line width to be 2.
- 8: Sets the source to be black.
- 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.
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.
![Square in the window](../image/da1.png)
Up: [Readme.md](../Readme.md), Prev: [Section 19](sec19.md), Next: [Section 21](sec21.md)

373
gfm/sec21.md Normal file
View file

@ -0,0 +1,373 @@
Up: [Readme.md](../Readme.md), Prev: [Section 20](sec20.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 20](sec20.md)

View file

@ -138,65 +138,65 @@ It reduces the cumbersome work.
First, let's look at the ui file `tfe3.ui` that defines a structure of the widgets. First, let's look at the ui file `tfe3.ui` that defines a structure of the widgets.
~~~xml ~~~xml
1 <interface> 1 <?xml version="1.0" encoding="UTF-8"?>
2 <object class="GtkApplicationWindow" id="win"> 2 <interface>
3 <property name="title">file editor</property> 3 <object class="GtkApplicationWindow" id="win">
4 <property name="default-width">600</property> 4 <property name="title">file editor</property>
5 <property name="default-height">400</property> 5 <property name="default-width">600</property>
6 <child> 6 <property name="default-height">400</property>
7 <object class="GtkBox" id="boxv"> 7 <child>
8 <property name="orientation">GTK_ORIENTATION_VERTICAL</property> 8 <object class="GtkBox" id="boxv">
9 <child> 9 <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
10 <object class="GtkBox" id="boxh"> 10 <child>
11 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property> 11 <object class="GtkBox" id="boxh">
12 <child> 12 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
13 <object class="GtkLabel" id="dmy1"> 13 <child>
14 <property name="width-chars">10</property> 14 <object class="GtkLabel" id="dmy1">
15 </object> 15 <property name="width-chars">10</property>
16 </child> 16 </object>
17 <child> 17 </child>
18 <object class="GtkButton" id="btnn"> 18 <child>
19 <property name="label">New</property> 19 <object class="GtkButton" id="btnn">
20 </object> 20 <property name="label">New</property>
21 </child> 21 </object>
22 <child> 22 </child>
23 <object class="GtkButton" id="btno"> 23 <child>
24 <property name="label">Open</property> 24 <object class="GtkButton" id="btno">
25 </object> 25 <property name="label">Open</property>
26 </child> 26 </object>
27 <child> 27 </child>
28 <object class="GtkLabel" id="dmy2"> 28 <child>
29 <property name="hexpand">TRUE</property> 29 <object class="GtkLabel" id="dmy2">
30 </object> 30 <property name="hexpand">TRUE</property>
31 </child> 31 </object>
32 <child> 32 </child>
33 <object class="GtkButton" id="btns"> 33 <child>
34 <property name="label">Save</property> 34 <object class="GtkButton" id="btns">
35 </object> 35 <property name="label">Save</property>
36 </child> 36 </object>
37 <child> 37 </child>
38 <object class="GtkButton" id="btnc"> 38 <child>
39 <property name="label">Close</property> 39 <object class="GtkButton" id="btnc">
40 </object> 40 <property name="label">Close</property>
41 </child> 41 </object>
42 <child> 42 </child>
43 <object class="GtkLabel" id="dmy3"> 43 <child>
44 <property name="width-chars">10</property> 44 <object class="GtkLabel" id="dmy3">
45 </object> 45 <property name="width-chars">10</property>
46 </child> 46 </object>
47 </object> 47 </child>
48 </child> 48 </object>
49 <child> 49 </child>
50 <object class="GtkNotebook" id="nb"> 50 <child>
51 <property name="hexpand">TRUE</property> 51 <object class="GtkNotebook" id="nb">
52 <property name="vexpand">TRUE</property> 52 <property name="hexpand">TRUE</property>
53 </object> 53 <property name="vexpand">TRUE</property>
54 </child> 54 </object>
55 </object> 55 </child>
56 </child> 56 </object>
57 </object> 57 </child>
58 </interface> 58 </object>
59 59 </interface>
~~~ ~~~
This is coded with XML structure. This is coded with XML structure.
@ -204,18 +204,35 @@ Constructs beginning with `<` and ending with `>` are called tags.
And there are two types of tags, start tag and end tag. And there are two types of tags, start tag and end tag.
For example, `<interface>` is a start tag and `</interface>` is an end tag. For example, `<interface>` is a start tag and `</interface>` is an end tag.
Ui file begins and ends with interface tags. Ui file begins and ends with interface tags.
Some tags, for example, object tags can have a class and id attributes inside the start tag. Some tags, for example, object tags can have a class and id attributes in the start tag.
- 2-5: An object with `GtkApplicationWindow` class and `win` id is defined. - 1: The first line is XML declaration.
It specifies that the version of XML is 1.0 and the encoding is UTF-8.
Even if the line is left out, GtkBuilder builds objects from the ui file.
But ui files must use UTF-8 encoding, or GtkBuilder can't recognize it and fatal error occurs.
- 3-6: An object with `GtkApplicationWindow` class and `win` id is defined.
This is the top level window. This is the top level window.
And the three properties of the window are defined. And the three properties of the window are defined.
`title` property is "file editor", `default-width` property is 400 and `default-height` property is 300. `title` property is "file editor", `default-width` property is 400 and `default-height` property is 300.
- 6: child tag means a child of the object above. - 7: child tag means a child of the object above.
For example, line 7 tells us that GtkBox object which id is "boxv" is a child of `win`. For example, line 7 tells us that GtkBox object which id is "boxv" is a child of `win`.
Compare this ui file and the lines 25-57 in the source code of `on_open` function. Compare this ui file and the lines 25-57 in the source code of `on_open` function.
Those two describe the same structure of widgets. Those two describe the same structure of widgets.
You can check the ui file with `gtk4-builder-tool`.
- `gtk4-builder-tool validate <ui file name>` validates the ui file.
If the ui file includes some syntactical error, `gtk4-builder-tool` prints the error.
- `gtk4-builder-tool simplify <ui file name>` simplifies the ui file and prints the result.
If `--replace` option is given, it replaces the ui file with the simplified one.
If the ui file specifies a value of property but it is default, then it will be removed.
Anf some values are simplified.
For example, "TRUE"and "FALSE" becomes "1" and "0" respectively.
However, "TRUE" or "FALSE" is better for maintenance.
It is a good idea to check your ui file before compiling.
## GtkBuilder ## GtkBuilder
GtkBuilder builds widgets based on the ui file. GtkBuilder builds widgets based on the ui file.

View file

@ -173,69 +173,7 @@ All the source files are listed below.
70 70
~~~ ~~~
`tfe.ui` The ui file `tfe.ui` is the same as `tfe3.ui` in the previous section.
~~~xml
1 <interface>
2 <object class="GtkApplicationWindow" id="win">
3 <property name="title">file editor</property>
4 <property name="default-width">600</property>
5 <property name="default-height">400</property>
6 <child>
7 <object class="GtkBox" id="boxv">
8 <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
9 <child>
10 <object class="GtkBox" id="boxh">
11 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
12 <child>
13 <object class="GtkLabel" id="dmy1">
14 <property name="width-chars">10</property>
15 </object>
16 </child>
17 <child>
18 <object class="GtkButton" id="btnn">
19 <property name="label">New</property>
20 </object>
21 </child>
22 <child>
23 <object class="GtkButton" id="btno">
24 <property name="label">Open</property>
25 </object>
26 </child>
27 <child>
28 <object class="GtkLabel" id="dmy2">
29 <property name="hexpand">TRUE</property>
30 </object>
31 </child>
32 <child>
33 <object class="GtkButton" id="btns">
34 <property name="label">Save</property>
35 </object>
36 </child>
37 <child>
38 <object class="GtkButton" id="btnc">
39 <property name="label">Close</property>
40 </object>
41 </child>
42 <child>
43 <object class="GtkLabel" id="dmy3">
44 <property name="width-chars">10</property>
45 </object>
46 </child>
47 </object>
48 </child>
49 <child>
50 <object class="GtkNotebook" id="nb">
51 <property name="hexpand">TRUE</property>
52 <property name="vexpand">TRUE</property>
53 </object>
54 </child>
55 </object>
56 </child>
57 </object>
58 </interface>
59
~~~
`tfe.gresource.xml` `tfe.gresource.xml`

BIN
image/dialog_warning.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

BIN
image/dialog_warning.xcf Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
image/tfe6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View file

@ -84,9 +84,9 @@ def src2md srcmd, md, width
c_file = $1 c_file = $1
c_functions = $2.strip.split(" ") c_functions = $2.strip.split(" ")
if c_file =~ /^\// # absolute path if c_file =~ /^\// # absolute path
c_file_buf = IO.readlines(c_file) c_file_buf = File.readlines(c_file)
else #relative path else #relative path
c_file_buf = IO.readlines(src_dir+"/"+c_file) c_file_buf = File.readlines(src_dir+"/"+c_file)
end end
if c_functions.empty? # no functions are specified if c_functions.empty? # no functions are specified
tmp_buf = c_file_buf tmp_buf = c_file_buf
@ -96,7 +96,7 @@ def src2md srcmd, md, width
c_functions.each do |c_function| c_functions.each do |c_function|
from = c_file_buf.find_index { |line| line =~ /^#{c_function} *\(/ } from = c_file_buf.find_index { |line| line =~ /^#{c_function} *\(/ }
if ! from if ! from
warn "ERROR!!! --- Didn't find #{c_function} in #{filename}. ---" warn "ERROR in #{srcmd}: Didn't find #{c_function} in #{c_file}."
break break
end end
to = from to = from

View file

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface> <interface>
<object class="GtkApplicationWindow" id="win"> <object class="GtkApplicationWindow" id="win">
<property name="title">color changer</property> <property name="title">color changer</property>
@ -8,68 +9,67 @@
<property name="orientation">GTK_ORIENTATION_VERTICAL</property> <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<child> <child>
<object class="GtkBox" id="boxh1"> <object class="GtkBox" id="boxh1">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property> <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<child> <child>
<object class="GtkLabel" id="dmy1"> <object class="GtkLabel" id="dmy1">
<property name="width-chars">10</property> <property name="width-chars">10</property>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkButton" id="btnr"> <object class="GtkButton" id="btnr">
<property name="label">Run</property> <property name="label">Run</property>
<signal name="clicked" handler="run_cb"></signal> <signal name="clicked" handler="run_cb"></signal>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkButton" id="btno"> <object class="GtkButton" id="btno">
<property name="label">Open</property> <property name="label">Open</property>
<signal name="clicked" handler="open_cb"></signal> <signal name="clicked" handler="open_cb"></signal>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkLabel" id="dmy2"> <object class="GtkLabel" id="dmy2">
<property name="hexpand">TRUE</property> <property name="hexpand">TRUE</property>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkButton" id="btns"> <object class="GtkButton" id="btns">
<property name="label">Save</property> <property name="label">Save</property>
<signal name="clicked" handler="save_cb"></signal> <signal name="clicked" handler="save_cb"></signal>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkButton" id="btnc"> <object class="GtkButton" id="btnc">
<property name="label">Close</property> <property name="label">Close</property>
<signal name="clicked" handler="close_cb"></signal> <signal name="clicked" handler="close_cb"></signal>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkLabel" id="dmy3"> <object class="GtkLabel" id="dmy3">
<property name="width-chars">10</property> <property name="width-chars">10</property>
</object> </object>
</child> </child>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkBox" id="boxh2"> <object class="GtkBox" id="boxh2">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property> <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<property name="homogeneous">TRUE</property> <property name="homogeneous">TRUE</property>
<child> <child>
<object class="GtkScrolledWindow" id="scr"> <object class="GtkScrolledWindow" id="scr">
<property name="hexpand">TRUE</property> <property name="hexpand">TRUE</property>
<property name="vexpand">TRUE</property> <property name="vexpand">TRUE</property>
<child> <child>
<object class="TfeTextView" id="tv"> <object class="TfeTextView" id="tv">
<property name="wrap-mode">GTK_WRAP_WORD_CHAR</property> <property name="wrap-mode">GTK_WRAP_WORD_CHAR</property>
</object> </object>
</child> </child>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkDrawingArea" id="da"> <object class="GtkDrawingArea" id="da">
<property name="hexpand">TRUE</property> <property name="hexpand">TRUE</property>
<property name="vexpand">TRUE</property> <property name="vexpand">TRUE</property>
</object> </object>
</child> </child>
</object> </object>
@ -78,4 +78,3 @@
</child> </child>
</object> </object>
</interface> </interface>

View file

@ -220,7 +220,7 @@ Then C destructs itself and finally the memories allocated to C is freed.
The idea above is based on an assumption that an object referred by nothing has reference count of zero. The idea above is based on an assumption that an object referred by nothing has reference count of zero.
When the reference count drops to zero, the object starts its destruction process. When the reference count drops to zero, the object starts its destruction process.
The destruction process is spitted into two phases: disposing and finalizing. The destruction process is split into two phases: disposing and finalizing.
In the disposing process, the object invokes the function pointed by `dispose` in its class to release all references to other objects. In the disposing process, the object invokes the function pointed by `dispose` in its class to release all references to other objects.
In the finalizing process, it invokes the function pointed by `finalize` in its class to complete the destruction process. In the finalizing process, it invokes the function pointed by `finalize` in its class to complete the destruction process.
These functions are also called handlers or methods. These functions are also called handlers or methods.

View file

@ -2,7 +2,7 @@
GtkNotebook is a very important object in the text file editor `tfe`. GtkNotebook is a very important object in the text file editor `tfe`.
It connects the application and TfeTextView objects. It connects the application and TfeTextView objects.
A set of functions related to GtkNotebook are declared in `tfenotebook.h`. A set of public functions related to GtkNotebook are declared in `tfenotebook.h`.
The word "tfenotebook" is used only in filenames. The word "tfenotebook" is used only in filenames.
There's no "TfeNotebook" object. There's no "TfeNotebook" object.
@ -12,18 +12,19 @@ tfe5/tfenotebook.h
This header file describes the public functions in `tfenotebook.c`. This header file describes the public functions in `tfenotebook.c`.
- 10-11: The function `notebook_page_new` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView to the page. - 1-2: `notebook_page_save` saves the current page to the file of which the name specified in the tab.
- 7-8: The function `notebook_page_new_with_file` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView to the page. A file is read and inserted into GtkTextBuffer. If the name is `untitled` or `untitled` followed by digits, FileChooserDialog appears and a user can choose or specify a filename.
The GFile `file` is copied and inserted to the TfeTextView object. - 4-5: `notebook_page_close` closes the current page.
- 4-5: `notebook_page_open` shows a file chooser dialog. Then, user chooses a file and the file is inserted into GtkTextBuffer. - 7-8: `notebook_page_open` shows a file chooser dialog and a user can choose a file. The file is inserted to a new page.
- 1-2: `notebook_page_save` saves the contents in GtkTextBuffer into the file, which is got from the TfeTextView. - 10-11: `notebook_page_new_with_file` generates a new page and the file give as an argument is read and inserted into the page.
- 13-14: `notebook_page_new` generates a new empty page.
You probably find that the functions above are higher level functions of You probably find that the functions except `notebook_page_close` are higher level functions of
- `tfe_text_view_new`
- `tfe_text_view_new_with_file`
- `tef_text_view_open`
- `tfe_text_view_save` - `tfe_text_view_save`
- `tef_text_view_open`
- `tfe_text_view_new_with_file`
- `tfe_text_view_new`
respectively. respectively.
@ -31,7 +32,7 @@ There are two layers.
One of them is `tfe_text_view ...`, which is the lower level layer. One of them is `tfe_text_view ...`, which is the lower level layer.
The other is `note_book ...`, which is the higher level layer. The other is `note_book ...`, which is the higher level layer.
Now let's look at each program of the functions. Now let's look at the program of each function.
## notebook\_page\_new ## notebook\_page\_new
@ -39,28 +40,28 @@ Now let's look at each program of the functions.
tfe5/tfenotebook.c get_untitled notebook_page_build notebook_page_new tfe5/tfenotebook.c get_untitled notebook_page_build notebook_page_new
@@@ @@@
- 28-38: `notebook_page_new` function. - 27-37: `notebook_page_new` function.
- 30: `g_return_if_fail` is used to check the argument. - 29: `g_return_if_fail` is used to check the argument.
- 35: Generates TfeTextView object. - 34: Generates TfeTextView object.
- 36: Generates filename, which is "Untitled", "Untitled2", ... . - 35: Generates filename, which is "Untitled", "Untitled1", ... .
- 1-8: `get_untitled` function. - 1-8: `get_untitled` function.
- 3: Static variable `c` is initialized at the first call of this function. After that `c` keeps its value except it is changed explicitly. - 3: Static variable `c` is initialized at the first call of this function. After that `c` keeps its value unless it is changed explicitly.
- 4-7: Increases `c` by one and if it is zero then it returns "Untitled". If it is a positive integer then the it returns "Untitled\<the integer\>", for example, "Untitled1", "Untitled2", and so on. - 4-7: Increases `c` by one and if it is zero then it returns "Untitled". If it is a positive integer then it returns "Untitled\<the integer\>", for example, "Untitled1", "Untitled2", and so on.
The function `g_strdup_printf` generates a string and it should be freed by `g_free` when it becomes useless. The function `g_strdup_printf` generates a string and it should be freed by `g_free` when it becomes useless.
The caller of `get_untitled` is in charge of freeing the string. The caller of `get_untitled` is in charge of freeing the string.
- 37: calls `notebook_page_build` to build the contents of the page. - 36: calls `notebook_page_build` to build the contents of the page.
- 10- 26: `notebook_page_build` function. - 10- 25: `notebook_page_build` function.
- 16: Generates GtkScrolledWindow. - 12: Generates GtkScrolledWindow.
- 18: Sets the wrap mode of `tv` to GTK_WRAP_WORD_CHAR so that lines are broken between words or graphemes. - 17: Sets the wrap mode of `tv` to GTK_WRAP_WORD_CHAR so that lines are broken between words or graphemes.
- 19: Inserts `tv` to GtkscrolledWindow as a child. - 18: Inserts `tv` to GtkscrolledWindow as a child.
- 20-21: Generates GtkLabel, then appends it to GtkNotebookPage. - 19-20: Generates GtkLabel, then appends it to GtkNotebookPage.
- 22-23: Sets "tab-expand" property to TRUE. - 21-22: Sets "tab-expand" property to TRUE.
The function g\_object\_set sets properties on an object. The function g\_object\_set sets properties on an object.
The object is any object derived from GObject. The object is any object derived from GObject.
In many cases, an object has its own function to set its properties, but sometimes not. In many cases, an object has its own function to set its properties, but sometimes not.
In that case, use g\_object\_set to set the property. In that case, use g\_object\_set to set the property.
- 24: Sets the current page of `nb` to `i` which is the number of the GtkNotebookPage above. - 23: Sets the current page of `nb` to the newly generated page.
- 25: Connects "change-file" signal and `file_changed` handler. - 24: Connects "change-file" signal and `file_changed_cb` handler.
## notebook\_page\_new\_with\_file ## notebook\_page\_new\_with\_file
@ -92,34 +93,54 @@ Such object has floating reference.
You need to call `g_object_ref_sink` first. You need to call `g_object_ref_sink` first.
Then the floating reference is converted into an ordinary reference. Then the floating reference is converted into an ordinary reference.
Now you call `g_object_unref` to decrease the reference count by one. Now you call `g_object_unref` to decrease the reference count by one.
- 9-11: If `tfe_text_view_get_file` returns a pointer not to point GFile, then something bad happens. - 9-11: If `tfe_text_view_get_file` returns a pointer not to point GFile, it means that an error has happened.
Sink and unref `tv`. Sink and unref `tv`.
- 12-16: Otherwise, everything is okay. - 12-16: Otherwise, everything is okay.
Gets the filename, builds the contents of the page. Gets the filename, builds the contents of the page.
## notebook\_page\_close
@@@include
tfe5/tfenotebook.c notebook_page_close
@@@
This function closes the current page.
If the page is the only page the notebook has, then the function destroys the top window and quits the application.
- 8-10: If the page is the only page the notebook has, it calls gtk\_window\_destroy to destroys the top window.
- 11-13: Otherwise, removes the current page.
## notebook\_page\_save ## notebook\_page\_save
@@@include @@@include
tfe5/tfenotebook.c notebook_page_save tfe5/tfenotebook.c get_current_textview notebook_page_save
@@@ @@@
- 7-9: Get TfeTextView belongs to the current notebook page. - 13-21: `notebook_page_save`.
- 10: Call `tfe_text_view_save`. - 19: Gets TfeTextView belongs to the current page.
- 20: Calls `tfe_text_view_save`.
- 1-11: `get_current_textview`.
This function gets the TfeTextView object belongs to the current page.
- 7: Gets the page number of the current page.
- 8: Gets the child object `scr`, which is GtkScrolledWindow object, of the current page.
- 9-10: Gets the child object of `scr`, which is TfeTextView object, and return it.
## file\_changed handler ## file\_changed\_cb handler
The function `file_changed` is a handler connected to "change-file" signal. The function `file_changed_cb` is a handler connected to "change-file" signal.
If the file in TfeTextView is changed, it emits this signal. If the file in TfeTextView is changed, it emits this signal.
This handler changes the label of GtkNotebookPage. This handler changes the label of GtkNotebookPage.
@@@include @@@include
tfe5/tfenotebook.c file_changed tfe5/tfenotebook.c file_changed_cb
@@@ @@@
- 8: Gets GFile from TfeTextView. - 8: Gets GFile from TfeTextView.
- 9: Gets GkScrolledWindow which is the parent widget of `tv`. - 9: Gets GkScrolledWindow which is the parent widget of `tv`.
- 10-13: If `file` points GFile, then assigns the filename of the GFile into `filename`. - 10-12: If `file` points GFile, then assigns the filename of the GFile into `filename`.
Otherwise (file is NULL), assigns untitled string to `filename`. Then, unref the GFile object `file`.
- 14-15: Generates a label with the filename and inserts it into GtkNotebookPage. - 13-14: Otherwise (file is NULL), assigns untitled string to `filename`.
- 16-17: Unrefs `file` and frees `filename`. - 15-16: Generates a label with the filename and inserts it into GtkNotebookPage.
The string `filename` is used in the GtkLabel object.
You mustn't free it.

View file

@ -1,12 +1,12 @@
# tfeapplication.c # tfeapplication.c
`tfeapplication.c` includes all the code other than `tfetxtview.c` and `tfenotebook.c`. `tfeapplication.c` includes all the code other than `tfetxtview.c` and `tfenotebook.c`.
It does following things. It does:
- Application support, mainly handling command line arguments. - Application support, mainly handling command line arguments.
- Build widgets using ui file. - Builds widgets using ui file.
- Connect button signals and their handlers. - Connects button signals and their handlers.
- Manage CSS. - Manages CSS.
## main ## main
@ -72,7 +72,8 @@ If you want to set style to GtkTextView, substitute "textview" for the selector.
textview {color: yellow; ...} textview {color: yellow; ...}
~~~ ~~~
Class, ID and some other things can be applied to the selector like Web CSS. Refer GTK4 API reference for further information. Class, ID and some other things can be applied to the selector like Web CSS.
Refer [GTK4 API reference](https://gnome.pages.gitlab.gnome.org/gtk/gtk/theming.html) for further information.
In line 30, the CSS is a string. In line 30, the CSS is a string.
@ -81,11 +82,10 @@ textview {padding: 10px; font-family: monospace; font-size: 12pt;}
~~~ ~~~
- padding is a space between the border and contents. - padding is a space between the border and contents.
This space makes the text easier to read. This space makes the textview easier to read.
- font-family is a name of font. - font-family is a name of font.
"monospace" is one of the generic family font keywords. "monospace" is one of the generic family font keywords.
- font-size is set to 12pt. - font-size is set to 12pt.
It is a bit large, but easy on the eyes especially for elderly people.
### GtkStyleContext, GtkCSSProvider and GdkDisplay ### GtkStyleContext, GtkCSSProvider and GdkDisplay
@ -107,6 +107,8 @@ Look at the source file of `startup` handler again.
It is possible to add the provider to the context of GtkTextView instead of GdkDiplay. It is possible to add the provider to the context of GtkTextView instead of GdkDiplay.
To do so, rewrite `tfe_text_view_new`. To do so, rewrite `tfe_text_view_new`.
First, get the GtkStyleContext object of a TfeTextView object.
Then adds the CSS provider to the context.
~~~C ~~~C
GtkWidget * GtkWidget *
@ -137,14 +139,12 @@ They just generate a new GtkNotebookPage.
tfe5/tfeapplication.c tfe_activate tfe_open tfe5/tfeapplication.c tfe_activate tfe_open
@@@ @@@
- 1-14: `tfe_activate`. - 1-11: `tfe_activate`.
- 8-10: Gets GtkNotebook object. - 8-10: Generates a new page and shows the window.
- 12-13: Generates a new GtkNotebookPage and show the window. - 12-25: `tfe_open`.
- 16-33: `tfe_open`. - 20-21: Generates notebook pages with files.
- 24-26: Gets GtkNotebook object. - 22-23: If no page has generated, maybe because of read error, then it generates a empty page.
- 28-29: Generates GtkNotebookPage with files. - 24: Shows the window.
- 30-31: If opening and reading file failed and no GtkNotebookPage has generated, then it generates a empty page.
- 32: Shows the window.
These codes have become really simple thanks to tfenotebook.c and tfetextview.c. These codes have become really simple thanks to tfenotebook.c and tfetextview.c.
@ -185,15 +185,10 @@ The second instance immediately quits so shell prompt soon appears.
## a series of handlers correspond to the button signals ## a series of handlers correspond to the button signals
@@@include @@@include
tfe5/tfeapplication.c open_clicked new_clicked save_clicked close_clicked tfe5/tfeapplication.c open_cb new_cb save_cb close_cb
@@@ @@@
`open_clicked`, `new_clicked` and `save_clicked` just call corresponding notebook page functions. `open_cb`, `new_cb`, `save_cb` and `close_cb` just call corresponding notebook page functions.
`close_clicked` is a bit complicated.
- 22-25: If there's only one page, we need to close the top level window and quit the application.
First, get the top level window and call `gtk_window_destroy`.
- 26-28: Otherwise, it removes the current page.
## meson.build ## meson.build
@ -201,13 +196,13 @@ First, get the top level window and call `gtk_window_destroy`.
tfe5/meson.build tfe5/meson.build
@@@ @@@
In this file, just the source file names are modified. In this file, just the source file names are modified from the prior version.
## source files ## source files
The [source files](https://github.com/ToshioCP/Gtk4-tutorial/tree/main/src/tfe5) of the text editor `tfe` will be shown in the next section. The [source files](https://github.com/ToshioCP/Gtk4-tutorial/tree/main/src/tfe5) of the text editor `tfe` will be shown in the next section.
You can download the files. You can also download the files from the [repository](https://github.com/ToshioCP/Gtk4-tutorial).
There are two options. There are two options.
- Use git and clone. - Use git and clone.
@ -218,4 +213,4 @@ If you use git, run the terminal and type the following.
$ git clone https://github.com/ToshioCP/Gtk4-tutorial.git $ git clone https://github.com/ToshioCP/Gtk4-tutorial.git
The source files are under `/src/tfe5` directory. The source files are under [`/src/tfe5`](tfe5) directory.

View file

@ -56,7 +56,7 @@ Some menu items have a link to another GMenu.
There are two types of links, submenu and section. There are two types of links, submenu and section.
GMenuItem can be inserted, appended or prepended to GMenu. GMenuItem can be inserted, appended or prepended to GMenu.
When it is inserted, all of the attribute and link values of the item are copied and used to form a new item within the menu. When it is inserted, all of the attributes and link values of the item are copied and used to form a new item within the menu.
The GMenuItem itself is not really inserted. The GMenuItem itself is not really inserted.
Therefore, after the insertion, GMenuItem is useless and it should be freed. Therefore, after the insertion, GMenuItem is useless and it should be freed.
The same goes for appending or prepending. The same goes for appending or prepending.

File diff suppressed because it is too large Load diff

View file

@ -162,9 +162,11 @@ Modify `env.sh`.
Include this file by . (dot) command before using gtk4 libraries. Include this file by . (dot) command before using gtk4 libraries.
You may think you can add them in your `.profile`. You may think you can add them in your `.profile`.
I think the environment variables above are necessary only when you compile gtk4 applications. But it's a wrong decision.
And it's not necessary except the case above and it might cause some bad things. Never write them to your `.profile`.
Therefore, I recommend you not to write them to your `.profile`. The environment variables above are necessary only when you compile and run gtk4 applications.
Otherwise it's not necessary.
If you changed the environment variables above and run gtk3 applications, it probably causes serious damage.
## Compiling gtk4 applications ## Compiling gtk4 applications

View file

@ -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
@@@ @@@
- 9-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.
Signal element are 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/sec21.src.md Normal file
View 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.

View file

@ -59,18 +59,35 @@ Constructs beginning with `<` and ending with `>` are called tags.
And there are two types of tags, start tag and end tag. And there are two types of tags, start tag and end tag.
For example, `<interface>` is a start tag and `</interface>` is an end tag. For example, `<interface>` is a start tag and `</interface>` is an end tag.
Ui file begins and ends with interface tags. Ui file begins and ends with interface tags.
Some tags, for example, object tags can have a class and id attributes inside the start tag. Some tags, for example, object tags can have a class and id attributes in the start tag.
- 2-5: An object with `GtkApplicationWindow` class and `win` id is defined. - 1: The first line is XML declaration.
It specifies that the version of XML is 1.0 and the encoding is UTF-8.
Even if the line is left out, GtkBuilder builds objects from the ui file.
But ui files must use UTF-8 encoding, or GtkBuilder can't recognize it and fatal error occurs.
- 3-6: An object with `GtkApplicationWindow` class and `win` id is defined.
This is the top level window. This is the top level window.
And the three properties of the window are defined. And the three properties of the window are defined.
`title` property is "file editor", `default-width` property is 400 and `default-height` property is 300. `title` property is "file editor", `default-width` property is 400 and `default-height` property is 300.
- 6: child tag means a child of the object above. - 7: child tag means a child of the object above.
For example, line 7 tells us that GtkBox object which id is "boxv" is a child of `win`. For example, line 7 tells us that GtkBox object which id is "boxv" is a child of `win`.
Compare this ui file and the lines 25-57 in the source code of `on_open` function. Compare this ui file and the lines 25-57 in the source code of `on_open` function.
Those two describe the same structure of widgets. Those two describe the same structure of widgets.
You can check the ui file with `gtk4-builder-tool`.
- `gtk4-builder-tool validate <ui file name>` validates the ui file.
If the ui file includes some syntactical error, `gtk4-builder-tool` prints the error.
- `gtk4-builder-tool simplify <ui file name>` simplifies the ui file and prints the result.
If `--replace` option is given, it replaces the ui file with the simplified one.
If the ui file specifies a value of property but it is default, then it will be removed.
Anf some values are simplified.
For example, "TRUE"and "FALSE" becomes "1" and "0" respectively.
However, "TRUE" or "FALSE" is better for maintenance.
It is a good idea to check your ui file before compiling.
## GtkBuilder ## GtkBuilder
GtkBuilder builds widgets based on the ui file. GtkBuilder builds widgets based on the ui file.

View file

@ -56,11 +56,7 @@ tfe4/tfetextview.c
tfe4/tfe.c tfe4/tfe.c
@@@ @@@
`tfe.ui` The ui file `tfe.ui` is the same as `tfe3.ui` in the previous section.
@@@include
tfe4/tfe.ui
@@@
`tfe.gresource.xml` `tfe.gresource.xml`

View file

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface> <interface>
<object class="GtkApplicationWindow" id="win"> <object class="GtkApplicationWindow" id="win">
<property name="title">file editor</property> <property name="title">file editor</property>
@ -8,40 +9,40 @@
<property name="orientation">GTK_ORIENTATION_VERTICAL</property> <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<child> <child>
<object class="GtkBox" id="boxh"> <object class="GtkBox" id="boxh">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property> <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<child> <child>
<object class="GtkLabel" id="dmy1"> <object class="GtkLabel" id="dmy1">
<property name="width-chars">10</property> <property name="width-chars">10</property>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkButton" id="btnn"> <object class="GtkButton" id="btnn">
<property name="label">New</property> <property name="label">New</property>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkButton" id="btno"> <object class="GtkButton" id="btno">
<property name="label">Open</property> <property name="label">Open</property>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkLabel" id="dmy2"> <object class="GtkLabel" id="dmy2">
<property name="hexpand">TRUE</property> <property name="hexpand">TRUE</property>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkButton" id="btns"> <object class="GtkButton" id="btns">
<property name="label">Save</property> <property name="label">Save</property>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkButton" id="btnc"> <object class="GtkButton" id="btnc">
<property name="label">Close</property> <property name="label">Close</property>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkLabel" id="dmy3"> <object class="GtkLabel" id="dmy3">
<property name="width-chars">10</property> <property name="width-chars">10</property>
</object> </object>
</child> </child>
</object> </object>
@ -56,4 +57,3 @@
</child> </child>
</object> </object>
</interface> </interface>

View file

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface> <interface>
<object class="GtkApplicationWindow" id="win"> <object class="GtkApplicationWindow" id="win">
<property name="title">file editor</property> <property name="title">file editor</property>
@ -8,40 +9,40 @@
<property name="orientation">GTK_ORIENTATION_VERTICAL</property> <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<child> <child>
<object class="GtkBox" id="boxh"> <object class="GtkBox" id="boxh">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property> <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<child> <child>
<object class="GtkLabel" id="dmy1"> <object class="GtkLabel" id="dmy1">
<property name="width-chars">10</property> <property name="width-chars">10</property>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkButton" id="btnn"> <object class="GtkButton" id="btnn">
<property name="label">New</property> <property name="label">New</property>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkButton" id="btno"> <object class="GtkButton" id="btno">
<property name="label">Open</property> <property name="label">Open</property>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkLabel" id="dmy2"> <object class="GtkLabel" id="dmy2">
<property name="hexpand">TRUE</property> <property name="hexpand">TRUE</property>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkButton" id="btns"> <object class="GtkButton" id="btns">
<property name="label">Save</property> <property name="label">Save</property>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkButton" id="btnc"> <object class="GtkButton" id="btnc">
<property name="label">Close</property> <property name="label">Close</property>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkLabel" id="dmy3"> <object class="GtkLabel" id="dmy3">
<property name="width-chars">10</property> <property name="width-chars">10</property>
</object> </object>
</child> </child>
</object> </object>
@ -56,4 +57,3 @@
</child> </child>
</object> </object>
</interface> </interface>

View file

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface> <interface>
<object class="GtkApplicationWindow" id="win"> <object class="GtkApplicationWindow" id="win">
<property name="title">file editor</property> <property name="title">file editor</property>
@ -8,44 +9,40 @@
<property name="orientation">GTK_ORIENTATION_VERTICAL</property> <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<child> <child>
<object class="GtkBox" id="boxh"> <object class="GtkBox" id="boxh">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property> <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<child> <child>
<object class="GtkLabel" id="dmy1"> <object class="GtkLabel" id="dmy1">
<property name="width-chars">10</property> <property name="width-chars">10</property>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkButton" id="btnn"> <object class="GtkButton" id="btnn">
<property name="label">_New</property> <property name="label">New</property>
<property name="use-underline">TRUE</property>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkButton" id="btno"> <object class="GtkButton" id="btno">
<property name="label">_Open</property> <property name="label">Open</property>
<property name="use-underline">TRUE</property>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkLabel" id="dmy2"> <object class="GtkLabel" id="dmy2">
<property name="hexpand">TRUE</property> <property name="hexpand">TRUE</property>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkButton" id="btns"> <object class="GtkButton" id="btns">
<property name="label">_Save</property> <property name="label">Save</property>
<property name="use-underline">TRUE</property>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkButton" id="btnc"> <object class="GtkButton" id="btnc">
<property name="label">_Close</property> <property name="label">Close</property>
<property name="use-underline">TRUE</property>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkLabel" id="dmy3"> <object class="GtkLabel" id="dmy3">
<property name="width-chars">10</property> <property name="width-chars">10</property>
</object> </object>
</child> </child>
</object> </object>

View file

@ -1,46 +1,31 @@
#include "tfe.h" #include "tfe.h"
static void static void
open_clicked (GtkWidget *btno, GtkNotebook *nb) { open_cb (GtkNotebook *nb) {
notebook_page_open (nb); notebook_page_open (nb);
} }
static void static void
new_clicked (GtkWidget *btnn, GtkNotebook *nb) { new_cb (GtkNotebook *nb) {
notebook_page_new (nb); notebook_page_new (nb);
} }
static void static void
save_clicked (GtkWidget *btns, GtkNotebook *nb) { save_cb (GtkNotebook *nb) {
notebook_page_save (nb); notebook_page_save (nb);
} }
static void static void
close_clicked (GtkWidget *btnc, GtkNotebook *nb) { close_cb (GtkNotebook *nb) {
GtkWidget *win; notebook_page_close (GTK_NOTEBOOK (nb));
GtkWidget *boxv;
gint i;
if (gtk_notebook_get_n_pages (nb) == 1) {
boxv = gtk_widget_get_parent (GTK_WIDGET (nb));
win = gtk_widget_get_parent (boxv);
gtk_window_destroy (GTK_WINDOW (win));
} else {
i = gtk_notebook_get_current_page (nb);
gtk_notebook_remove_page (GTK_NOTEBOOK (nb), i);
}
} }
static void static void
tfe_activate (GApplication *application) { tfe_activate (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application); GtkApplication *app = GTK_APPLICATION (application);
GtkWidget *win; GtkWidget *win = GTK_WIDGET (gtk_application_get_active_window (app));
GtkWidget *boxv; GtkWidget *boxv = gtk_window_get_child (GTK_WINDOW (win));
GtkNotebook *nb; GtkNotebook *nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
win = GTK_WIDGET (gtk_application_get_active_window (app));
boxv = gtk_window_get_child (GTK_WINDOW (win));
nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
notebook_page_new (nb); notebook_page_new (nb);
gtk_widget_show (GTK_WIDGET (win)); gtk_widget_show (GTK_WIDGET (win));
@ -49,15 +34,11 @@ tfe_activate (GApplication *application) {
static void static void
tfe_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) { tfe_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) {
GtkApplication *app = GTK_APPLICATION (application); GtkApplication *app = GTK_APPLICATION (application);
GtkWidget *win; GtkWidget *win = GTK_WIDGET (gtk_application_get_active_window (app));
GtkWidget *boxv; GtkWidget *boxv = gtk_window_get_child (GTK_WINDOW (win));
GtkNotebook *nb; GtkNotebook *nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
int i; int i;
win = GTK_WIDGET (gtk_application_get_active_window (app));
boxv = gtk_window_get_child (GTK_WINDOW (win));
nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
for (i = 0; i < n_files; i++) for (i = 0; i < n_files; i++)
notebook_page_new_with_file (nb, files[i]); notebook_page_new_with_file (nb, files[i]);
if (gtk_notebook_get_n_pages (nb) == 0) if (gtk_notebook_get_n_pages (nb) == 0)
@ -65,13 +46,12 @@ tfe_open (GApplication *application, GFile ** files, gint n_files, const gchar *
gtk_widget_show (win); gtk_widget_show (win);
} }
static void static void
tfe_startup (GApplication *application) { tfe_startup (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application); GtkApplication *app = GTK_APPLICATION (application);
GtkBuilder *build;
GtkApplicationWindow *win; GtkApplicationWindow *win;
GtkNotebook *nb; GtkNotebook *nb;
GtkBuilder *build;
GtkButton *btno; GtkButton *btno;
GtkButton *btnn; GtkButton *btnn;
GtkButton *btns; GtkButton *btns;
@ -85,10 +65,10 @@ tfe_startup (GApplication *application) {
btnn = GTK_BUTTON (gtk_builder_get_object (build, "btnn")); btnn = GTK_BUTTON (gtk_builder_get_object (build, "btnn"));
btns = GTK_BUTTON (gtk_builder_get_object (build, "btns")); btns = GTK_BUTTON (gtk_builder_get_object (build, "btns"));
btnc = GTK_BUTTON (gtk_builder_get_object (build, "btnc")); btnc = GTK_BUTTON (gtk_builder_get_object (build, "btnc"));
g_signal_connect (btno, "clicked", G_CALLBACK (open_clicked), nb); g_signal_connect_swapped (btno, "clicked", G_CALLBACK (open_cb), nb);
g_signal_connect (btnn, "clicked", G_CALLBACK (new_clicked), nb); g_signal_connect_swapped (btnn, "clicked", G_CALLBACK (new_cb), nb);
g_signal_connect (btns, "clicked", G_CALLBACK (save_clicked), nb); g_signal_connect_swapped (btns, "clicked", G_CALLBACK (save_cb), nb);
g_signal_connect (btnc, "clicked", G_CALLBACK (close_clicked), nb); g_signal_connect_swapped (btnc, "clicked", G_CALLBACK (close_cb), nb);
g_object_unref(build); g_object_unref(build);
GdkDisplay *display; GdkDisplay *display;

View file

@ -10,45 +10,68 @@ get_untitled () {
return g_strdup_printf ("Untitled%u", c); return g_strdup_printf ("Untitled%u", c);
} }
static void static TfeTextView *
file_changed (TfeTextView *tv, GtkNotebook *nb) { get_current_textview (GtkNotebook *nb) {
GFile *file; int i;
char *filename;
GtkWidget *scr;
GtkWidget *label;
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);
else
filename = get_untitled ();
label = gtk_label_new (filename);
gtk_notebook_set_tab_label (nb, scr, label);
g_object_unref (file);
g_free (filename);
}
/* Save the contents in the current page */
void
notebook_page_save(GtkNotebook *nb) {
gint i;
GtkWidget *scr; GtkWidget *scr;
GtkWidget *tv; GtkWidget *tv;
i = gtk_notebook_get_current_page (nb); i = gtk_notebook_get_current_page (nb);
scr = gtk_notebook_get_nth_page (nb, i); scr = gtk_notebook_get_nth_page (nb, i);
tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr)); tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
return TFE_TEXT_VIEW (tv);
}
static void
file_changed_cb (TfeTextView *tv, GtkNotebook *nb) {
GtkWidget *scr;
GtkWidget *label;
GFile *file;
char *filename;
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 (nb, scr, label);
}
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)); tfe_text_view_save (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 static void
notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) { notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) {
GtkWidget *scr; GtkWidget *scr = gtk_scrolled_window_new ();
GtkNotebookPage *nbp; GtkNotebookPage *nbp;
GtkWidget *lab; GtkWidget *lab;
gint i; int i;
scr = gtk_scrolled_window_new ();
gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR); gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv); gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
@ -57,11 +80,11 @@ notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) {
nbp = gtk_notebook_get_page (nb, scr); nbp = gtk_notebook_get_page (nb, scr);
g_object_set (nbp, "tab-expand", TRUE, NULL); g_object_set (nbp, "tab-expand", TRUE, NULL);
gtk_notebook_set_current_page (nb, i); gtk_notebook_set_current_page (nb, i);
g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed), nb); g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed_cb), nb);
} }
static void static void
open_response (TfeTextView *tv, gint response, GtkNotebook *nb) { open_response (TfeTextView *tv, int response, GtkNotebook *nb) {
GFile *file; GFile *file;
char *filename; char *filename;

View file

@ -1,6 +1,9 @@
void void
notebook_page_save(GtkNotebook *nb); notebook_page_save(GtkNotebook *nb);
void
notebook_page_close (GtkNotebook *nb);
void void
notebook_page_open (GtkNotebook *nb); notebook_page_open (GtkNotebook *nb);

View 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>

91
src/tfe6/css.c Normal file
View file

@ -0,0 +1,91 @@
#include "tfe.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);
}

11
src/tfe6/css.h Normal file
View file

@ -0,0 +1,11 @@
/* 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);

27
src/tfe6/menu.ui Executable file
View 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/tfe6/meson.build Normal file
View 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', 'tfenotebook.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)

View file

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

11
src/tfe6/tfe.h Normal file
View file

@ -0,0 +1,11 @@
/* tfe.h */
#include <gtk/gtk.h>
#include "../tfetextview/tfetextview.h"
#include "tfenotebook.h"
#include "css.h"
void
tfe_application_quit (GtkWindow *win);

140
src/tfe6/tfe.ui Normal file
View file

@ -0,0 +1,140 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkApplicationWindow" id="win">
<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>
</object>
<object class="GtkDialog" id="pref">
<property name="title">Preferences</property>
<property name="resizable">FALSE</property>
<property name="modal">TRUE</property>
<property name="transient-for">win</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>
</object>
<object class="GtkDialog" id="alert">
<property name="title">Are you sure?</property>
<property name="resizable">FALSE</property>
<property name="modal">TRUE</property>
<property name="transient-for">win</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>
<signal name="response" handler="alert_response_cb" swapped="NO" object="nb"></signal>
</object>
</interface>

230
src/tfe6/tfeapplication.c Normal file
View file

@ -0,0 +1,230 @@
#include "tfe.h"
static GtkDialog *pref;
static GtkFontButton *fontbtn;
static GSettings *settings;
static GtkDialog *alert;
static GtkLabel *lb_alert;
static GtkButton *btn_accept;
static gulong pref_close_request_handler_id = 0;
static gulong alert_close_request_handler_id = 0;
static gboolean is_quit;
/* ----- 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) {
is_quit = false;
if (has_saved (GTK_NOTEBOOK (nb)))
notebook_page_close (GTK_NOTEBOOK (nb));
else {
gtk_label_set_text (lb_alert, "Contents aren't saved yet.\nAre you sure to close?");
gtk_button_set_label (btn_accept, "Close");
gtk_widget_show (GTK_WIDGET (alert));
}
}
/* ----- menu or accelerator => action => handlers ----- */
static void
open_activated (GSimpleAction *action, GVariant *parameter, gpointer nb) {
open_cb (GTK_NOTEBOOK (nb));
}
static void
save_activated (GSimpleAction *action, GVariant *parameter, gpointer nb) {
save_cb (GTK_NOTEBOOK (nb));
}
static void
close_activated (GSimpleAction *action, GVariant *parameter, gpointer nb) {
close_cb (GTK_NOTEBOOK (nb));
}
static void
new_activated (GSimpleAction *action, GVariant *parameter, gpointer nb) {
notebook_page_new (GTK_NOTEBOOK (nb));
}
static void
saveas_activated (GSimpleAction *action, GVariant *parameter, gpointer nb) {
notebook_page_saveas (GTK_NOTEBOOK (nb));
}
static gboolean
dialog_close_cb (GtkDialog *dialog, gpointer user_data) {
gtk_widget_hide (GTK_WIDGET (dialog));
return TRUE;
}
static void
font_set_cb (GtkFontButton *fontbtn, gpointer user_data) {
GtkWindow *win = GTK_WINDOW (user_data);
PangoFontDescription *pango_font_desc;
pango_font_desc = gtk_font_chooser_get_font_desc (GTK_FONT_CHOOSER (fontbtn));
set_font_for_display_with_pango_font_desc (win, pango_font_desc);
}
static void
pref_activated (GSimpleAction *action, GVariant *parameter, gpointer nb) {
gtk_widget_show (GTK_WIDGET (pref));
}
void
alert_response_cb (GtkDialog *alert, int response_id, gpointer user_data) {
GtkNotebook *nb = GTK_NOTEBOOK (user_data);
GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW);
gtk_widget_hide (GTK_WIDGET (alert));
if (response_id == GTK_RESPONSE_ACCEPT) {
if (is_quit)
tfe_application_quit (GTK_WINDOW (win));
else
notebook_page_close (nb);
}
}
static void
quit_activated (GSimpleAction *action, GVariant *parameter, gpointer nb) {
GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW);
is_quit = true;
if (has_saved_all (GTK_NOTEBOOK (nb)))
tfe_application_quit (GTK_WINDOW (win));
else {
gtk_label_set_text (lb_alert, "Contents aren't saved yet.\nAre you sure to quit?");
gtk_button_set_label (btn_accept, "Quit");
gtk_widget_show (GTK_WIDGET (alert));
}
}
/* ----- quit application ----- */
void
tfe_application_quit (GtkWindow *win) {
if (pref_close_request_handler_id > 0)
g_signal_handler_disconnect (pref, pref_close_request_handler_id);
if (alert_close_request_handler_id > 0)
g_signal_handler_disconnect (alert, alert_close_request_handler_id);
g_clear_object (&settings);
gtk_window_destroy (GTK_WINDOW (alert));
gtk_window_destroy (GTK_WINDOW (pref));
gtk_window_destroy (win);
}
/* ----- 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));
GtkWidget *boxv = gtk_window_get_child (GTK_WINDOW (win));
GtkNotebook *nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
notebook_page_new (nb);
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));
GtkWidget *boxv = gtk_window_get_child (GTK_WINDOW (win));
GtkNotebook *nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
int i;
for (i = 0; i < n_files; i++)
notebook_page_new_with_file (nb, files[i]);
if (gtk_notebook_get_n_pages (nb) == 0)
notebook_page_new (nb);
gtk_widget_show (win);
}
static void
tfe_startup (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
GtkBuilder *build;
GtkApplicationWindow *win;
GtkNotebook *nb;
GtkMenuButton *btnm;
GMenuModel *menu;
int i;
build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe/tfe.ui");
win = GTK_APPLICATION_WINDOW (gtk_builder_get_object (build, "win"));
gtk_window_set_application (GTK_WINDOW (win), app);
nb = GTK_NOTEBOOK (gtk_builder_get_object (build, "nb"));
btnm = GTK_MENU_BUTTON (gtk_builder_get_object (build, "btnm"));
pref = GTK_DIALOG (gtk_builder_get_object (build, "pref"));
pref_close_request_handler_id = g_signal_connect (GTK_DIALOG (pref), "close-request", G_CALLBACK (dialog_close_cb), NULL);
fontbtn = GTK_FONT_BUTTON (gtk_builder_get_object (build, "fontbtn"));
g_signal_connect (fontbtn, "font-set", G_CALLBACK (font_set_cb), win);
settings = g_settings_new ("com.github.ToshioCP.tfe");
g_settings_bind (settings, "font", fontbtn, "font", G_SETTINGS_BIND_DEFAULT);
alert = GTK_DIALOG (gtk_builder_get_object (build, "alert"));
alert_close_request_handler_id = g_signal_connect (GTK_DIALOG (alert), "close-request", G_CALLBACK (dialog_close_cb), NULL);
lb_alert = GTK_LABEL (gtk_builder_get_object (build, "lb_alert"));
btn_accept = GTK_BUTTON (gtk_builder_get_object (build, "btn_accept"));
g_object_unref(build);
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 (btnm, menu);
g_object_unref(build);
/* ----- 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), nb);
/* ----- 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);
font_set_cb (fontbtn, GTK_WINDOW (win));
}
/* ----- 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;
}

210
src/tfe6/tfenotebook.c Normal file
View file

@ -0,0 +1,210 @@
#include "tfe.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);
tfe_application_quit (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);
}

26
src/tfe6/tfenotebook.h Normal file
View file

@ -0,0 +1,26 @@
/* tfenotbook.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);

View file

@ -64,77 +64,57 @@ GFile *
tfe_text_view_get_file (TfeTextView *tv) { tfe_text_view_get_file (TfeTextView *tv) {
g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL); g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);
return g_file_dup (tv->file); if (G_IS_FILE (tv->file))
return g_file_dup (tv->file);
else
return NULL;
} }
static void static gboolean
open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) { save_file (GFile *file, GtkTextBuffer *tb, GtkWindow *win) {
GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); GtkTextIter start_iter;
GFile *file; GtkTextIter end_iter;
char *contents; gchar *contents;
gsize length;
GtkWidget *message_dialog; GtkWidget *message_dialog;
GError *err = NULL; GError *err = NULL;
if (response != GTK_RESPONSE_ACCEPT) /* This function doesn't check G_IS_FILE (file). The caller should check it. */
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL); gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)))) contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR); if (g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err)) {
else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */ gtk_text_buffer_set_modified (tb, FALSE);
if (G_IS_FILE (file)) return TRUE;
g_object_unref (file); } else {
message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL, message_dialog = gtk_message_dialog_new (win, GTK_DIALOG_MODAL,
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
"%s.\n", err->message); "%s.\n", err->message);
g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL); g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
gtk_widget_show (message_dialog); gtk_widget_show (message_dialog);
g_error_free (err); g_error_free (err);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR); return FALSE;
} else {
gtk_text_buffer_set_text (tb, contents, length);
g_free (contents);
if (G_IS_FILE (tv->file))
g_object_unref (tv->file);
tv->file = file;
gtk_text_buffer_set_modified (tb, FALSE);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
} }
gtk_window_destroy (GTK_WINDOW (dialog));
}
void
tfe_text_view_open (TfeTextView *tv, GtkWidget *win) {
g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
g_return_if_fail (GTK_IS_WINDOW (win));
GtkWidget *dialog;
dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN,
"Cancel", GTK_RESPONSE_CANCEL,
"Open", GTK_RESPONSE_ACCEPT,
NULL);
g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
gtk_widget_show (dialog);
} }
static void static void
saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) { saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
GFile *file; GFile *file;
GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
gtk_window_destroy (GTK_WINDOW (dialog));
if (response == GTK_RESPONSE_ACCEPT) { if (response == GTK_RESPONSE_ACCEPT) {
file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
if (G_IS_FILE(file)) { if (! G_IS_FILE (file))
g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n");
else {
save_file(file, tb, GTK_WINDOW (win));
if (G_IS_FILE (tv->file)) if (G_IS_FILE (tv->file))
g_object_unref (tv->file); g_object_unref (tv->file);
tv->file = file; tv->file = file;
gtk_text_buffer_set_modified (tb, TRUE); gtk_text_buffer_set_modified (tb, FALSE);
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0); g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
tfe_text_view_save (TFE_TEXT_VIEW (tv));
} }
} }
gtk_window_destroy (GTK_WINDOW (dialog));
} }
void void
@ -142,36 +122,17 @@ tfe_text_view_save (TfeTextView *tv) {
g_return_if_fail (TFE_IS_TEXT_VIEW (tv)); g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
GtkTextIter start_iter;
GtkTextIter end_iter;
gchar *contents;
GtkWidget *message_dialog;
GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW); GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
GError *err = NULL;
if (! gtk_text_buffer_get_modified (tb)) if (! gtk_text_buffer_get_modified (tb))
return; /* no need to save it */ return; /* no need to save it */
else if (tv->file == NULL) else if (tv->file == NULL)
tfe_text_view_saveas (tv); tfe_text_view_saveas (tv);
else if (! G_IS_FILE (tv->file))
g_error ("TfeTextView: The pointer in this object isn't NULL nor GFile object.\n");
else { else {
gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter); if (save_file (tv->file, tb, GTK_WINDOW (win)))
contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
if (g_file_replace_contents (tv->file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err))
gtk_text_buffer_set_modified (tb, FALSE); gtk_text_buffer_set_modified (tb, FALSE);
else {
/* It is possible that tv->file is broken or you don't have permission to write. */
/* It is a good idea to set tv->file to NULL. */
if (G_IS_FILE (tv->file))
g_object_unref (tv->file);
tv->file =NULL;
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
message_dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL,
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
"%s.\n", err->message);
g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
gtk_widget_show (message_dialog);
g_error_free (err);
}
} }
} }
@ -210,6 +171,58 @@ tfe_text_view_new_with_file (GFile *file) {
return tv; return tv;
} }
static void
open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
GFile *file;
char *contents;
gsize length;
GtkWidget *message_dialog;
GError *err = NULL;
if (response != GTK_RESPONSE_ACCEPT)
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)))) {
g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n");
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
} else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
if (G_IS_FILE (file))
g_object_unref (file);
message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
"%s.\n", err->message);
g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
gtk_widget_show (message_dialog);
g_error_free (err);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
} else {
gtk_text_buffer_set_text (tb, contents, length);
g_free (contents);
if (G_IS_FILE (tv->file))
g_object_unref (tv->file);
tv->file = file;
gtk_text_buffer_set_modified (tb, FALSE);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
}
gtk_window_destroy (GTK_WINDOW (dialog));
}
void
tfe_text_view_open (TfeTextView *tv, GtkWidget *win) {
g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
g_return_if_fail (GTK_IS_WINDOW (win));
GtkWidget *dialog;
dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN,
"Cancel", GTK_RESPONSE_CANCEL,
"Open", GTK_RESPONSE_ACCEPT,
NULL);
g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
gtk_widget_show (dialog);
}
GtkWidget * GtkWidget *
tfe_text_view_new (void) { tfe_text_view_new (void) {
return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL)); return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));