1 | #include <gtk/gtk.h> |
2 | |
3 | #include "exampleapp.h" |
4 | #include "exampleappwin.h" |
5 | |
6 | struct _ExampleAppWindow |
7 | { |
8 | GtkApplicationWindow parent; |
9 | |
10 | GSettings *settings; |
11 | GtkWidget *stack; |
12 | GtkWidget *gears; |
13 | GtkWidget *search; |
14 | GtkWidget *searchbar; |
15 | GtkWidget *searchentry; |
16 | GtkWidget *; |
17 | GtkWidget *words; |
18 | GtkWidget *lines; |
19 | GtkWidget *lines_label; |
20 | }; |
21 | |
22 | G_DEFINE_TYPE (ExampleAppWindow, example_app_window, GTK_TYPE_APPLICATION_WINDOW) |
23 | |
24 | static void |
25 | search_text_changed (GtkEntry *entry, |
26 | ExampleAppWindow *win) |
27 | { |
28 | const char *text; |
29 | GtkWidget *tab; |
30 | GtkWidget *view; |
31 | GtkTextBuffer *buffer; |
32 | GtkTextIter start, match_start, match_end; |
33 | |
34 | text = gtk_editable_get_text (GTK_EDITABLE (entry)); |
35 | |
36 | if (text[0] == '\0') |
37 | return; |
38 | |
39 | tab = gtk_stack_get_visible_child (GTK_STACK (win->stack)); |
40 | view = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (tab)); |
41 | buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); |
42 | |
43 | /* Very simple-minded search implementation */ |
44 | gtk_text_buffer_get_start_iter (buffer, iter: &start); |
45 | if (gtk_text_iter_forward_search (iter: &start, str: text, flags: GTK_TEXT_SEARCH_CASE_INSENSITIVE, |
46 | match_start: &match_start, match_end: &match_end, NULL)) |
47 | { |
48 | gtk_text_buffer_select_range (buffer, ins: &match_start, bound: &match_end); |
49 | gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (view), iter: &match_start, |
50 | within_margin: 0.0, FALSE, xalign: 0.0, yalign: 0.0); |
51 | } |
52 | } |
53 | |
54 | static void |
55 | find_word (GtkButton *button, |
56 | ExampleAppWindow *win) |
57 | { |
58 | const char *word; |
59 | |
60 | word = gtk_button_get_label (button); |
61 | gtk_editable_set_text (GTK_EDITABLE (win->searchentry), text: word); |
62 | } |
63 | |
64 | static void |
65 | update_words (ExampleAppWindow *win) |
66 | { |
67 | GHashTable *strings; |
68 | GHashTableIter iter; |
69 | GtkWidget *tab, *view, *row; |
70 | GtkTextBuffer *buffer; |
71 | GtkTextIter start, end; |
72 | char *word, *key; |
73 | GtkWidget *child; |
74 | |
75 | tab = gtk_stack_get_visible_child (GTK_STACK (win->stack)); |
76 | |
77 | if (tab == NULL) |
78 | return; |
79 | |
80 | view = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (tab)); |
81 | buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); |
82 | |
83 | strings = g_hash_table_new_full (hash_func: g_str_hash, key_equal_func: g_str_equal, key_destroy_func: g_free, NULL); |
84 | |
85 | gtk_text_buffer_get_start_iter (buffer, iter: &start); |
86 | while (!gtk_text_iter_is_end (iter: &start)) |
87 | { |
88 | while (!gtk_text_iter_starts_word (iter: &start)) |
89 | { |
90 | if (!gtk_text_iter_forward_char (iter: &start)) |
91 | goto done; |
92 | } |
93 | end = start; |
94 | if (!gtk_text_iter_forward_word_end (iter: &end)) |
95 | goto done; |
96 | word = gtk_text_buffer_get_text (buffer, start: &start, end: &end, FALSE); |
97 | g_hash_table_add (hash_table: strings, key: g_utf8_strdown (str: word, len: -1)); |
98 | g_free (mem: word); |
99 | start = end; |
100 | } |
101 | |
102 | done: |
103 | while ((child = gtk_widget_get_first_child (widget: win->words))) |
104 | gtk_list_box_remove (GTK_LIST_BOX (win->words), child); |
105 | |
106 | g_hash_table_iter_init (iter: &iter, hash_table: strings); |
107 | while (g_hash_table_iter_next (iter: &iter, key: (gpointer *)&key, NULL)) |
108 | { |
109 | row = gtk_button_new_with_label (label: key); |
110 | g_signal_connect (row, "clicked" , |
111 | G_CALLBACK (find_word), win); |
112 | gtk_list_box_insert (GTK_LIST_BOX (win->words), child: row, position: -1); |
113 | } |
114 | |
115 | g_hash_table_unref (hash_table: strings); |
116 | } |
117 | |
118 | static void |
119 | update_lines (ExampleAppWindow *win) |
120 | { |
121 | GtkWidget *tab, *view; |
122 | GtkTextBuffer *buffer; |
123 | int count; |
124 | char *lines; |
125 | |
126 | tab = gtk_stack_get_visible_child (GTK_STACK (win->stack)); |
127 | |
128 | if (tab == NULL) |
129 | return; |
130 | |
131 | view = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (tab)); |
132 | buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); |
133 | |
134 | count = gtk_text_buffer_get_line_count (buffer); |
135 | lines = g_strdup_printf (format: "%d" , count); |
136 | gtk_label_set_text (GTK_LABEL (win->lines), str: lines); |
137 | g_free (mem: lines); |
138 | } |
139 | |
140 | static void |
141 | visible_child_changed (GObject *stack, |
142 | GParamSpec *pspec, |
143 | ExampleAppWindow *win) |
144 | { |
145 | if (gtk_widget_in_destruction (GTK_WIDGET (stack))) |
146 | return; |
147 | |
148 | gtk_search_bar_set_search_mode (GTK_SEARCH_BAR (win->searchbar), FALSE); |
149 | update_words (win); |
150 | update_lines (win); |
151 | } |
152 | |
153 | static void |
154 | words_changed (GObject *, |
155 | GParamSpec *pspec, |
156 | ExampleAppWindow *win) |
157 | { |
158 | update_words (win); |
159 | } |
160 | |
161 | static void |
162 | example_app_window_init (ExampleAppWindow *win) |
163 | { |
164 | GtkBuilder *builder; |
165 | GMenuModel *; |
166 | GAction *action; |
167 | |
168 | gtk_widget_init_template (GTK_WIDGET (win)); |
169 | |
170 | builder = gtk_builder_new_from_resource (resource_path: "/org/gtk/exampleapp/gears-menu.ui" ); |
171 | menu = G_MENU_MODEL (gtk_builder_get_object (builder, "menu" )); |
172 | gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (win->gears), menu_model: menu); |
173 | g_object_unref (object: builder); |
174 | |
175 | win->settings = g_settings_new (schema_id: "org.gtk.exampleapp" ); |
176 | |
177 | g_settings_bind (settings: win->settings, key: "transition" , |
178 | object: win->stack, property: "transition-type" , |
179 | flags: G_SETTINGS_BIND_DEFAULT); |
180 | |
181 | g_settings_bind (settings: win->settings, key: "show-words" , |
182 | object: win->sidebar, property: "reveal-child" , |
183 | flags: G_SETTINGS_BIND_DEFAULT); |
184 | |
185 | g_object_bind_property (source: win->search, source_property: "active" , |
186 | target: win->searchbar, target_property: "search-mode-enabled" , |
187 | flags: G_BINDING_BIDIRECTIONAL); |
188 | |
189 | g_signal_connect (win->sidebar, "notify::reveal-child" , |
190 | G_CALLBACK (words_changed), win); |
191 | |
192 | action = g_settings_create_action (settings: win->settings, key: "show-words" ); |
193 | g_action_map_add_action (G_ACTION_MAP (win), action); |
194 | g_object_unref (object: action); |
195 | |
196 | action = (GAction*) g_property_action_new (name: "show-lines" , object: win->lines, property_name: "visible" ); |
197 | g_action_map_add_action (G_ACTION_MAP (win), action); |
198 | g_object_unref (object: action); |
199 | |
200 | g_object_bind_property (source: win->lines, source_property: "visible" , |
201 | target: win->lines_label, target_property: "visible" , |
202 | flags: G_BINDING_DEFAULT); |
203 | } |
204 | |
205 | static void |
206 | example_app_window_dispose (GObject *object) |
207 | { |
208 | ExampleAppWindow *win; |
209 | |
210 | win = EXAMPLE_APP_WINDOW (ptr: object); |
211 | |
212 | g_clear_object (&win->settings); |
213 | |
214 | G_OBJECT_CLASS (example_app_window_parent_class)->dispose (object); |
215 | } |
216 | |
217 | static void |
218 | example_app_window_class_init (ExampleAppWindowClass *class) |
219 | { |
220 | G_OBJECT_CLASS (class)->dispose = example_app_window_dispose; |
221 | |
222 | gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class), |
223 | resource_name: "/org/gtk/exampleapp/window.ui" ); |
224 | |
225 | gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), ExampleAppWindow, stack); |
226 | gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), ExampleAppWindow, gears); |
227 | gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), ExampleAppWindow, search); |
228 | gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), ExampleAppWindow, searchbar); |
229 | gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), ExampleAppWindow, searchentry); |
230 | gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), ExampleAppWindow, words); |
231 | gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), ExampleAppWindow, sidebar); |
232 | gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), ExampleAppWindow, lines); |
233 | gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), ExampleAppWindow, lines_label); |
234 | |
235 | gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), search_text_changed); |
236 | gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), visible_child_changed); |
237 | } |
238 | |
239 | ExampleAppWindow * |
240 | example_app_window_new (ExampleApp *app) |
241 | { |
242 | return g_object_new (EXAMPLE_APP_WINDOW_TYPE, first_property_name: "application" , app, NULL); |
243 | } |
244 | |
245 | void |
246 | example_app_window_open (ExampleAppWindow *win, |
247 | GFile *file) |
248 | { |
249 | char *basename; |
250 | GtkWidget *scrolled, *view; |
251 | char *contents; |
252 | gsize length; |
253 | GtkTextBuffer *buffer; |
254 | GtkTextTag *tag; |
255 | GtkTextIter start_iter, end_iter; |
256 | |
257 | basename = g_file_get_basename (file); |
258 | |
259 | scrolled = gtk_scrolled_window_new (); |
260 | gtk_widget_set_hexpand (widget: scrolled, TRUE); |
261 | gtk_widget_set_vexpand (widget: scrolled, TRUE); |
262 | view = gtk_text_view_new (); |
263 | gtk_text_view_set_editable (GTK_TEXT_VIEW (view), FALSE); |
264 | gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (view), FALSE); |
265 | gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scrolled), child: view); |
266 | gtk_stack_add_titled (GTK_STACK (win->stack), child: scrolled, name: basename, title: basename); |
267 | |
268 | buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); |
269 | |
270 | if (g_file_load_contents (file, NULL, contents: &contents, length: &length, NULL, NULL)) |
271 | { |
272 | gtk_text_buffer_set_text (buffer, text: contents, len: length); |
273 | g_free (mem: contents); |
274 | } |
275 | |
276 | tag = gtk_text_buffer_create_tag (buffer, NULL, NULL); |
277 | g_settings_bind (settings: win->settings, key: "font" , |
278 | object: tag, property: "font" , |
279 | flags: G_SETTINGS_BIND_DEFAULT); |
280 | |
281 | gtk_text_buffer_get_start_iter (buffer, iter: &start_iter); |
282 | gtk_text_buffer_get_end_iter (buffer, iter: &end_iter); |
283 | gtk_text_buffer_apply_tag (buffer, tag, start: &start_iter, end: &end_iter); |
284 | |
285 | g_free (mem: basename); |
286 | |
287 | gtk_widget_set_sensitive (widget: win->search, TRUE); |
288 | |
289 | update_words (win); |
290 | update_lines (win); |
291 | } |
292 | |