1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
25 #include <gdk/gdkkeysyms.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
,
40 static void search_entry_set_property (GObject
*object
,
46 static void search_entry_search_activated (YelpSearchEntry
*entry
);
47 static void search_entry_bookmark_clicked (YelpSearchEntry
*entry
);
51 static void search_entry_set_completion (YelpSearchEntry
*entry
,
55 /* GtkEntry callbacks */
56 static void entry_activate_cb (GtkEntry
*text_entry
,
59 /* GtkEntryCompletion callbacks */
60 static void cell_set_completion_bookmark_icon (GtkCellLayout
*layout
,
61 GtkCellRenderer
*cell
,
64 YelpSearchEntry
*entry
);
65 static void cell_set_completion_text_cell (GtkCellLayout
*layout
,
66 GtkCellRenderer
*cell
,
69 YelpSearchEntry
*entry
);
70 static gboolean
entry_match_func (GtkEntryCompletion
*completion
,
73 YelpSearchEntry
*entry
);
74 static gint
entry_completion_sort (GtkTreeModel
*model
,
78 static gboolean
entry_match_selected (GtkEntryCompletion
*completion
,
81 YelpSearchEntry
*entry
);
82 /* YelpView callbacks */
83 static void view_loaded (YelpView
*view
,
84 YelpSearchEntry
*entry
);
87 typedef struct _YelpSearchEntryPrivate YelpSearchEntryPrivate
;
88 struct _YelpSearchEntryPrivate
91 YelpBookmarks
*bookmarks
;
92 gchar
*completion_uri
;
94 /* do not free below */
95 GtkEntryCompletion
*completion
;
107 COMPLETION_FLAG_ACTIVATE_SEARCH
= 1<<0
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))
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
154 search_entry_signals
[SEARCH_ACTIVATED
] =
155 g_signal_new ("search-activated",
156 G_OBJECT_CLASS_TYPE (klass
),
158 G_STRUCT_OFFSET (YelpSearchEntryClass
, search_activated
),
160 g_cclosure_marshal_VOID__STRING
,
165 * YelpLocationEntry:view
167 * The YelpView instance that this location entry controls.
169 g_object_class_install_property (object_class
,
171 g_param_spec_object ("view",
173 "A YelpView instance to control",
175 G_PARAM_CONSTRUCT_ONLY
|
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
,
187 g_param_spec_object ("bookmarks",
189 "A YelpBookmarks implementation instance",
191 G_PARAM_CONSTRUCT_ONLY
|
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
);
202 yelp_search_entry_init (YelpSearchEntry
*entry
)
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
);
218 search_entry_dispose (GObject
*object
)
220 YelpSearchEntryPrivate
*priv
= GET_PRIV (object
);
223 g_object_unref (priv
->view
);
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
);
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
);
246 search_entry_get_property (GObject
*object
,
251 YelpSearchEntryPrivate
*priv
= GET_PRIV (object
);
255 g_value_set_object (value
, priv
->view
);
258 g_value_set_object (value
, priv
->bookmarks
);
261 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
267 search_entry_set_property (GObject
*object
,
272 YelpSearchEntryPrivate
*priv
= GET_PRIV (object
);
276 priv
->view
= g_value_dup_object (value
);
279 priv
->bookmarks
= g_value_dup_object (value
);
282 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
288 search_entry_search_activated (YelpSearchEntry
*entry
)
291 YelpSearchEntryPrivate
*priv
= GET_PRIV (entry
);
293 g_object_get (priv
->view
, "yelp-uri", &base
, NULL
);
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
));
304 search_entry_bookmark_clicked (YelpSearchEntry
*entry
)
307 gchar
*doc_uri
, *page_id
;
308 YelpSearchEntryPrivate
*priv
= GET_PRIV (entry
);
310 g_object_get (priv
->view
,
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
)) {
318 g_object_get (priv
->view
,
320 "page-title", &title
,
322 yelp_bookmarks_add_bookmark (priv
->bookmarks
, doc_uri
, page_id
, icon
, title
);
327 yelp_bookmarks_remove_bookmark (priv
->bookmarks
, doc_uri
, page_id
);
332 g_object_unref (uri
);
336 search_entry_set_completion (YelpSearchEntry
*entry
,
339 YelpSearchEntryPrivate
*priv
= GET_PRIV (entry
);
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
,
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
,
359 g_object_set (cells
->data
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
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
),
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
),
376 (GtkCellLayoutDataFunc
) cell_set_completion_bookmark_icon
,
379 gtk_entry_set_completion (GTK_ENTRY (entry
), priv
->completion
);
383 entry_activate_cb (GtkEntry
*text_entry
,
386 gchar
*text
= g_strdup (gtk_entry_get_text (text_entry
));
388 if (text
== NULL
|| strlen(text
) == 0)
391 g_signal_emit (user_data
, search_entry_signals
[SEARCH_ACTIVATED
], 0, text
);
397 cell_set_completion_bookmark_icon (GtkCellLayout
*layout
,
398 GtkCellRenderer
*cell
,
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
,
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
);
415 g_object_set (cell
, "icon-name", NULL
, NULL
);
422 cell_set_completion_text_cell (GtkCellLayout
*layout
,
423 GtkCellRenderer
*cell
,
426 YelpSearchEntry
*entry
)
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
);
440 gtk_tree_model_get (model
, iter
,
441 COMPLETION_COL_TITLE
, &title
,
443 g_object_set (cell
, "text", title
, NULL
);
448 entry_match_func (GtkEntryCompletion
*completion
,
451 YelpSearchEntry
*entry
)
454 gchar
*title
, *desc
, *titlecase
= NULL
, *desccase
= NULL
;
455 gboolean ret
= FALSE
;
458 GtkTreeModel
*model
= gtk_entry_completion_get_model (completion
);
459 static GRegex
*nonword
= NULL
;
462 nonword
= g_regex_new ("\\W", 0, 0, NULL
);
466 gtk_tree_model_get (model
, iter
, COMPLETION_COL_FLAGS
, &flags
, -1);
467 if (flags
& COMPLETION_FLAG_ACTIVATE_SEARCH
)
470 gtk_tree_model_get (model
, iter
,
471 COMPLETION_COL_TITLE
, &title
,
472 COMPLETION_COL_DESC
, &desc
,
475 titlecase
= g_utf8_casefold (title
, -1);
479 desccase
= g_utf8_casefold (desc
, -1);
483 strs
= g_regex_split (nonword
, key
, 0);
485 for (stri
= 0; strs
[stri
]; stri
++) {
486 if (!titlecase
|| !strstr (titlecase
, strs
[stri
])) {
487 if (!desccase
|| !strstr (desccase
, strs
[stri
])) {
502 entry_completion_sort (GtkTreeModel
*model
,
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
)
515 else if (flags2
& COMPLETION_FLAG_ACTIVATE_SEARCH
)
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
);
527 gtk_tree_model_get (model
, iter1
, COMPLETION_COL_TITLE
, &key1
, -1);
528 gtk_tree_model_get (model
, iter2
, COMPLETION_COL_TITLE
, &key2
, -1);
530 ret
= g_utf8_collate (key1
, key2
);
531 else if (key2
== NULL
)
533 else if (key1
== NULL
)
542 entry_match_selected (GtkEntryCompletion
*completion
,
545 YelpSearchEntry
*entry
)
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
);
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
);
568 g_object_unref (uri
);
569 g_object_unref (base
);
571 gtk_widget_grab_focus (GTK_WIDGET (priv
->view
));
576 view_loaded (YelpView
*view
,
577 YelpSearchEntry
*entry
)
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
,
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
],
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
,
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.
651 yelp_search_entry_new (YelpView
*view
,
652 YelpBookmarks
*bookmarks
)
655 g_return_val_if_fail (YELP_IS_VIEW (view
), NULL
);
657 ret
= GTK_WIDGET (g_object_new (YELP_TYPE_SEARCH_ENTRY
,
659 "bookmarks", bookmarks
,