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 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 56 text_search_init (TextSearch *tsearch, G_GNUC_UNUSED TextSearchClass *klass) 57 { 58 tsearch->priv = g_new0 (TextSearchPrivate, 1); 59 } 60 61 static void 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 80 text_search_grab_focus (GtkWidget *widget) 81 { 82 gtk_widget_grab_focus (TEXT_SEARCH (widget)->priv->search_entry); 83 } 84 85 GType 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 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 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 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 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 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 * 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 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