1 /*
2  * Copyright (C) 2011 Murray Cumming <murrayc@murrayc.com>
3  * Copyright (C) 2011 Vivien Malerba <malerba@gnome-db.org>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18  */
19 
20 #include <glib/gi18n-lib.h>
21 #include <string.h>
22 #include "text-search.h"
23 #include "support.h"
24 
25 struct _TextSearchPrivate {
26 	GtkTextView *view;
27 	GtkTextBuffer *text;
28         GtkWidget *search_entry;
29         GtkToggleButton *search_sensitive;
30         GList     *search_marks;
31         GList     *current_mark; /* in @search_marks */
32 };
33 
34 static void text_search_class_init (TextSearchClass *klass);
35 static void text_search_init       (TextSearch *tsearch, TextSearchClass *klass);
36 static void text_search_dispose    (GObject *object);
37 static void text_search_grab_focus (GtkWidget *widget);
38 
39 static GObjectClass *parent_class = NULL;
40 
41 /*
42  * TextSearch class implementation
43  */
44 
45 static void
text_search_class_init(TextSearchClass * klass)46 text_search_class_init (TextSearchClass *klass)
47 {
48 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
49 
50 	parent_class = g_type_class_peek_parent (klass);
51 	GTK_WIDGET_CLASS (klass)->grab_focus = text_search_grab_focus;
52 	object_class->dispose = text_search_dispose;
53 }
54 
55 static void
text_search_init(TextSearch * tsearch,G_GNUC_UNUSED TextSearchClass * klass)56 text_search_init (TextSearch *tsearch, G_GNUC_UNUSED TextSearchClass *klass)
57 {
58 	tsearch->priv = g_new0 (TextSearchPrivate, 1);
59 }
60 
61 static void
text_search_dispose(GObject * object)62 text_search_dispose (GObject *object)
63 {
64 	TextSearch *tsearch = (TextSearch *) object;
65 
66 	/* free memory */
67 	if (tsearch->priv) {
68 		g_object_unref ((GObject*) tsearch->priv->view);
69 		if (tsearch->priv->search_marks)
70                         g_list_free (tsearch->priv->search_marks);
71 
72 		g_free (tsearch->priv);
73 		tsearch->priv = NULL;
74 	}
75 
76 	parent_class->dispose (object);
77 }
78 
79 static void
text_search_grab_focus(GtkWidget * widget)80 text_search_grab_focus (GtkWidget *widget)
81 {
82 	gtk_widget_grab_focus (TEXT_SEARCH (widget)->priv->search_entry);
83 }
84 
85 GType
text_search_get_type(void)86 text_search_get_type (void)
87 {
88 	static GType type = 0;
89 
90 	if (G_UNLIKELY (type == 0)) {
91 		static const GTypeInfo info = {
92 			sizeof (TextSearchClass),
93 			(GBaseInitFunc) NULL,
94 			(GBaseFinalizeFunc) NULL,
95 			(GClassInitFunc) text_search_class_init,
96 			NULL,
97 			NULL,
98 			sizeof (TextSearch),
99 			0,
100 			(GInstanceInitFunc) text_search_init,
101 			0
102 		};
103 
104 		type = g_type_register_static (GTK_TYPE_BOX, "TextSearch", &info, 0);
105 	}
106 	return type;
107 }
108 
109 static void
search_text_changed_cb(GtkEntry * entry,TextSearch * tsearch)110 search_text_changed_cb (GtkEntry *entry, TextSearch *tsearch)
111 {
112 	GtkTextIter iter, siter, end;
113 	GtkTextBuffer *buffer;
114 	const gchar *search_text, *sptr;
115 	gboolean sensitive;
116 
117 	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tsearch->priv->view));
118 
119 	/* clean all previous search result */
120 	gtk_text_buffer_get_bounds (buffer, &iter, &end);
121 	gtk_text_buffer_remove_tag_by_name (buffer, "search", &iter, &end);
122 	tsearch->priv->current_mark = NULL;
123 	if (tsearch->priv->search_marks) {
124 		GList *list;
125 		for (list = tsearch->priv->search_marks; list; list = list->next)
126 			gtk_text_buffer_delete_mark (buffer, GTK_TEXT_MARK (list->data));
127 
128 		g_list_free (tsearch->priv->search_marks);
129 		tsearch->priv->search_marks = NULL;
130 	}
131 
132 	gtk_text_buffer_get_start_iter (buffer, &iter);
133 	search_text = gtk_entry_get_text (entry);
134 
135 	if (!search_text || !*search_text)
136 		return;
137 
138 	sensitive = gtk_toggle_button_get_active (tsearch->priv->search_sensitive);
139 
140 	while (1) {
141 		gboolean high = TRUE;
142 		siter = iter;
143 		sptr = search_text;
144 
145 		/* search for @search_text starting from the @siter position */
146 		while (1) {
147 			gunichar c1, c2;
148 			c1 = gtk_text_iter_get_char (&siter);
149 			c2 = g_utf8_get_char (sptr);
150 			if (!sensitive) {
151 				c1 = g_unichar_tolower (c1);
152 				c2 = g_unichar_tolower (c2);
153 			}
154 			if (c1 != c2) {
155 				high = FALSE;
156 				break;
157 			}
158 
159 			sptr = g_utf8_find_next_char (sptr, NULL);
160 			if (!sptr || !*sptr)
161 				break;
162 
163 			if (! gtk_text_iter_forward_char (&siter)) {
164 				high = FALSE;
165 				break;
166 			}
167 		}
168 		if (high) {
169 			if (gtk_text_iter_forward_char (&siter)) {
170 				GtkTextMark *mark;
171 				gtk_text_buffer_apply_tag_by_name (buffer, "search", &iter, &siter);
172 				mark = gtk_text_buffer_create_mark (buffer, NULL, &iter, FALSE);
173 				tsearch->priv->search_marks = g_list_prepend (tsearch->priv->search_marks,
174 									    mark);
175 			}
176 			iter = siter;
177 		}
178 		else {
179 			if (! gtk_text_iter_forward_char (&iter))
180 				break;
181 		}
182 	}
183 
184 	if (tsearch->priv->search_marks) {
185 		tsearch->priv->search_marks = g_list_reverse (tsearch->priv->search_marks);
186 		tsearch->priv->current_mark = tsearch->priv->search_marks;
187 		gtk_text_view_scroll_mark_onscreen (tsearch->priv->view,
188 						    GTK_TEXT_MARK (tsearch->priv->current_mark->data));
189 	}
190 }
191 
192 static void
sensitive_toggled_cb(G_GNUC_UNUSED GtkToggleButton * button,TextSearch * tsearch)193 sensitive_toggled_cb (G_GNUC_UNUSED GtkToggleButton *button, TextSearch *tsearch)
194 {
195 	search_text_changed_cb (GTK_ENTRY (tsearch->priv->search_entry), tsearch);
196 }
197 
198 static void
hide_search_bar(TextSearch * tsearch)199 hide_search_bar (TextSearch *tsearch)
200 {
201 	GtkTextIter start, end;
202 	GtkTextBuffer *buffer;
203 
204 	/* clean all previous search result */
205 	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tsearch->priv->view));
206 	gtk_text_buffer_get_bounds (buffer, &start, &end);
207 	gtk_text_buffer_remove_tag_by_name (buffer, "search", &start, &end);
208 
209 	if (tsearch->priv->search_marks) {
210 		GList *list;
211 		for (list = tsearch->priv->search_marks; list; list = list->next)
212 			gtk_text_buffer_delete_mark (buffer, GTK_TEXT_MARK (list->data));
213 
214 		g_list_free (tsearch->priv->search_marks);
215 		tsearch->priv->search_marks = NULL;
216 	}
217 	tsearch->priv->current_mark = NULL;
218 
219 	gtk_widget_hide (GTK_WIDGET (tsearch));
220 }
221 
222 static void
go_back_search_cb(G_GNUC_UNUSED GtkButton * button,TextSearch * tsearch)223 go_back_search_cb (G_GNUC_UNUSED GtkButton *button, TextSearch *tsearch)
224 {
225 	if (tsearch->priv->current_mark && tsearch->priv->current_mark->prev) {
226 		tsearch->priv->current_mark = tsearch->priv->current_mark->prev;
227 		gtk_text_view_scroll_mark_onscreen (tsearch->priv->view,
228 						    GTK_TEXT_MARK (tsearch->priv->current_mark->data));
229 	}
230 }
231 
232 static void
go_forward_search_cb(G_GNUC_UNUSED GtkButton * button,TextSearch * tsearch)233 go_forward_search_cb (G_GNUC_UNUSED GtkButton *button, TextSearch *tsearch)
234 {
235 	if (tsearch->priv->current_mark && tsearch->priv->current_mark->next) {
236 		tsearch->priv->current_mark = tsearch->priv->current_mark->next;
237 		gtk_text_view_scroll_mark_onscreen (tsearch->priv->view,
238 						    GTK_TEXT_MARK (tsearch->priv->current_mark->data));
239 	}
240 }
241 
242 /**
243  * text_search_new:
244  *
245  * Returns: a new #GtkWidget
246  */
247 GtkWidget *
text_search_new(GtkTextView * view)248 text_search_new (GtkTextView *view)
249 {
250 	g_return_val_if_fail (GTK_IS_TEXT_VIEW (view), NULL);
251 
252 	TextSearch *tsearch;
253 	GtkWidget *wid;
254 
255 	tsearch = TEXT_SEARCH (g_object_new (TEXT_SEARCH_TYPE, "spacing", 5,
256 					     "homogeneous", FALSE, NULL));
257 	tsearch->priv->view = view;
258 	g_object_ref ((GObject*) tsearch->priv->view);
259 	tsearch->priv->text = gtk_text_view_get_buffer (view);
260 
261 	gtk_text_buffer_create_tag (tsearch->priv->text, "search",
262                                     "background", "yellow", NULL);
263 
264 	wid = browser_make_small_button (FALSE, FALSE, NULL, GTK_STOCK_CLOSE,
265 					 _("Hide search toolbar"));
266 	gtk_box_pack_start (GTK_BOX (tsearch), wid, FALSE, FALSE, 0);
267 	g_signal_connect_swapped (wid, "clicked",
268 				  G_CALLBACK (hide_search_bar), tsearch);
269 
270 	wid = gtk_label_new (_("Search:"));
271 	gtk_box_pack_start (GTK_BOX (tsearch), wid, FALSE, FALSE, 0);
272 
273 	wid = gtk_entry_new ();
274 	gtk_box_pack_start (GTK_BOX (tsearch), wid, TRUE, TRUE, 0);
275 	tsearch->priv->search_entry = wid;
276 	gtk_container_set_focus_child (GTK_CONTAINER (tsearch), tsearch->priv->search_entry);
277 	g_signal_connect (wid, "changed",
278 			  G_CALLBACK (search_text_changed_cb), tsearch);
279 
280 	wid = browser_make_small_button (FALSE, FALSE, NULL, GTK_STOCK_GO_BACK, NULL);
281 	gtk_box_pack_start (GTK_BOX (tsearch), wid, FALSE, FALSE, 0);
282 	g_signal_connect (wid, "clicked",
283 			  G_CALLBACK (go_back_search_cb), tsearch);
284 
285 	wid = browser_make_small_button (FALSE, FALSE, NULL, GTK_STOCK_GO_FORWARD, NULL);
286 	gtk_box_pack_start (GTK_BOX (tsearch), wid, FALSE, FALSE, 0);
287 	g_signal_connect (wid, "clicked",
288 			  G_CALLBACK (go_forward_search_cb), tsearch);
289 
290 	wid = gtk_check_button_new_with_label (_("Case sensitive"));
291 	gtk_box_pack_start (GTK_BOX (tsearch), wid, FALSE, FALSE, 0);
292 	tsearch->priv->search_sensitive = GTK_TOGGLE_BUTTON (wid);
293 	g_signal_connect (wid, "toggled",
294 			  G_CALLBACK (sensitive_toggled_cb), tsearch);
295 
296 	gtk_widget_show_all ((GtkWidget*) tsearch);
297 	gtk_widget_hide ((GtkWidget*) tsearch);
298 
299 	return (GtkWidget*) tsearch;
300 }
301 
302 /**
303  * text_search_rerun:
304  *
305  * To be executed when the #GtkTextView's contents has changed
306  */
307 void
text_search_rerun(TextSearch * tsearch)308 text_search_rerun (TextSearch *tsearch)
309 {
310 	g_return_if_fail (IS_TEXT_SEARCH (tsearch));
311 	search_text_changed_cb (GTK_ENTRY (tsearch->priv->search_entry), tsearch);
312 }
313