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