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