Up: [Readme.md](../Readme.md), Prev: [Section 14](sec14.md), Next: [Section 16](sec16.md) # tfe5 source files ## How to compile and execute tfe text editor. First, source files are shown in the later subsections. How to download them is written at the end of the [previous section](../src/sec14.src.md). The following is the instruction of compilation and execution. - You need meson and ninja. - Set necessary environment variables. If you have installed gtk4 under the instruction in [Section 2](sec2.md), type `. env.sh` to set the environment variables. - change your current directory to `src/tfe5` directory. - type `meson _build` for configuration. - type `ninja -C _build` for compilation. Then the application `tfe` is build under the `_build` directory. - type `_build/tfe` to execute it. Then the window appears. There are four buttons, `New`, `Open`, `Save` and `Close`. - Click on `Open` button, then a FileChooserDialog appears. Choose a file in the list and click on `Open` button. Then the file is read and a new Notebook Page appears. - Edit the file and click on `Save` button, then the text is saved to the original file. - Click `Close`, then the Notebook Page disappears. - Click `Close` again, then the `Untitled` Notebook Page disappears and at the same time the application quits. This is a very simple editor. It is a good practice for you to add more features. ## meson.build ~~~meson 1 project('tfe', 'c') 2 3 gtkdep = dependency('gtk4') 4 5 gnome=import('gnome') 6 resources = gnome.compile_resources('resources','tfe.gresource.xml') 7 8 sourcefiles=files('tfeapplication.c', 'tfenotebook.c', '../tfetextview/tfetextview.c') 9 10 executable('tfe', sourcefiles, resources, dependencies: gtkdep) ~~~ ## tfe.gresource.xml ~~~xml 1 2 3 4 tfe.ui 5 6 ~~~ ## tfe.ui ~~~xml 1 2 3 file editor 4 600 5 400 6 7 8 GTK_ORIENTATION_VERTICAL 9 10 11 GTK_ORIENTATION_HORIZONTAL 12 13 14 10 15 16 17 18 19 _New 20 TRUE 21 22 23 24 25 _Open 26 TRUE 27 28 29 30 31 TRUE 32 33 34 35 36 _Save 37 TRUE 38 39 40 41 42 _Close 43 TRUE 44 45 46 47 48 10 49 50 51 52 53 54 55 TRUE 56 TRUE 57 TRUE 58 59 60 61 62 63 64 ~~~ ## tfe.h ~~~C 1 #include 2 3 #include "../tfetextview/tfetextview.h" 4 #include "tfenotebook.h" ~~~ ## tfeapplication.c ~~~C 1 #include "tfe.h" 2 3 static void 4 open_clicked (GtkWidget *btno, GtkNotebook *nb) { 5 notebook_page_open (nb); 6 } 7 8 static void 9 new_clicked (GtkWidget *btnn, GtkNotebook *nb) { 10 notebook_page_new (nb); 11 } 12 13 static void 14 save_clicked (GtkWidget *btns, GtkNotebook *nb) { 15 notebook_page_save (nb); 16 } 17 18 static void 19 close_clicked (GtkWidget *btnc, GtkNotebook *nb) { 20 GtkWidget *win; 21 GtkWidget *boxv; 22 gint i; 23 24 if (gtk_notebook_get_n_pages (nb) == 1) { 25 boxv = gtk_widget_get_parent (GTK_WIDGET (nb)); 26 win = gtk_widget_get_parent (boxv); 27 gtk_window_destroy (GTK_WINDOW (win)); 28 } else { 29 i = gtk_notebook_get_current_page (nb); 30 gtk_notebook_remove_page (GTK_NOTEBOOK (nb), i); 31 } 32 } 33 34 static void 35 tfe_activate (GApplication *application) { 36 GtkApplication *app = GTK_APPLICATION (application); 37 GtkWidget *win; 38 GtkWidget *boxv; 39 GtkNotebook *nb; 40 41 win = GTK_WIDGET (gtk_application_get_active_window (app)); 42 boxv = gtk_window_get_child (GTK_WINDOW (win)); 43 nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv)); 44 45 notebook_page_new (nb); 46 gtk_widget_show (GTK_WIDGET (win)); 47 } 48 49 static void 50 tfe_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) { 51 GtkApplication *app = GTK_APPLICATION (application); 52 GtkWidget *win; 53 GtkWidget *boxv; 54 GtkNotebook *nb; 55 int i; 56 57 win = GTK_WIDGET (gtk_application_get_active_window (app)); 58 boxv = gtk_window_get_child (GTK_WINDOW (win)); 59 nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv)); 60 61 for (i = 0; i < n_files; i++) 62 notebook_page_new_with_file (nb, files[i]); 63 if (gtk_notebook_get_n_pages (nb) == 0) 64 notebook_page_new (nb); 65 gtk_widget_show (win); 66 } 67 68 69 static void 70 tfe_startup (GApplication *application) { 71 GtkApplication *app = GTK_APPLICATION (application); 72 GtkApplicationWindow *win; 73 GtkNotebook *nb; 74 GtkBuilder *build; 75 GtkButton *btno; 76 GtkButton *btnn; 77 GtkButton *btns; 78 GtkButton *btnc; 79 80 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe/tfe.ui"); 81 win = GTK_APPLICATION_WINDOW (gtk_builder_get_object (build, "win")); 82 nb = GTK_NOTEBOOK (gtk_builder_get_object (build, "nb")); 83 gtk_window_set_application (GTK_WINDOW (win), app); 84 btno = GTK_BUTTON (gtk_builder_get_object (build, "btno")); 85 btnn = GTK_BUTTON (gtk_builder_get_object (build, "btnn")); 86 btns = GTK_BUTTON (gtk_builder_get_object (build, "btns")); 87 btnc = GTK_BUTTON (gtk_builder_get_object (build, "btnc")); 88 g_signal_connect (btno, "clicked", G_CALLBACK (open_clicked), nb); 89 g_signal_connect (btnn, "clicked", G_CALLBACK (new_clicked), nb); 90 g_signal_connect (btns, "clicked", G_CALLBACK (save_clicked), nb); 91 g_signal_connect (btnc, "clicked", G_CALLBACK (close_clicked), nb); 92 g_object_unref(build); 93 94 GdkDisplay *display; 95 96 display = gtk_widget_get_display (GTK_WIDGET (win)); 97 GtkCssProvider *provider = gtk_css_provider_new (); 98 gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1); 99 gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER); 100 } 101 102 int 103 main (int argc, char **argv) { 104 GtkApplication *app; 105 int stat; 106 107 app = gtk_application_new ("com.github.ToshioCP.tfe", G_APPLICATION_HANDLES_OPEN); 108 109 g_signal_connect (app, "startup", G_CALLBACK (tfe_startup), NULL); 110 g_signal_connect (app, "activate", G_CALLBACK (tfe_activate), NULL); 111 g_signal_connect (app, "open", G_CALLBACK (tfe_open), NULL); 112 113 stat =g_application_run (G_APPLICATION (app), argc, argv); 114 g_object_unref (app); 115 return stat; 116 } 117 ~~~ ## tfenotebook.h ~~~C 1 void 2 notebook_page_save(GtkNotebook *nb); 3 4 void 5 notebook_page_open (GtkNotebook *nb); 6 7 void 8 notebook_page_new_with_file (GtkNotebook *nb, GFile *file); 9 10 void 11 notebook_page_new (GtkNotebook *nb); 12 ~~~ ## tfenotebook.c ~~~C 1 #include "tfe.h" 2 3 /* The returned string should be freed with g_free() when no longer needed. */ 4 static gchar* 5 get_untitled () { 6 static int c = -1; 7 if (++c == 0) 8 return g_strdup_printf("Untitled"); 9 else 10 return g_strdup_printf ("Untitled%u", c); 11 } 12 13 static void 14 file_changed (TfeTextView *tv, GtkNotebook *nb) { 15 GFile *file; 16 char *filename; 17 GtkWidget *scr; 18 GtkWidget *label; 19 20 file = tfe_text_view_get_file (tv); 21 scr = gtk_widget_get_parent (GTK_WIDGET (tv)); 22 if (G_IS_FILE (file)) 23 filename = g_file_get_basename (file); 24 else 25 filename = get_untitled (); 26 label = gtk_label_new (filename); 27 gtk_notebook_set_tab_label (nb, scr, label); 28 g_object_unref (file); 29 g_free (filename); 30 } 31 32 /* Save the contents in the current page */ 33 void 34 notebook_page_save(GtkNotebook *nb) { 35 gint i; 36 GtkWidget *scr; 37 GtkWidget *tv; 38 39 i = gtk_notebook_get_current_page (nb); 40 scr = gtk_notebook_get_nth_page (nb, i); 41 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr)); 42 tfe_text_view_save (TFE_TEXT_VIEW (tv)); 43 } 44 45 static void 46 notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) { 47 GtkWidget *scr; 48 GtkNotebookPage *nbp; 49 GtkWidget *lab; 50 gint i; 51 scr = gtk_scrolled_window_new (); 52 53 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR); 54 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv); 55 lab = gtk_label_new (filename); 56 i = gtk_notebook_append_page (nb, scr, lab); 57 nbp = gtk_notebook_get_page (nb, scr); 58 g_object_set (nbp, "tab-expand", TRUE, NULL); 59 gtk_notebook_set_current_page (nb, i); 60 g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed), nb); 61 } 62 63 static void 64 open_response (TfeTextView *tv, gint response, GtkNotebook *nb) { 65 GFile *file; 66 char *filename; 67 68 if (response != TFE_OPEN_RESPONSE_SUCCESS) { 69 g_object_ref_sink (tv); 70 g_object_unref (tv); 71 }else if (! G_IS_FILE (file = tfe_text_view_get_file (tv))) { 72 g_object_ref_sink (tv); 73 g_object_unref (tv); 74 }else { 75 filename = g_file_get_basename (file); 76 g_object_unref (file); 77 notebook_page_build (nb, GTK_WIDGET (tv), filename); 78 } 79 } 80 81 void 82 notebook_page_open (GtkNotebook *nb) { 83 g_return_if_fail(GTK_IS_NOTEBOOK (nb)); 84 85 GtkWidget *tv; 86 87 tv = tfe_text_view_new (); 88 g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb); 89 tfe_text_view_open (TFE_TEXT_VIEW (tv), gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW)); 90 } 91 92 void 93 notebook_page_new_with_file (GtkNotebook *nb, GFile *file) { 94 g_return_if_fail(GTK_IS_NOTEBOOK (nb)); 95 g_return_if_fail(G_IS_FILE (file)); 96 97 GtkWidget *tv; 98 char *filename; 99 100 if ((tv = tfe_text_view_new_with_file (file)) == NULL) 101 return; /* read error */ 102 filename = g_file_get_basename (file); 103 notebook_page_build (nb, tv, filename); 104 } 105 106 void 107 notebook_page_new (GtkNotebook *nb) { 108 g_return_if_fail(GTK_IS_NOTEBOOK (nb)); 109 110 GtkWidget *tv; 111 char *filename; 112 113 tv = tfe_text_view_new (); 114 filename = get_untitled (); 115 notebook_page_build (nb, tv, filename); 116 } 117 ~~~ ## tfetextview.h ~~~C 1 #ifndef __TFE_TEXT_VIEW_H__ 2 #define __TFE_TEXT_VIEW_H__ 3 4 #include 5 6 #define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type () 7 G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView) 8 9 /* "open-response" signal response */ 10 enum TfeTextViewOpenResponseType 11 { 12 TFE_OPEN_RESPONSE_SUCCESS, 13 TFE_OPEN_RESPONSE_CANCEL, 14 TFE_OPEN_RESPONSE_ERROR 15 }; 16 17 GFile * 18 tfe_text_view_get_file (TfeTextView *tv); 19 20 void 21 tfe_text_view_open (TfeTextView *tv, GtkWidget *win); 22 23 void 24 tfe_text_view_save (TfeTextView *tv); 25 26 void 27 tfe_text_view_saveas (TfeTextView *tv); 28 29 GtkWidget * 30 tfe_text_view_new_with_file (GFile *file); 31 32 GtkWidget * 33 tfe_text_view_new (void); 34 35 #endif /* __TFE_TEXT_VIEW_H__ */ ~~~ ## tfetextview.c ~~~C 1 #include 2 #include "tfetextview.h" 3 4 struct _TfeTextView 5 { 6 GtkTextView parent; 7 GFile *file; 8 }; 9 10 G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW); 11 12 enum { 13 CHANGE_FILE, 14 OPEN_RESPONSE, 15 NUMBER_OF_SIGNALS 16 }; 17 18 static guint tfe_text_view_signals[NUMBER_OF_SIGNALS]; 19 20 static void 21 tfe_text_view_dispose (GObject *gobject) { 22 TfeTextView *tv = TFE_TEXT_VIEW (gobject); 23 24 if (G_IS_FILE (tv->file)) 25 g_clear_object (&tv->file); 26 27 G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject); 28 } 29 30 static void 31 tfe_text_view_init (TfeTextView *tv) { 32 tv->file = NULL; 33 } 34 35 static void 36 tfe_text_view_class_init (TfeTextViewClass *class) { 37 GObjectClass *object_class = G_OBJECT_CLASS (class); 38 39 object_class->dispose = tfe_text_view_dispose; 40 tfe_text_view_signals[CHANGE_FILE] = g_signal_newv ("change-file", 41 G_TYPE_FROM_CLASS (class), 42 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 43 NULL /* closure */, 44 NULL /* accumulator */, 45 NULL /* accumulator data */, 46 NULL /* C marshaller */, 47 G_TYPE_NONE /* return_type */, 48 0 /* n_params */, 49 NULL /* param_types */); 50 GType param_types[] = {G_TYPE_INT}; 51 tfe_text_view_signals[OPEN_RESPONSE] = g_signal_newv ("open-response", 52 G_TYPE_FROM_CLASS (class), 53 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 54 NULL /* closure */, 55 NULL /* accumulator */, 56 NULL /* accumulator data */, 57 NULL /* C marshaller */, 58 G_TYPE_NONE /* return_type */, 59 1 /* n_params */, 60 param_types); 61 } 62 63 GFile * 64 tfe_text_view_get_file (TfeTextView *tv) { 65 g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL); 66 67 return g_file_dup (tv->file); 68 } 69 70 static void 71 open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) { 72 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); 73 GFile *file; 74 char *contents; 75 gsize length; 76 GtkWidget *message_dialog; 77 GError *err = NULL; 78 79 if (response != GTK_RESPONSE_ACCEPT) 80 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL); 81 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)))) 82 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR); 83 else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */ 84 if (G_IS_FILE (file)) 85 g_object_unref (file); 86 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL, 87 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, 88 "%s.\n", err->message); 89 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL); 90 gtk_widget_show (message_dialog); 91 g_error_free (err); 92 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR); 93 } else { 94 gtk_text_buffer_set_text (tb, contents, length); 95 g_free (contents); 96 if (G_IS_FILE (tv->file)) 97 g_object_unref (tv->file); 98 tv->file = file; 99 gtk_text_buffer_set_modified (tb, FALSE); 100 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS); 101 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0); 102 } 103 gtk_window_destroy (GTK_WINDOW (dialog)); 104 } 105 106 void 107 tfe_text_view_open (TfeTextView *tv, GtkWidget *win) { 108 g_return_if_fail (TFE_IS_TEXT_VIEW (tv)); 109 g_return_if_fail (GTK_IS_WINDOW (win)); 110 111 GtkWidget *dialog; 112 113 dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN, 114 "Cancel", GTK_RESPONSE_CANCEL, 115 "Open", GTK_RESPONSE_ACCEPT, 116 NULL); 117 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv); 118 gtk_widget_show (dialog); 119 } 120 121 static void 122 saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) { 123 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); 124 GFile *file; 125 126 if (response == GTK_RESPONSE_ACCEPT) { 127 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); 128 if (G_IS_FILE(file)) { 129 if (G_IS_FILE (tv->file)) 130 g_object_unref (tv->file); 131 tv->file = file; 132 gtk_text_buffer_set_modified (tb, TRUE); 133 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0); 134 tfe_text_view_save (TFE_TEXT_VIEW (tv)); 135 } 136 } 137 gtk_window_destroy (GTK_WINDOW (dialog)); 138 } 139 140 void 141 tfe_text_view_save (TfeTextView *tv) { 142 g_return_if_fail (TFE_IS_TEXT_VIEW (tv)); 143 144 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); 145 GtkTextIter start_iter; 146 GtkTextIter end_iter; 147 gchar *contents; 148 GtkWidget *message_dialog; 149 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW); 150 GError *err = NULL; 151 152 if (! gtk_text_buffer_get_modified (tb)) 153 return; /* no need to save it */ 154 else if (tv->file == NULL) 155 tfe_text_view_saveas (tv); 156 else { 157 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter); 158 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE); 159 if (g_file_replace_contents (tv->file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err)) 160 gtk_text_buffer_set_modified (tb, FALSE); 161 else { 162 /* It is possible that tv->file is broken or you don't have permission to write. */ 163 /* It is a good idea to set tv->file to NULL. */ 164 if (G_IS_FILE (tv->file)) 165 g_object_unref (tv->file); 166 tv->file =NULL; 167 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0); 168 message_dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL, 169 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, 170 "%s.\n", err->message); 171 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL); 172 gtk_widget_show (message_dialog); 173 g_error_free (err); 174 } 175 } 176 } 177 178 void 179 tfe_text_view_saveas (TfeTextView *tv) { 180 g_return_if_fail (TFE_IS_TEXT_VIEW (tv)); 181 182 GtkWidget *dialog; 183 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW); 184 185 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE, 186 "Cancel", GTK_RESPONSE_CANCEL, 187 "Save", GTK_RESPONSE_ACCEPT, 188 NULL); 189 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv); 190 gtk_widget_show (dialog); 191 } 192 193 GtkWidget * 194 tfe_text_view_new_with_file (GFile *file) { 195 g_return_val_if_fail (G_IS_FILE (file), NULL); 196 197 GtkWidget *tv; 198 GtkTextBuffer *tb; 199 char *contents; 200 gsize length; 201 202 if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */ 203 return NULL; 204 205 tv = tfe_text_view_new(); 206 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); 207 gtk_text_buffer_set_text (tb, contents, length); 208 g_free (contents); 209 TFE_TEXT_VIEW (tv)->file = g_file_dup (file); 210 return tv; 211 } 212 213 GtkWidget * 214 tfe_text_view_new (void) { 215 return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL)); 216 } 217 ~~~ ## Total number of lines, words and characters $ LANG=C wc tfe5/meson.build tfe5/tfeapplication.c tfe5/tfe.gresource.xml tfe5/tfe.h tfe5/tfenotebook.c tfe5/tfenotebook.h tfetextview/tfetextview.c tfetextview/tfetextview.h tfe5/tfe.ui 10 17 294 tfe5/meson.build 117 348 3576 tfe5/tfeapplication.c 6 9 153 tfe5/tfe.gresource.xml 4 6 87 tfe5/tfe.h 117 325 3064 tfe5/tfenotebook.c 12 17 196 tfe5/tfenotebook.h 217 637 7725 tfetextview/tfetextview.c 35 60 701 tfetextview/tfetextview.h 64 105 2266 tfe5/tfe.ui 582 1524 18062 total Up: [Readme.md](../Readme.md), Prev: [Section 14](sec14.md), Next: [Section 16](sec16.md)