1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * Copyright (C) 2009-2020 Shaun McCance <shaunm@gnome.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 as
7  * published by the Free Software Foundation; either version 2 of the
8  * 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 GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, see <http://www.gnu.org/licenses/>.
17  *
18  * Author: Shaun McCance <shaunm@gnome.org>
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24 
25 #include <gdk/gdkkeysyms.h>
26 #include <gtk/gtk.h>
27 #include <glib/gi18n.h>
28 
29 #include "yelp-search-entry.h"
30 #include "yelp-marshal.h"
31 #include "yelp-settings.h"
32 
33 static void     search_entry_constructed     (GObject           *object);
34 static void     search_entry_dispose         (GObject           *object);
35 static void     search_entry_finalize        (GObject           *object);
36 static void     search_entry_get_property    (GObject           *object,
37 					      guint              prop_id,
38 					      GValue            *value,
39 					      GParamSpec        *pspec);
40 static void     search_entry_set_property    (GObject           *object,
41 					      guint              prop_id,
42 					      const GValue      *value,
43 					      GParamSpec        *pspec);
44 
45 /* Signals */
46 static void     search_entry_search_activated  (YelpSearchEntry *entry);
47 static void     search_entry_bookmark_clicked  (YelpSearchEntry *entry);
48 
49 
50 /* Utilities */
51 static void     search_entry_set_completion  (YelpSearchEntry *entry,
52                                                 GtkTreeModel      *model);
53 
54 
55 /* GtkEntry callbacks */
56 static void     entry_activate_cb                   (GtkEntry          *text_entry,
57                                                      gpointer           user_data);
58 
59 /* GtkEntryCompletion callbacks */
60 static void     cell_set_completion_bookmark_icon   (GtkCellLayout     *layout,
61                                                      GtkCellRenderer   *cell,
62                                                      GtkTreeModel      *model,
63                                                      GtkTreeIter       *iter,
64                                                      YelpSearchEntry *entry);
65 static void     cell_set_completion_text_cell       (GtkCellLayout     *layout,
66                                                      GtkCellRenderer   *cell,
67                                                      GtkTreeModel      *model,
68                                                      GtkTreeIter       *iter,
69                                                      YelpSearchEntry *entry);
70 static gboolean entry_match_func                    (GtkEntryCompletion *completion,
71                                                      const gchar        *key,
72                                                      GtkTreeIter        *iter,
73                                                      YelpSearchEntry  *entry);
74 static gint     entry_completion_sort               (GtkTreeModel       *model,
75                                                      GtkTreeIter        *iter1,
76                                                      GtkTreeIter        *iter2,
77                                                      gpointer            user_data);
78 static gboolean entry_match_selected                (GtkEntryCompletion *completion,
79                                                      GtkTreeModel       *model,
80                                                      GtkTreeIter        *iter,
81                                                      YelpSearchEntry  *entry);
82 /* YelpView callbacks */
83 static void          view_loaded                    (YelpView           *view,
84                                                      YelpSearchEntry    *entry);
85 
86 
87 typedef struct _YelpSearchEntryPrivate  YelpSearchEntryPrivate;
88 struct _YelpSearchEntryPrivate
89 {
90     YelpView *view;
91     YelpBookmarks *bookmarks;
92     gchar *completion_uri;
93 
94     /* do not free below */
95     GtkEntryCompletion *completion;
96 };
97 
98 enum {
99     COMPLETION_COL_TITLE,
100     COMPLETION_COL_DESC,
101     COMPLETION_COL_ICON,
102     COMPLETION_COL_PAGE,
103     COMPLETION_COL_FLAGS,
104     COMPLETION_COL_KEYWORDS
105 };
106 
107 enum {
108     COMPLETION_FLAG_ACTIVATE_SEARCH = 1<<0
109 };
110 
111 enum {
112     SEARCH_ACTIVATED,
113     LAST_SIGNAL
114 };
115 
116 enum {
117     PROP_0,
118     PROP_VIEW,
119     PROP_BOOKMARKS
120 };
121 
122 static GHashTable *completions;
123 
124 static guint search_entry_signals[LAST_SIGNAL] = {0,};
125 
G_DEFINE_TYPE_WITH_PRIVATE(YelpSearchEntry,yelp_search_entry,GTK_TYPE_SEARCH_ENTRY)126 G_DEFINE_TYPE_WITH_PRIVATE (YelpSearchEntry, yelp_search_entry, GTK_TYPE_SEARCH_ENTRY)
127 
128 static void
129 yelp_search_entry_class_init (YelpSearchEntryClass *klass)
130 {
131     GObjectClass *object_class;
132 
133     klass->search_activated = search_entry_search_activated;
134     klass->bookmark_clicked = search_entry_bookmark_clicked;
135 
136     object_class = G_OBJECT_CLASS (klass);
137 
138     object_class->constructed = search_entry_constructed;
139     object_class->dispose = search_entry_dispose;
140     object_class->finalize = search_entry_finalize;
141     object_class->get_property = search_entry_get_property;
142     object_class->set_property = search_entry_set_property;
143 
144     /**
145      * YelpSearchEntry::search-activated
146      * @widget: The #YelpLocationEntry for which the signal was emitted.
147      * @text: The search text.
148      * @user_data: User data set when the handler was connected.
149      *
150      * This signal will be emitted whenever the user activates a search, generally
151      * by pressing <keycap>Enter</keycap> in the embedded text entry while @widget
152      * is in search mode.
153      **/
154     search_entry_signals[SEARCH_ACTIVATED] =
155         g_signal_new ("search-activated",
156                       G_OBJECT_CLASS_TYPE (klass),
157                       G_SIGNAL_RUN_LAST,
158                       G_STRUCT_OFFSET (YelpSearchEntryClass, search_activated),
159                       NULL, NULL,
160                       g_cclosure_marshal_VOID__STRING,
161                       G_TYPE_NONE, 1,
162                       G_TYPE_STRING);
163 
164     /**
165      * YelpLocationEntry:view
166      *
167      * The YelpView instance that this location entry controls.
168      **/
169     g_object_class_install_property (object_class,
170                                      PROP_VIEW,
171                                      g_param_spec_object ("view",
172 							  "View",
173 							  "A YelpView instance to control",
174                                                           YELP_TYPE_VIEW,
175                                                           G_PARAM_CONSTRUCT_ONLY |
176 							  G_PARAM_READWRITE |
177                                                           G_PARAM_STATIC_STRINGS));
178 
179     /**
180      * YelpLocationEntry:bookmarks
181      *
182      * An instance of an implementation of YelpBookmarks to provide
183      * bookmark information for this location entry.
184      **/
185     g_object_class_install_property (object_class,
186                                      PROP_BOOKMARKS,
187                                      g_param_spec_object ("bookmarks",
188 							  "Bookmarks",
189 							  "A YelpBookmarks implementation instance",
190                                                           YELP_TYPE_BOOKMARKS,
191                                                           G_PARAM_CONSTRUCT_ONLY |
192 							  G_PARAM_READWRITE |
193                                                           G_PARAM_STATIC_STRINGS));
194 
195     completions = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
196 }
197 
198 static void
yelp_search_entry_init(YelpSearchEntry * entry)199 yelp_search_entry_init (YelpSearchEntry *entry)
200 {
201 }
202 
203 static void
search_entry_constructed(GObject * object)204 search_entry_constructed (GObject *object)
205 {
206     YelpSearchEntryPrivate *priv =
207         yelp_search_entry_get_instance_private (YELP_SEARCH_ENTRY (object));
208 
209     g_signal_connect (object, "activate",
210                       G_CALLBACK (entry_activate_cb), object);
211 
212     g_signal_connect (priv->view, "loaded", G_CALLBACK (view_loaded), object);
213 }
214 
215 static void
search_entry_dispose(GObject * object)216 search_entry_dispose (GObject *object)
217 {
218     YelpSearchEntryPrivate *priv =
219         yelp_search_entry_get_instance_private (YELP_SEARCH_ENTRY (object));
220 
221     if (priv->view) {
222         g_object_unref (priv->view);
223         priv->view = NULL;
224     }
225 
226     if (priv->bookmarks) {
227         g_object_unref (priv->bookmarks);
228         priv->bookmarks = NULL;
229     }
230 
231     G_OBJECT_CLASS (yelp_search_entry_parent_class)->dispose (object);
232 }
233 
234 static void
search_entry_finalize(GObject * object)235 search_entry_finalize (GObject *object)
236 {
237     YelpSearchEntryPrivate *priv =
238         yelp_search_entry_get_instance_private (YELP_SEARCH_ENTRY (object));
239 
240     g_free (priv->completion_uri);
241 
242     G_OBJECT_CLASS (yelp_search_entry_parent_class)->finalize (object);
243 }
244 
245 static void
search_entry_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)246 search_entry_get_property   (GObject      *object,
247                                guint         prop_id,
248                                GValue       *value,
249                                GParamSpec   *pspec)
250 {
251     YelpSearchEntryPrivate *priv =
252         yelp_search_entry_get_instance_private (YELP_SEARCH_ENTRY (object));
253 
254     switch (prop_id) {
255     case PROP_VIEW:
256         g_value_set_object (value, priv->view);
257         break;
258     case PROP_BOOKMARKS:
259         g_value_set_object (value, priv->bookmarks);
260         break;
261     default:
262         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
263         break;
264     }
265 }
266 
267 static void
search_entry_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)268 search_entry_set_property   (GObject      *object,
269                                guint         prop_id,
270                                const GValue *value,
271                                GParamSpec   *pspec)
272 {
273     YelpSearchEntryPrivate *priv =
274         yelp_search_entry_get_instance_private (YELP_SEARCH_ENTRY (object));
275 
276     switch (prop_id) {
277     case PROP_VIEW:
278         priv->view = g_value_dup_object (value);
279         break;
280     case PROP_BOOKMARKS:
281         priv->bookmarks = g_value_dup_object (value);
282         break;
283     default:
284         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
285         break;
286     }
287 }
288 
289 static void
search_entry_search_activated(YelpSearchEntry * entry)290 search_entry_search_activated  (YelpSearchEntry *entry)
291 {
292     YelpUri *base, *uri;
293     YelpSearchEntryPrivate *priv = yelp_search_entry_get_instance_private (entry);
294 
295     g_object_get (priv->view, "yelp-uri", &base, NULL);
296     if (base == NULL)
297         return;
298     uri = yelp_uri_new_search (base,
299                                gtk_entry_get_text (GTK_ENTRY (entry)));
300     g_object_unref (base);
301     yelp_view_load_uri (priv->view, uri);
302     gtk_widget_grab_focus (GTK_WIDGET (priv->view));
303 }
304 
305 static void
search_entry_bookmark_clicked(YelpSearchEntry * entry)306 search_entry_bookmark_clicked  (YelpSearchEntry *entry)
307 {
308     YelpUri *uri;
309     gchar *doc_uri, *page_id;
310     YelpSearchEntryPrivate *priv = yelp_search_entry_get_instance_private (entry);
311 
312     g_object_get (priv->view,
313                   "yelp-uri", &uri,
314                   "page-id", &page_id,
315                   NULL);
316     doc_uri = yelp_uri_get_document_uri (uri);
317     if (priv->bookmarks && doc_uri && page_id) {
318         if (!yelp_bookmarks_is_bookmarked (priv->bookmarks, doc_uri, page_id)) {
319             gchar *icon, *title;
320             g_object_get (priv->view,
321                           "page-icon", &icon,
322                           "page-title", &title,
323                           NULL);
324             yelp_bookmarks_add_bookmark (priv->bookmarks, doc_uri, page_id, icon, title);
325             g_free (icon);
326             g_free (title);
327         }
328         else {
329             yelp_bookmarks_remove_bookmark (priv->bookmarks, doc_uri, page_id);
330         }
331     }
332     g_free (doc_uri);
333     g_free (page_id);
334     g_object_unref (uri);
335 }
336 
337 static void
search_entry_set_completion(YelpSearchEntry * entry,GtkTreeModel * model)338 search_entry_set_completion (YelpSearchEntry *entry,
339 			     GtkTreeModel    *model)
340 {
341     YelpSearchEntryPrivate *priv = yelp_search_entry_get_instance_private (entry);
342     GList *cells;
343     GtkCellRenderer *icon_cell, *bookmark_cell;
344 
345     priv->completion = gtk_entry_completion_new ();
346     gtk_entry_completion_set_minimum_key_length (priv->completion, 3);
347     gtk_entry_completion_set_model (priv->completion, model);
348     gtk_entry_completion_set_text_column (priv->completion, COMPLETION_COL_TITLE);
349     gtk_entry_completion_set_match_func (priv->completion,
350                                          (GtkEntryCompletionMatchFunc) entry_match_func,
351                                          entry, NULL);
352     g_signal_connect (priv->completion, "match-selected",
353                       G_CALLBACK (entry_match_selected), entry);
354 
355     cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (priv->completion));
356     g_object_set (cells->data, "xpad", 4, NULL);
357     gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (priv->completion),
358                                         GTK_CELL_RENDERER (cells->data),
359                                         (GtkCellLayoutDataFunc) cell_set_completion_text_cell,
360                                         entry, NULL);
361     g_object_set (cells->data, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
362     g_list_free (cells);
363 
364     icon_cell = gtk_cell_renderer_pixbuf_new ();
365     g_object_set (icon_cell, "yalign", 0.2, NULL);
366     gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (priv->completion), icon_cell, FALSE);
367     gtk_cell_layout_reorder (GTK_CELL_LAYOUT (priv->completion), icon_cell, 0);
368     gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (priv->completion),
369                                     icon_cell,
370                                     "icon-name",
371                                     COMPLETION_COL_ICON,
372                                     NULL);
373     if (priv->bookmarks) {
374         bookmark_cell = gtk_cell_renderer_pixbuf_new ();
375         gtk_cell_layout_pack_end (GTK_CELL_LAYOUT (priv->completion), bookmark_cell, FALSE);
376         gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (priv->completion),
377                                             bookmark_cell,
378                                             (GtkCellLayoutDataFunc) cell_set_completion_bookmark_icon,
379                                             entry, NULL);
380     }
381     gtk_entry_set_completion (GTK_ENTRY (entry), priv->completion);
382 }
383 
384 static void
entry_activate_cb(GtkEntry * text_entry,gpointer user_data)385 entry_activate_cb (GtkEntry  *text_entry,
386                    gpointer   user_data)
387 {
388     gchar *text = g_strdup (gtk_entry_get_text (text_entry));
389 
390     if (text == NULL || strlen(text) == 0)
391         return;
392 
393     g_signal_emit (user_data, search_entry_signals[SEARCH_ACTIVATED], 0, text);
394 
395     g_free (text);
396 }
397 
398 static void
cell_set_completion_bookmark_icon(GtkCellLayout * layout,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,YelpSearchEntry * entry)399 cell_set_completion_bookmark_icon (GtkCellLayout     *layout,
400                                    GtkCellRenderer   *cell,
401                                    GtkTreeModel      *model,
402                                    GtkTreeIter       *iter,
403                                    YelpSearchEntry *entry)
404 {
405     YelpSearchEntryPrivate *priv = yelp_search_entry_get_instance_private (entry);
406 
407     if (priv->completion_uri) {
408         gchar *page_id = NULL;
409         gtk_tree_model_get (model, iter,
410                             COMPLETION_COL_PAGE, &page_id,
411                             -1);
412 
413         if (page_id && yelp_bookmarks_is_bookmarked (priv->bookmarks,
414                                                      priv->completion_uri, page_id))
415             g_object_set (cell, "icon-name", "user-bookmarks-symbolic", NULL);
416         else
417             g_object_set (cell, "icon-name", NULL, NULL);
418 
419         g_free (page_id);
420     }
421 }
422 
423 static void
cell_set_completion_text_cell(GtkCellLayout * layout,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,YelpSearchEntry * entry)424 cell_set_completion_text_cell (GtkCellLayout     *layout,
425                                GtkCellRenderer   *cell,
426                                GtkTreeModel      *model,
427                                GtkTreeIter       *iter,
428                                YelpSearchEntry *entry)
429 {
430     gchar *title;
431     gint flags;
432 
433     gtk_tree_model_get (model, iter, COMPLETION_COL_FLAGS, &flags, -1);
434     if (flags & COMPLETION_FLAG_ACTIVATE_SEARCH) {
435         title = g_strdup_printf (_("Search for “%s”"),
436                                  gtk_entry_get_text (GTK_ENTRY (entry)));
437         g_object_set (cell, "text", title, NULL);
438         g_free (title);
439         return;
440     }
441 
442     gtk_tree_model_get (model, iter,
443                         COMPLETION_COL_TITLE, &title,
444                         -1);
445     g_object_set (cell, "text", title, NULL);
446     g_free (title);
447 }
448 
449 static gboolean
entry_match_func(GtkEntryCompletion * completion,const gchar * key,GtkTreeIter * iter,YelpSearchEntry * entry)450 entry_match_func (GtkEntryCompletion *completion,
451                   const gchar        *key,
452                   GtkTreeIter        *iter,
453                   YelpSearchEntry  *entry)
454 {
455     gint stri;
456     gchar *title, *desc, *keywords, *titlecase = NULL, *desccase = NULL, *keywordscase = NULL;
457     gboolean ret = FALSE;
458     gchar **strs;
459     gint flags;
460     GtkTreeModel *model = gtk_entry_completion_get_model (completion);
461     static GRegex *nonword = NULL;
462 
463     if (nonword == NULL)
464         nonword = g_regex_new ("\\W", 0, 0, NULL);
465     if (nonword == NULL)
466         return FALSE;
467 
468     gtk_tree_model_get (model, iter, COMPLETION_COL_FLAGS, &flags, -1);
469     if (flags & COMPLETION_FLAG_ACTIVATE_SEARCH)
470         return TRUE;
471 
472     gtk_tree_model_get (model, iter,
473                         COMPLETION_COL_TITLE, &title,
474                         COMPLETION_COL_DESC, &desc,
475                         COMPLETION_COL_KEYWORDS, &keywords,
476                         -1);
477     if (title) {
478         titlecase = g_utf8_casefold (title, -1);
479         g_free (title);
480     }
481     if (desc) {
482         desccase = g_utf8_casefold (desc, -1);
483         g_free (desc);
484     }
485     if (keywords) {
486         keywordscase = g_utf8_casefold (keywords, -1);
487         g_free (keywords);
488     }
489 
490     strs = g_regex_split (nonword, key, 0);
491     ret = TRUE;
492     for (stri = 0; strs[stri]; stri++) {
493         if (!titlecase || !strstr (titlecase, strs[stri])) {
494             if (!desccase || !strstr (desccase, strs[stri])) {
495                 if (!keywordscase || !strstr (keywordscase, strs[stri])) {
496                     ret = FALSE;
497                     break;
498                 }
499             }
500         }
501     }
502 
503     g_free (titlecase);
504     g_free (desccase);
505     g_strfreev (strs);
506 
507     return ret;
508 }
509 
510 static gint
entry_completion_sort(GtkTreeModel * model,GtkTreeIter * iter1,GtkTreeIter * iter2,gpointer user_data)511 entry_completion_sort (GtkTreeModel *model,
512                        GtkTreeIter  *iter1,
513                        GtkTreeIter  *iter2,
514                        gpointer      user_data)
515 {
516     gint ret = 0;
517     gint flags1, flags2;
518     gchar *key1, *key2;
519 
520     gtk_tree_model_get (model, iter1, COMPLETION_COL_FLAGS, &flags1, -1);
521     gtk_tree_model_get (model, iter2, COMPLETION_COL_FLAGS, &flags2, -1);
522     if (flags1 & COMPLETION_FLAG_ACTIVATE_SEARCH)
523         return 1;
524     else if (flags2 & COMPLETION_FLAG_ACTIVATE_SEARCH)
525         return -1;
526 
527     gtk_tree_model_get (model, iter1, COMPLETION_COL_ICON, &key1, -1);
528     gtk_tree_model_get (model, iter2, COMPLETION_COL_ICON, &key2, -1);
529     ret = yelp_settings_cmp_icons (key1, key2);
530     g_free (key1);
531     g_free (key2);
532 
533     if (ret)
534         return ret;
535 
536     gtk_tree_model_get (model, iter1, COMPLETION_COL_TITLE, &key1, -1);
537     gtk_tree_model_get (model, iter2, COMPLETION_COL_TITLE, &key2, -1);
538     if (key1 && key2)
539         ret = g_utf8_collate (key1, key2);
540     else if (key2 == NULL)
541         return -1;
542     else if (key1 == NULL)
543         return 1;
544     g_free (key1);
545     g_free (key2);
546 
547     return ret;
548 }
549 
550 static gboolean
entry_match_selected(GtkEntryCompletion * completion,GtkTreeModel * model,GtkTreeIter * iter,YelpSearchEntry * entry)551 entry_match_selected (GtkEntryCompletion *completion,
552                       GtkTreeModel       *model,
553                       GtkTreeIter        *iter,
554                       YelpSearchEntry    *entry)
555 {
556     YelpUri *base, *uri;
557     gchar *page, *xref;
558     gint flags;
559     YelpSearchEntryPrivate *priv = yelp_search_entry_get_instance_private (entry);
560 
561     gtk_tree_model_get (model, iter, COMPLETION_COL_FLAGS, &flags, -1);
562     if (flags & COMPLETION_FLAG_ACTIVATE_SEARCH) {
563         entry_activate_cb (GTK_ENTRY (entry), entry);
564         return TRUE;
565     }
566 
567     g_object_get (priv->view, "yelp-uri", &base, NULL);
568     gtk_tree_model_get (model, iter, COMPLETION_COL_PAGE, &page, -1);
569 
570     xref = g_strconcat ("xref:", page, NULL);
571     uri = yelp_uri_new_relative (base, xref);
572 
573     yelp_view_load_uri (priv->view, uri);
574 
575     g_free (page);
576     g_free (xref);
577     g_object_unref (uri);
578     g_object_unref (base);
579 
580     gtk_widget_grab_focus (GTK_WIDGET (priv->view));
581     return TRUE;
582 }
583 
584 static void
view_loaded(YelpView * view,YelpSearchEntry * entry)585 view_loaded (YelpView          *view,
586              YelpSearchEntry *entry)
587 {
588     gchar **ids;
589     gint i;
590     GtkTreeIter iter;
591     YelpUri *uri;
592     gchar *doc_uri;
593     GtkTreeModel *completion;
594     YelpSearchEntryPrivate *priv = yelp_search_entry_get_instance_private (entry);
595     YelpDocument *document = yelp_view_get_document (view);
596 
597     g_object_get (view, "yelp-uri", &uri, NULL);
598     doc_uri = yelp_uri_get_document_uri (uri);
599 
600     if ((priv->completion_uri == NULL) ||
601         !g_str_equal (doc_uri, priv->completion_uri)) {
602         completion = (GtkTreeModel *) g_hash_table_lookup (completions, doc_uri);
603         if (completion == NULL) {
604             GtkListStore *base = gtk_list_store_new (6,
605                                                      G_TYPE_STRING,  /* title */
606                                                      G_TYPE_STRING,  /* desc */
607                                                      G_TYPE_STRING,  /* icon */
608                                                      G_TYPE_STRING,  /* uri */
609                                                      G_TYPE_INT,     /* flags */
610                                                      G_TYPE_STRING   /* keywords */
611                                                      );
612             completion = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (base));
613             gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (completion),
614                                                      entry_completion_sort,
615                                                      NULL, NULL);
616             g_hash_table_insert (completions, g_strdup (doc_uri), completion);
617             if (document != NULL) {
618                 ids = yelp_document_list_page_ids (document);
619                 for (i = 0; ids[i]; i++) {
620                     gchar *title, *desc, *icon, *keywords;
621                     gtk_list_store_insert (GTK_LIST_STORE (base), &iter, 0);
622                     title = yelp_document_get_page_title (document, ids[i]);
623                     desc = yelp_document_get_page_desc (document, ids[i]);
624                     icon = yelp_document_get_page_icon (document, ids[i]);
625                     keywords = yelp_document_get_page_keywords (document, ids[i]);
626                     gtk_list_store_set (base, &iter,
627                                         COMPLETION_COL_TITLE, title,
628                                         COMPLETION_COL_DESC, desc,
629                                         COMPLETION_COL_ICON, icon,
630                                         COMPLETION_COL_KEYWORDS, keywords,
631                                         COMPLETION_COL_PAGE, ids[i],
632                                         -1);
633                     g_free (icon);
634                     g_free (desc);
635                     g_free (title);
636                 }
637                 g_strfreev (ids);
638                 gtk_list_store_insert (GTK_LIST_STORE (base), &iter, 0);
639                 gtk_list_store_set (base, &iter,
640                                     COMPLETION_COL_ICON, "edit-find-symbolic",
641                                     COMPLETION_COL_FLAGS, COMPLETION_FLAG_ACTIVATE_SEARCH,
642                                     -1);
643             }
644             g_object_unref (base);
645         }
646         g_free (priv->completion_uri);
647         priv->completion_uri = doc_uri;
648         search_entry_set_completion (entry, completion);
649     }
650 
651     g_object_unref (uri);
652 }
653 
654 /**
655  * yelp_search_entry_new:
656  * @view: A #YelpView.
657  *
658  * Creates a new #YelpSearchEntry widget to control @view.
659  *
660  * Returns: A new #YelpSearchEntry.
661  **/
662 GtkWidget*
yelp_search_entry_new(YelpView * view,YelpBookmarks * bookmarks)663 yelp_search_entry_new (YelpView      *view,
664 		       YelpBookmarks *bookmarks)
665 {
666     GtkWidget *ret;
667     g_return_val_if_fail (YELP_IS_VIEW (view), NULL);
668 
669     ret = GTK_WIDGET (g_object_new (YELP_TYPE_SEARCH_ENTRY,
670                                     "view", view,
671                                     "bookmarks", bookmarks,
672                                     NULL));
673 
674     return ret;
675 }
676