yelp-document: Fix return type of document_indexed
[yelp.git] / libyelp / yelp-search-entry.c
blob5b5706ce2875b55f9ebf7786c5cf7b3afdf306b4
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * Copyright (C) 2009-2014 Shaun McCance <shaunm@gnome.org>
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.
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.
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/>.
18 * Author: Shaun McCance <shaunm@gnome.org>
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
25 #include <gdk/gdkkeysyms.h>
26 #include <gtk/gtk.h>
27 #include <glib/gi18n.h>
29 #include "yelp-search-entry.h"
30 #include "yelp-marshal.h"
31 #include "yelp-settings.h"
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);
45 /* Signals */
46 static void search_entry_search_activated (YelpSearchEntry *entry);
47 static void search_entry_bookmark_clicked (YelpSearchEntry *entry);
50 /* Utilities */
51 static void search_entry_set_completion (YelpSearchEntry *entry,
52 GtkTreeModel *model);
55 /* GtkEntry callbacks */
56 static void entry_activate_cb (GtkEntry *text_entry,
57 gpointer user_data);
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);
87 typedef struct _YelpSearchEntryPrivate YelpSearchEntryPrivate;
88 struct _YelpSearchEntryPrivate
90 YelpView *view;
91 YelpBookmarks *bookmarks;
92 gchar *completion_uri;
94 /* do not free below */
95 GtkEntryCompletion *completion;
98 enum {
99 COMPLETION_COL_TITLE,
100 COMPLETION_COL_DESC,
101 COMPLETION_COL_ICON,
102 COMPLETION_COL_PAGE,
103 COMPLETION_COL_FLAGS
106 enum {
107 COMPLETION_FLAG_ACTIVATE_SEARCH = 1<<0
110 enum {
111 SEARCH_ACTIVATED,
112 LAST_SIGNAL
115 enum {
116 PROP_0,
117 PROP_VIEW,
118 PROP_BOOKMARKS
121 static GHashTable *completions;
123 static guint search_entry_signals[LAST_SIGNAL] = {0,};
125 G_DEFINE_TYPE (YelpSearchEntry, yelp_search_entry, GTK_TYPE_SEARCH_ENTRY)
126 #define GET_PRIV(object) (G_TYPE_INSTANCE_GET_PRIVATE((object), YELP_TYPE_SEARCH_ENTRY, YelpSearchEntryPrivate))
128 static void
129 yelp_search_entry_class_init (YelpSearchEntryClass *klass)
131 GObjectClass *object_class;
133 klass->search_activated = search_entry_search_activated;
134 klass->bookmark_clicked = search_entry_bookmark_clicked;
136 object_class = G_OBJECT_CLASS (klass);
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;
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.
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.
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);
165 * YelpLocationEntry:view
167 * The YelpView instance that this location entry controls.
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));
180 * YelpLocationEntry:bookmarks
182 * An instance of an implementation of YelpBookmarks to provide
183 * bookmark information for this location entry.
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));
195 g_type_class_add_private ((GObjectClass *) klass,
196 sizeof (YelpSearchEntryPrivate));
198 completions = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
201 static void
202 yelp_search_entry_init (YelpSearchEntry *entry)
206 static void
207 search_entry_constructed (GObject *object)
209 YelpSearchEntryPrivate *priv = GET_PRIV (object);
211 g_signal_connect (object, "activate",
212 G_CALLBACK (entry_activate_cb), object);
214 g_signal_connect (priv->view, "loaded", G_CALLBACK (view_loaded), object);
217 static void
218 search_entry_dispose (GObject *object)
220 YelpSearchEntryPrivate *priv = GET_PRIV (object);
222 if (priv->view) {
223 g_object_unref (priv->view);
224 priv->view = NULL;
227 if (priv->bookmarks) {
228 g_object_unref (priv->bookmarks);
229 priv->bookmarks = NULL;
232 G_OBJECT_CLASS (yelp_search_entry_parent_class)->dispose (object);
235 static void
236 search_entry_finalize (GObject *object)
238 YelpSearchEntryPrivate *priv = GET_PRIV (object);
240 g_free (priv->completion_uri);
242 G_OBJECT_CLASS (yelp_search_entry_parent_class)->finalize (object);
245 static void
246 search_entry_get_property (GObject *object,
247 guint prop_id,
248 GValue *value,
249 GParamSpec *pspec)
251 YelpSearchEntryPrivate *priv = GET_PRIV (object);
253 switch (prop_id) {
254 case PROP_VIEW:
255 g_value_set_object (value, priv->view);
256 break;
257 case PROP_BOOKMARKS:
258 g_value_set_object (value, priv->bookmarks);
259 break;
260 default:
261 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
262 break;
266 static void
267 search_entry_set_property (GObject *object,
268 guint prop_id,
269 const GValue *value,
270 GParamSpec *pspec)
272 YelpSearchEntryPrivate *priv = GET_PRIV (object);
274 switch (prop_id) {
275 case PROP_VIEW:
276 priv->view = g_value_dup_object (value);
277 break;
278 case PROP_BOOKMARKS:
279 priv->bookmarks = g_value_dup_object (value);
280 break;
281 default:
282 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
283 break;
287 static void
288 search_entry_search_activated (YelpSearchEntry *entry)
290 YelpUri *base, *uri;
291 YelpSearchEntryPrivate *priv = GET_PRIV (entry);
293 g_object_get (priv->view, "yelp-uri", &base, NULL);
294 if (base == NULL)
295 return;
296 uri = yelp_uri_new_search (base,
297 gtk_entry_get_text (GTK_ENTRY (entry)));
298 g_object_unref (base);
299 yelp_view_load_uri (priv->view, uri);
300 gtk_widget_grab_focus (GTK_WIDGET (priv->view));
303 static void
304 search_entry_bookmark_clicked (YelpSearchEntry *entry)
306 YelpUri *uri;
307 gchar *doc_uri, *page_id;
308 YelpSearchEntryPrivate *priv = GET_PRIV (entry);
310 g_object_get (priv->view,
311 "yelp-uri", &uri,
312 "page-id", &page_id,
313 NULL);
314 doc_uri = yelp_uri_get_document_uri (uri);
315 if (priv->bookmarks && doc_uri && page_id) {
316 if (!yelp_bookmarks_is_bookmarked (priv->bookmarks, doc_uri, page_id)) {
317 gchar *icon, *title;
318 g_object_get (priv->view,
319 "page-icon", &icon,
320 "page-title", &title,
321 NULL);
322 yelp_bookmarks_add_bookmark (priv->bookmarks, doc_uri, page_id, icon, title);
323 g_free (icon);
324 g_free (title);
326 else {
327 yelp_bookmarks_remove_bookmark (priv->bookmarks, doc_uri, page_id);
330 g_free (doc_uri);
331 g_free (page_id);
332 g_object_unref (uri);
335 static void
336 search_entry_set_completion (YelpSearchEntry *entry,
337 GtkTreeModel *model)
339 YelpSearchEntryPrivate *priv = GET_PRIV (entry);
340 GList *cells;
341 GtkCellRenderer *icon_cell, *bookmark_cell;
343 priv->completion = gtk_entry_completion_new ();
344 gtk_entry_completion_set_minimum_key_length (priv->completion, 3);
345 gtk_entry_completion_set_model (priv->completion, model);
346 gtk_entry_completion_set_text_column (priv->completion, COMPLETION_COL_TITLE);
347 gtk_entry_completion_set_match_func (priv->completion,
348 (GtkEntryCompletionMatchFunc) entry_match_func,
349 entry, NULL);
350 g_signal_connect (priv->completion, "match-selected",
351 G_CALLBACK (entry_match_selected), entry);
353 cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (priv->completion));
354 g_object_set (cells->data, "xpad", 4, NULL);
355 gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (priv->completion),
356 GTK_CELL_RENDERER (cells->data),
357 (GtkCellLayoutDataFunc) cell_set_completion_text_cell,
358 entry, NULL);
359 g_object_set (cells->data, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
360 g_list_free (cells);
362 icon_cell = gtk_cell_renderer_pixbuf_new ();
363 g_object_set (icon_cell, "yalign", 0.2, NULL);
364 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (priv->completion), icon_cell, FALSE);
365 gtk_cell_layout_reorder (GTK_CELL_LAYOUT (priv->completion), icon_cell, 0);
366 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (priv->completion),
367 icon_cell,
368 "icon-name",
369 COMPLETION_COL_ICON,
370 NULL);
371 if (priv->bookmarks) {
372 bookmark_cell = gtk_cell_renderer_pixbuf_new ();
373 gtk_cell_layout_pack_end (GTK_CELL_LAYOUT (priv->completion), bookmark_cell, FALSE);
374 gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (priv->completion),
375 bookmark_cell,
376 (GtkCellLayoutDataFunc) cell_set_completion_bookmark_icon,
377 entry, NULL);
379 gtk_entry_set_completion (GTK_ENTRY (entry), priv->completion);
382 static void
383 entry_activate_cb (GtkEntry *text_entry,
384 gpointer user_data)
386 gchar *text = g_strdup (gtk_entry_get_text (text_entry));
388 if (text == NULL || strlen(text) == 0)
389 return;
391 g_signal_emit (user_data, search_entry_signals[SEARCH_ACTIVATED], 0, text);
393 g_free (text);
396 static void
397 cell_set_completion_bookmark_icon (GtkCellLayout *layout,
398 GtkCellRenderer *cell,
399 GtkTreeModel *model,
400 GtkTreeIter *iter,
401 YelpSearchEntry *entry)
403 YelpSearchEntryPrivate *priv = GET_PRIV (entry);
405 if (priv->completion_uri) {
406 gchar *page_id = NULL;
407 gtk_tree_model_get (model, iter,
408 COMPLETION_COL_PAGE, &page_id,
409 -1);
411 if (page_id && yelp_bookmarks_is_bookmarked (priv->bookmarks,
412 priv->completion_uri, page_id))
413 g_object_set (cell, "icon-name", "user-bookmarks-symbolic", NULL);
414 else
415 g_object_set (cell, "icon-name", NULL, NULL);
417 g_free (page_id);
421 static void
422 cell_set_completion_text_cell (GtkCellLayout *layout,
423 GtkCellRenderer *cell,
424 GtkTreeModel *model,
425 GtkTreeIter *iter,
426 YelpSearchEntry *entry)
428 gchar *title;
429 gint flags;
431 gtk_tree_model_get (model, iter, COMPLETION_COL_FLAGS, &flags, -1);
432 if (flags & COMPLETION_FLAG_ACTIVATE_SEARCH) {
433 title = g_strdup_printf (_("Search for ā€œ%sā€"),
434 gtk_entry_get_text (GTK_ENTRY (entry)));
435 g_object_set (cell, "text", title, NULL);
436 g_free (title);
437 return;
440 gtk_tree_model_get (model, iter,
441 COMPLETION_COL_TITLE, &title,
442 -1);
443 g_object_set (cell, "text", title, NULL);
444 g_free (title);
447 static gboolean
448 entry_match_func (GtkEntryCompletion *completion,
449 const gchar *key,
450 GtkTreeIter *iter,
451 YelpSearchEntry *entry)
453 gint stri;
454 gchar *title, *desc, *titlecase = NULL, *desccase = NULL;
455 gboolean ret = FALSE;
456 gchar **strs;
457 gint flags;
458 GtkTreeModel *model = gtk_entry_completion_get_model (completion);
459 static GRegex *nonword = NULL;
461 if (nonword == NULL)
462 nonword = g_regex_new ("\\W", 0, 0, NULL);
463 if (nonword == NULL)
464 return FALSE;
466 gtk_tree_model_get (model, iter, COMPLETION_COL_FLAGS, &flags, -1);
467 if (flags & COMPLETION_FLAG_ACTIVATE_SEARCH)
468 return TRUE;
470 gtk_tree_model_get (model, iter,
471 COMPLETION_COL_TITLE, &title,
472 COMPLETION_COL_DESC, &desc,
473 -1);
474 if (title) {
475 titlecase = g_utf8_casefold (title, -1);
476 g_free (title);
478 if (desc) {
479 desccase = g_utf8_casefold (desc, -1);
480 g_free (desc);
483 strs = g_regex_split (nonword, key, 0);
484 ret = TRUE;
485 for (stri = 0; strs[stri]; stri++) {
486 if (!titlecase || !strstr (titlecase, strs[stri])) {
487 if (!desccase || !strstr (desccase, strs[stri])) {
488 ret = FALSE;
489 break;
494 g_free (titlecase);
495 g_free (desccase);
496 g_strfreev (strs);
498 return ret;
501 static gint
502 entry_completion_sort (GtkTreeModel *model,
503 GtkTreeIter *iter1,
504 GtkTreeIter *iter2,
505 gpointer user_data)
507 gint ret = 0;
508 gint flags1, flags2;
509 gchar *key1, *key2;
511 gtk_tree_model_get (model, iter1, COMPLETION_COL_FLAGS, &flags1, -1);
512 gtk_tree_model_get (model, iter2, COMPLETION_COL_FLAGS, &flags2, -1);
513 if (flags1 & COMPLETION_FLAG_ACTIVATE_SEARCH)
514 return 1;
515 else if (flags2 & COMPLETION_FLAG_ACTIVATE_SEARCH)
516 return -1;
518 gtk_tree_model_get (model, iter1, COMPLETION_COL_ICON, &key1, -1);
519 gtk_tree_model_get (model, iter2, COMPLETION_COL_ICON, &key2, -1);
520 ret = yelp_settings_cmp_icons (key1, key2);
521 g_free (key1);
522 g_free (key2);
524 if (ret)
525 return ret;
527 gtk_tree_model_get (model, iter1, COMPLETION_COL_TITLE, &key1, -1);
528 gtk_tree_model_get (model, iter2, COMPLETION_COL_TITLE, &key2, -1);
529 if (key1 && key2)
530 ret = g_utf8_collate (key1, key2);
531 else if (key2 == NULL)
532 return -1;
533 else if (key1 == NULL)
534 return 1;
535 g_free (key1);
536 g_free (key2);
538 return ret;
541 static gboolean
542 entry_match_selected (GtkEntryCompletion *completion,
543 GtkTreeModel *model,
544 GtkTreeIter *iter,
545 YelpSearchEntry *entry)
547 YelpUri *base, *uri;
548 gchar *page, *xref;
549 gint flags;
550 YelpSearchEntryPrivate *priv = GET_PRIV (entry);
552 gtk_tree_model_get (model, iter, COMPLETION_COL_FLAGS, &flags, -1);
553 if (flags & COMPLETION_FLAG_ACTIVATE_SEARCH) {
554 entry_activate_cb (GTK_ENTRY (entry), entry);
555 return TRUE;
558 g_object_get (priv->view, "yelp-uri", &base, NULL);
559 gtk_tree_model_get (model, iter, COMPLETION_COL_PAGE, &page, -1);
561 xref = g_strconcat ("xref:", page, NULL);
562 uri = yelp_uri_new_relative (base, xref);
564 yelp_view_load_uri (priv->view, uri);
566 g_free (page);
567 g_free (xref);
568 g_object_unref (uri);
569 g_object_unref (base);
571 gtk_widget_grab_focus (GTK_WIDGET (priv->view));
572 return TRUE;
575 static void
576 view_loaded (YelpView *view,
577 YelpSearchEntry *entry)
579 gchar **ids;
580 gint i;
581 GtkTreeIter iter;
582 YelpUri *uri;
583 gchar *doc_uri;
584 GtkTreeModel *completion;
585 YelpSearchEntryPrivate *priv = GET_PRIV (entry);
586 YelpDocument *document = yelp_view_get_document (view);
588 g_object_get (view, "yelp-uri", &uri, NULL);
589 doc_uri = yelp_uri_get_document_uri (uri);
591 if ((priv->completion_uri == NULL) ||
592 !g_str_equal (doc_uri, priv->completion_uri)) {
593 completion = (GtkTreeModel *) g_hash_table_lookup (completions, doc_uri);
594 if (completion == NULL) {
595 GtkListStore *base = gtk_list_store_new (5,
596 G_TYPE_STRING, /* title */
597 G_TYPE_STRING, /* desc */
598 G_TYPE_STRING, /* icon */
599 G_TYPE_STRING, /* uri */
600 G_TYPE_INT /* flags */
602 completion = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (base));
603 gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (completion),
604 entry_completion_sort,
605 NULL, NULL);
606 g_hash_table_insert (completions, g_strdup (doc_uri), completion);
607 if (document != NULL) {
608 ids = yelp_document_list_page_ids (document);
609 for (i = 0; ids[i]; i++) {
610 gchar *title, *desc, *icon;
611 gtk_list_store_insert (GTK_LIST_STORE (base), &iter, 0);
612 title = yelp_document_get_page_title (document, ids[i]);
613 desc = yelp_document_get_page_desc (document, ids[i]);
614 icon = yelp_document_get_page_icon (document, ids[i]);
615 gtk_list_store_set (base, &iter,
616 COMPLETION_COL_TITLE, title,
617 COMPLETION_COL_DESC, desc,
618 COMPLETION_COL_ICON, icon,
619 COMPLETION_COL_PAGE, ids[i],
620 -1);
621 g_free (icon);
622 g_free (desc);
623 g_free (title);
625 g_strfreev (ids);
626 gtk_list_store_insert (GTK_LIST_STORE (base), &iter, 0);
627 gtk_list_store_set (base, &iter,
628 COMPLETION_COL_ICON, "edit-find-symbolic",
629 COMPLETION_COL_FLAGS, COMPLETION_FLAG_ACTIVATE_SEARCH,
630 -1);
632 g_object_unref (base);
634 g_free (priv->completion_uri);
635 priv->completion_uri = doc_uri;
636 search_entry_set_completion (entry, completion);
639 g_object_unref (uri);
643 * yelp_search_entry_new:
644 * @view: A #YelpView.
646 * Creates a new #YelpSearchEntry widget to control @view.
648 * Returns: A new #YelpSearchEntry.
650 GtkWidget*
651 yelp_search_entry_new (YelpView *view,
652 YelpBookmarks *bookmarks)
654 GtkWidget *ret;
655 g_return_val_if_fail (YELP_IS_VIEW (view), NULL);
657 ret = GTK_WIDGET (g_object_new (YELP_TYPE_SEARCH_ENTRY,
658 "view", view,
659 "bookmarks", bookmarks,
660 NULL));
662 return ret;