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