mirror of
https://github.com/ToshioCP/Gtk4-tutorial.git
synced 2025-01-12 20:03:28 +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/a.out
|
||||||
src/tfe/hello.txt
|
src/tfe/hello.txt
|
||||||
src/tfe/resources.c
|
src/tfe/resources.c
|
||||||
|
src/tfe4/_build
|
||||||
src/tfe5/_build
|
src/tfe5/_build
|
||||||
src/tfe5/hello.txt
|
src/tfe5/hello.txt
|
||||||
|
src/tfe6/_build
|
||||||
src/menu/a.out
|
src/menu/a.out
|
||||||
src/color/_build
|
src/color/_build
|
||||||
src/turtle/_build
|
src/turtle/_build
|
||||||
|
|
|
@ -32,5 +32,6 @@ You can read it without download.
|
||||||
1. [Menu and action](gfm/sec16.md)
|
1. [Menu and action](gfm/sec16.md)
|
||||||
1. [Stateful action](gfm/sec17.md)
|
1. [Stateful action](gfm/sec17.md)
|
||||||
1. [Ui file for menu and action entries](gfm/sec18.md)
|
1. [Ui file for menu and action entries](gfm/sec18.md)
|
||||||
1. [GtkDrawingArea and Cairo](gfm/sec19.md)
|
1. [Upgrade text file editor](gfm/sec19.md)
|
||||||
1. [Combine GtkDrawingArea and TfeTextView](gfm/sec20.md)
|
1. [GtkDrawingArea and Cairo](gfm/sec20.md)
|
||||||
|
1. [Combine GtkDrawingArea and TfeTextView](gfm/sec21.md)
|
||||||
|
|
|
@ -372,7 +372,7 @@ Then C destructs itself and finally the memories allocated to C is freed.
|
||||||
|
|
||||||
The idea above is based on an assumption that an object referred by nothing has reference count of zero.
|
The idea above is based on an assumption that an object referred by nothing has reference count of zero.
|
||||||
When the reference count drops to zero, the object starts its destruction process.
|
When the reference count drops to zero, the object starts its destruction process.
|
||||||
The destruction process is spitted into two phases: disposing and finalizing.
|
The destruction process is split into two phases: disposing and finalizing.
|
||||||
In the disposing process, the object invokes the function pointed by `dispose` in its class to release all references to other objects.
|
In the disposing process, the object invokes the function pointed by `dispose` in its class to release all references to other objects.
|
||||||
In the finalizing process, it invokes the function pointed by `finalize` in its class to complete the destruction process.
|
In the finalizing process, it invokes the function pointed by `finalize` in its class to complete the destruction process.
|
||||||
These functions are also called handlers or methods.
|
These functions are also called handlers or methods.
|
||||||
|
|
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) {
|
2 saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
|
||||||
3 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
3 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
||||||
4 GFile *file;
|
4 GFile *file;
|
||||||
5
|
5 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
|
||||||
6 if (response == GTK_RESPONSE_ACCEPT) {
|
6
|
||||||
7 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
|
7 gtk_window_destroy (GTK_WINDOW (dialog));
|
||||||
8 if (G_IS_FILE(file)) {
|
8 if (response == GTK_RESPONSE_ACCEPT) {
|
||||||
9 if (G_IS_FILE (tv->file))
|
9 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
|
||||||
10 g_object_unref (tv->file);
|
10 if (! G_IS_FILE (file))
|
||||||
11 tv->file = file;
|
11 g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n");
|
||||||
12 gtk_text_buffer_set_modified (tb, TRUE);
|
12 else {
|
||||||
13 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
|
13 save_file(file, tb, GTK_WINDOW (win));
|
||||||
14 tfe_text_view_save (TFE_TEXT_VIEW (tv));
|
14 if (G_IS_FILE (tv->file))
|
||||||
15 }
|
15 g_object_unref (tv->file);
|
||||||
16 }
|
16 tv->file = file;
|
||||||
17 gtk_window_destroy (GTK_WINDOW (dialog));
|
17 gtk_text_buffer_set_modified (tb, FALSE);
|
||||||
18 }
|
18 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
|
||||||
19
|
19 }
|
||||||
20 void
|
20 }
|
||||||
21 tfe_text_view_save (TfeTextView *tv) {
|
21 }
|
||||||
22 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
|
22
|
||||||
23
|
23 void
|
||||||
24 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
24 tfe_text_view_save (TfeTextView *tv) {
|
||||||
25 GtkTextIter start_iter;
|
25 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
|
||||||
26 GtkTextIter end_iter;
|
26
|
||||||
27 gchar *contents;
|
27 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
||||||
28 GtkWidget *message_dialog;
|
28 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
|
||||||
29 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
|
29
|
||||||
30 GError *err = NULL;
|
30 if (! gtk_text_buffer_get_modified (tb))
|
||||||
31
|
31 return; /* no need to save it */
|
||||||
32 if (! gtk_text_buffer_get_modified (tb))
|
32 else if (tv->file == NULL)
|
||||||
33 return; /* no need to save it */
|
33 tfe_text_view_saveas (tv);
|
||||||
34 else if (tv->file == NULL)
|
34 else if (! G_IS_FILE (tv->file))
|
||||||
35 tfe_text_view_saveas (tv);
|
35 g_error ("TfeTextView: The pointer in this object isn't NULL nor GFile object.\n");
|
||||||
36 else {
|
36 else {
|
||||||
37 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
|
37 if (save_file (tv->file, tb, GTK_WINDOW (win)))
|
||||||
38 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
|
38 gtk_text_buffer_set_modified (tb, FALSE);
|
||||||
39 if (g_file_replace_contents (tv->file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err))
|
39 }
|
||||||
40 gtk_text_buffer_set_modified (tb, FALSE);
|
40 }
|
||||||
41 else {
|
41
|
||||||
42 /* It is possible that tv->file is broken or you don't have permission to write. */
|
42 void
|
||||||
43 /* It is a good idea to set tv->file to NULL. */
|
43 tfe_text_view_saveas (TfeTextView *tv) {
|
||||||
44 if (G_IS_FILE (tv->file))
|
44 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
|
||||||
45 g_object_unref (tv->file);
|
45
|
||||||
46 tv->file =NULL;
|
46 GtkWidget *dialog;
|
||||||
47 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
|
47 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
|
||||||
48 message_dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL,
|
48
|
||||||
49 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
|
49 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
|
||||||
50 "%s.\n", err->message);
|
50 "Cancel", GTK_RESPONSE_CANCEL,
|
||||||
51 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
|
51 "Save", GTK_RESPONSE_ACCEPT,
|
||||||
52 gtk_widget_show (message_dialog);
|
52 NULL);
|
||||||
53 g_error_free (err);
|
53 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
|
||||||
54 }
|
54 gtk_widget_show (dialog);
|
||||||
55 }
|
55 }
|
||||||
56 }
|
|
||||||
57
|
|
||||||
58 void
|
|
||||||
59 tfe_text_view_saveas (TfeTextView *tv) {
|
|
||||||
60 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
|
|
||||||
61
|
|
||||||
62 GtkWidget *dialog;
|
|
||||||
63 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
|
|
||||||
64
|
|
||||||
65 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
|
|
||||||
66 "Cancel", GTK_RESPONSE_CANCEL,
|
|
||||||
67 "Save", GTK_RESPONSE_ACCEPT,
|
|
||||||
68 NULL);
|
|
||||||
69 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
|
|
||||||
70 gtk_widget_show (dialog);
|
|
||||||
71 }
|
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
- 20-56: `Tfe_text_view_save` function.
|
- 20-56: `Tfe_text_view_save` function.
|
||||||
|
@ -300,45 +284,46 @@ Otherwise probably bad things will happen.
|
||||||
9
|
9
|
||||||
10 if (response != GTK_RESPONSE_ACCEPT)
|
10 if (response != GTK_RESPONSE_ACCEPT)
|
||||||
11 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
|
11 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
|
||||||
12 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog))))
|
12 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)))) {
|
||||||
13 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
|
13 g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n");
|
||||||
14 else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
|
14 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
|
||||||
15 if (G_IS_FILE (file))
|
15 } else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
|
||||||
16 g_object_unref (file);
|
16 if (G_IS_FILE (file))
|
||||||
17 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
|
17 g_object_unref (file);
|
||||||
18 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
|
18 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
|
||||||
19 "%s.\n", err->message);
|
19 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
|
||||||
20 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
|
20 "%s.\n", err->message);
|
||||||
21 gtk_widget_show (message_dialog);
|
21 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
|
||||||
22 g_error_free (err);
|
22 gtk_widget_show (message_dialog);
|
||||||
23 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
|
23 g_error_free (err);
|
||||||
24 } else {
|
24 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
|
||||||
25 gtk_text_buffer_set_text (tb, contents, length);
|
25 } else {
|
||||||
26 g_free (contents);
|
26 gtk_text_buffer_set_text (tb, contents, length);
|
||||||
27 if (G_IS_FILE (tv->file))
|
27 g_free (contents);
|
||||||
28 g_object_unref (tv->file);
|
28 if (G_IS_FILE (tv->file))
|
||||||
29 tv->file = file;
|
29 g_object_unref (tv->file);
|
||||||
30 gtk_text_buffer_set_modified (tb, FALSE);
|
30 tv->file = file;
|
||||||
31 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
|
31 gtk_text_buffer_set_modified (tb, FALSE);
|
||||||
32 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
|
32 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
|
||||||
33 }
|
33 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
|
||||||
34 gtk_window_destroy (GTK_WINDOW (dialog));
|
34 }
|
||||||
35 }
|
35 gtk_window_destroy (GTK_WINDOW (dialog));
|
||||||
36
|
36 }
|
||||||
37 void
|
37
|
||||||
38 tfe_text_view_open (TfeTextView *tv, GtkWidget *win) {
|
38 void
|
||||||
39 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
|
39 tfe_text_view_open (TfeTextView *tv, GtkWidget *win) {
|
||||||
40 g_return_if_fail (GTK_IS_WINDOW (win));
|
40 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
|
||||||
41
|
41 g_return_if_fail (GTK_IS_WINDOW (win));
|
||||||
42 GtkWidget *dialog;
|
42
|
||||||
43
|
43 GtkWidget *dialog;
|
||||||
44 dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN,
|
44
|
||||||
45 "Cancel", GTK_RESPONSE_CANCEL,
|
45 dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||||
46 "Open", GTK_RESPONSE_ACCEPT,
|
46 "Cancel", GTK_RESPONSE_CANCEL,
|
||||||
47 NULL);
|
47 "Open", GTK_RESPONSE_ACCEPT,
|
||||||
48 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
|
48 NULL);
|
||||||
49 gtk_widget_show (dialog);
|
49 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
|
||||||
50 }
|
50 gtk_widget_show (dialog);
|
||||||
|
51 }
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
- 37-50: `tfe_text_view_open` function.
|
- 37-50: `tfe_text_view_open` function.
|
||||||
|
@ -384,8 +369,11 @@ However, in Gtk4, `gtk_dialog_run`is unavailable any more.
|
||||||
2 tfe_text_view_get_file (TfeTextView *tv) {
|
2 tfe_text_view_get_file (TfeTextView *tv) {
|
||||||
3 g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);
|
3 g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);
|
||||||
4
|
4
|
||||||
5 return g_file_dup (tv->file);
|
5 if (G_IS_FILE (tv->file))
|
||||||
6 }
|
6 return g_file_dup (tv->file);
|
||||||
|
7 else
|
||||||
|
8 return NULL;
|
||||||
|
9 }
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
The important thing is to duplicate `tv->file`.
|
The important thing is to duplicate `tv->file`.
|
||||||
|
|
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`.
|
GtkNotebook is a very important object in the text file editor `tfe`.
|
||||||
It connects the application and TfeTextView objects.
|
It connects the application and TfeTextView objects.
|
||||||
A set of functions related to GtkNotebook are declared in `tfenotebook.h`.
|
A set of public functions related to GtkNotebook are declared in `tfenotebook.h`.
|
||||||
The word "tfenotebook" is used only in filenames.
|
The word "tfenotebook" is used only in filenames.
|
||||||
There's no "TfeNotebook" object.
|
There's no "TfeNotebook" object.
|
||||||
|
|
||||||
|
@ -13,30 +13,34 @@ There's no "TfeNotebook" object.
|
||||||
2 notebook_page_save(GtkNotebook *nb);
|
2 notebook_page_save(GtkNotebook *nb);
|
||||||
3
|
3
|
||||||
4 void
|
4 void
|
||||||
5 notebook_page_open (GtkNotebook *nb);
|
5 notebook_page_close (GtkNotebook *nb);
|
||||||
6
|
6
|
||||||
7 void
|
7 void
|
||||||
8 notebook_page_new_with_file (GtkNotebook *nb, GFile *file);
|
8 notebook_page_open (GtkNotebook *nb);
|
||||||
9
|
9
|
||||||
10 void
|
10 void
|
||||||
11 notebook_page_new (GtkNotebook *nb);
|
11 notebook_page_new_with_file (GtkNotebook *nb, GFile *file);
|
||||||
12
|
12
|
||||||
|
13 void
|
||||||
|
14 notebook_page_new (GtkNotebook *nb);
|
||||||
|
15
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
This header file describes the public functions in `tfenotebook.c`.
|
This header file describes the public functions in `tfenotebook.c`.
|
||||||
|
|
||||||
- 10-11: The function `notebook_page_new` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView to the page.
|
- 1-2: `notebook_page_save` saves the current page to the file of which the name specified in the tab.
|
||||||
- 7-8: The function `notebook_page_new_with_file` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView to the page. A file is read and inserted into GtkTextBuffer.
|
If the name is `untitled` or `untitled` followed by digits, FileChooserDialog appears and a user can choose or specify a filename.
|
||||||
The GFile `file` is copied and inserted to the TfeTextView object.
|
- 4-5: `notebook_page_close` closes the current page.
|
||||||
- 4-5: `notebook_page_open` shows a file chooser dialog. Then, user chooses a file and the file is inserted into GtkTextBuffer.
|
- 7-8: `notebook_page_open` shows a file chooser dialog and a user can choose a file. The file is inserted to a new page.
|
||||||
- 1-2: `notebook_page_save` saves the contents in GtkTextBuffer into the file, which is got from the TfeTextView.
|
- 10-11: `notebook_page_new_with_file` generates a new page and the file give as an argument is read and inserted into the page.
|
||||||
|
- 13-14: `notebook_page_new` generates a new empty page.
|
||||||
|
|
||||||
You probably find that the functions above are higher level functions of
|
You probably find that the functions except `notebook_page_close` are higher level functions of
|
||||||
|
|
||||||
- `tfe_text_view_new`
|
|
||||||
- `tfe_text_view_new_with_file`
|
|
||||||
- `tef_text_view_open`
|
|
||||||
- `tfe_text_view_save`
|
- `tfe_text_view_save`
|
||||||
|
- `tef_text_view_open`
|
||||||
|
- `tfe_text_view_new_with_file`
|
||||||
|
- `tfe_text_view_new`
|
||||||
|
|
||||||
respectively.
|
respectively.
|
||||||
|
|
||||||
|
@ -44,7 +48,7 @@ There are two layers.
|
||||||
One of them is `tfe_text_view ...`, which is the lower level layer.
|
One of them is `tfe_text_view ...`, which is the lower level layer.
|
||||||
The other is `note_book ...`, which is the higher level layer.
|
The other is `note_book ...`, which is the higher level layer.
|
||||||
|
|
||||||
Now let's look at each program of the functions.
|
Now let's look at the program of each function.
|
||||||
|
|
||||||
## notebook\_page\_new
|
## notebook\_page\_new
|
||||||
|
|
||||||
|
@ -60,57 +64,56 @@ Now let's look at each program of the functions.
|
||||||
9
|
9
|
||||||
10 static void
|
10 static void
|
||||||
11 notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) {
|
11 notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) {
|
||||||
12 GtkWidget *scr;
|
12 GtkWidget *scr = gtk_scrolled_window_new ();
|
||||||
13 GtkNotebookPage *nbp;
|
13 GtkNotebookPage *nbp;
|
||||||
14 GtkWidget *lab;
|
14 GtkWidget *lab;
|
||||||
15 gint i;
|
15 int i;
|
||||||
16 scr = gtk_scrolled_window_new ();
|
16
|
||||||
17
|
17 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
|
||||||
18 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
|
18 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
|
||||||
19 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
|
19 lab = gtk_label_new (filename);
|
||||||
20 lab = gtk_label_new (filename);
|
20 i = gtk_notebook_append_page (nb, scr, lab);
|
||||||
21 i = gtk_notebook_append_page (nb, scr, lab);
|
21 nbp = gtk_notebook_get_page (nb, scr);
|
||||||
22 nbp = gtk_notebook_get_page (nb, scr);
|
22 g_object_set (nbp, "tab-expand", TRUE, NULL);
|
||||||
23 g_object_set (nbp, "tab-expand", TRUE, NULL);
|
23 gtk_notebook_set_current_page (nb, i);
|
||||||
24 gtk_notebook_set_current_page (nb, i);
|
24 g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed_cb), nb);
|
||||||
25 g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed), nb);
|
25 }
|
||||||
26 }
|
26
|
||||||
27
|
27 void
|
||||||
28 void
|
28 notebook_page_new (GtkNotebook *nb) {
|
||||||
29 notebook_page_new (GtkNotebook *nb) {
|
29 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
|
||||||
30 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
|
30
|
||||||
31
|
31 GtkWidget *tv;
|
||||||
32 GtkWidget *tv;
|
32 char *filename;
|
||||||
33 char *filename;
|
33
|
||||||
34
|
34 tv = tfe_text_view_new ();
|
||||||
35 tv = tfe_text_view_new ();
|
35 filename = get_untitled ();
|
||||||
36 filename = get_untitled ();
|
36 notebook_page_build (nb, tv, filename);
|
||||||
37 notebook_page_build (nb, tv, filename);
|
37 }
|
||||||
38 }
|
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
- 28-38: `notebook_page_new` function.
|
- 27-37: `notebook_page_new` function.
|
||||||
- 30: `g_return_if_fail` is used to check the argument.
|
- 29: `g_return_if_fail` is used to check the argument.
|
||||||
- 35: Generates TfeTextView object.
|
- 34: Generates TfeTextView object.
|
||||||
- 36: Generates filename, which is "Untitled", "Untitled2", ... .
|
- 35: Generates filename, which is "Untitled", "Untitled1", ... .
|
||||||
- 1-8: `get_untitled` function.
|
- 1-8: `get_untitled` function.
|
||||||
- 3: Static variable `c` is initialized at the first call of this function. After that `c` keeps its value except it is changed explicitly.
|
- 3: Static variable `c` is initialized at the first call of this function. After that `c` keeps its value unless it is changed explicitly.
|
||||||
- 4-7: Increases `c` by one and if it is zero then it returns "Untitled". If it is a positive integer then the it returns "Untitled\<the integer\>", for example, "Untitled1", "Untitled2", and so on.
|
- 4-7: Increases `c` by one and if it is zero then it returns "Untitled". If it is a positive integer then it returns "Untitled\<the integer\>", for example, "Untitled1", "Untitled2", and so on.
|
||||||
The function `g_strdup_printf` generates a string and it should be freed by `g_free` when it becomes useless.
|
The function `g_strdup_printf` generates a string and it should be freed by `g_free` when it becomes useless.
|
||||||
The caller of `get_untitled` is in charge of freeing the string.
|
The caller of `get_untitled` is in charge of freeing the string.
|
||||||
- 37: calls `notebook_page_build` to build the contents of the page.
|
- 36: calls `notebook_page_build` to build the contents of the page.
|
||||||
- 10- 26: `notebook_page_build` function.
|
- 10- 25: `notebook_page_build` function.
|
||||||
- 16: Generates GtkScrolledWindow.
|
- 12: Generates GtkScrolledWindow.
|
||||||
- 18: Sets the wrap mode of `tv` to GTK_WRAP_WORD_CHAR so that lines are broken between words or graphemes.
|
- 17: Sets the wrap mode of `tv` to GTK_WRAP_WORD_CHAR so that lines are broken between words or graphemes.
|
||||||
- 19: Inserts `tv` to GtkscrolledWindow as a child.
|
- 18: Inserts `tv` to GtkscrolledWindow as a child.
|
||||||
- 20-21: Generates GtkLabel, then appends it to GtkNotebookPage.
|
- 19-20: Generates GtkLabel, then appends it to GtkNotebookPage.
|
||||||
- 22-23: Sets "tab-expand" property to TRUE.
|
- 21-22: Sets "tab-expand" property to TRUE.
|
||||||
The function g\_object\_set sets properties on an object.
|
The function g\_object\_set sets properties on an object.
|
||||||
The object is any object derived from GObject.
|
The object is any object derived from GObject.
|
||||||
In many cases, an object has its own function to set its properties, but sometimes not.
|
In many cases, an object has its own function to set its properties, but sometimes not.
|
||||||
In that case, use g\_object\_set to set the property.
|
In that case, use g\_object\_set to set the property.
|
||||||
- 24: Sets the current page of `nb` to `i` which is the number of the GtkNotebookPage above.
|
- 23: Sets the current page of `nb` to the newly generated page.
|
||||||
- 25: Connects "change-file" signal and `file_changed` handler.
|
- 24: Connects "change-file" signal and `file_changed_cb` handler.
|
||||||
|
|
||||||
## notebook\_page\_new\_with\_file
|
## notebook\_page\_new\_with\_file
|
||||||
|
|
||||||
|
@ -139,7 +142,7 @@ The return value NULL means that an error has happened.
|
||||||
|
|
||||||
~~~C
|
~~~C
|
||||||
1 static void
|
1 static void
|
||||||
2 open_response (TfeTextView *tv, gint response, GtkNotebook *nb) {
|
2 open_response (TfeTextView *tv, int response, GtkNotebook *nb) {
|
||||||
3 GFile *file;
|
3 GFile *file;
|
||||||
4 char *filename;
|
4 char *filename;
|
||||||
5
|
5
|
||||||
|
@ -181,63 +184,106 @@ Such object has floating reference.
|
||||||
You need to call `g_object_ref_sink` first.
|
You need to call `g_object_ref_sink` first.
|
||||||
Then the floating reference is converted into an ordinary reference.
|
Then the floating reference is converted into an ordinary reference.
|
||||||
Now you call `g_object_unref` to decrease the reference count by one.
|
Now you call `g_object_unref` to decrease the reference count by one.
|
||||||
- 9-11: If `tfe_text_view_get_file` returns a pointer not to point GFile, then something bad happens.
|
- 9-11: If `tfe_text_view_get_file` returns a pointer not to point GFile, it means that an error has happened.
|
||||||
Sink and unref `tv`.
|
Sink and unref `tv`.
|
||||||
- 12-16: Otherwise, everything is okay.
|
- 12-16: Otherwise, everything is okay.
|
||||||
Gets the filename, builds the contents of the page.
|
Gets the filename, builds the contents of the page.
|
||||||
|
|
||||||
## notebook\_page\_save
|
## notebook\_page\_close
|
||||||
|
|
||||||
~~~C
|
~~~C
|
||||||
1 void
|
1 void
|
||||||
2 notebook_page_save(GtkNotebook *nb) {
|
2 notebook_page_close (GtkNotebook *nb) {
|
||||||
3 gint i;
|
3 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
|
||||||
|
4
|
||||||
|
5 GtkWidget *win;
|
||||||
|
6 int i;
|
||||||
|
7
|
||||||
|
8 if (gtk_notebook_get_n_pages (nb) == 1) {
|
||||||
|
9 win = gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW);
|
||||||
|
10 gtk_window_destroy(GTK_WINDOW (win));
|
||||||
|
11 } else {
|
||||||
|
12 i = gtk_notebook_get_current_page (nb);
|
||||||
|
13 gtk_notebook_remove_page (GTK_NOTEBOOK (nb), i);
|
||||||
|
14 }
|
||||||
|
15 }
|
||||||
|
~~~
|
||||||
|
|
||||||
|
This function closes the current page.
|
||||||
|
If the page is the only page the notebook has, then the function destroys the top window and quits the application.
|
||||||
|
|
||||||
|
- 8-10: If the page is the only page the notebook has, it calls gtk\_window\_destroy to destroys the top window.
|
||||||
|
- 11-13: Otherwise, removes the current page.
|
||||||
|
|
||||||
|
## notebook\_page\_save
|
||||||
|
|
||||||
|
~~~C
|
||||||
|
1 static TfeTextView *
|
||||||
|
2 get_current_textview (GtkNotebook *nb) {
|
||||||
|
3 int i;
|
||||||
4 GtkWidget *scr;
|
4 GtkWidget *scr;
|
||||||
5 GtkWidget *tv;
|
5 GtkWidget *tv;
|
||||||
6
|
6
|
||||||
7 i = gtk_notebook_get_current_page (nb);
|
7 i = gtk_notebook_get_current_page (nb);
|
||||||
8 scr = gtk_notebook_get_nth_page (nb, i);
|
8 scr = gtk_notebook_get_nth_page (nb, i);
|
||||||
9 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
|
9 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
|
||||||
10 tfe_text_view_save (TFE_TEXT_VIEW (tv));
|
10 return TFE_TEXT_VIEW (tv);
|
||||||
11 }
|
11 }
|
||||||
|
12
|
||||||
|
13 void
|
||||||
|
14 notebook_page_save (GtkNotebook *nb) {
|
||||||
|
15 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
|
||||||
|
16
|
||||||
|
17 TfeTextView *tv;
|
||||||
|
18
|
||||||
|
19 tv = get_current_textview (nb);
|
||||||
|
20 tfe_text_view_save (TFE_TEXT_VIEW (tv));
|
||||||
|
21 }
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
- 7-9: Get TfeTextView belongs to the current notebook page.
|
- 13-21: `notebook_page_save`.
|
||||||
- 10: Call `tfe_text_view_save`.
|
- 19: Gets TfeTextView belongs to the current page.
|
||||||
|
- 20: Calls `tfe_text_view_save`.
|
||||||
|
- 1-11: `get_current_textview`.
|
||||||
|
This function gets the TfeTextView object belongs to the current page.
|
||||||
|
- 7: Gets the page number of the current page.
|
||||||
|
- 8: Gets the child object `scr`, which is GtkScrolledWindow object, of the current page.
|
||||||
|
- 9-10: Gets the child object of `scr`, which is TfeTextView object, and return it.
|
||||||
|
|
||||||
## file\_changed handler
|
## file\_changed\_cb handler
|
||||||
|
|
||||||
The function `file_changed` is a handler connected to "change-file" signal.
|
The function `file_changed_cb` is a handler connected to "change-file" signal.
|
||||||
If the file in TfeTextView is changed, it emits this signal.
|
If the file in TfeTextView is changed, it emits this signal.
|
||||||
This handler changes the label of GtkNotebookPage.
|
This handler changes the label of GtkNotebookPage.
|
||||||
|
|
||||||
~~~C
|
~~~C
|
||||||
1 static void
|
1 static void
|
||||||
2 file_changed (TfeTextView *tv, GtkNotebook *nb) {
|
2 file_changed_cb (TfeTextView *tv, GtkNotebook *nb) {
|
||||||
3 GFile *file;
|
3 GtkWidget *scr;
|
||||||
4 char *filename;
|
4 GtkWidget *label;
|
||||||
5 GtkWidget *scr;
|
5 GFile *file;
|
||||||
6 GtkWidget *label;
|
6 char *filename;
|
||||||
7
|
7
|
||||||
8 file = tfe_text_view_get_file (tv);
|
8 file = tfe_text_view_get_file (tv);
|
||||||
9 scr = gtk_widget_get_parent (GTK_WIDGET (tv));
|
9 scr = gtk_widget_get_parent (GTK_WIDGET (tv));
|
||||||
10 if (G_IS_FILE (file))
|
10 if (G_IS_FILE (file)) {
|
||||||
11 filename = g_file_get_basename (file);
|
11 filename = g_file_get_basename (file);
|
||||||
12 else
|
12 g_object_unref (file);
|
||||||
13 filename = get_untitled ();
|
13 } else
|
||||||
14 label = gtk_label_new (filename);
|
14 filename = get_untitled ();
|
||||||
15 gtk_notebook_set_tab_label (nb, scr, label);
|
15 label = gtk_label_new (filename);
|
||||||
16 g_object_unref (file);
|
16 gtk_notebook_set_tab_label (nb, scr, label);
|
||||||
17 g_free (filename);
|
17 }
|
||||||
18 }
|
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
- 8: Gets GFile from TfeTextView.
|
- 8: Gets GFile from TfeTextView.
|
||||||
- 9: Gets GkScrolledWindow which is the parent widget of `tv`.
|
- 9: Gets GkScrolledWindow which is the parent widget of `tv`.
|
||||||
- 10-13: If `file` points GFile, then assigns the filename of the GFile into `filename`.
|
- 10-12: If `file` points GFile, then assigns the filename of the GFile into `filename`.
|
||||||
Otherwise (file is NULL), assigns untitled string to `filename`.
|
Then, unref the GFile object `file`.
|
||||||
- 14-15: Generates a label with the filename and inserts it into GtkNotebookPage.
|
- 13-14: Otherwise (file is NULL), assigns untitled string to `filename`.
|
||||||
- 16-17: Unrefs `file` and frees `filename`.
|
- 15-16: Generates a label with the filename and inserts it into GtkNotebookPage.
|
||||||
|
|
||||||
|
The string `filename` is used in the GtkLabel object.
|
||||||
|
You mustn't free it.
|
||||||
|
|
||||||
Up: [Readme.md](../Readme.md), Prev: [Section 12](sec12.md), Next: [Section 14](sec14.md)
|
Up: [Readme.md](../Readme.md), Prev: [Section 12](sec12.md), Next: [Section 14](sec14.md)
|
||||||
|
|
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
|
||||||
|
|
||||||
`tfeapplication.c` includes all the code other than `tfetxtview.c` and `tfenotebook.c`.
|
`tfeapplication.c` includes all the code other than `tfetxtview.c` and `tfenotebook.c`.
|
||||||
It does following things.
|
It does:
|
||||||
|
|
||||||
- Application support, mainly handling command line arguments.
|
- Application support, mainly handling command line arguments.
|
||||||
- Build widgets using ui file.
|
- Builds widgets using ui file.
|
||||||
- Connect button signals and their handlers.
|
- Connects button signals and their handlers.
|
||||||
- Manage CSS.
|
- Manages CSS.
|
||||||
|
|
||||||
## main
|
## main
|
||||||
|
|
||||||
|
@ -53,9 +53,9 @@ The handler is as follows.
|
||||||
1 static void
|
1 static void
|
||||||
2 tfe_startup (GApplication *application) {
|
2 tfe_startup (GApplication *application) {
|
||||||
3 GtkApplication *app = GTK_APPLICATION (application);
|
3 GtkApplication *app = GTK_APPLICATION (application);
|
||||||
4 GtkApplicationWindow *win;
|
4 GtkBuilder *build;
|
||||||
5 GtkNotebook *nb;
|
5 GtkApplicationWindow *win;
|
||||||
6 GtkBuilder *build;
|
6 GtkNotebook *nb;
|
||||||
7 GtkButton *btno;
|
7 GtkButton *btno;
|
||||||
8 GtkButton *btnn;
|
8 GtkButton *btnn;
|
||||||
9 GtkButton *btns;
|
9 GtkButton *btns;
|
||||||
|
@ -69,10 +69,10 @@ The handler is as follows.
|
||||||
17 btnn = GTK_BUTTON (gtk_builder_get_object (build, "btnn"));
|
17 btnn = GTK_BUTTON (gtk_builder_get_object (build, "btnn"));
|
||||||
18 btns = GTK_BUTTON (gtk_builder_get_object (build, "btns"));
|
18 btns = GTK_BUTTON (gtk_builder_get_object (build, "btns"));
|
||||||
19 btnc = GTK_BUTTON (gtk_builder_get_object (build, "btnc"));
|
19 btnc = GTK_BUTTON (gtk_builder_get_object (build, "btnc"));
|
||||||
20 g_signal_connect (btno, "clicked", G_CALLBACK (open_clicked), nb);
|
20 g_signal_connect_swapped (btno, "clicked", G_CALLBACK (open_cb), nb);
|
||||||
21 g_signal_connect (btnn, "clicked", G_CALLBACK (new_clicked), nb);
|
21 g_signal_connect_swapped (btnn, "clicked", G_CALLBACK (new_cb), nb);
|
||||||
22 g_signal_connect (btns, "clicked", G_CALLBACK (save_clicked), nb);
|
22 g_signal_connect_swapped (btns, "clicked", G_CALLBACK (save_cb), nb);
|
||||||
23 g_signal_connect (btnc, "clicked", G_CALLBACK (close_clicked), nb);
|
23 g_signal_connect_swapped (btnc, "clicked", G_CALLBACK (close_cb), nb);
|
||||||
24 g_object_unref(build);
|
24 g_object_unref(build);
|
||||||
25
|
25
|
||||||
26 GdkDisplay *display;
|
26 GdkDisplay *display;
|
||||||
|
@ -119,7 +119,8 @@ If you want to set style to GtkTextView, substitute "textview" for the selector.
|
||||||
textview {color: yellow; ...}
|
textview {color: yellow; ...}
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
Class, ID and some other things can be applied to the selector like Web CSS. Refer GTK4 API reference for further information.
|
Class, ID and some other things can be applied to the selector like Web CSS.
|
||||||
|
Refer [GTK4 API reference](https://gnome.pages.gitlab.gnome.org/gtk/gtk/theming.html) for further information.
|
||||||
|
|
||||||
In line 30, the CSS is a string.
|
In line 30, the CSS is a string.
|
||||||
|
|
||||||
|
@ -128,11 +129,10 @@ textview {padding: 10px; font-family: monospace; font-size: 12pt;}
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
- padding is a space between the border and contents.
|
- padding is a space between the border and contents.
|
||||||
This space makes the text easier to read.
|
This space makes the textview easier to read.
|
||||||
- font-family is a name of font.
|
- font-family is a name of font.
|
||||||
"monospace" is one of the generic family font keywords.
|
"monospace" is one of the generic family font keywords.
|
||||||
- font-size is set to 12pt.
|
- font-size is set to 12pt.
|
||||||
It is a bit large, but easy on the eyes especially for elderly people.
|
|
||||||
|
|
||||||
### GtkStyleContext, GtkCSSProvider and GdkDisplay
|
### GtkStyleContext, GtkCSSProvider and GdkDisplay
|
||||||
|
|
||||||
|
@ -154,6 +154,8 @@ Look at the source file of `startup` handler again.
|
||||||
|
|
||||||
It is possible to add the provider to the context of GtkTextView instead of GdkDiplay.
|
It is possible to add the provider to the context of GtkTextView instead of GdkDiplay.
|
||||||
To do so, rewrite `tfe_text_view_new`.
|
To do so, rewrite `tfe_text_view_new`.
|
||||||
|
First, get the GtkStyleContext object of a TfeTextView object.
|
||||||
|
Then adds the CSS provider to the context.
|
||||||
|
|
||||||
~~~C
|
~~~C
|
||||||
GtkWidget *
|
GtkWidget *
|
||||||
|
@ -184,46 +186,36 @@ They just generate a new GtkNotebookPage.
|
||||||
1 static void
|
1 static void
|
||||||
2 tfe_activate (GApplication *application) {
|
2 tfe_activate (GApplication *application) {
|
||||||
3 GtkApplication *app = GTK_APPLICATION (application);
|
3 GtkApplication *app = GTK_APPLICATION (application);
|
||||||
4 GtkWidget *win;
|
4 GtkWidget *win = GTK_WIDGET (gtk_application_get_active_window (app));
|
||||||
5 GtkWidget *boxv;
|
5 GtkWidget *boxv = gtk_window_get_child (GTK_WINDOW (win));
|
||||||
6 GtkNotebook *nb;
|
6 GtkNotebook *nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
|
||||||
7
|
7
|
||||||
8 win = GTK_WIDGET (gtk_application_get_active_window (app));
|
8 notebook_page_new (nb);
|
||||||
9 boxv = gtk_window_get_child (GTK_WINDOW (win));
|
9 gtk_widget_show (GTK_WIDGET (win));
|
||||||
10 nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
|
10 }
|
||||||
11
|
11
|
||||||
12 notebook_page_new (nb);
|
12 static void
|
||||||
13 gtk_widget_show (GTK_WIDGET (win));
|
13 tfe_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) {
|
||||||
14 }
|
14 GtkApplication *app = GTK_APPLICATION (application);
|
||||||
15
|
15 GtkWidget *win = GTK_WIDGET (gtk_application_get_active_window (app));
|
||||||
16 static void
|
16 GtkWidget *boxv = gtk_window_get_child (GTK_WINDOW (win));
|
||||||
17 tfe_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) {
|
17 GtkNotebook *nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
|
||||||
18 GtkApplication *app = GTK_APPLICATION (application);
|
18 int i;
|
||||||
19 GtkWidget *win;
|
19
|
||||||
20 GtkWidget *boxv;
|
20 for (i = 0; i < n_files; i++)
|
||||||
21 GtkNotebook *nb;
|
21 notebook_page_new_with_file (nb, files[i]);
|
||||||
22 int i;
|
22 if (gtk_notebook_get_n_pages (nb) == 0)
|
||||||
23
|
23 notebook_page_new (nb);
|
||||||
24 win = GTK_WIDGET (gtk_application_get_active_window (app));
|
24 gtk_widget_show (win);
|
||||||
25 boxv = gtk_window_get_child (GTK_WINDOW (win));
|
25 }
|
||||||
26 nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
|
|
||||||
27
|
|
||||||
28 for (i = 0; i < n_files; i++)
|
|
||||||
29 notebook_page_new_with_file (nb, files[i]);
|
|
||||||
30 if (gtk_notebook_get_n_pages (nb) == 0)
|
|
||||||
31 notebook_page_new (nb);
|
|
||||||
32 gtk_widget_show (win);
|
|
||||||
33 }
|
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
- 1-14: `tfe_activate`.
|
- 1-11: `tfe_activate`.
|
||||||
- 8-10: Gets GtkNotebook object.
|
- 8-10: Generates a new page and shows the window.
|
||||||
- 12-13: Generates a new GtkNotebookPage and show the window.
|
- 12-25: `tfe_open`.
|
||||||
- 16-33: `tfe_open`.
|
- 20-21: Generates notebook pages with files.
|
||||||
- 24-26: Gets GtkNotebook object.
|
- 22-23: If no page has generated, maybe because of read error, then it generates a empty page.
|
||||||
- 28-29: Generates GtkNotebookPage with files.
|
- 24: Shows the window.
|
||||||
- 30-31: If opening and reading file failed and no GtkNotebookPage has generated, then it generates a empty page.
|
|
||||||
- 32: Shows the window.
|
|
||||||
|
|
||||||
These codes have become really simple thanks to tfenotebook.c and tfetextview.c.
|
These codes have become really simple thanks to tfenotebook.c and tfetextview.c.
|
||||||
|
|
||||||
|
@ -265,43 +257,27 @@ The second instance immediately quits so shell prompt soon appears.
|
||||||
|
|
||||||
~~~C
|
~~~C
|
||||||
1 static void
|
1 static void
|
||||||
2 open_clicked (GtkWidget *btno, GtkNotebook *nb) {
|
2 open_cb (GtkNotebook *nb) {
|
||||||
3 notebook_page_open (nb);
|
3 notebook_page_open (nb);
|
||||||
4 }
|
4 }
|
||||||
5
|
5
|
||||||
6 static void
|
6 static void
|
||||||
7 new_clicked (GtkWidget *btnn, GtkNotebook *nb) {
|
7 new_cb (GtkNotebook *nb) {
|
||||||
8 notebook_page_new (nb);
|
8 notebook_page_new (nb);
|
||||||
9 }
|
9 }
|
||||||
10
|
10
|
||||||
11 static void
|
11 static void
|
||||||
12 save_clicked (GtkWidget *btns, GtkNotebook *nb) {
|
12 save_cb (GtkNotebook *nb) {
|
||||||
13 notebook_page_save (nb);
|
13 notebook_page_save (nb);
|
||||||
14 }
|
14 }
|
||||||
15
|
15
|
||||||
16 static void
|
16 static void
|
||||||
17 close_clicked (GtkWidget *btnc, GtkNotebook *nb) {
|
17 close_cb (GtkNotebook *nb) {
|
||||||
18 GtkWidget *win;
|
18 notebook_page_close (GTK_NOTEBOOK (nb));
|
||||||
19 GtkWidget *boxv;
|
19 }
|
||||||
20 gint i;
|
|
||||||
21
|
|
||||||
22 if (gtk_notebook_get_n_pages (nb) == 1) {
|
|
||||||
23 boxv = gtk_widget_get_parent (GTK_WIDGET (nb));
|
|
||||||
24 win = gtk_widget_get_parent (boxv);
|
|
||||||
25 gtk_window_destroy (GTK_WINDOW (win));
|
|
||||||
26 } else {
|
|
||||||
27 i = gtk_notebook_get_current_page (nb);
|
|
||||||
28 gtk_notebook_remove_page (GTK_NOTEBOOK (nb), i);
|
|
||||||
29 }
|
|
||||||
30 }
|
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
`open_clicked`, `new_clicked` and `save_clicked` just call corresponding notebook page functions.
|
`open_cb`, `new_cb`, `save_cb` and `close_cb` just call corresponding notebook page functions.
|
||||||
`close_clicked` is a bit complicated.
|
|
||||||
|
|
||||||
- 22-25: If there's only one page, we need to close the top level window and quit the application.
|
|
||||||
First, get the top level window and call `gtk_window_destroy`.
|
|
||||||
- 26-28: Otherwise, it removes the current page.
|
|
||||||
|
|
||||||
## meson.build
|
## meson.build
|
||||||
|
|
||||||
|
@ -318,13 +294,13 @@ First, get the top level window and call `gtk_window_destroy`.
|
||||||
10 executable('tfe', sourcefiles, resources, dependencies: gtkdep)
|
10 executable('tfe', sourcefiles, resources, dependencies: gtkdep)
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
In this file, just the source file names are modified.
|
In this file, just the source file names are modified from the prior version.
|
||||||
|
|
||||||
## source files
|
## source files
|
||||||
|
|
||||||
The [source files](https://github.com/ToshioCP/Gtk4-tutorial/tree/main/src/tfe5) of the text editor `tfe` will be shown in the next section.
|
The [source files](https://github.com/ToshioCP/Gtk4-tutorial/tree/main/src/tfe5) of the text editor `tfe` will be shown in the next section.
|
||||||
|
|
||||||
You can download the files.
|
You can also download the files from the [repository](https://github.com/ToshioCP/Gtk4-tutorial).
|
||||||
There are two options.
|
There are two options.
|
||||||
|
|
||||||
- Use git and clone.
|
- Use git and clone.
|
||||||
|
@ -335,6 +311,6 @@ If you use git, run the terminal and type the following.
|
||||||
|
|
||||||
$ git clone https://github.com/ToshioCP/Gtk4-tutorial.git
|
$ git clone https://github.com/ToshioCP/Gtk4-tutorial.git
|
||||||
|
|
||||||
The source files are under `/src/tfe5` directory.
|
The source files are under [`/src/tfe5`](../src/tfe5) directory.
|
||||||
|
|
||||||
Up: [Readme.md](../Readme.md), Prev: [Section 13](sec13.md), Next: [Section 15](sec15.md)
|
Up: [Readme.md](../Readme.md), Prev: [Section 13](sec13.md), Next: [Section 15](sec15.md)
|
||||||
|
|
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
|
## tfe.ui
|
||||||
|
|
||||||
~~~xml
|
~~~xml
|
||||||
1 <interface>
|
1 <?xml version="1.0" encoding="UTF-8"?>
|
||||||
2 <object class="GtkApplicationWindow" id="win">
|
2 <interface>
|
||||||
3 <property name="title">file editor</property>
|
3 <object class="GtkApplicationWindow" id="win">
|
||||||
4 <property name="default-width">600</property>
|
4 <property name="title">file editor</property>
|
||||||
5 <property name="default-height">400</property>
|
5 <property name="default-width">600</property>
|
||||||
6 <child>
|
6 <property name="default-height">400</property>
|
||||||
7 <object class="GtkBox" id="boxv">
|
7 <child>
|
||||||
8 <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
8 <object class="GtkBox" id="boxv">
|
||||||
9 <child>
|
9 <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
||||||
10 <object class="GtkBox" id="boxh">
|
10 <child>
|
||||||
11 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
11 <object class="GtkBox" id="boxh">
|
||||||
12 <child>
|
12 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
||||||
13 <object class="GtkLabel" id="dmy1">
|
13 <child>
|
||||||
14 <property name="width-chars">10</property>
|
14 <object class="GtkLabel" id="dmy1">
|
||||||
15 </object>
|
15 <property name="width-chars">10</property>
|
||||||
16 </child>
|
16 </object>
|
||||||
17 <child>
|
17 </child>
|
||||||
18 <object class="GtkButton" id="btnn">
|
18 <child>
|
||||||
19 <property name="label">_New</property>
|
19 <object class="GtkButton" id="btnn">
|
||||||
20 <property name="use-underline">TRUE</property>
|
20 <property name="label">New</property>
|
||||||
21 </object>
|
21 </object>
|
||||||
22 </child>
|
22 </child>
|
||||||
23 <child>
|
23 <child>
|
||||||
24 <object class="GtkButton" id="btno">
|
24 <object class="GtkButton" id="btno">
|
||||||
25 <property name="label">_Open</property>
|
25 <property name="label">Open</property>
|
||||||
26 <property name="use-underline">TRUE</property>
|
26 </object>
|
||||||
27 </object>
|
27 </child>
|
||||||
28 </child>
|
28 <child>
|
||||||
29 <child>
|
29 <object class="GtkLabel" id="dmy2">
|
||||||
30 <object class="GtkLabel" id="dmy2">
|
30 <property name="hexpand">TRUE</property>
|
||||||
31 <property name="hexpand">TRUE</property>
|
31 </object>
|
||||||
32 </object>
|
32 </child>
|
||||||
33 </child>
|
33 <child>
|
||||||
34 <child>
|
34 <object class="GtkButton" id="btns">
|
||||||
35 <object class="GtkButton" id="btns">
|
35 <property name="label">Save</property>
|
||||||
36 <property name="label">_Save</property>
|
36 </object>
|
||||||
37 <property name="use-underline">TRUE</property>
|
37 </child>
|
||||||
38 </object>
|
38 <child>
|
||||||
39 </child>
|
39 <object class="GtkButton" id="btnc">
|
||||||
40 <child>
|
40 <property name="label">Close</property>
|
||||||
41 <object class="GtkButton" id="btnc">
|
41 </object>
|
||||||
42 <property name="label">_Close</property>
|
42 </child>
|
||||||
43 <property name="use-underline">TRUE</property>
|
43 <child>
|
||||||
44 </object>
|
44 <object class="GtkLabel" id="dmy3">
|
||||||
45 </child>
|
45 <property name="width-chars">10</property>
|
||||||
46 <child>
|
46 </object>
|
||||||
47 <object class="GtkLabel" id="dmy3">
|
47 </child>
|
||||||
48 <property name="width-chars">10</property>
|
48 </object>
|
||||||
49 </object>
|
49 </child>
|
||||||
50 </child>
|
50 <child>
|
||||||
51 </object>
|
51 <object class="GtkNotebook" id="nb">
|
||||||
52 </child>
|
52 <property name="scrollable">TRUE</property>
|
||||||
53 <child>
|
53 <property name="hexpand">TRUE</property>
|
||||||
54 <object class="GtkNotebook" id="nb">
|
54 <property name="vexpand">TRUE</property>
|
||||||
55 <property name="scrollable">TRUE</property>
|
55 </object>
|
||||||
56 <property name="hexpand">TRUE</property>
|
56 </child>
|
||||||
57 <property name="vexpand">TRUE</property>
|
57 </object>
|
||||||
58 </object>
|
58 </child>
|
||||||
59 </child>
|
59 </object>
|
||||||
60 </object>
|
60 </interface>
|
||||||
61 </child>
|
61
|
||||||
62 </object>
|
|
||||||
63 </interface>
|
|
||||||
64
|
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
## tfe.h
|
## tfe.h
|
||||||
|
@ -138,123 +135,103 @@ It is a good practice for you to add more features.
|
||||||
## tfeapplication.c
|
## tfeapplication.c
|
||||||
|
|
||||||
~~~C
|
~~~C
|
||||||
1 #include "tfe.h"
|
1 #include "tfe.h"
|
||||||
2
|
2
|
||||||
3 static void
|
3 static void
|
||||||
4 open_clicked (GtkWidget *btno, GtkNotebook *nb) {
|
4 open_cb (GtkNotebook *nb) {
|
||||||
5 notebook_page_open (nb);
|
5 notebook_page_open (nb);
|
||||||
6 }
|
6 }
|
||||||
7
|
7
|
||||||
8 static void
|
8 static void
|
||||||
9 new_clicked (GtkWidget *btnn, GtkNotebook *nb) {
|
9 new_cb (GtkNotebook *nb) {
|
||||||
10 notebook_page_new (nb);
|
10 notebook_page_new (nb);
|
||||||
11 }
|
11 }
|
||||||
12
|
12
|
||||||
13 static void
|
13 static void
|
||||||
14 save_clicked (GtkWidget *btns, GtkNotebook *nb) {
|
14 save_cb (GtkNotebook *nb) {
|
||||||
15 notebook_page_save (nb);
|
15 notebook_page_save (nb);
|
||||||
16 }
|
16 }
|
||||||
17
|
17
|
||||||
18 static void
|
18 static void
|
||||||
19 close_clicked (GtkWidget *btnc, GtkNotebook *nb) {
|
19 close_cb (GtkNotebook *nb) {
|
||||||
20 GtkWidget *win;
|
20 notebook_page_close (GTK_NOTEBOOK (nb));
|
||||||
21 GtkWidget *boxv;
|
21 }
|
||||||
22 gint i;
|
22
|
||||||
23
|
23 static void
|
||||||
24 if (gtk_notebook_get_n_pages (nb) == 1) {
|
24 tfe_activate (GApplication *application) {
|
||||||
25 boxv = gtk_widget_get_parent (GTK_WIDGET (nb));
|
25 GtkApplication *app = GTK_APPLICATION (application);
|
||||||
26 win = gtk_widget_get_parent (boxv);
|
26 GtkWidget *win = GTK_WIDGET (gtk_application_get_active_window (app));
|
||||||
27 gtk_window_destroy (GTK_WINDOW (win));
|
27 GtkWidget *boxv = gtk_window_get_child (GTK_WINDOW (win));
|
||||||
28 } else {
|
28 GtkNotebook *nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
|
||||||
29 i = gtk_notebook_get_current_page (nb);
|
29
|
||||||
30 gtk_notebook_remove_page (GTK_NOTEBOOK (nb), i);
|
30 notebook_page_new (nb);
|
||||||
31 }
|
31 gtk_widget_show (GTK_WIDGET (win));
|
||||||
32 }
|
32 }
|
||||||
33
|
33
|
||||||
34 static void
|
34 static void
|
||||||
35 tfe_activate (GApplication *application) {
|
35 tfe_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) {
|
||||||
36 GtkApplication *app = GTK_APPLICATION (application);
|
36 GtkApplication *app = GTK_APPLICATION (application);
|
||||||
37 GtkWidget *win;
|
37 GtkWidget *win = GTK_WIDGET (gtk_application_get_active_window (app));
|
||||||
38 GtkWidget *boxv;
|
38 GtkWidget *boxv = gtk_window_get_child (GTK_WINDOW (win));
|
||||||
39 GtkNotebook *nb;
|
39 GtkNotebook *nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
|
||||||
40
|
40 int i;
|
||||||
41 win = GTK_WIDGET (gtk_application_get_active_window (app));
|
41
|
||||||
42 boxv = gtk_window_get_child (GTK_WINDOW (win));
|
42 for (i = 0; i < n_files; i++)
|
||||||
43 nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
|
43 notebook_page_new_with_file (nb, files[i]);
|
||||||
44
|
44 if (gtk_notebook_get_n_pages (nb) == 0)
|
||||||
45 notebook_page_new (nb);
|
45 notebook_page_new (nb);
|
||||||
46 gtk_widget_show (GTK_WIDGET (win));
|
46 gtk_widget_show (win);
|
||||||
47 }
|
47 }
|
||||||
48
|
48
|
||||||
49 static void
|
49 static void
|
||||||
50 tfe_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) {
|
50 tfe_startup (GApplication *application) {
|
||||||
51 GtkApplication *app = GTK_APPLICATION (application);
|
51 GtkApplication *app = GTK_APPLICATION (application);
|
||||||
52 GtkWidget *win;
|
52 GtkBuilder *build;
|
||||||
53 GtkWidget *boxv;
|
53 GtkApplicationWindow *win;
|
||||||
54 GtkNotebook *nb;
|
54 GtkNotebook *nb;
|
||||||
55 int i;
|
55 GtkButton *btno;
|
||||||
56
|
56 GtkButton *btnn;
|
||||||
57 win = GTK_WIDGET (gtk_application_get_active_window (app));
|
57 GtkButton *btns;
|
||||||
58 boxv = gtk_window_get_child (GTK_WINDOW (win));
|
58 GtkButton *btnc;
|
||||||
59 nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
|
59
|
||||||
60
|
60 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe/tfe.ui");
|
||||||
61 for (i = 0; i < n_files; i++)
|
61 win = GTK_APPLICATION_WINDOW (gtk_builder_get_object (build, "win"));
|
||||||
62 notebook_page_new_with_file (nb, files[i]);
|
62 nb = GTK_NOTEBOOK (gtk_builder_get_object (build, "nb"));
|
||||||
63 if (gtk_notebook_get_n_pages (nb) == 0)
|
63 gtk_window_set_application (GTK_WINDOW (win), app);
|
||||||
64 notebook_page_new (nb);
|
64 btno = GTK_BUTTON (gtk_builder_get_object (build, "btno"));
|
||||||
65 gtk_widget_show (win);
|
65 btnn = GTK_BUTTON (gtk_builder_get_object (build, "btnn"));
|
||||||
66 }
|
66 btns = GTK_BUTTON (gtk_builder_get_object (build, "btns"));
|
||||||
67
|
67 btnc = GTK_BUTTON (gtk_builder_get_object (build, "btnc"));
|
||||||
68
|
68 g_signal_connect_swapped (btno, "clicked", G_CALLBACK (open_cb), nb);
|
||||||
69 static void
|
69 g_signal_connect_swapped (btnn, "clicked", G_CALLBACK (new_cb), nb);
|
||||||
70 tfe_startup (GApplication *application) {
|
70 g_signal_connect_swapped (btns, "clicked", G_CALLBACK (save_cb), nb);
|
||||||
71 GtkApplication *app = GTK_APPLICATION (application);
|
71 g_signal_connect_swapped (btnc, "clicked", G_CALLBACK (close_cb), nb);
|
||||||
72 GtkApplicationWindow *win;
|
72 g_object_unref(build);
|
||||||
73 GtkNotebook *nb;
|
73
|
||||||
74 GtkBuilder *build;
|
74 GdkDisplay *display;
|
||||||
75 GtkButton *btno;
|
75
|
||||||
76 GtkButton *btnn;
|
76 display = gtk_widget_get_display (GTK_WIDGET (win));
|
||||||
77 GtkButton *btns;
|
77 GtkCssProvider *provider = gtk_css_provider_new ();
|
||||||
78 GtkButton *btnc;
|
78 gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1);
|
||||||
79
|
79 gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
|
||||||
80 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe/tfe.ui");
|
80 }
|
||||||
81 win = GTK_APPLICATION_WINDOW (gtk_builder_get_object (build, "win"));
|
81
|
||||||
82 nb = GTK_NOTEBOOK (gtk_builder_get_object (build, "nb"));
|
82 int
|
||||||
83 gtk_window_set_application (GTK_WINDOW (win), app);
|
83 main (int argc, char **argv) {
|
||||||
84 btno = GTK_BUTTON (gtk_builder_get_object (build, "btno"));
|
84 GtkApplication *app;
|
||||||
85 btnn = GTK_BUTTON (gtk_builder_get_object (build, "btnn"));
|
85 int stat;
|
||||||
86 btns = GTK_BUTTON (gtk_builder_get_object (build, "btns"));
|
86
|
||||||
87 btnc = GTK_BUTTON (gtk_builder_get_object (build, "btnc"));
|
87 app = gtk_application_new ("com.github.ToshioCP.tfe", G_APPLICATION_HANDLES_OPEN);
|
||||||
88 g_signal_connect (btno, "clicked", G_CALLBACK (open_clicked), nb);
|
88
|
||||||
89 g_signal_connect (btnn, "clicked", G_CALLBACK (new_clicked), nb);
|
89 g_signal_connect (app, "startup", G_CALLBACK (tfe_startup), NULL);
|
||||||
90 g_signal_connect (btns, "clicked", G_CALLBACK (save_clicked), nb);
|
90 g_signal_connect (app, "activate", G_CALLBACK (tfe_activate), NULL);
|
||||||
91 g_signal_connect (btnc, "clicked", G_CALLBACK (close_clicked), nb);
|
91 g_signal_connect (app, "open", G_CALLBACK (tfe_open), NULL);
|
||||||
92 g_object_unref(build);
|
92
|
||||||
93
|
93 stat =g_application_run (G_APPLICATION (app), argc, argv);
|
||||||
94 GdkDisplay *display;
|
94 g_object_unref (app);
|
||||||
95
|
95 return stat;
|
||||||
96 display = gtk_widget_get_display (GTK_WIDGET (win));
|
96 }
|
||||||
97 GtkCssProvider *provider = gtk_css_provider_new ();
|
97
|
||||||
98 gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1);
|
|
||||||
99 gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
|
|
||||||
100 }
|
|
||||||
101
|
|
||||||
102 int
|
|
||||||
103 main (int argc, char **argv) {
|
|
||||||
104 GtkApplication *app;
|
|
||||||
105 int stat;
|
|
||||||
106
|
|
||||||
107 app = gtk_application_new ("com.github.ToshioCP.tfe", G_APPLICATION_HANDLES_OPEN);
|
|
||||||
108
|
|
||||||
109 g_signal_connect (app, "startup", G_CALLBACK (tfe_startup), NULL);
|
|
||||||
110 g_signal_connect (app, "activate", G_CALLBACK (tfe_activate), NULL);
|
|
||||||
111 g_signal_connect (app, "open", G_CALLBACK (tfe_open), NULL);
|
|
||||||
112
|
|
||||||
113 stat =g_application_run (G_APPLICATION (app), argc, argv);
|
|
||||||
114 g_object_unref (app);
|
|
||||||
115 return stat;
|
|
||||||
116 }
|
|
||||||
117
|
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
## tfenotebook.h
|
## tfenotebook.h
|
||||||
|
@ -264,14 +241,17 @@ It is a good practice for you to add more features.
|
||||||
2 notebook_page_save(GtkNotebook *nb);
|
2 notebook_page_save(GtkNotebook *nb);
|
||||||
3
|
3
|
||||||
4 void
|
4 void
|
||||||
5 notebook_page_open (GtkNotebook *nb);
|
5 notebook_page_close (GtkNotebook *nb);
|
||||||
6
|
6
|
||||||
7 void
|
7 void
|
||||||
8 notebook_page_new_with_file (GtkNotebook *nb, GFile *file);
|
8 notebook_page_open (GtkNotebook *nb);
|
||||||
9
|
9
|
||||||
10 void
|
10 void
|
||||||
11 notebook_page_new (GtkNotebook *nb);
|
11 notebook_page_new_with_file (GtkNotebook *nb, GFile *file);
|
||||||
12
|
12
|
||||||
|
13 void
|
||||||
|
14 notebook_page_new (GtkNotebook *nb);
|
||||||
|
15
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
## tfenotebook.c
|
## tfenotebook.c
|
||||||
|
@ -289,111 +269,134 @@ It is a good practice for you to add more features.
|
||||||
10 return g_strdup_printf ("Untitled%u", c);
|
10 return g_strdup_printf ("Untitled%u", c);
|
||||||
11 }
|
11 }
|
||||||
12
|
12
|
||||||
13 static void
|
13 static TfeTextView *
|
||||||
14 file_changed (TfeTextView *tv, GtkNotebook *nb) {
|
14 get_current_textview (GtkNotebook *nb) {
|
||||||
15 GFile *file;
|
15 int i;
|
||||||
16 char *filename;
|
16 GtkWidget *scr;
|
||||||
17 GtkWidget *scr;
|
17 GtkWidget *tv;
|
||||||
18 GtkWidget *label;
|
18
|
||||||
19
|
19 i = gtk_notebook_get_current_page (nb);
|
||||||
20 file = tfe_text_view_get_file (tv);
|
20 scr = gtk_notebook_get_nth_page (nb, i);
|
||||||
21 scr = gtk_widget_get_parent (GTK_WIDGET (tv));
|
21 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
|
||||||
22 if (G_IS_FILE (file))
|
22 return TFE_TEXT_VIEW (tv);
|
||||||
23 filename = g_file_get_basename (file);
|
23 }
|
||||||
24 else
|
24
|
||||||
25 filename = get_untitled ();
|
25 static void
|
||||||
26 label = gtk_label_new (filename);
|
26 file_changed_cb (TfeTextView *tv, GtkNotebook *nb) {
|
||||||
27 gtk_notebook_set_tab_label (nb, scr, label);
|
27 GtkWidget *scr;
|
||||||
28 g_object_unref (file);
|
28 GtkWidget *label;
|
||||||
29 g_free (filename);
|
29 GFile *file;
|
||||||
30 }
|
30 char *filename;
|
||||||
31
|
31
|
||||||
32 /* Save the contents in the current page */
|
32 file = tfe_text_view_get_file (tv);
|
||||||
33 void
|
33 scr = gtk_widget_get_parent (GTK_WIDGET (tv));
|
||||||
34 notebook_page_save(GtkNotebook *nb) {
|
34 if (G_IS_FILE (file)) {
|
||||||
35 gint i;
|
35 filename = g_file_get_basename (file);
|
||||||
36 GtkWidget *scr;
|
36 g_object_unref (file);
|
||||||
37 GtkWidget *tv;
|
37 } else
|
||||||
38
|
38 filename = get_untitled ();
|
||||||
39 i = gtk_notebook_get_current_page (nb);
|
39 label = gtk_label_new (filename);
|
||||||
40 scr = gtk_notebook_get_nth_page (nb, i);
|
40 gtk_notebook_set_tab_label (nb, scr, label);
|
||||||
41 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
|
41 }
|
||||||
42 tfe_text_view_save (TFE_TEXT_VIEW (tv));
|
42
|
||||||
43 }
|
43 void
|
||||||
44
|
44 notebook_page_save (GtkNotebook *nb) {
|
||||||
45 static void
|
45 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
|
||||||
46 notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) {
|
46
|
||||||
47 GtkWidget *scr;
|
47 TfeTextView *tv;
|
||||||
48 GtkNotebookPage *nbp;
|
48
|
||||||
49 GtkWidget *lab;
|
49 tv = get_current_textview (nb);
|
||||||
50 gint i;
|
50 tfe_text_view_save (TFE_TEXT_VIEW (tv));
|
||||||
51 scr = gtk_scrolled_window_new ();
|
51 }
|
||||||
52
|
52
|
||||||
53 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
|
53 void
|
||||||
54 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
|
54 notebook_page_close (GtkNotebook *nb) {
|
||||||
55 lab = gtk_label_new (filename);
|
55 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
|
||||||
56 i = gtk_notebook_append_page (nb, scr, lab);
|
56
|
||||||
57 nbp = gtk_notebook_get_page (nb, scr);
|
57 GtkWidget *win;
|
||||||
58 g_object_set (nbp, "tab-expand", TRUE, NULL);
|
58 int i;
|
||||||
59 gtk_notebook_set_current_page (nb, i);
|
59
|
||||||
60 g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed), nb);
|
60 if (gtk_notebook_get_n_pages (nb) == 1) {
|
||||||
61 }
|
61 win = gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW);
|
||||||
62
|
62 gtk_window_destroy(GTK_WINDOW (win));
|
||||||
63 static void
|
63 } else {
|
||||||
64 open_response (TfeTextView *tv, gint response, GtkNotebook *nb) {
|
64 i = gtk_notebook_get_current_page (nb);
|
||||||
65 GFile *file;
|
65 gtk_notebook_remove_page (GTK_NOTEBOOK (nb), i);
|
||||||
66 char *filename;
|
66 }
|
||||||
67
|
67 }
|
||||||
68 if (response != TFE_OPEN_RESPONSE_SUCCESS) {
|
68
|
||||||
69 g_object_ref_sink (tv);
|
69 static void
|
||||||
70 g_object_unref (tv);
|
70 notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) {
|
||||||
71 }else if (! G_IS_FILE (file = tfe_text_view_get_file (tv))) {
|
71 GtkWidget *scr = gtk_scrolled_window_new ();
|
||||||
72 g_object_ref_sink (tv);
|
72 GtkNotebookPage *nbp;
|
||||||
73 g_object_unref (tv);
|
73 GtkWidget *lab;
|
||||||
74 }else {
|
74 int i;
|
||||||
75 filename = g_file_get_basename (file);
|
75
|
||||||
76 g_object_unref (file);
|
76 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
|
||||||
77 notebook_page_build (nb, GTK_WIDGET (tv), filename);
|
77 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
|
||||||
78 }
|
78 lab = gtk_label_new (filename);
|
||||||
79 }
|
79 i = gtk_notebook_append_page (nb, scr, lab);
|
||||||
80
|
80 nbp = gtk_notebook_get_page (nb, scr);
|
||||||
81 void
|
81 g_object_set (nbp, "tab-expand", TRUE, NULL);
|
||||||
82 notebook_page_open (GtkNotebook *nb) {
|
82 gtk_notebook_set_current_page (nb, i);
|
||||||
83 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
|
83 g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed_cb), nb);
|
||||||
84
|
84 }
|
||||||
85 GtkWidget *tv;
|
85
|
||||||
86
|
86 static void
|
||||||
87 tv = tfe_text_view_new ();
|
87 open_response (TfeTextView *tv, int response, GtkNotebook *nb) {
|
||||||
88 g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
|
88 GFile *file;
|
||||||
89 tfe_text_view_open (TFE_TEXT_VIEW (tv), gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW));
|
89 char *filename;
|
||||||
90 }
|
90
|
||||||
91
|
91 if (response != TFE_OPEN_RESPONSE_SUCCESS) {
|
||||||
92 void
|
92 g_object_ref_sink (tv);
|
||||||
93 notebook_page_new_with_file (GtkNotebook *nb, GFile *file) {
|
93 g_object_unref (tv);
|
||||||
94 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
|
94 }else if (! G_IS_FILE (file = tfe_text_view_get_file (tv))) {
|
||||||
95 g_return_if_fail(G_IS_FILE (file));
|
95 g_object_ref_sink (tv);
|
||||||
96
|
96 g_object_unref (tv);
|
||||||
97 GtkWidget *tv;
|
97 }else {
|
||||||
98 char *filename;
|
98 filename = g_file_get_basename (file);
|
||||||
99
|
99 g_object_unref (file);
|
||||||
100 if ((tv = tfe_text_view_new_with_file (file)) == NULL)
|
100 notebook_page_build (nb, GTK_WIDGET (tv), filename);
|
||||||
101 return; /* read error */
|
101 }
|
||||||
102 filename = g_file_get_basename (file);
|
102 }
|
||||||
103 notebook_page_build (nb, tv, filename);
|
103
|
||||||
104 }
|
104 void
|
||||||
105
|
105 notebook_page_open (GtkNotebook *nb) {
|
||||||
106 void
|
106 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
|
||||||
107 notebook_page_new (GtkNotebook *nb) {
|
107
|
||||||
108 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
|
108 GtkWidget *tv;
|
||||||
109
|
109
|
||||||
110 GtkWidget *tv;
|
110 tv = tfe_text_view_new ();
|
||||||
111 char *filename;
|
111 g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
|
||||||
112
|
112 tfe_text_view_open (TFE_TEXT_VIEW (tv), gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW));
|
||||||
113 tv = tfe_text_view_new ();
|
113 }
|
||||||
114 filename = get_untitled ();
|
114
|
||||||
115 notebook_page_build (nb, tv, filename);
|
115 void
|
||||||
116 }
|
116 notebook_page_new_with_file (GtkNotebook *nb, GFile *file) {
|
||||||
117
|
117 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
|
||||||
|
118 g_return_if_fail(G_IS_FILE (file));
|
||||||
|
119
|
||||||
|
120 GtkWidget *tv;
|
||||||
|
121 char *filename;
|
||||||
|
122
|
||||||
|
123 if ((tv = tfe_text_view_new_with_file (file)) == NULL)
|
||||||
|
124 return; /* read error */
|
||||||
|
125 filename = g_file_get_basename (file);
|
||||||
|
126 notebook_page_build (nb, tv, filename);
|
||||||
|
127 }
|
||||||
|
128
|
||||||
|
129 void
|
||||||
|
130 notebook_page_new (GtkNotebook *nb) {
|
||||||
|
131 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
|
||||||
|
132
|
||||||
|
133 GtkWidget *tv;
|
||||||
|
134 char *filename;
|
||||||
|
135
|
||||||
|
136 tv = tfe_text_view_new ();
|
||||||
|
137 filename = get_untitled ();
|
||||||
|
138 notebook_page_build (nb, tv, filename);
|
||||||
|
139 }
|
||||||
|
140
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
## tfetextview.h
|
## tfetextview.h
|
||||||
|
@ -505,157 +508,170 @@ It is a good practice for you to add more features.
|
||||||
64 tfe_text_view_get_file (TfeTextView *tv) {
|
64 tfe_text_view_get_file (TfeTextView *tv) {
|
||||||
65 g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);
|
65 g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);
|
||||||
66
|
66
|
||||||
67 return g_file_dup (tv->file);
|
67 if (G_IS_FILE (tv->file))
|
||||||
68 }
|
68 return g_file_dup (tv->file);
|
||||||
69
|
69 else
|
||||||
70 static void
|
70 return NULL;
|
||||||
71 open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
|
71 }
|
||||||
72 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
72
|
||||||
73 GFile *file;
|
73 static gboolean
|
||||||
74 char *contents;
|
74 save_file (GFile *file, GtkTextBuffer *tb, GtkWindow *win) {
|
||||||
75 gsize length;
|
75 GtkTextIter start_iter;
|
||||||
76 GtkWidget *message_dialog;
|
76 GtkTextIter end_iter;
|
||||||
77 GError *err = NULL;
|
77 gchar *contents;
|
||||||
78
|
78 GtkWidget *message_dialog;
|
||||||
79 if (response != GTK_RESPONSE_ACCEPT)
|
79 GError *err = NULL;
|
||||||
80 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
|
80
|
||||||
81 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog))))
|
81 /* This function doesn't check G_IS_FILE (file). The caller should check it. */
|
||||||
82 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
|
82 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
|
||||||
83 else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
|
83 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
|
||||||
84 if (G_IS_FILE (file))
|
84 if (g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err)) {
|
||||||
85 g_object_unref (file);
|
85 gtk_text_buffer_set_modified (tb, FALSE);
|
||||||
86 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
|
86 return TRUE;
|
||||||
87 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
|
87 } else {
|
||||||
88 "%s.\n", err->message);
|
88 message_dialog = gtk_message_dialog_new (win, GTK_DIALOG_MODAL,
|
||||||
89 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
|
89 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
|
||||||
90 gtk_widget_show (message_dialog);
|
90 "%s.\n", err->message);
|
||||||
91 g_error_free (err);
|
91 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
|
||||||
92 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
|
92 gtk_widget_show (message_dialog);
|
||||||
93 } else {
|
93 g_error_free (err);
|
||||||
94 gtk_text_buffer_set_text (tb, contents, length);
|
94 return FALSE;
|
||||||
95 g_free (contents);
|
95 }
|
||||||
96 if (G_IS_FILE (tv->file))
|
96 }
|
||||||
97 g_object_unref (tv->file);
|
97
|
||||||
98 tv->file = file;
|
98 static void
|
||||||
99 gtk_text_buffer_set_modified (tb, FALSE);
|
99 saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
|
||||||
100 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
|
100 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
||||||
101 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
|
101 GFile *file;
|
||||||
102 }
|
102 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
|
||||||
103 gtk_window_destroy (GTK_WINDOW (dialog));
|
103
|
||||||
104 }
|
104 gtk_window_destroy (GTK_WINDOW (dialog));
|
||||||
105
|
105 if (response == GTK_RESPONSE_ACCEPT) {
|
||||||
106 void
|
106 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
|
||||||
107 tfe_text_view_open (TfeTextView *tv, GtkWidget *win) {
|
107 if (! G_IS_FILE (file))
|
||||||
108 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
|
108 g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n");
|
||||||
109 g_return_if_fail (GTK_IS_WINDOW (win));
|
109 else {
|
||||||
110
|
110 save_file(file, tb, GTK_WINDOW (win));
|
||||||
111 GtkWidget *dialog;
|
111 if (G_IS_FILE (tv->file))
|
||||||
112
|
112 g_object_unref (tv->file);
|
||||||
113 dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN,
|
113 tv->file = file;
|
||||||
114 "Cancel", GTK_RESPONSE_CANCEL,
|
114 gtk_text_buffer_set_modified (tb, FALSE);
|
||||||
115 "Open", GTK_RESPONSE_ACCEPT,
|
115 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
|
||||||
116 NULL);
|
116 }
|
||||||
117 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
|
117 }
|
||||||
118 gtk_widget_show (dialog);
|
118 }
|
||||||
119 }
|
119
|
||||||
120
|
120 void
|
||||||
121 static void
|
121 tfe_text_view_save (TfeTextView *tv) {
|
||||||
122 saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
|
122 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
|
||||||
123 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
123
|
||||||
124 GFile *file;
|
124 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
||||||
125
|
125 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
|
||||||
126 if (response == GTK_RESPONSE_ACCEPT) {
|
126
|
||||||
127 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
|
127 if (! gtk_text_buffer_get_modified (tb))
|
||||||
128 if (G_IS_FILE(file)) {
|
128 return; /* no need to save it */
|
||||||
129 if (G_IS_FILE (tv->file))
|
129 else if (tv->file == NULL)
|
||||||
130 g_object_unref (tv->file);
|
130 tfe_text_view_saveas (tv);
|
||||||
131 tv->file = file;
|
131 else if (! G_IS_FILE (tv->file))
|
||||||
132 gtk_text_buffer_set_modified (tb, TRUE);
|
132 g_error ("TfeTextView: The pointer in this object isn't NULL nor GFile object.\n");
|
||||||
133 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
|
133 else {
|
||||||
134 tfe_text_view_save (TFE_TEXT_VIEW (tv));
|
134 if (save_file (tv->file, tb, GTK_WINDOW (win)))
|
||||||
135 }
|
135 gtk_text_buffer_set_modified (tb, FALSE);
|
||||||
136 }
|
136 }
|
||||||
137 gtk_window_destroy (GTK_WINDOW (dialog));
|
137 }
|
||||||
138 }
|
138
|
||||||
139
|
139 void
|
||||||
140 void
|
140 tfe_text_view_saveas (TfeTextView *tv) {
|
||||||
141 tfe_text_view_save (TfeTextView *tv) {
|
141 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
|
||||||
142 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
|
142
|
||||||
143
|
143 GtkWidget *dialog;
|
||||||
144 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
144 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
|
||||||
145 GtkTextIter start_iter;
|
145
|
||||||
146 GtkTextIter end_iter;
|
146 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
|
||||||
147 gchar *contents;
|
147 "Cancel", GTK_RESPONSE_CANCEL,
|
||||||
148 GtkWidget *message_dialog;
|
148 "Save", GTK_RESPONSE_ACCEPT,
|
||||||
149 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
|
149 NULL);
|
||||||
150 GError *err = NULL;
|
150 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
|
||||||
151
|
151 gtk_widget_show (dialog);
|
||||||
152 if (! gtk_text_buffer_get_modified (tb))
|
152 }
|
||||||
153 return; /* no need to save it */
|
153
|
||||||
154 else if (tv->file == NULL)
|
154 GtkWidget *
|
||||||
155 tfe_text_view_saveas (tv);
|
155 tfe_text_view_new_with_file (GFile *file) {
|
||||||
156 else {
|
156 g_return_val_if_fail (G_IS_FILE (file), NULL);
|
||||||
157 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
|
157
|
||||||
158 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
|
158 GtkWidget *tv;
|
||||||
159 if (g_file_replace_contents (tv->file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err))
|
159 GtkTextBuffer *tb;
|
||||||
160 gtk_text_buffer_set_modified (tb, FALSE);
|
160 char *contents;
|
||||||
161 else {
|
161 gsize length;
|
||||||
162 /* It is possible that tv->file is broken or you don't have permission to write. */
|
162
|
||||||
163 /* It is a good idea to set tv->file to NULL. */
|
163 if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */
|
||||||
164 if (G_IS_FILE (tv->file))
|
164 return NULL;
|
||||||
165 g_object_unref (tv->file);
|
165
|
||||||
166 tv->file =NULL;
|
166 tv = tfe_text_view_new();
|
||||||
167 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
|
167 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
||||||
168 message_dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL,
|
168 gtk_text_buffer_set_text (tb, contents, length);
|
||||||
169 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
|
169 g_free (contents);
|
||||||
170 "%s.\n", err->message);
|
170 TFE_TEXT_VIEW (tv)->file = g_file_dup (file);
|
||||||
171 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
|
171 return tv;
|
||||||
172 gtk_widget_show (message_dialog);
|
172 }
|
||||||
173 g_error_free (err);
|
173
|
||||||
174 }
|
174 static void
|
||||||
175 }
|
175 open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
|
||||||
176 }
|
176 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
||||||
177
|
177 GFile *file;
|
||||||
178 void
|
178 char *contents;
|
||||||
179 tfe_text_view_saveas (TfeTextView *tv) {
|
179 gsize length;
|
||||||
180 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
|
180 GtkWidget *message_dialog;
|
||||||
181
|
181 GError *err = NULL;
|
||||||
182 GtkWidget *dialog;
|
182
|
||||||
183 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
|
183 if (response != GTK_RESPONSE_ACCEPT)
|
||||||
184
|
184 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
|
||||||
185 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
|
185 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)))) {
|
||||||
186 "Cancel", GTK_RESPONSE_CANCEL,
|
186 g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n");
|
||||||
187 "Save", GTK_RESPONSE_ACCEPT,
|
187 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
|
||||||
188 NULL);
|
188 } else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
|
||||||
189 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
|
189 if (G_IS_FILE (file))
|
||||||
190 gtk_widget_show (dialog);
|
190 g_object_unref (file);
|
||||||
191 }
|
191 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
|
||||||
192
|
192 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
|
||||||
193 GtkWidget *
|
193 "%s.\n", err->message);
|
||||||
194 tfe_text_view_new_with_file (GFile *file) {
|
194 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
|
||||||
195 g_return_val_if_fail (G_IS_FILE (file), NULL);
|
195 gtk_widget_show (message_dialog);
|
||||||
196
|
196 g_error_free (err);
|
||||||
197 GtkWidget *tv;
|
197 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
|
||||||
198 GtkTextBuffer *tb;
|
198 } else {
|
||||||
199 char *contents;
|
199 gtk_text_buffer_set_text (tb, contents, length);
|
||||||
200 gsize length;
|
200 g_free (contents);
|
||||||
201
|
201 if (G_IS_FILE (tv->file))
|
||||||
202 if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */
|
202 g_object_unref (tv->file);
|
||||||
203 return NULL;
|
203 tv->file = file;
|
||||||
204
|
204 gtk_text_buffer_set_modified (tb, FALSE);
|
||||||
205 tv = tfe_text_view_new();
|
205 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
|
||||||
206 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
206 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
|
||||||
207 gtk_text_buffer_set_text (tb, contents, length);
|
207 }
|
||||||
208 g_free (contents);
|
208 gtk_window_destroy (GTK_WINDOW (dialog));
|
||||||
209 TFE_TEXT_VIEW (tv)->file = g_file_dup (file);
|
209 }
|
||||||
210 return tv;
|
210
|
||||||
211 }
|
211 void
|
||||||
212
|
212 tfe_text_view_open (TfeTextView *tv, GtkWidget *win) {
|
||||||
213 GtkWidget *
|
213 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
|
||||||
214 tfe_text_view_new (void) {
|
214 g_return_if_fail (GTK_IS_WINDOW (win));
|
||||||
215 return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
|
215
|
||||||
216 }
|
216 GtkWidget *dialog;
|
||||||
217
|
217
|
||||||
|
218 dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||||
|
219 "Cancel", GTK_RESPONSE_CANCEL,
|
||||||
|
220 "Open", GTK_RESPONSE_ACCEPT,
|
||||||
|
221 NULL);
|
||||||
|
222 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
|
||||||
|
223 gtk_widget_show (dialog);
|
||||||
|
224 }
|
||||||
|
225
|
||||||
|
226 GtkWidget *
|
||||||
|
227 tfe_text_view_new (void) {
|
||||||
|
228 return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
|
||||||
|
229 }
|
||||||
|
230
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
## Total number of lines, words and characters
|
## Total number of lines, words and characters
|
||||||
|
@ -663,15 +679,15 @@ It is a good practice for you to add more features.
|
||||||
~~~
|
~~~
|
||||||
$ LANG=C wc tfe5/meson.build tfe5/tfeapplication.c tfe5/tfe.gresource.xml tfe5/tfe.h tfe5/tfenotebook.c tfe5/tfenotebook.h tfetextview/tfetextview.c tfetextview/tfetextview.h tfe5/tfe.ui
|
$ LANG=C wc tfe5/meson.build tfe5/tfeapplication.c tfe5/tfe.gresource.xml tfe5/tfe.h tfe5/tfenotebook.c tfe5/tfenotebook.h tfetextview/tfetextview.c tfetextview/tfetextview.h tfe5/tfe.ui
|
||||||
10 17 294 tfe5/meson.build
|
10 17 294 tfe5/meson.build
|
||||||
117 348 3576 tfe5/tfeapplication.c
|
97 301 3159 tfe5/tfeapplication.c
|
||||||
6 9 153 tfe5/tfe.gresource.xml
|
6 9 153 tfe5/tfe.gresource.xml
|
||||||
4 6 87 tfe5/tfe.h
|
4 6 87 tfe5/tfe.h
|
||||||
117 325 3064 tfe5/tfenotebook.c
|
140 373 3580 tfe5/tfenotebook.c
|
||||||
12 17 196 tfe5/tfenotebook.h
|
15 21 241 tfe5/tfenotebook.h
|
||||||
217 637 7725 tfetextview/tfetextview.c
|
230 686 8144 tfetextview/tfetextview.c
|
||||||
35 60 701 tfetextview/tfetextview.h
|
35 60 701 tfetextview/tfetextview.h
|
||||||
64 105 2266 tfe5/tfe.ui
|
61 100 2073 tfe5/tfe.ui
|
||||||
582 1524 18062 total
|
598 1573 18432 total
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ Some menu items have a link to another GMenu.
|
||||||
There are two types of links, submenu and section.
|
There are two types of links, submenu and section.
|
||||||
|
|
||||||
GMenuItem can be inserted, appended or prepended to GMenu.
|
GMenuItem can be inserted, appended or prepended to GMenu.
|
||||||
When it is inserted, all of the attribute and link values of the item are copied and used to form a new item within the menu.
|
When it is inserted, all of the attributes and link values of the item are copied and used to form a new item within the menu.
|
||||||
The GMenuItem itself is not really inserted.
|
The GMenuItem itself is not really inserted.
|
||||||
Therefore, after the insertion, GMenuItem is useless and it should be freed.
|
Therefore, after the insertion, GMenuItem is useless and it should be freed.
|
||||||
The same goes for appending or prepending.
|
The same goes for appending or prepending.
|
||||||
|
|
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.
|
Include this file by . (dot) command before using gtk4 libraries.
|
||||||
|
|
||||||
You may think you can add them in your `.profile`.
|
You may think you can add them in your `.profile`.
|
||||||
I think the environment variables above are necessary only when you compile gtk4 applications.
|
But it's a wrong decision.
|
||||||
And it's not necessary except the case above and it might cause some bad things.
|
Never write them to your `.profile`.
|
||||||
Therefore, I recommend you not to write them to your `.profile`.
|
The environment variables above are necessary only when you compile and run gtk4 applications.
|
||||||
|
Otherwise it's not necessary.
|
||||||
|
If you changed the environment variables above and run gtk3 applications, it probably causes serious damage.
|
||||||
|
|
||||||
## Compiling gtk4 applications
|
## Compiling gtk4 applications
|
||||||
|
|
||||||
|
|
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.
|
If you want to draw dynamically, like an image window of gimp graphics editor, GtkDrawingArea widget is the most suitable widget.
|
||||||
Its name is "color".
|
You can draw or redraw an image in this widget freely.
|
||||||
If you write a color in TfeTextView and click on the `run` button, then the color of GtkDrawingArea changes to the color given by you.
|
It is called custom drawing.
|
||||||
|
|
||||||
![color](../image/color.png)
|
GtkDrawingArea provides a cairo context so users can draw images by cairo functions.
|
||||||
|
In this section, I will explain:
|
||||||
|
|
||||||
The following colors are available.
|
1. Cairo, but briefly.
|
||||||
|
2. GtkDrawingArea with very simple example.
|
||||||
|
|
||||||
- white
|
## Cairo
|
||||||
- black
|
|
||||||
- red
|
|
||||||
- green
|
|
||||||
- blue
|
|
||||||
|
|
||||||
In addition the following two options are also available.
|
Cairo is a two dimensional graphics library.
|
||||||
|
First, you need to know surface, source, mask, destination, cairo context and transformation.
|
||||||
|
|
||||||
- light: Make the color of the drawing area lighter.
|
- Surface represents an image.
|
||||||
- dark: Make the color of the drawing area darker.
|
It is like a canvas.
|
||||||
|
We can draw shapes and images with different colors on surfaces.
|
||||||
|
- Source pattern, or simply source, is a kind of paint, which will be transferred to destination surface by cairo functions.
|
||||||
|
- Mask is image mask used in the transference.
|
||||||
|
- Destination is a target surface.
|
||||||
|
- Cairo context manages the transference from source to destination through mask with its functions.
|
||||||
|
For example, `cairo_stroke` is a function to draw a path to the destination by the transference.
|
||||||
|
- Transformation is applied before the transfer completes.
|
||||||
|
The transformation is called affine, which is a mathematics terminology, and represented by matrix multiplication and vector addition.
|
||||||
|
Scaling, rotation, reflection, shearing and translation are examples of affine transformation.
|
||||||
|
In this section, we don't use it.
|
||||||
|
That means we only use identity transformation.
|
||||||
|
Therefore, the coordinate in source and mask is the same as the coordinate in destination.
|
||||||
|
|
||||||
This application can only do very simple things.
|
![Stroke a rectangle](../image/cairo.png)
|
||||||
However, it tells us that if we add powerful parser to it, we will be able to make it more efficient.
|
|
||||||
I want to show it to you in the later section by making a turtle graphics language like Logo program language.
|
|
||||||
|
|
||||||
In this section, we focus on how to bind the two objects.
|
The instruction is as follows:
|
||||||
|
|
||||||
## Color.ui and color.gresource.xml
|
1. Create a surface.
|
||||||
|
This will be a destination.
|
||||||
|
2. Create a cairo context with the surface and the surface will be the destination of the context.
|
||||||
|
3. Create a source pattern within the context.
|
||||||
|
4. Create paths, which are lines, rectangles, arcs, texts or more complicated shapes, to generate a mask.
|
||||||
|
5. Use drawing operator such as `cairo_stroke` to transfer the paint in the source to the destination.
|
||||||
|
6. Save the destination surface to a file if necessary.
|
||||||
|
|
||||||
First, We need to make the ui file of the widgets.
|
Here's a simple example code that draws a small square and save it as a png file.
|
||||||
The image in the previous subsection gives us the structure of the widgets.
|
|
||||||
Title bar, four buttons in the tool bar and two widgets textview and drawing area.
|
|
||||||
The ui file is as follows.
|
|
||||||
|
|
||||||
~~~xml
|
|
||||||
1 <interface>
|
|
||||||
2 <object class="GtkApplicationWindow" id="win">
|
|
||||||
3 <property name="title">color changer</property>
|
|
||||||
4 <property name="default-width">600</property>
|
|
||||||
5 <property name="default-height">400</property>
|
|
||||||
6 <child>
|
|
||||||
7 <object class="GtkBox" id="boxv">
|
|
||||||
8 <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
|
||||||
9 <child>
|
|
||||||
10 <object class="GtkBox" id="boxh1">
|
|
||||||
11 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
|
||||||
12 <child>
|
|
||||||
13 <object class="GtkLabel" id="dmy1">
|
|
||||||
14 <property name="width-chars">10</property>
|
|
||||||
15 </object>
|
|
||||||
16 </child>
|
|
||||||
17 <child>
|
|
||||||
18 <object class="GtkButton" id="btnr">
|
|
||||||
19 <property name="label">Run</property>
|
|
||||||
20 <signal name="clicked" handler="run_cb"></signal>
|
|
||||||
21
|
|
||||||
22 </object>
|
|
||||||
23 </child>
|
|
||||||
24 <child>
|
|
||||||
25 <object class="GtkButton" id="btno">
|
|
||||||
26 <property name="label">Open</property>
|
|
||||||
27 <signal name="clicked" handler="open_cb"></signal>
|
|
||||||
28 </object>
|
|
||||||
29 </child>
|
|
||||||
30 <child>
|
|
||||||
31 <object class="GtkLabel" id="dmy2">
|
|
||||||
32 <property name="hexpand">TRUE</property>
|
|
||||||
33 </object>
|
|
||||||
34 </child>
|
|
||||||
35 <child>
|
|
||||||
36 <object class="GtkButton" id="btns">
|
|
||||||
37 <property name="label">Save</property>
|
|
||||||
38 <signal name="clicked" handler="save_cb"></signal>
|
|
||||||
39 </object>
|
|
||||||
40 </child>
|
|
||||||
41 <child>
|
|
||||||
42 <object class="GtkButton" id="btnc">
|
|
||||||
43 <property name="label">Close</property>
|
|
||||||
44 <signal name="clicked" handler="close_cb"></signal>
|
|
||||||
45 </object>
|
|
||||||
46 </child>
|
|
||||||
47 <child>
|
|
||||||
48 <object class="GtkLabel" id="dmy3">
|
|
||||||
49 <property name="width-chars">10</property>
|
|
||||||
50 </object>
|
|
||||||
51 </child>
|
|
||||||
52 </object>
|
|
||||||
53 </child>
|
|
||||||
54 <child>
|
|
||||||
55 <object class="GtkBox" id="boxh2">
|
|
||||||
56 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
|
||||||
57 <property name="homogeneous">TRUE</property>
|
|
||||||
58 <child>
|
|
||||||
59 <object class="GtkScrolledWindow" id="scr">
|
|
||||||
60 <property name="hexpand">TRUE</property>
|
|
||||||
61 <property name="vexpand">TRUE</property>
|
|
||||||
62 <child>
|
|
||||||
63 <object class="TfeTextView" id="tv">
|
|
||||||
64 <property name="wrap-mode">GTK_WRAP_WORD_CHAR</property>
|
|
||||||
65 </object>
|
|
||||||
66 </child>
|
|
||||||
67 </object>
|
|
||||||
68 </child>
|
|
||||||
69 <child>
|
|
||||||
70 <object class="GtkDrawingArea" id="da">
|
|
||||||
71 <property name="hexpand">TRUE</property>
|
|
||||||
72 <property name="vexpand">TRUE</property>
|
|
||||||
73 </object>
|
|
||||||
74 </child>
|
|
||||||
75 </object>
|
|
||||||
76 </child>
|
|
||||||
77 </object>
|
|
||||||
78 </child>
|
|
||||||
79 </object>
|
|
||||||
80 </interface>
|
|
||||||
81
|
|
||||||
~~~
|
|
||||||
|
|
||||||
- 9-53: This part describes the tool bar which has four buttons, `Run`, `Open`, `Save` and `Close`.
|
|
||||||
This is similar to the toolbar of tfe text editor in [Section 8](sec8.md).
|
|
||||||
There are two differences.
|
|
||||||
`Run` button replaces `New` button.
|
|
||||||
Signal element are added to each button object.
|
|
||||||
It has "name" attribute which is a signal name and "handler" attribute which is the name of its signal handler function.
|
|
||||||
Options "-WI, --export-dynamic" CFLAG is necessary when you compile the application.
|
|
||||||
You can achieve this by adding "export_dynamic: true" argument to executable function in `meson.build`.
|
|
||||||
And be careful that the handler must be defined without 'static' class.
|
|
||||||
- 54-76: Puts GtkScrolledWindow and GtkDrawingArea into GtkBox.
|
|
||||||
GtkBox has "homogeneous property with TRUE value, so the two children have the same width in the box.
|
|
||||||
TfeTextView is a child of GtkScrolledWindow.
|
|
||||||
|
|
||||||
The xml file for the resource compiler is almost same as before.
|
|
||||||
Just substitute "color" for "tfe".
|
|
||||||
|
|
||||||
~~~xml
|
|
||||||
1 <?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
2 <gresources>
|
|
||||||
3 <gresource prefix="/com/github/ToshioCP/color">
|
|
||||||
4 <file>color.ui</file>
|
|
||||||
5 </gresource>
|
|
||||||
6 </gresources>
|
|
||||||
~~~
|
|
||||||
|
|
||||||
## Tfetextview.h, tfetextview.c and color.h
|
|
||||||
|
|
||||||
First two files are the same as before.
|
|
||||||
Color.h just includes tfetextview.h.
|
|
||||||
|
|
||||||
~~~C
|
~~~C
|
||||||
1 #include <gtk/gtk.h>
|
1 #include <cairo.h>
|
||||||
2
|
|
||||||
3 #include "../tfetextview/tfetextview.h"
|
|
||||||
~~~
|
|
||||||
|
|
||||||
## Colorapplication.c
|
|
||||||
|
|
||||||
This is the main file.
|
|
||||||
It deals with:
|
|
||||||
|
|
||||||
- Building widgets by GtkBuilder.
|
|
||||||
- Seting a drawing function of GtkDrawingArea.
|
|
||||||
And connecting a handler to "resize" signal on GtkDrawingArea.
|
|
||||||
- Implementing each call back functions.
|
|
||||||
Particularly, `Run` signal handler is the point in this program.
|
|
||||||
|
|
||||||
The following is `colorapplication.c`.
|
|
||||||
|
|
||||||
~~~C
|
|
||||||
1 #include "color.h"
|
|
||||||
2
|
|
||||||
3 static GtkWidget *win;
|
|
||||||
4 static GtkWidget *tv;
|
|
||||||
5 static GtkWidget *da;
|
|
||||||
6
|
|
||||||
7 static cairo_surface_t *surface = NULL;
|
|
||||||
8
|
|
||||||
9 static void
|
|
||||||
10 run (void) {
|
|
||||||
11 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
|
||||||
12 GtkTextIter start_iter;
|
|
||||||
13 GtkTextIter end_iter;
|
|
||||||
14 char *contents;
|
|
||||||
15 cairo_t *cr;
|
|
||||||
16
|
|
||||||
17 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
|
|
||||||
18 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
|
|
||||||
19 if (surface) {
|
|
||||||
20 cr = cairo_create (surface);
|
|
||||||
21 if (g_strcmp0 ("red", contents) == 0)
|
|
||||||
22 cairo_set_source_rgb (cr, 1, 0, 0);
|
|
||||||
23 else if (g_strcmp0 ("green", contents) == 0)
|
|
||||||
24 cairo_set_source_rgb (cr, 0, 1, 0);
|
|
||||||
25 else if (g_strcmp0 ("blue", contents) == 0)
|
|
||||||
26 cairo_set_source_rgb (cr, 0, 0, 1);
|
|
||||||
27 else if (g_strcmp0 ("white", contents) == 0)
|
|
||||||
28 cairo_set_source_rgb (cr, 1, 1, 1);
|
|
||||||
29 else if (g_strcmp0 ("black", contents) == 0)
|
|
||||||
30 cairo_set_source_rgb (cr, 0, 0, 0);
|
|
||||||
31 else if (g_strcmp0 ("light", contents) == 0)
|
|
||||||
32 cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
|
|
||||||
33 else if (g_strcmp0 ("dark", contents) == 0)
|
|
||||||
34 cairo_set_source_rgba (cr, 0, 0, 0, 0.5);
|
|
||||||
35 else
|
|
||||||
36 cairo_set_source_surface (cr, surface, 0, 0);
|
|
||||||
37 cairo_paint (cr);
|
|
||||||
38 cairo_destroy (cr);
|
|
||||||
39 }
|
|
||||||
40 }
|
|
||||||
41
|
|
||||||
42 void
|
|
||||||
43 run_cb (GtkWidget *btnr) {
|
|
||||||
44 run ();
|
|
||||||
45 gtk_widget_queue_draw (GTK_WIDGET (da));
|
|
||||||
46 }
|
|
||||||
47
|
|
||||||
48 void
|
|
||||||
49 open_cb (GtkWidget *btno) {
|
|
||||||
50 tfe_text_view_open (TFE_TEXT_VIEW (tv), win);
|
|
||||||
51 }
|
|
||||||
52
|
|
||||||
53 void
|
|
||||||
54 save_cb (GtkWidget *btns) {
|
|
||||||
55 tfe_text_view_save (TFE_TEXT_VIEW (tv));
|
|
||||||
56 }
|
|
||||||
57
|
|
||||||
58 void
|
|
||||||
59 close_cb (GtkWidget *btnc) {
|
|
||||||
60 if (surface)
|
|
||||||
61 cairo_surface_destroy (surface);
|
|
||||||
62 gtk_window_destroy (GTK_WINDOW (win));
|
|
||||||
63 }
|
|
||||||
64
|
|
||||||
65 static void
|
|
||||||
66 resize_cb (GtkDrawingArea *drawing_area, int width, int height, gpointer user_data) {
|
|
||||||
67 if (surface)
|
|
||||||
68 cairo_surface_destroy (surface);
|
|
||||||
69 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
|
|
||||||
70 run ();
|
|
||||||
71 }
|
|
||||||
72
|
|
||||||
73 static void
|
|
||||||
74 draw_func (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height, gpointer user_data) {
|
|
||||||
75 if (surface) {
|
|
||||||
76 cairo_set_source_surface (cr, surface, 0, 0);
|
|
||||||
77 cairo_paint (cr);
|
|
||||||
78 }
|
|
||||||
79 }
|
|
||||||
80
|
|
||||||
81 static void
|
|
||||||
82 activate (GApplication *application) {
|
|
||||||
83 gtk_widget_show (win);
|
|
||||||
84 }
|
|
||||||
85
|
|
||||||
86 static void
|
|
||||||
87 startup (GApplication *application) {
|
|
||||||
88 GtkApplication *app = GTK_APPLICATION (application);
|
|
||||||
89 GtkBuilder *build;
|
|
||||||
90
|
|
||||||
91 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/color/color.ui");
|
|
||||||
92 win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
|
|
||||||
93 gtk_window_set_application (GTK_WINDOW (win), app);
|
|
||||||
94 tv = GTK_WIDGET (gtk_builder_get_object (build, "tv"));
|
|
||||||
95 da = GTK_WIDGET (gtk_builder_get_object (build, "da"));
|
|
||||||
96 g_object_unref(build);
|
|
||||||
97 g_signal_connect (GTK_DRAWING_AREA (da), "resize", G_CALLBACK (resize_cb), NULL);
|
|
||||||
98 gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (da), draw_func, NULL, NULL);
|
|
||||||
99
|
|
||||||
100 GdkDisplay *display;
|
|
||||||
101
|
|
||||||
102 display = gtk_widget_get_display (GTK_WIDGET (win));
|
|
||||||
103 GtkCssProvider *provider = gtk_css_provider_new ();
|
|
||||||
104 gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1);
|
|
||||||
105 gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
|
|
||||||
106 }
|
|
||||||
107
|
|
||||||
108 int
|
|
||||||
109 main (int argc, char **argv) {
|
|
||||||
110 GtkApplication *app;
|
|
||||||
111 int stat;
|
|
||||||
112
|
|
||||||
113 app = gtk_application_new ("com.github.ToshioCP.color", G_APPLICATION_FLAGS_NONE);
|
|
||||||
114
|
|
||||||
115 g_signal_connect (app, "startup", G_CALLBACK (startup), NULL);
|
|
||||||
116 g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
|
|
||||||
117
|
|
||||||
118 stat =g_application_run (G_APPLICATION (app), argc, argv);
|
|
||||||
119 g_object_unref (app);
|
|
||||||
120 return stat;
|
|
||||||
121 }
|
|
||||||
122
|
|
||||||
~~~
|
|
||||||
|
|
||||||
- 108-121: The function `main` is almost same as before but there are some differences.
|
|
||||||
The application ID is "com.github.ToshioCP.color".
|
|
||||||
`G_APPLICATION_FLAGS_NONE` is specified so no open signal handler is necessary.
|
|
||||||
- 86-106: Startup handler.
|
|
||||||
- 91-96: Builds widgets.
|
|
||||||
The pointers of the top window, TfeTextView and GtkDrawingArea objects are stored to static variables `win`, `tv` and `da` respectively.
|
|
||||||
This is because these objects are often used in handlers.
|
|
||||||
They never be rewritten so they're thread safe.
|
|
||||||
- 97: connects "resize" signal and the handler.
|
|
||||||
- 98: sets the drawing function.
|
|
||||||
- 81-84: Activates handler, which just shows the widgets.
|
|
||||||
- 73-79: The drawing function.
|
|
||||||
It just copies `surface` to destination.
|
|
||||||
- 65-71: Resize handler.
|
|
||||||
Re-creates the surface to fit the width and height of the drawing area and paints by calling the function `run`.
|
|
||||||
- 58-63: Closes the handler.
|
|
||||||
It destroys `surface` if it exists.
|
|
||||||
Then it destroys the top window and quits the application.
|
|
||||||
- 48-56: Open and save handler.
|
|
||||||
They just call the corresponding functions of TfeTextView.
|
|
||||||
- 42-46: Run handler.
|
|
||||||
It calls run function to paint the surface.
|
|
||||||
After that `gtk_widget_queue_draw` is called.
|
|
||||||
This fhunction adds the widget (GtkDrawingArea) to the queue to be redrawn.
|
|
||||||
It is important to know that the drawing function is called when it is necessary.
|
|
||||||
For example, when another window is moved and uncovers part of the widget, or when the window containing it is resized.
|
|
||||||
But repaint of `surface` is not automatically notified to gtk.
|
|
||||||
Therefore, you need to call `gtk_widget_queue_draw` to redraw the widget.
|
|
||||||
- 9-40: Run function paints the surface.
|
|
||||||
First, it gets the contents of GtkTextBuffer.
|
|
||||||
Then it compares it to "red", "green" and so on.
|
|
||||||
If it matches the color, then the surface is painted the color.
|
|
||||||
If it matches "light" or "dark", then the color of the surface is lightened or darkened respectively.
|
|
||||||
Alpha channel is used.
|
|
||||||
|
|
||||||
## Meson.build
|
|
||||||
|
|
||||||
This file is almost same as before.
|
|
||||||
An argument "export_dynamic: true" is added to executable function.
|
|
||||||
|
|
||||||
~~~meson
|
|
||||||
1 project('color', 'c')
|
|
||||||
2
|
2
|
||||||
3 gtkdep = dependency('gtk4')
|
3 int
|
||||||
4
|
4 main (int argc, char **argv)
|
||||||
5 gnome=import('gnome')
|
5 {
|
||||||
6 resources = gnome.compile_resources('resources','color.gresource.xml')
|
6 cairo_surface_t *surface;
|
||||||
7
|
7 cairo_t *cr;
|
||||||
8 sourcefiles=files('colorapplication.c', '../tfetextview/tfetextview.c')
|
8 int width = 100;
|
||||||
9
|
9 int height = 100;
|
||||||
10 executable('color', sourcefiles, resources, dependencies: gtkdep, export_dynamic: true)
|
10
|
||||||
|
11 /* Generate surface and cairo */
|
||||||
|
12 surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
|
||||||
|
13 cr = cairo_create (surface);
|
||||||
|
14
|
||||||
|
15 /* Drawing starts here. */
|
||||||
|
16 /* Paint the background white */
|
||||||
|
17 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
|
||||||
|
18 cairo_paint (cr);
|
||||||
|
19 /* Draw a black rectangle */
|
||||||
|
20 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
|
||||||
|
21 cairo_set_line_width (cr, 2.0);
|
||||||
|
22 cairo_rectangle (cr, width/2.0 - 20.0, height/2.0 - 20.0, 40.0, 40.0);
|
||||||
|
23 cairo_stroke (cr);
|
||||||
|
24
|
||||||
|
25 /* Write the surface to a png file and clean up cairo and surface. */
|
||||||
|
26 cairo_surface_write_to_png (surface, "rectangle.png");
|
||||||
|
27 cairo_destroy (cr);
|
||||||
|
28 cairo_surface_destroy (surface);
|
||||||
|
29
|
||||||
|
30 return 0;
|
||||||
|
31 }
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
## Compile and execute it
|
- 1: Includes the header file of cairo.
|
||||||
|
- 12: `cairo_image_surface_create` creates an image surface.
|
||||||
|
`CAIRO_FORMAT_RGB24` is a constant which means that each pixel has red, green and blue data.
|
||||||
|
Each data has 8 bit quantity.
|
||||||
|
Modern displays have this type of color depth.
|
||||||
|
Width and height are pixels and given as integers.
|
||||||
|
- 13: Creates cairo context.
|
||||||
|
The surface given as an argument will be the destination of the context.
|
||||||
|
- 17: `cairo_set_source_rgb` creates a source pattern, which is a solid white paint.
|
||||||
|
The second to fourth argument is red, green and blue color depth respectively.
|
||||||
|
Their type is float and the values are between zero and one.
|
||||||
|
(0,0,0) is black and (1,1,1) is white.
|
||||||
|
- 18: `cairo_paint` copies everywhere in the source to destination.
|
||||||
|
The destination is filled with white pixels by this command.
|
||||||
|
- 20: Sets the source color to black.
|
||||||
|
- 21: `cairo_set_line_width` set the width of lines.
|
||||||
|
In this case, the line width is set to two pixels.
|
||||||
|
(It is because the transformation is identity.
|
||||||
|
If the transformation isn't identity, for example scaling with the factor three, the actual width in destination will be six (2x3=6) pixels.)
|
||||||
|
- 22: Draws a rectangle (square).
|
||||||
|
The top-left coordinate is (width/2.0-20.0, height/2.0-20.0) and the width and height have the same length 40.0.
|
||||||
|
- 23: `cairo_stroke` transfer the source to destination through the rectangle in mask.
|
||||||
|
- 26: Outputs the image to a png file `rectangle.png`.
|
||||||
|
- 27: Destroys the context. At the same time the source is destroyed.
|
||||||
|
- 28: Destroys the destination surface.
|
||||||
|
|
||||||
First you need to export some variables (refer to [Section 2](sec2.md)).
|
To compile this, type the following.
|
||||||
|
|
||||||
$ . env.sh
|
$ gcc `pkg-config --cflags cairo` cairo.c `pkg-config --libs cairo`
|
||||||
|
|
||||||
Then type the following to compile it.
|
![rectangle.png](../src/misc/rectangle.png)
|
||||||
|
|
||||||
$ meson _build
|
There are lots of documentations in [Cairo's website](https://www.cairographics.org/).
|
||||||
$ ninja -C _build
|
If you aren't familiar with cairo, it is strongly recommended to read the [tutorial](https://www.cairographics.org/tutorial/) in the website.
|
||||||
|
|
||||||
The application is made in `_build` directory.
|
## GtkDrawingArea
|
||||||
Type the following to execute it.
|
|
||||||
|
|
||||||
$ _build/color
|
The following is a very simple example.
|
||||||
|
|
||||||
Type "red", "green", "blue", "white", black", "light" or "dark" in the TfeTextView.
|
~~~C
|
||||||
Then, click on `Run` button.
|
1 #include <gtk/gtk.h>
|
||||||
Make sure the color of GtkDrawingArea changes.
|
2
|
||||||
|
3 static void
|
||||||
|
4 draw_function (GtkDrawingArea *area, cairo_t *cr, int width, int height, gpointer user_data) {
|
||||||
|
5 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); /* whilte */
|
||||||
|
6 cairo_paint (cr);
|
||||||
|
7 cairo_set_line_width (cr, 2.0);
|
||||||
|
8 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
|
||||||
|
9 cairo_rectangle (cr, width/2.0 - 20.0, height/2.0 - 20.0, 40.0, 40.0);
|
||||||
|
10 cairo_stroke (cr);
|
||||||
|
11 }
|
||||||
|
12
|
||||||
|
13 static void
|
||||||
|
14 on_activate (GApplication *app, gpointer user_data) {
|
||||||
|
15 GtkWidget *win = gtk_application_window_new (GTK_APPLICATION (app));
|
||||||
|
16 GtkWidget *area = gtk_drawing_area_new ();
|
||||||
|
17
|
||||||
|
18 gtk_window_set_title (GTK_WINDOW (win), "da1");
|
||||||
|
19 /* Set initial size of width and height */
|
||||||
|
20 gtk_drawing_area_set_content_width (GTK_DRAWING_AREA (area), 100);
|
||||||
|
21 gtk_drawing_area_set_content_height (GTK_DRAWING_AREA (area), 100);
|
||||||
|
22 gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (area), draw_function, NULL, NULL);
|
||||||
|
23 gtk_window_set_child (GTK_WINDOW (win), area);
|
||||||
|
24
|
||||||
|
25 gtk_widget_show (win);
|
||||||
|
26 }
|
||||||
|
27
|
||||||
|
28 int
|
||||||
|
29 main (int argc, char **argv) {
|
||||||
|
30 GtkApplication *app;
|
||||||
|
31 int stat;
|
||||||
|
32
|
||||||
|
33 app = gtk_application_new ("com.github.ToshioCP.da1", G_APPLICATION_FLAGS_NONE);
|
||||||
|
34 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
|
||||||
|
35 stat =g_application_run (G_APPLICATION (app), argc, argv);
|
||||||
|
36 g_object_unref (app);
|
||||||
|
37 return stat;
|
||||||
|
38 }
|
||||||
|
39
|
||||||
|
~~~
|
||||||
|
|
||||||
In this program TfeTextView is used to change the color.
|
The function `main` is almost same as before.
|
||||||
You can use buttons or menus instead of textview.
|
The two functions `on_activate` and `draw_function` is important in this example.
|
||||||
Probably it is more appropriate.
|
|
||||||
Using textview is unnatural.
|
|
||||||
It is a good practice to make such application by yourself.
|
|
||||||
|
|
||||||
Up: [Readme.md](../Readme.md), Prev: [Section 19](sec19.md)
|
- 16: Generates a GtkDrawingArea object.
|
||||||
|
- 20,21: Sets the width and height of the contents of the GtkDrawingArea widget.
|
||||||
|
These width and height is the size of the destination surface of the cairo context provided by the widget.
|
||||||
|
- 22: Sets a drawing function of the widget.
|
||||||
|
GtkDrawingArea widget uses the function to draw the contents of itself whenever its necessary.
|
||||||
|
For example, when a user drag a mouse pointer and resize a top level window, GtkDrawingArea also changes the size.
|
||||||
|
Then, the whole window needs to be redrawn.
|
||||||
|
|
||||||
|
The drawing function has five parameters.
|
||||||
|
|
||||||
|
void drawing_function (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height,
|
||||||
|
gpointer user_data);
|
||||||
|
|
||||||
|
The first parameter is the GtkDrawingArea widget which calls the drawing function.
|
||||||
|
However, you can't change any properties, for example `content-width` or `content-height`, in this function.
|
||||||
|
The second parameter is a cairo context given by the widget.
|
||||||
|
The destination surface of the context is connected to the contents of the widget.
|
||||||
|
What you draw to this surface will appear in the widget on the screen.
|
||||||
|
The third and fourth parameters are the size of the destination surface.
|
||||||
|
|
||||||
|
- 3-11: The drawing function.
|
||||||
|
- 4-5: Sets the source to be white and paint the destination white.
|
||||||
|
- 7: Sets the line width to be 2.
|
||||||
|
- 8: Sets the source to be black.
|
||||||
|
- 9: Adds a rectangle to the mask.
|
||||||
|
- 10: Draws the rectangle with black color to the destination.
|
||||||
|
|
||||||
|
Compile and run it, then a window with a black rectangle (square) appears.
|
||||||
|
Try resizing the window.
|
||||||
|
The square always appears at the center of the window because the drawing function is invoked every moment the window is resized.
|
||||||
|
|
||||||
|
![Square in the window](../image/da1.png)
|
||||||
|
|
||||||
|
|
||||||
|
Up: [Readme.md](../Readme.md), Prev: [Section 19](sec19.md), Next: [Section 21](sec21.md)
|
||||||
|
|
373
gfm/sec21.md
Normal file
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.
|
First, let's look at the ui file `tfe3.ui` that defines a structure of the widgets.
|
||||||
|
|
||||||
~~~xml
|
~~~xml
|
||||||
1 <interface>
|
1 <?xml version="1.0" encoding="UTF-8"?>
|
||||||
2 <object class="GtkApplicationWindow" id="win">
|
2 <interface>
|
||||||
3 <property name="title">file editor</property>
|
3 <object class="GtkApplicationWindow" id="win">
|
||||||
4 <property name="default-width">600</property>
|
4 <property name="title">file editor</property>
|
||||||
5 <property name="default-height">400</property>
|
5 <property name="default-width">600</property>
|
||||||
6 <child>
|
6 <property name="default-height">400</property>
|
||||||
7 <object class="GtkBox" id="boxv">
|
7 <child>
|
||||||
8 <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
8 <object class="GtkBox" id="boxv">
|
||||||
9 <child>
|
9 <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
||||||
10 <object class="GtkBox" id="boxh">
|
10 <child>
|
||||||
11 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
11 <object class="GtkBox" id="boxh">
|
||||||
12 <child>
|
12 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
||||||
13 <object class="GtkLabel" id="dmy1">
|
13 <child>
|
||||||
14 <property name="width-chars">10</property>
|
14 <object class="GtkLabel" id="dmy1">
|
||||||
15 </object>
|
15 <property name="width-chars">10</property>
|
||||||
16 </child>
|
16 </object>
|
||||||
17 <child>
|
17 </child>
|
||||||
18 <object class="GtkButton" id="btnn">
|
18 <child>
|
||||||
19 <property name="label">New</property>
|
19 <object class="GtkButton" id="btnn">
|
||||||
20 </object>
|
20 <property name="label">New</property>
|
||||||
21 </child>
|
21 </object>
|
||||||
22 <child>
|
22 </child>
|
||||||
23 <object class="GtkButton" id="btno">
|
23 <child>
|
||||||
24 <property name="label">Open</property>
|
24 <object class="GtkButton" id="btno">
|
||||||
25 </object>
|
25 <property name="label">Open</property>
|
||||||
26 </child>
|
26 </object>
|
||||||
27 <child>
|
27 </child>
|
||||||
28 <object class="GtkLabel" id="dmy2">
|
28 <child>
|
||||||
29 <property name="hexpand">TRUE</property>
|
29 <object class="GtkLabel" id="dmy2">
|
||||||
30 </object>
|
30 <property name="hexpand">TRUE</property>
|
||||||
31 </child>
|
31 </object>
|
||||||
32 <child>
|
32 </child>
|
||||||
33 <object class="GtkButton" id="btns">
|
33 <child>
|
||||||
34 <property name="label">Save</property>
|
34 <object class="GtkButton" id="btns">
|
||||||
35 </object>
|
35 <property name="label">Save</property>
|
||||||
36 </child>
|
36 </object>
|
||||||
37 <child>
|
37 </child>
|
||||||
38 <object class="GtkButton" id="btnc">
|
38 <child>
|
||||||
39 <property name="label">Close</property>
|
39 <object class="GtkButton" id="btnc">
|
||||||
40 </object>
|
40 <property name="label">Close</property>
|
||||||
41 </child>
|
41 </object>
|
||||||
42 <child>
|
42 </child>
|
||||||
43 <object class="GtkLabel" id="dmy3">
|
43 <child>
|
||||||
44 <property name="width-chars">10</property>
|
44 <object class="GtkLabel" id="dmy3">
|
||||||
45 </object>
|
45 <property name="width-chars">10</property>
|
||||||
46 </child>
|
46 </object>
|
||||||
47 </object>
|
47 </child>
|
||||||
48 </child>
|
48 </object>
|
||||||
49 <child>
|
49 </child>
|
||||||
50 <object class="GtkNotebook" id="nb">
|
50 <child>
|
||||||
51 <property name="hexpand">TRUE</property>
|
51 <object class="GtkNotebook" id="nb">
|
||||||
52 <property name="vexpand">TRUE</property>
|
52 <property name="hexpand">TRUE</property>
|
||||||
53 </object>
|
53 <property name="vexpand">TRUE</property>
|
||||||
54 </child>
|
54 </object>
|
||||||
55 </object>
|
55 </child>
|
||||||
56 </child>
|
56 </object>
|
||||||
57 </object>
|
57 </child>
|
||||||
58 </interface>
|
58 </object>
|
||||||
59
|
59 </interface>
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
This is coded with XML structure.
|
This is coded with XML structure.
|
||||||
|
@ -204,18 +204,35 @@ Constructs beginning with `<` and ending with `>` are called tags.
|
||||||
And there are two types of tags, start tag and end tag.
|
And there are two types of tags, start tag and end tag.
|
||||||
For example, `<interface>` is a start tag and `</interface>` is an end tag.
|
For example, `<interface>` is a start tag and `</interface>` is an end tag.
|
||||||
Ui file begins and ends with interface tags.
|
Ui file begins and ends with interface tags.
|
||||||
Some tags, for example, object tags can have a class and id attributes inside the start tag.
|
Some tags, for example, object tags can have a class and id attributes in the start tag.
|
||||||
|
|
||||||
- 2-5: An object with `GtkApplicationWindow` class and `win` id is defined.
|
- 1: The first line is XML declaration.
|
||||||
|
It specifies that the version of XML is 1.0 and the encoding is UTF-8.
|
||||||
|
Even if the line is left out, GtkBuilder builds objects from the ui file.
|
||||||
|
But ui files must use UTF-8 encoding, or GtkBuilder can't recognize it and fatal error occurs.
|
||||||
|
- 3-6: An object with `GtkApplicationWindow` class and `win` id is defined.
|
||||||
This is the top level window.
|
This is the top level window.
|
||||||
And the three properties of the window are defined.
|
And the three properties of the window are defined.
|
||||||
`title` property is "file editor", `default-width` property is 400 and `default-height` property is 300.
|
`title` property is "file editor", `default-width` property is 400 and `default-height` property is 300.
|
||||||
- 6: child tag means a child of the object above.
|
- 7: child tag means a child of the object above.
|
||||||
For example, line 7 tells us that GtkBox object which id is "boxv" is a child of `win`.
|
For example, line 7 tells us that GtkBox object which id is "boxv" is a child of `win`.
|
||||||
|
|
||||||
Compare this ui file and the lines 25-57 in the source code of `on_open` function.
|
Compare this ui file and the lines 25-57 in the source code of `on_open` function.
|
||||||
Those two describe the same structure of widgets.
|
Those two describe the same structure of widgets.
|
||||||
|
|
||||||
|
You can check the ui file with `gtk4-builder-tool`.
|
||||||
|
|
||||||
|
- `gtk4-builder-tool validate <ui file name>` validates the ui file.
|
||||||
|
If the ui file includes some syntactical error, `gtk4-builder-tool` prints the error.
|
||||||
|
- `gtk4-builder-tool simplify <ui file name>` simplifies the ui file and prints the result.
|
||||||
|
If `--replace` option is given, it replaces the ui file with the simplified one.
|
||||||
|
If the ui file specifies a value of property but it is default, then it will be removed.
|
||||||
|
Anf some values are simplified.
|
||||||
|
For example, "TRUE"and "FALSE" becomes "1" and "0" respectively.
|
||||||
|
However, "TRUE" or "FALSE" is better for maintenance.
|
||||||
|
|
||||||
|
It is a good idea to check your ui file before compiling.
|
||||||
|
|
||||||
## GtkBuilder
|
## GtkBuilder
|
||||||
|
|
||||||
GtkBuilder builds widgets based on the ui file.
|
GtkBuilder builds widgets based on the ui file.
|
||||||
|
|
64
gfm/sec9.md
64
gfm/sec9.md
|
@ -173,69 +173,7 @@ All the source files are listed below.
|
||||||
70
|
70
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
`tfe.ui`
|
The ui file `tfe.ui` is the same as `tfe3.ui` in the previous section.
|
||||||
|
|
||||||
~~~xml
|
|
||||||
1 <interface>
|
|
||||||
2 <object class="GtkApplicationWindow" id="win">
|
|
||||||
3 <property name="title">file editor</property>
|
|
||||||
4 <property name="default-width">600</property>
|
|
||||||
5 <property name="default-height">400</property>
|
|
||||||
6 <child>
|
|
||||||
7 <object class="GtkBox" id="boxv">
|
|
||||||
8 <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
|
||||||
9 <child>
|
|
||||||
10 <object class="GtkBox" id="boxh">
|
|
||||||
11 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
|
||||||
12 <child>
|
|
||||||
13 <object class="GtkLabel" id="dmy1">
|
|
||||||
14 <property name="width-chars">10</property>
|
|
||||||
15 </object>
|
|
||||||
16 </child>
|
|
||||||
17 <child>
|
|
||||||
18 <object class="GtkButton" id="btnn">
|
|
||||||
19 <property name="label">New</property>
|
|
||||||
20 </object>
|
|
||||||
21 </child>
|
|
||||||
22 <child>
|
|
||||||
23 <object class="GtkButton" id="btno">
|
|
||||||
24 <property name="label">Open</property>
|
|
||||||
25 </object>
|
|
||||||
26 </child>
|
|
||||||
27 <child>
|
|
||||||
28 <object class="GtkLabel" id="dmy2">
|
|
||||||
29 <property name="hexpand">TRUE</property>
|
|
||||||
30 </object>
|
|
||||||
31 </child>
|
|
||||||
32 <child>
|
|
||||||
33 <object class="GtkButton" id="btns">
|
|
||||||
34 <property name="label">Save</property>
|
|
||||||
35 </object>
|
|
||||||
36 </child>
|
|
||||||
37 <child>
|
|
||||||
38 <object class="GtkButton" id="btnc">
|
|
||||||
39 <property name="label">Close</property>
|
|
||||||
40 </object>
|
|
||||||
41 </child>
|
|
||||||
42 <child>
|
|
||||||
43 <object class="GtkLabel" id="dmy3">
|
|
||||||
44 <property name="width-chars">10</property>
|
|
||||||
45 </object>
|
|
||||||
46 </child>
|
|
||||||
47 </object>
|
|
||||||
48 </child>
|
|
||||||
49 <child>
|
|
||||||
50 <object class="GtkNotebook" id="nb">
|
|
||||||
51 <property name="hexpand">TRUE</property>
|
|
||||||
52 <property name="vexpand">TRUE</property>
|
|
||||||
53 </object>
|
|
||||||
54 </child>
|
|
||||||
55 </object>
|
|
||||||
56 </child>
|
|
||||||
57 </object>
|
|
||||||
58 </interface>
|
|
||||||
59
|
|
||||||
~~~
|
|
||||||
|
|
||||||
`tfe.gresource.xml`
|
`tfe.gresource.xml`
|
||||||
|
|
||||||
|
|
BIN
image/dialog_warning.png
Normal file
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_file = $1
|
||||||
c_functions = $2.strip.split(" ")
|
c_functions = $2.strip.split(" ")
|
||||||
if c_file =~ /^\// # absolute path
|
if c_file =~ /^\// # absolute path
|
||||||
c_file_buf = IO.readlines(c_file)
|
c_file_buf = File.readlines(c_file)
|
||||||
else #relative path
|
else #relative path
|
||||||
c_file_buf = IO.readlines(src_dir+"/"+c_file)
|
c_file_buf = File.readlines(src_dir+"/"+c_file)
|
||||||
end
|
end
|
||||||
if c_functions.empty? # no functions are specified
|
if c_functions.empty? # no functions are specified
|
||||||
tmp_buf = c_file_buf
|
tmp_buf = c_file_buf
|
||||||
|
@ -96,7 +96,7 @@ def src2md srcmd, md, width
|
||||||
c_functions.each do |c_function|
|
c_functions.each do |c_function|
|
||||||
from = c_file_buf.find_index { |line| line =~ /^#{c_function} *\(/ }
|
from = c_file_buf.find_index { |line| line =~ /^#{c_function} *\(/ }
|
||||||
if ! from
|
if ! from
|
||||||
warn "ERROR!!! --- Didn't find #{c_function} in #{filename}. ---"
|
warn "ERROR in #{srcmd}: Didn't find #{c_function} in #{c_file}."
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
to = from
|
to = from
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<interface>
|
<interface>
|
||||||
<object class="GtkApplicationWindow" id="win">
|
<object class="GtkApplicationWindow" id="win">
|
||||||
<property name="title">color changer</property>
|
<property name="title">color changer</property>
|
||||||
|
@ -8,68 +9,67 @@
|
||||||
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox" id="boxh1">
|
<object class="GtkBox" id="boxh1">
|
||||||
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel" id="dmy1">
|
<object class="GtkLabel" id="dmy1">
|
||||||
<property name="width-chars">10</property>
|
<property name="width-chars">10</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="btnr">
|
<object class="GtkButton" id="btnr">
|
||||||
<property name="label">Run</property>
|
<property name="label">Run</property>
|
||||||
<signal name="clicked" handler="run_cb"></signal>
|
<signal name="clicked" handler="run_cb"></signal>
|
||||||
|
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="btno">
|
<object class="GtkButton" id="btno">
|
||||||
<property name="label">Open</property>
|
<property name="label">Open</property>
|
||||||
<signal name="clicked" handler="open_cb"></signal>
|
<signal name="clicked" handler="open_cb"></signal>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel" id="dmy2">
|
<object class="GtkLabel" id="dmy2">
|
||||||
<property name="hexpand">TRUE</property>
|
<property name="hexpand">TRUE</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="btns">
|
<object class="GtkButton" id="btns">
|
||||||
<property name="label">Save</property>
|
<property name="label">Save</property>
|
||||||
<signal name="clicked" handler="save_cb"></signal>
|
<signal name="clicked" handler="save_cb"></signal>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="btnc">
|
<object class="GtkButton" id="btnc">
|
||||||
<property name="label">Close</property>
|
<property name="label">Close</property>
|
||||||
<signal name="clicked" handler="close_cb"></signal>
|
<signal name="clicked" handler="close_cb"></signal>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel" id="dmy3">
|
<object class="GtkLabel" id="dmy3">
|
||||||
<property name="width-chars">10</property>
|
<property name="width-chars">10</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox" id="boxh2">
|
<object class="GtkBox" id="boxh2">
|
||||||
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
||||||
<property name="homogeneous">TRUE</property>
|
<property name="homogeneous">TRUE</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkScrolledWindow" id="scr">
|
<object class="GtkScrolledWindow" id="scr">
|
||||||
<property name="hexpand">TRUE</property>
|
<property name="hexpand">TRUE</property>
|
||||||
<property name="vexpand">TRUE</property>
|
<property name="vexpand">TRUE</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="TfeTextView" id="tv">
|
<object class="TfeTextView" id="tv">
|
||||||
<property name="wrap-mode">GTK_WRAP_WORD_CHAR</property>
|
<property name="wrap-mode">GTK_WRAP_WORD_CHAR</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkDrawingArea" id="da">
|
<object class="GtkDrawingArea" id="da">
|
||||||
<property name="hexpand">TRUE</property>
|
<property name="hexpand">TRUE</property>
|
||||||
<property name="vexpand">TRUE</property>
|
<property name="vexpand">TRUE</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
@ -78,4 +78,3 @@
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</interface>
|
</interface>
|
||||||
|
|
||||||
|
|
|
@ -220,7 +220,7 @@ Then C destructs itself and finally the memories allocated to C is freed.
|
||||||
|
|
||||||
The idea above is based on an assumption that an object referred by nothing has reference count of zero.
|
The idea above is based on an assumption that an object referred by nothing has reference count of zero.
|
||||||
When the reference count drops to zero, the object starts its destruction process.
|
When the reference count drops to zero, the object starts its destruction process.
|
||||||
The destruction process is spitted into two phases: disposing and finalizing.
|
The destruction process is split into two phases: disposing and finalizing.
|
||||||
In the disposing process, the object invokes the function pointed by `dispose` in its class to release all references to other objects.
|
In the disposing process, the object invokes the function pointed by `dispose` in its class to release all references to other objects.
|
||||||
In the finalizing process, it invokes the function pointed by `finalize` in its class to complete the destruction process.
|
In the finalizing process, it invokes the function pointed by `finalize` in its class to complete the destruction process.
|
||||||
These functions are also called handlers or methods.
|
These functions are also called handlers or methods.
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
GtkNotebook is a very important object in the text file editor `tfe`.
|
GtkNotebook is a very important object in the text file editor `tfe`.
|
||||||
It connects the application and TfeTextView objects.
|
It connects the application and TfeTextView objects.
|
||||||
A set of functions related to GtkNotebook are declared in `tfenotebook.h`.
|
A set of public functions related to GtkNotebook are declared in `tfenotebook.h`.
|
||||||
The word "tfenotebook" is used only in filenames.
|
The word "tfenotebook" is used only in filenames.
|
||||||
There's no "TfeNotebook" object.
|
There's no "TfeNotebook" object.
|
||||||
|
|
||||||
|
@ -12,18 +12,19 @@ tfe5/tfenotebook.h
|
||||||
|
|
||||||
This header file describes the public functions in `tfenotebook.c`.
|
This header file describes the public functions in `tfenotebook.c`.
|
||||||
|
|
||||||
- 10-11: The function `notebook_page_new` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView to the page.
|
- 1-2: `notebook_page_save` saves the current page to the file of which the name specified in the tab.
|
||||||
- 7-8: The function `notebook_page_new_with_file` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView to the page. A file is read and inserted into GtkTextBuffer.
|
If the name is `untitled` or `untitled` followed by digits, FileChooserDialog appears and a user can choose or specify a filename.
|
||||||
The GFile `file` is copied and inserted to the TfeTextView object.
|
- 4-5: `notebook_page_close` closes the current page.
|
||||||
- 4-5: `notebook_page_open` shows a file chooser dialog. Then, user chooses a file and the file is inserted into GtkTextBuffer.
|
- 7-8: `notebook_page_open` shows a file chooser dialog and a user can choose a file. The file is inserted to a new page.
|
||||||
- 1-2: `notebook_page_save` saves the contents in GtkTextBuffer into the file, which is got from the TfeTextView.
|
- 10-11: `notebook_page_new_with_file` generates a new page and the file give as an argument is read and inserted into the page.
|
||||||
|
- 13-14: `notebook_page_new` generates a new empty page.
|
||||||
|
|
||||||
You probably find that the functions above are higher level functions of
|
You probably find that the functions except `notebook_page_close` are higher level functions of
|
||||||
|
|
||||||
- `tfe_text_view_new`
|
|
||||||
- `tfe_text_view_new_with_file`
|
|
||||||
- `tef_text_view_open`
|
|
||||||
- `tfe_text_view_save`
|
- `tfe_text_view_save`
|
||||||
|
- `tef_text_view_open`
|
||||||
|
- `tfe_text_view_new_with_file`
|
||||||
|
- `tfe_text_view_new`
|
||||||
|
|
||||||
respectively.
|
respectively.
|
||||||
|
|
||||||
|
@ -31,7 +32,7 @@ There are two layers.
|
||||||
One of them is `tfe_text_view ...`, which is the lower level layer.
|
One of them is `tfe_text_view ...`, which is the lower level layer.
|
||||||
The other is `note_book ...`, which is the higher level layer.
|
The other is `note_book ...`, which is the higher level layer.
|
||||||
|
|
||||||
Now let's look at each program of the functions.
|
Now let's look at the program of each function.
|
||||||
|
|
||||||
## notebook\_page\_new
|
## notebook\_page\_new
|
||||||
|
|
||||||
|
@ -39,28 +40,28 @@ Now let's look at each program of the functions.
|
||||||
tfe5/tfenotebook.c get_untitled notebook_page_build notebook_page_new
|
tfe5/tfenotebook.c get_untitled notebook_page_build notebook_page_new
|
||||||
@@@
|
@@@
|
||||||
|
|
||||||
- 28-38: `notebook_page_new` function.
|
- 27-37: `notebook_page_new` function.
|
||||||
- 30: `g_return_if_fail` is used to check the argument.
|
- 29: `g_return_if_fail` is used to check the argument.
|
||||||
- 35: Generates TfeTextView object.
|
- 34: Generates TfeTextView object.
|
||||||
- 36: Generates filename, which is "Untitled", "Untitled2", ... .
|
- 35: Generates filename, which is "Untitled", "Untitled1", ... .
|
||||||
- 1-8: `get_untitled` function.
|
- 1-8: `get_untitled` function.
|
||||||
- 3: Static variable `c` is initialized at the first call of this function. After that `c` keeps its value except it is changed explicitly.
|
- 3: Static variable `c` is initialized at the first call of this function. After that `c` keeps its value unless it is changed explicitly.
|
||||||
- 4-7: Increases `c` by one and if it is zero then it returns "Untitled". If it is a positive integer then the it returns "Untitled\<the integer\>", for example, "Untitled1", "Untitled2", and so on.
|
- 4-7: Increases `c` by one and if it is zero then it returns "Untitled". If it is a positive integer then it returns "Untitled\<the integer\>", for example, "Untitled1", "Untitled2", and so on.
|
||||||
The function `g_strdup_printf` generates a string and it should be freed by `g_free` when it becomes useless.
|
The function `g_strdup_printf` generates a string and it should be freed by `g_free` when it becomes useless.
|
||||||
The caller of `get_untitled` is in charge of freeing the string.
|
The caller of `get_untitled` is in charge of freeing the string.
|
||||||
- 37: calls `notebook_page_build` to build the contents of the page.
|
- 36: calls `notebook_page_build` to build the contents of the page.
|
||||||
- 10- 26: `notebook_page_build` function.
|
- 10- 25: `notebook_page_build` function.
|
||||||
- 16: Generates GtkScrolledWindow.
|
- 12: Generates GtkScrolledWindow.
|
||||||
- 18: Sets the wrap mode of `tv` to GTK_WRAP_WORD_CHAR so that lines are broken between words or graphemes.
|
- 17: Sets the wrap mode of `tv` to GTK_WRAP_WORD_CHAR so that lines are broken between words or graphemes.
|
||||||
- 19: Inserts `tv` to GtkscrolledWindow as a child.
|
- 18: Inserts `tv` to GtkscrolledWindow as a child.
|
||||||
- 20-21: Generates GtkLabel, then appends it to GtkNotebookPage.
|
- 19-20: Generates GtkLabel, then appends it to GtkNotebookPage.
|
||||||
- 22-23: Sets "tab-expand" property to TRUE.
|
- 21-22: Sets "tab-expand" property to TRUE.
|
||||||
The function g\_object\_set sets properties on an object.
|
The function g\_object\_set sets properties on an object.
|
||||||
The object is any object derived from GObject.
|
The object is any object derived from GObject.
|
||||||
In many cases, an object has its own function to set its properties, but sometimes not.
|
In many cases, an object has its own function to set its properties, but sometimes not.
|
||||||
In that case, use g\_object\_set to set the property.
|
In that case, use g\_object\_set to set the property.
|
||||||
- 24: Sets the current page of `nb` to `i` which is the number of the GtkNotebookPage above.
|
- 23: Sets the current page of `nb` to the newly generated page.
|
||||||
- 25: Connects "change-file" signal and `file_changed` handler.
|
- 24: Connects "change-file" signal and `file_changed_cb` handler.
|
||||||
|
|
||||||
## notebook\_page\_new\_with\_file
|
## notebook\_page\_new\_with\_file
|
||||||
|
|
||||||
|
@ -92,34 +93,54 @@ Such object has floating reference.
|
||||||
You need to call `g_object_ref_sink` first.
|
You need to call `g_object_ref_sink` first.
|
||||||
Then the floating reference is converted into an ordinary reference.
|
Then the floating reference is converted into an ordinary reference.
|
||||||
Now you call `g_object_unref` to decrease the reference count by one.
|
Now you call `g_object_unref` to decrease the reference count by one.
|
||||||
- 9-11: If `tfe_text_view_get_file` returns a pointer not to point GFile, then something bad happens.
|
- 9-11: If `tfe_text_view_get_file` returns a pointer not to point GFile, it means that an error has happened.
|
||||||
Sink and unref `tv`.
|
Sink and unref `tv`.
|
||||||
- 12-16: Otherwise, everything is okay.
|
- 12-16: Otherwise, everything is okay.
|
||||||
Gets the filename, builds the contents of the page.
|
Gets the filename, builds the contents of the page.
|
||||||
|
|
||||||
|
## notebook\_page\_close
|
||||||
|
|
||||||
|
@@@include
|
||||||
|
tfe5/tfenotebook.c notebook_page_close
|
||||||
|
@@@
|
||||||
|
|
||||||
|
This function closes the current page.
|
||||||
|
If the page is the only page the notebook has, then the function destroys the top window and quits the application.
|
||||||
|
|
||||||
|
- 8-10: If the page is the only page the notebook has, it calls gtk\_window\_destroy to destroys the top window.
|
||||||
|
- 11-13: Otherwise, removes the current page.
|
||||||
|
|
||||||
## notebook\_page\_save
|
## notebook\_page\_save
|
||||||
|
|
||||||
@@@include
|
@@@include
|
||||||
tfe5/tfenotebook.c notebook_page_save
|
tfe5/tfenotebook.c get_current_textview notebook_page_save
|
||||||
@@@
|
@@@
|
||||||
|
|
||||||
- 7-9: Get TfeTextView belongs to the current notebook page.
|
- 13-21: `notebook_page_save`.
|
||||||
- 10: Call `tfe_text_view_save`.
|
- 19: Gets TfeTextView belongs to the current page.
|
||||||
|
- 20: Calls `tfe_text_view_save`.
|
||||||
|
- 1-11: `get_current_textview`.
|
||||||
|
This function gets the TfeTextView object belongs to the current page.
|
||||||
|
- 7: Gets the page number of the current page.
|
||||||
|
- 8: Gets the child object `scr`, which is GtkScrolledWindow object, of the current page.
|
||||||
|
- 9-10: Gets the child object of `scr`, which is TfeTextView object, and return it.
|
||||||
|
|
||||||
## file\_changed handler
|
## file\_changed\_cb handler
|
||||||
|
|
||||||
The function `file_changed` is a handler connected to "change-file" signal.
|
The function `file_changed_cb` is a handler connected to "change-file" signal.
|
||||||
If the file in TfeTextView is changed, it emits this signal.
|
If the file in TfeTextView is changed, it emits this signal.
|
||||||
This handler changes the label of GtkNotebookPage.
|
This handler changes the label of GtkNotebookPage.
|
||||||
|
|
||||||
@@@include
|
@@@include
|
||||||
tfe5/tfenotebook.c file_changed
|
tfe5/tfenotebook.c file_changed_cb
|
||||||
@@@
|
@@@
|
||||||
|
|
||||||
- 8: Gets GFile from TfeTextView.
|
- 8: Gets GFile from TfeTextView.
|
||||||
- 9: Gets GkScrolledWindow which is the parent widget of `tv`.
|
- 9: Gets GkScrolledWindow which is the parent widget of `tv`.
|
||||||
- 10-13: If `file` points GFile, then assigns the filename of the GFile into `filename`.
|
- 10-12: If `file` points GFile, then assigns the filename of the GFile into `filename`.
|
||||||
Otherwise (file is NULL), assigns untitled string to `filename`.
|
Then, unref the GFile object `file`.
|
||||||
- 14-15: Generates a label with the filename and inserts it into GtkNotebookPage.
|
- 13-14: Otherwise (file is NULL), assigns untitled string to `filename`.
|
||||||
- 16-17: Unrefs `file` and frees `filename`.
|
- 15-16: Generates a label with the filename and inserts it into GtkNotebookPage.
|
||||||
|
|
||||||
|
The string `filename` is used in the GtkLabel object.
|
||||||
|
You mustn't free it.
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
# tfeapplication.c
|
# tfeapplication.c
|
||||||
|
|
||||||
`tfeapplication.c` includes all the code other than `tfetxtview.c` and `tfenotebook.c`.
|
`tfeapplication.c` includes all the code other than `tfetxtview.c` and `tfenotebook.c`.
|
||||||
It does following things.
|
It does:
|
||||||
|
|
||||||
- Application support, mainly handling command line arguments.
|
- Application support, mainly handling command line arguments.
|
||||||
- Build widgets using ui file.
|
- Builds widgets using ui file.
|
||||||
- Connect button signals and their handlers.
|
- Connects button signals and their handlers.
|
||||||
- Manage CSS.
|
- Manages CSS.
|
||||||
|
|
||||||
## main
|
## main
|
||||||
|
|
||||||
|
@ -72,7 +72,8 @@ If you want to set style to GtkTextView, substitute "textview" for the selector.
|
||||||
textview {color: yellow; ...}
|
textview {color: yellow; ...}
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
Class, ID and some other things can be applied to the selector like Web CSS. Refer GTK4 API reference for further information.
|
Class, ID and some other things can be applied to the selector like Web CSS.
|
||||||
|
Refer [GTK4 API reference](https://gnome.pages.gitlab.gnome.org/gtk/gtk/theming.html) for further information.
|
||||||
|
|
||||||
In line 30, the CSS is a string.
|
In line 30, the CSS is a string.
|
||||||
|
|
||||||
|
@ -81,11 +82,10 @@ textview {padding: 10px; font-family: monospace; font-size: 12pt;}
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
- padding is a space between the border and contents.
|
- padding is a space between the border and contents.
|
||||||
This space makes the text easier to read.
|
This space makes the textview easier to read.
|
||||||
- font-family is a name of font.
|
- font-family is a name of font.
|
||||||
"monospace" is one of the generic family font keywords.
|
"monospace" is one of the generic family font keywords.
|
||||||
- font-size is set to 12pt.
|
- font-size is set to 12pt.
|
||||||
It is a bit large, but easy on the eyes especially for elderly people.
|
|
||||||
|
|
||||||
### GtkStyleContext, GtkCSSProvider and GdkDisplay
|
### GtkStyleContext, GtkCSSProvider and GdkDisplay
|
||||||
|
|
||||||
|
@ -107,6 +107,8 @@ Look at the source file of `startup` handler again.
|
||||||
|
|
||||||
It is possible to add the provider to the context of GtkTextView instead of GdkDiplay.
|
It is possible to add the provider to the context of GtkTextView instead of GdkDiplay.
|
||||||
To do so, rewrite `tfe_text_view_new`.
|
To do so, rewrite `tfe_text_view_new`.
|
||||||
|
First, get the GtkStyleContext object of a TfeTextView object.
|
||||||
|
Then adds the CSS provider to the context.
|
||||||
|
|
||||||
~~~C
|
~~~C
|
||||||
GtkWidget *
|
GtkWidget *
|
||||||
|
@ -137,14 +139,12 @@ They just generate a new GtkNotebookPage.
|
||||||
tfe5/tfeapplication.c tfe_activate tfe_open
|
tfe5/tfeapplication.c tfe_activate tfe_open
|
||||||
@@@
|
@@@
|
||||||
|
|
||||||
- 1-14: `tfe_activate`.
|
- 1-11: `tfe_activate`.
|
||||||
- 8-10: Gets GtkNotebook object.
|
- 8-10: Generates a new page and shows the window.
|
||||||
- 12-13: Generates a new GtkNotebookPage and show the window.
|
- 12-25: `tfe_open`.
|
||||||
- 16-33: `tfe_open`.
|
- 20-21: Generates notebook pages with files.
|
||||||
- 24-26: Gets GtkNotebook object.
|
- 22-23: If no page has generated, maybe because of read error, then it generates a empty page.
|
||||||
- 28-29: Generates GtkNotebookPage with files.
|
- 24: Shows the window.
|
||||||
- 30-31: If opening and reading file failed and no GtkNotebookPage has generated, then it generates a empty page.
|
|
||||||
- 32: Shows the window.
|
|
||||||
|
|
||||||
These codes have become really simple thanks to tfenotebook.c and tfetextview.c.
|
These codes have become really simple thanks to tfenotebook.c and tfetextview.c.
|
||||||
|
|
||||||
|
@ -185,15 +185,10 @@ The second instance immediately quits so shell prompt soon appears.
|
||||||
## a series of handlers correspond to the button signals
|
## a series of handlers correspond to the button signals
|
||||||
|
|
||||||
@@@include
|
@@@include
|
||||||
tfe5/tfeapplication.c open_clicked new_clicked save_clicked close_clicked
|
tfe5/tfeapplication.c open_cb new_cb save_cb close_cb
|
||||||
@@@
|
@@@
|
||||||
|
|
||||||
`open_clicked`, `new_clicked` and `save_clicked` just call corresponding notebook page functions.
|
`open_cb`, `new_cb`, `save_cb` and `close_cb` just call corresponding notebook page functions.
|
||||||
`close_clicked` is a bit complicated.
|
|
||||||
|
|
||||||
- 22-25: If there's only one page, we need to close the top level window and quit the application.
|
|
||||||
First, get the top level window and call `gtk_window_destroy`.
|
|
||||||
- 26-28: Otherwise, it removes the current page.
|
|
||||||
|
|
||||||
## meson.build
|
## meson.build
|
||||||
|
|
||||||
|
@ -201,13 +196,13 @@ First, get the top level window and call `gtk_window_destroy`.
|
||||||
tfe5/meson.build
|
tfe5/meson.build
|
||||||
@@@
|
@@@
|
||||||
|
|
||||||
In this file, just the source file names are modified.
|
In this file, just the source file names are modified from the prior version.
|
||||||
|
|
||||||
## source files
|
## source files
|
||||||
|
|
||||||
The [source files](https://github.com/ToshioCP/Gtk4-tutorial/tree/main/src/tfe5) of the text editor `tfe` will be shown in the next section.
|
The [source files](https://github.com/ToshioCP/Gtk4-tutorial/tree/main/src/tfe5) of the text editor `tfe` will be shown in the next section.
|
||||||
|
|
||||||
You can download the files.
|
You can also download the files from the [repository](https://github.com/ToshioCP/Gtk4-tutorial).
|
||||||
There are two options.
|
There are two options.
|
||||||
|
|
||||||
- Use git and clone.
|
- Use git and clone.
|
||||||
|
@ -218,4 +213,4 @@ If you use git, run the terminal and type the following.
|
||||||
|
|
||||||
$ git clone https://github.com/ToshioCP/Gtk4-tutorial.git
|
$ git clone https://github.com/ToshioCP/Gtk4-tutorial.git
|
||||||
|
|
||||||
The source files are under `/src/tfe5` directory.
|
The source files are under [`/src/tfe5`](tfe5) directory.
|
||||||
|
|
|
@ -56,7 +56,7 @@ Some menu items have a link to another GMenu.
|
||||||
There are two types of links, submenu and section.
|
There are two types of links, submenu and section.
|
||||||
|
|
||||||
GMenuItem can be inserted, appended or prepended to GMenu.
|
GMenuItem can be inserted, appended or prepended to GMenu.
|
||||||
When it is inserted, all of the attribute and link values of the item are copied and used to form a new item within the menu.
|
When it is inserted, all of the attributes and link values of the item are copied and used to form a new item within the menu.
|
||||||
The GMenuItem itself is not really inserted.
|
The GMenuItem itself is not really inserted.
|
||||||
Therefore, after the insertion, GMenuItem is useless and it should be freed.
|
Therefore, after the insertion, GMenuItem is useless and it should be freed.
|
||||||
The same goes for appending or prepending.
|
The same goes for appending or prepending.
|
||||||
|
|
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.
|
Include this file by . (dot) command before using gtk4 libraries.
|
||||||
|
|
||||||
You may think you can add them in your `.profile`.
|
You may think you can add them in your `.profile`.
|
||||||
I think the environment variables above are necessary only when you compile gtk4 applications.
|
But it's a wrong decision.
|
||||||
And it's not necessary except the case above and it might cause some bad things.
|
Never write them to your `.profile`.
|
||||||
Therefore, I recommend you not to write them to your `.profile`.
|
The environment variables above are necessary only when you compile and run gtk4 applications.
|
||||||
|
Otherwise it's not necessary.
|
||||||
|
If you changed the environment variables above and run gtk3 applications, it probably causes serious damage.
|
||||||
|
|
||||||
## Compiling gtk4 applications
|
## Compiling gtk4 applications
|
||||||
|
|
||||||
|
|
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.
|
If you want to draw dynamically, like an image window of gimp graphics editor, GtkDrawingArea widget is the most suitable widget.
|
||||||
Its name is "color".
|
You can draw or redraw an image in this widget freely.
|
||||||
If you write a color in TfeTextView and click on the `run` button, then the color of GtkDrawingArea changes to the color given by you.
|
It is called custom drawing.
|
||||||
|
|
||||||
![color](../image/color.png){width=7.0cm height=5.13cm}
|
GtkDrawingArea provides a cairo context so users can draw images by cairo functions.
|
||||||
|
In this section, I will explain:
|
||||||
|
|
||||||
The following colors are available.
|
1. Cairo, but briefly.
|
||||||
|
2. GtkDrawingArea with very simple example.
|
||||||
|
|
||||||
- white
|
## Cairo
|
||||||
- black
|
|
||||||
- red
|
|
||||||
- green
|
|
||||||
- blue
|
|
||||||
|
|
||||||
In addition the following two options are also available.
|
Cairo is a two dimensional graphics library.
|
||||||
|
First, you need to know surface, source, mask, destination, cairo context and transformation.
|
||||||
|
|
||||||
- light: Make the color of the drawing area lighter.
|
- Surface represents an image.
|
||||||
- dark: Make the color of the drawing area darker.
|
It is like a canvas.
|
||||||
|
We can draw shapes and images with different colors on surfaces.
|
||||||
|
- Source pattern, or simply source, is a kind of paint, which will be transferred to destination surface by cairo functions.
|
||||||
|
- Mask is image mask used in the transference.
|
||||||
|
- Destination is a target surface.
|
||||||
|
- Cairo context manages the transference from source to destination through mask with its functions.
|
||||||
|
For example, `cairo_stroke` is a function to draw a path to the destination by the transference.
|
||||||
|
- Transformation is applied before the transfer completes.
|
||||||
|
The transformation is called affine, which is a mathematics terminology, and represented by matrix multiplication and vector addition.
|
||||||
|
Scaling, rotation, reflection, shearing and translation are examples of affine transformation.
|
||||||
|
In this section, we don't use it.
|
||||||
|
That means we only use identity transformation.
|
||||||
|
Therefore, the coordinate in source and mask is the same as the coordinate in destination.
|
||||||
|
|
||||||
This application can only do very simple things.
|
![Stroke a rectangle](../image/cairo.png){width=9.0cm height=6.0cm}
|
||||||
However, it tells us that if we add powerful parser to it, we will be able to make it more efficient.
|
|
||||||
I want to show it to you in the later section by making a turtle graphics language like Logo program language.
|
|
||||||
|
|
||||||
In this section, we focus on how to bind the two objects.
|
The instruction is as follows:
|
||||||
|
|
||||||
## Color.ui and color.gresource.xml
|
1. Create a surface.
|
||||||
|
This will be a destination.
|
||||||
|
2. Create a cairo context with the surface and the surface will be the destination of the context.
|
||||||
|
3. Create a source pattern within the context.
|
||||||
|
4. Create paths, which are lines, rectangles, arcs, texts or more complicated shapes, to generate a mask.
|
||||||
|
5. Use drawing operator such as `cairo_stroke` to transfer the paint in the source to the destination.
|
||||||
|
6. Save the destination surface to a file if necessary.
|
||||||
|
|
||||||
First, We need to make the ui file of the widgets.
|
Here's a simple example code that draws a small square and save it as a png file.
|
||||||
The image in the previous subsection gives us the structure of the widgets.
|
|
||||||
Title bar, four buttons in the tool bar and two widgets textview and drawing area.
|
|
||||||
The ui file is as follows.
|
|
||||||
|
|
||||||
@@@include
|
@@@include
|
||||||
color/color.ui
|
misc/cairo.c
|
||||||
@@@
|
@@@
|
||||||
|
|
||||||
- 9-53: This part describes the tool bar which has four buttons, `Run`, `Open`, `Save` and `Close`.
|
- 1: Includes the header file of cairo.
|
||||||
This is similar to the toolbar of tfe text editor in [Section 8](sec8.src.md).
|
- 12: `cairo_image_surface_create` creates an image surface.
|
||||||
There are two differences.
|
`CAIRO_FORMAT_RGB24` is a constant which means that each pixel has red, green and blue data.
|
||||||
`Run` button replaces `New` button.
|
Each data has 8 bit quantity.
|
||||||
Signal element are added to each button object.
|
Modern displays have this type of color depth.
|
||||||
It has "name" attribute which is a signal name and "handler" attribute which is the name of its signal handler function.
|
Width and height are pixels and given as integers.
|
||||||
Options "-WI, --export-dynamic" CFLAG is necessary when you compile the application.
|
- 13: Creates cairo context.
|
||||||
You can achieve this by adding "export_dynamic: true" argument to executable function in `meson.build`.
|
The surface given as an argument will be the destination of the context.
|
||||||
And be careful that the handler must be defined without 'static' class.
|
- 17: `cairo_set_source_rgb` creates a source pattern, which is a solid white paint.
|
||||||
- 54-76: Puts GtkScrolledWindow and GtkDrawingArea into GtkBox.
|
The second to fourth argument is red, green and blue color depth respectively.
|
||||||
GtkBox has "homogeneous property with TRUE value, so the two children have the same width in the box.
|
Their type is float and the values are between zero and one.
|
||||||
TfeTextView is a child of GtkScrolledWindow.
|
(0,0,0) is black and (1,1,1) is white.
|
||||||
|
- 18: `cairo_paint` copies everywhere in the source to destination.
|
||||||
|
The destination is filled with white pixels by this command.
|
||||||
|
- 20: Sets the source color to black.
|
||||||
|
- 21: `cairo_set_line_width` set the width of lines.
|
||||||
|
In this case, the line width is set to two pixels.
|
||||||
|
(It is because the transformation is identity.
|
||||||
|
If the transformation isn't identity, for example scaling with the factor three, the actual width in destination will be six (2x3=6) pixels.)
|
||||||
|
- 22: Draws a rectangle (square).
|
||||||
|
The top-left coordinate is (width/2.0-20.0, height/2.0-20.0) and the width and height have the same length 40.0.
|
||||||
|
- 23: `cairo_stroke` transfer the source to destination through the rectangle in mask.
|
||||||
|
- 26: Outputs the image to a png file `rectangle.png`.
|
||||||
|
- 27: Destroys the context. At the same time the source is destroyed.
|
||||||
|
- 28: Destroys the destination surface.
|
||||||
|
|
||||||
The xml file for the resource compiler is almost same as before.
|
To compile this, type the following.
|
||||||
Just substitute "color" for "tfe".
|
|
||||||
|
$ gcc `pkg-config --cflags cairo` cairo.c `pkg-config --libs cairo`
|
||||||
|
|
||||||
|
![rectangle.png](misc/rectangle.png)
|
||||||
|
|
||||||
|
There are lots of documentations in [Cairo's website](https://www.cairographics.org/).
|
||||||
|
If you aren't familiar with cairo, it is strongly recommended to read the [tutorial](https://www.cairographics.org/tutorial/) in the website.
|
||||||
|
|
||||||
|
## GtkDrawingArea
|
||||||
|
|
||||||
|
The following is a very simple example.
|
||||||
|
|
||||||
@@@include
|
@@@include
|
||||||
color/color.gresource.xml
|
misc/da1.c
|
||||||
@@@
|
@@@
|
||||||
|
|
||||||
## Tfetextview.h, tfetextview.c and color.h
|
The function `main` is almost same as before.
|
||||||
|
The two functions `on_activate` and `draw_function` is important in this example.
|
||||||
|
|
||||||
First two files are the same as before.
|
- 16: Generates a GtkDrawingArea object.
|
||||||
Color.h just includes tfetextview.h.
|
- 20,21: Sets the width and height of the contents of the GtkDrawingArea widget.
|
||||||
|
These width and height is the size of the destination surface of the cairo context provided by the widget.
|
||||||
|
- 22: Sets a drawing function of the widget.
|
||||||
|
GtkDrawingArea widget uses the function to draw the contents of itself whenever its necessary.
|
||||||
|
For example, when a user drag a mouse pointer and resize a top level window, GtkDrawingArea also changes the size.
|
||||||
|
Then, the whole window needs to be redrawn.
|
||||||
|
|
||||||
@@@include
|
The drawing function has five parameters.
|
||||||
color/color.h
|
|
||||||
@@@
|
|
||||||
|
|
||||||
## Colorapplication.c
|
void drawing_function (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height,
|
||||||
|
gpointer user_data);
|
||||||
|
|
||||||
This is the main file.
|
The first parameter is the GtkDrawingArea widget which calls the drawing function.
|
||||||
It deals with:
|
However, you can't change any properties, for example `content-width` or `content-height`, in this function.
|
||||||
|
The second parameter is a cairo context given by the widget.
|
||||||
|
The destination surface of the context is connected to the contents of the widget.
|
||||||
|
What you draw to this surface will appear in the widget on the screen.
|
||||||
|
The third and fourth parameters are the size of the destination surface.
|
||||||
|
|
||||||
- Building widgets by GtkBuilder.
|
- 3-11: The drawing function.
|
||||||
- Seting a drawing function of GtkDrawingArea.
|
- 4-5: Sets the source to be white and paint the destination white.
|
||||||
And connecting a handler to "resize" signal on GtkDrawingArea.
|
- 7: Sets the line width to be 2.
|
||||||
- Implementing each call back functions.
|
- 8: Sets the source to be black.
|
||||||
Particularly, `Run` signal handler is the point in this program.
|
- 9: Adds a rectangle to the mask.
|
||||||
|
- 10: Draws the rectangle with black color to the destination.
|
||||||
|
|
||||||
The following is `colorapplication.c`.
|
Compile and run it, then a window with a black rectangle (square) appears.
|
||||||
|
Try resizing the window.
|
||||||
|
The square always appears at the center of the window because the drawing function is invoked every moment the window is resized.
|
||||||
|
|
||||||
@@@include
|
![Square in the window](../image/da1.png)
|
||||||
color/colorapplication.c
|
|
||||||
@@@
|
|
||||||
|
|
||||||
- 108-121: The function `main` is almost same as before but there are some differences.
|
|
||||||
The application ID is "com.github.ToshioCP.color".
|
|
||||||
`G_APPLICATION_FLAGS_NONE` is specified so no open signal handler is necessary.
|
|
||||||
- 86-106: Startup handler.
|
|
||||||
- 91-96: Builds widgets.
|
|
||||||
The pointers of the top window, TfeTextView and GtkDrawingArea objects are stored to static variables `win`, `tv` and `da` respectively.
|
|
||||||
This is because these objects are often used in handlers.
|
|
||||||
They never be rewritten so they're thread safe.
|
|
||||||
- 97: connects "resize" signal and the handler.
|
|
||||||
- 98: sets the drawing function.
|
|
||||||
- 81-84: Activates handler, which just shows the widgets.
|
|
||||||
- 73-79: The drawing function.
|
|
||||||
It just copies `surface` to destination.
|
|
||||||
- 65-71: Resize handler.
|
|
||||||
Re-creates the surface to fit the width and height of the drawing area and paints by calling the function `run`.
|
|
||||||
- 58-63: Closes the handler.
|
|
||||||
It destroys `surface` if it exists.
|
|
||||||
Then it destroys the top window and quits the application.
|
|
||||||
- 48-56: Open and save handler.
|
|
||||||
They just call the corresponding functions of TfeTextView.
|
|
||||||
- 42-46: Run handler.
|
|
||||||
It calls run function to paint the surface.
|
|
||||||
After that `gtk_widget_queue_draw` is called.
|
|
||||||
This fhunction adds the widget (GtkDrawingArea) to the queue to be redrawn.
|
|
||||||
It is important to know that the drawing function is called when it is necessary.
|
|
||||||
For example, when another window is moved and uncovers part of the widget, or when the window containing it is resized.
|
|
||||||
But repaint of `surface` is not automatically notified to gtk.
|
|
||||||
Therefore, you need to call `gtk_widget_queue_draw` to redraw the widget.
|
|
||||||
- 9-40: Run function paints the surface.
|
|
||||||
First, it gets the contents of GtkTextBuffer.
|
|
||||||
Then it compares it to "red", "green" and so on.
|
|
||||||
If it matches the color, then the surface is painted the color.
|
|
||||||
If it matches "light" or "dark", then the color of the surface is lightened or darkened respectively.
|
|
||||||
Alpha channel is used.
|
|
||||||
|
|
||||||
## Meson.build
|
|
||||||
|
|
||||||
This file is almost same as before.
|
|
||||||
An argument "export_dynamic: true" is added to executable function.
|
|
||||||
|
|
||||||
@@@include
|
|
||||||
color/meson.build
|
|
||||||
@@@
|
|
||||||
|
|
||||||
## Compile and execute it
|
|
||||||
|
|
||||||
First you need to export some variables (refer to [Section 2](sec2.src.md)).
|
|
||||||
|
|
||||||
$ . env.sh
|
|
||||||
|
|
||||||
Then type the following to compile it.
|
|
||||||
|
|
||||||
$ meson _build
|
|
||||||
$ ninja -C _build
|
|
||||||
|
|
||||||
The application is made in `_build` directory.
|
|
||||||
Type the following to execute it.
|
|
||||||
|
|
||||||
$ _build/color
|
|
||||||
|
|
||||||
Type "red", "green", "blue", "white", black", "light" or "dark" in the TfeTextView.
|
|
||||||
Then, click on `Run` button.
|
|
||||||
Make sure the color of GtkDrawingArea changes.
|
|
||||||
|
|
||||||
In this program TfeTextView is used to change the color.
|
|
||||||
You can use buttons or menus instead of textview.
|
|
||||||
Probably it is more appropriate.
|
|
||||||
Using textview is unnatural.
|
|
||||||
It is a good practice to make such application by yourself.
|
|
||||||
|
|
153
src/sec21.src.md
Normal file
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.
|
And there are two types of tags, start tag and end tag.
|
||||||
For example, `<interface>` is a start tag and `</interface>` is an end tag.
|
For example, `<interface>` is a start tag and `</interface>` is an end tag.
|
||||||
Ui file begins and ends with interface tags.
|
Ui file begins and ends with interface tags.
|
||||||
Some tags, for example, object tags can have a class and id attributes inside the start tag.
|
Some tags, for example, object tags can have a class and id attributes in the start tag.
|
||||||
|
|
||||||
- 2-5: An object with `GtkApplicationWindow` class and `win` id is defined.
|
- 1: The first line is XML declaration.
|
||||||
|
It specifies that the version of XML is 1.0 and the encoding is UTF-8.
|
||||||
|
Even if the line is left out, GtkBuilder builds objects from the ui file.
|
||||||
|
But ui files must use UTF-8 encoding, or GtkBuilder can't recognize it and fatal error occurs.
|
||||||
|
- 3-6: An object with `GtkApplicationWindow` class and `win` id is defined.
|
||||||
This is the top level window.
|
This is the top level window.
|
||||||
And the three properties of the window are defined.
|
And the three properties of the window are defined.
|
||||||
`title` property is "file editor", `default-width` property is 400 and `default-height` property is 300.
|
`title` property is "file editor", `default-width` property is 400 and `default-height` property is 300.
|
||||||
- 6: child tag means a child of the object above.
|
- 7: child tag means a child of the object above.
|
||||||
For example, line 7 tells us that GtkBox object which id is "boxv" is a child of `win`.
|
For example, line 7 tells us that GtkBox object which id is "boxv" is a child of `win`.
|
||||||
|
|
||||||
Compare this ui file and the lines 25-57 in the source code of `on_open` function.
|
Compare this ui file and the lines 25-57 in the source code of `on_open` function.
|
||||||
Those two describe the same structure of widgets.
|
Those two describe the same structure of widgets.
|
||||||
|
|
||||||
|
You can check the ui file with `gtk4-builder-tool`.
|
||||||
|
|
||||||
|
- `gtk4-builder-tool validate <ui file name>` validates the ui file.
|
||||||
|
If the ui file includes some syntactical error, `gtk4-builder-tool` prints the error.
|
||||||
|
- `gtk4-builder-tool simplify <ui file name>` simplifies the ui file and prints the result.
|
||||||
|
If `--replace` option is given, it replaces the ui file with the simplified one.
|
||||||
|
If the ui file specifies a value of property but it is default, then it will be removed.
|
||||||
|
Anf some values are simplified.
|
||||||
|
For example, "TRUE"and "FALSE" becomes "1" and "0" respectively.
|
||||||
|
However, "TRUE" or "FALSE" is better for maintenance.
|
||||||
|
|
||||||
|
It is a good idea to check your ui file before compiling.
|
||||||
|
|
||||||
## GtkBuilder
|
## GtkBuilder
|
||||||
|
|
||||||
GtkBuilder builds widgets based on the ui file.
|
GtkBuilder builds widgets based on the ui file.
|
||||||
|
|
|
@ -56,11 +56,7 @@ tfe4/tfetextview.c
|
||||||
tfe4/tfe.c
|
tfe4/tfe.c
|
||||||
@@@
|
@@@
|
||||||
|
|
||||||
`tfe.ui`
|
The ui file `tfe.ui` is the same as `tfe3.ui` in the previous section.
|
||||||
|
|
||||||
@@@include
|
|
||||||
tfe4/tfe.ui
|
|
||||||
@@@
|
|
||||||
|
|
||||||
`tfe.gresource.xml`
|
`tfe.gresource.xml`
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<interface>
|
<interface>
|
||||||
<object class="GtkApplicationWindow" id="win">
|
<object class="GtkApplicationWindow" id="win">
|
||||||
<property name="title">file editor</property>
|
<property name="title">file editor</property>
|
||||||
|
@ -8,40 +9,40 @@
|
||||||
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox" id="boxh">
|
<object class="GtkBox" id="boxh">
|
||||||
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel" id="dmy1">
|
<object class="GtkLabel" id="dmy1">
|
||||||
<property name="width-chars">10</property>
|
<property name="width-chars">10</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="btnn">
|
<object class="GtkButton" id="btnn">
|
||||||
<property name="label">New</property>
|
<property name="label">New</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="btno">
|
<object class="GtkButton" id="btno">
|
||||||
<property name="label">Open</property>
|
<property name="label">Open</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel" id="dmy2">
|
<object class="GtkLabel" id="dmy2">
|
||||||
<property name="hexpand">TRUE</property>
|
<property name="hexpand">TRUE</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="btns">
|
<object class="GtkButton" id="btns">
|
||||||
<property name="label">Save</property>
|
<property name="label">Save</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="btnc">
|
<object class="GtkButton" id="btnc">
|
||||||
<property name="label">Close</property>
|
<property name="label">Close</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel" id="dmy3">
|
<object class="GtkLabel" id="dmy3">
|
||||||
<property name="width-chars">10</property>
|
<property name="width-chars">10</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
@ -56,4 +57,3 @@
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</interface>
|
</interface>
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<interface>
|
<interface>
|
||||||
<object class="GtkApplicationWindow" id="win">
|
<object class="GtkApplicationWindow" id="win">
|
||||||
<property name="title">file editor</property>
|
<property name="title">file editor</property>
|
||||||
|
@ -8,40 +9,40 @@
|
||||||
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox" id="boxh">
|
<object class="GtkBox" id="boxh">
|
||||||
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel" id="dmy1">
|
<object class="GtkLabel" id="dmy1">
|
||||||
<property name="width-chars">10</property>
|
<property name="width-chars">10</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="btnn">
|
<object class="GtkButton" id="btnn">
|
||||||
<property name="label">New</property>
|
<property name="label">New</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="btno">
|
<object class="GtkButton" id="btno">
|
||||||
<property name="label">Open</property>
|
<property name="label">Open</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel" id="dmy2">
|
<object class="GtkLabel" id="dmy2">
|
||||||
<property name="hexpand">TRUE</property>
|
<property name="hexpand">TRUE</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="btns">
|
<object class="GtkButton" id="btns">
|
||||||
<property name="label">Save</property>
|
<property name="label">Save</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="btnc">
|
<object class="GtkButton" id="btnc">
|
||||||
<property name="label">Close</property>
|
<property name="label">Close</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel" id="dmy3">
|
<object class="GtkLabel" id="dmy3">
|
||||||
<property name="width-chars">10</property>
|
<property name="width-chars">10</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
@ -56,4 +57,3 @@
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</interface>
|
</interface>
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<interface>
|
<interface>
|
||||||
<object class="GtkApplicationWindow" id="win">
|
<object class="GtkApplicationWindow" id="win">
|
||||||
<property name="title">file editor</property>
|
<property name="title">file editor</property>
|
||||||
|
@ -8,44 +9,40 @@
|
||||||
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox" id="boxh">
|
<object class="GtkBox" id="boxh">
|
||||||
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel" id="dmy1">
|
<object class="GtkLabel" id="dmy1">
|
||||||
<property name="width-chars">10</property>
|
<property name="width-chars">10</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="btnn">
|
<object class="GtkButton" id="btnn">
|
||||||
<property name="label">_New</property>
|
<property name="label">New</property>
|
||||||
<property name="use-underline">TRUE</property>
|
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="btno">
|
<object class="GtkButton" id="btno">
|
||||||
<property name="label">_Open</property>
|
<property name="label">Open</property>
|
||||||
<property name="use-underline">TRUE</property>
|
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel" id="dmy2">
|
<object class="GtkLabel" id="dmy2">
|
||||||
<property name="hexpand">TRUE</property>
|
<property name="hexpand">TRUE</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="btns">
|
<object class="GtkButton" id="btns">
|
||||||
<property name="label">_Save</property>
|
<property name="label">Save</property>
|
||||||
<property name="use-underline">TRUE</property>
|
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="btnc">
|
<object class="GtkButton" id="btnc">
|
||||||
<property name="label">_Close</property>
|
<property name="label">Close</property>
|
||||||
<property name="use-underline">TRUE</property>
|
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel" id="dmy3">
|
<object class="GtkLabel" id="dmy3">
|
||||||
<property name="width-chars">10</property>
|
<property name="width-chars">10</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
|
|
@ -1,46 +1,31 @@
|
||||||
#include "tfe.h"
|
#include "tfe.h"
|
||||||
|
|
||||||
static void
|
static void
|
||||||
open_clicked (GtkWidget *btno, GtkNotebook *nb) {
|
open_cb (GtkNotebook *nb) {
|
||||||
notebook_page_open (nb);
|
notebook_page_open (nb);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
new_clicked (GtkWidget *btnn, GtkNotebook *nb) {
|
new_cb (GtkNotebook *nb) {
|
||||||
notebook_page_new (nb);
|
notebook_page_new (nb);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
save_clicked (GtkWidget *btns, GtkNotebook *nb) {
|
save_cb (GtkNotebook *nb) {
|
||||||
notebook_page_save (nb);
|
notebook_page_save (nb);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
close_clicked (GtkWidget *btnc, GtkNotebook *nb) {
|
close_cb (GtkNotebook *nb) {
|
||||||
GtkWidget *win;
|
notebook_page_close (GTK_NOTEBOOK (nb));
|
||||||
GtkWidget *boxv;
|
|
||||||
gint i;
|
|
||||||
|
|
||||||
if (gtk_notebook_get_n_pages (nb) == 1) {
|
|
||||||
boxv = gtk_widget_get_parent (GTK_WIDGET (nb));
|
|
||||||
win = gtk_widget_get_parent (boxv);
|
|
||||||
gtk_window_destroy (GTK_WINDOW (win));
|
|
||||||
} else {
|
|
||||||
i = gtk_notebook_get_current_page (nb);
|
|
||||||
gtk_notebook_remove_page (GTK_NOTEBOOK (nb), i);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
tfe_activate (GApplication *application) {
|
tfe_activate (GApplication *application) {
|
||||||
GtkApplication *app = GTK_APPLICATION (application);
|
GtkApplication *app = GTK_APPLICATION (application);
|
||||||
GtkWidget *win;
|
GtkWidget *win = GTK_WIDGET (gtk_application_get_active_window (app));
|
||||||
GtkWidget *boxv;
|
GtkWidget *boxv = gtk_window_get_child (GTK_WINDOW (win));
|
||||||
GtkNotebook *nb;
|
GtkNotebook *nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
|
||||||
|
|
||||||
win = GTK_WIDGET (gtk_application_get_active_window (app));
|
|
||||||
boxv = gtk_window_get_child (GTK_WINDOW (win));
|
|
||||||
nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
|
|
||||||
|
|
||||||
notebook_page_new (nb);
|
notebook_page_new (nb);
|
||||||
gtk_widget_show (GTK_WIDGET (win));
|
gtk_widget_show (GTK_WIDGET (win));
|
||||||
|
@ -49,15 +34,11 @@ tfe_activate (GApplication *application) {
|
||||||
static void
|
static void
|
||||||
tfe_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) {
|
tfe_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) {
|
||||||
GtkApplication *app = GTK_APPLICATION (application);
|
GtkApplication *app = GTK_APPLICATION (application);
|
||||||
GtkWidget *win;
|
GtkWidget *win = GTK_WIDGET (gtk_application_get_active_window (app));
|
||||||
GtkWidget *boxv;
|
GtkWidget *boxv = gtk_window_get_child (GTK_WINDOW (win));
|
||||||
GtkNotebook *nb;
|
GtkNotebook *nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
win = GTK_WIDGET (gtk_application_get_active_window (app));
|
|
||||||
boxv = gtk_window_get_child (GTK_WINDOW (win));
|
|
||||||
nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
|
|
||||||
|
|
||||||
for (i = 0; i < n_files; i++)
|
for (i = 0; i < n_files; i++)
|
||||||
notebook_page_new_with_file (nb, files[i]);
|
notebook_page_new_with_file (nb, files[i]);
|
||||||
if (gtk_notebook_get_n_pages (nb) == 0)
|
if (gtk_notebook_get_n_pages (nb) == 0)
|
||||||
|
@ -65,13 +46,12 @@ tfe_open (GApplication *application, GFile ** files, gint n_files, const gchar *
|
||||||
gtk_widget_show (win);
|
gtk_widget_show (win);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
tfe_startup (GApplication *application) {
|
tfe_startup (GApplication *application) {
|
||||||
GtkApplication *app = GTK_APPLICATION (application);
|
GtkApplication *app = GTK_APPLICATION (application);
|
||||||
|
GtkBuilder *build;
|
||||||
GtkApplicationWindow *win;
|
GtkApplicationWindow *win;
|
||||||
GtkNotebook *nb;
|
GtkNotebook *nb;
|
||||||
GtkBuilder *build;
|
|
||||||
GtkButton *btno;
|
GtkButton *btno;
|
||||||
GtkButton *btnn;
|
GtkButton *btnn;
|
||||||
GtkButton *btns;
|
GtkButton *btns;
|
||||||
|
@ -85,10 +65,10 @@ tfe_startup (GApplication *application) {
|
||||||
btnn = GTK_BUTTON (gtk_builder_get_object (build, "btnn"));
|
btnn = GTK_BUTTON (gtk_builder_get_object (build, "btnn"));
|
||||||
btns = GTK_BUTTON (gtk_builder_get_object (build, "btns"));
|
btns = GTK_BUTTON (gtk_builder_get_object (build, "btns"));
|
||||||
btnc = GTK_BUTTON (gtk_builder_get_object (build, "btnc"));
|
btnc = GTK_BUTTON (gtk_builder_get_object (build, "btnc"));
|
||||||
g_signal_connect (btno, "clicked", G_CALLBACK (open_clicked), nb);
|
g_signal_connect_swapped (btno, "clicked", G_CALLBACK (open_cb), nb);
|
||||||
g_signal_connect (btnn, "clicked", G_CALLBACK (new_clicked), nb);
|
g_signal_connect_swapped (btnn, "clicked", G_CALLBACK (new_cb), nb);
|
||||||
g_signal_connect (btns, "clicked", G_CALLBACK (save_clicked), nb);
|
g_signal_connect_swapped (btns, "clicked", G_CALLBACK (save_cb), nb);
|
||||||
g_signal_connect (btnc, "clicked", G_CALLBACK (close_clicked), nb);
|
g_signal_connect_swapped (btnc, "clicked", G_CALLBACK (close_cb), nb);
|
||||||
g_object_unref(build);
|
g_object_unref(build);
|
||||||
|
|
||||||
GdkDisplay *display;
|
GdkDisplay *display;
|
||||||
|
|
|
@ -10,45 +10,68 @@ get_untitled () {
|
||||||
return g_strdup_printf ("Untitled%u", c);
|
return g_strdup_printf ("Untitled%u", c);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static TfeTextView *
|
||||||
file_changed (TfeTextView *tv, GtkNotebook *nb) {
|
get_current_textview (GtkNotebook *nb) {
|
||||||
GFile *file;
|
int i;
|
||||||
char *filename;
|
|
||||||
GtkWidget *scr;
|
|
||||||
GtkWidget *label;
|
|
||||||
|
|
||||||
file = tfe_text_view_get_file (tv);
|
|
||||||
scr = gtk_widget_get_parent (GTK_WIDGET (tv));
|
|
||||||
if (G_IS_FILE (file))
|
|
||||||
filename = g_file_get_basename (file);
|
|
||||||
else
|
|
||||||
filename = get_untitled ();
|
|
||||||
label = gtk_label_new (filename);
|
|
||||||
gtk_notebook_set_tab_label (nb, scr, label);
|
|
||||||
g_object_unref (file);
|
|
||||||
g_free (filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Save the contents in the current page */
|
|
||||||
void
|
|
||||||
notebook_page_save(GtkNotebook *nb) {
|
|
||||||
gint i;
|
|
||||||
GtkWidget *scr;
|
GtkWidget *scr;
|
||||||
GtkWidget *tv;
|
GtkWidget *tv;
|
||||||
|
|
||||||
i = gtk_notebook_get_current_page (nb);
|
i = gtk_notebook_get_current_page (nb);
|
||||||
scr = gtk_notebook_get_nth_page (nb, i);
|
scr = gtk_notebook_get_nth_page (nb, i);
|
||||||
tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
|
tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
|
||||||
|
return TFE_TEXT_VIEW (tv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
file_changed_cb (TfeTextView *tv, GtkNotebook *nb) {
|
||||||
|
GtkWidget *scr;
|
||||||
|
GtkWidget *label;
|
||||||
|
GFile *file;
|
||||||
|
char *filename;
|
||||||
|
|
||||||
|
file = tfe_text_view_get_file (tv);
|
||||||
|
scr = gtk_widget_get_parent (GTK_WIDGET (tv));
|
||||||
|
if (G_IS_FILE (file)) {
|
||||||
|
filename = g_file_get_basename (file);
|
||||||
|
g_object_unref (file);
|
||||||
|
} else
|
||||||
|
filename = get_untitled ();
|
||||||
|
label = gtk_label_new (filename);
|
||||||
|
gtk_notebook_set_tab_label (nb, scr, label);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
notebook_page_save (GtkNotebook *nb) {
|
||||||
|
g_return_if_fail(GTK_IS_NOTEBOOK (nb));
|
||||||
|
|
||||||
|
TfeTextView *tv;
|
||||||
|
|
||||||
|
tv = get_current_textview (nb);
|
||||||
tfe_text_view_save (TFE_TEXT_VIEW (tv));
|
tfe_text_view_save (TFE_TEXT_VIEW (tv));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
notebook_page_close (GtkNotebook *nb) {
|
||||||
|
g_return_if_fail(GTK_IS_NOTEBOOK (nb));
|
||||||
|
|
||||||
|
GtkWidget *win;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (gtk_notebook_get_n_pages (nb) == 1) {
|
||||||
|
win = gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW);
|
||||||
|
gtk_window_destroy(GTK_WINDOW (win));
|
||||||
|
} else {
|
||||||
|
i = gtk_notebook_get_current_page (nb);
|
||||||
|
gtk_notebook_remove_page (GTK_NOTEBOOK (nb), i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) {
|
notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) {
|
||||||
GtkWidget *scr;
|
GtkWidget *scr = gtk_scrolled_window_new ();
|
||||||
GtkNotebookPage *nbp;
|
GtkNotebookPage *nbp;
|
||||||
GtkWidget *lab;
|
GtkWidget *lab;
|
||||||
gint i;
|
int i;
|
||||||
scr = gtk_scrolled_window_new ();
|
|
||||||
|
|
||||||
gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
|
gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
|
||||||
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
|
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
|
||||||
|
@ -57,11 +80,11 @@ notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) {
|
||||||
nbp = gtk_notebook_get_page (nb, scr);
|
nbp = gtk_notebook_get_page (nb, scr);
|
||||||
g_object_set (nbp, "tab-expand", TRUE, NULL);
|
g_object_set (nbp, "tab-expand", TRUE, NULL);
|
||||||
gtk_notebook_set_current_page (nb, i);
|
gtk_notebook_set_current_page (nb, i);
|
||||||
g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed), nb);
|
g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed_cb), nb);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
open_response (TfeTextView *tv, gint response, GtkNotebook *nb) {
|
open_response (TfeTextView *tv, int response, GtkNotebook *nb) {
|
||||||
GFile *file;
|
GFile *file;
|
||||||
char *filename;
|
char *filename;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
void
|
void
|
||||||
notebook_page_save(GtkNotebook *nb);
|
notebook_page_save(GtkNotebook *nb);
|
||||||
|
|
||||||
|
void
|
||||||
|
notebook_page_close (GtkNotebook *nb);
|
||||||
|
|
||||||
void
|
void
|
||||||
notebook_page_open (GtkNotebook *nb);
|
notebook_page_open (GtkNotebook *nb);
|
||||||
|
|
||||||
|
|
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) {
|
tfe_text_view_get_file (TfeTextView *tv) {
|
||||||
g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);
|
g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);
|
||||||
|
|
||||||
return g_file_dup (tv->file);
|
if (G_IS_FILE (tv->file))
|
||||||
|
return g_file_dup (tv->file);
|
||||||
|
else
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static gboolean
|
||||||
open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
|
save_file (GFile *file, GtkTextBuffer *tb, GtkWindow *win) {
|
||||||
GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
GtkTextIter start_iter;
|
||||||
GFile *file;
|
GtkTextIter end_iter;
|
||||||
char *contents;
|
gchar *contents;
|
||||||
gsize length;
|
|
||||||
GtkWidget *message_dialog;
|
GtkWidget *message_dialog;
|
||||||
GError *err = NULL;
|
GError *err = NULL;
|
||||||
|
|
||||||
if (response != GTK_RESPONSE_ACCEPT)
|
/* This function doesn't check G_IS_FILE (file). The caller should check it. */
|
||||||
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
|
gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
|
||||||
else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog))))
|
contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
|
||||||
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
|
if (g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err)) {
|
||||||
else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
|
gtk_text_buffer_set_modified (tb, FALSE);
|
||||||
if (G_IS_FILE (file))
|
return TRUE;
|
||||||
g_object_unref (file);
|
} else {
|
||||||
message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
|
message_dialog = gtk_message_dialog_new (win, GTK_DIALOG_MODAL,
|
||||||
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
|
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
|
||||||
"%s.\n", err->message);
|
"%s.\n", err->message);
|
||||||
g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
|
g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
|
||||||
gtk_widget_show (message_dialog);
|
gtk_widget_show (message_dialog);
|
||||||
g_error_free (err);
|
g_error_free (err);
|
||||||
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
|
return FALSE;
|
||||||
} else {
|
|
||||||
gtk_text_buffer_set_text (tb, contents, length);
|
|
||||||
g_free (contents);
|
|
||||||
if (G_IS_FILE (tv->file))
|
|
||||||
g_object_unref (tv->file);
|
|
||||||
tv->file = file;
|
|
||||||
gtk_text_buffer_set_modified (tb, FALSE);
|
|
||||||
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
|
|
||||||
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
|
|
||||||
}
|
}
|
||||||
gtk_window_destroy (GTK_WINDOW (dialog));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
tfe_text_view_open (TfeTextView *tv, GtkWidget *win) {
|
|
||||||
g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
|
|
||||||
g_return_if_fail (GTK_IS_WINDOW (win));
|
|
||||||
|
|
||||||
GtkWidget *dialog;
|
|
||||||
|
|
||||||
dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN,
|
|
||||||
"Cancel", GTK_RESPONSE_CANCEL,
|
|
||||||
"Open", GTK_RESPONSE_ACCEPT,
|
|
||||||
NULL);
|
|
||||||
g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
|
|
||||||
gtk_widget_show (dialog);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
|
saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
|
||||||
GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
||||||
GFile *file;
|
GFile *file;
|
||||||
|
GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
|
||||||
|
|
||||||
|
gtk_window_destroy (GTK_WINDOW (dialog));
|
||||||
if (response == GTK_RESPONSE_ACCEPT) {
|
if (response == GTK_RESPONSE_ACCEPT) {
|
||||||
file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
|
file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
|
||||||
if (G_IS_FILE(file)) {
|
if (! G_IS_FILE (file))
|
||||||
|
g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n");
|
||||||
|
else {
|
||||||
|
save_file(file, tb, GTK_WINDOW (win));
|
||||||
if (G_IS_FILE (tv->file))
|
if (G_IS_FILE (tv->file))
|
||||||
g_object_unref (tv->file);
|
g_object_unref (tv->file);
|
||||||
tv->file = file;
|
tv->file = file;
|
||||||
gtk_text_buffer_set_modified (tb, TRUE);
|
gtk_text_buffer_set_modified (tb, FALSE);
|
||||||
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
|
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
|
||||||
tfe_text_view_save (TFE_TEXT_VIEW (tv));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gtk_window_destroy (GTK_WINDOW (dialog));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -142,36 +122,17 @@ tfe_text_view_save (TfeTextView *tv) {
|
||||||
g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
|
g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
|
||||||
|
|
||||||
GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
||||||
GtkTextIter start_iter;
|
|
||||||
GtkTextIter end_iter;
|
|
||||||
gchar *contents;
|
|
||||||
GtkWidget *message_dialog;
|
|
||||||
GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
|
GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
|
||||||
GError *err = NULL;
|
|
||||||
|
|
||||||
if (! gtk_text_buffer_get_modified (tb))
|
if (! gtk_text_buffer_get_modified (tb))
|
||||||
return; /* no need to save it */
|
return; /* no need to save it */
|
||||||
else if (tv->file == NULL)
|
else if (tv->file == NULL)
|
||||||
tfe_text_view_saveas (tv);
|
tfe_text_view_saveas (tv);
|
||||||
|
else if (! G_IS_FILE (tv->file))
|
||||||
|
g_error ("TfeTextView: The pointer in this object isn't NULL nor GFile object.\n");
|
||||||
else {
|
else {
|
||||||
gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
|
if (save_file (tv->file, tb, GTK_WINDOW (win)))
|
||||||
contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
|
|
||||||
if (g_file_replace_contents (tv->file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err))
|
|
||||||
gtk_text_buffer_set_modified (tb, FALSE);
|
gtk_text_buffer_set_modified (tb, FALSE);
|
||||||
else {
|
|
||||||
/* It is possible that tv->file is broken or you don't have permission to write. */
|
|
||||||
/* It is a good idea to set tv->file to NULL. */
|
|
||||||
if (G_IS_FILE (tv->file))
|
|
||||||
g_object_unref (tv->file);
|
|
||||||
tv->file =NULL;
|
|
||||||
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
|
|
||||||
message_dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL,
|
|
||||||
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
|
|
||||||
"%s.\n", err->message);
|
|
||||||
g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
|
|
||||||
gtk_widget_show (message_dialog);
|
|
||||||
g_error_free (err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,6 +171,58 @@ tfe_text_view_new_with_file (GFile *file) {
|
||||||
return tv;
|
return tv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
|
||||||
|
GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
||||||
|
GFile *file;
|
||||||
|
char *contents;
|
||||||
|
gsize length;
|
||||||
|
GtkWidget *message_dialog;
|
||||||
|
GError *err = NULL;
|
||||||
|
|
||||||
|
if (response != GTK_RESPONSE_ACCEPT)
|
||||||
|
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
|
||||||
|
else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)))) {
|
||||||
|
g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n");
|
||||||
|
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
|
||||||
|
} else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
|
||||||
|
if (G_IS_FILE (file))
|
||||||
|
g_object_unref (file);
|
||||||
|
message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
|
||||||
|
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
|
||||||
|
"%s.\n", err->message);
|
||||||
|
g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
|
||||||
|
gtk_widget_show (message_dialog);
|
||||||
|
g_error_free (err);
|
||||||
|
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
|
||||||
|
} else {
|
||||||
|
gtk_text_buffer_set_text (tb, contents, length);
|
||||||
|
g_free (contents);
|
||||||
|
if (G_IS_FILE (tv->file))
|
||||||
|
g_object_unref (tv->file);
|
||||||
|
tv->file = file;
|
||||||
|
gtk_text_buffer_set_modified (tb, FALSE);
|
||||||
|
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
|
||||||
|
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
|
||||||
|
}
|
||||||
|
gtk_window_destroy (GTK_WINDOW (dialog));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
tfe_text_view_open (TfeTextView *tv, GtkWidget *win) {
|
||||||
|
g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
|
||||||
|
g_return_if_fail (GTK_IS_WINDOW (win));
|
||||||
|
|
||||||
|
GtkWidget *dialog;
|
||||||
|
|
||||||
|
dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||||
|
"Cancel", GTK_RESPONSE_CANCEL,
|
||||||
|
"Open", GTK_RESPONSE_ACCEPT,
|
||||||
|
NULL);
|
||||||
|
g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
|
||||||
|
gtk_widget_show (dialog);
|
||||||
|
}
|
||||||
|
|
||||||
GtkWidget *
|
GtkWidget *
|
||||||
tfe_text_view_new (void) {
|
tfe_text_view_new (void) {
|
||||||
return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
|
return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
|
||||||
|
|
Loading…
Reference in a new issue