1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*-
2 *
3 * This file is part of GtkSourceView
4 *
5 * Copyright (C) 2013 - Sébastien Wilmet <swilmet@gnome.org>
6 *
7 * GtkSourceView is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * GtkSourceView is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this library; if not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include <gtk/gtk.h>
22 #include <gtksourceview/gtksource.h>
23
24 #define TEST_TYPE_SEARCH (test_search_get_type ())
25 #define TEST_SEARCH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TEST_TYPE_SEARCH, TestSearch))
26 #define TEST_SEARCH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TEST_TYPE_SEARCH, TestSearchClass))
27 #define TEST_IS_SEARCH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TEST_TYPE_SEARCH))
28 #define TEST_IS_SEARCH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TEST_TYPE_SEARCH))
29 #define TEST_SEARCH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TEST_TYPE_SEARCH, TestSearchClass))
30
31 typedef struct _TestSearch TestSearch;
32 typedef struct _TestSearchClass TestSearchClass;
33 typedef struct _TestSearchPrivate TestSearchPrivate;
34
35 struct _TestSearch
36 {
37 GtkGrid parent;
38 TestSearchPrivate *priv;
39 };
40
41 struct _TestSearchClass
42 {
43 GtkGridClass parent_class;
44 };
45
46 struct _TestSearchPrivate
47 {
48 GtkSourceView *source_view;
49 GtkSourceBuffer *source_buffer;
50 GtkSourceSearchContext *search_context;
51 GtkSourceSearchSettings *search_settings;
52 GtkEntry *replace_entry;
53 GtkLabel *label_occurrences;
54 GtkLabel *label_regex_error;
55
56 guint idle_update_label_id;
57 };
58
59 GType test_search_get_type (void);
60
G_DEFINE_TYPE_WITH_PRIVATE(TestSearch,test_search,GTK_TYPE_GRID)61 G_DEFINE_TYPE_WITH_PRIVATE (TestSearch, test_search, GTK_TYPE_GRID)
62
63 static void
64 open_file (TestSearch *search,
65 const gchar *filename)
66 {
67 gchar *contents;
68 GError *error = NULL;
69 GtkSourceLanguageManager *language_manager;
70 GtkSourceLanguage *language;
71 GtkTextIter iter;
72
73 /* In a realistic application you would use GtkSourceFile of course. */
74 if (!g_file_get_contents (filename, &contents, NULL, &error))
75 {
76 g_error ("Impossible to load file: %s", error->message);
77 }
78
79 gtk_text_buffer_set_text (GTK_TEXT_BUFFER (search->priv->source_buffer),
80 contents,
81 -1);
82
83 language_manager = gtk_source_language_manager_get_default ();
84 language = gtk_source_language_manager_get_language (language_manager, "c");
85 gtk_source_buffer_set_language (search->priv->source_buffer, language);
86
87 gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (search->priv->source_buffer),
88 &iter);
89
90 gtk_text_buffer_select_range (GTK_TEXT_BUFFER (search->priv->source_buffer),
91 &iter,
92 &iter);
93
94 g_free (contents);
95 }
96
97 static void
update_label_occurrences(TestSearch * search)98 update_label_occurrences (TestSearch *search)
99 {
100 gint occurrences_count;
101 GtkTextIter select_start;
102 GtkTextIter select_end;
103 gint occurrence_pos;
104 gchar *text;
105
106 occurrences_count = gtk_source_search_context_get_occurrences_count (search->priv->search_context);
107
108 gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (search->priv->source_buffer),
109 &select_start,
110 &select_end);
111
112 occurrence_pos = gtk_source_search_context_get_occurrence_position (search->priv->search_context,
113 &select_start,
114 &select_end);
115
116 if (occurrences_count == -1)
117 {
118 text = g_strdup ("");
119 }
120 else if (occurrence_pos == -1)
121 {
122 text = g_strdup_printf ("%u occurrences", occurrences_count);
123 }
124 else
125 {
126 text = g_strdup_printf ("%d of %u", occurrence_pos, occurrences_count);
127 }
128
129 gtk_label_set_text (search->priv->label_occurrences, text);
130 g_free (text);
131 }
132
133 static void
update_label_regex_error(TestSearch * search)134 update_label_regex_error (TestSearch *search)
135 {
136 GError *error;
137
138 error = gtk_source_search_context_get_regex_error (search->priv->search_context);
139
140 if (error == NULL)
141 {
142 gtk_label_set_text (search->priv->label_regex_error, "");
143 gtk_widget_hide (GTK_WIDGET (search->priv->label_regex_error));
144 }
145 else
146 {
147 gtk_label_set_text (search->priv->label_regex_error, error->message);
148 gtk_widget_show (GTK_WIDGET (search->priv->label_regex_error));
149 g_clear_error (&error);
150 }
151 }
152
153 static void
search_entry_changed_cb(TestSearch * search,GtkEntry * entry)154 search_entry_changed_cb (TestSearch *search,
155 GtkEntry *entry)
156 {
157 const gchar *text = gtk_entry_get_text (entry);
158 gchar *unescaped_text = gtk_source_utils_unescape_search_text (text);
159
160 gtk_source_search_settings_set_search_text (search->priv->search_settings, unescaped_text);
161 g_free (unescaped_text);
162 }
163
164 static void
select_search_occurrence(TestSearch * search,const GtkTextIter * match_start,const GtkTextIter * match_end)165 select_search_occurrence (TestSearch *search,
166 const GtkTextIter *match_start,
167 const GtkTextIter *match_end)
168 {
169 GtkTextMark *insert;
170
171 gtk_text_buffer_select_range (GTK_TEXT_BUFFER (search->priv->source_buffer),
172 match_start,
173 match_end);
174
175 insert = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (search->priv->source_buffer));
176
177 gtk_text_view_scroll_mark_onscreen (GTK_TEXT_VIEW (search->priv->source_view),
178 insert);
179 }
180
181 static void
backward_search_finished(GtkSourceSearchContext * search_context,GAsyncResult * result,TestSearch * search)182 backward_search_finished (GtkSourceSearchContext *search_context,
183 GAsyncResult *result,
184 TestSearch *search)
185 {
186 GtkTextIter match_start;
187 GtkTextIter match_end;
188
189 if (gtk_source_search_context_backward_finish (search_context,
190 result,
191 &match_start,
192 &match_end,
193 NULL,
194 NULL))
195 {
196 select_search_occurrence (search, &match_start, &match_end);
197 }
198 }
199
200 static void
button_previous_clicked_cb(TestSearch * search,GtkButton * button)201 button_previous_clicked_cb (TestSearch *search,
202 GtkButton *button)
203 {
204 GtkTextIter start_at;
205
206 gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (search->priv->source_buffer),
207 &start_at,
208 NULL);
209
210 gtk_source_search_context_backward_async (search->priv->search_context,
211 &start_at,
212 NULL,
213 (GAsyncReadyCallback)backward_search_finished,
214 search);
215 }
216
217 static void
forward_search_finished(GtkSourceSearchContext * search_context,GAsyncResult * result,TestSearch * search)218 forward_search_finished (GtkSourceSearchContext *search_context,
219 GAsyncResult *result,
220 TestSearch *search)
221 {
222 GtkTextIter match_start;
223 GtkTextIter match_end;
224
225 if (gtk_source_search_context_forward_finish (search_context,
226 result,
227 &match_start,
228 &match_end,
229 NULL,
230 NULL))
231 {
232 select_search_occurrence (search, &match_start, &match_end);
233 }
234 }
235
236 static void
button_next_clicked_cb(TestSearch * search,GtkButton * button)237 button_next_clicked_cb (TestSearch *search,
238 GtkButton *button)
239 {
240 GtkTextIter start_at;
241
242 gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (search->priv->source_buffer),
243 NULL,
244 &start_at);
245
246 gtk_source_search_context_forward_async (search->priv->search_context,
247 &start_at,
248 NULL,
249 (GAsyncReadyCallback)forward_search_finished,
250 search);
251 }
252
253 static void
button_replace_clicked_cb(TestSearch * search,GtkButton * button)254 button_replace_clicked_cb (TestSearch *search,
255 GtkButton *button)
256 {
257 GtkTextIter match_start;
258 GtkTextIter match_end;
259 GtkTextIter iter;
260 GtkEntryBuffer *entry_buffer;
261 gint replace_length;
262
263 gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (search->priv->source_buffer),
264 &match_start,
265 &match_end);
266
267 entry_buffer = gtk_entry_get_buffer (search->priv->replace_entry);
268 replace_length = gtk_entry_buffer_get_bytes (entry_buffer);
269
270 gtk_source_search_context_replace (search->priv->search_context,
271 &match_start,
272 &match_end,
273 gtk_entry_get_text (search->priv->replace_entry),
274 replace_length,
275 NULL);
276
277 gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (search->priv->source_buffer),
278 NULL,
279 &iter);
280
281 gtk_source_search_context_forward_async (search->priv->search_context,
282 &iter,
283 NULL,
284 (GAsyncReadyCallback)forward_search_finished,
285 search);
286 }
287
288 static void
button_replace_all_clicked_cb(TestSearch * search,GtkButton * button)289 button_replace_all_clicked_cb (TestSearch *search,
290 GtkButton *button)
291 {
292 GtkEntryBuffer *entry_buffer = gtk_entry_get_buffer (search->priv->replace_entry);
293 gint replace_length = gtk_entry_buffer_get_bytes (entry_buffer);
294
295 gtk_source_search_context_replace_all (search->priv->search_context,
296 gtk_entry_get_text (search->priv->replace_entry),
297 replace_length,
298 NULL);
299 }
300
301 static gboolean
update_label_idle_cb(TestSearch * search)302 update_label_idle_cb (TestSearch *search)
303 {
304 search->priv->idle_update_label_id = 0;
305
306 update_label_occurrences (search);
307
308 return G_SOURCE_REMOVE;
309 }
310
311 static void
mark_set_cb(GtkTextBuffer * buffer,GtkTextIter * location,GtkTextMark * mark,TestSearch * search)312 mark_set_cb (GtkTextBuffer *buffer,
313 GtkTextIter *location,
314 GtkTextMark *mark,
315 TestSearch *search)
316 {
317 GtkTextMark *insert;
318 GtkTextMark *selection_bound;
319
320 insert = gtk_text_buffer_get_insert (buffer);
321 selection_bound = gtk_text_buffer_get_selection_bound (buffer);
322
323 if ((mark == insert || mark == selection_bound) &&
324 search->priv->idle_update_label_id == 0)
325 {
326 search->priv->idle_update_label_id = g_idle_add ((GSourceFunc)update_label_idle_cb,
327 search);
328 }
329 }
330
331 static void
highlight_toggled_cb(TestSearch * search,GtkToggleButton * button)332 highlight_toggled_cb (TestSearch *search,
333 GtkToggleButton *button)
334 {
335 gtk_source_search_context_set_highlight (search->priv->search_context,
336 gtk_toggle_button_get_active (button));
337 }
338
339 static void
match_case_toggled_cb(TestSearch * search,GtkToggleButton * button)340 match_case_toggled_cb (TestSearch *search,
341 GtkToggleButton *button)
342 {
343 gtk_source_search_settings_set_case_sensitive (search->priv->search_settings,
344 gtk_toggle_button_get_active (button));
345 }
346
347 static void
at_word_boundaries_toggled_cb(TestSearch * search,GtkToggleButton * button)348 at_word_boundaries_toggled_cb (TestSearch *search,
349 GtkToggleButton *button)
350 {
351 gtk_source_search_settings_set_at_word_boundaries (search->priv->search_settings,
352 gtk_toggle_button_get_active (button));
353 }
354
355 static void
wrap_around_toggled_cb(TestSearch * search,GtkToggleButton * button)356 wrap_around_toggled_cb (TestSearch *search,
357 GtkToggleButton *button)
358 {
359 gtk_source_search_settings_set_wrap_around (search->priv->search_settings,
360 gtk_toggle_button_get_active (button));
361 }
362
363 static void
regex_toggled_cb(TestSearch * search,GtkToggleButton * button)364 regex_toggled_cb (TestSearch *search,
365 GtkToggleButton *button)
366 {
367 gtk_source_search_settings_set_regex_enabled (search->priv->search_settings,
368 gtk_toggle_button_get_active (button));
369 }
370
371 static void
test_search_dispose(GObject * object)372 test_search_dispose (GObject *object)
373 {
374 TestSearch *search = TEST_SEARCH (object);
375
376 g_clear_object (&search->priv->source_buffer);
377 g_clear_object (&search->priv->search_context);
378 g_clear_object (&search->priv->search_settings);
379
380 G_OBJECT_CLASS (test_search_parent_class)->dispose (object);
381 }
382
383 static void
test_search_class_init(TestSearchClass * klass)384 test_search_class_init (TestSearchClass *klass)
385 {
386 GObjectClass *object_class = G_OBJECT_CLASS (klass);
387 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
388
389 object_class->dispose = test_search_dispose;
390
391 gtk_widget_class_set_template_from_resource (widget_class,
392 "/org/gnome/gtksourceview/tests/ui/test-search.ui");
393
394 gtk_widget_class_bind_template_child_private (widget_class, TestSearch, source_view);
395 gtk_widget_class_bind_template_child_private (widget_class, TestSearch, replace_entry);
396 gtk_widget_class_bind_template_child_private (widget_class, TestSearch, label_occurrences);
397 gtk_widget_class_bind_template_child_private (widget_class, TestSearch, label_regex_error);
398
399 gtk_widget_class_bind_template_callback (widget_class, search_entry_changed_cb);
400 gtk_widget_class_bind_template_callback (widget_class, button_previous_clicked_cb);
401 gtk_widget_class_bind_template_callback (widget_class, button_next_clicked_cb);
402 gtk_widget_class_bind_template_callback (widget_class, button_replace_clicked_cb);
403 gtk_widget_class_bind_template_callback (widget_class, button_replace_all_clicked_cb);
404
405 /* It is also possible to bind the properties with
406 * g_object_bind_property(), between the check buttons and the source
407 * buffer. But GtkBuilder and Glade don't support that yet.
408 */
409 gtk_widget_class_bind_template_callback (widget_class, highlight_toggled_cb);
410 gtk_widget_class_bind_template_callback (widget_class, match_case_toggled_cb);
411 gtk_widget_class_bind_template_callback (widget_class, at_word_boundaries_toggled_cb);
412 gtk_widget_class_bind_template_callback (widget_class, wrap_around_toggled_cb);
413 gtk_widget_class_bind_template_callback (widget_class, regex_toggled_cb);
414 }
415
416 static void
test_search_init(TestSearch * search)417 test_search_init (TestSearch *search)
418 {
419 search->priv = test_search_get_instance_private (search);
420
421 gtk_widget_init_template (GTK_WIDGET (search));
422
423 search->priv->source_buffer = GTK_SOURCE_BUFFER (
424 gtk_text_view_get_buffer (GTK_TEXT_VIEW (search->priv->source_view)));
425
426 g_object_ref (search->priv->source_buffer);
427
428 open_file (search, TOP_SRCDIR "/gtksourceview/gtksourcesearchcontext.c");
429
430 search->priv->search_settings = gtk_source_search_settings_new ();
431
432 search->priv->search_context = gtk_source_search_context_new (search->priv->source_buffer,
433 search->priv->search_settings);
434
435 g_signal_connect_swapped (search->priv->search_context,
436 "notify::occurrences-count",
437 G_CALLBACK (update_label_occurrences),
438 search);
439
440 g_signal_connect (search->priv->source_buffer,
441 "mark-set",
442 G_CALLBACK (mark_set_cb),
443 search);
444
445 g_signal_connect_swapped (search->priv->search_context,
446 "notify::regex-error",
447 G_CALLBACK (update_label_regex_error),
448 search);
449
450 update_label_regex_error (search);
451 }
452
453 static TestSearch *
test_search_new(void)454 test_search_new (void)
455 {
456 return g_object_new (test_search_get_type (), NULL);
457 }
458
459 gint
main(gint argc,gchar * argv[])460 main (gint argc, gchar *argv[])
461 {
462 GtkWidget *window;
463 TestSearch *search;
464
465 gtk_init (&argc, &argv);
466
467 window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
468
469 gtk_window_set_default_size (GTK_WINDOW (window), 700, 500);
470
471 g_signal_connect (window,
472 "destroy",
473 G_CALLBACK (gtk_main_quit),
474 NULL);
475
476 search = test_search_new ();
477 gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (search));
478
479 gtk_widget_show (window);
480
481 gtk_main ();
482
483 return 0;
484 }
485