Blame src/dh-assistant-view.c

Packit 116408
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
Packit 116408
/*
Packit 116408
 * Copyright (C) 2008 Imendio AB
Packit 116408
 * Copyright (C) 2008 Sven Herzberg
Packit 116408
 *
Packit 116408
 * This program is free software; you can redistribute it and/or
Packit 116408
 * modify it under the terms of the GNU General Public License as
Packit 116408
 * published by the Free Software Foundation; either version 2 of the
Packit 116408
 * License, or (at your option) any later version.
Packit 116408
 *
Packit 116408
 * This program is distributed in the hope that it will be useful,
Packit 116408
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 116408
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 116408
 * GNU General Public License for more details.
Packit 116408
 *
Packit 116408
 * You should have received a copy of the GNU General Public License
Packit 116408
 * along with this program; if not, write to the Free Software
Packit 116408
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
Packit 116408
 * USA
Packit 116408
 */
Packit 116408
Packit 116408
#include "config.h"
Packit 116408
#include "dh-assistant-view.h"
Packit 116408
#include <string.h>
Packit 116408
#include <glib/gi18n-lib.h>
Packit 116408
#include "dh-util.h"
Packit 116408
#include "dh-book.h"
Packit 116408
#include "dh-book-manager.h"
Packit 116408
Packit 116408
/**
Packit 116408
 * SECTION:dh-assistant-view
Packit 116408
 * @Title: DhAssistantView
Packit 116408
 * @Short_description: A small “assistant” widget for displaying just one hit
Packit 116408
 *
Packit 116408
 * #DhAssistantView is a subclass of #WebKitWebView for displaying the
Packit 116408
 * documentation of just one symbol.
Packit 116408
 *
Packit 116408
 * A possible use-case: in a text editor, pressing a keyboard shortcut could
Packit 116408
 * display this widget for the symbol under the cursor.
Packit 116408
 *
Packit 116408
 * With the Devhelp application, an assistant can easily be launched with the
Packit 116408
 * command line option `--search-assistant`.
Packit 116408
 */
Packit 116408
Packit 116408
typedef struct {
Packit 116408
        DhLink *link;
Packit 116408
        gchar *current_search;
Packit 116408
        guint snippet_loaded : 1;
Packit 116408
} DhAssistantViewPrivate;
Packit 116408
Packit 116408
enum {
Packit 116408
        SIGNAL_OPEN_URI,
Packit 116408
        N_SIGNALS
Packit 116408
};
Packit 116408
Packit 116408
static guint signals[N_SIGNALS] = { 0 };
Packit 116408
Packit 116408
G_DEFINE_TYPE_WITH_PRIVATE (DhAssistantView, dh_assistant_view, WEBKIT_TYPE_WEB_VIEW);
Packit 116408
Packit 116408
static void
Packit 116408
view_finalize (GObject *object)
Packit 116408
{
Packit 116408
        DhAssistantView *view = DH_ASSISTANT_VIEW (object);
Packit 116408
        DhAssistantViewPrivate *priv = dh_assistant_view_get_instance_private (view);
Packit 116408
Packit 116408
        if (priv->link) {
Packit 116408
                g_object_unref (priv->link);
Packit 116408
        }
Packit 116408
Packit 116408
        g_free (priv->current_search);
Packit 116408
Packit 116408
        G_OBJECT_CLASS (dh_assistant_view_parent_class)->finalize (object);
Packit 116408
}
Packit 116408
Packit 116408
static gboolean
Packit 116408
assistant_decide_policy (WebKitWebView           *web_view,
Packit 116408
                         WebKitPolicyDecision    *decision,
Packit 116408
                         WebKitPolicyDecisionType decision_type)
Packit 116408
{
Packit 116408
        DhAssistantViewPrivate         *priv;
Packit 116408
        const gchar                    *uri;
Packit 116408
        WebKitNavigationPolicyDecision *navigation_decision;
Packit 116408
        WebKitNavigationAction         *navigation_action;
Packit 116408
        WebKitNavigationType            navigation_type;
Packit 116408
        WebKitURIRequest               *request;
Packit 116408
Packit 116408
        if (decision_type != WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION) {
Packit 116408
                webkit_policy_decision_ignore (decision);
Packit 116408
Packit 116408
                return TRUE;
Packit 116408
        }
Packit 116408
Packit 116408
        priv = dh_assistant_view_get_instance_private (DH_ASSISTANT_VIEW (web_view));
Packit 116408
Packit 116408
        navigation_decision = WEBKIT_NAVIGATION_POLICY_DECISION (decision);
Packit 116408
        navigation_action = webkit_navigation_policy_decision_get_navigation_action (navigation_decision);
Packit 116408
        navigation_type = webkit_navigation_action_get_navigation_type (navigation_action);
Packit 116408
        if (navigation_type != WEBKIT_NAVIGATION_TYPE_LINK_CLICKED) {
Packit 116408
                if (! priv->snippet_loaded) {
Packit 116408
                        priv->snippet_loaded = TRUE;
Packit 116408
                        webkit_policy_decision_use (decision);
Packit 116408
                }
Packit 116408
Packit 116408
                webkit_policy_decision_ignore (decision);
Packit 116408
Packit 116408
                return TRUE;
Packit 116408
        }
Packit 116408
Packit 116408
        request = webkit_navigation_action_get_request (navigation_action);
Packit 116408
        uri = webkit_uri_request_get_uri (request);
Packit 116408
        if (strcmp (uri, "about:blank") == 0) {
Packit 116408
                webkit_policy_decision_use (decision);
Packit 116408
Packit 116408
                return TRUE;
Packit 116408
        }
Packit 116408
Packit 116408
        g_signal_emit (web_view, signals[SIGNAL_OPEN_URI], 0, uri);
Packit 116408
        webkit_policy_decision_ignore (decision);
Packit 116408
Packit 116408
        return TRUE;
Packit 116408
}
Packit 116408
Packit 116408
static gboolean
Packit 116408
assistant_button_press_event (GtkWidget      *widget,
Packit 116408
                              GdkEventButton *event)
Packit 116408
{
Packit 116408
        /* Block webkit's builtin context menu. */
Packit 116408
        if (event->button != 1) {
Packit 116408
                return TRUE;
Packit 116408
        }
Packit 116408
Packit 116408
        return GTK_WIDGET_CLASS (dh_assistant_view_parent_class)->button_press_event (widget, event);
Packit 116408
}
Packit 116408
Packit 116408
static void
Packit 116408
dh_assistant_view_class_init (DhAssistantViewClass* klass)
Packit 116408
{
Packit 116408
        GObjectClass       *object_class = G_OBJECT_CLASS (klass);
Packit 116408
        GtkWidgetClass     *widget_class = GTK_WIDGET_CLASS (klass);
Packit 116408
        WebKitWebViewClass *web_view_class = WEBKIT_WEB_VIEW_CLASS (klass);
Packit 116408
Packit 116408
        object_class->finalize = view_finalize;
Packit 116408
Packit 116408
        widget_class->button_press_event = assistant_button_press_event;
Packit 116408
        web_view_class->decide_policy = assistant_decide_policy;
Packit 116408
Packit 116408
        /**
Packit 116408
         * DhAssistantView::open-uri:
Packit 116408
         * @view: the view on which the signal is emitted
Packit 116408
         * @uri: the uri to open
Packit 116408
         */
Packit 116408
        signals[SIGNAL_OPEN_URI] = g_signal_new ("open-uri",
Packit 116408
                                                 G_TYPE_FROM_CLASS (object_class),
Packit 116408
                                                 0, 0,
Packit 116408
                                                 NULL, NULL,
Packit 116408
                                                 NULL,
Packit 116408
                                                 G_TYPE_NONE, 1,
Packit 116408
                                                 G_TYPE_STRING);
Packit 116408
}
Packit 116408
Packit 116408
static void
Packit 116408
dh_assistant_view_init (DhAssistantView *view)
Packit 116408
{
Packit 116408
}
Packit 116408
Packit 116408
/**
Packit 116408
 * dh_assistant_view_new:
Packit 116408
 *
Packit 116408
 * Returns: (transfer floating): a new #DhAssistantView widget.
Packit 116408
 */
Packit 116408
GtkWidget *
Packit 116408
dh_assistant_view_new (void)
Packit 116408
{
Packit 116408
        return g_object_new (DH_TYPE_ASSISTANT_VIEW, NULL);
Packit 116408
}
Packit 116408
Packit 116408
static const gchar *
Packit 116408
find_in_buffer (const gchar *buffer,
Packit 116408
                const gchar *key,
Packit 116408
                gsize        length,
Packit 116408
                gsize        key_length)
Packit 116408
{
Packit 116408
        gsize m = 0;
Packit 116408
        gsize i = 0;
Packit 116408
Packit 116408
        while (i < length) {
Packit 116408
                if (key[m] == buffer[i]) {
Packit 116408
                        m++;
Packit 116408
                        if (m == key_length) {
Packit 116408
                                return buffer + i - m + 1;
Packit 116408
                        }
Packit 116408
                } else {
Packit 116408
                        m = 0;
Packit 116408
                }
Packit 116408
                i++;
Packit 116408
        }
Packit 116408
Packit 116408
        return NULL;
Packit 116408
}
Packit 116408
Packit 116408
/**
Packit 116408
 * dh_assistant_view_set_link:
Packit 116408
 * @view: a #DhAssistantView.
Packit 116408
 * @link: (nullable): a #DhLink to set or %NULL.
Packit 116408
 *
Packit 116408
 * Open @link in the assistant view, if %NULL the view will be blanked.
Packit 116408
 *
Packit 116408
 * Returns: %TRUE if the requested link is open, %FALSE otherwise.
Packit 116408
 */
Packit 116408
gboolean
Packit 116408
dh_assistant_view_set_link (DhAssistantView *view,
Packit 116408
                            DhLink          *link)
Packit 116408
{
Packit 116408
        DhAssistantViewPrivate *priv;
Packit 116408
        gchar               *uri;
Packit 116408
        const gchar         *anchor;
Packit 116408
        gchar               *filename;
Packit 116408
        GMappedFile         *file;
Packit 116408
        const gchar         *contents;
Packit 116408
        gsize                length;
Packit 116408
        gchar               *key;
Packit 116408
        gsize                key_length;
Packit 116408
        gsize                offset = 0;
Packit 116408
        const gchar         *start;
Packit 116408
        const gchar         *end;
Packit 116408
Packit 116408
        g_return_val_if_fail (DH_IS_ASSISTANT_VIEW (view), FALSE);
Packit 116408
Packit 116408
        priv = dh_assistant_view_get_instance_private (view);
Packit 116408
Packit 116408
        if (priv->link == link) {
Packit 116408
                return TRUE;
Packit 116408
        }
Packit 116408
Packit 116408
        if (priv->link) {
Packit 116408
                dh_link_unref (priv->link);
Packit 116408
                priv->link = NULL;
Packit 116408
        }
Packit 116408
Packit 116408
        if (link) {
Packit 116408
                link = dh_link_ref (link);
Packit 116408
        } else {
Packit 116408
                webkit_web_view_load_uri (WEBKIT_WEB_VIEW (view), "about:blank");
Packit 116408
                return TRUE;
Packit 116408
        }
Packit 116408
Packit 116408
        /* FIXME uri can be NULL. */
Packit 116408
        uri = dh_link_get_uri (link);
Packit 116408
        anchor = strrchr (uri, '#');
Packit 116408
        if (anchor) {
Packit 116408
                filename = g_strndup (uri, anchor - uri);
Packit 116408
                anchor++;
Packit 116408
        } else {
Packit 116408
                g_free (uri);
Packit 116408
                return FALSE;
Packit 116408
        }
Packit 116408
Packit 116408
        if (g_str_has_prefix (filename, "file://"))
Packit 116408
            offset = 7;
Packit 116408
Packit 116408
        file = g_mapped_file_new (filename + offset, FALSE, NULL);
Packit 116408
        if (!file) {
Packit 116408
                g_free (filename);
Packit 116408
                g_free (uri);
Packit 116408
                return FALSE;
Packit 116408
        }
Packit 116408
Packit 116408
        contents = g_mapped_file_get_contents (file);
Packit 116408
        length = g_mapped_file_get_length (file);
Packit 116408
Packit 116408
        key = g_strdup_printf ("
Packit 116408
        g_free (uri);
Packit 116408
        key_length = strlen (key);
Packit 116408
Packit 116408
        start = find_in_buffer (contents, key, length, key_length);
Packit 116408
        g_free (key);
Packit 116408
Packit 116408
        end = NULL;
Packit 116408
Packit 116408
        if (start) {
Packit 116408
                const gchar *start_key;
Packit 116408
                const gchar *end_key;
Packit 116408
Packit 116408
                length -= start - contents;
Packit 116408
Packit 116408
                start_key = "
";
Packit 116408
Packit 116408
                start = find_in_buffer (start,
Packit 116408
                                        start_key,
Packit 116408
                                        length,
Packit 116408
                                        strlen (start_key));
Packit 116408
Packit 116408
                end_key = "
Packit 116408
Packit 116408
                if (start) {
Packit 116408
                        end = find_in_buffer (start, end_key,
Packit 116408
                                              length - strlen (start_key),
Packit 116408
                                              strlen (end_key));
Packit 116408
                        if (!end) {
Packit 116408
                                end_key = "
Packit 116408
                                end = find_in_buffer (start, end_key,
Packit 116408
                                                      length - strlen (start_key),
Packit 116408
                                                      strlen (end_key));
Packit 116408
                        }
Packit 116408
                }
Packit 116408
        }
Packit 116408
Packit 116408
        if (start && end) {
Packit 116408
                gchar       *buf;
Packit 116408
                gboolean     break_line;
Packit 116408
                const gchar *function;
Packit 116408
                gchar       *stylesheet;
Packit 116408
                gchar       *stylesheet_uri;
Packit 116408
                gchar       *stylesheet_html = NULL;
Packit 116408
                gchar       *javascript;
Packit 116408
                gchar       *javascript_uri;
Packit 116408
                gchar       *javascript_html = NULL;
Packit 116408
                gchar       *html;
Packit 116408
Packit 116408
                buf = g_strndup (start, end-start);
Packit 116408
Packit 116408
                /* Try to reformat function signatures so they take less
Packit 116408
                 * space and look nicer. Don't reformat things that don't
Packit 116408
                 * look like functions.
Packit 116408
                 */
Packit 116408
                switch (dh_link_get_link_type (link)) {
Packit 116408
                case DH_LINK_TYPE_FUNCTION:
Packit 116408
                        break_line = TRUE;
Packit 116408
                        function = "onload=\"reformatSignature()\"";
Packit 116408
                        break;
Packit 116408
                case DH_LINK_TYPE_MACRO:
Packit 116408
                        break_line = TRUE;
Packit 116408
                        function = "onload=\"cleanupSignature()\"";
Packit 116408
                        break;
Packit 116408
                case DH_LINK_TYPE_BOOK:
Packit 116408
                case DH_LINK_TYPE_PAGE:
Packit 116408
                case DH_LINK_TYPE_KEYWORD:
Packit 116408
                case DH_LINK_TYPE_STRUCT:
Packit 116408
                case DH_LINK_TYPE_ENUM:
Packit 116408
                case DH_LINK_TYPE_TYPEDEF:
Packit 116408
                case DH_LINK_TYPE_PROPERTY:
Packit 116408
                case DH_LINK_TYPE_SIGNAL:
Packit 116408
                default:
Packit 116408
                        break_line = FALSE;
Packit 116408
                        function = "";
Packit 116408
                        break;
Packit 116408
                }
Packit 116408
Packit 116408
                if (break_line) {
Packit 116408
                        gchar *name;
Packit 116408
Packit 116408
                        name = strstr (buf, dh_link_get_name (link));
Packit 116408
                        if (name && name > buf) {
Packit 116408
                                name[-1] = '\n';
Packit 116408
                        }
Packit 116408
                }
Packit 116408
Packit 116408
                stylesheet = dh_util_build_data_filename ("devhelp",
Packit 116408
                                                          "assistant",
Packit 116408
                                                          "assistant.css",
Packit 116408
                                                          NULL);
Packit 116408
                stylesheet_uri = dh_util_create_data_uri_for_filename (stylesheet,
Packit 116408
                                                                       "text/css");
Packit 116408
                g_free (stylesheet);
Packit 116408
                if (stylesheet_uri)
Packit 116408
                        stylesheet_html = g_strdup_printf ("<link rel=\"stylesheet\" type=\"text/css\" href=\"%s\"/>",
Packit 116408
                                                           stylesheet_uri);
Packit 116408
                g_free (stylesheet_uri);
Packit 116408
Packit 116408
                javascript = dh_util_build_data_filename ("devhelp",
Packit 116408
                                                          "assistant",
Packit 116408
                                                          "assistant.js",
Packit 116408
                                                          NULL);
Packit 116408
                javascript_uri = dh_util_create_data_uri_for_filename (javascript,
Packit 116408
                                                                       "application/javascript");
Packit 116408
                g_free (javascript);
Packit 116408
Packit 116408
                if (javascript_uri)
Packit 116408
                        javascript_html = g_strdup_printf ("<script src=\"%s\"></script>", javascript_uri);
Packit 116408
                g_free (javascript_uri);
Packit 116408
Packit 116408
                html = g_strdup_printf (
Packit 116408
                        "<html>"
Packit 116408
                        "<head>"
Packit 116408
                        "%s"
Packit 116408
                        "%s"
Packit 116408
                        "</head>"
Packit 116408
                        "<body %s>"
Packit 116408
                        "
%s: %s
"
Packit 116408
                        "
%s %s
"
Packit 116408
                        "
%s
"
Packit 116408
                        "</body>"
Packit 116408
                        "</html>",
Packit 116408
                        stylesheet_html,
Packit 116408
                        javascript_html,
Packit 116408
                        function,
Packit 116408
                        dh_link_type_to_string (dh_link_get_link_type (link)),
Packit 116408
                        dh_link_get_uri (link),
Packit 116408
                        dh_link_get_name (link),
Packit 116408
                        _("Book:"),
Packit 116408
                        dh_link_get_book_title (link),
Packit 116408
                        buf);
Packit 116408
                g_free (buf);
Packit 116408
Packit 116408
                g_free (stylesheet_html);
Packit 116408
                g_free (javascript_html);
Packit 116408
Packit 116408
                priv->snippet_loaded = FALSE;
Packit 116408
                webkit_web_view_load_html (
Packit 116408
                        WEBKIT_WEB_VIEW (view),
Packit 116408
                        html,
Packit 116408
                        filename);
Packit 116408
                g_free (html);
Packit 116408
        } else {
Packit 116408
                webkit_web_view_load_uri (WEBKIT_WEB_VIEW (view), "about:blank");
Packit 116408
        }
Packit 116408
Packit 116408
        g_mapped_file_unref (file);
Packit 116408
        g_free (filename);
Packit 116408
Packit 116408
        return TRUE;
Packit 116408
}
Packit 116408
Packit 116408
/**
Packit 116408
 * dh_assistant_view_search:
Packit 116408
 * @view: a #DhAssistantView.
Packit 116408
 * @str: the search query.
Packit 116408
 *
Packit 116408
 * Search for @str in the current assistant view.
Packit 116408
 *
Packit 116408
 * Returns: %TRUE if @str was found, %FALSE otherwise.
Packit 116408
 */
Packit 116408
gboolean
Packit 116408
dh_assistant_view_search (DhAssistantView *view,
Packit 116408
                          const gchar     *str)
Packit 116408
{
Packit 116408
        DhAssistantViewPrivate *priv;
Packit 116408
        DhBookManager       *book_manager;
Packit 116408
        const gchar         *name;
Packit 116408
        DhLink              *link;
Packit 116408
        DhLink              *exact_link;
Packit 116408
        DhLink              *prefix_link;
Packit 116408
        GList               *books;
Packit 116408
Packit 116408
        g_return_val_if_fail (DH_IS_ASSISTANT_VIEW (view), FALSE);
Packit 116408
        g_return_val_if_fail (str, FALSE);
Packit 116408
Packit 116408
        priv = dh_assistant_view_get_instance_private (view);
Packit 116408
Packit 116408
        /* Filter out very short strings. */
Packit 116408
        if (strlen (str) < 4) {
Packit 116408
                return FALSE;
Packit 116408
        }
Packit 116408
Packit 116408
        if (priv->current_search && strcmp (priv->current_search, str) == 0) {
Packit 116408
                return FALSE;
Packit 116408
        }
Packit 116408
        g_free (priv->current_search);
Packit 116408
        priv->current_search = g_strdup (str);
Packit 116408
Packit 116408
        prefix_link = NULL;
Packit 116408
        exact_link = NULL;
Packit 116408
Packit 116408
        book_manager = dh_book_manager_get_singleton ();
Packit 116408
Packit 116408
        for (books = dh_book_manager_get_books (book_manager);
Packit 116408
             !exact_link && books;
Packit 116408
             books = g_list_next (books)) {
Packit 116408
                GList *l;
Packit 116408
Packit 116408
                for (l = dh_book_get_links (DH_BOOK (books->data));
Packit 116408
                     l && exact_link == NULL;
Packit 116408
                     l = l->next) {
Packit 116408
                        DhLinkType type;
Packit 116408
Packit 116408
                        link = l->data;
Packit 116408
Packit 116408
                        type = dh_link_get_link_type (link);
Packit 116408
Packit 116408
                        if (type == DH_LINK_TYPE_BOOK ||
Packit 116408
                            type == DH_LINK_TYPE_PAGE ||
Packit 116408
                            type == DH_LINK_TYPE_KEYWORD) {
Packit 116408
                                continue;
Packit 116408
                        }
Packit 116408
Packit 116408
                        name = dh_link_get_name (link);
Packit 116408
                        if (strcmp (name, str) == 0) {
Packit 116408
                                exact_link = link;
Packit 116408
                        }
Packit 116408
                        else if (g_str_has_prefix (name, str)) {
Packit 116408
                                /* Prefer shorter prefix matches. */
Packit 116408
                                if (!prefix_link) {
Packit 116408
                                        prefix_link = link;
Packit 116408
                                }
Packit 116408
                                else if (strlen (dh_link_get_name (prefix_link)) > strlen (name)) {
Packit 116408
                                        prefix_link = link;
Packit 116408
                                }
Packit 116408
                        }
Packit 116408
                }
Packit 116408
        }
Packit 116408
Packit 116408
        if (exact_link) {
Packit 116408
                /*g_print ("exact hit: '%s' '%s'\n", exact_link->name, str);*/
Packit 116408
                dh_assistant_view_set_link (view, exact_link);
Packit 116408
        }
Packit 116408
        else if (prefix_link) {
Packit 116408
                /*g_print ("prefix hit: '%s' '%s'\n", prefix_link->name, str);*/
Packit 116408
                dh_assistant_view_set_link (view, prefix_link);
Packit 116408
        } else {
Packit 116408
                /*g_print ("no hit\n");*/
Packit 116408
                /*assistant_view_set_link (view, NULL);*/
Packit 116408
                return FALSE;
Packit 116408
        }
Packit 116408
Packit 116408
        return TRUE;
Packit 116408
}