/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ /* * Copyright (C) 2008 Imendio AB * Copyright (C) 2008 Sven Herzberg * * 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 */ #include "config.h" #include "dh-assistant-view.h" #include #include #include "dh-util.h" #include "dh-book.h" #include "dh-book-manager.h" /** * SECTION:dh-assistant-view * @Title: DhAssistantView * @Short_description: A small “assistant” widget for displaying just one hit * * #DhAssistantView is a subclass of #WebKitWebView for displaying the * documentation of just one symbol. * * A possible use-case: in a text editor, pressing a keyboard shortcut could * display this widget for the symbol under the cursor. * * With the Devhelp application, an assistant can easily be launched with the * command line option `--search-assistant`. */ typedef struct { DhLink *link; gchar *current_search; guint snippet_loaded : 1; } DhAssistantViewPrivate; enum { SIGNAL_OPEN_URI, N_SIGNALS }; static guint signals[N_SIGNALS] = { 0 }; G_DEFINE_TYPE_WITH_PRIVATE (DhAssistantView, dh_assistant_view, WEBKIT_TYPE_WEB_VIEW); static void view_finalize (GObject *object) { DhAssistantView *view = DH_ASSISTANT_VIEW (object); DhAssistantViewPrivate *priv = dh_assistant_view_get_instance_private (view); if (priv->link) { g_object_unref (priv->link); } g_free (priv->current_search); G_OBJECT_CLASS (dh_assistant_view_parent_class)->finalize (object); } static gboolean assistant_decide_policy (WebKitWebView *web_view, WebKitPolicyDecision *decision, WebKitPolicyDecisionType decision_type) { DhAssistantViewPrivate *priv; const gchar *uri; WebKitNavigationPolicyDecision *navigation_decision; WebKitNavigationAction *navigation_action; WebKitNavigationType navigation_type; WebKitURIRequest *request; if (decision_type != WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION) { webkit_policy_decision_ignore (decision); return TRUE; } priv = dh_assistant_view_get_instance_private (DH_ASSISTANT_VIEW (web_view)); navigation_decision = WEBKIT_NAVIGATION_POLICY_DECISION (decision); navigation_action = webkit_navigation_policy_decision_get_navigation_action (navigation_decision); navigation_type = webkit_navigation_action_get_navigation_type (navigation_action); if (navigation_type != WEBKIT_NAVIGATION_TYPE_LINK_CLICKED) { if (! priv->snippet_loaded) { priv->snippet_loaded = TRUE; webkit_policy_decision_use (decision); } webkit_policy_decision_ignore (decision); return TRUE; } request = webkit_navigation_action_get_request (navigation_action); uri = webkit_uri_request_get_uri (request); if (strcmp (uri, "about:blank") == 0) { webkit_policy_decision_use (decision); return TRUE; } g_signal_emit (web_view, signals[SIGNAL_OPEN_URI], 0, uri); webkit_policy_decision_ignore (decision); return TRUE; } static gboolean assistant_button_press_event (GtkWidget *widget, GdkEventButton *event) { /* Block webkit's builtin context menu. */ if (event->button != 1) { return TRUE; } return GTK_WIDGET_CLASS (dh_assistant_view_parent_class)->button_press_event (widget, event); } static void dh_assistant_view_class_init (DhAssistantViewClass* klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); WebKitWebViewClass *web_view_class = WEBKIT_WEB_VIEW_CLASS (klass); object_class->finalize = view_finalize; widget_class->button_press_event = assistant_button_press_event; web_view_class->decide_policy = assistant_decide_policy; /** * DhAssistantView::open-uri: * @view: the view on which the signal is emitted * @uri: the uri to open */ signals[SIGNAL_OPEN_URI] = g_signal_new ("open-uri", G_TYPE_FROM_CLASS (object_class), 0, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING); } static void dh_assistant_view_init (DhAssistantView *view) { } /** * dh_assistant_view_new: * * Returns: (transfer floating): a new #DhAssistantView widget. */ GtkWidget * dh_assistant_view_new (void) { return g_object_new (DH_TYPE_ASSISTANT_VIEW, NULL); } static const gchar * find_in_buffer (const gchar *buffer, const gchar *key, gsize length, gsize key_length) { gsize m = 0; gsize i = 0; while (i < length) { if (key[m] == buffer[i]) { m++; if (m == key_length) { return buffer + i - m + 1; } } else { m = 0; } i++; } return NULL; } /** * dh_assistant_view_set_link: * @view: a #DhAssistantView. * @link: (nullable): a #DhLink to set or %NULL. * * Open @link in the assistant view, if %NULL the view will be blanked. * * Returns: %TRUE if the requested link is open, %FALSE otherwise. */ gboolean dh_assistant_view_set_link (DhAssistantView *view, DhLink *link) { DhAssistantViewPrivate *priv; gchar *uri; const gchar *anchor; gchar *filename; GMappedFile *file; const gchar *contents; gsize length; gchar *key; gsize key_length; gsize offset = 0; const gchar *start; const gchar *end; g_return_val_if_fail (DH_IS_ASSISTANT_VIEW (view), FALSE); priv = dh_assistant_view_get_instance_private (view); if (priv->link == link) { return TRUE; } if (priv->link) { dh_link_unref (priv->link); priv->link = NULL; } if (link) { link = dh_link_ref (link); } else { webkit_web_view_load_uri (WEBKIT_WEB_VIEW (view), "about:blank"); return TRUE; } /* FIXME uri can be NULL. */ uri = dh_link_get_uri (link); anchor = strrchr (uri, '#'); if (anchor) { filename = g_strndup (uri, anchor - uri); anchor++; } else { g_free (uri); return FALSE; } if (g_str_has_prefix (filename, "file://")) offset = 7; file = g_mapped_file_new (filename + offset, FALSE, NULL); if (!file) { g_free (filename); g_free (uri); return FALSE; } contents = g_mapped_file_get_contents (file); length = g_mapped_file_get_length (file); key = g_strdup_printf (""; start = find_in_buffer (start, start_key, length, strlen (start_key)); end_key = "
buf) { name[-1] = '\n'; } } stylesheet = dh_util_build_data_filename ("devhelp", "assistant", "assistant.css", NULL); stylesheet_uri = dh_util_create_data_uri_for_filename (stylesheet, "text/css"); g_free (stylesheet); if (stylesheet_uri) stylesheet_html = g_strdup_printf ("", stylesheet_uri); g_free (stylesheet_uri); javascript = dh_util_build_data_filename ("devhelp", "assistant", "assistant.js", NULL); javascript_uri = dh_util_create_data_uri_for_filename (javascript, "application/javascript"); g_free (javascript); if (javascript_uri) javascript_html = g_strdup_printf ("", javascript_uri); g_free (javascript_uri); html = g_strdup_printf ( "" "" "%s" "%s" "" "" "" "
%s %s
" "
%s
" "" "", stylesheet_html, javascript_html, function, dh_link_type_to_string (dh_link_get_link_type (link)), dh_link_get_uri (link), dh_link_get_name (link), _("Book:"), dh_link_get_book_title (link), buf); g_free (buf); g_free (stylesheet_html); g_free (javascript_html); priv->snippet_loaded = FALSE; webkit_web_view_load_html ( WEBKIT_WEB_VIEW (view), html, filename); g_free (html); } else { webkit_web_view_load_uri (WEBKIT_WEB_VIEW (view), "about:blank"); } g_mapped_file_unref (file); g_free (filename); return TRUE; } /** * dh_assistant_view_search: * @view: a #DhAssistantView. * @str: the search query. * * Search for @str in the current assistant view. * * Returns: %TRUE if @str was found, %FALSE otherwise. */ gboolean dh_assistant_view_search (DhAssistantView *view, const gchar *str) { DhAssistantViewPrivate *priv; DhBookManager *book_manager; const gchar *name; DhLink *link; DhLink *exact_link; DhLink *prefix_link; GList *books; g_return_val_if_fail (DH_IS_ASSISTANT_VIEW (view), FALSE); g_return_val_if_fail (str, FALSE); priv = dh_assistant_view_get_instance_private (view); /* Filter out very short strings. */ if (strlen (str) < 4) { return FALSE; } if (priv->current_search && strcmp (priv->current_search, str) == 0) { return FALSE; } g_free (priv->current_search); priv->current_search = g_strdup (str); prefix_link = NULL; exact_link = NULL; book_manager = dh_book_manager_get_singleton (); for (books = dh_book_manager_get_books (book_manager); !exact_link && books; books = g_list_next (books)) { GList *l; for (l = dh_book_get_links (DH_BOOK (books->data)); l && exact_link == NULL; l = l->next) { DhLinkType type; link = l->data; type = dh_link_get_link_type (link); if (type == DH_LINK_TYPE_BOOK || type == DH_LINK_TYPE_PAGE || type == DH_LINK_TYPE_KEYWORD) { continue; } name = dh_link_get_name (link); if (strcmp (name, str) == 0) { exact_link = link; } else if (g_str_has_prefix (name, str)) { /* Prefer shorter prefix matches. */ if (!prefix_link) { prefix_link = link; } else if (strlen (dh_link_get_name (prefix_link)) > strlen (name)) { prefix_link = link; } } } } if (exact_link) { /*g_print ("exact hit: '%s' '%s'\n", exact_link->name, str);*/ dh_assistant_view_set_link (view, exact_link); } else if (prefix_link) { /*g_print ("prefix hit: '%s' '%s'\n", prefix_link->name, str);*/ dh_assistant_view_set_link (view, prefix_link); } else { /*g_print ("no hit\n");*/ /*assistant_view_set_link (view, NULL);*/ return FALSE; } return TRUE; }