Provide xslt translation domain for install link text
[yelp.git] / libyelp / yelp-location-entry.c
blobead3441ba5d8fdc6d9507bf0bff8f4ae45761af8
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * Copyright (C) 2009 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, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
20 * Author: Shaun McCance <shaunm@gnome.org>
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
27 #include <gdk/gdkkeysyms.h>
28 #include <gtk/gtk.h>
29 #include <glib/gi18n.h>
31 #include "yelp-location-entry.h"
32 #include "yelp-marshal.h"
33 #include "yelp-settings.h"
35 /**
36 * SECTION:yelp-location-entry
37 * @short_description: A location entry with history and search
38 * @include: yelp.h
40 * #YelpLocationEntry is a #GtkComboBox designed to show the current location,
41 * provide a drop-down menu of previous locations, and allow the user to perform
42 * searches.
44 * The #GtkTreeModel used by a #YelpLocationEntry is expected to have at least
45 * four columns: #GtkComboBox::entry-text-column contains the displayed name
46 * of the location, #YelpLocationEntry::desc-column contains a description
47 * for each entry, #YelpLocationEntry::icon-column contains an icon name for
48 * the location, and #YelpLocationEntry::flags-column contains a bit field
49 * of #YelpLocationEntryFlags. These columns are specified when creating a
50 * #YelpLocationEntry widget with yelp_location_entry_new_with_model().
52 * Usually, a single row in the #GtkTreeModel corresponds to a location. When
53 * a user selects a row from the drop-down menu, the icon and text for that
54 * row will be placed in the embedded text entry, and the
55 * #YelpLocationEntry:location-selected signal will be emitted.
57 * If a row has the %YELP_LOCATION_ENTRY_IS_SEARCH flag set, selecting that
58 * row will not emit the #YelpLocationEntry::location-selected signal. Instead,
59 * the #YelpLocationEntry widget will be placed into search mode, as if by a
60 * call to yelp_location_entry_start_search().
62 * When a row has the %YELP_LOCATION_ENTRY_CAN_BOOKMARK flag set, an icon
63 * will be displayed in the secondary icon position of the embedded text
64 * entry allowing the user to bookmark the location. Clicking this icon
65 * will cause the FIXME signal to be emitted.
66 **/
68 static void location_entry_constructed (GObject *object);
69 static void location_entry_dispose (GObject *object);
70 static void location_entry_finalize (GObject *object);
71 static void location_entry_get_property (GObject *object,
72 guint prop_id,
73 GValue *value,
74 GParamSpec *pspec);
75 static void location_entry_set_property (GObject *object,
76 guint prop_id,
77 const GValue *value,
78 GParamSpec *pspec);
80 /* Signals */
81 static void location_entry_location_selected (YelpLocationEntry *entry);
82 static void location_entry_search_activated (YelpLocationEntry *entry);
83 static void location_entry_bookmark_clicked (YelpLocationEntry *entry);
85 /* Utilities */
86 static void location_entry_start_search (YelpLocationEntry *entry,
87 gboolean clear);
88 static void location_entry_cancel_search (YelpLocationEntry *entry);
89 static void location_entry_set_entry (YelpLocationEntry *entry,
90 gboolean emit);
91 static gboolean location_entry_pulse (gpointer data);
92 static void location_entry_set_completion (YelpLocationEntry *entry,
93 GtkTreeModel *model);
96 /* GtkTreeModel callbacks */
97 static void history_row_changed (GtkTreeModel *model,
98 GtkTreePath *path,
99 GtkTreeIter *iter,
100 gpointer user_data);
101 /* GtkComboBox callbacks */
102 static void combo_box_changed_cb (GtkComboBox *widget,
103 gpointer user_data);
104 static gboolean combo_box_row_separator_func (GtkTreeModel *model,
105 GtkTreeIter *iter,
106 gpointer user_data);
107 /* GtkEntry callbacks */
108 static gboolean entry_focus_in_cb (GtkWidget *widget,
109 GdkEventFocus *event,
110 gpointer user_data);
111 static gboolean entry_focus_out_cb (GtkWidget *widget,
112 GdkEventFocus *event,
113 gpointer user_data);
114 static void entry_activate_cb (GtkEntry *text_entry,
115 gpointer user_data);
116 static void entry_icon_press_cb (GtkEntry *gtkentry,
117 GtkEntryIconPosition icon_pos,
118 GdkEvent *event,
119 YelpLocationEntry *entry);
120 static gboolean entry_key_press_cb (GtkWidget *widget,
121 GdkEventKey *event,
122 YelpLocationEntry *entry);
123 /* GtkCellLayout callbacks */
124 static void cell_set_text_cell (GtkCellLayout *layout,
125 GtkCellRenderer *cell,
126 GtkTreeModel *model,
127 GtkTreeIter *iter,
128 YelpLocationEntry *entry);
129 static void cell_set_bookmark_icon (GtkCellLayout *layout,
130 GtkCellRenderer *cell,
131 GtkTreeModel *model,
132 GtkTreeIter *iter,
133 YelpLocationEntry *entry);
134 /* GtkEntryCompletion callbacks */
135 static void cell_set_completion_bookmark_icon (GtkCellLayout *layout,
136 GtkCellRenderer *cell,
137 GtkTreeModel *model,
138 GtkTreeIter *iter,
139 YelpLocationEntry *entry);
140 static void cell_set_completion_text_cell (GtkCellLayout *layout,
141 GtkCellRenderer *cell,
142 GtkTreeModel *model,
143 GtkTreeIter *iter,
144 YelpLocationEntry *entry);
145 static gboolean entry_match_func (GtkEntryCompletion *completion,
146 const gchar *key,
147 GtkTreeIter *iter,
148 YelpLocationEntry *entry);
149 static gint entry_completion_sort (GtkTreeModel *model,
150 GtkTreeIter *iter1,
151 GtkTreeIter *iter2,
152 gpointer user_data);
153 static gboolean entry_match_selected (GtkEntryCompletion *completion,
154 GtkTreeModel *model,
155 GtkTreeIter *iter,
156 YelpLocationEntry *entry);
157 /* YelpView callbacks */
158 static void view_loaded (YelpView *view,
159 YelpLocationEntry *entry);
160 static void view_uri_selected (YelpView *view,
161 GParamSpec *pspec,
162 YelpLocationEntry *entry);
163 static void view_page_title (YelpView *view,
164 GParamSpec *pspec,
165 YelpLocationEntry *entry);
166 static void view_page_desc (YelpView *view,
167 GParamSpec *pspec,
168 YelpLocationEntry *entry);
169 static void view_page_icon (YelpView *view,
170 GParamSpec *pspec,
171 YelpLocationEntry *entry);
172 /* YelpBookmarks callbacks */
173 static void bookmarks_changed (YelpBookmarks *bookmarks,
174 const gchar *doc_uri,
175 YelpLocationEntry *entry);
179 typedef struct _YelpLocationEntryPrivate YelpLocationEntryPrivate;
180 struct _YelpLocationEntryPrivate
182 YelpView *view;
183 YelpBookmarks *bookmarks;
184 GtkTreeRowReference *row;
185 gchar *completion_uri;
187 /* do not free below */
188 GtkWidget *text_entry;
189 GtkCellRenderer *icon_cell;
190 GtkListStore *history;
191 GtkEntryCompletion *completion;
193 gboolean enable_search;
194 gboolean view_uri_selected;
195 gboolean search_mode;
196 guint pulse;
197 gulong bookmarks_changed;
199 gboolean icon_is_clear;
202 enum {
203 LOCATION_ENTRY_IS_LOADING = 1 << 0,
204 LOCATION_ENTRY_IS_SEPARATOR = 1 << 1,
205 LOCATION_ENTRY_IS_SEARCH = 1 << 2
208 enum {
209 HISTORY_COL_TITLE,
210 HISTORY_COL_DESC,
211 HISTORY_COL_ICON,
212 HISTORY_COL_URI,
213 HISTORY_COL_DOC,
214 HISTORY_COL_PAGE,
215 HISTORY_COL_FLAGS,
216 HISTORY_COL_TERMS
219 enum {
220 COMPLETION_COL_TITLE,
221 COMPLETION_COL_DESC,
222 COMPLETION_COL_ICON,
223 COMPLETION_COL_PAGE,
224 COMPLETION_COL_FLAGS
227 enum {
228 LOCATION_SELECTED,
229 SEARCH_ACTIVATED,
230 BOOKMARK_CLICKED,
231 LAST_SIGNAL
234 enum {
235 PROP_0,
236 PROP_VIEW,
237 PROP_BOOKMARKS,
238 PROP_ENABLE_SEARCH
241 static GHashTable *completions;
243 static guint location_entry_signals[LAST_SIGNAL] = {0,};
245 #define MAX_HISTORY 8
247 G_DEFINE_TYPE (YelpLocationEntry, yelp_location_entry, GTK_TYPE_COMBO_BOX)
248 #define GET_PRIV(object) (G_TYPE_INSTANCE_GET_PRIVATE((object), YELP_TYPE_LOCATION_ENTRY, YelpLocationEntryPrivate))
250 static void
251 yelp_location_entry_class_init (YelpLocationEntryClass *klass)
253 GObjectClass *object_class;
255 klass->location_selected = location_entry_location_selected;
256 klass->search_activated = location_entry_search_activated;
257 klass->bookmark_clicked = location_entry_bookmark_clicked;
259 object_class = G_OBJECT_CLASS (klass);
261 object_class->constructed = location_entry_constructed;
262 object_class->dispose = location_entry_dispose;
263 object_class->finalize = location_entry_finalize;
264 object_class->get_property = location_entry_get_property;
265 object_class->set_property = location_entry_set_property;
268 * YelpLocationEntry::location-selected
269 * @widget: The #YelpLocationEntry for which the signal was emitted.
270 * @user_data: User data set when the handler was connected.
272 * This signal will be emitted whenever a user selects a normal row from the
273 * drop-down menu. Note that if a row has the flag %YELP_LOCATION_ENTRY_IS_SEARCH,
274 * clicking the row will cause @widget to enter search mode, and this signal
275 * will not be emitted.
277 location_entry_signals[LOCATION_SELECTED] =
278 g_signal_new ("location-selected",
279 G_OBJECT_CLASS_TYPE (klass),
280 G_SIGNAL_RUN_LAST,
281 G_STRUCT_OFFSET (YelpLocationEntryClass, location_selected),
282 NULL, NULL,
283 g_cclosure_marshal_VOID__VOID,
284 G_TYPE_NONE, 0);
287 * YelpLocationEntry::search-activated
288 * @widget: The #YelpLocationEntry for which the signal was emitted.
289 * @text: The search text.
290 * @user_data: User data set when the handler was connected.
292 * This signal will be emitted whenever the user activates a search, generally
293 * by pressing <keycap>Enter</keycap> in the embedded text entry while @widget
294 * is in search mode.
296 location_entry_signals[SEARCH_ACTIVATED] =
297 g_signal_new ("search-activated",
298 G_OBJECT_CLASS_TYPE (klass),
299 G_SIGNAL_RUN_LAST,
300 G_STRUCT_OFFSET (YelpLocationEntryClass, search_activated),
301 NULL, NULL,
302 g_cclosure_marshal_VOID__STRING,
303 G_TYPE_NONE, 1,
304 G_TYPE_STRING);
307 * YelpLocationEntry::bookmark-clicked
308 * @widget: The #YelpLocationEntry for which the signal was emitted.
309 * @user_data: User data set when the handler was connected.
311 * This signal will be emitted whenever a user clicks the bookmark icon
312 * embedded in the location entry.
314 location_entry_signals[BOOKMARK_CLICKED] =
315 g_signal_new ("bookmark-clicked",
316 G_OBJECT_CLASS_TYPE (klass),
317 G_SIGNAL_RUN_LAST,
318 G_STRUCT_OFFSET (YelpLocationEntryClass, bookmark_clicked),
319 NULL, NULL,
320 g_cclosure_marshal_VOID__VOID,
321 G_TYPE_NONE, 0);
324 * YelpLocationEntry:view
326 * The YelpView instance that this location entry controls.
328 g_object_class_install_property (object_class,
329 PROP_VIEW,
330 g_param_spec_object ("view",
331 _("View"),
332 _("A YelpView instance to control"),
333 YELP_TYPE_VIEW,
334 G_PARAM_CONSTRUCT_ONLY |
335 G_PARAM_READWRITE |
336 G_PARAM_STATIC_STRINGS));
339 * YelpLocationEntry:bookmarks
341 * An instance of an implementation of YelpBookmarks to provide
342 * bookmark information for this location entry.
344 g_object_class_install_property (object_class,
345 PROP_BOOKMARKS,
346 g_param_spec_object ("bookmarks",
347 _("Bookmarks"),
348 _("A YelpBookmarks implementation instance"),
349 YELP_TYPE_BOOKMARKS,
350 G_PARAM_CONSTRUCT_ONLY |
351 G_PARAM_READWRITE |
352 G_PARAM_STATIC_STRINGS));
355 * YelpLocationEntry:enable-search
357 * Whether the location entry can act as a search entry. If search is not
358 * enabled, the user will not be able to initiate a search by clicking in
359 * the embedded text entry or selecting a search row in the drop-down menu.
361 g_object_class_install_property (object_class,
362 PROP_ENABLE_SEARCH,
363 g_param_spec_boolean ("enable-search",
364 N_("Enable Search"),
365 N_("Whether the location entry can be used as a search field"),
366 TRUE,
367 G_PARAM_CONSTRUCT |
368 G_PARAM_READWRITE |
369 G_PARAM_STATIC_STRINGS));
371 g_type_class_add_private ((GObjectClass *) klass,
372 sizeof (YelpLocationEntryPrivate));
374 completions = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
377 static void
378 yelp_location_entry_init (YelpLocationEntry *entry)
380 YelpLocationEntryPrivate *priv = GET_PRIV (entry);
382 priv->search_mode = FALSE;
383 g_object_set (entry, "entry-text-column", HISTORY_COL_TITLE, NULL);
386 static void
387 location_entry_constructed (GObject *object)
389 GtkCellRenderer *bookmark_cell;
390 GList *cells;
391 GtkTreeIter iter;
392 YelpLocationEntryPrivate *priv = GET_PRIV (object);
394 /* Set up the text entry child */
395 priv->text_entry = gtk_bin_get_child (GTK_BIN (object));
396 gtk_entry_set_icon_from_icon_name (GTK_ENTRY (priv->text_entry),
397 GTK_ENTRY_ICON_PRIMARY,
398 "help-browser");
399 gtk_entry_set_icon_activatable (GTK_ENTRY (priv->text_entry),
400 GTK_ENTRY_ICON_PRIMARY,
401 FALSE);
402 gtk_entry_set_icon_activatable (GTK_ENTRY (priv->text_entry),
403 GTK_ENTRY_ICON_SECONDARY,
404 TRUE);
405 gtk_editable_set_editable (GTK_EDITABLE (priv->text_entry),
406 priv->enable_search);
407 if (!priv->enable_search) {
408 priv->search_mode = FALSE;
409 location_entry_set_entry ((YelpLocationEntry *) object, FALSE);
412 /* Set up the history model */
413 priv->history = gtk_list_store_new (8,
414 G_TYPE_STRING, /* title */
415 G_TYPE_STRING, /* desc */
416 G_TYPE_STRING, /* icon */
417 G_TYPE_STRING, /* uri */
418 G_TYPE_STRING, /* doc */
419 G_TYPE_STRING, /* page */
420 G_TYPE_INT, /* flags */
421 G_TYPE_STRING /* search terms */
423 g_signal_connect (priv->history, "row-changed",
424 G_CALLBACK (history_row_changed),
425 object);
426 g_object_set (object, "model", priv->history, NULL);
427 if (priv->enable_search) {
428 gtk_list_store_append (priv->history, &iter);
429 gtk_list_store_set (priv->history, &iter,
430 HISTORY_COL_FLAGS, LOCATION_ENTRY_IS_SEPARATOR,
431 -1);
432 gtk_list_store_append (priv->history, &iter);
433 gtk_list_store_set (priv->history, &iter,
434 HISTORY_COL_ICON, "edit-find-symbolic",
435 HISTORY_COL_TITLE, _("Search..."),
436 HISTORY_COL_FLAGS, LOCATION_ENTRY_IS_SEARCH,
437 -1);
440 /* Set up the history drop-down */
441 /* Trying to get the text to line up with the text in the GtkEntry.
442 * The text cell is the zeroeth cell right now, since we haven't
443 * yet called reorder. I realize using a guesstimate pixel value
444 * won't be perfect all the time, but 4 looks niceish.
446 cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (object));
447 g_object_set (cells->data, "xpad", 4, NULL);
448 gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (object),
449 GTK_CELL_RENDERER (cells->data),
450 (GtkCellLayoutDataFunc) cell_set_text_cell,
451 object, NULL);
452 g_object_set (cells->data, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
453 g_list_free (cells);
455 priv->icon_cell = gtk_cell_renderer_pixbuf_new ();
456 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (object), priv->icon_cell, FALSE);
457 g_object_set (priv->icon_cell, "yalign", 0.2, NULL);
458 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (object),
459 priv->icon_cell,
460 "icon-name",
461 HISTORY_COL_ICON,
462 NULL);
463 gtk_cell_layout_reorder (GTK_CELL_LAYOUT (object), priv->icon_cell, 0);
465 if (priv->bookmarks) {
466 bookmark_cell = gtk_cell_renderer_pixbuf_new ();
467 gtk_cell_layout_pack_end (GTK_CELL_LAYOUT (object), bookmark_cell, FALSE);
468 gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (object),
469 bookmark_cell,
470 (GtkCellLayoutDataFunc) cell_set_bookmark_icon,
471 object, NULL);
473 gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (object),
474 (GtkTreeViewRowSeparatorFunc)
475 combo_box_row_separator_func,
476 object, NULL);
477 /* Without this, you get a warning about the popup widget
478 * being NULL the firt time you click the arrow.
480 gtk_widget_show_all (GTK_WIDGET (object));
482 /* Connect signals */
483 g_signal_connect (object, "changed",
484 G_CALLBACK (combo_box_changed_cb), NULL);
486 g_signal_connect (priv->text_entry, "focus-in-event",
487 G_CALLBACK (entry_focus_in_cb), object);
488 g_signal_connect (priv->text_entry, "focus-out-event",
489 G_CALLBACK (entry_focus_out_cb), object);
490 g_signal_connect (priv->text_entry, "icon-press",
491 G_CALLBACK (entry_icon_press_cb), object);
492 g_signal_connect (priv->text_entry, "key-press-event",
493 G_CALLBACK (entry_key_press_cb), object);
494 g_signal_connect (priv->text_entry, "activate",
495 G_CALLBACK (entry_activate_cb), object);
497 if (priv->bookmarks)
498 priv->bookmarks_changed = g_signal_connect (priv->bookmarks,
499 "bookmarks-changed",
500 G_CALLBACK (bookmarks_changed),
501 object);
503 g_signal_connect (priv->view, "loaded", G_CALLBACK (view_loaded), object);
504 g_signal_connect (priv->view, "notify::yelp-uri", G_CALLBACK (view_uri_selected), object);
505 g_signal_connect (priv->view, "notify::page-title", G_CALLBACK (view_page_title), object);
506 g_signal_connect (priv->view, "notify::page-desc", G_CALLBACK (view_page_desc), object);
507 g_signal_connect (priv->view, "notify::page-icon", G_CALLBACK (view_page_icon), object);
510 static void
511 location_entry_dispose (GObject *object)
513 YelpLocationEntryPrivate *priv = GET_PRIV (object);
515 if (priv->view) {
516 g_object_unref (priv->view);
517 priv->view = NULL;
520 if (priv->bookmarks) {
521 g_object_unref (priv->bookmarks);
522 priv->bookmarks = NULL;
525 if (priv->bookmarks_changed) {
526 g_source_remove (priv->bookmarks_changed);
527 priv->bookmarks_changed = 0;
530 if (priv->row) {
531 gtk_tree_row_reference_free (priv->row);
532 priv->row = NULL;
535 if (priv->pulse > 0) {
536 g_source_remove (priv->pulse);
537 priv->pulse = 0;
540 G_OBJECT_CLASS (yelp_location_entry_parent_class)->dispose (object);
543 static void
544 location_entry_finalize (GObject *object)
546 YelpLocationEntryPrivate *priv = GET_PRIV (object);
548 g_free (priv->completion_uri);
550 G_OBJECT_CLASS (yelp_location_entry_parent_class)->finalize (object);
553 static void
554 location_entry_get_property (GObject *object,
555 guint prop_id,
556 GValue *value,
557 GParamSpec *pspec)
559 YelpLocationEntryPrivate *priv = GET_PRIV (object);
561 switch (prop_id) {
562 case PROP_VIEW:
563 g_value_set_object (value, priv->view);
564 break;
565 case PROP_BOOKMARKS:
566 g_value_set_object (value, priv->bookmarks);
567 break;
568 case PROP_ENABLE_SEARCH:
569 g_value_set_boolean (value, priv->enable_search);
570 break;
571 default:
572 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
573 break;
577 static void
578 location_entry_set_property (GObject *object,
579 guint prop_id,
580 const GValue *value,
581 GParamSpec *pspec)
583 YelpLocationEntryPrivate *priv = GET_PRIV (object);
585 switch (prop_id) {
586 case PROP_VIEW:
587 priv->view = g_value_dup_object (value);
588 break;
589 case PROP_BOOKMARKS:
590 priv->bookmarks = g_value_dup_object (value);
591 break;
592 case PROP_ENABLE_SEARCH:
593 priv->enable_search = g_value_get_boolean (value);
594 break;
595 default:
596 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
597 break;
601 static void
602 location_entry_location_selected (YelpLocationEntry *entry)
604 GtkTreeIter iter;
605 YelpLocationEntryPrivate *priv = GET_PRIV (entry);
607 if (priv->view_uri_selected)
608 return;
610 if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (entry), &iter)) {
611 gchar *uri;
612 gtk_tree_model_get (GTK_TREE_MODEL (priv->history), &iter,
613 HISTORY_COL_URI, &uri,
614 -1);
615 yelp_view_load (priv->view, uri);
616 g_free (uri);
620 static void
621 location_entry_search_activated (YelpLocationEntry *entry)
623 YelpUri *base, *uri;
624 YelpLocationEntryPrivate *priv = GET_PRIV (entry);
626 g_object_get (priv->view, "yelp-uri", &base, NULL);
627 if (base == NULL)
628 return;
629 uri = yelp_uri_new_search (base,
630 gtk_entry_get_text (GTK_ENTRY (priv->text_entry)));
631 g_object_unref (base);
632 yelp_view_load_uri (priv->view, uri);
633 gtk_widget_grab_focus (GTK_WIDGET (priv->view));
636 static void
637 location_entry_bookmark_clicked (YelpLocationEntry *entry)
639 YelpUri *uri;
640 gchar *doc_uri, *page_id;
641 YelpLocationEntryPrivate *priv = GET_PRIV (entry);
643 g_object_get (priv->view,
644 "yelp-uri", &uri,
645 "page-id", &page_id,
646 NULL);
647 doc_uri = yelp_uri_get_document_uri (uri);
648 if (priv->bookmarks && doc_uri && page_id) {
649 if (!yelp_bookmarks_is_bookmarked (priv->bookmarks, doc_uri, page_id)) {
650 gchar *icon, *title;
651 g_object_get (priv->view,
652 "page-icon", &icon,
653 "page-title", &title,
654 NULL);
655 yelp_bookmarks_add_bookmark (priv->bookmarks, doc_uri, page_id, icon, title);
656 g_free (icon);
657 g_free (title);
659 else {
660 yelp_bookmarks_remove_bookmark (priv->bookmarks, doc_uri, page_id);
663 g_free (doc_uri);
664 g_free (page_id);
665 g_object_unref (uri);
668 static void
669 location_entry_start_search (YelpLocationEntry *entry,
670 gboolean clear)
672 YelpLocationEntryPrivate *priv = GET_PRIV (entry);
674 if (!priv->enable_search)
675 return;
676 if (clear && !priv->search_mode) {
677 const gchar *icon = gtk_entry_get_icon_name (GTK_ENTRY (priv->text_entry),
678 GTK_ENTRY_ICON_PRIMARY);
679 if (!g_str_equal (icon, "folder-saved-search"))
680 gtk_entry_set_text (GTK_ENTRY (priv->text_entry), "");
682 priv->search_mode = TRUE;
683 location_entry_set_entry (entry, FALSE);
684 gtk_widget_grab_focus (priv->text_entry);
687 static void
688 location_entry_cancel_search (YelpLocationEntry *entry)
690 gboolean ret;
691 YelpLocationEntryPrivate *priv = GET_PRIV (entry);
692 GdkEventFocus *event = g_new0 (GdkEventFocus, 1);
693 priv->search_mode = FALSE;
694 location_entry_set_entry (entry, FALSE);
695 event->type = GDK_FOCUS_CHANGE;
696 event->window = gtk_widget_get_window (GTK_WIDGET (entry));
697 event->send_event = FALSE;
698 event->in = FALSE;
699 g_signal_emit_by_name (entry, "focus-out-event", event, &ret);
700 g_free (event);
701 /* Hack: This makes the popup disappear when you hit Esc. */
702 if (priv->completion) {
703 g_object_ref (priv->completion);
704 gtk_entry_set_completion (GTK_ENTRY (priv->text_entry), NULL);
705 gtk_entry_set_completion (GTK_ENTRY (priv->text_entry),
706 priv->completion);
707 g_object_unref (priv->completion);
709 gtk_editable_select_region (GTK_EDITABLE (priv->text_entry), 0, 0);
712 static void
713 location_entry_set_completion (YelpLocationEntry *entry,
714 GtkTreeModel *model)
716 YelpLocationEntryPrivate *priv = GET_PRIV (entry);
717 GList *cells;
718 GtkCellRenderer *icon_cell, *bookmark_cell;
720 priv->completion = gtk_entry_completion_new ();
721 gtk_entry_completion_set_minimum_key_length (priv->completion, 3);
722 gtk_entry_completion_set_model (priv->completion, model);
723 gtk_entry_completion_set_text_column (priv->completion, COMPLETION_COL_TITLE);
724 gtk_entry_completion_set_match_func (priv->completion,
725 (GtkEntryCompletionMatchFunc) entry_match_func,
726 entry, NULL);
727 g_signal_connect (priv->completion, "match-selected",
728 G_CALLBACK (entry_match_selected), entry);
730 cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (priv->completion));
731 g_object_set (cells->data, "xpad", 4, NULL);
732 gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (priv->completion),
733 GTK_CELL_RENDERER (cells->data),
734 (GtkCellLayoutDataFunc) cell_set_completion_text_cell,
735 entry, NULL);
736 g_object_set (cells->data, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
737 g_list_free (cells);
739 icon_cell = gtk_cell_renderer_pixbuf_new ();
740 g_object_set (priv->icon_cell, "yalign", 0.2, NULL);
741 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (priv->completion), icon_cell, FALSE);
742 gtk_cell_layout_reorder (GTK_CELL_LAYOUT (priv->completion), icon_cell, 0);
743 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (priv->completion),
744 icon_cell,
745 "icon-name",
746 COMPLETION_COL_ICON,
747 NULL);
748 if (priv->bookmarks) {
749 bookmark_cell = gtk_cell_renderer_pixbuf_new ();
750 gtk_cell_layout_pack_end (GTK_CELL_LAYOUT (priv->completion), bookmark_cell, FALSE);
751 gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (priv->completion),
752 bookmark_cell,
753 (GtkCellLayoutDataFunc) cell_set_completion_bookmark_icon,
754 entry, NULL);
756 gtk_entry_set_completion (GTK_ENTRY (priv->text_entry),
757 priv->completion);
760 static void
761 location_entry_set_entry (YelpLocationEntry *entry, gboolean emit)
763 YelpLocationEntryPrivate *priv = GET_PRIV (entry);
764 GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX (entry));
765 GtkTreePath *path = NULL;
766 GtkTreeIter iter;
767 gchar *icon_name;
769 if (priv->search_mode) {
770 gtk_entry_set_icon_from_icon_name (GTK_ENTRY (priv->text_entry),
771 GTK_ENTRY_ICON_PRIMARY,
772 "edit-find-symbolic");
773 gtk_entry_set_icon_from_icon_name (GTK_ENTRY (priv->text_entry),
774 GTK_ENTRY_ICON_SECONDARY,
775 "edit-clear-symbolic");
776 gtk_entry_set_icon_tooltip_text (GTK_ENTRY (priv->text_entry),
777 GTK_ENTRY_ICON_SECONDARY,
778 _("Clear the search text"));
779 priv->icon_is_clear = TRUE;
780 return;
782 else {
783 priv->icon_is_clear = FALSE;
787 if (priv->row)
788 path = gtk_tree_row_reference_get_path (priv->row);
790 if (path) {
791 gchar *text, *doc_uri, *page_id;
792 gint flags;
793 gtk_tree_model_get_iter (model, &iter, path);
794 gtk_tree_model_get (model, &iter,
795 HISTORY_COL_TITLE, &text,
796 HISTORY_COL_ICON, &icon_name,
797 HISTORY_COL_FLAGS, &flags,
798 HISTORY_COL_DOC, &doc_uri,
799 HISTORY_COL_PAGE, &page_id,
800 -1);
801 if (flags & LOCATION_ENTRY_IS_LOADING) {
802 /* Would be nice to have a "loading" icon. I was using image-loading,
803 * but it look out of place with symbolic page icons. Plus, using
804 * image-loading-symbolic shows a broken image, because GtkEntry
805 * doesn't correctly use symbolic icons as of GNOME 3.0.
807 gtk_entry_set_icon_from_icon_name (GTK_ENTRY (priv->text_entry),
808 GTK_ENTRY_ICON_PRIMARY,
809 "yelp-page-symbolic");
810 if (priv->pulse > 0)
811 g_source_remove (priv->pulse);
812 priv->pulse = g_timeout_add (80, location_entry_pulse, entry);
814 else {
815 gtk_entry_set_icon_from_icon_name (GTK_ENTRY (priv->text_entry),
816 GTK_ENTRY_ICON_PRIMARY,
817 icon_name);
819 if (priv->bookmarks && doc_uri && page_id) {
820 GdkPixbuf *pixbuf;
821 if (!yelp_bookmarks_is_bookmarked (priv->bookmarks, doc_uri, page_id)) {
822 gtk_entry_set_icon_from_icon_name (GTK_ENTRY (priv->text_entry),
823 GTK_ENTRY_ICON_SECONDARY,
824 "yelp-bookmark-add-symbolic");
825 gtk_entry_set_icon_tooltip_text (GTK_ENTRY (priv->text_entry),
826 GTK_ENTRY_ICON_SECONDARY,
827 _("Bookmark this page"));
829 else {
830 gtk_entry_set_icon_from_icon_name (GTK_ENTRY (priv->text_entry),
831 GTK_ENTRY_ICON_SECONDARY,
832 "yelp-bookmark-remove-symbolic");
833 gtk_entry_set_icon_tooltip_text (GTK_ENTRY (priv->text_entry),
834 GTK_ENTRY_ICON_SECONDARY,
835 _("Remove bookmark"));
838 else {
839 gtk_entry_set_icon_from_icon_name (GTK_ENTRY (priv->text_entry),
840 GTK_ENTRY_ICON_SECONDARY,
841 NULL);
843 g_free (doc_uri);
844 g_free (page_id);
845 gtk_entry_set_text (GTK_ENTRY (priv->text_entry), text);
846 if (emit)
847 g_signal_emit (entry, location_entry_signals[LOCATION_SELECTED], 0);
848 g_free (text);
849 gtk_tree_path_free (path);
851 else {
852 gtk_entry_set_icon_from_icon_name (GTK_ENTRY (priv->text_entry),
853 GTK_ENTRY_ICON_PRIMARY,
854 "help-browser");
855 gtk_entry_set_icon_from_icon_name (GTK_ENTRY (priv->text_entry),
856 GTK_ENTRY_ICON_SECONDARY,
857 NULL);
861 static gboolean
862 location_entry_pulse (gpointer data)
864 YelpLocationEntryPrivate *priv = GET_PRIV (data);
865 GtkTreeModel *model;
866 GtkTreePath *path;
867 GtkTreeIter iter;
868 gint flags;
870 model = gtk_tree_row_reference_get_model (priv->row);
871 path = gtk_tree_row_reference_get_path (priv->row);
872 if (path) {
873 gtk_tree_model_get_iter (model, &iter, path);
874 gtk_tree_model_get (model, &iter,
875 HISTORY_COL_FLAGS, &flags,
876 -1);
877 gtk_tree_path_free (path);
880 if (flags & LOCATION_ENTRY_IS_LOADING && !priv->search_mode) {
881 gtk_entry_progress_pulse (GTK_ENTRY (priv->text_entry));
883 else {
884 gtk_entry_set_progress_fraction (GTK_ENTRY (priv->text_entry), 0.0);
887 return flags & LOCATION_ENTRY_IS_LOADING;
890 static void
891 combo_box_changed_cb (GtkComboBox *widget,
892 gpointer user_data)
894 YelpLocationEntryPrivate *priv = GET_PRIV (widget);
895 GtkTreeIter iter;
896 GtkTreeModel *model;
898 model = gtk_combo_box_get_model (GTK_COMBO_BOX (widget));
900 if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (widget), &iter)) {
901 gint flags;
902 gtk_tree_model_get (model, &iter,
903 HISTORY_COL_FLAGS, &flags,
904 -1);
905 if (flags & LOCATION_ENTRY_IS_SEARCH) {
906 location_entry_start_search ((YelpLocationEntry *) widget, TRUE);
908 else {
909 GtkTreePath *cur, *path;
910 path = gtk_tree_model_get_path (model, &iter);
911 if (priv->row) {
912 cur = gtk_tree_row_reference_get_path (priv->row);
913 if (gtk_tree_path_compare (cur, path) == 0) {
914 gtk_tree_path_free (cur);
915 gtk_tree_path_free (path);
916 return;
918 gtk_tree_path_free (cur);
919 gtk_tree_row_reference_free (priv->row);
921 priv->row = gtk_tree_row_reference_new (model, path);
922 gtk_tree_path_free (path);
923 priv->search_mode = FALSE;
924 location_entry_set_entry ((YelpLocationEntry *) widget, TRUE);
929 static gboolean
930 combo_box_row_separator_func (GtkTreeModel *model,
931 GtkTreeIter *iter,
932 gpointer user_data)
934 YelpLocationEntryPrivate *priv = GET_PRIV (user_data);
935 gint flags;
936 gtk_tree_model_get (model, iter,
937 HISTORY_COL_FLAGS, &flags,
938 -1);
939 return (flags & LOCATION_ENTRY_IS_SEPARATOR);
942 static void
943 history_row_changed (GtkTreeModel *model,
944 GtkTreePath *path,
945 GtkTreeIter *iter,
946 gpointer user_data)
948 /* FIXME: Should we bother checking path/iter against priv->row?
949 It doesn't really make a difference, since set_entry is pretty much
950 safe to call whenever. Does it make a performance impact?
952 location_entry_set_entry (YELP_LOCATION_ENTRY (user_data), FALSE);
955 static gboolean
956 entry_focus_in_cb (GtkWidget *widget,
957 GdkEventFocus *event,
958 gpointer user_data)
960 GtkEntry *text_entry = GTK_ENTRY (widget);
961 YelpLocationEntryPrivate *priv = GET_PRIV (user_data);
963 if (priv->enable_search && !priv->search_mode)
964 location_entry_start_search ((YelpLocationEntry *) user_data, TRUE);
966 return FALSE;
969 static gboolean
970 entry_focus_out_cb (GtkWidget *widget,
971 GdkEventFocus *event,
972 gpointer user_data)
974 YelpLocationEntryPrivate *priv = GET_PRIV (user_data);
976 if (gtk_entry_get_text_length (GTK_ENTRY (widget)) == 0) {
977 priv->search_mode = FALSE;
978 location_entry_set_entry ((YelpLocationEntry *) user_data, FALSE);
981 return FALSE;
984 static void
985 entry_activate_cb (GtkEntry *text_entry,
986 gpointer user_data)
988 YelpLocationEntryPrivate *priv = GET_PRIV (user_data);
989 gchar *text = g_strdup (gtk_entry_get_text (text_entry));
991 if (!priv->enable_search)
992 return;
994 if (!priv->search_mode || text == NULL || strlen(text) == 0)
995 return;
997 g_signal_emit (user_data, location_entry_signals[SEARCH_ACTIVATED], 0, text);
999 g_free (text);
1002 static void
1003 entry_icon_press_cb (GtkEntry *gtkentry,
1004 GtkEntryIconPosition icon_pos,
1005 GdkEvent *event,
1006 YelpLocationEntry *entry)
1008 YelpLocationEntryPrivate *priv = GET_PRIV (entry);
1010 if (icon_pos == GTK_ENTRY_ICON_SECONDARY) {
1011 if (priv->icon_is_clear)
1012 location_entry_cancel_search (entry);
1013 else
1014 g_signal_emit (entry, location_entry_signals[BOOKMARK_CLICKED], 0);
1018 static gboolean
1019 entry_key_press_cb (GtkWidget *widget,
1020 GdkEventKey *event,
1021 YelpLocationEntry *entry)
1023 YelpLocationEntryPrivate *priv = GET_PRIV (entry);
1024 if (event->keyval == GDK_KEY_Escape) {
1025 location_entry_cancel_search (entry);
1026 return TRUE;
1028 else if (!priv->search_mode) {
1029 location_entry_start_search (entry, FALSE);
1032 return FALSE;
1035 static void
1036 cell_set_text_cell (GtkCellLayout *layout,
1037 GtkCellRenderer *cell,
1038 GtkTreeModel *model,
1039 GtkTreeIter *iter,
1040 YelpLocationEntry *entry)
1042 gchar *title, *desc, *color, *text;
1043 YelpLocationEntryPrivate *priv = GET_PRIV (entry);
1045 gtk_tree_model_get (model, iter,
1046 HISTORY_COL_TITLE, &title,
1047 HISTORY_COL_DESC, &desc,
1048 -1);
1049 if (desc) {
1050 color = yelp_settings_get_color (yelp_settings_get_default (),
1051 YELP_SETTINGS_COLOR_TEXT_LIGHT);
1052 text = g_markup_printf_escaped ("<span size='larger'>%s</span>\n<span color='%s'>%s</span>",
1053 title, color, desc);
1054 g_free (color);
1055 g_free (desc);
1057 else {
1058 text = g_markup_printf_escaped ("<span size='larger'>%s</span>", title);
1061 g_object_set (cell, "markup", text, NULL);
1062 g_free (text);
1063 g_free (title);
1066 static void
1067 cell_set_bookmark_icon (GtkCellLayout *layout,
1068 GtkCellRenderer *cell,
1069 GtkTreeModel *model,
1070 GtkTreeIter *iter,
1071 YelpLocationEntry *entry)
1073 gint flags;
1074 gchar *doc_uri, *page_id;
1075 YelpLocationEntryPrivate *priv = GET_PRIV (entry);
1077 if (priv->bookmarks == NULL) {
1078 g_object_set (cell, "icon-name", NULL, NULL);
1079 return;
1082 gtk_tree_model_get (model, iter, HISTORY_COL_FLAGS, &flags, -1);
1083 if (flags & (LOCATION_ENTRY_IS_SEPARATOR | LOCATION_ENTRY_IS_SEARCH)) {
1084 g_object_set (cell, "icon-name", NULL, NULL);
1085 return;
1088 gtk_tree_model_get (model, iter,
1089 HISTORY_COL_DOC, &doc_uri,
1090 HISTORY_COL_PAGE, &page_id,
1091 -1);
1092 if (doc_uri && page_id &&
1093 yelp_bookmarks_is_bookmarked (priv->bookmarks, doc_uri, page_id))
1094 g_object_set (cell, "icon-name", "yelp-bookmark-remove-symbolic", NULL);
1095 else
1096 g_object_set (cell, "icon-name", NULL, NULL);
1098 g_free (doc_uri);
1099 g_free (page_id);
1102 static void
1103 cell_set_completion_bookmark_icon (GtkCellLayout *layout,
1104 GtkCellRenderer *cell,
1105 GtkTreeModel *model,
1106 GtkTreeIter *iter,
1107 YelpLocationEntry *entry)
1109 YelpLocationEntryPrivate *priv = GET_PRIV (entry);
1111 if (priv->completion_uri) {
1112 gchar *page_id = NULL;
1113 gtk_tree_model_get (model, iter,
1114 COMPLETION_COL_PAGE, &page_id,
1115 -1);
1117 if (page_id && yelp_bookmarks_is_bookmarked (priv->bookmarks,
1118 priv->completion_uri, page_id))
1119 g_object_set (cell, "icon-name", "yelp-bookmark-remove-symbolic", NULL);
1120 else
1121 g_object_set (cell, "icon-name", NULL, NULL);
1123 g_free (page_id);
1127 static void
1128 cell_set_completion_text_cell (GtkCellLayout *layout,
1129 GtkCellRenderer *cell,
1130 GtkTreeModel *model,
1131 GtkTreeIter *iter,
1132 YelpLocationEntry *entry)
1134 gchar *title, *desc, *color, *text;
1135 YelpLocationEntryPrivate *priv = GET_PRIV (entry);
1137 gtk_tree_model_get (model, iter,
1138 COMPLETION_COL_TITLE, &title,
1139 COMPLETION_COL_DESC, &desc,
1140 -1);
1141 if (desc) {
1142 color = yelp_settings_get_color (yelp_settings_get_default (),
1143 YELP_SETTINGS_COLOR_TEXT_LIGHT);
1144 text = g_markup_printf_escaped ("<span size='larger'>%s</span>\n<span color='%s'>%s</span>",
1145 title, color, desc);
1146 g_free (color);
1147 g_free (desc);
1149 else {
1150 text = g_markup_printf_escaped ("<span size='larger'>%s</span>", title);
1153 g_object_set (cell, "markup", text, NULL);
1154 g_free (text);
1155 g_free (title);
1158 static gboolean
1159 entry_match_func (GtkEntryCompletion *completion,
1160 const gchar *key,
1161 GtkTreeIter *iter,
1162 YelpLocationEntry *entry)
1164 gint stri;
1165 gchar *title, *desc, *titlecase = NULL, *desccase = NULL;
1166 gboolean ret = FALSE;
1167 gchar **strs;
1168 GtkTreeModel *model = gtk_entry_completion_get_model (completion);
1169 YelpLocationEntryPrivate *priv = GET_PRIV (entry);
1170 static GRegex *nonword = NULL;
1172 if (nonword == NULL)
1173 nonword = g_regex_new ("\\W", 0, 0, NULL);
1174 if (nonword == NULL)
1175 return FALSE;
1177 gtk_tree_model_get (model, iter,
1178 HISTORY_COL_TITLE, &title,
1179 HISTORY_COL_DESC, &desc,
1180 -1);
1181 if (title) {
1182 titlecase = g_utf8_casefold (title, -1);
1183 g_free (title);
1185 if (desc) {
1186 desccase = g_utf8_casefold (desc, -1);
1187 g_free (desc);
1190 strs = g_regex_split (nonword, key, 0);
1191 ret = TRUE;
1192 for (stri = 0; strs[stri]; stri++) {
1193 if (!titlecase || !strstr (titlecase, strs[stri])) {
1194 if (!desccase || !strstr (desccase, strs[stri])) {
1195 ret = FALSE;
1196 break;
1201 g_free (titlecase);
1202 g_free (desccase);
1203 g_strfreev (strs);
1205 return ret;
1208 static gint
1209 entry_completion_sort (GtkTreeModel *model,
1210 GtkTreeIter *iter1,
1211 GtkTreeIter *iter2,
1212 gpointer user_data)
1214 gint ret = 0;
1215 gchar *key1, *key2;
1217 gtk_tree_model_get (model, iter1, COMPLETION_COL_ICON, &key1, -1);
1218 gtk_tree_model_get (model, iter2, COMPLETION_COL_ICON, &key2, -1);
1219 ret = yelp_settings_cmp_icons (key1, key2);
1220 g_free (key1);
1221 g_free (key2);
1223 if (ret)
1224 return ret;
1226 gtk_tree_model_get (model, iter1, COMPLETION_COL_TITLE, &key1, -1);
1227 gtk_tree_model_get (model, iter2, COMPLETION_COL_TITLE, &key2, -1);
1228 if (key1 && key2)
1229 ret = g_utf8_collate (key1, key2);
1230 else if (key2 == NULL)
1231 return -1;
1232 else if (key1 == NULL)
1233 return 1;
1234 g_free (key1);
1235 g_free (key2);
1237 return ret;
1240 static gboolean
1241 entry_match_selected (GtkEntryCompletion *completion,
1242 GtkTreeModel *model,
1243 GtkTreeIter *iter,
1244 YelpLocationEntry *entry)
1246 YelpUri *base, *uri;
1247 gchar *page, *xref;
1248 YelpLocationEntryPrivate *priv = GET_PRIV (entry);
1250 g_object_get (priv->view, "yelp-uri", &base, NULL);
1251 gtk_tree_model_get (model, iter, COMPLETION_COL_PAGE, &page, -1);
1253 xref = g_strconcat ("xref:", page, NULL);
1254 uri = yelp_uri_new_relative (base, xref);
1256 yelp_view_load_uri (priv->view, uri);
1258 g_free (page);
1259 g_free (xref);
1260 g_object_unref (uri);
1261 g_object_unref (base);
1263 gtk_widget_grab_focus (GTK_WIDGET (priv->view));
1264 return TRUE;
1267 static void
1268 view_loaded (YelpView *view,
1269 YelpLocationEntry *entry)
1271 gchar **ids;
1272 gint i;
1273 GtkTreeIter iter;
1274 gint flags;
1275 YelpUri *uri;
1276 gchar *doc_uri, *page_id;
1277 GtkTreeModel *completion;
1278 YelpLocationEntryPrivate *priv = GET_PRIV (entry);
1279 YelpDocument *document = yelp_view_get_document (view);
1281 gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->history), &iter);
1282 gtk_tree_model_get (GTK_TREE_MODEL (priv->history), &iter,
1283 HISTORY_COL_FLAGS, &flags,
1284 -1);
1285 if (flags & LOCATION_ENTRY_IS_LOADING) {
1286 flags = flags & ~LOCATION_ENTRY_IS_LOADING;
1287 gtk_list_store_set (priv->history, &iter, HISTORY_COL_FLAGS, flags, -1);
1290 g_object_get (view,
1291 "yelp-uri", &uri,
1292 "page-id", &page_id,
1293 NULL);
1294 doc_uri = yelp_uri_get_document_uri (uri);
1295 gtk_list_store_set (priv->history, &iter,
1296 HISTORY_COL_DOC, doc_uri,
1297 HISTORY_COL_PAGE, page_id,
1298 -1);
1299 g_free (page_id);
1301 if ((priv->completion_uri == NULL) ||
1302 !g_str_equal (doc_uri, priv->completion_uri)) {
1303 completion = (GtkTreeModel *) g_hash_table_lookup (completions, doc_uri);
1304 if (completion == NULL) {
1305 GtkListStore *base = gtk_list_store_new (5,
1306 G_TYPE_STRING, /* title */
1307 G_TYPE_STRING, /* desc */
1308 G_TYPE_STRING, /* icon */
1309 G_TYPE_STRING, /* uri */
1310 G_TYPE_INT /* flags */
1312 completion = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (base));
1313 gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (completion),
1314 entry_completion_sort,
1315 NULL, NULL);
1316 g_hash_table_insert (completions, g_strdup (doc_uri), completion);
1317 if (document != NULL) {
1318 ids = yelp_document_list_page_ids (document);
1319 for (i = 0; ids[i]; i++) {
1320 GtkTreeIter iter;
1321 gchar *title, *desc, *icon;
1322 gtk_list_store_insert (GTK_LIST_STORE (base), &iter, 0);
1323 title = yelp_document_get_page_title (document, ids[i]);
1324 desc = yelp_document_get_page_desc (document, ids[i]);
1325 icon = yelp_document_get_page_icon (document, ids[i]);
1326 gtk_list_store_set (base, &iter,
1327 COMPLETION_COL_TITLE, title,
1328 COMPLETION_COL_DESC, desc,
1329 COMPLETION_COL_ICON, icon,
1330 COMPLETION_COL_PAGE, ids[i],
1331 -1);
1332 g_free (icon);
1333 g_free (desc);
1334 g_free (title);
1336 g_strfreev (ids);
1338 g_object_unref (base);
1340 g_free (priv->completion_uri);
1341 priv->completion_uri = doc_uri;
1342 location_entry_set_completion (entry, completion);
1345 g_object_unref (uri);
1348 static void
1349 view_uri_selected (YelpView *view,
1350 GParamSpec *pspec,
1351 YelpLocationEntry *entry)
1353 GtkTreeIter iter;
1354 gchar *iter_uri;
1355 gboolean cont;
1356 YelpUri *uri;
1357 gchar *struri, *doc_uri;
1358 YelpLocationEntryPrivate *priv = GET_PRIV (entry);
1360 g_object_get (G_OBJECT (view), "yelp-uri", &uri, NULL);
1361 if (uri == NULL)
1362 return;
1364 struri = yelp_uri_get_canonical_uri (uri);
1366 cont = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->history), &iter);
1367 while (cont) {
1368 gtk_tree_model_get (GTK_TREE_MODEL (priv->history), &iter,
1369 HISTORY_COL_URI, &iter_uri,
1370 -1);
1371 if (iter_uri && g_str_equal (struri, iter_uri)) {
1372 g_free (iter_uri);
1373 break;
1375 else {
1376 g_free (iter_uri);
1377 cont = gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->history), &iter);
1380 if (cont) {
1381 GtkTreeIter first;
1382 gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->history), &first);
1383 gtk_list_store_move_before (priv->history, &iter, &first);
1385 else {
1386 gint num;
1387 GtkTreeIter last;
1388 gtk_list_store_prepend (priv->history, &iter);
1389 gtk_list_store_set (priv->history, &iter,
1390 HISTORY_COL_TITLE, _("Loading"),
1391 HISTORY_COL_ICON, "help-contents",
1392 HISTORY_COL_URI, struri,
1393 HISTORY_COL_FLAGS, LOCATION_ENTRY_IS_LOADING,
1394 -1);
1395 /* Limit to MAX_HISTORY entries. There are two extra for the search entry
1396 * and the separator above it.
1398 num = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (priv->history), NULL);
1399 if (num > MAX_HISTORY + 2) {
1400 if (gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (priv->history),
1401 &last, NULL,
1402 num - 3)) {
1403 gtk_list_store_remove (priv->history, &last);
1407 priv->view_uri_selected = TRUE;
1408 gtk_combo_box_set_active_iter (GTK_COMBO_BOX (entry), &iter);
1409 priv->view_uri_selected = FALSE;
1411 g_free (struri);
1412 g_object_unref (uri);
1415 static void
1416 view_page_title (YelpView *view,
1417 GParamSpec *pspec,
1418 YelpLocationEntry *entry)
1420 GtkTreeIter first;
1421 YelpLocationEntryPrivate *priv = GET_PRIV (entry);
1423 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->history), &first)) {
1424 gchar *title, *frag = NULL;
1425 YelpViewState state;
1426 YelpUri *uri;
1428 g_object_get (view,
1429 "page-title", &title,
1430 "state", &state,
1431 NULL);
1432 if (title == NULL)
1433 return;
1435 if (state != YELP_VIEW_STATE_ERROR) {
1436 YelpUri *uri;
1437 g_object_get (view, "yelp-uri", &uri, NULL);
1438 frag = yelp_uri_get_frag_id (uri);
1439 g_object_unref (uri);
1442 if (frag) {
1443 gchar *tmp = g_strdup_printf ("%s (#%s)", title, frag);
1444 gtk_list_store_set (priv->history, &first,
1445 HISTORY_COL_TITLE, tmp,
1446 -1);
1447 g_free (tmp);
1448 g_free (frag);
1450 else {
1451 gtk_list_store_set (priv->history, &first,
1452 HISTORY_COL_TITLE, title,
1453 -1);
1455 g_free (title);
1459 static void
1460 view_page_desc (YelpView *view,
1461 GParamSpec *pspec,
1462 YelpLocationEntry *entry)
1464 GtkTreeIter first;
1465 gchar *desc;
1466 YelpLocationEntryPrivate *priv = GET_PRIV (entry);
1468 g_object_get (view, "page-desc", &desc, NULL);
1469 if (desc == NULL)
1470 return;
1472 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->history), &first))
1473 gtk_list_store_set (priv->history, &first,
1474 HISTORY_COL_DESC, desc,
1475 -1);
1476 g_free (desc);
1479 static void
1480 view_page_icon (YelpView *view,
1481 GParamSpec *pspec,
1482 YelpLocationEntry *entry)
1484 GtkTreeIter first;
1485 gchar *icon;
1486 YelpLocationEntryPrivate *priv = GET_PRIV (entry);
1488 g_object_get (view, "page-icon", &icon, NULL);
1489 if (icon == NULL)
1490 return;
1492 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->history), &first))
1493 gtk_list_store_set (priv->history, &first,
1494 HISTORY_COL_ICON, icon,
1495 -1);
1496 g_free (icon);
1499 static void
1500 bookmarks_changed (YelpBookmarks *bookmarks,
1501 const gchar *doc_uri,
1502 YelpLocationEntry *entry)
1504 GtkTreePath *path;
1505 YelpLocationEntryPrivate *priv = GET_PRIV (entry);
1507 if (priv->row)
1508 path = gtk_tree_row_reference_get_path (priv->row);
1510 if (path) {
1511 GtkTreeIter iter;
1512 gchar *this_uri, *page_id;
1513 gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->history), &iter, path);
1514 gtk_tree_model_get (GTK_TREE_MODEL (priv->history), &iter,
1515 HISTORY_COL_DOC, &this_uri,
1516 HISTORY_COL_PAGE, &page_id,
1517 -1);
1518 if (this_uri && g_str_equal (this_uri, doc_uri) && page_id) {
1519 if (!yelp_bookmarks_is_bookmarked (priv->bookmarks, doc_uri, page_id)) {
1520 gtk_entry_set_icon_from_icon_name (GTK_ENTRY (priv->text_entry),
1521 GTK_ENTRY_ICON_SECONDARY,
1522 "yelp-bookmark-add-symbolic");
1523 gtk_entry_set_icon_tooltip_text (GTK_ENTRY (priv->text_entry),
1524 GTK_ENTRY_ICON_SECONDARY,
1525 _("Bookmark this page"));
1527 else {
1528 gtk_entry_set_icon_from_icon_name (GTK_ENTRY (priv->text_entry),
1529 GTK_ENTRY_ICON_SECONDARY,
1530 "yelp-bookmark-remove-symbolic");
1531 gtk_entry_set_icon_tooltip_text (GTK_ENTRY (priv->text_entry),
1532 GTK_ENTRY_ICON_SECONDARY,
1533 _("Remove bookmark"));
1536 else {
1537 gtk_entry_set_icon_from_icon_name (GTK_ENTRY (priv->text_entry),
1538 GTK_ENTRY_ICON_SECONDARY,
1539 NULL);
1541 g_free (this_uri);
1542 g_free (page_id);
1547 * yelp_location_entry_new:
1548 * @view: A #YelpView.
1550 * Creates a new #YelpLocationEntry widget to control @view.
1552 * Returns: A new #YelpLocationEntry.
1554 GtkWidget*
1555 yelp_location_entry_new (YelpView *view,
1556 YelpBookmarks *bookmarks)
1558 GtkWidget *ret;
1559 g_return_val_if_fail (YELP_IS_VIEW (view), NULL);
1561 ret = GTK_WIDGET (g_object_new (YELP_TYPE_LOCATION_ENTRY,
1562 "has-entry", TRUE,
1563 "view", view,
1564 "bookmarks", bookmarks,
1565 NULL));
1567 return ret;
1571 * yelp_location_entry_start_search:
1572 * @entry: A #YelpLocationEntry.
1574 * Puts @entry into search mode. This focuses the entry and clears its text
1575 * contents. When the user activates the search, the
1576 * #YelpLocationEntry::search-activated signal will be emitted.
1578 void
1579 yelp_location_entry_start_search (YelpLocationEntry *entry)
1581 location_entry_start_search (entry, TRUE);