1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
2 /*
3 * Copyright (C) 2001-2003 CodeFactory AB
4 * Copyright (C) 2001-2003 Mikael Hallendal <micke@imendio.com>
5 * Copyright (C) 2005-2008 Imendio AB
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of the
10 * License, or (at your option) any later version.
11 *
12 * This program 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 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public
18 * License along with this program; if not, write to the
19 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 * Boston, MA 02111-1307, USA.
21 */
22
23 #include "config.h"
24 #include <string.h>
25 #include <glib/gi18n-lib.h>
26 #include <gdk/gdkkeysyms.h>
27 #include <gtk/gtk.h>
28 #include "dh-marshal.h"
29 #include "dh-keyword-model.h"
30 #include "dh-search.h"
31 #include "dh-preferences.h"
32 #include "dh-base.h"
33 #include "dh-util.h"
34 #include "dh-book-manager.h"
35 #include "dh-book.h"
36
37 typedef struct {
38 DhKeywordModel *model;
39
40 DhBookManager *book_manager;
41
42 DhLink *selected_link;
43
44 GtkWidget *book_combo;
45 GtkWidget *entry;
46 GtkWidget *hitlist;
47
48 GCompletion *completion;
49
50 guint idle_complete;
51 guint idle_filter;
52 } DhSearchPriv;
53
54 static void dh_search_init (DhSearch *search);
55 static void dh_search_class_init (DhSearchClass *klass);
56 static void search_grab_focus (GtkWidget *widget);
57 static void search_selection_changed_cb (GtkTreeSelection *selection,
58 DhSearch *content);
59 static gboolean search_tree_button_press_cb (GtkTreeView *view,
60 GdkEventButton *event,
61 DhSearch *search);
62 static gboolean search_entry_key_press_event_cb (GtkEntry *entry,
63 GdkEventKey *event,
64 DhSearch *search);
65 static void search_combo_changed_cb (GtkComboBox *combo,
66 DhSearch *search);
67 static void search_entry_changed_cb (GtkEntry *entry,
68 DhSearch *search);
69 static void search_entry_activated_cb (GtkEntry *entry,
70 DhSearch *search);
71 static void search_entry_text_inserted_cb (GtkEntry *entry,
72 const gchar *text,
73 gint length,
74 gint *position,
75 DhSearch *search);
76 static gboolean search_complete_idle (DhSearch *search);
77 static gboolean search_filter_idle (DhSearch *search);
78 static const gchar *search_complete_func (DhLink *link);
79
80 enum {
81 LINK_SELECTED,
82 LAST_SIGNAL
83 };
84
85 G_DEFINE_TYPE (DhSearch, dh_search, GTK_TYPE_VBOX);
86
87 #define GET_PRIVATE(instance) G_TYPE_INSTANCE_GET_PRIVATE \
88 (instance, DH_TYPE_SEARCH, DhSearchPriv);
89
90 static gint signals[LAST_SIGNAL] = { 0 };
91
92 static void
search_finalize(GObject * object)93 search_finalize (GObject *object)
94 {
95 DhSearchPriv *priv;
96
97 priv = GET_PRIVATE (object);
98
99 g_completion_free (priv->completion);
100 g_object_unref (priv->book_manager);
101
102 G_OBJECT_CLASS (dh_search_parent_class)->finalize (object);
103 }
104
105 static void
dh_search_class_init(DhSearchClass * klass)106 dh_search_class_init (DhSearchClass *klass)
107 {
108 GObjectClass *object_class = (GObjectClass *) klass;;
109 GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;;
110
111 object_class->finalize = search_finalize;
112
113 widget_class->grab_focus = search_grab_focus;
114
115 signals[LINK_SELECTED] =
116 g_signal_new ("link_selected",
117 G_TYPE_FROM_CLASS (klass),
118 G_SIGNAL_RUN_LAST,
119 G_STRUCT_OFFSET (DhSearchClass, link_selected),
120 NULL, NULL,
121 _dh_marshal_VOID__POINTER,
122 G_TYPE_NONE,
123 1, G_TYPE_POINTER);
124
125 g_type_class_add_private (klass, sizeof (DhSearchPriv));
126 }
127
128 static void
dh_search_init(DhSearch * search)129 dh_search_init (DhSearch *search)
130 {
131 DhSearchPriv *priv = GET_PRIVATE (search);
132
133 priv->completion = g_completion_new (
134 (GCompletionFunc) search_complete_func);
135
136 priv->hitlist = gtk_tree_view_new ();
137 priv->model = dh_keyword_model_new ();
138
139 gtk_tree_view_set_model (GTK_TREE_VIEW (priv->hitlist),
140 GTK_TREE_MODEL (priv->model));
141
142 gtk_tree_view_set_enable_search (GTK_TREE_VIEW (priv->hitlist), FALSE);
143
144 gtk_box_set_spacing (GTK_BOX (search), 4);
145 }
146
147 static void
search_grab_focus(GtkWidget * widget)148 search_grab_focus (GtkWidget *widget)
149 {
150 DhSearchPriv *priv = GET_PRIVATE (widget);
151
152 gtk_widget_grab_focus (priv->entry);
153 }
154
155 static void
search_selection_changed_cb(GtkTreeSelection * selection,DhSearch * search)156 search_selection_changed_cb (GtkTreeSelection *selection,
157 DhSearch *search)
158 {
159 DhSearchPriv *priv;
160 GtkTreeIter iter;
161
162 priv = GET_PRIVATE (search);
163
164 if (gtk_tree_selection_get_selected (selection, NULL, &iter)) {
165 DhLink *link;
166
167 gtk_tree_model_get (GTK_TREE_MODEL (priv->model), &iter,
168 DH_KEYWORD_MODEL_COL_LINK, &link,
169 -1);
170
171 if (link != priv->selected_link) {
172 priv->selected_link = link;
173 g_signal_emit (search, signals[LINK_SELECTED], 0, link);
174 }
175 }
176 }
177
178 /* Make it possible to jump back to the currently selected item, useful when the
179 * html view has been scrolled away.
180 */
181 static gboolean
search_tree_button_press_cb(GtkTreeView * view,GdkEventButton * event,DhSearch * search)182 search_tree_button_press_cb (GtkTreeView *view,
183 GdkEventButton *event,
184 DhSearch *search)
185 {
186 GtkTreePath *path;
187 GtkTreeIter iter;
188 DhSearchPriv *priv;
189 DhLink *link;
190
191 priv = GET_PRIVATE (search);
192
193 gtk_tree_view_get_path_at_pos (view, event->x, event->y, &path,
194 NULL, NULL, NULL);
195 if (!path) {
196 return FALSE;
197 }
198
199 gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->model), &iter, path);
200 gtk_tree_path_free (path);
201
202 gtk_tree_model_get (GTK_TREE_MODEL (priv->model),
203 &iter,
204 DH_KEYWORD_MODEL_COL_LINK, &link,
205 -1);
206
207 priv->selected_link = link;
208
209 g_signal_emit (search, signals[LINK_SELECTED], 0, link);
210
211 /* Always return FALSE so the tree view gets the event and can update
212 * the selection etc.
213 */
214 return FALSE;
215 }
216
217 static gboolean
search_entry_key_press_event_cb(GtkEntry * entry,GdkEventKey * event,DhSearch * search)218 search_entry_key_press_event_cb (GtkEntry *entry,
219 GdkEventKey *event,
220 DhSearch *search)
221 {
222 DhSearchPriv *priv = GET_PRIVATE (search);
223
224 if (event->keyval == GDK_Tab) {
225 if (event->state & GDK_CONTROL_MASK) {
226 gtk_widget_grab_focus (priv->hitlist);
227 } else {
228 gtk_editable_set_position (GTK_EDITABLE (entry), -1);
229 gtk_editable_select_region (GTK_EDITABLE (entry), -1, -1);
230 }
231 return TRUE;
232 }
233
234 if (event->keyval == GDK_Return ||
235 event->keyval == GDK_KP_Enter) {
236 GtkTreeIter iter;
237 DhLink *link;
238 gchar *name;
239
240 /* Get the first entry found. */
241 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->model), &iter)) {
242 gtk_tree_model_get (GTK_TREE_MODEL (priv->model),
243 &iter,
244 DH_KEYWORD_MODEL_COL_LINK, &link,
245 DH_KEYWORD_MODEL_COL_NAME, &name,
246 -1);
247
248 gtk_entry_set_text (GTK_ENTRY (entry), name);
249 g_free (name);
250
251 gtk_editable_set_position (GTK_EDITABLE (entry), -1);
252 gtk_editable_select_region (GTK_EDITABLE (entry), -1, -1);
253
254 g_signal_emit (search, signals[LINK_SELECTED], 0, link);
255
256 return TRUE;
257 }
258 }
259
260 return FALSE;
261 }
262
263 static void
search_combo_set_active_id(DhSearch * search,const gchar * book_id)264 search_combo_set_active_id (DhSearch *search,
265 const gchar *book_id)
266 {
267 DhSearchPriv *priv = GET_PRIVATE (search);
268 GtkTreeIter iter;
269 GtkTreeModel *model;
270 gboolean has_next;
271
272 g_signal_handlers_block_by_func (priv->book_combo,
273 search_combo_changed_cb,
274 search);
275
276 if (book_id != NULL) {
277 model = gtk_combo_box_get_model (GTK_COMBO_BOX (priv->book_combo));
278
279 has_next = gtk_tree_model_get_iter_first (model, &iter);
280 while (has_next) {
281 gchar *id;
282
283 gtk_tree_model_get (model, &iter,
284 1, &id,
285 -1);
286
287 if (id && strcmp (book_id, id) == 0) {
288 g_free (id);
289
290 gtk_combo_box_set_active_iter (GTK_COMBO_BOX (priv->book_combo),
291 &iter);
292 break;
293 }
294
295 g_free (id);
296
297 has_next = gtk_tree_model_iter_next (model, &iter);
298 }
299 } else {
300 gtk_combo_box_set_active (GTK_COMBO_BOX (priv->book_combo), 0);
301 }
302
303 g_signal_handlers_unblock_by_func (priv->book_combo,
304 search_combo_changed_cb,
305 search);
306 }
307
308 static gchar *
search_combo_get_active_id(DhSearch * search)309 search_combo_get_active_id (DhSearch *search)
310 {
311 DhSearchPriv *priv = GET_PRIVATE (search);
312 GtkTreeIter iter;
313 GtkTreeModel *model;
314 gchar *id;
315
316 if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (priv->book_combo),
317 &iter)) {
318 return NULL;
319 }
320
321 model = gtk_combo_box_get_model (GTK_COMBO_BOX (priv->book_combo));
322
323 gtk_tree_model_get (model, &iter,
324 1, &id,
325 -1);
326
327 return id;
328 }
329
330 static void
search_combo_changed_cb(GtkComboBox * combo,DhSearch * search)331 search_combo_changed_cb (GtkComboBox *combo,
332 DhSearch *search)
333 {
334 DhSearchPriv *priv = GET_PRIVATE (search);
335
336 if (!priv->idle_filter) {
337 priv->idle_filter =
338 g_idle_add ((GSourceFunc) search_filter_idle, search);
339 }
340 }
341
342 static void
search_entry_changed_cb(GtkEntry * entry,DhSearch * search)343 search_entry_changed_cb (GtkEntry *entry,
344 DhSearch *search)
345 {
346 DhSearchPriv *priv = GET_PRIVATE (search);
347
348 if (!priv->idle_filter) {
349 priv->idle_filter =
350 g_idle_add ((GSourceFunc) search_filter_idle, search);
351 }
352 }
353
354 static void
search_entry_activated_cb(GtkEntry * entry,DhSearch * search)355 search_entry_activated_cb (GtkEntry *entry,
356 DhSearch *search)
357 {
358 DhSearchPriv *priv = GET_PRIVATE (search);
359 gchar *id;
360 const gchar *str;
361
362 id = search_combo_get_active_id (search);
363 str = gtk_entry_get_text (GTK_ENTRY (priv->entry));
364 dh_keyword_model_filter (priv->model, str, id);
365 g_free (id);
366 }
367
368 static void
search_entry_text_inserted_cb(GtkEntry * entry,const gchar * text,gint length,gint * position,DhSearch * search)369 search_entry_text_inserted_cb (GtkEntry *entry,
370 const gchar *text,
371 gint length,
372 gint *position,
373 DhSearch *search)
374 {
375 DhSearchPriv *priv = GET_PRIVATE (search);
376
377 if (!priv->idle_complete) {
378 priv->idle_complete =
379 g_idle_add ((GSourceFunc) search_complete_idle,
380 search);
381 }
382 }
383
384 static gboolean
search_complete_idle(DhSearch * search)385 search_complete_idle (DhSearch *search)
386 {
387 DhSearchPriv *priv = GET_PRIVATE (search);
388 const gchar *str;
389 gchar *completed = NULL;
390 gsize length;
391
392 str = gtk_entry_get_text (GTK_ENTRY (priv->entry));
393
394 g_completion_complete (priv->completion, str, &completed);
395 if (completed) {
396 length = strlen (str);
397
398 gtk_entry_set_text (GTK_ENTRY (priv->entry), completed);
399 gtk_editable_set_position (GTK_EDITABLE (priv->entry), length);
400 gtk_editable_select_region (GTK_EDITABLE (priv->entry),
401 length, -1);
402 g_free (completed);
403 }
404
405 priv->idle_complete = 0;
406
407 return FALSE;
408 }
409
410 static gboolean
search_filter_idle(DhSearch * search)411 search_filter_idle (DhSearch *search)
412 {
413 DhSearchPriv *priv = GET_PRIVATE (search);
414 const gchar *str;
415 gchar *id;
416 DhLink *link;
417
418 str = gtk_entry_get_text (GTK_ENTRY (priv->entry));
419 id = search_combo_get_active_id (search);
420 link = dh_keyword_model_filter (priv->model, str, id);
421 g_free (id);
422
423 priv->idle_filter = 0;
424
425 if (link) {
426 g_signal_emit (search, signals[LINK_SELECTED], 0, link);
427 }
428
429 return FALSE;
430 }
431
432 static const gchar *
search_complete_func(DhLink * link)433 search_complete_func (DhLink *link)
434 {
435 return dh_link_get_name (link);
436 }
437
438 static void
search_cell_data_func(GtkTreeViewColumn * tree_column,GtkCellRenderer * cell,GtkTreeModel * tree_model,GtkTreeIter * iter,gpointer data)439 search_cell_data_func (GtkTreeViewColumn *tree_column,
440 GtkCellRenderer *cell,
441 GtkTreeModel *tree_model,
442 GtkTreeIter *iter,
443 gpointer data)
444 {
445 DhLink *link;
446 PangoStyle style;
447
448 gtk_tree_model_get (tree_model, iter,
449 DH_KEYWORD_MODEL_COL_LINK, &link,
450 -1);
451
452 style = PANGO_STYLE_NORMAL;
453
454 if (dh_link_get_flags (link) & DH_LINK_FLAGS_DEPRECATED) {
455 style |= PANGO_STYLE_ITALIC;
456 }
457
458 g_object_set (cell,
459 "text", dh_link_get_name (link),
460 "style", style,
461 NULL);
462 }
463
464 static gboolean
search_combo_row_separator_func(GtkTreeModel * model,GtkTreeIter * iter,gpointer data)465 search_combo_row_separator_func (GtkTreeModel *model,
466 GtkTreeIter *iter,
467 gpointer data)
468 {
469 char *label;
470 char *link;
471 gboolean result;
472
473 gtk_tree_model_get (model, iter, 0, &label, 1, &link, -1);
474
475 result = (link == NULL && label == NULL);
476 g_free (label);
477 g_free (link);
478
479 return result;
480 }
481
482 static void
search_combo_populate(DhSearch * search)483 search_combo_populate (DhSearch *search)
484 {
485 DhSearchPriv *priv;
486 GtkListStore *store;
487 GtkTreeIter iter;
488 GList *l;
489
490 priv = GET_PRIVATE (search);
491
492 store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (priv->book_combo)));
493
494 gtk_list_store_clear (store);
495
496 gtk_list_store_append (store, &iter);
497 gtk_list_store_set (store, &iter,
498 0, _("All books"),
499 1, NULL,
500 -1);
501
502 /* Add a separator */
503 gtk_list_store_append (store, &iter);
504 gtk_list_store_set (store, &iter,
505 0, NULL,
506 1, NULL,
507 -1);
508
509 for (l = dh_book_manager_get_books (priv->book_manager);
510 l;
511 l = g_list_next (l)) {
512 DhBook *book = DH_BOOK (l->data);
513 GNode *node;
514
515 node = dh_book_get_tree (book);
516 if (node) {
517 DhLink *link;
518
519 link = node->data;
520
521 gtk_list_store_append (store, &iter);
522 gtk_list_store_set (store, &iter,
523 0, dh_link_get_name (link),
524 1, dh_link_get_book_id (link),
525 -1);
526 }
527 }
528
529 gtk_combo_box_set_active (GTK_COMBO_BOX (priv->book_combo), 0);
530 }
531
532
533 static void
search_combo_create(DhSearch * search)534 search_combo_create (DhSearch *search)
535 {
536 GtkListStore *store;
537 GtkCellRenderer *cell;
538 DhSearchPriv *priv;
539
540 priv = GET_PRIVATE (search);
541
542 store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
543 priv->book_combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store));
544 g_object_unref (store);
545
546 search_combo_populate (search);
547
548 gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (priv->book_combo),
549 search_combo_row_separator_func,
550 NULL, NULL);
551
552 cell = gtk_cell_renderer_text_new ();
553 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (priv->book_combo),
554 cell,
555 TRUE);
556 gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (priv->book_combo),
557 cell,
558 "text", 0);
559 }
560
561 static void
completion_add_items(DhSearch * search)562 completion_add_items (DhSearch *search)
563 {
564 DhSearchPriv *priv;
565 GList *l;
566
567 priv = GET_PRIVATE (search);
568
569 for (l = dh_book_manager_get_books (priv->book_manager);
570 l;
571 l = g_list_next (l)) {
572 DhBook *book = DH_BOOK (l->data);
573 GList *keywords;
574
575 keywords = dh_book_get_keywords(book);
576
577 if (keywords) {
578 g_completion_add_items (priv->completion,
579 keywords);
580 }
581 }
582 }
583
584 static void
book_manager_disabled_book_list_changed_cb(DhBookManager * book_manager,gpointer user_data)585 book_manager_disabled_book_list_changed_cb (DhBookManager *book_manager,
586 gpointer user_data)
587 {
588 DhSearch *search = user_data;
589 search_combo_populate (search);
590 }
591
592 GtkWidget *
dh_search_new(DhBookManager * book_manager)593 dh_search_new (DhBookManager *book_manager)
594 {
595 DhSearch *search;
596 DhSearchPriv *priv;
597 GtkTreeSelection *selection;
598 GtkWidget *list_sw;
599 GtkWidget *hbox;
600 GtkWidget *book_label;
601 GtkCellRenderer *cell;
602
603 search = g_object_new (DH_TYPE_SEARCH, NULL);
604
605 priv = GET_PRIVATE (search);
606
607 priv->book_manager = g_object_ref (book_manager);
608 g_signal_connect (priv->book_manager,
609 "disabled-book-list-updated",
610 G_CALLBACK (book_manager_disabled_book_list_changed_cb),
611 search);
612
613 gtk_container_set_border_width (GTK_CONTAINER (search), 2);
614
615 search_combo_create (search);
616 g_signal_connect (priv->book_combo, "changed",
617 G_CALLBACK (search_combo_changed_cb),
618 search);
619
620 book_label = gtk_label_new_with_mnemonic (_("Search in:"));
621 gtk_label_set_mnemonic_widget (GTK_LABEL (book_label), priv->book_combo);
622
623 hbox = gtk_hbox_new (FALSE, 6);
624 gtk_box_pack_start (GTK_BOX (hbox), book_label, FALSE, FALSE, 0);
625 gtk_box_pack_start (GTK_BOX (hbox), priv->book_combo, TRUE, TRUE, 0);
626 gtk_box_pack_start (GTK_BOX (search), hbox, FALSE, FALSE, 0);
627
628 /* Setup the keyword box. */
629 priv->entry = gtk_entry_new ();
630 g_signal_connect (priv->entry, "key-press-event",
631 G_CALLBACK (search_entry_key_press_event_cb),
632 search);
633
634 g_signal_connect (priv->hitlist, "button-press-event",
635 G_CALLBACK (search_tree_button_press_cb),
636 search);
637
638 g_signal_connect (priv->entry, "changed",
639 G_CALLBACK (search_entry_changed_cb),
640 search);
641
642 g_signal_connect (priv->entry, "activate",
643 G_CALLBACK (search_entry_activated_cb),
644 search);
645
646 g_signal_connect (priv->entry, "insert-text",
647 G_CALLBACK (search_entry_text_inserted_cb),
648 search);
649
650 gtk_box_pack_start (GTK_BOX (search), priv->entry, FALSE, FALSE, 0);
651
652 /* Setup the hitlist */
653 list_sw = gtk_scrolled_window_new (NULL, NULL);
654 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (list_sw), GTK_SHADOW_IN);
655 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (list_sw),
656 GTK_POLICY_NEVER,
657 GTK_POLICY_AUTOMATIC);
658
659 cell = gtk_cell_renderer_text_new ();
660 g_object_set (cell,
661 "ellipsize", PANGO_ELLIPSIZE_END,
662 NULL);
663
664 gtk_tree_view_insert_column_with_data_func (
665 GTK_TREE_VIEW (priv->hitlist),
666 -1,
667 NULL,
668 cell,
669 search_cell_data_func,
670 search, NULL);
671
672 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (priv->hitlist),
673 FALSE);
674 gtk_tree_view_set_search_column (GTK_TREE_VIEW (priv->hitlist),
675 DH_KEYWORD_MODEL_COL_NAME);
676
677 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->hitlist));
678
679 g_signal_connect (selection, "changed",
680 G_CALLBACK (search_selection_changed_cb),
681 search);
682
683 gtk_container_add (GTK_CONTAINER (list_sw), priv->hitlist);
684
685 gtk_box_pack_end (GTK_BOX (search), list_sw, TRUE, TRUE, 0);
686
687 completion_add_items (search);
688 dh_keyword_model_set_words (priv->model, book_manager);
689
690 gtk_widget_show_all (GTK_WIDGET (search));
691
692 return GTK_WIDGET (search);
693 }
694
695 void
dh_search_set_search_string(DhSearch * search,const gchar * str,const gchar * book_id)696 dh_search_set_search_string (DhSearch *search,
697 const gchar *str,
698 const gchar *book_id)
699 {
700 DhSearchPriv *priv;
701
702 g_return_if_fail (DH_IS_SEARCH (search));
703
704 priv = GET_PRIVATE (search);
705
706 g_signal_handlers_block_by_func (priv->entry,
707 search_entry_changed_cb,
708 search);
709
710 gtk_entry_set_text (GTK_ENTRY (priv->entry), str);
711
712 gtk_editable_set_position (GTK_EDITABLE (priv->entry), -1);
713 gtk_editable_select_region (GTK_EDITABLE (priv->entry), -1, -1);
714
715 g_signal_handlers_unblock_by_func (priv->entry,
716 search_entry_changed_cb,
717 search);
718
719 search_combo_set_active_id (search, book_id);
720
721 if (!priv->idle_filter) {
722 priv->idle_filter =
723 g_idle_add ((GSourceFunc) search_filter_idle, search);
724 }
725 }
726