/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * Copyright (C) 2010 Shaun McCance * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. * * Author: Shaun McCance */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "yelp-location-entry.h" #include "yelp-settings.h" #include "yelp-uri.h" #include "yelp-view.h" #include "yelp-application.h" #include "yelp-window.h" #define YELP_ZEITGEIST_PAGE "http://www.gnome.org/yelp/ns#HelpPage" static void yelp_window_init (YelpWindow *window); static void yelp_window_class_init (YelpWindowClass *klass); static void yelp_window_dispose (GObject *object); static void yelp_window_finalize (GObject *object); static void yelp_window_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void yelp_window_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void window_construct (YelpWindow *window); static void window_new (GtkAction *action, YelpWindow *window); static gboolean window_map_event (YelpWindow *window, GdkEvent *event, gpointer user_data); static gboolean window_configure_event (YelpWindow *window, GdkEventConfigure *event, gpointer user_data); static void window_drag_received (YelpWindow *window, GdkDragContext *context, gint x, gint y, GtkSelectionData *data, guint info, guint time, gpointer userdata); static gboolean window_resize_signal (YelpWindow *window); static void window_close (GtkAction *action, YelpWindow *window); static void window_go_all (GtkAction *action, YelpWindow *window); static void window_add_bookmark (GtkAction *action, YelpWindow *window); static void window_remove_bookmark (GtkAction *action, YelpWindow *window); static void window_load_bookmark (GtkAction *action, YelpWindow *window); static void window_find_in_page (GtkAction *action, YelpWindow *window); static void window_start_search (GtkAction *action, YelpWindow *window); static void window_open_location (GtkAction *action, YelpWindow *window); static void window_read_later (GtkAction *action, YelpWindow *window); static gboolean read_later_clicked (GtkLinkButton *button, YelpWindow *window); static void app_read_later_changed (YelpApplication *app, const gchar *doc_uri, YelpWindow *window); static void app_bookmarks_changed (YelpApplication *app, const gchar *doc_uri, YelpWindow *window); static void window_set_bookmarks (YelpWindow *window, const gchar *doc_uri); static void window_set_bookmark_action (YelpWindow *window); static gboolean find_entry_focus_out (GtkEntry *entry, GdkEventFocus *event, YelpWindow *window); static void window_update_zeitgeist (YelpWindow *window, YelpUri *uri, const gchar *title); static gboolean find_entry_key_press (GtkEntry *entry, GdkEventKey *event, YelpWindow *window); static void find_entry_changed (GtkEntry *entry, YelpWindow *window); static gboolean entry_focus_in (GtkEntry *entry, GdkEventFocus *event, YelpWindow *window); static gboolean entry_focus_out (YelpLocationEntry *entry, GdkEventFocus *event, YelpWindow *window); static void view_new_window (YelpView *view, YelpUri *uri, YelpWindow *window); static void view_loaded (YelpView *view, YelpWindow *window); static void view_uri_selected (YelpView *view, GParamSpec *pspec, YelpWindow *window); static void view_root_title (YelpView *view, GParamSpec *pspec, YelpWindow *window); static gboolean view_is_xref_uri (YelpView *view, GtkAction *action, const gchar *uri, YelpWindow *window); static void hidden_entry_activate (GtkEntry *entry, YelpWindow *window); static void hidden_entry_hide (YelpWindow *window); static gboolean hidden_key_press (GtkWidget *widget, GdkEventKey *event, YelpWindow *window); enum { PROP_0, PROP_APPLICATION }; enum { RESIZE_EVENT, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; G_DEFINE_TYPE (YelpWindow, yelp_window, GTK_TYPE_WINDOW); #define GET_PRIV(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), YELP_TYPE_WINDOW, YelpWindowPrivate)) static const gchar *YELP_UI = "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""; typedef struct _YelpWindowPrivate YelpWindowPrivate; struct _YelpWindowPrivate { GtkListStore *history; GtkUIManager *ui_manager; GtkActionGroup *action_group; YelpApplication *application; gulong bookmarks_changed; gulong read_later_changed; /* no refs on these, owned by containers */ YelpView *view; GtkWidget *vbox_view; GtkWidget *vbox_full; GtkWidget *hbox; YelpLocationEntry *entry; GtkWidget *hidden_entry; GtkWidget *find_entry; GtkWidget *find_label; GtkWidget *read_later_vbox; /* refs because we dynamically add & remove */ GtkWidget *find_bar; GtkWidget *align_location; GtkWidget *align_hidden; GtkWidget *read_later; gchar *doc_uri; GtkActionGroup *bookmark_actions; guint bookmarks_merge_id; guint resize_signal; gint width; gint height; guint entry_color_animate; gfloat entry_color_step; gboolean configured; ZeitgeistLog *zeitgeist; }; static const GtkActionEntry entries[] = { { "PageMenu", NULL, N_("_Page") }, { "ViewMenu", NULL, N_("_View") }, { "GoMenu", NULL, N_("_Go") }, { "BookmarksMenu", NULL, N_("_Bookmarks") }, { "NewWindow", NULL, N_("_New Window"), "N", NULL, G_CALLBACK (window_new) }, { "CloseWindow", NULL, N_("_Close"), "W", NULL, G_CALLBACK (window_close) }, { "GoAll", NULL, N_("_All Documents"), NULL, NULL, G_CALLBACK (window_go_all) }, { "AddBookmark", NULL, N_("_Add Bookmark"), "D", NULL, G_CALLBACK (window_add_bookmark) }, { "RemoveBookmark", NULL, N_("_Remove Bookmark"), NULL, NULL, G_CALLBACK (window_remove_bookmark) }, { "Find", NULL, N_("Find in Page..."), "F", NULL, G_CALLBACK (window_find_in_page) }, { "Search", NULL, N_("Search..."), "S", NULL, G_CALLBACK (window_start_search) }, { "OpenLocation", NULL, N_("Open Location"), "L", NULL, G_CALLBACK (window_open_location) } }; static void yelp_window_init (YelpWindow *window) { YelpWindowPrivate *priv = GET_PRIV (window); g_signal_connect (window, "configure-event", G_CALLBACK (window_configure_event), NULL); g_signal_connect (window, "map-event", G_CALLBACK (window_map_event), NULL); priv->zeitgeist = zeitgeist_log_new (); } static void yelp_window_class_init (YelpWindowClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = yelp_window_dispose; object_class->finalize = yelp_window_finalize; object_class->get_property = yelp_window_get_property; object_class->set_property = yelp_window_set_property; g_object_class_install_property (object_class, PROP_APPLICATION, g_param_spec_object ("application", _("Application"), _("A YelpApplication instance that controls this window"), YELP_TYPE_APPLICATION, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); signals[RESIZE_EVENT] = g_signal_new ("resized", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); g_type_class_add_private (klass, sizeof (YelpWindowPrivate)); } static void yelp_window_dispose (GObject *object) { YelpWindowPrivate *priv = GET_PRIV (object); if (priv->history) { g_object_unref (priv->history); priv->history = NULL; } if (priv->action_group) { g_object_unref (priv->action_group); priv->action_group = NULL; } if (priv->bookmark_actions) { g_object_unref (priv->bookmark_actions); priv->bookmark_actions = NULL; } if (priv->read_later_changed) { g_source_remove (priv->read_later_changed); priv->read_later_changed = 0; } if (priv->bookmarks_changed) { g_source_remove (priv->bookmarks_changed); priv->bookmarks_changed = 0; } if (priv->align_location) { g_object_unref (priv->align_location); priv->align_location = NULL; } if (priv->align_hidden) { g_object_unref (priv->align_hidden); priv->align_hidden = NULL; } if (priv->find_bar) { g_object_unref (priv->find_bar); priv->find_bar = NULL; } if (priv->entry_color_animate != 0) { g_source_remove (priv->entry_color_animate); priv->entry_color_animate = 0; } if (priv->zeitgeist) { g_object_unref (priv->zeitgeist); priv->zeitgeist = NULL; } G_OBJECT_CLASS (yelp_window_parent_class)->dispose (object); } static void yelp_window_finalize (GObject *object) { YelpWindowPrivate *priv = GET_PRIV (object); g_free (priv->doc_uri); G_OBJECT_CLASS (yelp_window_parent_class)->finalize (object); } static void yelp_window_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { YelpWindowPrivate *priv = GET_PRIV (object); switch (prop_id) { case PROP_APPLICATION: g_value_set_object (value, priv->application); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void yelp_window_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { YelpWindowPrivate *priv = GET_PRIV (object); switch (prop_id) { case PROP_APPLICATION: priv->application = g_value_get_object (value); window_construct ((YelpWindow *) object); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void window_construct (YelpWindow *window) { GtkWidget *scroll; GtkActionGroup *view_actions; GtkAction *action; GtkWidget *vbox, *button, *label; gchar *color, *text; YelpWindowPrivate *priv = GET_PRIV (window); gtk_window_set_icon_name (GTK_WINDOW (window), "help-browser"); priv->view = (YelpView *) yelp_view_new (); action = gtk_action_new ("ReadLinkLater", _("Read Link _Later"), NULL, NULL); g_signal_connect (action, "activate", G_CALLBACK (window_read_later), window); yelp_view_add_link_action (priv->view, action, (YelpViewActionValidFunc) view_is_xref_uri, window); priv->read_later_changed = g_signal_connect (priv->application, "read-later-changed", G_CALLBACK (app_read_later_changed), window); priv->vbox_full = gtk_vbox_new (FALSE, 3); gtk_container_add (GTK_CONTAINER (window), priv->vbox_full); priv->vbox_view = gtk_vbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (priv->vbox_full), priv->vbox_view, TRUE, TRUE, 0); priv->action_group = gtk_action_group_new ("YelpWindowActions"); gtk_action_group_set_translation_domain (priv->action_group, GETTEXT_PACKAGE); gtk_action_group_add_actions (priv->action_group, entries, G_N_ELEMENTS (entries), window); priv->bookmark_actions = gtk_action_group_new ("BookmarkActions"); gtk_action_group_set_translate_func (priv->bookmark_actions, NULL, NULL, NULL); priv->ui_manager = gtk_ui_manager_new (); gtk_ui_manager_insert_action_group (priv->ui_manager, priv->action_group, 0); gtk_ui_manager_insert_action_group (priv->ui_manager, priv->bookmark_actions, 1); gtk_ui_manager_insert_action_group (priv->ui_manager, yelp_application_get_action_group (priv->application), 2); view_actions = yelp_view_get_action_group (priv->view); gtk_ui_manager_insert_action_group (priv->ui_manager, view_actions, 3); gtk_window_add_accel_group (GTK_WINDOW (window), gtk_ui_manager_get_accel_group (priv->ui_manager)); gtk_ui_manager_add_ui_from_string (priv->ui_manager, YELP_UI, -1, NULL); gtk_box_pack_start (GTK_BOX (priv->vbox_view), gtk_ui_manager_get_widget (priv->ui_manager, "/ui/menubar"), FALSE, FALSE, 0); priv->bookmarks_merge_id = gtk_ui_manager_new_merge_id (priv->ui_manager); priv->bookmarks_changed = g_signal_connect (priv->application, "bookmarks-changed", G_CALLBACK (app_bookmarks_changed), window); priv->hbox = gtk_hbox_new (FALSE, 0); g_object_set (priv->hbox, "border-width", 2, NULL); gtk_box_pack_start (GTK_BOX (priv->vbox_view), priv->hbox, FALSE, FALSE, 0); action = gtk_action_group_get_action (view_actions, "YelpViewGoBack"); button = gtk_action_create_tool_item (action); gtk_box_pack_start (GTK_BOX (priv->hbox), button, FALSE, FALSE, 0); action = gtk_action_group_get_action (view_actions, "YelpViewGoForward"); button = gtk_action_create_tool_item (action); gtk_box_pack_start (GTK_BOX (priv->hbox), button, FALSE, FALSE, 0); priv->entry = (YelpLocationEntry *) yelp_location_entry_new (priv->view, YELP_BOOKMARKS (priv->application)); g_signal_connect (gtk_bin_get_child (GTK_BIN (priv->entry)), "focus-in-event", G_CALLBACK (entry_focus_in), window); g_signal_connect (priv->entry, "focus-out-event", G_CALLBACK (entry_focus_out), window); priv->align_location = g_object_ref_sink (gtk_alignment_new (0.0, 0.5, 1.0, 0.0)); gtk_box_pack_start (GTK_BOX (priv->hbox), GTK_WIDGET (priv->align_location), TRUE, TRUE, 0); gtk_container_add (GTK_CONTAINER (priv->align_location), GTK_WIDGET (priv->entry)); priv->hidden_entry = gtk_entry_new (); priv->align_hidden = g_object_ref_sink (gtk_alignment_new (0.0, 0.5, 1.0, 0.0)); gtk_container_add (GTK_CONTAINER (priv->align_hidden), GTK_WIDGET (priv->hidden_entry)); g_signal_connect (priv->hidden_entry, "activate", G_CALLBACK (hidden_entry_activate), window); g_signal_connect_swapped (priv->hidden_entry, "focus-out-event", G_CALLBACK (hidden_entry_hide), window); g_signal_connect (priv->hidden_entry, "key-press-event", G_CALLBACK (hidden_key_press), window); scroll = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroll), GTK_SHADOW_IN); gtk_box_pack_start (GTK_BOX (priv->vbox_view), scroll, TRUE, TRUE, 0); priv->find_bar = g_object_ref_sink (gtk_hbox_new (FALSE, 6)); g_object_set (priv->find_bar, "border-width", 2, "margin-right", 16, NULL); label = gtk_label_new (_("Find:")); gtk_box_pack_start (GTK_BOX (priv->find_bar), label, FALSE, FALSE, 6); priv->find_entry = gtk_entry_new (); g_signal_connect (priv->find_entry, "changed", G_CALLBACK (find_entry_changed), window); g_signal_connect (priv->find_entry, "key-press-event", G_CALLBACK (find_entry_key_press), window); g_signal_connect (priv->find_entry, "focus-out-event", G_CALLBACK (find_entry_focus_out), window); g_object_set (priv->find_entry, "width-request", 300, NULL); gtk_box_pack_start (GTK_BOX (priv->find_bar), priv->find_entry, FALSE, FALSE, 0); priv->find_label = gtk_label_new (""); g_object_set (priv->find_label, "xalign", 0.0, NULL); gtk_box_pack_start (GTK_BOX (priv->find_bar), priv->find_label, FALSE, FALSE, 0); priv->read_later = g_object_ref_sink (gtk_info_bar_new ()); vbox = gtk_vbox_new (FALSE, 0); color = yelp_settings_get_color (yelp_settings_get_default (), YELP_SETTINGS_COLOR_TEXT_LIGHT); text = g_markup_printf_escaped ("%s", color, _("Read Later")); button = gtk_label_new (text); g_object_set (button, "use-markup", TRUE, "xalign", 0.0, NULL); g_free (color); g_free (text); gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (gtk_info_bar_get_content_area (GTK_INFO_BAR (priv->read_later))), vbox, FALSE, FALSE, 0); priv->read_later_vbox = gtk_vbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (vbox), priv->read_later_vbox, FALSE, FALSE, 0); g_signal_connect (priv->view, "new-view-requested", G_CALLBACK (view_new_window), window); g_signal_connect (priv->view, "loaded", G_CALLBACK (view_loaded), window); g_signal_connect (priv->view, "notify::yelp-uri", G_CALLBACK (view_uri_selected), window); g_signal_connect_swapped (priv->view, "notify::page-id", G_CALLBACK (window_set_bookmark_action), window); window_set_bookmark_action (window); g_signal_connect (priv->view, "notify::root-title", G_CALLBACK (view_root_title), window); gtk_container_add (GTK_CONTAINER (scroll), GTK_WIDGET (priv->view)); gtk_widget_grab_focus (GTK_WIDGET (priv->view)); gtk_drag_dest_set (GTK_WIDGET (window), GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY); gtk_drag_dest_add_uri_targets (GTK_WIDGET (window)); g_signal_connect (window, "drag-data-received", G_CALLBACK (window_drag_received), NULL); } /******************************************************************************/ YelpWindow * yelp_window_new (YelpApplication *app) { YelpWindow *window; window = (YelpWindow *) g_object_new (YELP_TYPE_WINDOW, "application", app, NULL); return window; } void yelp_window_load_uri (YelpWindow *window, YelpUri *uri) { YelpWindowPrivate *priv = GET_PRIV (window); yelp_view_load_uri (priv->view, uri); } YelpUri * yelp_window_get_uri (YelpWindow *window) { YelpUri *uri; YelpWindowPrivate *priv = GET_PRIV (window); g_object_get (G_OBJECT (priv->view), "yelp-uri", &uri, NULL); return uri; } void yelp_window_get_geometry (YelpWindow *window, gint *width, gint *height) { YelpWindowPrivate *priv = GET_PRIV (window); *width = priv->width; *height = priv->height; } /******************************************************************************/ static void window_new (GtkAction *action, YelpWindow *window) { YelpUri *yuri; gchar *uri = NULL; YelpWindowPrivate *priv = GET_PRIV (window); g_object_get (priv->view, "yelp-uri", &yuri, NULL); uri = yelp_uri_get_document_uri (yuri); yelp_application_new_window (priv->application, uri); g_free (uri); g_object_unref (yuri); } static void window_drag_received (YelpWindow *window, GdkDragContext *context, gint x, gint y, GtkSelectionData *data, guint info, guint time, gpointer userdata) { gchar **uris = gtk_selection_data_get_uris (data); if (uris && uris[0]) { YelpUri *uri = yelp_uri_new (uris[0]); yelp_window_load_uri (window, uri); g_object_unref (uri); g_strfreev (uris); gtk_drag_finish (context, TRUE, FALSE, time); } gtk_drag_finish (context, FALSE, FALSE, time); } static gboolean window_map_event (YelpWindow *window, GdkEvent *event, gpointer user_data) { YelpWindowPrivate *priv = GET_PRIV (window); priv->configured = TRUE; return FALSE; } static gboolean window_configure_event (YelpWindow *window, GdkEventConfigure *event, gpointer user_data) { YelpWindowPrivate *priv = GET_PRIV (window); gboolean skip = TRUE; if (priv->width != event->width) { skip = FALSE; priv->width = event->width; } if (priv->height != event->height) { skip = FALSE; priv->height = event->height; } /* Skip the configure-event signals that GTK+ sends as it's mapping * the window, and also skip if the event didn't change the size of * the window (i.e. it was just a move). */ if (!priv->configured || skip) return FALSE; if (priv->resize_signal > 0) g_source_remove (priv->resize_signal); priv->resize_signal = g_timeout_add (200, (GSourceFunc) window_resize_signal, window); g_object_set (priv->find_entry, "width-request", 2 * priv->width / 3, NULL); return FALSE; } static gboolean window_resize_signal (YelpWindow *window) { YelpWindowPrivate *priv = GET_PRIV (window); g_signal_emit (window, signals[RESIZE_EVENT], 0); priv->resize_signal = 0; return FALSE; } static void window_close (GtkAction *action, YelpWindow *window) { gboolean ret; g_signal_emit_by_name (window, "delete-event", NULL, &ret); gtk_widget_destroy (GTK_WIDGET (window)); } static void window_go_all (GtkAction *action, YelpWindow *window) { YelpWindowPrivate *priv = GET_PRIV (window); yelp_view_load (priv->view, "help-list:"); } static void window_add_bookmark (GtkAction *action, YelpWindow *window) { YelpUri *uri; gchar *doc_uri, *page_id, *icon, *title; YelpWindowPrivate *priv = GET_PRIV (window); g_object_get (priv->view, "yelp-uri", &uri, "page-id", &page_id, "page-icon", &icon, "page-title", &title, NULL); doc_uri = yelp_uri_get_document_uri (uri); yelp_application_add_bookmark (YELP_BOOKMARKS (priv->application), doc_uri, page_id, icon, title); g_free (doc_uri); g_free (page_id); g_free (icon); g_free (title); g_object_unref (uri); } static void window_remove_bookmark (GtkAction *action, YelpWindow *window) { YelpUri *uri; gchar *doc_uri, *page_id; YelpWindowPrivate *priv = GET_PRIV (window); g_object_get (priv->view, "yelp-uri", &uri, "page-id", &page_id, NULL); doc_uri = yelp_uri_get_document_uri (uri); yelp_application_remove_bookmark (YELP_BOOKMARKS (priv->application), doc_uri, page_id); g_free (doc_uri); g_free (page_id); g_object_unref (uri); } static void window_load_bookmark (GtkAction *action, YelpWindow *window) { YelpUri *base, *uri; gchar *xref; YelpWindowPrivate *priv = GET_PRIV (window); /* Bookmark action names are prefixed with 'LoadBookmark-' */ xref = g_strconcat ("xref:", gtk_action_get_name (action) + 13, NULL); g_object_get (priv->view, "yelp-uri", &base, NULL); uri = yelp_uri_new_relative (base, xref); yelp_view_load_uri (priv->view, uri); g_object_unref (base); g_object_unref (uri); g_free (xref); } static void app_bookmarks_changed (YelpApplication *app, const gchar *doc_uri, YelpWindow *window) { YelpUri *uri; gchar *this_doc_uri; YelpWindowPrivate *priv = GET_PRIV (window); g_object_get (priv->view, "yelp-uri", &uri, NULL); this_doc_uri = yelp_uri_get_document_uri (uri); if (g_str_equal (this_doc_uri, doc_uri)) { window_set_bookmarks (window, doc_uri); } g_free (this_doc_uri); g_object_unref (uri); } typedef struct _YelpMenuEntry YelpMenuEntry; struct _YelpMenuEntry { gchar *page_id; gchar *icon; gchar *title; }; static gint entry_compare (YelpMenuEntry *a, YelpMenuEntry *b) { gint ret = yelp_settings_cmp_icons (a->icon, b->icon); if (ret != 0) return ret; if (a->title && b->title) return g_utf8_collate (a->title, b->title); else if (b->title == NULL) return -1; else if (a->title == NULL) return 1; return 0; } static void window_set_bookmarks (YelpWindow *window, const gchar *doc_uri) { GVariant *value; GVariantIter *iter; gchar *page_id, *icon, *title; YelpWindowPrivate *priv = GET_PRIV (window); GSList *entries = NULL; window_set_bookmark_action (window); gtk_ui_manager_remove_ui (priv->ui_manager, priv->bookmarks_merge_id); value = yelp_application_get_bookmarks (priv->application, doc_uri); g_variant_get (value, "a(sss)", &iter); while (g_variant_iter_loop (iter, "(&s&s&s)", &page_id, &icon, &title)) { YelpMenuEntry *entry = g_new0 (YelpMenuEntry, 1); entry->page_id = page_id; entry->icon = g_strdup (icon); entry->title = title; entries = g_slist_insert_sorted (entries, entry, (GCompareFunc) entry_compare); } for ( ; entries != NULL; entries = g_slist_delete_link (entries, entries)) { GSList *cur; GtkAction *bookmark; YelpMenuEntry *entry = (YelpMenuEntry *) entries->data; gchar *action_id = g_strconcat ("LoadBookmark-", entry->page_id, NULL); bookmark = gtk_action_group_get_action (priv->bookmark_actions, action_id); if (bookmark) { /* The action might have been set by a different document using * the same page ID. We can just reuse the action, since it's * just a page ID relative to the current URI, but we need to * reset the title and icon. */ g_object_set (bookmark, "label", entry->title, "icon-name", entry->icon, NULL); } else { bookmark = gtk_action_new (action_id, entry->title, NULL, NULL); g_signal_connect (bookmark, "activate", G_CALLBACK (window_load_bookmark), window); gtk_action_set_icon_name (bookmark, entry->icon); gtk_action_group_add_action (priv->bookmark_actions, bookmark); } gtk_ui_manager_add_ui (priv->ui_manager, priv->bookmarks_merge_id, "ui/menubar/BookmarksMenu/Bookmarks", action_id, action_id, GTK_UI_MANAGER_MENUITEM, FALSE); gtk_ui_manager_ensure_update (priv->ui_manager); for (cur = gtk_action_get_proxies (bookmark); cur != NULL; cur = cur->next) { if (GTK_IS_IMAGE_MENU_ITEM (cur->data)) g_object_set (cur->data, "always-show-image", TRUE, NULL); } g_free (action_id); g_free (entry->icon); g_free (entry); } g_variant_iter_free (iter); g_variant_unref (value); } static void window_set_bookmark_action (YelpWindow *window) { YelpUri *uri = NULL; gchar *doc_uri = NULL, *page_id = NULL; GtkAction *action_add, *action_del; gboolean bookmarked; YelpWindowPrivate *priv = GET_PRIV (window); action_add = gtk_action_group_get_action (priv->action_group, "AddBookmark"); action_del = gtk_action_group_get_action (priv->action_group, "RemoveBookmark"); g_object_get (priv->view, "yelp-uri", &uri, "page-id", &page_id, NULL); if (page_id == NULL || uri == NULL) { gtk_action_set_sensitive (action_add, FALSE); gtk_action_set_sensitive (action_del, FALSE); goto done; } doc_uri = yelp_uri_get_document_uri (uri); bookmarked = yelp_application_is_bookmarked (YELP_BOOKMARKS (priv->application), doc_uri, page_id); gtk_action_set_sensitive (action_add, !bookmarked); gtk_action_set_sensitive (action_del, bookmarked); done: g_free (page_id); g_free (doc_uri); if (uri) g_object_unref (uri); } static void window_start_search (GtkAction *action, YelpWindow *window) { YelpWindowPrivate *priv = GET_PRIV (window); yelp_location_entry_start_search (priv->entry); } static void window_open_location (GtkAction *action, YelpWindow *window) { YelpUri *yuri = NULL; gchar *uri = NULL; GdkColor yellow; gchar *color; YelpWindowPrivate *priv = GET_PRIV (window); gtk_container_remove (GTK_CONTAINER (priv->hbox), priv->align_location); gtk_box_pack_start (GTK_BOX (priv->hbox), priv->align_hidden, TRUE, TRUE, 0); gtk_widget_show_all (priv->align_hidden); gtk_entry_set_text (GTK_ENTRY (priv->hidden_entry), ""); gtk_widget_grab_focus (priv->hidden_entry); color = yelp_settings_get_color (yelp_settings_get_default (), YELP_SETTINGS_COLOR_YELLOW_BASE); if (gdk_color_parse (color, &yellow)) { gtk_widget_modify_base (priv->hidden_entry, GTK_STATE_NORMAL, &yellow); } g_free (color); g_object_get (priv->view, "yelp-uri", &yuri, NULL); if (yuri) { uri = yelp_uri_get_canonical_uri (yuri); g_object_unref (yuri); } if (uri) { gchar *c; gtk_entry_set_text (GTK_ENTRY (priv->hidden_entry), uri); c = strchr (uri, ':'); if (c) gtk_editable_select_region (GTK_EDITABLE (priv->hidden_entry), c - uri + 1, -1); else gtk_editable_select_region (GTK_EDITABLE (priv->hidden_entry), 5, -1); g_free (uri); } } static void read_later_resolved (YelpUri *uri, YelpWindow *window) { gchar *fulluri; const gchar *text = (const gchar *) g_object_get_data ((GObject *) uri, "link-text"); YelpWindowPrivate *priv = GET_PRIV (window); YelpUri *base; gchar *doc_uri; g_object_get (priv->view, "yelp-uri", &base, NULL); doc_uri = yelp_uri_get_document_uri (uri); fulluri = yelp_uri_get_canonical_uri (uri); yelp_application_add_read_later (priv->application, doc_uri, fulluri, text); g_object_unref (base); g_free (doc_uri); g_free (fulluri); } static void window_read_later (GtkAction *action, YelpWindow *window) { YelpWindowPrivate *priv = GET_PRIV (window); YelpUri *uri; gchar *text; uri = yelp_view_get_active_link_uri (priv->view); text = yelp_view_get_active_link_text (priv->view); g_object_set_data_full ((GObject *) uri, "link-text", text, g_free); if (!yelp_uri_is_resolved (uri)) { g_signal_connect (uri, "resolved", G_CALLBACK (read_later_resolved), window); yelp_uri_resolve (uri); } else { read_later_resolved (uri, window); } } static gboolean read_later_clicked (GtkLinkButton *button, YelpWindow *window) { YelpWindowPrivate *priv = GET_PRIV (window); YelpUri *base; gchar *doc_uri; gchar *fulluri; fulluri = g_strdup (gtk_link_button_get_uri (button)); g_object_get (priv->view, "yelp-uri", &base, NULL); doc_uri = yelp_uri_get_document_uri (base); yelp_application_remove_read_later (priv->application, doc_uri, fulluri); g_object_unref (base); g_free (doc_uri); yelp_view_load (priv->view, fulluri); g_free (fulluri); return TRUE; } static void app_read_later_changed (YelpApplication *app, const gchar *doc_uri, YelpWindow *window) { GVariant *value; GVariantIter *viter; gchar *uri, *title; /* do not free */ GList *children; gboolean has_children = FALSE; YelpWindowPrivate *priv = GET_PRIV (window); children = gtk_container_get_children (GTK_CONTAINER (priv->read_later_vbox)); while (children) { gtk_container_remove (GTK_CONTAINER (priv->read_later_vbox), GTK_WIDGET (children->data)); children = g_list_delete_link (children, children); } value = yelp_application_get_read_later (priv->application, doc_uri); g_variant_get (value, "a(ss)", &viter); while (g_variant_iter_loop (viter, "(&s&s)", &uri, &title)) { GtkWidget *align, *link; align = gtk_alignment_new (0.0, 0.0, 0.0, 0.0); g_object_set (align, "left-padding", 6, NULL); gtk_box_pack_start (GTK_BOX (priv->read_later_vbox), align, FALSE, FALSE, 0); link = gtk_link_button_new_with_label (uri, title); g_object_set (link, "xalign", 0.0, NULL); g_signal_connect (link, "activate-link", G_CALLBACK (read_later_clicked), window); gtk_container_add (GTK_CONTAINER (align), link); gtk_widget_show_all (align); has_children = TRUE; } g_variant_iter_free (viter); g_variant_unref (value); if (has_children) { if (gtk_widget_get_parent (priv->read_later) == NULL) { gtk_box_pack_end (GTK_BOX (priv->vbox_full), priv->read_later, FALSE, FALSE, 0); gtk_widget_show_all (priv->read_later); } } else { if (gtk_widget_get_parent (priv->read_later) != NULL) gtk_container_remove (GTK_CONTAINER (priv->vbox_full), priv->read_later); } } static void window_find_in_page (GtkAction *action, YelpWindow *window) { YelpWindowPrivate *priv = GET_PRIV (window); if (gtk_widget_get_parent (priv->find_bar) != NULL) { gtk_widget_grab_focus (priv->find_entry); return; } g_object_set (priv->find_entry, "width-request", 2 * priv->width / 3, NULL); gtk_box_pack_end (GTK_BOX (priv->vbox_view), priv->find_bar, FALSE, FALSE, 0); gtk_widget_show_all (priv->find_bar); gtk_widget_grab_focus (priv->find_entry); } static gboolean find_entry_key_press (GtkEntry *entry, GdkEventKey *event, YelpWindow *window) { YelpWindowPrivate *priv = GET_PRIV (window); if (event->keyval == GDK_KEY_Escape) { gtk_widget_grab_focus (GTK_WIDGET (priv->view)); return TRUE; } if (event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter) { gchar *text = gtk_editable_get_chars (GTK_EDITABLE (entry), 0, -1); webkit_web_view_search_text (WEBKIT_WEB_VIEW (priv->view), text, FALSE, TRUE, TRUE); g_free (text); return TRUE; } return FALSE; } static gboolean find_entry_focus_out (GtkEntry *entry, GdkEventFocus *event, YelpWindow *window) { YelpWindowPrivate *priv = GET_PRIV (window); webkit_web_view_unmark_text_matches (WEBKIT_WEB_VIEW (priv->view)); webkit_web_view_set_highlight_text_matches (WEBKIT_WEB_VIEW (priv->view), FALSE); if (gtk_widget_get_parent (priv->find_bar) != NULL) gtk_container_remove (GTK_CONTAINER (priv->vbox_view), priv->find_bar); return FALSE; } static void find_entry_changed (GtkEntry *entry, YelpWindow *window) { gchar *text; gint count; YelpWindowPrivate *priv = GET_PRIV (window); webkit_web_view_unmark_text_matches (WEBKIT_WEB_VIEW (priv->view)); text = gtk_editable_get_chars (GTK_EDITABLE (entry), 0, -1); if (text[0] == '\0') { gtk_label_set_text (GTK_LABEL (priv->find_label), ""); return; } count = webkit_web_view_mark_text_matches (WEBKIT_WEB_VIEW (priv->view), text, FALSE, 0); if (count > 0) { gchar *label = g_strdup_printf (ngettext ("%i match", "%i matches", count), count); webkit_web_view_set_highlight_text_matches (WEBKIT_WEB_VIEW (priv->view), TRUE); webkit_web_view_search_text (WEBKIT_WEB_VIEW (priv->view), text, FALSE, TRUE, TRUE); gtk_label_set_text (GTK_LABEL (priv->find_label), label); g_free (label); } else { webkit_web_view_set_highlight_text_matches (WEBKIT_WEB_VIEW (priv->view), FALSE); gtk_label_set_text (GTK_LABEL (priv->find_label), _("No matches")); } g_free (text); } static gboolean entry_color_animate (YelpWindow *window) { gchar *color; GdkColor yellow, base; YelpWindowPrivate *priv = GET_PRIV (window); color = yelp_settings_get_color (yelp_settings_get_default (), YELP_SETTINGS_COLOR_YELLOW_BASE); gdk_color_parse (color, &yellow); g_free (color); color = yelp_settings_get_color (yelp_settings_get_default (), YELP_SETTINGS_COLOR_BASE); gdk_color_parse (color, &base); g_free (color); yellow.red = priv->entry_color_step * yellow.red + (1.0 - priv->entry_color_step) * base.red; yellow.green = priv->entry_color_step * yellow.green + (1.0 - priv->entry_color_step) * base.green; yellow.blue = priv->entry_color_step * yellow.blue + (1.0 - priv->entry_color_step) * base.blue; gtk_widget_modify_base (gtk_bin_get_child (GTK_BIN (priv->entry)), GTK_STATE_NORMAL, &yellow); priv->entry_color_step -= 0.05; if (priv->entry_color_step < 0.0) { priv->entry_color_animate = 0; return FALSE; } return TRUE; } static gboolean entry_focus_in (GtkEntry *entry, GdkEventFocus *event, YelpWindow *window) { YelpWindowPrivate *priv = GET_PRIV (window); if (priv->entry_color_animate != 0) return FALSE; priv->entry_color_step = 1.0; priv->entry_color_animate = g_timeout_add (40, (GSourceFunc) entry_color_animate, window); return FALSE; } static gboolean entry_focus_out (YelpLocationEntry *entry, GdkEventFocus *event, YelpWindow *window) { YelpWindowPrivate *priv = GET_PRIV (window); gtk_widget_grab_focus (GTK_WIDGET (priv->view)); return FALSE; } static void window_zeitgeist_results_cb (ZeitgeistLog *zeitgeist, GAsyncResult *res, YelpWindow *window) { YelpWindowPrivate *priv = GET_PRIV (window); ZeitgeistResultSet *results; ZeitgeistEvent *event; ZeitgeistSubject *subject; WebKitDOMDocument *document; WebKitDOMElement *section = NULL; results = zeitgeist_log_find_events_finish (zeitgeist, res, NULL); if (results && zeitgeist_result_set_size(results) > 0) { document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (priv->view)); section = webkit_dom_document_query_selector (document, "div.sect-links > div.contents", NULL); if (section != NULL) { WebKitDOMElement *child, *span; child = webkit_dom_document_create_element (document, "div", NULL); webkit_dom_element_set_attribute (child, "class", "links zeitgeistlinks", NULL); webkit_dom_node_append_child (WEBKIT_DOM_NODE (section), WEBKIT_DOM_NODE (child), NULL); section = child; child = webkit_dom_document_create_element (document, "div", NULL); webkit_dom_element_set_attribute (child, "class", "inner", NULL); webkit_dom_node_append_child (WEBKIT_DOM_NODE (section), WEBKIT_DOM_NODE (child), NULL); section = child; child = webkit_dom_document_create_element (document, "div", NULL); webkit_dom_element_set_attribute (child, "class", "title", NULL); span = webkit_dom_document_create_element (document, "span", NULL); webkit_dom_element_set_attribute (span, "class", "title", NULL); webkit_dom_node_set_text_content (WEBKIT_DOM_NODE (span), _("Often Viewed With"), NULL); webkit_dom_node_append_child (WEBKIT_DOM_NODE (child), WEBKIT_DOM_NODE (span), NULL); webkit_dom_node_append_child (WEBKIT_DOM_NODE (section), WEBKIT_DOM_NODE (child), NULL); child = webkit_dom_document_create_element (document, "div", NULL); webkit_dom_element_set_attribute (child, "class", "region", NULL); webkit_dom_node_append_child (WEBKIT_DOM_NODE (section), WEBKIT_DOM_NODE (child), NULL); section = child; child = webkit_dom_document_create_element (document, "ul", NULL); webkit_dom_node_append_child (WEBKIT_DOM_NODE (section), WEBKIT_DOM_NODE (child), NULL); section = child; while (zeitgeist_result_set_has_next (results)) { event = zeitgeist_result_set_next (results); if (zeitgeist_event_num_subjects (event) == 0) continue; subject = zeitgeist_event_get_subject (event, 0); child = webkit_dom_document_create_element (document, "li", NULL); webkit_dom_element_set_attribute (child, "class", "links zeitgeistlinks", NULL); span = webkit_dom_document_create_element (document, "a", NULL); webkit_dom_element_set_attribute (span, "href", zeitgeist_subject_get_uri (subject), NULL); webkit_dom_node_set_text_content (WEBKIT_DOM_NODE (span), zeitgeist_subject_get_text (subject), NULL); webkit_dom_node_append_child (WEBKIT_DOM_NODE (child), WEBKIT_DOM_NODE (span), NULL); webkit_dom_node_append_child (WEBKIT_DOM_NODE (section), WEBKIT_DOM_NODE (child), NULL); } } } g_object_unref (results); } static void window_zeitgeist_related_cb (ZeitgeistLog *zeitgeist, GAsyncResult *res, YelpWindow *window) { GPtrArray *tmpl; gint i; gchar **results = zeitgeist_log_find_related_uris_finish (zeitgeist, res, NULL); if (results && results[0]) { tmpl = g_ptr_array_new_full (0, g_object_unref); for (i = 0; results[i]; i++) { g_ptr_array_add (tmpl, zeitgeist_event_new_full (ZEITGEIST_ZG_ACCESS_EVENT, ZEITGEIST_ZG_USER_ACTIVITY, "application://yelp.desktop", zeitgeist_subject_new_full (results[i], YELP_ZEITGEIST_PAGE, ZEITGEIST_NFO_FILE_DATA_OBJECT, NULL, NULL, NULL, NULL), NULL)); } zeitgeist_log_find_events (zeitgeist, zeitgeist_time_range_new_anytime (), tmpl, ZEITGEIST_STORAGE_STATE_ANY, i, ZEITGEIST_RESULT_TYPE_MOST_RECENT_SUBJECTS, NULL, /* cancellable */ (GAsyncReadyCallback) window_zeitgeist_results_cb, window); } g_strfreev (results); } static void window_update_zeitgeist (YelpWindow *window, YelpUri *uri, const gchar *title) { YelpWindowPrivate *priv = GET_PRIV (window); ZeitgeistEvent *event; GPtrArray *tmpl, *rtmpl; gchar *docuri = yelp_uri_get_document_uri (uri); gchar *fulluri = yelp_uri_get_canonical_uri (uri); event = zeitgeist_event_new_full (ZEITGEIST_ZG_ACCESS_EVENT, ZEITGEIST_ZG_USER_ACTIVITY, "application://yelp.desktop", zeitgeist_subject_new_full (fulluri, YELP_ZEITGEIST_PAGE, ZEITGEIST_NFO_FILE_DATA_OBJECT, NULL, docuri, title, NULL), NULL); zeitgeist_log_insert_events_no_reply (priv->zeitgeist, event, NULL); tmpl = g_ptr_array_new_full (0, g_object_unref); g_ptr_array_add (tmpl, zeitgeist_event_new_full (ZEITGEIST_ZG_ACCESS_EVENT, ZEITGEIST_ZG_USER_ACTIVITY, "application://yelp.desktop", zeitgeist_subject_new_full (fulluri, YELP_ZEITGEIST_PAGE, ZEITGEIST_NFO_FILE_DATA_OBJECT, NULL, docuri, NULL, NULL), NULL)); rtmpl = g_ptr_array_new_full (0, g_object_unref); g_ptr_array_add (rtmpl, zeitgeist_event_new_full (ZEITGEIST_ZG_ACCESS_EVENT, ZEITGEIST_ZG_USER_ACTIVITY, "application://yelp.desktop", zeitgeist_subject_new_full (NULL, YELP_ZEITGEIST_PAGE, ZEITGEIST_NFO_FILE_DATA_OBJECT, NULL, docuri, NULL, NULL), NULL)); zeitgeist_log_find_related_uris (priv->zeitgeist, zeitgeist_time_range_new_anytime (), tmpl, rtmpl, ZEITGEIST_STORAGE_STATE_ANY, 4, ZEITGEIST_RESULT_TYPE_MOST_RECENT_EVENTS, NULL, /* cancellable */ (GAsyncReadyCallback) window_zeitgeist_related_cb, window); g_free (fulluri); g_free (docuri); } static void view_new_window (YelpView *view, YelpUri *uri, YelpWindow *window) { YelpWindowPrivate *priv = GET_PRIV (window); yelp_application_new_window_uri (priv->application, uri); } static void view_loaded (YelpView *view, YelpWindow *window) { YelpUri *uri; gchar *doc_uri; YelpViewState state; GdkWindow *gdkwin; YelpWindowPrivate *priv = GET_PRIV (window); g_object_get (view, "yelp-uri", &uri, "state", &state, NULL); doc_uri = yelp_uri_get_document_uri (uri); gdkwin = gtk_widget_get_window (GTK_WIDGET (window)); if (gdkwin != NULL) gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (window)), NULL); if (state != YELP_VIEW_STATE_ERROR) { gchar *page_id, *icon, *title; g_object_get (view, "page-id", &page_id, "page-icon", &icon, "page-title", &title, NULL); yelp_application_update_bookmarks (priv->application, doc_uri, page_id, icon, title); app_read_later_changed (priv->application, doc_uri, window); window_update_zeitgeist (window, uri, title); g_free (page_id); g_free (icon); g_free (title); } g_free (doc_uri); g_object_unref (uri); } static void view_uri_selected (YelpView *view, GParamSpec *pspec, YelpWindow *window) { YelpUri *uri; gchar *doc_uri; GdkWindow *gdkwin; YelpWindowPrivate *priv = GET_PRIV (window); g_object_get (G_OBJECT (view), "yelp-uri", &uri, NULL); if (uri == NULL) return; gdkwin = gtk_widget_get_window (GTK_WIDGET (window)); if (gdkwin != NULL) gdk_window_set_cursor (gdkwin, gdk_cursor_new_for_display (gdk_window_get_display (gdkwin), GDK_WATCH)); doc_uri = yelp_uri_get_document_uri (uri); if (priv->doc_uri == NULL || !g_str_equal (priv->doc_uri, doc_uri)) { window_set_bookmarks (window, doc_uri); g_free (priv->doc_uri); priv->doc_uri = doc_uri; } else { g_free (doc_uri); } g_object_unref (uri); } static gboolean view_is_xref_uri (YelpView *view, GtkAction *action, const gchar *uri, YelpWindow *window) { return g_str_has_prefix (uri, "xref:"); } static void view_root_title (YelpView *view, GParamSpec *pspec, YelpWindow *window) { gchar *title; g_object_get (view, "root-title", &title, NULL); if (title) { gtk_window_set_title (GTK_WINDOW (window), title); g_free (title); } else { gtk_window_set_title (GTK_WINDOW (window), _("Help")); } } static void hidden_entry_activate (GtkEntry *entry, YelpWindow *window) { YelpWindowPrivate *priv = GET_PRIV (window); YelpUri *uri = yelp_uri_new (gtk_entry_get_text (entry)); yelp_window_load_uri (window, uri); g_object_unref (uri); gtk_widget_grab_focus (GTK_WIDGET (priv->view)); } static void hidden_entry_hide (YelpWindow *window) { YelpWindowPrivate *priv = GET_PRIV (window); if (gtk_widget_get_parent (priv->align_hidden) != NULL) { gtk_container_remove (GTK_CONTAINER (priv->hbox), priv->align_hidden); gtk_box_pack_start (GTK_BOX (priv->hbox), priv->align_location, TRUE, TRUE, 0); } } static gboolean hidden_key_press (GtkWidget *widget, GdkEventKey *event, YelpWindow *window) { YelpWindowPrivate *priv = GET_PRIV (window); if (event->keyval == GDK_KEY_Escape) { gtk_widget_grab_focus (GTK_WIDGET (priv->view)); return TRUE; } return FALSE; }