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/hello.txt
src/tfe/resources.c
src/tfe4/_build
src/tfe5/_build
src/tfe5/hello.txt
src/tfe6/_build
src/menu/a.out
src/color/_build
src/turtle/_build

View file

@ -32,5 +32,6 @@ You can read it without download.
1. [Menu and action](gfm/sec16.md)
1. [Stateful action](gfm/sec17.md)
1. [Ui file for menu and action entries](gfm/sec18.md)
1. [GtkDrawingArea and Cairo](gfm/sec19.md)
1. [Combine GtkDrawingArea and TfeTextView](gfm/sec20.md)
1. [Upgrade text file editor](gfm/sec19.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.
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 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.

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) {
3 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
4 GFile *file;
5
6 if (response == GTK_RESPONSE_ACCEPT) {
7 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
8 if (G_IS_FILE(file)) {
9 if (G_IS_FILE (tv->file))
10 g_object_unref (tv->file);
11 tv->file = file;
12 gtk_text_buffer_set_modified (tb, TRUE);
13 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
14 tfe_text_view_save (TFE_TEXT_VIEW (tv));
15 }
16 }
17 gtk_window_destroy (GTK_WINDOW (dialog));
18 }
19
20 void
21 tfe_text_view_save (TfeTextView *tv) {
22 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
23
24 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
25 GtkTextIter start_iter;
26 GtkTextIter end_iter;
27 gchar *contents;
28 GtkWidget *message_dialog;
29 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
30 GError *err = NULL;
31
32 if (! gtk_text_buffer_get_modified (tb))
33 return; /* no need to save it */
34 else if (tv->file == NULL)
35 tfe_text_view_saveas (tv);
5 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
6
7 gtk_window_destroy (GTK_WINDOW (dialog));
8 if (response == GTK_RESPONSE_ACCEPT) {
9 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
10 if (! G_IS_FILE (file))
11 g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n");
12 else {
13 save_file(file, tb, GTK_WINDOW (win));
14 if (G_IS_FILE (tv->file))
15 g_object_unref (tv->file);
16 tv->file = file;
17 gtk_text_buffer_set_modified (tb, FALSE);
18 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
19 }
20 }
21 }
22
23 void
24 tfe_text_view_save (TfeTextView *tv) {
25 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
26
27 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
28 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
29
30 if (! gtk_text_buffer_get_modified (tb))
31 return; /* no need to save it */
32 else if (tv->file == NULL)
33 tfe_text_view_saveas (tv);
34 else if (! G_IS_FILE (tv->file))
35 g_error ("TfeTextView: The pointer in this object isn't NULL nor GFile object.\n");
36 else {
37 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
38 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
39 if (g_file_replace_contents (tv->file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err))
40 gtk_text_buffer_set_modified (tb, FALSE);
41 else {
42 /* It is possible that tv->file is broken or you don't have permission to write. */
43 /* It is a good idea to set tv->file to NULL. */
44 if (G_IS_FILE (tv->file))
45 g_object_unref (tv->file);
46 tv->file =NULL;
47 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
48 message_dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL,
49 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
50 "%s.\n", err->message);
51 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
52 gtk_widget_show (message_dialog);
53 g_error_free (err);
54 }
37 if (save_file (tv->file, tb, GTK_WINDOW (win)))
38 gtk_text_buffer_set_modified (tb, FALSE);
39 }
40 }
41
42 void
43 tfe_text_view_saveas (TfeTextView *tv) {
44 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
45
46 GtkWidget *dialog;
47 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
48
49 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
50 "Cancel", GTK_RESPONSE_CANCEL,
51 "Save", GTK_RESPONSE_ACCEPT,
52 NULL);
53 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
54 gtk_widget_show (dialog);
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.
@ -300,45 +284,46 @@ Otherwise probably bad things will happen.
9
10 if (response != GTK_RESPONSE_ACCEPT)
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))))
13 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
14 else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
15 if (G_IS_FILE (file))
16 g_object_unref (file);
17 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
18 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
19 "%s.\n", err->message);
20 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
21 gtk_widget_show (message_dialog);
22 g_error_free (err);
23 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
24 } else {
25 gtk_text_buffer_set_text (tb, contents, length);
26 g_free (contents);
27 if (G_IS_FILE (tv->file))
28 g_object_unref (tv->file);
29 tv->file = file;
30 gtk_text_buffer_set_modified (tb, FALSE);
31 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
32 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
33 }
34 gtk_window_destroy (GTK_WINDOW (dialog));
35 }
36
37 void
38 tfe_text_view_open (TfeTextView *tv, GtkWidget *win) {
39 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
40 g_return_if_fail (GTK_IS_WINDOW (win));
41
42 GtkWidget *dialog;
43
44 dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN,
45 "Cancel", GTK_RESPONSE_CANCEL,
46 "Open", GTK_RESPONSE_ACCEPT,
47 NULL);
48 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
49 gtk_widget_show (dialog);
50 }
12 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)))) {
13 g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n");
14 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
15 } else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
16 if (G_IS_FILE (file))
17 g_object_unref (file);
18 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
19 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
20 "%s.\n", err->message);
21 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
22 gtk_widget_show (message_dialog);
23 g_error_free (err);
24 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
25 } else {
26 gtk_text_buffer_set_text (tb, contents, length);
27 g_free (contents);
28 if (G_IS_FILE (tv->file))
29 g_object_unref (tv->file);
30 tv->file = file;
31 gtk_text_buffer_set_modified (tb, FALSE);
32 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
33 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
34 }
35 gtk_window_destroy (GTK_WINDOW (dialog));
36 }
37
38 void
39 tfe_text_view_open (TfeTextView *tv, GtkWidget *win) {
40 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
41 g_return_if_fail (GTK_IS_WINDOW (win));
42
43 GtkWidget *dialog;
44
45 dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN,
46 "Cancel", GTK_RESPONSE_CANCEL,
47 "Open", GTK_RESPONSE_ACCEPT,
48 NULL);
49 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
50 gtk_widget_show (dialog);
51 }
~~~
- 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) {
3 g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);
4
5 return g_file_dup (tv->file);
6 }
5 if (G_IS_FILE (tv->file))
6 return g_file_dup (tv->file);
7 else
8 return NULL;
9 }
~~~
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`.
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.
There's no "TfeNotebook" object.
@ -13,30 +13,34 @@ There's no "TfeNotebook" object.
2 notebook_page_save(GtkNotebook *nb);
3
4 void
5 notebook_page_open (GtkNotebook *nb);
5 notebook_page_close (GtkNotebook *nb);
6
7 void
8 notebook_page_new_with_file (GtkNotebook *nb, GFile *file);
8 notebook_page_open (GtkNotebook *nb);
9
10 void
11 notebook_page_new (GtkNotebook *nb);
11 notebook_page_new_with_file (GtkNotebook *nb, GFile *file);
12
13 void
14 notebook_page_new (GtkNotebook *nb);
15
~~~
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.
- 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.
The GFile `file` is copied and inserted to the TfeTextView object.
- 4-5: `notebook_page_open` shows a file chooser dialog. Then, user chooses a file and the file is inserted into GtkTextBuffer.
- 1-2: `notebook_page_save` saves the contents in GtkTextBuffer into the file, which is got from the TfeTextView.
- 1-2: `notebook_page_save` saves the current page to the file of which the name specified in the tab.
If the name is `untitled` or `untitled` followed by digits, FileChooserDialog appears and a user can choose or specify a filename.
- 4-5: `notebook_page_close` closes the current page.
- 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.
- 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`
- `tef_text_view_open`
- `tfe_text_view_new_with_file`
- `tfe_text_view_new`
respectively.
@ -44,7 +48,7 @@ There are two layers.
One of them is `tfe_text_view ...`, which is the lower 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
@ -60,57 +64,56 @@ Now let's look at each program of the functions.
9
10 static void
11 notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) {
12 GtkWidget *scr;
12 GtkWidget *scr = gtk_scrolled_window_new ();
13 GtkNotebookPage *nbp;
14 GtkWidget *lab;
15 gint i;
16 scr = gtk_scrolled_window_new ();
17
18 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
19 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
20 lab = gtk_label_new (filename);
21 i = gtk_notebook_append_page (nb, scr, lab);
22 nbp = gtk_notebook_get_page (nb, scr);
23 g_object_set (nbp, "tab-expand", TRUE, NULL);
24 gtk_notebook_set_current_page (nb, i);
25 g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed), nb);
26 }
27
28 void
29 notebook_page_new (GtkNotebook *nb) {
30 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
31
32 GtkWidget *tv;
33 char *filename;
34
35 tv = tfe_text_view_new ();
36 filename = get_untitled ();
37 notebook_page_build (nb, tv, filename);
38 }
15 int i;
16
17 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 lab = gtk_label_new (filename);
20 i = gtk_notebook_append_page (nb, scr, lab);
21 nbp = gtk_notebook_get_page (nb, scr);
22 g_object_set (nbp, "tab-expand", TRUE, NULL);
23 gtk_notebook_set_current_page (nb, i);
24 g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed_cb), nb);
25 }
26
27 void
28 notebook_page_new (GtkNotebook *nb) {
29 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
30
31 GtkWidget *tv;
32 char *filename;
33
34 tv = tfe_text_view_new ();
35 filename = get_untitled ();
36 notebook_page_build (nb, tv, filename);
37 }
~~~
- 28-38: `notebook_page_new` function.
- 30: `g_return_if_fail` is used to check the argument.
- 35: Generates TfeTextView object.
- 36: Generates filename, which is "Untitled", "Untitled2", ... .
- 27-37: `notebook_page_new` function.
- 29: `g_return_if_fail` is used to check the argument.
- 34: Generates TfeTextView object.
- 35: Generates filename, which is "Untitled", "Untitled1", ... .
- 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.
- 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.
- 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 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 caller of `get_untitled` is in charge of freeing the string.
- 37: calls `notebook_page_build` to build the contents of the page.
- 10- 26: `notebook_page_build` function.
- 16: Generates GtkScrolledWindow.
- 18: 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.
- 20-21: Generates GtkLabel, then appends it to GtkNotebookPage.
- 22-23: Sets "tab-expand" property to TRUE.
- 36: calls `notebook_page_build` to build the contents of the page.
- 10- 25: `notebook_page_build` function.
- 12: Generates GtkScrolledWindow.
- 17: Sets the wrap mode of `tv` to GTK_WRAP_WORD_CHAR so that lines are broken between words or graphemes.
- 18: Inserts `tv` to GtkscrolledWindow as a child.
- 19-20: Generates GtkLabel, then appends it to GtkNotebookPage.
- 21-22: Sets "tab-expand" property to TRUE.
The function g\_object\_set sets properties on an object.
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 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.
- 25: Connects "change-file" signal and `file_changed` handler.
- 23: Sets the current page of `nb` to the newly generated page.
- 24: Connects "change-file" signal and `file_changed_cb` handler.
## notebook\_page\_new\_with\_file
@ -139,7 +142,7 @@ The return value NULL means that an error has happened.
~~~C
1 static void
2 open_response (TfeTextView *tv, gint response, GtkNotebook *nb) {
2 open_response (TfeTextView *tv, int response, GtkNotebook *nb) {
3 GFile *file;
4 char *filename;
5
@ -181,63 +184,106 @@ Such object has floating reference.
You need to call `g_object_ref_sink` first.
Then the floating reference is converted into an ordinary reference.
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`.
- 12-16: Otherwise, everything is okay.
Gets the filename, builds the contents of the page.
## notebook\_page\_save
## notebook\_page\_close
~~~C
1 void
2 notebook_page_save(GtkNotebook *nb) {
3 gint i;
2 notebook_page_close (GtkNotebook *nb) {
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;
5 GtkWidget *tv;
6
7 i = gtk_notebook_get_current_page (nb);
8 scr = gtk_notebook_get_nth_page (nb, i);
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 }
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.
- 10: Call `tfe_text_view_save`.
- 13-21: `notebook_page_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.
This handler changes the label of GtkNotebookPage.
~~~C
1 static void
2 file_changed (TfeTextView *tv, GtkNotebook *nb) {
3 GFile *file;
4 char *filename;
5 GtkWidget *scr;
6 GtkWidget *label;
2 file_changed_cb (TfeTextView *tv, GtkNotebook *nb) {
3 GtkWidget *scr;
4 GtkWidget *label;
5 GFile *file;
6 char *filename;
7
8 file = tfe_text_view_get_file (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);
12 else
13 filename = get_untitled ();
14 label = gtk_label_new (filename);
15 gtk_notebook_set_tab_label (nb, scr, label);
16 g_object_unref (file);
17 g_free (filename);
18 }
12 g_object_unref (file);
13 } else
14 filename = get_untitled ();
15 label = gtk_label_new (filename);
16 gtk_notebook_set_tab_label (nb, scr, label);
17 }
~~~
- 8: Gets GFile from TfeTextView.
- 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`.
Otherwise (file is NULL), assigns untitled string to `filename`.
- 14-15: Generates a label with the filename and inserts it into GtkNotebookPage.
- 16-17: Unrefs `file` and frees `filename`.
- 10-12: If `file` points GFile, then assigns the filename of the GFile into `filename`.
Then, unref the GFile object `file`.
- 13-14: Otherwise (file is NULL), assigns untitled string to `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)

View file

@ -3,12 +3,12 @@ Up: [Readme.md](../Readme.md), Prev: [Section 13](sec13.md), Next: [Section 15]
# tfeapplication.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.
- Build widgets using ui file.
- Connect button signals and their handlers.
- Manage CSS.
- Builds widgets using ui file.
- Connects button signals and their handlers.
- Manages CSS.
## main
@ -53,9 +53,9 @@ The handler is as follows.
1 static void
2 tfe_startup (GApplication *application) {
3 GtkApplication *app = GTK_APPLICATION (application);
4 GtkApplicationWindow *win;
5 GtkNotebook *nb;
6 GtkBuilder *build;
4 GtkBuilder *build;
5 GtkApplicationWindow *win;
6 GtkNotebook *nb;
7 GtkButton *btno;
8 GtkButton *btnn;
9 GtkButton *btns;
@ -69,10 +69,10 @@ The handler is as follows.
17 btnn = GTK_BUTTON (gtk_builder_get_object (build, "btnn"));
18 btns = GTK_BUTTON (gtk_builder_get_object (build, "btns"));
19 btnc = GTK_BUTTON (gtk_builder_get_object (build, "btnc"));
20 g_signal_connect (btno, "clicked", G_CALLBACK (open_clicked), nb);
21 g_signal_connect (btnn, "clicked", G_CALLBACK (new_clicked), nb);
22 g_signal_connect (btns, "clicked", G_CALLBACK (save_clicked), nb);
23 g_signal_connect (btnc, "clicked", G_CALLBACK (close_clicked), nb);
20 g_signal_connect_swapped (btno, "clicked", G_CALLBACK (open_cb), nb);
21 g_signal_connect_swapped (btnn, "clicked", G_CALLBACK (new_cb), nb);
22 g_signal_connect_swapped (btns, "clicked", G_CALLBACK (save_cb), nb);
23 g_signal_connect_swapped (btnc, "clicked", G_CALLBACK (close_cb), nb);
24 g_object_unref(build);
25
26 GdkDisplay *display;
@ -119,7 +119,8 @@ If you want to set style to GtkTextView, substitute "textview" for the selector.
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.
@ -128,11 +129,10 @@ textview {padding: 10px; font-family: monospace; font-size: 12pt;}
~~~
- 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.
"monospace" is one of the generic family font keywords.
- font-size is set to 12pt.
It is a bit large, but easy on the eyes especially for elderly people.
### 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.
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
GtkWidget *
@ -184,46 +186,36 @@ They just generate a new GtkNotebookPage.
1 static void
2 tfe_activate (GApplication *application) {
3 GtkApplication *app = GTK_APPLICATION (application);
4 GtkWidget *win;
5 GtkWidget *boxv;
6 GtkNotebook *nb;
4 GtkWidget *win = GTK_WIDGET (gtk_application_get_active_window (app));
5 GtkWidget *boxv = gtk_window_get_child (GTK_WINDOW (win));
6 GtkNotebook *nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
7
8 win = GTK_WIDGET (gtk_application_get_active_window (app));
9 boxv = gtk_window_get_child (GTK_WINDOW (win));
10 nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
8 notebook_page_new (nb);
9 gtk_widget_show (GTK_WIDGET (win));
10 }
11
12 notebook_page_new (nb);
13 gtk_widget_show (GTK_WIDGET (win));
14 }
15
16 static void
17 tfe_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) {
18 GtkApplication *app = GTK_APPLICATION (application);
19 GtkWidget *win;
20 GtkWidget *boxv;
21 GtkNotebook *nb;
22 int i;
23
24 win = GTK_WIDGET (gtk_application_get_active_window (app));
25 boxv = gtk_window_get_child (GTK_WINDOW (win));
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 }
12 static void
13 tfe_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) {
14 GtkApplication *app = GTK_APPLICATION (application);
15 GtkWidget *win = GTK_WIDGET (gtk_application_get_active_window (app));
16 GtkWidget *boxv = gtk_window_get_child (GTK_WINDOW (win));
17 GtkNotebook *nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
18 int i;
19
20 for (i = 0; i < n_files; i++)
21 notebook_page_new_with_file (nb, files[i]);
22 if (gtk_notebook_get_n_pages (nb) == 0)
23 notebook_page_new (nb);
24 gtk_widget_show (win);
25 }
~~~
- 1-14: `tfe_activate`.
- 8-10: Gets GtkNotebook object.
- 12-13: Generates a new GtkNotebookPage and show the window.
- 16-33: `tfe_open`.
- 24-26: Gets GtkNotebook object.
- 28-29: Generates GtkNotebookPage with files.
- 30-31: If opening and reading file failed and no GtkNotebookPage has generated, then it generates a empty page.
- 32: Shows the window.
- 1-11: `tfe_activate`.
- 8-10: Generates a new page and shows the window.
- 12-25: `tfe_open`.
- 20-21: Generates notebook pages with files.
- 22-23: If no page has generated, maybe because of read error, then it generates a empty page.
- 24: Shows the window.
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
1 static void
2 open_clicked (GtkWidget *btno, GtkNotebook *nb) {
2 open_cb (GtkNotebook *nb) {
3 notebook_page_open (nb);
4 }
5
6 static void
7 new_clicked (GtkWidget *btnn, GtkNotebook *nb) {
7 new_cb (GtkNotebook *nb) {
8 notebook_page_new (nb);
9 }
10
11 static void
12 save_clicked (GtkWidget *btns, GtkNotebook *nb) {
12 save_cb (GtkNotebook *nb) {
13 notebook_page_save (nb);
14 }
15
16 static void
17 close_clicked (GtkWidget *btnc, GtkNotebook *nb) {
18 GtkWidget *win;
19 GtkWidget *boxv;
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 }
17 close_cb (GtkNotebook *nb) {
18 notebook_page_close (GTK_NOTEBOOK (nb));
19 }
~~~
`open_clicked`, `new_clicked` and `save_clicked` 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.
`open_cb`, `new_cb`, `save_cb` and `close_cb` just call corresponding notebook page functions.
## meson.build
@ -318,13 +294,13 @@ First, get the top level window and call `gtk_window_destroy`.
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
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.
- 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
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)

View file

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

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.
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.
If you want to draw dynamically, like an image window of gimp graphics editor, GtkDrawingArea widget is the most suitable widget.
You can draw or redraw an image in this widget freely.
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
- black
- red
- green
- blue
## Cairo
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.
- dark: Make the color of the drawing area darker.
- Surface represents an image.
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.
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.
![Stroke a rectangle](../image/cairo.png)
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.
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.
Here's a simple example code that draws a small square and save it as a png file.
~~~C
1 #include <gtk/gtk.h>
2
3 #include "../tfetextview/tfetextview.h"
1 #include <cairo.h>
2
3 int
4 main (int argc, char **argv)
5 {
6 cairo_surface_t *surface;
7 cairo_t *cr;
8 int width = 100;
9 int height = 100;
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 }
~~~
## Colorapplication.c
- 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.
This is the main file.
It deals with:
To compile this, type the following.
- 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.
$ gcc `pkg-config --cflags cairo` cairo.c `pkg-config --libs cairo`
The following is `colorapplication.c`.
![rectangle.png](../src/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.
~~~C
1 #include "color.h"
1 #include <gtk/gtk.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
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
~~~
- 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.
The function `main` is almost same as before.
The two functions `on_activate` and `draw_function` is important in this example.
## Meson.build
- 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.
This file is almost same as before.
An argument "export_dynamic: true" is added to executable function.
The drawing function has five parameters.
~~~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)
~~~
void drawing_function (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height,
gpointer user_data);
## Compile and execute it
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.
First you need to export some variables (refer to [Section 2](sec2.md)).
- 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.
$ . env.sh
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.
Then type the following to compile it.
![Square in the window](../image/da1.png)
$ 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 19](sec19.md)
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.
~~~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
1 <?xml version="1.0" encoding="UTF-8"?>
2 <interface>
3 <object class="GtkApplicationWindow" id="win">
4 <property name="title">file editor</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="boxh">
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="btnn">
20 <property name="label">New</property>
21 </object>
22 </child>
23 <child>
24 <object class="GtkButton" id="btno">
25 <property name="label">Open</property>
26 </object>
27 </child>
28 <child>
29 <object class="GtkLabel" id="dmy2">
30 <property name="hexpand">TRUE</property>
31 </object>
32 </child>
33 <child>
34 <object class="GtkButton" id="btns">
35 <property name="label">Save</property>
36 </object>
37 </child>
38 <child>
39 <object class="GtkButton" id="btnc">
40 <property name="label">Close</property>
41 </object>
42 </child>
43 <child>
44 <object class="GtkLabel" id="dmy3">
45 <property name="width-chars">10</property>
46 </object>
47 </child>
48 </object>
49 </child>
50 <child>
51 <object class="GtkNotebook" id="nb">
52 <property name="hexpand">TRUE</property>
53 <property name="vexpand">TRUE</property>
54 </object>
55 </child>
56 </object>
57 </child>
58 </object>
59 </interface>
~~~
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.
For example, `<interface>` is a start tag and `</interface>` is an end tag.
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.
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.
- 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`.
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.
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 builds widgets based on the ui file.

View file

@ -173,69 +173,7 @@ All the source files are listed below.
70
~~~
`tfe.ui`
~~~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
~~~
The ui file `tfe.ui` is the same as `tfe3.ui` in the previous section.
`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_functions = $2.strip.split(" ")
if c_file =~ /^\// # absolute path
c_file_buf = IO.readlines(c_file)
c_file_buf = File.readlines(c_file)
else #relative path
c_file_buf = IO.readlines(src_dir+"/"+c_file)
c_file_buf = File.readlines(src_dir+"/"+c_file)
end
if c_functions.empty? # no functions are specified
tmp_buf = c_file_buf
@ -96,7 +96,7 @@ def src2md srcmd, md, width
c_functions.each do |c_function|
from = c_file_buf.find_index { |line| line =~ /^#{c_function} *\(/ }
if ! from
warn "ERROR!!! --- Didn't find #{c_function} in #{filename}. ---"
warn "ERROR in #{srcmd}: Didn't find #{c_function} in #{c_file}."
break
end
to = from

View file

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkApplicationWindow" id="win">
<property name="title">color changer</property>
@ -18,7 +19,6 @@
<object class="GtkButton" id="btnr">
<property name="label">Run</property>
<signal name="clicked" handler="run_cb"></signal>
</object>
</child>
<child>
@ -78,4 +78,3 @@
</child>
</object>
</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.
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 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.

View file

@ -2,7 +2,7 @@
GtkNotebook is a very important object in the text file editor `tfe`.
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.
There's no "TfeNotebook" object.
@ -12,18 +12,19 @@ tfe5/tfenotebook.h
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.
- 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.
The GFile `file` is copied and inserted to the TfeTextView object.
- 4-5: `notebook_page_open` shows a file chooser dialog. Then, user chooses a file and the file is inserted into GtkTextBuffer.
- 1-2: `notebook_page_save` saves the contents in GtkTextBuffer into the file, which is got from the TfeTextView.
- 1-2: `notebook_page_save` saves the current page to the file of which the name specified in the tab.
If the name is `untitled` or `untitled` followed by digits, FileChooserDialog appears and a user can choose or specify a filename.
- 4-5: `notebook_page_close` closes the current page.
- 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.
- 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`
- `tef_text_view_open`
- `tfe_text_view_new_with_file`
- `tfe_text_view_new`
respectively.
@ -31,7 +32,7 @@ There are two layers.
One of them is `tfe_text_view ...`, which is the lower 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
@ -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
@@@
- 28-38: `notebook_page_new` function.
- 30: `g_return_if_fail` is used to check the argument.
- 35: Generates TfeTextView object.
- 36: Generates filename, which is "Untitled", "Untitled2", ... .
- 27-37: `notebook_page_new` function.
- 29: `g_return_if_fail` is used to check the argument.
- 34: Generates TfeTextView object.
- 35: Generates filename, which is "Untitled", "Untitled1", ... .
- 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.
- 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.
- 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 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 caller of `get_untitled` is in charge of freeing the string.
- 37: calls `notebook_page_build` to build the contents of the page.
- 10- 26: `notebook_page_build` function.
- 16: Generates GtkScrolledWindow.
- 18: 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.
- 20-21: Generates GtkLabel, then appends it to GtkNotebookPage.
- 22-23: Sets "tab-expand" property to TRUE.
- 36: calls `notebook_page_build` to build the contents of the page.
- 10- 25: `notebook_page_build` function.
- 12: Generates GtkScrolledWindow.
- 17: Sets the wrap mode of `tv` to GTK_WRAP_WORD_CHAR so that lines are broken between words or graphemes.
- 18: Inserts `tv` to GtkscrolledWindow as a child.
- 19-20: Generates GtkLabel, then appends it to GtkNotebookPage.
- 21-22: Sets "tab-expand" property to TRUE.
The function g\_object\_set sets properties on an object.
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 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.
- 25: Connects "change-file" signal and `file_changed` handler.
- 23: Sets the current page of `nb` to the newly generated page.
- 24: Connects "change-file" signal and `file_changed_cb` handler.
## notebook\_page\_new\_with\_file
@ -92,34 +93,54 @@ Such object has floating reference.
You need to call `g_object_ref_sink` first.
Then the floating reference is converted into an ordinary reference.
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`.
- 12-16: Otherwise, everything is okay.
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
@@@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.
- 10: Call `tfe_text_view_save`.
- 13-21: `notebook_page_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.
This handler changes the label of GtkNotebookPage.
@@@include
tfe5/tfenotebook.c file_changed
tfe5/tfenotebook.c file_changed_cb
@@@
- 8: Gets GFile from TfeTextView.
- 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`.
Otherwise (file is NULL), assigns untitled string to `filename`.
- 14-15: Generates a label with the filename and inserts it into GtkNotebookPage.
- 16-17: Unrefs `file` and frees `filename`.
- 10-12: If `file` points GFile, then assigns the filename of the GFile into `filename`.
Then, unref the GFile object `file`.
- 13-14: Otherwise (file is NULL), assigns untitled string to `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` includes all the code other than `tfetxtview.c` and `tfenotebook.c`.
It does following things.
It does:
- Application support, mainly handling command line arguments.
- Build widgets using ui file.
- Connect button signals and their handlers.
- Manage CSS.
- Builds widgets using ui file.
- Connects button signals and their handlers.
- Manages CSS.
## main
@ -72,7 +72,8 @@ If you want to set style to GtkTextView, substitute "textview" for the selector.
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.
@ -81,11 +82,10 @@ textview {padding: 10px; font-family: monospace; font-size: 12pt;}
~~~
- 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.
"monospace" is one of the generic family font keywords.
- font-size is set to 12pt.
It is a bit large, but easy on the eyes especially for elderly people.
### 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.
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
GtkWidget *
@ -137,14 +139,12 @@ They just generate a new GtkNotebookPage.
tfe5/tfeapplication.c tfe_activate tfe_open
@@@
- 1-14: `tfe_activate`.
- 8-10: Gets GtkNotebook object.
- 12-13: Generates a new GtkNotebookPage and show the window.
- 16-33: `tfe_open`.
- 24-26: Gets GtkNotebook object.
- 28-29: Generates GtkNotebookPage with files.
- 30-31: If opening and reading file failed and no GtkNotebookPage has generated, then it generates a empty page.
- 32: Shows the window.
- 1-11: `tfe_activate`.
- 8-10: Generates a new page and shows the window.
- 12-25: `tfe_open`.
- 20-21: Generates notebook pages with files.
- 22-23: If no page has generated, maybe because of read error, then it generates a empty page.
- 24: Shows the window.
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
@@@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.
`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.
`open_cb`, `new_cb`, `save_cb` and `close_cb` just call corresponding notebook page functions.
## meson.build
@ -201,13 +196,13 @@ First, get the top level window and call `gtk_window_destroy`.
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
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.
- 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
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.
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.
Therefore, after the insertion, GMenuItem is useless and it should be freed.
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.
You may think you can add them in your `.profile`.
I think the environment variables above are necessary only when you compile gtk4 applications.
And it's not necessary except the case above and it might cause some bad things.
Therefore, I recommend you not to write them to your `.profile`.
But it's a wrong decision.
Never 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

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.
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.
If you want to draw dynamically, like an image window of gimp graphics editor, GtkDrawingArea widget is the most suitable widget.
You can draw or redraw an image in this widget freely.
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
- black
- red
- green
- blue
## Cairo
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.
- dark: Make the color of the drawing area darker.
- Surface represents an image.
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.
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.
![Stroke a rectangle](../image/cairo.png){width=9.0cm height=6.0cm}
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.
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.
Here's a simple example code that draws a small square and save it as a png file.
@@@include
color/color.ui
misc/cairo.c
@@@
- 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.src.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.
- 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.
The xml file for the resource compiler is almost same as before.
Just substitute "color" for "tfe".
To compile this, type the following.
$ 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
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.
Color.h just includes tfetextview.h.
- 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.
@@@include
color/color.h
@@@
The drawing function has five parameters.
## Colorapplication.c
void drawing_function (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height,
gpointer user_data);
This is the main file.
It deals with:
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.
- 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.
- 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.
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
color/colorapplication.c
@@@
![Square in the window](../image/da1.png)
- 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.
For example, `<interface>` is a start tag and `</interface>` is an end tag.
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.
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.
- 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`.
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.
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 builds widgets based on the ui file.

View file

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

View file

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkApplicationWindow" id="win">
<property name="title">file editor</property>
@ -56,4 +57,3 @@
</child>
</object>
</interface>

View file

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkApplicationWindow" id="win">
<property name="title">file editor</property>
@ -56,4 +57,3 @@
</child>
</object>
</interface>

View file

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkApplicationWindow" id="win">
<property name="title">file editor</property>
@ -16,14 +17,12 @@
</child>
<child>
<object class="GtkButton" id="btnn">
<property name="label">_New</property>
<property name="use-underline">TRUE</property>
<property name="label">New</property>
</object>
</child>
<child>
<object class="GtkButton" id="btno">
<property name="label">_Open</property>
<property name="use-underline">TRUE</property>
<property name="label">Open</property>
</object>
</child>
<child>
@ -33,14 +32,12 @@
</child>
<child>
<object class="GtkButton" id="btns">
<property name="label">_Save</property>
<property name="use-underline">TRUE</property>
<property name="label">Save</property>
</object>
</child>
<child>
<object class="GtkButton" id="btnc">
<property name="label">_Close</property>
<property name="use-underline">TRUE</property>
<property name="label">Close</property>
</object>
</child>
<child>

View file

@ -1,46 +1,31 @@
#include "tfe.h"
static void
open_clicked (GtkWidget *btno, GtkNotebook *nb) {
open_cb (GtkNotebook *nb) {
notebook_page_open (nb);
}
static void
new_clicked (GtkWidget *btnn, GtkNotebook *nb) {
new_cb (GtkNotebook *nb) {
notebook_page_new (nb);
}
static void
save_clicked (GtkWidget *btns, GtkNotebook *nb) {
save_cb (GtkNotebook *nb) {
notebook_page_save (nb);
}
static void
close_clicked (GtkWidget *btnc, GtkNotebook *nb) {
GtkWidget *win;
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);
}
close_cb (GtkNotebook *nb) {
notebook_page_close (GTK_NOTEBOOK (nb));
}
static void
tfe_activate (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
GtkWidget *win;
GtkWidget *boxv;
GtkNotebook *nb;
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));
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));
@ -49,15 +34,11 @@ tfe_activate (GApplication *application) {
static void
tfe_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) {
GtkApplication *app = GTK_APPLICATION (application);
GtkWidget *win;
GtkWidget *boxv;
GtkNotebook *nb;
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;
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++)
notebook_page_new_with_file (nb, files[i]);
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);
}
static void
tfe_startup (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
GtkBuilder *build;
GtkApplicationWindow *win;
GtkNotebook *nb;
GtkBuilder *build;
GtkButton *btno;
GtkButton *btnn;
GtkButton *btns;
@ -85,10 +65,10 @@ tfe_startup (GApplication *application) {
btnn = GTK_BUTTON (gtk_builder_get_object (build, "btnn"));
btns = GTK_BUTTON (gtk_builder_get_object (build, "btns"));
btnc = GTK_BUTTON (gtk_builder_get_object (build, "btnc"));
g_signal_connect (btno, "clicked", G_CALLBACK (open_clicked), nb);
g_signal_connect (btnn, "clicked", G_CALLBACK (new_clicked), nb);
g_signal_connect (btns, "clicked", G_CALLBACK (save_clicked), nb);
g_signal_connect (btnc, "clicked", G_CALLBACK (close_clicked), nb);
g_signal_connect_swapped (btno, "clicked", G_CALLBACK (open_cb), nb);
g_signal_connect_swapped (btnn, "clicked", G_CALLBACK (new_cb), nb);
g_signal_connect_swapped (btns, "clicked", G_CALLBACK (save_cb), nb);
g_signal_connect_swapped (btnc, "clicked", G_CALLBACK (close_cb), nb);
g_object_unref(build);
GdkDisplay *display;

View file

@ -10,45 +10,68 @@ get_untitled () {
return g_strdup_printf ("Untitled%u", c);
}
static void
file_changed (TfeTextView *tv, GtkNotebook *nb) {
GFile *file;
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;
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, 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));
}
void
notebook_page_close (GtkNotebook *nb) {
g_return_if_fail(GTK_IS_NOTEBOOK (nb));
GtkWidget *win;
int i;
if (gtk_notebook_get_n_pages (nb) == 1) {
win = gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW);
gtk_window_destroy(GTK_WINDOW (win));
} else {
i = gtk_notebook_get_current_page (nb);
gtk_notebook_remove_page (GTK_NOTEBOOK (nb), i);
}
}
static void
notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) {
GtkWidget *scr;
GtkWidget *scr = gtk_scrolled_window_new ();
GtkNotebookPage *nbp;
GtkWidget *lab;
gint i;
scr = gtk_scrolled_window_new ();
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);
@ -57,11 +80,11 @@ notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) {
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), nb);
g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed_cb), nb);
}
static void
open_response (TfeTextView *tv, gint response, GtkNotebook *nb) {
open_response (TfeTextView *tv, int response, GtkNotebook *nb) {
GFile *file;
char *filename;

View file

@ -1,6 +1,9 @@
void
notebook_page_save(GtkNotebook *nb);
void
notebook_page_close (GtkNotebook *nb);
void
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) {
g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);
if (G_IS_FILE (tv->file))
return g_file_dup (tv->file);
else
return NULL;
}
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;
static gboolean
save_file (GFile *file, GtkTextBuffer *tb, GtkWindow *win) {
GtkTextIter start_iter;
GtkTextIter end_iter;
gchar *contents;
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_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,
/* This function doesn't check G_IS_FILE (file). The caller should check it. */
gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
if (g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err)) {
gtk_text_buffer_set_modified (tb, FALSE);
return TRUE;
} else {
message_dialog = gtk_message_dialog_new (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);
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);
return FALSE;
}
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
saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
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) {
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))
g_object_unref (tv->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);
tfe_text_view_save (TFE_TEXT_VIEW (tv));
}
}
gtk_window_destroy (GTK_WINDOW (dialog));
}
void
@ -142,36 +122,17 @@ tfe_text_view_save (TfeTextView *tv) {
g_return_if_fail (TFE_IS_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);
GError *err = NULL;
if (! gtk_text_buffer_get_modified (tb))
return; /* no need to save it */
else if (tv->file == NULL)
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 {
gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
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))
if (save_file (tv->file, tb, GTK_WINDOW (win)))
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;
}
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 *
tfe_text_view_new (void) {
return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));