Added basic logic for hints.
authorDaniel Carl <danielcarl@gmx.de>
Sat, 1 Dec 2012 21:50:23 +0000 (22:50 +0100)
committerDaniel Carl <danielcarl@gmx.de>
Sat, 1 Dec 2012 21:50:23 +0000 (22:50 +0100)
doc/config
src/command.c
src/command.h
src/dom.c
src/dom.h
src/hints.c [new file with mode: 0644]
src/hints.h [new file with mode: 0644]
src/main.c
src/main.h

index a7d2fcf..12703c9 100644 (file)
@@ -21,5 +21,8 @@ nmap h scrollleft
 nmap l scrollright
 nmap k scrollup
 nmap j scrolldown
+nmap f hint-link
 cmap <tab> complete
 cmap <shift-tab> complete-back
+hmap <tab> hint-focus-next
+hmap <shift-tab> hint-focus-prev
index e1c7bb0..8939152 100644 (file)
@@ -22,6 +22,7 @@
 #include "keybind.h"
 #include "setting.h"
 #include "completion.h"
+#include "hints.h"
 
 extern const char *inputbox_font[2];
 extern const char *inputbox_fg[2];
@@ -55,13 +56,18 @@ static CommandInfo cmd_list[] = {
     {"nmap",             command_map,         {VP_MODE_NORMAL},                                                             VP_MODE_NORMAL},
     {"imap",             command_map,         {VP_MODE_INSERT},                                                             VP_MODE_NORMAL},
     {"cmap",             command_map,         {VP_MODE_COMMAND},                                                            VP_MODE_NORMAL},
+    {"hmap",             command_map,         {VP_MODE_HINTING},                                                            VP_MODE_NORMAL},
     {"nunmap",           command_unmap,       {VP_MODE_NORMAL},                                                             VP_MODE_NORMAL},
     {"iunmap",           command_unmap,       {VP_MODE_INSERT},                                                             VP_MODE_NORMAL},
     {"cunmap",           command_unmap,       {VP_MODE_COMMAND},                                                            VP_MODE_NORMAL},
+    {"hunmap",           command_unmap,       {VP_MODE_HINTING},                                                            VP_MODE_NORMAL},
     {"set",              command_set,         {0},                                                                          VP_MODE_NORMAL},
     {"complete",         command_complete,    {0},                                                                          VP_MODE_COMMAND | VP_MODE_COMPLETE},
     {"complete-back",    command_complete,    {1},                                                                          VP_MODE_COMMAND | VP_MODE_COMPLETE},
     {"inspect",          command_inspect,     {0},                                                                          VP_MODE_NORMAL},
+    {"hint-link",        command_hints,       {0},                                                                          VP_MODE_HINTING},
+    {"hint-focus-next",  command_hints_focus, {0},                                                                          VP_MODE_HINTING},
+    {"hint-focus-prev",  command_hints_focus, {1},                                                                          VP_MODE_HINTING},
 };
 
 void command_init(void)
@@ -273,3 +279,17 @@ gboolean command_inspect(const Arg* arg)
         return FALSE;
     }
 }
+
+gboolean command_hints(const Arg* arg)
+{
+    hints_create(NULL, arg->i);
+
+    return TRUE;
+}
+
+gboolean command_hints_focus(const Arg* arg)
+{
+    hints_focus_next(arg->i ? TRUE : FALSE);
+
+    return TRUE;
+}
index 8d8d569..ebefee2 100644 (file)
@@ -46,5 +46,7 @@ gboolean command_unmap(const Arg* arg);
 gboolean command_set(const Arg* arg);
 gboolean command_complete(const Arg* arg);
 gboolean command_inspect(const Arg* arg);
+gboolean command_hints(const Arg* arg);
+gboolean command_hints_focus(const Arg* arg);
 
 #endif /* end of include guard: COMMAND_H */
index ef04e42..6de41a4 100644 (file)
--- a/src/dom.c
+++ b/src/dom.c
@@ -41,6 +41,22 @@ void dom_check_auto_insert(void)
     }
 }
 
+void dom_element_set_style(WebKitDOMElement* element, const gchar* style)
+{
+    WebKitDOMCSSStyleDeclaration* css = webkit_dom_element_get_style(element);
+    if (css) {
+        webkit_dom_css_style_declaration_set_css_text(css, style, NULL);
+    }
+}
+
+void dom_element_style_set_property(WebKitDOMElement* element, const gchar* property, const gchar* style)
+{
+    WebKitDOMCSSStyleDeclaration* css = webkit_dom_element_get_style(element);
+    if (css) {
+        webkit_dom_css_style_declaration_set_property(css, property, style, "", NULL);
+    }
+}
+
 static gboolean dom_auto_insert(WebKitDOMElement* element)
 {
     if (dom_is_editable(element)) {
@@ -101,4 +117,3 @@ static WebKitDOMElement* dom_get_active_element(WebKitDOMDocument* doc)
     }
     return active;
 }
-
index e58c4ee..3b3e539 100644 (file)
--- a/src/dom.h
+++ b/src/dom.h
 #ifndef DOM_H
 #define DOM_H
 
+#include <webkit/webkit.h>
+
 void dom_check_auto_insert(void);
+void dom_element_set_style(WebKitDOMElement* element, const gchar* style);
+void dom_element_style_set_property(WebKitDOMElement* element, const gchar* property, const gchar* style);
 
 #endif /* end of include guard: DOM_H */
diff --git a/src/hints.c b/src/hints.c
new file mode 100644 (file)
index 0000000..0b9f607
--- /dev/null
@@ -0,0 +1,290 @@
+#include "hints.h"
+#include "dom.h"
+
+#define MAX_HINTS 200
+#define HINT_CONTAINER_ID "__hint_container"
+#define HINT_CLASS "__hint"
+
+#define ELEM_BACKGROUND "#ff0"
+#define ELEM_BACKGROUND_FOCUS "#8f0"
+#define ELEM_COLOR "#000"
+
+#define HINT_CONTAINER_STYLE "line-height:1em;"
+#define HINT_STYLE "z-index:100000;"\
+    "position:absolute;"\
+    "font-family:monospace;"\
+    "font-size:10px;"\
+    "font-weight:bold;"\
+    "color:#000;"\
+    "background-color:#fff;"\
+    "margin:0;"\
+    "padding:0px 1px;"\
+    "border:1px solid #444;"\
+    "opacity:0.7;"\
+    "left:%lipx;top:%lipx;"
+
+typedef struct {
+    gulong num;
+    WebKitDOMElement* elem;                 /* hinted element */
+    gchar*            elemColor;            /* element color */
+    gchar*            elemBackgroundColor;  /* element background color */
+    WebKitDOMElement* hint;                 /* numbered hint element */
+} Hint;
+
+static void hints_create_for_window(const gchar* input, WebKitDOMDOMWindow* win, gulong offsetX, gulong offsetY);
+static void hints_focus(const gulong num);
+static Hint* hints_get_hint_by_number(const gulong num);
+static GList* hints_get_hint_list_by_number(const gulong num);
+static gchar* hints_get_xpath(const gchar* input);
+
+static GList* hints = NULL;
+static gulong currentFocusNum = 0;
+static HintMode currentMode = HINTS_MODE_LINK;
+static WebKitDOMElement* hintContainer = NULL;
+
+void hints_clear(void)
+{
+    /* free the list of hints */
+    if (hints) {
+        GList* link;
+        for (link = hints; link != NULL; link = link->next) {
+            Hint* hint = (Hint*)link->data;
+
+            /* reset the previous color of the hinted elements */
+            dom_element_style_set_property(hint->elem, "color", hint->elemColor);
+            dom_element_style_set_property(hint->elem, "background-color", hint->elemBackgroundColor);
+        }
+
+        g_list_free(hints);
+        hints = NULL;
+    }
+    /* remove the hint container */
+    if (hintContainer) {
+        WebKitDOMNode* parent = webkit_dom_node_get_parent_node(WEBKIT_DOM_NODE(hintContainer));
+        webkit_dom_node_remove_child(parent, WEBKIT_DOM_NODE(hintContainer), NULL);
+        hintContainer = NULL;
+    }
+}
+
+void hints_create(const gchar* input, HintMode mode)
+{
+    hints_clear();
+
+    currentMode = mode;
+
+    WebKitWebView* webview = WEBKIT_WEB_VIEW(vp.gui.webview);
+    WebKitDOMDocument* doc = webkit_web_view_get_dom_document(webview);
+    if (!doc) {
+        return;
+    }
+    WebKitDOMDOMWindow* win = webkit_dom_document_get_default_view(doc);
+    hints_create_for_window(input, win, 0, 0);
+
+    hints_clear_focus();
+    hints_focus(1);
+
+    if (g_list_length(hints) == 1) {
+        /* only one element hinted - we can fire it */
+        hints_fire(1);
+    }
+}
+
+void hints_update(const gulong num)
+{
+
+}
+
+void hints_fire(const gulong num)
+{
+    PRINT_DEBUG("fire hint %li", num);
+}
+
+void hints_clear_focus(void)
+{
+
+}
+
+void hints_focus_next(const gboolean back)
+{
+    Hint* hint  = NULL;
+    GList* list = hints_get_hint_list_by_number(currentFocusNum);
+
+    list = back ? g_list_previous(list) : g_list_next(list);
+    if (list != NULL) {
+        hint = (Hint*)list->data;
+    } else {
+        /* if we reached begin or end start on the opposite side */
+        list = back ? g_list_last(hints) : g_list_first(hints);
+        hint = (Hint*)list->data;
+    }
+    hints_focus(hint->num);
+}
+
+static void hints_create_for_window(const gchar* input, WebKitDOMDOMWindow* win, gulong offsetX, gulong offsetY)
+{
+    WebKitDOMDocument* doc = webkit_dom_dom_window_get_document(win);
+    WebKitDOMNodeList* list = webkit_dom_document_get_elements_by_tag_name(doc, "body");
+    if (!list) {
+        return;
+    }
+    WebKitDOMNode* body = webkit_dom_node_list_item(list, 0);
+
+    WebKitDOMXPathNSResolver* ns_resolver = webkit_dom_document_create_ns_resolver(doc, body);
+    if (!ns_resolver) {
+        return;
+    }
+
+    gchar* xpath = hints_get_xpath(input);
+    WebKitDOMXPathResult* result = webkit_dom_document_evaluate(
+        doc, xpath, WEBKIT_DOM_NODE(doc), ns_resolver, 7, NULL, NULL
+    );
+    g_free(xpath);
+
+    if (!result) {
+        return;
+    }
+
+    /* create the hint container element */
+    hintContainer = webkit_dom_document_create_element(doc, "p", NULL);
+    dom_element_set_style(hintContainer, HINT_CONTAINER_STYLE);
+
+    webkit_dom_node_append_child(body, WEBKIT_DOM_NODE(hintContainer), NULL);
+    webkit_dom_html_element_set_id(WEBKIT_DOM_HTML_ELEMENT(hintContainer), HINT_CONTAINER_ID);
+
+    gulong snapshot_length = webkit_dom_xpath_result_get_snapshot_length(result, NULL);
+
+    for (gulong i = 0; i < snapshot_length; i++) {
+        WebKitDOMNode* node = webkit_dom_xpath_result_snapshot_item(result, i, NULL);
+        WebKitDOMCSSStyleDeclaration* css_style = webkit_dom_element_get_style(WEBKIT_DOM_ELEMENT(node));
+        gchar* visibility = webkit_dom_css_style_declaration_get_property_value(css_style, "visibility");
+        if (visibility != NULL && g_ascii_strcasecmp(visibility, "hidden") == 0) {
+            continue;
+        }
+        gchar* display = webkit_dom_css_style_declaration_get_property_value(css_style, "display");
+        if (display != NULL && g_ascii_strcasecmp(display, "none") == 0) {
+            continue;
+        }
+
+        /* create the hint element */
+        WebKitDOMElement* hint = webkit_dom_document_create_element(doc, "span", NULL);
+
+        Hint* newHint = g_new0(Hint, 1);
+        newHint->num  = i + 1;
+        newHint->elem = WEBKIT_DOM_ELEMENT(node);
+        newHint->elemColor = webkit_dom_css_style_declaration_get_property_value(css_style, "color");
+        newHint->elemBackgroundColor = webkit_dom_css_style_declaration_get_property_value(css_style, "background-color");
+        newHint->hint = hint;
+        hints = g_list_append(hints, newHint);
+
+        /* change the style of the hinted element */
+        dom_element_style_set_property(newHint->elem, "background-color", ELEM_BACKGROUND);
+        dom_element_style_set_property(newHint->elem, "color", ELEM_COLOR);
+
+        /*
+        WebKitDOMDOMWindow* win = webkit_dom_document_get_default_view(doc);
+        gulong win_height = webkit_dom_dom_window_get_inner_height(win);
+        gulong win_width  = webkit_dom_dom_window_get_inner_width(win);
+        */
+
+        gchar* num = g_strdup_printf("%li", i + 1);
+        webkit_dom_html_element_set_inner_text(WEBKIT_DOM_HTML_ELEMENT(hint), num, NULL);
+        webkit_dom_html_element_set_class_name(WEBKIT_DOM_HTML_ELEMENT(hint), HINT_CLASS);
+        g_free(num);
+
+        /* calculate the hint position */
+        glong left = webkit_dom_element_get_offset_left(WEBKIT_DOM_ELEMENT(node));
+        glong top  = webkit_dom_element_get_offset_top(WEBKIT_DOM_ELEMENT(node));
+
+        left -= 3;
+        top  -= 3;
+        gchar* hint_style = g_strdup_printf(HINT_STYLE, left, top);
+        dom_element_set_style(hint, hint_style);
+        g_free(hint_style);
+
+        webkit_dom_node_append_child(WEBKIT_DOM_NODE(hintContainer), WEBKIT_DOM_NODE(hint), NULL);
+
+        if (g_list_length(hints) >= MAX_HINTS) {
+            break;
+        }
+    }
+}
+
+static void hints_focus(const gulong num)
+{
+    Hint* hint = hints_get_hint_by_number(currentFocusNum);
+    if (hint) {
+        /* reset previous focused element */
+        dom_element_style_set_property(hint->elem, "background-color", ELEM_BACKGROUND);
+    }
+
+    hint = hints_get_hint_by_number(num);
+    if (hint) {
+        /* mark new hint as focused */
+        dom_element_style_set_property(hint->elem, "background-color", ELEM_BACKGROUND_FOCUS);
+    }
+
+    currentFocusNum = num;
+}
+
+static Hint* hints_get_hint_by_number(const gulong num)
+{
+    GList* list = hints_get_hint_list_by_number(num);
+    if (list) {
+        return (Hint*)list->data;
+    }
+
+    return NULL;
+}
+
+static GList* hints_get_hint_list_by_number(const gulong num)
+{
+    GList* link;
+    for (link = hints; link != NULL; link = link->next) {
+        Hint* hint = (Hint*)link->data;
+        /* TODO check if it would be faster to use the sorting of the numbers
+         * in the list to get the items */
+        if (hint->num == num) {
+            return link;
+        }
+    }
+
+    return NULL;
+}
+
+/**
+ * Retreives the xpatch epression according to current hinting mode and filter
+ * input text.
+ *
+ * The returned string have to be freed.
+ */
+static gchar* hints_get_xpath(const gchar* input)
+{
+    gchar* xpath = NULL;
+
+    switch (currentMode) {
+        case HINTS_MODE_LINK:
+            if (input == NULL) {
+                xpath = g_strdup(
+                    "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @ role='link'] | "
+                    "//a[@href]"
+                );
+            } else {
+                xpath = g_strdup_printf(
+                    "//*[(@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link') and contains(., '%s')] | "
+                    "//a[@href and contains(., '%s')]",
+                    input, input
+                );
+            }
+            break;
+
+        case HINTS_MODE_IMAGE:
+            if (input == NULL) {
+                xpath = g_strdup("//img[@src]");
+            } else {
+                xpath = g_strdup_printf("//img[@src and contains(., '%s')]", input);
+            }
+            break;
+    }
+
+    return xpath;
+}
diff --git a/src/hints.h b/src/hints.h
new file mode 100644 (file)
index 0000000..f291876
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef HINTS_H
+#define HINTS_H
+
+#include "main.h"
+
+typedef enum {
+    HINTS_MODE_LINK,
+    HINTS_MODE_IMAGE,
+} HintMode;
+
+void hints_create(const gchar* input, HintMode mode);
+void hints_update(const gulong num);
+void hints_fire(const gulong num);
+void hints_clear(void);
+void hints_clear_focus(void);
+void hints_focus_next(const gboolean back);
+
+#endif /* end of include guard: HINTS_H */
index 63442aa..055c8cd 100644 (file)
@@ -25,6 +25,7 @@
 #include "config.h"
 #include "completion.h"
 #include "dom.h"
+#include "hints.h"
 
 /* variables */
 VpCore vp;
@@ -291,11 +292,12 @@ gboolean vp_set_mode(Mode mode, gboolean clean)
     if (vp.state.mode & VP_MODE_COMPLETE) {
         completion_clean();
     }
-    vp.state.mode = mode;
-    vp.state.modkey = vp.state.count  = 0;
-
     switch (CLEAN_MODE(mode)) {
         case VP_MODE_NORMAL:
+            /* if previous mode was hinting clear the hints */
+            if (GET_CLEAN_MODE() == VP_MODE_HINTING) {
+                hints_clear();
+            }
             gtk_widget_grab_focus(GTK_WIDGET(vp.gui.webview));
             break;
 
@@ -313,6 +315,9 @@ gboolean vp_set_mode(Mode mode, gboolean clean)
             break;
     }
 
+    vp.state.mode = mode;
+    vp.state.modkey = vp.state.count  = 0;
+
     /* echo message if given */
     if (clean) {
         vp_echo(VP_MSG_NORMAL, FALSE, "");
index 8e507d2..f64b4e9 100644 (file)
@@ -74,6 +74,7 @@ typedef enum _vp_mode {
     VP_MODE_INSERT        = 1<<3,
     VP_MODE_SEARCH        = 1<<4,
     VP_MODE_COMPLETE      = 1<<5,
+    VP_MODE_HINTING       = 1<<6,
 } Mode;
 
 enum {