mirror of
https://github.com/ToshioCP/Gtk4-tutorial.git
synced 2025-01-11 20:03:35 +01:00
Add tfe6 and insert new section19. Bug fixed.
This commit is contained in:
parent
62d2fe46ba
commit
8e788b0749
49 changed files with 4880 additions and 1844 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
202
gfm/sec12.md
202
gfm/sec12.md
|
@ -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 }
|
||||
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 }
|
||||
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 }
|
||||
~~~
|
||||
|
||||
- 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`.
|
||||
|
|
208
gfm/sec13.md
208
gfm/sec13.md
|
@ -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)
|
||||
|
|
128
gfm/sec14.md
128
gfm/sec14.md
|
@ -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)
|
||||
|
|
890
gfm/sec15.md
890
gfm/sec15.md
|
@ -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
|
||||
|
@ -138,123 +135,103 @@ It is a good practice for you to add more features.
|
|||
## tfeapplication.c
|
||||
|
||||
~~~C
|
||||
1 #include "tfe.h"
|
||||
2
|
||||
3 static void
|
||||
4 open_clicked (GtkWidget *btno, 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
|
||||
1 #include "tfe.h"
|
||||
2
|
||||
3 static void
|
||||
4 open_cb (GtkNotebook *nb) {
|
||||
5 notebook_page_open (nb);
|
||||
6 }
|
||||
7
|
||||
8 static void
|
||||
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
|
||||
~~~
|
||||
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
1386
gfm/sec19.md
1386
gfm/sec19.md
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
|
||||
|
|
525
gfm/sec20.md
525
gfm/sec20.md
|
@ -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"
|
||||
~~~
|
||||
|
||||
## 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')
|
||||
1 #include <cairo.h>
|
||||
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)
|
||||
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 }
|
||||
~~~
|
||||
|
||||
## Compile and execute it
|
||||
- 1: Includes the header file of cairo.
|
||||
- 12: `cairo_image_surface_create` creates an image surface.
|
||||
`CAIRO_FORMAT_RGB24` is a constant which means that each pixel has red, green and blue data.
|
||||
Each data has 8 bit quantity.
|
||||
Modern displays have this type of color depth.
|
||||
Width and height are pixels and given as integers.
|
||||
- 13: Creates cairo context.
|
||||
The surface given as an argument will be the destination of the context.
|
||||
- 17: `cairo_set_source_rgb` creates a source pattern, which is a solid white paint.
|
||||
The second to fourth argument is red, green and blue color depth respectively.
|
||||
Their type is float and the values are between zero and one.
|
||||
(0,0,0) is black and (1,1,1) is white.
|
||||
- 18: `cairo_paint` copies everywhere in the source to destination.
|
||||
The destination is filled with white pixels by this command.
|
||||
- 20: Sets the source color to black.
|
||||
- 21: `cairo_set_line_width` set the width of lines.
|
||||
In this case, the line width is set to two pixels.
|
||||
(It is because the transformation is identity.
|
||||
If the transformation isn't identity, for example scaling with the factor three, the actual width in destination will be six (2x3=6) pixels.)
|
||||
- 22: Draws a rectangle (square).
|
||||
The top-left coordinate is (width/2.0-20.0, height/2.0-20.0) and the width and height have the same length 40.0.
|
||||
- 23: `cairo_stroke` transfer the source to destination through the rectangle in mask.
|
||||
- 26: Outputs the image to a png file `rectangle.png`.
|
||||
- 27: Destroys the context. At the same time the source is destroyed.
|
||||
- 28: Destroys the destination surface.
|
||||
|
||||
First you need to export some variables (refer to [Section 2](sec2.md)).
|
||||
To compile this, type the following.
|
||||
|
||||
$ . env.sh
|
||||
$ gcc `pkg-config --cflags cairo` cairo.c `pkg-config --libs cairo`
|
||||
|
||||
Then type the following to compile it.
|
||||
![rectangle.png](../src/misc/rectangle.png)
|
||||
|
||||
$ meson _build
|
||||
$ ninja -C _build
|
||||
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.
|
||||
|
||||
The application is made in `_build` directory.
|
||||
Type the following to execute it.
|
||||
## GtkDrawingArea
|
||||
|
||||
$ _build/color
|
||||
The following is a very simple example.
|
||||
|
||||
Type "red", "green", "blue", "white", black", "light" or "dark" in the TfeTextView.
|
||||
Then, click on `Run` button.
|
||||
Make sure the color of GtkDrawingArea changes.
|
||||
~~~C
|
||||
1 #include <gtk/gtk.h>
|
||||
2
|
||||
3 static void
|
||||
4 draw_function (GtkDrawingArea *area, cairo_t *cr, int width, int height, gpointer user_data) {
|
||||
5 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); /* whilte */
|
||||
6 cairo_paint (cr);
|
||||
7 cairo_set_line_width (cr, 2.0);
|
||||
8 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
|
||||
9 cairo_rectangle (cr, width/2.0 - 20.0, height/2.0 - 20.0, 40.0, 40.0);
|
||||
10 cairo_stroke (cr);
|
||||
11 }
|
||||
12
|
||||
13 static void
|
||||
14 on_activate (GApplication *app, gpointer user_data) {
|
||||
15 GtkWidget *win = gtk_application_window_new (GTK_APPLICATION (app));
|
||||
16 GtkWidget *area = gtk_drawing_area_new ();
|
||||
17
|
||||
18 gtk_window_set_title (GTK_WINDOW (win), "da1");
|
||||
19 /* Set initial size of width and height */
|
||||
20 gtk_drawing_area_set_content_width (GTK_DRAWING_AREA (area), 100);
|
||||
21 gtk_drawing_area_set_content_height (GTK_DRAWING_AREA (area), 100);
|
||||
22 gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (area), draw_function, NULL, NULL);
|
||||
23 gtk_window_set_child (GTK_WINDOW (win), area);
|
||||
24
|
||||
25 gtk_widget_show (win);
|
||||
26 }
|
||||
27
|
||||
28 int
|
||||
29 main (int argc, char **argv) {
|
||||
30 GtkApplication *app;
|
||||
31 int stat;
|
||||
32
|
||||
33 app = gtk_application_new ("com.github.ToshioCP.da1", G_APPLICATION_FLAGS_NONE);
|
||||
34 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
|
||||
35 stat =g_application_run (G_APPLICATION (app), argc, argv);
|
||||
36 g_object_unref (app);
|
||||
37 return stat;
|
||||
38 }
|
||||
39
|
||||
~~~
|
||||
|
||||
In this program TfeTextView is used to change the color.
|
||||
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.
|
||||
The function `main` is almost same as before.
|
||||
The two functions `on_activate` and `draw_function` is important in this example.
|
||||
|
||||
Up: [Readme.md](../Readme.md), Prev: [Section 19](sec19.md)
|
||||
- 16: Generates a GtkDrawingArea object.
|
||||
- 20,21: Sets the width and height of the contents of the GtkDrawingArea widget.
|
||||
These width and height is the size of the destination surface of the cairo context provided by the widget.
|
||||
- 22: Sets a drawing function of the widget.
|
||||
GtkDrawingArea widget uses the function to draw the contents of itself whenever its necessary.
|
||||
For example, when a user drag a mouse pointer and resize a top level window, GtkDrawingArea also changes the size.
|
||||
Then, the whole window needs to be redrawn.
|
||||
|
||||
The drawing function has five parameters.
|
||||
|
||||
void drawing_function (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height,
|
||||
gpointer user_data);
|
||||
|
||||
The first parameter is the GtkDrawingArea widget which calls the drawing function.
|
||||
However, you can't change any properties, for example `content-width` or `content-height`, in this function.
|
||||
The second parameter is a cairo context given by the widget.
|
||||
The destination surface of the context is connected to the contents of the widget.
|
||||
What you draw to this surface will appear in the widget on the screen.
|
||||
The third and fourth parameters are the size of the destination surface.
|
||||
|
||||
- 3-11: The drawing function.
|
||||
- 4-5: Sets the source to be white and paint the destination white.
|
||||
- 7: Sets the line width to be 2.
|
||||
- 8: Sets the source to be black.
|
||||
- 9: Adds a rectangle to the mask.
|
||||
- 10: Draws the rectangle with black color to the destination.
|
||||
|
||||
Compile and run it, then a window with a black rectangle (square) appears.
|
||||
Try resizing the window.
|
||||
The square always appears at the center of the window because the drawing function is invoked every moment the window is resized.
|
||||
|
||||
![Square in the window](../image/da1.png)
|
||||
|
||||
|
||||
Up: [Readme.md](../Readme.md), Prev: [Section 19](sec19.md), Next: [Section 21](sec21.md)
|
||||
|
|
373
gfm/sec21.md
Normal file
373
gfm/sec21.md
Normal 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)
|
141
gfm/sec8.md
141
gfm/sec8.md
|
@ -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.
|
||||
|
|
64
gfm/sec9.md
64
gfm/sec9.md
|
@ -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
BIN
image/dialog_warning.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6 KiB |
BIN
image/dialog_warning.xcf
Normal file
BIN
image/dialog_warning.xcf
Normal file
Binary file not shown.
BIN
image/gnome_calculator_advanced.png
Normal file
BIN
image/gnome_calculator_advanced.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
BIN
image/gnome_calculator_basic.png
Normal file
BIN
image/gnome_calculator_basic.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
BIN
image/tfe6.png
Normal file
BIN
image/tfe6.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 52 KiB |
|
@ -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
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<object class="GtkApplicationWindow" id="win">
|
||||
<property name="title">color changer</property>
|
||||
|
@ -8,68 +9,67 @@
|
|||
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="boxh1">
|
||||
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
||||
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="dmy1">
|
||||
<property name="width-chars">10</property>
|
||||
<property name="width-chars">10</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="btnr">
|
||||
<property name="label">Run</property>
|
||||
<signal name="clicked" handler="run_cb"></signal>
|
||||
|
||||
<property name="label">Run</property>
|
||||
<signal name="clicked" handler="run_cb"></signal>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="btno">
|
||||
<property name="label">Open</property>
|
||||
<signal name="clicked" handler="open_cb"></signal>
|
||||
<property name="label">Open</property>
|
||||
<signal name="clicked" handler="open_cb"></signal>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="dmy2">
|
||||
<property name="hexpand">TRUE</property>
|
||||
<property name="hexpand">TRUE</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="btns">
|
||||
<property name="label">Save</property>
|
||||
<signal name="clicked" handler="save_cb"></signal>
|
||||
<property name="label">Save</property>
|
||||
<signal name="clicked" handler="save_cb"></signal>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="btnc">
|
||||
<property name="label">Close</property>
|
||||
<signal name="clicked" handler="close_cb"></signal>
|
||||
<property name="label">Close</property>
|
||||
<signal name="clicked" handler="close_cb"></signal>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="dmy3">
|
||||
<property name="width-chars">10</property>
|
||||
<property name="width-chars">10</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="boxh2">
|
||||
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
||||
<property name="homogeneous">TRUE</property>
|
||||
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
||||
<property name="homogeneous">TRUE</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="scr">
|
||||
<property name="hexpand">TRUE</property>
|
||||
<property name="vexpand">TRUE</property>
|
||||
<child>
|
||||
<object class="TfeTextView" id="tv">
|
||||
<property name="wrap-mode">GTK_WRAP_WORD_CHAR</property>
|
||||
</object>
|
||||
</child>
|
||||
<property name="hexpand">TRUE</property>
|
||||
<property name="vexpand">TRUE</property>
|
||||
<child>
|
||||
<object class="TfeTextView" id="tv">
|
||||
<property name="wrap-mode">GTK_WRAP_WORD_CHAR</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkDrawingArea" id="da">
|
||||
<property name="hexpand">TRUE</property>
|
||||
<property name="vexpand">TRUE</property>
|
||||
<property name="hexpand">TRUE</property>
|
||||
<property name="vexpand">TRUE</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
@ -78,4 +78,3 @@
|
|||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
1054
src/sec19.src.md
1054
src/sec19.src.md
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
|
||||
|
|
228
src/sec20.src.md
228
src/sec20.src.md
|
@ -1,153 +1,129 @@
|
|||
# Combine GtkDrawingArea and TfeTextView
|
||||
# GtkDrawingArea and Cairo
|
||||
|
||||
Now, we will make a new application which has GtkDrawingArea and TfeTextView in it.
|
||||
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
153
src/sec21.src.md
Normal file
|
@ -0,0 +1,153 @@
|
|||
# Combine GtkDrawingArea and TfeTextView
|
||||
|
||||
Now, we will make a new application which has GtkDrawingArea and TfeTextView in it.
|
||||
Its name is "color".
|
||||
If you write a color in TfeTextView and click on the `run` button, then the color of GtkDrawingArea changes to the color given by you.
|
||||
|
||||
![color](../image/color.png){width=7.0cm height=5.13cm}
|
||||
|
||||
The following colors are available.
|
||||
|
||||
- white
|
||||
- black
|
||||
- red
|
||||
- green
|
||||
- blue
|
||||
|
||||
In addition the following two options are also available.
|
||||
|
||||
- light: Make the color of the drawing area lighter.
|
||||
- dark: Make the color of the drawing area darker.
|
||||
|
||||
This application can only do very simple things.
|
||||
However, it tells us that if we add powerful parser to it, we will be able to make it more efficient.
|
||||
I want to show it to you in the later section by making a turtle graphics language like Logo program language.
|
||||
|
||||
In this section, we focus on how to bind the two objects.
|
||||
|
||||
## Color.ui and color.gresource.xml
|
||||
|
||||
First, We need to make the ui file of the widgets.
|
||||
The image in the previous subsection gives us the structure of the widgets.
|
||||
Title bar, four buttons in the tool bar and two widgets textview and drawing area.
|
||||
The ui file is as follows.
|
||||
|
||||
@@@include
|
||||
color/color.ui
|
||||
@@@
|
||||
|
||||
- 10-53: This part describes the tool bar which has four buttons, `Run`, `Open`, `Save` and `Close`.
|
||||
This is similar to the toolbar of tfe text editor in [Section 8](sec8.src.md).
|
||||
There are two differences.
|
||||
`Run` button replaces `New` button.
|
||||
A signal element is added to each button object.
|
||||
It has "name" attribute which is a signal name and "handler" attribute which is the name of its signal handler function.
|
||||
Options "-WI, --export-dynamic" CFLAG is necessary when you compile the application.
|
||||
You can achieve this by adding "export_dynamic: true" argument to executable function in `meson.build`.
|
||||
And be careful that the handler must be defined without 'static' class.
|
||||
- 54-76: Puts GtkScrolledWindow and GtkDrawingArea into GtkBox.
|
||||
GtkBox has "homogeneous property" with TRUE value, so the two children have the same width in the box.
|
||||
TfeTextView is a child of GtkScrolledWindow.
|
||||
|
||||
The xml file for the resource compiler is almost same as before.
|
||||
Just substitute "color" for "tfe".
|
||||
|
||||
@@@include
|
||||
color/color.gresource.xml
|
||||
@@@
|
||||
|
||||
## Tfetextview.h, tfetextview.c and color.h
|
||||
|
||||
First two files are the same as before.
|
||||
Color.h just includes tfetextview.h.
|
||||
|
||||
@@@include
|
||||
color/color.h
|
||||
@@@
|
||||
|
||||
## Colorapplication.c
|
||||
|
||||
This is the main file.
|
||||
It deals with:
|
||||
|
||||
- Building widgets by GtkBuilder.
|
||||
- Seting a drawing function of GtkDrawingArea.
|
||||
And connecting a handler to "resize" signal on GtkDrawingArea.
|
||||
- Implementing each call back functions.
|
||||
Particularly, `Run` signal handler is the point in this program.
|
||||
|
||||
The following is `colorapplication.c`.
|
||||
|
||||
@@@include
|
||||
color/colorapplication.c
|
||||
@@@
|
||||
|
||||
- 108-121: The function `main` is almost same as before but there are some differences.
|
||||
The application ID is "com.github.ToshioCP.color".
|
||||
`G_APPLICATION_FLAGS_NONE` is specified so no open signal handler is necessary.
|
||||
- 86-106: Startup handler.
|
||||
- 91-96: Builds widgets.
|
||||
The pointers of the top window, TfeTextView and GtkDrawingArea objects are stored to static variables `win`, `tv` and `da` respectively.
|
||||
This is because these objects are often used in handlers.
|
||||
They never be rewritten so they're thread safe.
|
||||
- 97: connects "resize" signal and the handler.
|
||||
- 98: sets the drawing function.
|
||||
- 81-84: Activates handler, which just shows the widgets.
|
||||
- 73-79: The drawing function.
|
||||
It just copies `surface` to destination.
|
||||
- 65-71: Resize handler.
|
||||
Re-creates the surface to fit the width and height of the drawing area and paints by calling the function `run`.
|
||||
- 58-63: Closes the handler.
|
||||
It destroys `surface` if it exists.
|
||||
Then it destroys the top window and quits the application.
|
||||
- 48-56: Open and save handler.
|
||||
They just call the corresponding functions of TfeTextView.
|
||||
- 42-46: Run handler.
|
||||
It calls run function to paint the surface.
|
||||
After that `gtk_widget_queue_draw` is called.
|
||||
This fhunction adds the widget (GtkDrawingArea) to the queue to be redrawn.
|
||||
It is important to know that the drawing function is called when it is necessary.
|
||||
For example, when another window is moved and uncovers part of the widget, or when the window containing it is resized.
|
||||
But repaint of `surface` is not automatically notified to gtk.
|
||||
Therefore, you need to call `gtk_widget_queue_draw` to redraw the widget.
|
||||
- 9-40: Run function paints the surface.
|
||||
First, it gets the contents of GtkTextBuffer.
|
||||
Then it compares it to "red", "green" and so on.
|
||||
If it matches the color, then the surface is painted the color.
|
||||
If it matches "light" or "dark", then the color of the surface is lightened or darkened respectively.
|
||||
Alpha channel is used.
|
||||
|
||||
## Meson.build
|
||||
|
||||
This file is almost same as before.
|
||||
An argument "export_dynamic: true" is added to executable function.
|
||||
|
||||
@@@include
|
||||
color/meson.build
|
||||
@@@
|
||||
|
||||
## Compile and execute it
|
||||
|
||||
First you need to export some variables (refer to [Section 2](sec2.src.md)).
|
||||
|
||||
$ . env.sh
|
||||
|
||||
Then type the following to compile it.
|
||||
|
||||
$ meson _build
|
||||
$ ninja -C _build
|
||||
|
||||
The application is made in `_build` directory.
|
||||
Type the following to execute it.
|
||||
|
||||
$ _build/color
|
||||
|
||||
Type "red", "green", "blue", "white", black", "light" or "dark" in the TfeTextView.
|
||||
Then, click on `Run` button.
|
||||
Make sure the color of GtkDrawingArea changes.
|
||||
|
||||
In this program TfeTextView is used to change the color.
|
||||
You can use buttons or menus instead of textview.
|
||||
Probably it is more appropriate.
|
||||
Using textview is unnatural.
|
||||
It is a good practice to make such application by yourself.
|
|
@ -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.
|
||||
|
|
|
@ -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`
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<object class="GtkApplicationWindow" id="win">
|
||||
<property name="title">file editor</property>
|
||||
|
@ -8,40 +9,40 @@
|
|||
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="boxh">
|
||||
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
||||
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="dmy1">
|
||||
<property name="width-chars">10</property>
|
||||
<property name="width-chars">10</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="btnn">
|
||||
<property name="label">New</property>
|
||||
<property name="label">New</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="btno">
|
||||
<property name="label">Open</property>
|
||||
<property name="label">Open</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="dmy2">
|
||||
<property name="hexpand">TRUE</property>
|
||||
<property name="hexpand">TRUE</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="btns">
|
||||
<property name="label">Save</property>
|
||||
<property name="label">Save</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="btnc">
|
||||
<property name="label">Close</property>
|
||||
<property name="label">Close</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="dmy3">
|
||||
<property name="width-chars">10</property>
|
||||
<property name="width-chars">10</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
@ -56,4 +57,3 @@
|
|||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<object class="GtkApplicationWindow" id="win">
|
||||
<property name="title">file editor</property>
|
||||
|
@ -8,40 +9,40 @@
|
|||
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="boxh">
|
||||
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
||||
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="dmy1">
|
||||
<property name="width-chars">10</property>
|
||||
<property name="width-chars">10</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="btnn">
|
||||
<property name="label">New</property>
|
||||
<property name="label">New</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="btno">
|
||||
<property name="label">Open</property>
|
||||
<property name="label">Open</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="dmy2">
|
||||
<property name="hexpand">TRUE</property>
|
||||
<property name="hexpand">TRUE</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="btns">
|
||||
<property name="label">Save</property>
|
||||
<property name="label">Save</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="btnc">
|
||||
<property name="label">Close</property>
|
||||
<property name="label">Close</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="dmy3">
|
||||
<property name="width-chars">10</property>
|
||||
<property name="width-chars">10</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
@ -56,4 +57,3 @@
|
|||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<object class="GtkApplicationWindow" id="win">
|
||||
<property name="title">file editor</property>
|
||||
|
@ -8,44 +9,40 @@
|
|||
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="boxh">
|
||||
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
||||
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="dmy1">
|
||||
<property name="width-chars">10</property>
|
||||
<property name="width-chars">10</property>
|
||||
</object>
|
||||
</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>
|
||||
<object class="GtkLabel" id="dmy2">
|
||||
<property name="hexpand">TRUE</property>
|
||||
<property name="hexpand">TRUE</property>
|
||||
</object>
|
||||
</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>
|
||||
<object class="GtkLabel" id="dmy3">
|
||||
<property name="width-chars">10</property>
|
||||
<property name="width-chars">10</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
void
|
||||
notebook_page_save(GtkNotebook *nb);
|
||||
|
||||
void
|
||||
notebook_page_close (GtkNotebook *nb);
|
||||
|
||||
void
|
||||
notebook_page_open (GtkNotebook *nb);
|
||||
|
||||
|
|
10
src/tfe6/com.github.ToshioCP.tfe.gschema.xml
Normal file
10
src/tfe6/com.github.ToshioCP.tfe.gschema.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<schemalist>
|
||||
<schema path="/com/github/ToshioCP/tfe/" id="com.github.ToshioCP.tfe">
|
||||
<key name="font" type="s">
|
||||
<default>'Monospace 12'</default>
|
||||
<summary>Font</summary>
|
||||
<description>The font to be used for textview.</description>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
91
src/tfe6/css.c
Normal file
91
src/tfe6/css.c
Normal 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
11
src/tfe6/css.h
Normal 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
27
src/tfe6/menu.ui
Executable file
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<menu id="menu">
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label">New</attribute>
|
||||
<attribute name="action">win.new</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label">Save As…</attribute>
|
||||
<attribute name="action">win.saveas</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label">Preference</attribute>
|
||||
<attribute name="action">win.pref</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label">Quit</attribute>
|
||||
<attribute name="action">win.close-all</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</menu>
|
||||
</interface>
|
16
src/tfe6/meson.build
Normal file
16
src/tfe6/meson.build
Normal file
|
@ -0,0 +1,16 @@
|
|||
project('tfe', 'c')
|
||||
|
||||
gtkdep = dependency('gtk4')
|
||||
|
||||
gnome=import('gnome')
|
||||
resources = gnome.compile_resources('resources','tfe.gresource.xml')
|
||||
gnome.compile_schemas(build_by_default: true, depend_files: 'com.github.ToshioCP.tfe.gschema.xml')
|
||||
|
||||
sourcefiles=files('tfeapplication.c', '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)
|
||||
|
7
src/tfe6/tfe.gresource.xml
Normal file
7
src/tfe6/tfe.gresource.xml
Normal 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
11
src/tfe6/tfe.h
Normal 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
140
src/tfe6/tfe.ui
Normal 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
230
src/tfe6/tfeapplication.c
Normal 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
210
src/tfe6/tfenotebook.c
Normal 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
26
src/tfe6/tfenotebook.h
Normal 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);
|
||||
|
|
@ -64,77 +64,57 @@ GFile *
|
|||
tfe_text_view_get_file (TfeTextView *tv) {
|
||||
g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);
|
||||
|
||||
return g_file_dup (tv->file);
|
||||
if (G_IS_FILE (tv->file))
|
||||
return g_file_dup (tv->file);
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
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));
|
||||
|
|
Loading…
Reference in a new issue