diff --git a/.gitignore b/.gitignore index 31cc088..f938ff3 100755 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ src/tfe4/_build src/tfe5/_build src/tfe5/hello.txt src/tfe6/_build +src/tfe7/_build src/menu/a.out src/color/_build src/turtle/_build diff --git a/Readme.md b/Readme.md index 7e704dc..fc56811 100644 --- a/Readme.md +++ b/Readme.md @@ -32,6 +32,7 @@ You can read it without download. 1. [Menu and action](gfm/sec16.md) 1. [Stateful action](gfm/sec17.md) 1. [Ui file for menu and action entries](gfm/sec18.md) -1. [Upgrade text file editor](gfm/sec19.md) -1. [GtkDrawingArea and Cairo](gfm/sec20.md) -1. [Combine GtkDrawingArea and TfeTextView](gfm/sec21.md) +1. [GtkMenuButton, accelerators, font, pango and gsettings](gfm/sec19.md) +1. [Template XML](gfm/sec20.md) +1. [GtkDrawingArea and Cairo](gfm/sec21.md) +1. [Combine GtkDrawingArea and TfeTextView](gfm/sec22.md) diff --git a/gfm/sec19.md b/gfm/sec19.md index 18750a4..1fc2ce9 100644 --- a/gfm/sec19.md +++ b/gfm/sec19.md @@ -1,6 +1,6 @@ Up: [Readme.md](../Readme.md), Prev: [Section 18](sec18.md), Next: [Section 20](sec20.md) -# Upgrade text file editor +# GtkMenuButton, accelerators, font, pango and gsettings Traditional menu structure is fine. However, Buttons or menu items we often use are not so many. diff --git a/gfm/sec20.md b/gfm/sec20.md index ef82eb2..7ec8274 100644 --- a/gfm/sec20.md +++ b/gfm/sec20.md @@ -1,201 +1,857 @@ Up: [Readme.md](../Readme.md), Prev: [Section 19](sec19.md), Next: [Section 21](sec21.md) -# GtkDrawingArea and Cairo +# Template XML -If you want to draw dynamically, like an image window of gimp graphics editor, GtkDrawingArea widget is the most suitable widget. -You can draw or redraw an image in this widget freely. -It is called custom drawing. - -GtkDrawingArea provides a cairo context so users can draw images by cairo functions. -In this section, I will explain: - -1. Cairo, but briefly. -2. GtkDrawingArea with very simple example. - -## Cairo - -Cairo is a two dimensional graphics library. -First, you need to know surface, source, mask, destination, cairo context and transformation. - -- Surface represents an image. -It is like a canvas. -We can draw shapes and images with different colors on surfaces. -- Source pattern, or simply source, is a kind of paint, which will be transferred to destination surface by cairo functions. -- Mask is image mask used in the transference. -- Destination is a target surface. -- Cairo context manages the transference from source to destination through mask with its functions. -For example, `cairo_stroke` is a function to draw a path to the destination by the transference. -- Transformation is applied before the transfer completes. -The transformation is called affine, which is a mathematics terminology, and represented by matrix multiplication and vector addition. -Scaling, rotation, reflection, shearing and translation are examples of affine transformation. -In this section, we don't use it. -That means we only use identity transformation. -Therefore, the coordinate in source and mask is the same as the coordinate in destination. - -![Stroke a rectangle](../image/cairo.png) - -The instruction is as follows: - -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. - -Here's a simple example code that draws a small square and save it as a png file. +The tfe program in the previous section is not so good because many things are crammed into `tfepplication.c`. +Many static variables in `tfepplication.c` shows that. ~~~C - 1 #include - 2 - 3 int - 4 main (int argc, char **argv) - 5 { - 6 cairo_surface_t *surface; - 7 cairo_t *cr; - 8 int width = 100; - 9 int height = 100; -10 -11 /* Generate surface and cairo */ -12 surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height); -13 cr = cairo_create (surface); -14 -15 /* Drawing starts here. */ -16 /* Paint the background white */ -17 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); -18 cairo_paint (cr); -19 /* Draw a black rectangle */ -20 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); -21 cairo_set_line_width (cr, 2.0); -22 cairo_rectangle (cr, width/2.0 - 20.0, height/2.0 - 20.0, 40.0, 40.0); -23 cairo_stroke (cr); -24 -25 /* Write the surface to a png file and clean up cairo and surface. */ -26 cairo_surface_write_to_png (surface, "rectangle.png"); -27 cairo_destroy (cr); -28 cairo_surface_destroy (surface); -29 -30 return 0; -31 } +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; ~~~ -- 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. +Generally, if there are many global or static variables in the program, it is not a good program. +Such programs are difficult to maintain. -To compile this, type the following. +The file `tfeapplication.c` should be divided into several files. - $ gcc `pkg-config --cflags cairo` cairo.c `pkg-config --libs cairo` +- `tfeapplication.c` only has codes related to GtkApplication. +- A file about GtkApplicationWindow +- A file about a preference dialog +- A file about an alert dialog -![rectangle.png](../src/misc/rectangle.png) +The preference dialog is defined by a ui file. +And it has GtkBox, GtkLabel and GtkFontButton in it. +Such widget is called composite widget. +Composite widget is a child object of the parent widget. +For example, the preference composite widget is a child object of GtkDialog. +Composite widget can be built from template XML. +Next subsection shows how to build a preference dialog. -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. +## Preference dialog -## GtkDrawingArea +First, write a template XML file. -The following is a very simple example. +~~~xml + 1 + 2 + 3 +32 +33 +~~~ + +- 3: Template tag specifies a composite widget. +The value of a class attribute is the object name of the composite widget. +This XML file names the object "TfePref". +It is defined in a C source file and it will be shown later. +A parent attribute specifies the direct parent object of the composite widget. +`TfePref` is a child object of `GtkDialog`. +Therefore the value of the attribute is "GtkDialog". +A parent attribute is optional but it is recommended to specify. + +Other lines are the same as before. +The object `TfePref` is defined in `tfepref.h` and `tfepref.c`. ~~~C - 1 #include + 1 #ifndef __TFE_PREF_H__ + 2 #define __TFE_PREF_H__ + 3 + 4 #include + 5 + 6 #define TFE_TYPE_PREF tfe_pref_get_type () + 7 G_DECLARE_FINAL_TYPE (TfePref, tfe_pref, TFE, PREF, GtkDialog) + 8 + 9 GtkWidget * +10 tfe_pref_new (GtkWindow *win); +11 +12 #endif /* __TFE_PREF_H__ */ +13 +~~~ + +- 6-7: When you define a new object, you need to write these two lines. +Refer to [Section 7](sec7.md). +- 9-10: `tfe_pref_new` generates a new TfePref object. +It has a parameter which the object use as a transient parent to show the dialog. + +~~~C + 1 #include "tfepref.h" 2 - 3 static void - 4 draw_function (GtkDrawingArea *area, cairo_t *cr, int width, int height, gpointer user_data) { - 5 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); /* whilte */ - 6 cairo_paint (cr); - 7 cairo_set_line_width (cr, 2.0); - 8 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */ - 9 cairo_rectangle (cr, width/2.0 - 20.0, height/2.0 - 20.0, 40.0, 40.0); -10 cairo_stroke (cr); + 3 struct _TfePref + 4 { + 5 GtkDialog parent; + 6 GSettings *settings; + 7 GtkFontButton *fontbtn; + 8 }; + 9 +10 G_DEFINE_TYPE (TfePref, tfe_pref, GTK_TYPE_DIALOG); +11 +12 static void +13 tfe_pref_dispose (GObject *gobject) { +14 TfePref *pref = TFE_PREF (gobject); +15 +16 g_clear_object (&pref->settings); +17 G_OBJECT_CLASS (tfe_pref_parent_class)->dispose (gobject); +18 } +19 +20 static void +21 tfe_pref_init (TfePref *pref) { +22 gtk_widget_init_template (GTK_WIDGET (pref)); +23 pref->settings = g_settings_new ("com.github.ToshioCP.tfe"); +24 g_settings_bind (pref->settings, "font", pref->fontbtn, "font", G_SETTINGS_BIND_DEFAULT); +25 } +26 +27 static void +28 tfe_pref_class_init (TfePrefClass *class) { +29 GObjectClass *object_class = G_OBJECT_CLASS (class); +30 +31 object_class->dispose = tfe_pref_dispose; +32 gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class), "/com/github/ToshioCP/tfe/tfepref.ui"); +33 gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfePref, fontbtn); +34 } +35 +36 GtkWidget * +37 tfe_pref_new (GtkWindow *win) { +38 return GTK_WIDGET (g_object_new (TFE_TYPE_PREF, "transient-for", win, NULL)); +39 } +40 +~~~ + +- 3-8: The structure of an instance of this object. +It has two variables, settings and fontbtn. +- 10: G\_DEFINE\_TYPE macro generates lines to register the type. +- 12-18: dispose handler. +This handler is called when this object is finalizing. +The process has two stages, disposing and finalizing. +When disposing, the object releases all the objects it has had. +TfePref object holds a GSetting object. +It is released in line 16. +After that parents dispose handler is called in line 17. +Refer to [Section 10](sec10.md). +- 27-34: Class initialization function. +This is called in the class generation process. +- 31: Set the dispose handler. +- 32: `gtk_widget_class_set_template_from_resource` function associates the description in the XML file with the widget. +At this moment no object is generated. +It just make the class to know the structure of the object. +That's why the top level tag is not an object but template in the XML file. +- 33: `gtk_widget_class_bind_template_child` function binds a private variable of the object with a child object in the template. +This function is a macro. +The name of the private variable (in the line 7) and the id (in the line 24) in the XML file must be the same. +In the program above, the name is `fontbtn`. +The pointer to the object will be assigned to the variable when an instance is generated. +- 20-25: Instance initialization function. +- 22: Initializes the template of this object. +The template has been made during the class initialization process. +Now it is implemented to the instance. +- 23: Create GSettings object with the id `com.github.ToshioCP.tfe`. +- 24: Bind the font key in the GSettings object and the font property in the GtkFontButton. + +- 36-39: The function `tfe_pref_new` creates an instance of TfePref. +The parameter `win` is a transient parent. + +Now, It is very simple to use this dialog. +A caller just creates this object and shows it. + +~~~C +TfePref *pref; +pref = tfe_pref_new (win) /* win is the top level window */ +gtk_widget_show (GTK_WINDOW (win)); +~~~ + +This instance is automatically destroyed when a user clicks on the close button. +That's all. +If you want to show the dialog again, just create and show it. + +## Alert dialog + +It is almost same as preference dialog. + +Its XML file is: + +~~~xml + 1 + 2 + 3 +46 +47 +~~~ + +The header file is: + +~~~C + 1 #ifndef __TFE_ALERT_H__ + 2 #define __TFE_ALERT_H__ + 3 + 4 #include + 5 + 6 #define TFE_TYPE_ALERT tfe_alert_get_type () + 7 G_DECLARE_FINAL_TYPE (TfeAlert, tfe_alert, TFE, ALERT, GtkDialog) + 8 + 9 void +10 tfe_alert_set_message (TfeAlert *alert, const char *message); +11 +12 void +13 tfe_alert_set_button_label (TfeAlert *alert, const char *label); +14 +15 GtkWidget * +16 tfe_alert_new (GtkWindow *win); +17 +18 #endif /* __TFE_ALERT_H__ */ +19 +~~~ + +There are three public functions. +The functions `tfe_alert_set_message` and `tfe_alert_set_button_label` sets the label and button name of the alert dialog. +For example, if you want to show an alert that the user tries to close without saving the content, set them like: + +~~~C +tfe_alert_set_message (alert, "Are you really close without saving?"); /* alert points to a TfeAlert object */ +tfe_alert_set_button_label (alert, "Close"); +~~~ + +The function `tfe_alert_new` creates a TfeAlert dialog. + +The C source file is: + +~~~C + 1 #include "tfealert.h" + 2 + 3 struct _TfeAlert + 4 { + 5 GtkDialog parent; + 6 GtkLabel *lb_alert; + 7 GtkButton *btn_accept; + 8 }; + 9 +10 G_DEFINE_TYPE (TfeAlert, tfe_alert, GTK_TYPE_DIALOG); +11 +12 void +13 tfe_alert_set_message (TfeAlert *alert, const char *message) { +14 gtk_label_set_text (alert->lb_alert, message); +15 } +16 +17 void +18 tfe_alert_set_button_label (TfeAlert *alert, const char *label) { +19 gtk_button_set_label (alert->btn_accept, label); +20 } +21 +22 static void +23 tfe_alert_init (TfeAlert *alert) { +24 gtk_widget_init_template (GTK_WIDGET (alert)); +25 } +26 +27 static void +28 tfe_alert_class_init (TfeAlertClass *class) { +29 gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class), "/com/github/ToshioCP/tfe/tfealert.ui"); +30 gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeAlert, lb_alert); +31 gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeAlert, btn_accept); +32 } +33 +34 GtkWidget * +35 tfe_alert_new (GtkWindow *win) { +36 return GTK_WIDGET (g_object_new (TFE_TYPE_ALERT, "transient-for", win, NULL)); +37 } +38 +~~~ + +The program is almost same as `tfepref.c`. + +The instruction how to use this object is as follows. + +1. Write a "response" signal handler. +2. Create a TfeAlert object. +3. Connect "response" signal to a handler +4. Show the dialog +5. In the signal handler do something along the response-id. +Then destroy the dialog. + +## Top level window + +In the same way, create a child object of GtkApplicationWindow. +The object name is "TfeWindow". + +~~~xml + 1 + 2 + 3 +64 +65 +~~~ + +This XML file is the same as before except template tag. + +~~~C + 1 #ifndef __TFE_WINDOW_H__ + 2 #define __TFE_WINDOW_H__ + 3 + 4 #include + 5 + 6 #define TFE_TYPE_WINDOW tfe_window_get_type () + 7 G_DECLARE_FINAL_TYPE (TfeWindow, tfe_window, TFE, WINDOW, GtkApplicationWindow) + 8 + 9 void +10 tfe_window_notebook_page_new (TfeWindow *win); +11 +12 void +13 tfe_window_notebook_page_new_with_files (TfeWindow *win, GFile **files, int n_files); +14 +15 GtkWidget * +16 tfe_window_new (GtkApplication *app); +17 +18 #endif /* __TFE_WINDOW_H__ */ +19 +~~~ + +There are three public functions. +The function `tfe_window_notebook_page_new` creates a new notebook page. +This is a wrapper function of `notebook_page_new`. +It is called by GtkApplication object. +The function `tfe_window_notebook_page_new_with_files` creates notebook pages with a contents read from the given files. +The function `tfe_window_new` creates a TfeWindow instance. + +~~~C + 1 #include "tfewindow.h" + 2 #include "tfenotebook.h" + 3 #include "tfepref.h" + 4 #include "tfealert.h" + 5 #include "css.h" + 6 + 7 struct _TfeWindow { + 8 GtkApplicationWindow parent; + 9 GtkButton *btno; + 10 GtkButton *btns; + 11 GtkButton *btnc; + 12 GtkMenuButton *btnm; + 13 GtkNotebook *nb; + 14 GSettings *settings; + 15 gboolean is_quit; + 16 }; + 17 + 18 G_DEFINE_TYPE (TfeWindow, tfe_window, GTK_TYPE_APPLICATION_WINDOW); + 19 + 20 /* alert response signal handler */ + 21 static void + 22 alert_response_cb (GtkDialog *alert, int response_id, gpointer user_data) { + 23 TfeWindow *win = TFE_WINDOW (user_data); + 24 + 25 if (response_id == GTK_RESPONSE_ACCEPT) { + 26 if (win->is_quit) + 27 gtk_window_destroy(GTK_WINDOW (win)); + 28 else + 29 notebook_page_close (win->nb); + 30 } + 31 gtk_window_destroy (GTK_WINDOW (alert)); + 32 } + 33 + 34 /* ----- button handlers ----- */ + 35 void + 36 open_cb (GtkNotebook *nb) { + 37 notebook_page_open (nb); + 38 } + 39 + 40 void + 41 save_cb (GtkNotebook *nb) { + 42 notebook_page_save (nb); + 43 } + 44 + 45 void + 46 close_cb (GtkNotebook *nb) { + 47 TfeAlert *alert; + 48 TfeWindow *win = TFE_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (nb), TFE_TYPE_WINDOW)); + 49 + 50 if (has_saved (nb)) + 51 notebook_page_close (nb); + 52 else { + 53 win->is_quit = false; + 54 alert = TFE_ALERT (tfe_alert_new (GTK_WINDOW (win))); + 55 tfe_alert_set_message (alert, "Contents aren't saved yet.\nAre you sure to close?"); + 56 tfe_alert_set_button_label (alert, "Close"); + 57 g_signal_connect (GTK_DIALOG (alert), "response", G_CALLBACK (alert_response_cb), win); + 58 gtk_widget_show (GTK_WIDGET (alert)); + 59 } + 60 } + 61 + 62 /* ----- action activated handlers ----- */ + 63 static void + 64 open_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) { + 65 TfeWindow *win = TFE_WINDOW (user_data); + 66 + 67 open_cb (GTK_NOTEBOOK (win->nb)); + 68 } + 69 + 70 static void + 71 save_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) { + 72 TfeWindow *win = TFE_WINDOW (user_data); + 73 + 74 save_cb (GTK_NOTEBOOK (win->nb)); + 75 } + 76 + 77 static void + 78 close_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) { + 79 TfeWindow *win = TFE_WINDOW (user_data); + 80 + 81 close_cb (GTK_NOTEBOOK (win->nb)); + 82 } + 83 + 84 static void + 85 new_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) { + 86 TfeWindow *win = TFE_WINDOW (user_data); + 87 + 88 notebook_page_new (GTK_NOTEBOOK (win->nb)); + 89 } + 90 + 91 static void + 92 saveas_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) { + 93 TfeWindow *win = TFE_WINDOW (user_data); + 94 + 95 notebook_page_saveas (GTK_NOTEBOOK (win->nb)); + 96 } + 97 + 98 static void + 99 pref_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) { +100 TfeWindow *win = TFE_WINDOW (user_data); +101 GtkWidget *pref; +102 +103 pref = tfe_pref_new (GTK_WINDOW (win)); +104 gtk_widget_show (pref); +105 } +106 +107 static void +108 quit_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) { +109 TfeWindow *win = TFE_WINDOW (user_data); +110 +111 TfeAlert *alert; +112 +113 if (has_saved_all (GTK_NOTEBOOK (win->nb))) +114 gtk_window_destroy (GTK_WINDOW (win)); +115 else { +116 win->is_quit = true; +117 alert = TFE_ALERT (tfe_alert_new (GTK_WINDOW (win))); +118 tfe_alert_set_message (alert, "Contents aren't saved yet.\nAre you sure to quit?"); +119 tfe_alert_set_button_label (alert, "Quit"); +120 g_signal_connect (GTK_DIALOG (alert), "response", G_CALLBACK (alert_response_cb), win); +121 gtk_widget_show (GTK_WIDGET (alert)); +122 } +123 } +124 +125 /* gsettings changed::font signal handler */ +126 static void +127 changed_font_cb (GSettings *settings, char *key, gpointer user_data) { +128 GtkWindow *win = GTK_WINDOW (user_data); +129 const char *font; +130 PangoFontDescription *pango_font_desc; +131 +132 font = g_settings_get_string (settings, "font"); +133 pango_font_desc = pango_font_description_from_string (font); +134 set_font_for_display_with_pango_font_desc (win, pango_font_desc); +135 } +136 +137 /* --- public functions --- */ +138 +139 void +140 tfe_window_notebook_page_new (TfeWindow *win) { +141 notebook_page_new (win->nb); +142 } +143 +144 void +145 tfe_window_notebook_page_new_with_files (TfeWindow *win, GFile **files, int n_files) { +146 int i; +147 +148 for (i = 0; i < n_files; i++) +149 notebook_page_new_with_file (win->nb, files[i]); +150 if (gtk_notebook_get_n_pages (win->nb) == 0) +151 notebook_page_new (win->nb); +152 } +153 +154 /* --- TfeWindow object construction/destruction --- */ +155 static void +156 tfe_window_dispose (GObject *gobject) { +157 TfeWindow *window = TFE_WINDOW (gobject); +158 +159 g_clear_object (&window->settings); +160 G_OBJECT_CLASS (tfe_window_parent_class)->dispose (gobject); +161 } +162 +163 static void +164 tfe_window_init (TfeWindow *win) { +165 GtkBuilder *build; +166 GMenuModel *menu; +167 +168 gtk_widget_init_template (GTK_WIDGET (win)); +169 +170 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe/menu.ui"); +171 menu = G_MENU_MODEL (gtk_builder_get_object (build, "menu")); +172 gtk_menu_button_set_menu_model (win->btnm, menu); +173 g_object_unref(build); +174 +175 win->settings = g_settings_new ("com.github.ToshioCP.tfe"); +176 g_signal_connect (win->settings, "changed::font", G_CALLBACK (changed_font_cb), win); +177 +178 /* ----- action ----- */ +179 const GActionEntry win_entries[] = { +180 { "open", open_activated, NULL, NULL, NULL }, +181 { "save", save_activated, NULL, NULL, NULL }, +182 { "close", close_activated, NULL, NULL, NULL }, +183 { "new", new_activated, NULL, NULL, NULL }, +184 { "saveas", saveas_activated, NULL, NULL, NULL }, +185 { "pref", pref_activated, NULL, NULL, NULL }, +186 { "close-all", quit_activated, NULL, NULL, NULL } +187 }; +188 g_action_map_add_action_entries (G_ACTION_MAP (win), win_entries, G_N_ELEMENTS (win_entries), win); +189 +190 changed_font_cb(win->settings, "font", win); +191 } +192 +193 static void +194 tfe_window_class_init (TfeWindowClass *class) { +195 GObjectClass *object_class = G_OBJECT_CLASS (class); +196 +197 object_class->dispose = tfe_window_dispose; +198 gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class), "/com/github/ToshioCP/tfe/tfewindow.ui"); +199 gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeWindow, btno); +200 gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeWindow, btns); +201 gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeWindow, btnc); +202 gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeWindow, btnm); +203 gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeWindow, nb); +204 gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), open_cb); +205 gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), save_cb); +206 gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), close_cb); +207 } +208 +209 GtkWidget * +210 tfe_window_new (GtkApplication *app) { +211 return GTK_WIDGET (g_object_new (TFE_TYPE_WINDOW, "application", app, NULL)); +212 } +213 +~~~ + +- 20-32: `alert_response_cb` is a call back function of the "response" signal of TfeAlert dialog. +This is the same as before except `gtk_window_destroy(GTK_WINDOW (win))` is used instead of `tfe_application_quit`. +- 34-60: Handlers of Button clicked signal. +- 62-123: Handlers of action activated signal. +The `user_data` is a pointer to TfeWindow instance. +- 125-135: A handler of "changed::font" signal of GSettings object. +- 132: Gets the font from GSettings data. +- 133: Gets a PangoFontDescription from the font. +In the previous version, the program gets the font description from the GtkFontButton. +The button data and GSettings data are the same. +Therefore, the data got here is the same as the data in the GtkFontButton. +In addition, we don't need to worry about the preference dialog is alive or not thanks to the GSettings. +- 134: Sets CSS on the display with the font description. +- 137-152: Public functions. +- 155-161: Dispose handler. +The GSettings object needs to be released. +- 163-191: Object initialize function. +- 168: Generates a composite widget with the template. +- 170-173: Insert menu to the menu button. +- 175-176: Creates a GSettings object with the id. +Connects "changed::font" signal to the handler `changed_font_cb`. +This signal emits when the GSettings data is changed. +The second part "font" of the signal name "changed::font" is called details. +Signals can have details. +If a GSettings object has more than one key, "changed" signal emits only if the key which has the same name as the detail changes its value. +For example, Suppose a GSettings object has three keys "a", "b" and "c". + - "changed::a" is emitted when the value of "a" is changed. It isn't emitted when the value of "b" or "c" is changed. + - "changed::b" is emitted when the value of "b" is changed. It isn't emitted when the value of "a" or "c" is changed. + - "changed::c" is emitted when the value of "c" is changed. It isn't emitted when the value of "a" or "b" is changed. +In this version of tfe, there is only one key ("font"). +So, even if the signal doesn't have a detail, the result is the same. +But in the future version, it will probably need details. +- 178-188: Creates actions. +- 190: Sets CSS font. +- 193-207: Class initialization function. +- 197: Sets the dispose handler. +- 198: Sets the composite widget template +- 199-203: Binds private variable with child objects in the template. +- 204-206: Binds signal handlers with signal tags in the template. +- 209-212: `tfe_window_new`. +This function creates TfeWindow instance. + +## GtkApplication + +The file `tfeapplication.c` is now very simple. + +~~~C + 1 #include "tfewindow.h" + 2 + 3 /* ----- activate, open, startup handlers ----- */ + 4 static void + 5 tfe_activate (GApplication *application) { + 6 GtkApplication *app = GTK_APPLICATION (application); + 7 GtkWidget *win = GTK_WIDGET (gtk_application_get_active_window (app)); + 8 + 9 tfe_window_notebook_page_new (win); +10 gtk_widget_show (GTK_WIDGET (win)); 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 (); +14 tfe_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) { +15 GtkApplication *app = GTK_APPLICATION (application); +16 GtkWidget *win = GTK_WIDGET (gtk_application_get_active_window (app)); 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 +18 tfe_window_notebook_page_new_with_files (win, files, n_files); +19 gtk_widget_show (win); +20 } +21 +22 static void +23 tfe_startup (GApplication *application) { +24 GtkApplication *app = GTK_APPLICATION (application); +25 GtkBuilder *build; +26 TfeWindow *win; +27 int i; +28 +29 win = tfe_window_new (app); +30 +31 /* ----- accelerator ----- */ +32 struct { +33 const char *action; +34 const char *accels[2]; +35 } action_accels[] = { +36 { "win.open", { "o", NULL } }, +37 { "win.save", { "s", NULL } }, +38 { "win.close", { "w", NULL } }, +39 { "win.new", { "n", NULL } }, +40 { "win.saveas", { "s", NULL } }, +41 { "win.close-all", { "q", NULL } }, +42 }; +43 +44 for (i = 0; i < G_N_ELEMENTS(action_accels); i++) +45 gtk_application_set_accels_for_action(GTK_APPLICATION(app), action_accels[i].action, action_accels[i].accels); +46 } +47 +48 /* ----- main ----- */ +49 int +50 main (int argc, char **argv) { +51 GtkApplication *app; +52 int stat; +53 +54 app = gtk_application_new ("com.github.ToshioCP.tfe", G_APPLICATION_HANDLES_OPEN); +55 +56 g_signal_connect (app, "startup", G_CALLBACK (tfe_startup), NULL); +57 g_signal_connect (app, "activate", G_CALLBACK (tfe_activate), NULL); +58 g_signal_connect (app, "open", G_CALLBACK (tfe_open), NULL); +59 +60 stat =g_application_run (G_APPLICATION (app), argc, argv); +61 g_object_unref (app); +62 return stat; +63 } +64 ~~~ -The function `main` is almost same as before. -The two functions `on_activate` and `draw_function` is important in this example. +- 3-11: Activate signal handler. +It uses `tfe_window_notebook_page_new` instead of `notebook_page_new`. +- 13-20: Open signal handler. +Thanks to `tfe_window_notebook_page_new_with_files`, this handler becomes very simple. +- 22-46: Startup signal handler. +Most of the task is moved to TfeWindow, the remaining task is creating a window and setting accelerations. +- 49-63: A function main. -- 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. +## Other files -The drawing function has five parameters. +Resource XML file. - void drawing_function (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height, - gpointer user_data); +~~~xml +1 +2 +3 +4 tfewindow.ui +5 tfepref.ui +6 tfealert.ui +7 menu.ui +8 +9 +~~~ -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. +GSchema XML file -- 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. +~~~xml + 1 + 2 + 3 + 4 + 5 'Monospace 12' + 6 Font + 7 The font to be used for textview. + 8 + 9 +10 +~~~ -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. +Meson.build -![Square in the window](../image/da1.png) +~~~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 gnome.compile_schemas(build_by_default: true, depend_files: 'com.github.ToshioCP.tfe.gschema.xml') + 8 + 9 sourcefiles=files('tfeapplication.c', 'tfewindow.c', 'tfenotebook.c', 'tfepref.c', 'tfealert.c', 'css.c', '../tfetextview/tfetextview.c') +10 +11 executable('tfe', sourcefiles, resources, dependencies: gtkdep, export_dynamic: true, install: true) +12 +13 schema_dir = get_option('prefix') / get_option('datadir') / 'glib-2.0/schemas/' +14 install_data('com.github.ToshioCP.tfe.gschema.xml', install_dir: schema_dir) +15 meson.add_install_script('glib-compile-schemas', schema_dir) +16 +~~~ +## Compiling and installation. + +~~~ +$ meson --prefix=$HOME/local _build +$ ninja -C _build +$ ninja -C _build install +=== + +Source files are in [src/tfe7](../src/tfe7) directory. + +We made a very small text editor. +You can add features to this editor. +When you add a new feature, care about the structure of the program. +Maybe you need to divide a file into several files like this section. +It isn't good to put many things into one file. +And it is important to think about the relationship between source files and widget structures. +It is appropriate that they correspond to each other in many cases. Up: [Readme.md](../Readme.md), Prev: [Section 19](sec19.md), Next: [Section 21](sec21.md) diff --git a/gfm/sec21.md b/gfm/sec21.md index 0993e69..f245b89 100644 --- a/gfm/sec21.md +++ b/gfm/sec21.md @@ -1,373 +1,201 @@ -Up: [Readme.md](../Readme.md), Prev: [Section 20](sec20.md) +Up: [Readme.md](../Readme.md), Prev: [Section 20](sec20.md), Next: [Section 22](sec22.md) -# Combine GtkDrawingArea and TfeTextView +# GtkDrawingArea and Cairo -Now, we will make a new application which has GtkDrawingArea and TfeTextView in it. -Its name is "color". -If you write a color in TfeTextView and click on the `run` button, then the color of GtkDrawingArea changes to the color given by you. +If you want to draw dynamically, like an image window of gimp graphics editor, GtkDrawingArea widget is the most suitable widget. +You can draw or redraw an image in this widget freely. +It is called custom drawing. -![color](../image/color.png) +GtkDrawingArea provides a cairo context so users can draw images by cairo functions. +In this section, I will explain: -The following colors are available. +1. Cairo, but briefly. +2. GtkDrawingArea with very simple example. -- white -- black -- red -- green -- blue +## Cairo -In addition the following two options are also available. +Cairo is a two dimensional graphics library. +First, you need to know surface, source, mask, destination, cairo context and transformation. -- light: Make the color of the drawing area lighter. -- dark: Make the color of the drawing area darker. +- Surface represents an image. +It is like a canvas. +We can draw shapes and images with different colors on surfaces. +- Source pattern, or simply source, is a kind of paint, which will be transferred to destination surface by cairo functions. +- Mask is image mask used in the transference. +- Destination is a target surface. +- Cairo context manages the transference from source to destination through mask with its functions. +For example, `cairo_stroke` is a function to draw a path to the destination by the transference. +- Transformation is applied before the transfer completes. +The transformation is called affine, which is a mathematics terminology, and represented by matrix multiplication and vector addition. +Scaling, rotation, reflection, shearing and translation are examples of affine transformation. +In this section, we don't use it. +That means we only use identity transformation. +Therefore, the coordinate in source and mask is the same as the coordinate in destination. -This application can only do very simple things. -However, it tells us that if we add powerful parser to it, we will be able to make it more efficient. -I want to show it to you in the later section by making a turtle graphics language like Logo program language. +![Stroke a rectangle](../image/cairo.png) -In this section, we focus on how to bind the two objects. +The instruction is as follows: -## Color.ui and color.gresource.xml +1. Create a surface. +This will be a destination. +2. Create a cairo context with the surface and the surface will be the destination of the context. +3. Create a source pattern within the context. +4. Create paths, which are lines, rectangles, arcs, texts or more complicated shapes, to generate a mask. +5. Use drawing operator such as `cairo_stroke` to transfer the paint in the source to the destination. +6. Save the destination surface to a file if necessary. -First, We need to make the ui file of the widgets. -The image in the previous subsection gives us the structure of the widgets. -Title bar, four buttons in the tool bar and two widgets textview and drawing area. -The ui file is as follows. - -~~~xml - 1 - 2 - 3 - 4 color changer - 5 600 - 6 400 - 7 - 8 - 9 GTK_ORIENTATION_VERTICAL -10 -11 -12 GTK_ORIENTATION_HORIZONTAL -13 -14 -15 10 -16 -17 -18 -19 -20 Run -21 -22 -23 -24 -25 -26 Open -27 -28 -29 -30 -31 -32 TRUE -33 -34 -35 -36 -37 Save -38 -39 -40 -41 -42 -43 Close -44 -45 -46 -47 -48 -49 10 -50 -51 -52 -53 -54 -55 -56 GTK_ORIENTATION_HORIZONTAL -57 TRUE -58 -59 -60 TRUE -61 TRUE -62 -63 -64 GTK_WRAP_WORD_CHAR -65 -66 -67 -68 -69 -70 -71 TRUE -72 TRUE -73 -74 -75 -76 -77 -78 -79 -80 -~~~ - -- 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 -2 -3 -4 color.ui -5 -6 -~~~ - -## Tfetextview.h, tfetextview.c and color.h - -First two files are the same as before. -Color.h just includes tfetextview.h. +Here's a simple example code that draws a small square and save it as a png file. ~~~C -1 #include -2 -3 #include "../tfetextview/tfetextview.h" -~~~ - -## Colorapplication.c - -This is the main file. -It deals with: - -- Building widgets by GtkBuilder. -- Seting a drawing function of GtkDrawingArea. -And connecting a handler to "resize" signal on GtkDrawingArea. -- Implementing each call back functions. -Particularly, `Run` signal handler is the point in this program. - -The following is `colorapplication.c`. - -~~~C - 1 #include "color.h" - 2 - 3 static GtkWidget *win; - 4 static GtkWidget *tv; - 5 static GtkWidget *da; - 6 - 7 static cairo_surface_t *surface = NULL; - 8 - 9 static void - 10 run (void) { - 11 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); - 12 GtkTextIter start_iter; - 13 GtkTextIter end_iter; - 14 char *contents; - 15 cairo_t *cr; - 16 - 17 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter); - 18 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE); - 19 if (surface) { - 20 cr = cairo_create (surface); - 21 if (g_strcmp0 ("red", contents) == 0) - 22 cairo_set_source_rgb (cr, 1, 0, 0); - 23 else if (g_strcmp0 ("green", contents) == 0) - 24 cairo_set_source_rgb (cr, 0, 1, 0); - 25 else if (g_strcmp0 ("blue", contents) == 0) - 26 cairo_set_source_rgb (cr, 0, 0, 1); - 27 else if (g_strcmp0 ("white", contents) == 0) - 28 cairo_set_source_rgb (cr, 1, 1, 1); - 29 else if (g_strcmp0 ("black", contents) == 0) - 30 cairo_set_source_rgb (cr, 0, 0, 0); - 31 else if (g_strcmp0 ("light", contents) == 0) - 32 cairo_set_source_rgba (cr, 1, 1, 1, 0.5); - 33 else if (g_strcmp0 ("dark", contents) == 0) - 34 cairo_set_source_rgba (cr, 0, 0, 0, 0.5); - 35 else - 36 cairo_set_source_surface (cr, surface, 0, 0); - 37 cairo_paint (cr); - 38 cairo_destroy (cr); - 39 } - 40 } - 41 - 42 void - 43 run_cb (GtkWidget *btnr) { - 44 run (); - 45 gtk_widget_queue_draw (GTK_WIDGET (da)); - 46 } - 47 - 48 void - 49 open_cb (GtkWidget *btno) { - 50 tfe_text_view_open (TFE_TEXT_VIEW (tv), win); - 51 } - 52 - 53 void - 54 save_cb (GtkWidget *btns) { - 55 tfe_text_view_save (TFE_TEXT_VIEW (tv)); - 56 } - 57 - 58 void - 59 close_cb (GtkWidget *btnc) { - 60 if (surface) - 61 cairo_surface_destroy (surface); - 62 gtk_window_destroy (GTK_WINDOW (win)); - 63 } - 64 - 65 static void - 66 resize_cb (GtkDrawingArea *drawing_area, int width, int height, gpointer user_data) { - 67 if (surface) - 68 cairo_surface_destroy (surface); - 69 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); - 70 run (); - 71 } - 72 - 73 static void - 74 draw_func (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height, gpointer user_data) { - 75 if (surface) { - 76 cairo_set_source_surface (cr, surface, 0, 0); - 77 cairo_paint (cr); - 78 } - 79 } - 80 - 81 static void - 82 activate (GApplication *application) { - 83 gtk_widget_show (win); - 84 } - 85 - 86 static void - 87 startup (GApplication *application) { - 88 GtkApplication *app = GTK_APPLICATION (application); - 89 GtkBuilder *build; - 90 - 91 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/color/color.ui"); - 92 win = GTK_WIDGET (gtk_builder_get_object (build, "win")); - 93 gtk_window_set_application (GTK_WINDOW (win), app); - 94 tv = GTK_WIDGET (gtk_builder_get_object (build, "tv")); - 95 da = GTK_WIDGET (gtk_builder_get_object (build, "da")); - 96 g_object_unref(build); - 97 g_signal_connect (GTK_DRAWING_AREA (da), "resize", G_CALLBACK (resize_cb), NULL); - 98 gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (da), draw_func, NULL, NULL); - 99 -100 GdkDisplay *display; -101 -102 display = gtk_widget_get_display (GTK_WIDGET (win)); -103 GtkCssProvider *provider = gtk_css_provider_new (); -104 gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1); -105 gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER); -106 } -107 -108 int -109 main (int argc, char **argv) { -110 GtkApplication *app; -111 int stat; -112 -113 app = gtk_application_new ("com.github.ToshioCP.color", G_APPLICATION_FLAGS_NONE); -114 -115 g_signal_connect (app, "startup", G_CALLBACK (startup), NULL); -116 g_signal_connect (app, "activate", G_CALLBACK (activate), NULL); -117 -118 stat =g_application_run (G_APPLICATION (app), argc, argv); -119 g_object_unref (app); -120 return stat; -121 } -122 -~~~ - -- 108-121: The function `main` is almost same as before but there are some differences. -The application ID is "com.github.ToshioCP.color". -`G_APPLICATION_FLAGS_NONE` is specified so no open signal handler is necessary. -- 86-106: Startup handler. -- 91-96: Builds widgets. -The pointers of the top window, TfeTextView and GtkDrawingArea objects are stored to static variables `win`, `tv` and `da` respectively. -This is because these objects are often used in handlers. -They never be rewritten so they're thread safe. -- 97: connects "resize" signal and the handler. -- 98: sets the drawing function. -- 81-84: Activates handler, which just shows the widgets. -- 73-79: The drawing function. -It just copies `surface` to destination. -- 65-71: Resize handler. -Re-creates the surface to fit the width and height of the drawing area and paints by calling the function `run`. -- 58-63: Closes the handler. -It destroys `surface` if it exists. -Then it destroys the top window and quits the application. -- 48-56: Open and save handler. -They just call the corresponding functions of TfeTextView. -- 42-46: Run handler. -It calls run function to paint the surface. -After that `gtk_widget_queue_draw` is called. -This fhunction adds the widget (GtkDrawingArea) to the queue to be redrawn. -It is important to know that the drawing function is called when it is necessary. -For example, when another window is moved and uncovers part of the widget, or when the window containing it is resized. -But repaint of `surface` is not automatically notified to gtk. -Therefore, you need to call `gtk_widget_queue_draw` to redraw the widget. -- 9-40: Run function paints the surface. -First, it gets the contents of GtkTextBuffer. -Then it compares it to "red", "green" and so on. -If it matches the color, then the surface is painted the color. -If it matches "light" or "dark", then the color of the surface is lightened or darkened respectively. -Alpha channel is used. - -## Meson.build - -This file is almost same as before. -An argument "export_dynamic: true" is added to executable function. - -~~~meson - 1 project('color', 'c') + 1 #include 2 - 3 gtkdep = dependency('gtk4') - 4 - 5 gnome=import('gnome') - 6 resources = gnome.compile_resources('resources','color.gresource.xml') - 7 - 8 sourcefiles=files('colorapplication.c', '../tfetextview/tfetextview.c') - 9 -10 executable('color', sourcefiles, resources, dependencies: gtkdep, export_dynamic: true) + 3 int + 4 main (int argc, char **argv) + 5 { + 6 cairo_surface_t *surface; + 7 cairo_t *cr; + 8 int width = 100; + 9 int height = 100; +10 +11 /* Generate surface and cairo */ +12 surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height); +13 cr = cairo_create (surface); +14 +15 /* Drawing starts here. */ +16 /* Paint the background white */ +17 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); +18 cairo_paint (cr); +19 /* Draw a black rectangle */ +20 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); +21 cairo_set_line_width (cr, 2.0); +22 cairo_rectangle (cr, width/2.0 - 20.0, height/2.0 - 20.0, 40.0, 40.0); +23 cairo_stroke (cr); +24 +25 /* Write the surface to a png file and clean up cairo and surface. */ +26 cairo_surface_write_to_png (surface, "rectangle.png"); +27 cairo_destroy (cr); +28 cairo_surface_destroy (surface); +29 +30 return 0; +31 } ~~~ -## Compile and execute it +- 1: Includes the header file of cairo. +- 12: `cairo_image_surface_create` creates an image surface. +`CAIRO_FORMAT_RGB24` is a constant which means that each pixel has red, green and blue data. +Each data has 8 bit quantity. +Modern displays have this type of color depth. +Width and height are pixels and given as integers. +- 13: Creates cairo context. +The surface given as an argument will be the destination of the context. +- 17: `cairo_set_source_rgb` creates a source pattern, which is a solid white paint. +The second to fourth argument is red, green and blue color depth respectively. +Their type is float and the values are between zero and one. +(0,0,0) is black and (1,1,1) is white. +- 18: `cairo_paint` copies everywhere in the source to destination. +The destination is filled with white pixels by this command. +- 20: Sets the source color to black. +- 21: `cairo_set_line_width` set the width of lines. +In this case, the line width is set to two pixels. +(It is because the transformation is identity. +If the transformation isn't identity, for example scaling with the factor three, the actual width in destination will be six (2x3=6) pixels.) +- 22: Draws a rectangle (square). +The top-left coordinate is (width/2.0-20.0, height/2.0-20.0) and the width and height have the same length 40.0. +- 23: `cairo_stroke` transfer the source to destination through the rectangle in mask. +- 26: Outputs the image to a png file `rectangle.png`. +- 27: Destroys the context. At the same time the source is destroyed. +- 28: Destroys the destination surface. -First you need to export some variables (refer to [Section 2](sec2.md)). +To compile this, type the following. - $ . env.sh + $ gcc `pkg-config --cflags cairo` cairo.c `pkg-config --libs cairo` -Then type the following to compile it. +![rectangle.png](../src/misc/rectangle.png) - $ meson _build - $ ninja -C _build +There are lots of documentations in [Cairo's website](https://www.cairographics.org/). +If you aren't familiar with cairo, it is strongly recommended to read the [tutorial](https://www.cairographics.org/tutorial/) in the website. -The application is made in `_build` directory. -Type the following to execute it. +## GtkDrawingArea - $ _build/color +The following is a very simple example. -Type "red", "green", "blue", "white", black", "light" or "dark" in the TfeTextView. -Then, click on `Run` button. -Make sure the color of GtkDrawingArea changes. +~~~C + 1 #include + 2 + 3 static void + 4 draw_function (GtkDrawingArea *area, cairo_t *cr, int width, int height, gpointer user_data) { + 5 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); /* whilte */ + 6 cairo_paint (cr); + 7 cairo_set_line_width (cr, 2.0); + 8 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */ + 9 cairo_rectangle (cr, width/2.0 - 20.0, height/2.0 - 20.0, 40.0, 40.0); +10 cairo_stroke (cr); +11 } +12 +13 static void +14 on_activate (GApplication *app, gpointer user_data) { +15 GtkWidget *win = gtk_application_window_new (GTK_APPLICATION (app)); +16 GtkWidget *area = gtk_drawing_area_new (); +17 +18 gtk_window_set_title (GTK_WINDOW (win), "da1"); +19 /* Set initial size of width and height */ +20 gtk_drawing_area_set_content_width (GTK_DRAWING_AREA (area), 100); +21 gtk_drawing_area_set_content_height (GTK_DRAWING_AREA (area), 100); +22 gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (area), draw_function, NULL, NULL); +23 gtk_window_set_child (GTK_WINDOW (win), area); +24 +25 gtk_widget_show (win); +26 } +27 +28 int +29 main (int argc, char **argv) { +30 GtkApplication *app; +31 int stat; +32 +33 app = gtk_application_new ("com.github.ToshioCP.da1", G_APPLICATION_FLAGS_NONE); +34 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL); +35 stat =g_application_run (G_APPLICATION (app), argc, argv); +36 g_object_unref (app); +37 return stat; +38 } +39 +~~~ -In this program TfeTextView is used to change the color. -You can use buttons or menus instead of textview. -Probably it is more appropriate. -Using textview is unnatural. -It is a good practice to make such application by yourself. +The function `main` is almost same as before. +The two functions `on_activate` and `draw_function` is important in this example. -Up: [Readme.md](../Readme.md), Prev: [Section 20](sec20.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 20](sec20.md), Next: [Section 22](sec22.md) diff --git a/gfm/sec22.md b/gfm/sec22.md new file mode 100644 index 0000000..f989bf0 --- /dev/null +++ b/gfm/sec22.md @@ -0,0 +1,373 @@ +Up: [Readme.md](../Readme.md), Prev: [Section 21](sec21.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 + 2 + 3 + 4 color changer + 5 600 + 6 400 + 7 + 8 + 9 GTK_ORIENTATION_VERTICAL +10 +11 +12 GTK_ORIENTATION_HORIZONTAL +13 +14 +15 10 +16 +17 +18 +19 +20 Run +21 +22 +23 +24 +25 +26 Open +27 +28 +29 +30 +31 +32 TRUE +33 +34 +35 +36 +37 Save +38 +39 +40 +41 +42 +43 Close +44 +45 +46 +47 +48 +49 10 +50 +51 +52 +53 +54 +55 +56 GTK_ORIENTATION_HORIZONTAL +57 TRUE +58 +59 +60 TRUE +61 TRUE +62 +63 +64 GTK_WRAP_WORD_CHAR +65 +66 +67 +68 +69 +70 +71 TRUE +72 TRUE +73 +74 +75 +76 +77 +78 +79 +80 +~~~ + +- 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 +2 +3 +4 color.ui +5 +6 +~~~ + +## Tfetextview.h, tfetextview.c and color.h + +First two files are the same as before. +Color.h just includes tfetextview.h. + +~~~C +1 #include +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 21](sec21.md) diff --git a/src/sec19.src.md b/src/sec19.src.md index b80b769..dc63ea9 100644 --- a/src/sec19.src.md +++ b/src/sec19.src.md @@ -1,4 +1,4 @@ -# Upgrade text file editor +# GtkMenuButton, accelerators, font, pango and gsettings Traditional menu structure is fine. However, Buttons or menu items we often use are not so many. diff --git a/src/sec20.src.md b/src/sec20.src.md index 0f31154..7f66123 100644 --- a/src/sec20.src.md +++ b/src/sec20.src.md @@ -1,129 +1,280 @@ -# GtkDrawingArea and Cairo +# Template XML -If you want to draw dynamically, like an image window of gimp graphics editor, GtkDrawingArea widget is the most suitable widget. -You can draw or redraw an image in this widget freely. -It is called custom drawing. +The tfe program in the previous section is not so good because many things are crammed into `tfepplication.c`. +Many static variables in `tfepplication.c` shows that. -GtkDrawingArea provides a cairo context so users can draw images by cairo functions. -In this section, I will explain: +~~~C +static GtkDialog *pref; +static GtkFontButton *fontbtn; +static GSettings *settings; +static GtkDialog *alert; +static GtkLabel *lb_alert; +static GtkButton *btn_accept; -1. Cairo, but briefly. -2. GtkDrawingArea with very simple example. +static gulong pref_close_request_handler_id = 0; +static gulong alert_close_request_handler_id = 0; +static gboolean is_quit; +~~~ -## Cairo +Generally, if there are many global or static variables in the program, it is not a good program. +Such programs are difficult to maintain. -Cairo is a two dimensional graphics library. -First, you need to know surface, source, mask, destination, cairo context and transformation. +The file `tfeapplication.c` should be divided into several files. -- Surface represents an image. -It is like a canvas. -We can draw shapes and images with different colors on surfaces. -- Source pattern, or simply source, is a kind of paint, which will be transferred to destination surface by cairo functions. -- Mask is image mask used in the transference. -- Destination is a target surface. -- Cairo context manages the transference from source to destination through mask with its functions. -For example, `cairo_stroke` is a function to draw a path to the destination by the transference. -- Transformation is applied before the transfer completes. -The transformation is called affine, which is a mathematics terminology, and represented by matrix multiplication and vector addition. -Scaling, rotation, reflection, shearing and translation are examples of affine transformation. -In this section, we don't use it. -That means we only use identity transformation. -Therefore, the coordinate in source and mask is the same as the coordinate in destination. +- `tfeapplication.c` only has codes related to GtkApplication. +- A file about GtkApplicationWindow +- A file about a preference dialog +- A file about an alert dialog -![Stroke a rectangle](../image/cairo.png){width=9.0cm height=6.0cm} +The preference dialog is defined by a ui file. +And it has GtkBox, GtkLabel and GtkFontButton in it. +Such widget is called composite widget. +Composite widget is a child object of the parent widget. +For example, the preference composite widget is a child object of GtkDialog. +Composite widget can be built from template XML. +Next subsection shows how to build a preference dialog. -The instruction is as follows: +## Preference dialog -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. - -Here's a simple example code that draws a small square and save it as a png file. +First, write a template XML file. @@@include -misc/cairo.c +tfe7/tfepref.ui @@@ -- 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. +- 3: Template tag specifies a composite widget. +The value of a class attribute is the object name of the composite widget. +This XML file names the object "TfePref". +It is defined in a C source file and it will be shown later. +A parent attribute specifies the direct parent object of the composite widget. +`TfePref` is a child object of `GtkDialog`. +Therefore the value of the attribute is "GtkDialog". +A parent attribute is optional but it is recommended to specify. -To compile this, type the following. - - $ gcc `pkg-config --cflags cairo` cairo.c `pkg-config --libs cairo` - -![rectangle.png](misc/rectangle.png) - -There are lots of documentations in [Cairo's website](https://www.cairographics.org/). -If you aren't familiar with cairo, it is strongly recommended to read the [tutorial](https://www.cairographics.org/tutorial/) in the website. - -## GtkDrawingArea - -The following is a very simple example. +Other lines are the same as before. +The object `TfePref` is defined in `tfepref.h` and `tfepref.c`. @@@include -misc/da1.c +tfe7/tfepref.h @@@ -The function `main` is almost same as before. -The two functions `on_activate` and `draw_function` is important in this example. +- 6-7: When you define a new object, you need to write these two lines. +Refer to [Section 7](sec7.src.md). +- 9-10: `tfe_pref_new` generates a new TfePref object. +It has a parameter which the object use as a transient parent to show the dialog. -- 16: Generates a GtkDrawingArea object. -- 20,21: Sets the width and height of the contents of the GtkDrawingArea widget. -These width and height is the size of the destination surface of the cairo context provided by the widget. -- 22: Sets a drawing function of the widget. -GtkDrawingArea widget uses the function to draw the contents of itself whenever its necessary. -For example, when a user drag a mouse pointer and resize a top level window, GtkDrawingArea also changes the size. -Then, the whole window needs to be redrawn. +@@@include +tfe7/tfepref.c +@@@ -The drawing function has five parameters. +- 3-8: The structure of an instance of this object. +It has two variables, settings and fontbtn. +- 10: G\_DEFINE\_TYPE macro generates lines to register the type. +- 12-18: dispose handler. +This handler is called when this object is finalizing. +The process has two stages, disposing and finalizing. +When disposing, the object releases all the objects it has had. +TfePref object holds a GSetting object. +It is released in line 16. +After that parents dispose handler is called in line 17. +Refer to [Section 10](sec10.src.md). +- 27-34: Class initialization function. +This is called in the class generation process. +- 31: Set the dispose handler. +- 32: `gtk_widget_class_set_template_from_resource` function associates the description in the XML file with the widget. +At this moment no object is generated. +It just make the class to know the structure of the object. +That's why the top level tag is not an object but template in the XML file. +- 33: `gtk_widget_class_bind_template_child` function binds a private variable of the object with a child object in the template. +This function is a macro. +The name of the private variable (in the line 7) and the id (in the line 24) in the XML file must be the same. +In the program above, the name is `fontbtn`. +The pointer to the object will be assigned to the variable when an instance is generated. +- 20-25: Instance initialization function. +- 22: Initializes the template of this object. +The template has been made during the class initialization process. +Now it is implemented to the instance. +- 23: Create GSettings object with the id `com.github.ToshioCP.tfe`. +- 24: Bind the font key in the GSettings object and the font property in the GtkFontButton. - void drawing_function (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height, - gpointer user_data); +- 36-39: The function `tfe_pref_new` creates an instance of TfePref. +The parameter `win` is a transient parent. -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. +Now, It is very simple to use this dialog. +A caller just creates this object and shows it. -- 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. +~~~C +TfePref *pref; +pref = tfe_pref_new (win) /* win is the top level window */ +gtk_widget_show (GTK_WINDOW (win)); +~~~ -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. +This instance is automatically destroyed when a user clicks on the close button. +That's all. +If you want to show the dialog again, just create and show it. -![Square in the window](../image/da1.png) +## Alert dialog +It is almost same as preference dialog. + +Its XML file is: + +@@@include +tfe7/tfealert.ui +@@@ + +The header file is: + +@@@include +tfe7/tfealert.h +@@@ + +There are three public functions. +The functions `tfe_alert_set_message` and `tfe_alert_set_button_label` sets the label and button name of the alert dialog. +For example, if you want to show an alert that the user tries to close without saving the content, set them like: + +~~~C +tfe_alert_set_message (alert, "Are you really close without saving?"); /* alert points to a TfeAlert object */ +tfe_alert_set_button_label (alert, "Close"); +~~~ + +The function `tfe_alert_new` creates a TfeAlert dialog. + +The C source file is: + +@@@include +tfe7/tfealert.c +@@@ + +The program is almost same as `tfepref.c`. + +The instruction how to use this object is as follows. + +1. Write a "response" signal handler. +2. Create a TfeAlert object. +3. Connect "response" signal to a handler +4. Show the dialog +5. In the signal handler do something along the response-id. +Then destroy the dialog. + +## Top level window + +In the same way, create a child object of GtkApplicationWindow. +The object name is "TfeWindow". + +@@@include +tfe7/tfewindow.ui +@@@ + +This XML file is the same as before except template tag. + +@@@include +tfe7/tfewindow.h +@@@ + +There are three public functions. +The function `tfe_window_notebook_page_new` creates a new notebook page. +This is a wrapper function of `notebook_page_new`. +It is called by GtkApplication object. +The function `tfe_window_notebook_page_new_with_files` creates notebook pages with a contents read from the given files. +The function `tfe_window_new` creates a TfeWindow instance. + +@@@include +tfe7/tfewindow.c +@@@ + +- 20-32: `alert_response_cb` is a call back function of the "response" signal of TfeAlert dialog. +This is the same as before except `gtk_window_destroy(GTK_WINDOW (win))` is used instead of `tfe_application_quit`. +- 34-60: Handlers of Button clicked signal. +- 62-123: Handlers of action activated signal. +The `user_data` is a pointer to TfeWindow instance. +- 125-135: A handler of "changed::font" signal of GSettings object. +- 132: Gets the font from GSettings data. +- 133: Gets a PangoFontDescription from the font. +In the previous version, the program gets the font description from the GtkFontButton. +The button data and GSettings data are the same. +Therefore, the data got here is the same as the data in the GtkFontButton. +In addition, we don't need to worry about the preference dialog is alive or not thanks to the GSettings. +- 134: Sets CSS on the display with the font description. +- 137-152: Public functions. +- 155-161: Dispose handler. +The GSettings object needs to be released. +- 163-191: Object initialize function. +- 168: Generates a composite widget with the template. +- 170-173: Insert menu to the menu button. +- 175-176: Creates a GSettings object with the id. +Connects "changed::font" signal to the handler `changed_font_cb`. +This signal emits when the GSettings data is changed. +The second part "font" of the signal name "changed::font" is called details. +Signals can have details. +If a GSettings object has more than one key, "changed" signal emits only if the key which has the same name as the detail changes its value. +For example, Suppose a GSettings object has three keys "a", "b" and "c". + - "changed::a" is emitted when the value of "a" is changed. It isn't emitted when the value of "b" or "c" is changed. + - "changed::b" is emitted when the value of "b" is changed. It isn't emitted when the value of "a" or "c" is changed. + - "changed::c" is emitted when the value of "c" is changed. It isn't emitted when the value of "a" or "b" is changed. +In this version of tfe, there is only one key ("font"). +So, even if the signal doesn't have a detail, the result is the same. +But in the future version, it will probably need details. +- 178-188: Creates actions. +- 190: Sets CSS font. +- 193-207: Class initialization function. +- 197: Sets the dispose handler. +- 198: Sets the composite widget template +- 199-203: Binds private variable with child objects in the template. +- 204-206: Binds signal handlers with signal tags in the template. +- 209-212: `tfe_window_new`. +This function creates TfeWindow instance. + +## GtkApplication + +The file `tfeapplication.c` is now very simple. + +@@@include +tfe7/tfeapplication.c +@@@ + +- 3-11: Activate signal handler. +It uses `tfe_window_notebook_page_new` instead of `notebook_page_new`. +- 13-20: Open signal handler. +Thanks to `tfe_window_notebook_page_new_with_files`, this handler becomes very simple. +- 22-46: Startup signal handler. +Most of the task is moved to TfeWindow, the remaining task is creating a window and setting accelerations. +- 49-63: A function main. + +## Other files + +Resource XML file. + +@@@include +tfe7/tfe.gresource.xml +@@@ + +GSchema XML file + +@@@include +tfe7/com.github.ToshioCP.tfe.gschema.xml +@@@ + +Meson.build + +@@@include +tfe7/meson.build +@@@ + +## Compiling and installation. + +~~~ +$ meson --prefix=$HOME/local _build +$ ninja -C _build +$ ninja -C _build install +=== + +Source files are in [src/tfe7](tfe7) directory. + +We made a very small text editor. +You can add features to this editor. +When you add a new feature, care about the structure of the program. +Maybe you need to divide a file into several files like this section. +It isn't good to put many things into one file. +And it is important to think about the relationship between source files and widget structures. +It is appropriate that they correspond to each other in many cases. diff --git a/src/sec21.src.md b/src/sec21.src.md index 30f9774..0f31154 100644 --- a/src/sec21.src.md +++ b/src/sec21.src.md @@ -1,153 +1,129 @@ -# Combine GtkDrawingArea and TfeTextView +# GtkDrawingArea and Cairo -Now, we will make a new application which has GtkDrawingArea and TfeTextView in it. -Its name is "color". -If you write a color in TfeTextView and click on the `run` button, then the color of GtkDrawingArea changes to the color given by you. +If you want to draw dynamically, like an image window of gimp graphics editor, GtkDrawingArea widget is the most suitable widget. +You can draw or redraw an image in this widget freely. +It is called custom drawing. -![color](../image/color.png){width=7.0cm height=5.13cm} +GtkDrawingArea provides a cairo context so users can draw images by cairo functions. +In this section, I will explain: -The following colors are available. +1. Cairo, but briefly. +2. GtkDrawingArea with very simple example. -- white -- black -- red -- green -- blue +## Cairo -In addition the following two options are also available. +Cairo is a two dimensional graphics library. +First, you need to know surface, source, mask, destination, cairo context and transformation. -- light: Make the color of the drawing area lighter. -- dark: Make the color of the drawing area darker. +- Surface represents an image. +It is like a canvas. +We can draw shapes and images with different colors on surfaces. +- Source pattern, or simply source, is a kind of paint, which will be transferred to destination surface by cairo functions. +- Mask is image mask used in the transference. +- Destination is a target surface. +- Cairo context manages the transference from source to destination through mask with its functions. +For example, `cairo_stroke` is a function to draw a path to the destination by the transference. +- Transformation is applied before the transfer completes. +The transformation is called affine, which is a mathematics terminology, and represented by matrix multiplication and vector addition. +Scaling, rotation, reflection, shearing and translation are examples of affine transformation. +In this section, we don't use it. +That means we only use identity transformation. +Therefore, the coordinate in source and mask is the same as the coordinate in destination. -This application can only do very simple things. -However, it tells us that if we add powerful parser to it, we will be able to make it more efficient. -I want to show it to you in the later section by making a turtle graphics language like Logo program language. +![Stroke a rectangle](../image/cairo.png){width=9.0cm height=6.0cm} -In this section, we focus on how to bind the two objects. +The instruction is as follows: -## Color.ui and color.gresource.xml +1. Create a surface. +This will be a destination. +2. Create a cairo context with the surface and the surface will be the destination of the context. +3. Create a source pattern within the context. +4. Create paths, which are lines, rectangles, arcs, texts or more complicated shapes, to generate a mask. +5. Use drawing operator such as `cairo_stroke` to transfer the paint in the source to the destination. +6. Save the destination surface to a file if necessary. -First, We need to make the ui file of the widgets. -The image in the previous subsection gives us the structure of the widgets. -Title bar, four buttons in the tool bar and two widgets textview and drawing area. -The ui file is as follows. +Here's a simple example code that draws a small square and save it as a png file. @@@include -color/color.ui +misc/cairo.c @@@ -- 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. +- 1: Includes the header file of cairo. +- 12: `cairo_image_surface_create` creates an image surface. +`CAIRO_FORMAT_RGB24` is a constant which means that each pixel has red, green and blue data. +Each data has 8 bit quantity. +Modern displays have this type of color depth. +Width and height are pixels and given as integers. +- 13: Creates cairo context. +The surface given as an argument will be the destination of the context. +- 17: `cairo_set_source_rgb` creates a source pattern, which is a solid white paint. +The second to fourth argument is red, green and blue color depth respectively. +Their type is float and the values are between zero and one. +(0,0,0) is black and (1,1,1) is white. +- 18: `cairo_paint` copies everywhere in the source to destination. +The destination is filled with white pixels by this command. +- 20: Sets the source color to black. +- 21: `cairo_set_line_width` set the width of lines. +In this case, the line width is set to two pixels. +(It is because the transformation is identity. +If the transformation isn't identity, for example scaling with the factor three, the actual width in destination will be six (2x3=6) pixels.) +- 22: Draws a rectangle (square). +The top-left coordinate is (width/2.0-20.0, height/2.0-20.0) and the width and height have the same length 40.0. +- 23: `cairo_stroke` transfer the source to destination through the rectangle in mask. +- 26: Outputs the image to a png file `rectangle.png`. +- 27: Destroys the context. At the same time the source is destroyed. +- 28: Destroys the destination surface. -The xml file for the resource compiler is almost same as before. -Just substitute "color" for "tfe". +To compile this, type the following. + + $ gcc `pkg-config --cflags cairo` cairo.c `pkg-config --libs cairo` + +![rectangle.png](misc/rectangle.png) + +There are lots of documentations in [Cairo's website](https://www.cairographics.org/). +If you aren't familiar with cairo, it is strongly recommended to read the [tutorial](https://www.cairographics.org/tutorial/) in the website. + +## GtkDrawingArea + +The following is a very simple example. @@@include -color/color.gresource.xml +misc/da1.c @@@ -## Tfetextview.h, tfetextview.c and color.h +The function `main` is almost same as before. +The two functions `on_activate` and `draw_function` is important in this example. -First two files are the same as before. -Color.h just includes tfetextview.h. +- 16: Generates a GtkDrawingArea object. +- 20,21: Sets the width and height of the contents of the GtkDrawingArea widget. +These width and height is the size of the destination surface of the cairo context provided by the widget. +- 22: Sets a drawing function of the widget. +GtkDrawingArea widget uses the function to draw the contents of itself whenever its necessary. +For example, when a user drag a mouse pointer and resize a top level window, GtkDrawingArea also changes the size. +Then, the whole window needs to be redrawn. -@@@include -color/color.h -@@@ +The drawing function has five parameters. -## Colorapplication.c + void drawing_function (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height, + gpointer user_data); -This is the main file. -It deals with: +The first parameter is the GtkDrawingArea widget which calls the drawing function. +However, you can't change any properties, for example `content-width` or `content-height`, in this function. +The second parameter is a cairo context given by the widget. +The destination surface of the context is connected to the contents of the widget. +What you draw to this surface will appear in the widget on the screen. +The third and fourth parameters are the size of the destination surface. -- Building widgets by GtkBuilder. -- Seting a drawing function of GtkDrawingArea. -And connecting a handler to "resize" signal on GtkDrawingArea. -- Implementing each call back functions. -Particularly, `Run` signal handler is the point in this program. +- 3-11: The drawing function. +- 4-5: Sets the source to be white and paint the destination white. +- 7: Sets the line width to be 2. +- 8: Sets the source to be black. +- 9: Adds a rectangle to the mask. +- 10: Draws the rectangle with black color to the destination. -The following is `colorapplication.c`. +Compile and run it, then a window with a black rectangle (square) appears. +Try resizing the window. +The square always appears at the center of the window because the drawing function is invoked every moment the window is resized. -@@@include -color/colorapplication.c -@@@ +![Square in the window](../image/da1.png) -- 108-121: The function `main` is almost same as before but there are some differences. -The application ID is "com.github.ToshioCP.color". -`G_APPLICATION_FLAGS_NONE` is specified so no open signal handler is necessary. -- 86-106: Startup handler. -- 91-96: Builds widgets. -The pointers of the top window, TfeTextView and GtkDrawingArea objects are stored to static variables `win`, `tv` and `da` respectively. -This is because these objects are often used in handlers. -They never be rewritten so they're thread safe. -- 97: connects "resize" signal and the handler. -- 98: sets the drawing function. -- 81-84: Activates handler, which just shows the widgets. -- 73-79: The drawing function. -It just copies `surface` to destination. -- 65-71: Resize handler. -Re-creates the surface to fit the width and height of the drawing area and paints by calling the function `run`. -- 58-63: Closes the handler. -It destroys `surface` if it exists. -Then it destroys the top window and quits the application. -- 48-56: Open and save handler. -They just call the corresponding functions of TfeTextView. -- 42-46: Run handler. -It calls run function to paint the surface. -After that `gtk_widget_queue_draw` is called. -This fhunction adds the widget (GtkDrawingArea) to the queue to be redrawn. -It is important to know that the drawing function is called when it is necessary. -For example, when another window is moved and uncovers part of the widget, or when the window containing it is resized. -But repaint of `surface` is not automatically notified to gtk. -Therefore, you need to call `gtk_widget_queue_draw` to redraw the widget. -- 9-40: Run function paints the surface. -First, it gets the contents of GtkTextBuffer. -Then it compares it to "red", "green" and so on. -If it matches the color, then the surface is painted the color. -If it matches "light" or "dark", then the color of the surface is lightened or darkened respectively. -Alpha channel is used. - -## Meson.build - -This file is almost same as before. -An argument "export_dynamic: true" is added to executable function. - -@@@include -color/meson.build -@@@ - -## Compile and execute it - -First you need to export some variables (refer to [Section 2](sec2.src.md)). - - $ . env.sh - -Then type the following to compile it. - - $ meson _build - $ ninja -C _build - -The application is made in `_build` directory. -Type the following to execute it. - - $ _build/color - -Type "red", "green", "blue", "white", black", "light" or "dark" in the TfeTextView. -Then, click on `Run` button. -Make sure the color of GtkDrawingArea changes. - -In this program TfeTextView is used to change the color. -You can use buttons or menus instead of textview. -Probably it is more appropriate. -Using textview is unnatural. -It is a good practice to make such application by yourself. diff --git a/src/sec22.src.md b/src/sec22.src.md new file mode 100644 index 0000000..30f9774 --- /dev/null +++ b/src/sec22.src.md @@ -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. diff --git a/src/tfe7/com.github.ToshioCP.tfe.gschema.xml b/src/tfe7/com.github.ToshioCP.tfe.gschema.xml new file mode 100644 index 0000000..90f26ca --- /dev/null +++ b/src/tfe7/com.github.ToshioCP.tfe.gschema.xml @@ -0,0 +1,10 @@ + + + + + 'Monospace 12' + Font + The font to be used for textview. + + + diff --git a/src/tfe7/css.c b/src/tfe7/css.c new file mode 100644 index 0000000..ca183cd --- /dev/null +++ b/src/tfe7/css.c @@ -0,0 +1,92 @@ +/* css.h */ +#include + +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); +} diff --git a/src/tfe7/css.h b/src/tfe7/css.h new file mode 100644 index 0000000..908aed2 --- /dev/null +++ b/src/tfe7/css.h @@ -0,0 +1,14 @@ +#ifndef __TFE_CSS_H__ +#define __TFE_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); + +#endif /* __TFE_CSS_H__ */ + diff --git a/src/tfe7/menu.ui b/src/tfe7/menu.ui new file mode 100755 index 0000000..20477ce --- /dev/null +++ b/src/tfe7/menu.ui @@ -0,0 +1,27 @@ + + + +
+ + New + win.new + + + Save As… + win.saveas + +
+
+ + Preference + win.pref + +
+
+ + Quit + win.close-all + +
+
+
diff --git a/src/tfe7/meson.build b/src/tfe7/meson.build new file mode 100644 index 0000000..a2bbb51 --- /dev/null +++ b/src/tfe7/meson.build @@ -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', 'tfewindow.c', 'tfenotebook.c', 'tfepref.c', 'tfealert.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) + diff --git a/src/tfe7/tfe.gresource.xml b/src/tfe7/tfe.gresource.xml new file mode 100644 index 0000000..66127fa --- /dev/null +++ b/src/tfe7/tfe.gresource.xml @@ -0,0 +1,9 @@ + + + + tfewindow.ui + tfepref.ui + tfealert.ui + menu.ui + + diff --git a/src/tfe7/tfealert.c b/src/tfe7/tfealert.c new file mode 100644 index 0000000..9ff7ab3 --- /dev/null +++ b/src/tfe7/tfealert.c @@ -0,0 +1,38 @@ +#include "tfealert.h" + +struct _TfeAlert +{ + GtkDialog parent; + GtkLabel *lb_alert; + GtkButton *btn_accept; +}; + +G_DEFINE_TYPE (TfeAlert, tfe_alert, GTK_TYPE_DIALOG); + +void +tfe_alert_set_message (TfeAlert *alert, const char *message) { + gtk_label_set_text (alert->lb_alert, message); +} + +void +tfe_alert_set_button_label (TfeAlert *alert, const char *label) { + gtk_button_set_label (alert->btn_accept, label); +} + +static void +tfe_alert_init (TfeAlert *alert) { + gtk_widget_init_template (GTK_WIDGET (alert)); +} + +static void +tfe_alert_class_init (TfeAlertClass *class) { + gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class), "/com/github/ToshioCP/tfe/tfealert.ui"); + gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeAlert, lb_alert); + gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeAlert, btn_accept); +} + +GtkWidget * +tfe_alert_new (GtkWindow *win) { + return GTK_WIDGET (g_object_new (TFE_TYPE_ALERT, "transient-for", win, NULL)); +} + diff --git a/src/tfe7/tfealert.h b/src/tfe7/tfealert.h new file mode 100644 index 0000000..b1c6cc5 --- /dev/null +++ b/src/tfe7/tfealert.h @@ -0,0 +1,19 @@ +#ifndef __TFE_ALERT_H__ +#define __TFE_ALERT_H__ + +#include + +#define TFE_TYPE_ALERT tfe_alert_get_type () +G_DECLARE_FINAL_TYPE (TfeAlert, tfe_alert, TFE, ALERT, GtkDialog) + +void +tfe_alert_set_message (TfeAlert *alert, const char *message); + +void +tfe_alert_set_button_label (TfeAlert *alert, const char *label); + +GtkWidget * +tfe_alert_new (GtkWindow *win); + +#endif /* __TFE_ALERT_H__ */ + diff --git a/src/tfe7/tfealert.ui b/src/tfe7/tfealert.ui new file mode 100644 index 0000000..a78eeb3 --- /dev/null +++ b/src/tfe7/tfealert.ui @@ -0,0 +1,47 @@ + + + + + diff --git a/src/tfe7/tfeapplication.c b/src/tfe7/tfeapplication.c new file mode 100644 index 0000000..5663655 --- /dev/null +++ b/src/tfe7/tfeapplication.c @@ -0,0 +1,64 @@ +#include "tfewindow.h" + +/* ----- 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)); + + tfe_window_notebook_page_new (win); + 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)); + + tfe_window_notebook_page_new_with_files (win, files, n_files); + gtk_widget_show (win); +} + +static void +tfe_startup (GApplication *application) { + GtkApplication *app = GTK_APPLICATION (application); + GtkBuilder *build; + TfeWindow *win; + int i; + + win = tfe_window_new (app); + +/* ----- accelerator ----- */ + struct { + const char *action; + const char *accels[2]; + } action_accels[] = { + { "win.open", { "o", NULL } }, + { "win.save", { "s", NULL } }, + { "win.close", { "w", NULL } }, + { "win.new", { "n", NULL } }, + { "win.saveas", { "s", NULL } }, + { "win.close-all", { "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); +} + +/* ----- 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; +} + diff --git a/src/tfe7/tfenotebook.c b/src/tfe7/tfenotebook.c new file mode 100644 index 0000000..1f56d5c --- /dev/null +++ b/src/tfe7/tfenotebook.c @@ -0,0 +1,211 @@ +#include "tfenotebook.h" +#include "../tfetextview/tfetextview.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); + gtk_window_destroy (GTK_WINDOW (win)); + } else { + i = gtk_notebook_get_current_page (nb); + gtk_notebook_remove_page (GTK_NOTEBOOK (nb), i); + } +} + +static void +notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) { + GtkWidget *scr = 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); +} + diff --git a/src/tfe7/tfenotebook.h b/src/tfe7/tfenotebook.h new file mode 100644 index 0000000..1ab05eb --- /dev/null +++ b/src/tfe7/tfenotebook.h @@ -0,0 +1,31 @@ +#ifndef __TFE_NOTEBOOK_H__ +#define __TFE_NOTEBOOK_H__ + +#include + +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); + +#endif /* __TFE_NOTEBOOK_H__ */ + diff --git a/src/tfe7/tfepref.c b/src/tfe7/tfepref.c new file mode 100644 index 0000000..75e60b1 --- /dev/null +++ b/src/tfe7/tfepref.c @@ -0,0 +1,40 @@ +#include "tfepref.h" + +struct _TfePref +{ + GtkDialog parent; + GSettings *settings; + GtkFontButton *fontbtn; +}; + +G_DEFINE_TYPE (TfePref, tfe_pref, GTK_TYPE_DIALOG); + +static void +tfe_pref_dispose (GObject *gobject) { + TfePref *pref = TFE_PREF (gobject); + + g_clear_object (&pref->settings); + G_OBJECT_CLASS (tfe_pref_parent_class)->dispose (gobject); +} + +static void +tfe_pref_init (TfePref *pref) { + gtk_widget_init_template (GTK_WIDGET (pref)); + pref->settings = g_settings_new ("com.github.ToshioCP.tfe"); + g_settings_bind (pref->settings, "font", pref->fontbtn, "font", G_SETTINGS_BIND_DEFAULT); +} + +static void +tfe_pref_class_init (TfePrefClass *class) { + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->dispose = tfe_pref_dispose; + gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class), "/com/github/ToshioCP/tfe/tfepref.ui"); + gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfePref, fontbtn); +} + +GtkWidget * +tfe_pref_new (GtkWindow *win) { + return GTK_WIDGET (g_object_new (TFE_TYPE_PREF, "transient-for", win, NULL)); +} + diff --git a/src/tfe7/tfepref.h b/src/tfe7/tfepref.h new file mode 100644 index 0000000..9167b0f --- /dev/null +++ b/src/tfe7/tfepref.h @@ -0,0 +1,13 @@ +#ifndef __TFE_PREF_H__ +#define __TFE_PREF_H__ + +#include + +#define TFE_TYPE_PREF tfe_pref_get_type () +G_DECLARE_FINAL_TYPE (TfePref, tfe_pref, TFE, PREF, GtkDialog) + +GtkWidget * +tfe_pref_new (GtkWindow *win); + +#endif /* __TFE_PREF_H__ */ + diff --git a/src/tfe7/tfepref.ui b/src/tfe7/tfepref.ui new file mode 100644 index 0000000..26af032 --- /dev/null +++ b/src/tfe7/tfepref.ui @@ -0,0 +1,33 @@ + + + + + diff --git a/src/tfe7/tfewindow.c b/src/tfe7/tfewindow.c new file mode 100644 index 0000000..baa6269 --- /dev/null +++ b/src/tfe7/tfewindow.c @@ -0,0 +1,213 @@ +#include "tfewindow.h" +#include "tfenotebook.h" +#include "tfepref.h" +#include "tfealert.h" +#include "css.h" + +struct _TfeWindow { + GtkApplicationWindow parent; + GtkButton *btno; + GtkButton *btns; + GtkButton *btnc; + GtkMenuButton *btnm; + GtkNotebook *nb; + GSettings *settings; + gboolean is_quit; +}; + +G_DEFINE_TYPE (TfeWindow, tfe_window, GTK_TYPE_APPLICATION_WINDOW); + +/* alert response signal handler */ +static void +alert_response_cb (GtkDialog *alert, int response_id, gpointer user_data) { + TfeWindow *win = TFE_WINDOW (user_data); + + if (response_id == GTK_RESPONSE_ACCEPT) { + if (win->is_quit) + gtk_window_destroy(GTK_WINDOW (win)); + else + notebook_page_close (win->nb); + } + gtk_window_destroy (GTK_WINDOW (alert)); +} + +/* ----- 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) { + TfeAlert *alert; + TfeWindow *win = TFE_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (nb), TFE_TYPE_WINDOW)); + + if (has_saved (nb)) + notebook_page_close (nb); + else { + win->is_quit = false; + alert = TFE_ALERT (tfe_alert_new (GTK_WINDOW (win))); + tfe_alert_set_message (alert, "Contents aren't saved yet.\nAre you sure to close?"); + tfe_alert_set_button_label (alert, "Close"); + g_signal_connect (GTK_DIALOG (alert), "response", G_CALLBACK (alert_response_cb), win); + gtk_widget_show (GTK_WIDGET (alert)); + } +} + +/* ----- action activated handlers ----- */ +static void +open_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) { + TfeWindow *win = TFE_WINDOW (user_data); + + open_cb (GTK_NOTEBOOK (win->nb)); +} + +static void +save_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) { + TfeWindow *win = TFE_WINDOW (user_data); + + save_cb (GTK_NOTEBOOK (win->nb)); +} + +static void +close_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) { + TfeWindow *win = TFE_WINDOW (user_data); + + close_cb (GTK_NOTEBOOK (win->nb)); +} + +static void +new_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) { + TfeWindow *win = TFE_WINDOW (user_data); + + notebook_page_new (GTK_NOTEBOOK (win->nb)); +} + +static void +saveas_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) { + TfeWindow *win = TFE_WINDOW (user_data); + + notebook_page_saveas (GTK_NOTEBOOK (win->nb)); +} + +static void +pref_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) { + TfeWindow *win = TFE_WINDOW (user_data); + GtkWidget *pref; + + pref = tfe_pref_new (GTK_WINDOW (win)); + gtk_widget_show (pref); +} + +static void +quit_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) { + TfeWindow *win = TFE_WINDOW (user_data); + + TfeAlert *alert; + + if (has_saved_all (GTK_NOTEBOOK (win->nb))) + gtk_window_destroy (GTK_WINDOW (win)); + else { + win->is_quit = true; + alert = TFE_ALERT (tfe_alert_new (GTK_WINDOW (win))); + tfe_alert_set_message (alert, "Contents aren't saved yet.\nAre you sure to quit?"); + tfe_alert_set_button_label (alert, "Quit"); + g_signal_connect (GTK_DIALOG (alert), "response", G_CALLBACK (alert_response_cb), win); + gtk_widget_show (GTK_WIDGET (alert)); + } +} + +/* gsettings changed::font signal handler */ +static void +changed_font_cb (GSettings *settings, char *key, gpointer user_data) { + GtkWindow *win = GTK_WINDOW (user_data); + const char *font; + PangoFontDescription *pango_font_desc; + + font = g_settings_get_string (settings, "font"); + pango_font_desc = pango_font_description_from_string (font); + set_font_for_display_with_pango_font_desc (win, pango_font_desc); +} + +/* --- public functions --- */ + +void +tfe_window_notebook_page_new (TfeWindow *win) { + notebook_page_new (win->nb); +} + +void +tfe_window_notebook_page_new_with_files (TfeWindow *win, GFile **files, int n_files) { + int i; + + for (i = 0; i < n_files; i++) + notebook_page_new_with_file (win->nb, files[i]); + if (gtk_notebook_get_n_pages (win->nb) == 0) + notebook_page_new (win->nb); +} + +/* --- TfeWindow object construction/destruction --- */ +static void +tfe_window_dispose (GObject *gobject) { + TfeWindow *window = TFE_WINDOW (gobject); + + g_clear_object (&window->settings); + G_OBJECT_CLASS (tfe_window_parent_class)->dispose (gobject); +} + +static void +tfe_window_init (TfeWindow *win) { + GtkBuilder *build; + GMenuModel *menu; + + gtk_widget_init_template (GTK_WIDGET (win)); + + 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 (win->btnm, menu); + g_object_unref(build); + + win->settings = g_settings_new ("com.github.ToshioCP.tfe"); + g_signal_connect (win->settings, "changed::font", G_CALLBACK (changed_font_cb), win); + +/* ----- 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), win); + + changed_font_cb(win->settings, "font", win); +} + +static void +tfe_window_class_init (TfeWindowClass *class) { + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->dispose = tfe_window_dispose; + gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class), "/com/github/ToshioCP/tfe/tfewindow.ui"); + gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeWindow, btno); + gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeWindow, btns); + gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeWindow, btnc); + gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeWindow, btnm); + gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeWindow, nb); + gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), open_cb); + gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), save_cb); + gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), close_cb); +} + +GtkWidget * +tfe_window_new (GtkApplication *app) { + return GTK_WIDGET (g_object_new (TFE_TYPE_WINDOW, "application", app, NULL)); +} + diff --git a/src/tfe7/tfewindow.h b/src/tfe7/tfewindow.h new file mode 100644 index 0000000..11a4e79 --- /dev/null +++ b/src/tfe7/tfewindow.h @@ -0,0 +1,19 @@ +#ifndef __TFE_WINDOW_H__ +#define __TFE_WINDOW_H__ + +#include + +#define TFE_TYPE_WINDOW tfe_window_get_type () +G_DECLARE_FINAL_TYPE (TfeWindow, tfe_window, TFE, WINDOW, GtkApplicationWindow) + +void +tfe_window_notebook_page_new (TfeWindow *win); + +void +tfe_window_notebook_page_new_with_files (TfeWindow *win, GFile **files, int n_files); + +GtkWidget * +tfe_window_new (GtkApplication *app); + +#endif /* __TFE_WINDOW_H__ */ + diff --git a/src/tfe7/tfewindow.ui b/src/tfe7/tfewindow.ui new file mode 100644 index 0000000..77dc382 --- /dev/null +++ b/src/tfe7/tfewindow.ui @@ -0,0 +1,65 @@ + + + + +