Replaced hinting by javascript hinting.
authorDaniel Carl <danielcarl@gmx.de>
Wed, 27 Feb 2013 22:53:13 +0000 (23:53 +0100)
committerDaniel Carl <danielcarl@gmx.de>
Fri, 1 Mar 2013 13:31:01 +0000 (14:31 +0100)
The previous approach to use the dom api to generate the hints was much slower
than the javascript solution. I think the javascript way is also a little bit
more flexible and easier to implement. But now we have to concern about data
sharing between c-layer an the javascript.

Makefile
config.mk
src/dom.c
src/dom.h
src/hint.js [new file with mode: 0644]
src/hints.c
src/hints.h
src/js2h.sh [new file with mode: 0755]
src/main.c
src/main.h
src/setting.c

index 4f032b7..b088b3c 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -12,6 +12,11 @@ options:
        @echo "CPPFLAGS  = $(CPPFLAGS)"
        @echo "LDFLAGS   = $(LDFLAGS)"
 
+src/hints.o: src/hint.js.h
+src/hint.js.h: src/hint.js
+       @echo "minify $<"
+       @cat $< | src/js2h.sh > $@
+
 $(TARGET): $(OBJ)
        @echo "$(CC) $@"
        @$(CC) $(OBJ) -o $(TARGET) $(LDFLAGS)
@@ -44,7 +49,7 @@ uninstall:
 
 clean:
        @$(MAKE) -C doc clean
-       $(RM) $(OBJ) $(DOBJ) $(TARGET) $(DTARGET)
+       $(RM) $(OBJ) $(DOBJ) $(TARGET) $(DTARGET) src/hint.js.h
 
 dist: distclean
        @echo "Creating tarball."
index a3ad6a4..78b6638 100644 (file)
--- a/config.mk
+++ b/config.mk
@@ -31,6 +31,7 @@ CFLAGS += -std=c99
 CFLAGS += -pedantic
 CFLAGS += -Wmissing-declarations
 CFLAGS += -Wmissing-parameter-type
+CFLAGS += -Wno-overlength-strings
 #CFLAGS += -Wstrict-prototypes
 
 LDFLAGS += $(shell pkg-config --libs $(LIBS)) -lX11 -lXext
index 9ebba04..8bccebd 100644 (file)
--- a/src/dom.c
+++ b/src/dom.c
@@ -40,76 +40,6 @@ void dom_check_auto_insert(void)
     }
 }
 
-void dom_element_set_style(Element* element, const char* format, ...)
-{
-    va_list args;
-    va_start(args, format);
-    char* value = g_strdup_vprintf(format, args);
-    CssDeclaration* css = webkit_dom_element_get_style(element);
-    if (css) {
-        webkit_dom_css_style_declaration_set_css_text(css, value, NULL);
-    }
-    g_free(value);
-}
-
-void dom_element_style_set_property(Element* element, const char* property, const char* style)
-{
-    CssDeclaration* css = webkit_dom_element_get_style(element);
-    if (css) {
-        webkit_dom_css_style_declaration_set_property(css, property, style, "", NULL);
-    }
-}
-
-gboolean dom_element_is_visible(Window* win, Element* element)
-{
-    if (webkit_dom_html_element_get_hidden(WEBKIT_DOM_HTML_ELEMENT(element))) {
-        return FALSE;
-    }
-
-    CssDeclaration* style = webkit_dom_dom_window_get_computed_style(win, element, "");
-    if (style_compare_property(style, "display", "none")) {
-        return FALSE;
-    }
-    if (style_compare_property(style, "visibility", "hidde")) {
-        return FALSE;
-    }
-
-    return TRUE;
-}
-
-DomBoundingRect dom_elemen_get_bounding_rect(Element* element)
-{
-    DomBoundingRect rect;
-    rect.left = 0;
-    rect.top  = 0;
-    for (Element* e = element; e; e = webkit_dom_element_get_offset_parent(e)) {
-        rect.left += webkit_dom_element_get_offset_left(e);
-        rect.top  += webkit_dom_element_get_offset_top(e);
-    }
-    rect.right  = rect.left + webkit_dom_element_get_offset_width(element);
-    rect.bottom = rect.top + webkit_dom_element_get_offset_height(element);
-
-    return rect;
-}
-
-void dom_dispatch_mouse_event(Document* doc, Element* element, char* type, gushort button)
-{
-    Event* event = webkit_dom_document_create_event(doc, "MouseEvents", NULL);
-    webkit_dom_mouse_event_init_mouse_event(
-        WEBKIT_DOM_MOUSE_EVENT(event),
-        type,
-        TRUE,
-        TRUE,
-        webkit_dom_document_get_default_view(doc),
-        1, 1, 1, 0, 0,
-        FALSE, FALSE, FALSE, FALSE,
-        button,
-        WEBKIT_DOM_EVENT_TARGET(element)
-    );
-
-    webkit_dom_node_dispatch_event(WEBKIT_DOM_NODE(element), event, NULL);
-}
-
 /**
  * Indicates if the given dom element is an editable element like text input,
  * password or textarea.
@@ -136,21 +66,6 @@ gboolean dom_is_editable(Element* element)
     return FALSE;
 }
 
-/**
- * Retrieves the src or href attribute of the given element.
- */
-char* dom_element_get_source(Element* elem)
-{
-    char* url = NULL;
-
-    url = webkit_dom_html_anchor_element_get_href(WEBKIT_DOM_HTML_ANCHOR_ELEMENT(elem));
-    if (!url) {
-        url = webkit_dom_html_image_element_get_src(WEBKIT_DOM_HTML_IMAGE_ELEMENT(elem));
-    }
-
-    return url;
-}
-
 static gboolean dom_auto_insert(Element* element)
 {
     if (dom_is_editable(element)) {
index e08f462..88897f0 100644 (file)
--- a/src/dom.h
+++ b/src/dom.h
@@ -29,7 +29,6 @@
 #define Window         WebKitDOMDOMWindow
 #define NodeList       WebKitDOMNodeList
 #define Node           WebKitDOMNode
-#define Style          WebKitDOMCSSStyleDeclaration
 #define HtmlElement    WebKitDOMHTMLElement
 #define Element        WebKitDOMElement
 #define CssDeclaration WebKitDOMCSSStyleDeclaration
@@ -49,11 +48,6 @@ typedef struct {
 } DomBoundingRect;
 
 void dom_check_auto_insert(void);
-void dom_element_set_style(Element* element, const char* format, ...);
-void dom_element_style_set_property(Element* element, const char* property, const char* style);
-gboolean dom_element_is_visible(Window* win, Element* element);
-DomBoundingRect dom_elemen_get_bounding_rect(Element* element);
-void dom_dispatch_mouse_event(Document* doc, Element* element, char* type, gushort button);
 gboolean dom_is_editable(Element* element);
 char* dom_element_get_source(Element* elem);
 
diff --git a/src/hint.js b/src/hint.js
new file mode 100644 (file)
index 0000000..1738f78
--- /dev/null
@@ -0,0 +1,386 @@
+VimpHints = function Hints(bg, bgf, fg, style) {
+    var config = {
+        maxHints: 200,
+        hintCss: style,
+        hintClass: "__hint",
+        hintClassFocus: "__hint_container",
+        eBg: bg,
+        eBgf: bgf,
+        eFg: fg,
+    };
+
+    var hCont;
+    var curFocusNum = 1;
+    var hints = [];
+    var mode;
+
+    this.create = function(inputText, hintMode)
+    {
+        if (hintMode) {
+            mode = hintMode;
+        }
+
+        var topwin = window;
+        var top_height = topwin.innerHeight;
+        var top_width = topwin.innerWidth;
+        var xpath_expr;
+
+        var hintCount = 0;
+        this.clear();
+
+        function _helper (win, offsetX, offsetY) {
+            var doc = win.document;
+
+            var win_height = win.height;
+            var win_width = win.width;
+
+            /* Bounds */
+            var minX = offsetX < 0 ? -offsetX : 0;
+            var minY = offsetY < 0 ? -offsetY : 0;
+            var maxX = offsetX + win_width > top_width ? top_width - offsetX : top_width;
+            var maxY = offsetY + win_height > top_height ? top_height - offsetY : top_height;
+
+            var scrollX = win.scrollX;
+            var scrollY = win.scrollY;
+
+            hCont = doc.createElement("div");
+            hCont.id = "hint_container";
+
+            xpath_expr = _getXpathXpression(inputText);
+
+            var res = doc.evaluate(xpath_expr, doc,
+                function (p) {
+                    return "http://www.w3.org/1999/xhtml";
+                }, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+
+            /* generate basic hint element which will be cloned and updated later */
+            var hintSpan = doc.createElement("span");
+            hintSpan.setAttribute("class", config.hintClass);
+            hintSpan.style.cssText = config.hintCss;
+
+            /* due to the different XPath result type, we will need two counter variables */
+            var rect, elem, text, node, show_text;
+            for (var i = 0; i < res.snapshotLength; i++) {
+                if (hintCount >= config.maxHints) {
+                    break;
+                }
+
+                elem = res.snapshotItem(i);
+                rect = elem.getBoundingClientRect();
+                if (!rect || rect.left > maxX || rect.right < minX || rect.top > maxY || rect.bottom < minY) {
+                    continue;
+                }
+
+                var style = topwin.getComputedStyle(elem, "");
+                if (style.display == "none" || style.visibility != "visible") {
+                    continue;
+                }
+
+                var leftpos = Math.max((rect.left + scrollX), scrollX);
+                var toppos = Math.max((rect.top + scrollY), scrollY);
+
+                /* making this block DOM compliant */
+                var hint = hintSpan.cloneNode(false);
+                hint.style.left = leftpos - 3 + "px";
+                hint.style.top =  toppos - 3 + "px";
+                text = doc.createTextNode(hintCount + 1);
+                hint.appendChild(text);
+
+                hCont.appendChild(hint);
+                hintCount++;
+                hints.push({
+                    elem:       elem,
+                    number:     hintCount,
+                    span:       hint,
+                    background: elem.style.background,
+                    foreground: elem.style.color}
+                );
+
+                /* make the link black to ensure it's readable */
+                elem.style.color = config.eFg;
+                elem.style.background = config.eBg;
+            }
+
+            doc.documentElement.appendChild(hCont);
+
+            /* recurse into any iframe or frame element */
+            var frameTags = ["frame","iframe"];
+            for (var f = 0; f < frameTags.length; ++f) {
+                var frames = doc.getElementsByTagName(frameTags[f]);
+                for (var i = 0, nframes = frames.length; i < nframes; ++i) {
+                    elem = frames[i];
+                    rect = elem.getBoundingClientRect();
+                    if (!elem.contentWindow || !rect || rect.left > maxX || rect.right < minX || rect.top > maxY || rect.bottom < minY)
+                        continue;
+                    _helper(elem.contentWindow, offsetX + rect.left, offsetY + rect.top);
+                }
+            }
+        }
+
+        _helper(topwin, 0, 0);
+
+        _focus(1);
+        if (hintCount == 1) {
+            /* just one hinted element - might as well follow it */
+            return this.fire(1);
+        }
+    };
+
+    /* set focus to next avaiable hint */
+    this.focusNext = function()
+    {
+        var index = _getHintIdByNumber(curFocusNum);
+
+        if (typeof(hints[index + 1]) != "undefined") {
+            _focus(hints[index + 1].number);
+        } else {
+            _focus(hints[0].number);
+        }
+    };
+
+    /* set focus to previous avaiable hint */
+    this.focusPrev = function()
+    {
+        var index = _getHintIdByNumber(curFocusNum);
+        if (index != 0 && typeof(hints[index - 1].number) != "undefined") {
+            _focus(hints[index - 1].number);
+        } else {
+            _focus(hints[hints.length - 1].number);
+        }
+    };
+
+    /* filters hints matching given number */
+    this.update = function(n)
+    {
+        if (n == 0) {
+            return this.create();
+        }
+        /* remove none matching hints */
+        var remove = [];
+        for (var i = 0; i < hints.length; ++i) {
+            var hint = hints[i];
+            if (0 != hint.number.toString().indexOf(n.toString())) {
+                remove.push(hint.number);
+            }
+        }
+
+        for (var i = 0; i < remove.length; ++i) {
+            _removeHint(remove[i]);
+        }
+
+        if (hints.length === 1) {
+            return this.fire(hints[0].number);
+        } else {
+            return _focus(n);
+        }
+    };
+
+    /* remove all hints and set previous style to them */
+    this.clear = function()
+    {
+        if (hints.length == 0) {
+            return;
+        }
+        for (var i = 0; i < hints.length; ++i) {
+            var hint = hints[i];
+            if (typeof(hint.elem) != "undefined") {
+                hint.elem.style.background = hint.background;
+                hint.elem.style.color = hint.foreground;
+                hint.span.parentNode.removeChild(hint.span);
+            }
+        }
+        hints = [];
+        hCont.parentNode.removeChild(hCont);
+        window.onkeyup = null;
+    };
+
+    /* fires the modeevent on hint with given number */
+    this.fire = function(n)
+    {
+        var doc, result;
+        if (!n) {
+            var n = curFocusNum;
+        }
+        var hint = _getHintByNumber(n);
+        if (typeof(hint.elem) == "undefined") {
+            return "DONE:";
+        }
+
+        var el = hint.elem;
+        var tag = el.nodeName.toLowerCase();
+
+        this.clear();
+
+        if (tag == "iframe" || tag == "frame" || tag == "textarea" || tag == "input" && (el.type == "text" || el.type == "password" || el.type == "checkbox" || el.type == "radio") || tag == "select") {
+            el.focus();
+            if (tag == "input" || tag == "textarea") {
+                return "INSERT:"
+            }
+            return "DONE:";
+        }
+
+        result = "DONE:";
+        switch (mode) {
+            case "f": _open(el); break;
+            case "F": _openNewWindow(el); break;
+            case "i": _open(el); break;
+            case "I": _openNewWindow(el); break;
+            default:  result = "DATA:" + _getElemtSource(el);
+        }
+
+        return result;
+    };
+
+    /* set focus on hint with given number */
+    function _focus(n)
+    {
+        /* reset previous focused hint */
+        var hint = _getHintByNumber(curFocusNum);
+        if (hint !== null) {
+            hint.elem.className = hint.elem.className.replace(config.hintClassFocus, config.hintClass);
+            hint.elem.style.background = config.eBg;
+            _mouseEvent(hint.elem, "mouseout");
+        }
+
+        curFocusNum = n;
+
+        /* mark new hint as focused */
+        var hint = _getHintByNumber(curFocusNum);
+        if (hint !== null) {
+            hint.elem.className = hint.elem.className.replace(config.hintClass, config.hintClassFocus);
+            hint.elem.style.background = config.eBgf;
+            _mouseEvent(hint.elem, "mouseover");
+        }
+    }
+
+    /* retrieves text content fro given element */
+    function _getTextFromElement(el)
+    {
+        if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
+            text = el.value;
+        } else if (el instanceof HTMLSelectElement) {
+            if (el.selectedIndex >= 0) {
+                text = el.item(el.selectedIndex).text;
+            } else{
+                text = "";
+            }
+        } else {
+            text = el.textContent;
+        }
+        return text.toLowerCase();;
+    }
+
+    /* retrieves the hint for given hint number */
+    function _getHintByNumber(n)
+    {
+        var index = _getHintIdByNumber(n);
+        if (index !== null) {
+            return hints[index];
+        }
+        return null;
+    }
+
+    /* retrieves the id of hint with given number */
+    function _getHintIdByNumber(n)
+    {
+        for (var i = 0; i < hints.length; ++i) {
+            var hint = hints[i];
+            if (hint.number === n) {
+                return i;
+            }
+        }
+        return null;
+    }
+
+    /* removes hint with given number from hints array */
+    function _removeHint(n)
+    {
+        var index = _getHintIdByNumber(n);
+        if (index === null) {
+            return;
+        }
+        var hint = hints[index];
+        if (hint.number === n) {
+            hint.elem.style.background = hint.background;
+            hint.elem.style.color = hint.foreground;
+            hint.span.parentNode.removeChild(hint.span);
+
+            /* remove hints from all hints */
+            hints.splice(index, 1);
+        }
+    }
+
+    /* opens given element */
+    function _open(elem)
+    {
+        if (elem.target == "_blank") {
+            elem.removeAttribute("target");
+        }
+        _mouseEvent(elem, "moudedown");
+        _mouseEvent(elem, "click");
+    }
+
+    /* opens given element into new window */
+    function _openNewWindow(elem)
+    {
+        var oldTarget = elem.target;
+
+        /* set target to open in new window */
+        elem.target = "_blank";
+        _mouseEvent(elem, "moudedown");
+        _mouseEvent(elem, "click");
+        elem.target = oldTarget;
+    }
+
+    function _mouseEvent(elem, name)
+    {
+        doc = elem.ownerDocument;
+        view = elem.contentWindow;
+
+        var evObj = doc.createEvent("MouseEvents");
+        evObj.initMouseEvent(name, true, true, view, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+        elem.dispatchEvent(evObj);
+    }
+
+    /* retrieves the url of given element */
+    function _getElemtSource(elem)
+    {
+        var url = elem.href || elem.src;
+        return url;
+    }
+
+    /* retrieves the xpath expression according to mode */
+    function _getXpathXpression(text)
+    {
+        var expr;
+        if (typeof(text) == "undefined") {
+            text = "";
+        }
+        switch (mode) {
+            case "f":
+            case "F":
+                if (text == "") {
+                    expr = "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href] | //input[not(@type='hidden')] | //a[href] | //area | //textarea | //button | //select";
+                } else {
+                    expr = "//*[(@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href) and contains(., '" + text + "')] | //input[not(@type='hidden') and contains(., '" + text + "')] | //a[@href and contains(., '" + text + "')] | //area[contains(., '" + text + "')] |  //textarea[contains(., '" + text + "')] | //button[contains(@value, '" + text + "')] | //select[contains(., '" + text + "')]";
+                }
+                break;
+            case "i":
+            case "I":
+                if (text == "") {
+                    expr = "//img[@src]";
+                } else {
+                    expr = "//img[@src and contains(., '" + text + "')]";
+                }
+                break;
+            default:
+                if (text == "") {
+                    expr = "//*[@role='link' or @href] | //a[href] | //area | //img[not(ancestor::a)]";
+                } else {
+                    expr = "//*[(@role='link' or @href) and contains(., '" + text + "')] | //a[@href and contains(., '" + text + "')] | //area[contains(., '" + text + "')] | //img[not(ancestor::a) and contains(., '" + text + "')]";
+                }
+                break;
+        }
+        return expr;
+    }
+}
index f1df38d..2042694 100644 (file)
 #include "hints.h"
 #include "dom.h"
 #include "command.h"
+#include "hint.js.h"
 
+/* TODO use this in hinting script */
 #define MAX_HINTS 200
 #define HINT_CONTAINER_ID "__hint_container"
 #define HINT_CLASS "__hint"
 
-#define HINT_CONTAINER_STYLE "line-height:1em;"
-#define HINT_STYLE "z-index:100000;position:absolute;left:%lipx;top:%lipx;%s"
-
-typedef struct {
-    gulong num;
-    Element*  elem;                 /* hinted element */
-    char*    elemColor;            /* element color */
-    char*    elemBackgroundColor;  /* element background color */
-    Element*  hint;                 /* numbered hint element */
-    Element*  container;
-} Hint;
-
-static Element* hints_get_hint_container(Document* doc);
-static void hints_create_for_window(const char* input, Window* win, gulong hintCount);
-static void hints_focus(const gulong num);
-static void hints_fire(const gulong num);
-static void hints_click_fired_hint(guint mode, Element* elem);
-static void hints_process_fired_hint(guint mode, const char* uri);
-static Hint* hints_get_hint_by_number(const gulong num);
-static GList* hints_get_hint_list_by_number(const gulong num);
-static char* hints_get_xpath(const char* input);
+static void hints_run_script(char* js);
+static void hints_fire(void);
 static void hints_observe_input(gboolean observe);
 static gboolean hints_changed_callback(GtkEditable *entry, gpointer data);
 static gboolean hints_keypress_callback(WebKitWebView* webview, GdkEventKey* event);
-static gboolean hints_num_has_prefix(gulong num, gulong prefix);
-
 
 void hints_init(void)
 {
-    Hints* hints = &vp.hints;
-
-    hints->list         = NULL;
-    hints->focusNum     = 0;
-    hints->num          = 0;
-    hints->prefixLength = 0;
+    char* value = NULL;
+    char* error = NULL;
+    vp_eval_script(webkit_web_view_get_main_frame(vp.gui.webview), HINTS_JS, &value, &error);
+    g_free(value);
+    g_free(error);
 }
 
 void hints_clear(void)
 {
-    Hints* hints = &vp.hints;
-
-    /* free the list of hints */
-    if (hints->list) {
-        GList* link;
-        for (link = hints->list; 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);
-
-            /* remove the hint element from hint container */
-            Node* parent = webkit_dom_node_get_parent_node(WEBKIT_DOM_NODE(hint->hint));
-            webkit_dom_node_remove_child(parent, WEBKIT_DOM_NODE(hint->hint), NULL);
-            g_free(hint);
-        }
-
-        g_list_free(hints->list);
-
-        /* use hints_init to unset previous data */
-        hints_init();
-    }
-
     hints_observe_input(FALSE);
+    if (GET_CLEAN_MODE() == VP_MODE_HINTING) {
+        char* js = g_strdup("hints.clear();");
+        char* value = NULL;
+        char* error = NULL;
 
-    /* simulate the mouse out of the last focused hint */
-    g_signal_emit_by_name(vp.gui.webview, "hovering-over-link", NULL, NULL);
+        vp_eval_script(webkit_web_view_get_main_frame(vp.gui.webview), js, &value, &error);
+        g_free(value);
+        g_free(error);
+        g_free(js);
+    }
 }
 
 void hints_create(const char* input, guint mode, const guint prefixLength)
 {
-    Hints* hints = &vp.hints;
-    Document* doc;
-
-    hints_clear();
-    hints->mode         = mode;
-    hints->prefixLength = prefixLength;
+    char* js = NULL;
+    char  type;
+    if (GET_CLEAN_MODE() != VP_MODE_HINTING) {
+        Style* style = &core.style;
+        vp.hints.prefixLength = prefixLength;
+        vp.hints.mode         = mode;
+        vp.hints.num          = 0;
+
+        js = g_strdup_printf(
+            "hints = new VimpHints('%s', '%s', '%s', '%s');",
+            style->hint_bg,
+            style->hint_bg_focus,
+            style->hint_fg,
+            style->hint_style
+        );
+        hints_run_script(js);
+        g_free(js);
 
-    doc = webkit_web_view_get_dom_document(WEBKIT_WEB_VIEW(vp.gui.webview));
-    if (!doc) {
-        return;
+        hints_observe_input(TRUE);
     }
 
-    hints_create_for_window(input, webkit_dom_document_get_default_view(doc), 0);
-    hints_focus(1);
-
-    if (g_list_length(hints->list) == 1) {
-        /* only one element hinted - we can fire it */
-        hints_fire(1);
+    /* convert the mode into the type chare used in the hint script */
+    if (mode & HINTS_PROCESS) {
+        type = 'd';
+    } else if (mode & HINTS_TYPE_IMAGE) {
+        type = (HINTS_TARGET_BLANK & mode) ? 'I' : 'i';
     } else {
-        /* add event hanlder for inputbox */
-        hints_observe_input(TRUE);
+        type = (HINTS_TARGET_BLANK & mode) ? 'F' : 'f';
     }
+
+    js = g_strdup_printf("hints.create('%s', '%c');", input ? input : "", type);
+    hints_run_script(js);
+    g_free(js);
 }
 
 void hints_update(const gulong num)
 {
-    Hints* hints = &vp.hints;
-    Hint* hint   = NULL;
-    GList* next  = NULL;
-    GList* link  = hints->list;
-
-    if (num == 0) {
-        /* recreate the hints */
-        hints_create(NULL, hints->mode, hints->prefixLength);
-        return;
-    }
-
-    while (link != NULL) {
-        hint = (Hint*)link->data;
-        if (!hints_num_has_prefix(hint->num, num)) {
-            /* 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);
-
-            /* remove the hint element from hint container */
-            Node* parent = webkit_dom_node_get_parent_node(WEBKIT_DOM_NODE(hint->hint));
-            webkit_dom_node_remove_child(parent, WEBKIT_DOM_NODE(hint->hint), NULL);
-            g_free(hint);
-
-            /* store next element before remove current */
-            next = g_list_next(link);
-            hints->list = g_list_remove_link(hints->list, link);
-            link = next;
-        } else {
-            link = g_list_next(link);
-        }
-    }
-    if (g_list_length(hints->list) == 1) {
-        hints_fire(num);
-    } else {
-        hints_focus(num);
-    }
+    char* js = g_strdup_printf("hints.update(%lu);", num);
+    hints_run_script(js);
+    g_free(js);
 }
 
 void hints_focus_next(const gboolean back)
 {
-    Hints* hints = &vp.hints;
-    Hint* hint   = NULL;
-    GList* list  = hints_get_hint_list_by_number(hints->focusNum);
-
-    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->list) : g_list_first(hints->list);
-        hint = (Hint*)list->data;
-    }
-    hints_focus(hint->num);
+    char* js = g_strdup(back ? "hints.focusPrev()" : "hints.focusNext();");
+    hints_run_script(js);
+    g_free(js);
 }
 
-/**
- * Retrieves an existing or new created hint container for given document.
- */
-static Element* hints_get_hint_container(Document* doc)
+static void hints_run_script(char* js)
 {
-    Element* container;
-
-    /* first try to find a previous container */
-    container = webkit_dom_document_get_element_by_id(doc, HINT_CONTAINER_ID);
+    char* value = NULL;
+    char* error = NULL;
+    int mode = vp.hints.mode;
 
-    if (!container) {
-        /* create the hint container element */
-        container = webkit_dom_document_create_element(doc, "p", NULL);
-        dom_element_set_style(container, HINT_CONTAINER_STYLE);
+    vp_eval_script(webkit_web_view_get_main_frame(vp.gui.webview), js, &value, &error);
+    if (error) {
+        fprintf(stderr, "%s\n", error);
+        g_free(error);
 
-        webkit_dom_html_element_set_id(WEBKIT_DOM_HTML_ELEMENT(container), HINT_CONTAINER_ID);
-    }
+        vp_set_mode(VP_MODE_NORMAL, FALSE);
 
-    return container;
-}
-
-static void hints_create_for_window(const char* input, Window* win, gulong hintCount)
-{
-    Hints* hints       = &vp.hints;
-    Element* container = NULL;
-    NodeList* list     = NULL;
-    Document* doc      = NULL;
-    gulong i, listLength;
-
-    doc = webkit_dom_dom_window_get_document(win);
-    list = webkit_dom_document_get_elements_by_tag_name(doc, "body");
-    if (!list) {
-        return;
-    }
-    Node* body = webkit_dom_node_list_item(list, 0);
-
-    WebKitDOMXPathNSResolver* ns_resolver = webkit_dom_document_create_ns_resolver(doc, body);
-    if (!ns_resolver) {
         return;
     }
-
-    char* 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;
-    }
-
-    /* get the bounds */
-    gulong win_height = webkit_dom_dom_window_get_inner_height(win);
-    gulong win_width  = webkit_dom_dom_window_get_inner_width(win);
-    gulong minY = webkit_dom_dom_window_get_scroll_y(win);
-    gulong minX = webkit_dom_dom_window_get_scroll_x(win);
-    gulong maxX = minX + win_width;
-    gulong maxY = minY + win_height;
-
-    /* create the hint container element */
-    container = hints_get_hint_container(doc);
-    webkit_dom_node_append_child(body, WEBKIT_DOM_NODE(container), NULL);
-
-    listLength = webkit_dom_xpath_result_get_snapshot_length(result, NULL);
-
-    for (i = 0; i < listLength && hintCount < MAX_HINTS; i++) {
-        /* TODO this cast causes a bug if hinting is started after a link was
-         * opened into new window via middle mouse click or right click
-         * context menu */
-        Element* elem = WEBKIT_DOM_ELEMENT(webkit_dom_xpath_result_snapshot_item(result, i, NULL));
-        if (!dom_element_is_visible(win, elem)) {
-            continue;
-        }
-        DomBoundingRect rect = dom_elemen_get_bounding_rect(elem);
-        if (rect.left > maxX || rect.right < minX || rect.top > maxY || rect.bottom < minY) {
-            continue;
-        }
-
-        hintCount++;
-
-        /* create the hint element */
-        Element* hint = webkit_dom_document_create_element(doc, "span", NULL);
-        CssDeclaration* css_style = webkit_dom_element_get_style(elem);
-
-        Hint* newHint = g_new0(Hint, 1);
-        newHint->num                 = hintCount;
-        newHint->elem                = elem;
-        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->list = g_list_append(hints->list, newHint);
-
-        gulong left = rect.left - 3;
-        gulong top  = rect.top - 3;
-        dom_element_set_style(hint, HINT_STYLE, left, top, core.style.hint_style);
-
-        char* num = g_strdup_printf("%li", newHint->num);
-        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);
-
-        /* change the style of the hinted element */
-        dom_element_style_set_property(newHint->elem, "background-color", core.style.hint_bg);
-        dom_element_style_set_property(newHint->elem, "color", core.style.hint_fg);
-
-        webkit_dom_node_append_child(WEBKIT_DOM_NODE(container), WEBKIT_DOM_NODE(hint), NULL);
-    }
-
-    /* call this function for every found frame or iframe too */
-    list       = webkit_dom_document_get_elements_by_tag_name(doc, "IFRAME");
-    listLength = webkit_dom_node_list_get_length(list);
-    for (i = 0; i < listLength; i++) {
-        Node* iframe   = webkit_dom_node_list_item(list, i);
-        Window* window = webkit_dom_html_iframe_element_get_content_window(WEBKIT_DOM_HTML_IFRAME_ELEMENT(iframe));
-        DomBoundingRect rect = dom_elemen_get_bounding_rect(WEBKIT_DOM_ELEMENT(iframe));
-
-        if (rect.left > maxX || rect.right < minX || rect.top > maxY || rect.bottom < minY) {
-            continue;
-        }
-
-        hints_create_for_window(input, window, hintCount);
-    }
-}
-
-static void hints_focus(const gulong num)
-{
-    Document* doc = NULL;
-
-    Hint* hint = hints_get_hint_by_number(vp.hints.focusNum);
-    if (hint) {
-        /* reset previous focused element */
-        dom_element_style_set_property(hint->elem, "background-color", core.style.hint_bg);
-
-        doc = webkit_dom_node_get_owner_document(WEBKIT_DOM_NODE(hint->elem));
-        dom_dispatch_mouse_event(doc, hint->elem, "mouseout", 0);
-    }
-
-    hint = hints_get_hint_by_number(num);
-    if (hint) {
-        /* mark new hint as focused */
-        dom_element_style_set_property(hint->elem, "background-color", core.style.hint_bg_focus);
-
-        doc = webkit_dom_node_get_owner_document(WEBKIT_DOM_NODE(hint->elem));
-        dom_dispatch_mouse_event(doc, hint->elem, "mouseover", 0);
-        webkit_dom_element_blur(hint->elem);
-
-        const char* tag = webkit_dom_element_get_tag_name(hint->elem);
-        if (!g_ascii_strcasecmp(tag, "a")) {
-            /* simulate the hovering over the hinted element this is done to show
-             * the hinted elements url in the url bar */
-            g_signal_emit_by_name(
-                vp.gui.webview,
-                "hovering-over-link",
-                "",
-                dom_element_get_source(hint->elem)
-            );
-        } else {
-            /* if hinted element has no url unhover the previous element */
-            g_signal_emit_by_name(vp.gui.webview, "hovering-over-link", NULL, NULL);
-        }
-    }
-
-    vp.hints.focusNum = num;
-}
-
-static void hints_fire(const gulong num)
-{
-    Hints* hints = &vp.hints;
-    Hint* hint   = hints_get_hint_by_number(num);
-    if (!hint) {
+    if (!value) {
         return;
     }
-    if (dom_is_editable(hint->elem)) {
-        webkit_dom_element_focus(hint->elem);
-        vp_set_mode(VP_MODE_INSERT, FALSE);
-    } else {
-        if (hints->mode & HINTS_PROCESS) {
-            hints_process_fired_hint(hints->mode, dom_element_get_source(hint->elem));
-        } else {
-            hints_click_fired_hint(hints->mode, hint->elem);
-
-            /* remove the hint filter input and witch to normal mode */
-            vp_set_mode(VP_MODE_NORMAL, TRUE);
+    if (!strncmp(value, "DONE:", 5)) {
+        hints_observe_input(FALSE);
+        vp_set_mode(VP_MODE_NORMAL, TRUE);
+    } else if (!strncmp(value, "INSERT:", 7)) {
+        hints_observe_input(FALSE);
+        vp_set_mode(VP_MODE_INSERT, TRUE);
+    } else if (!strncmp(value, "DATA:", 5)) {
+        hints_observe_input(FALSE);
+        HintsProcess type = HINTS_GET_PROCESSING(mode);
+        Arg a = {0};
+        switch (type) {
+            case HINTS_PROCESS_INPUT:
+                a.s = g_strconcat((mode & HINTS_TARGET_BLANK) ? ":tabopen " : ":open ", (value + 5), NULL);
+                command_input(&a);
+                g_free(a.s);
+                break;
+
+            case HINTS_PROCESS_YANK:
+                a.i = COMMAND_YANK_PRIMARY | COMMAND_YANK_SECONDARY;
+                a.s = g_strdup((value + 5));
+                command_yank(&a);
+                g_free(a.s);
+                break;
         }
     }
-    hints_clear();
+    g_free(value);
 }
 
-/**
- * Perform a mouse click to given element.
- */
-static void hints_click_fired_hint(guint mode, Element* elem)
+static void hints_fire(void)
 {
-    char* target = webkit_dom_element_get_attribute(elem, "target");
-    if (mode & HINTS_TARGET_BLANK) {                /* open in new window */
-        webkit_dom_element_set_attribute(elem, "target", "_blank", NULL);
-    } else if (g_strcmp0(target, "_blank") == 0) {  /* remove possible target attribute */
-        webkit_dom_element_remove_attribute(elem, "target");
-    }
-
-    /* dispatch click event */
-    Document* doc = webkit_dom_node_get_owner_document(WEBKIT_DOM_NODE(elem));
-    dom_dispatch_mouse_event(doc, elem, "click", 0);
-
-    /* reset previous target attribute */
-    if (target && strlen(target)) {
-        webkit_dom_element_set_attribute(elem, "target", target, NULL);
-    } else {
-        webkit_dom_element_remove_attribute(elem, "target");
-    }
-}
-
-/**
- * Handle fired hints that are not opened via simulated mouse click.
- */
-static void hints_process_fired_hint(guint mode, const char* uri)
-{
-    HintsProcess type = HINTS_GET_PROCESSING(mode);
-    Arg a = {0};
-    switch (type) {
-        case HINTS_PROCESS_INPUT:
-            a.s = g_strconcat((mode & HINTS_TARGET_BLANK) ? ":tabopen " : ":open ", uri, NULL);
-            command_input(&a);
-            g_free(a.s);
-            break;
-
-        case HINTS_PROCESS_YANK:
-            a.i = COMMAND_YANK_PRIMARY | COMMAND_YANK_SECONDARY;
-            a.s = g_strdup(uri);
-            command_yank(&a);
-            g_free(a.s);
-            break;
-    }
-}
-
-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 = vp.hints.list; 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 xpath epression according to current hinting mode and filter
- * input text.
- *
- * The returned string have to be freed.
- */
-static char* hints_get_xpath(const char* input)
-{
-    char* xpath = NULL;
-
-    switch (HINTS_GET_TYPE(vp.hints.mode)) {
-        case HINTS_TYPE_LINK:
-            if (input == NULL) {
-                xpath = g_strdup(
-                    "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @ role='link'] | "
-                    "//a[@href] | "
-                    "//input[not(@type='hidden')] | "
-                    "//textarea | "
-                    "//button | "
-                    "//select | "
-                    "//area"
-                );
-            } 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[not(@type='hidden') and contains(., '%s')] | "
-                    "//textarea[contains(., '%s')] | "
-                    "//button[contains(@value, '%s')] | "
-                    "//select[contains(., '%s')] | "
-                    "//area[contains(., '%s')]",
-                    input, input, input, input, input, input, input
-                );
-            }
-            break;
-
-        case HINTS_TYPE_IMAGE:
-            if (input == NULL) {
-                xpath = g_strdup("//img[@src]");
-            } else {
-                xpath = g_strdup_printf("//img[@src and contains(., '%s')]", input);
-            }
-            break;
-    }
-
-    return xpath;
+    hints_observe_input(FALSE);
+    char* js = g_strdup("hints.fire();");
+    hints_run_script(js);
+    g_free(js);
 }
 
 static void hints_observe_input(gboolean observe)
@@ -525,7 +202,7 @@ static gboolean hints_keypress_callback(WebKitWebView* webview, GdkEventKey* eve
     guint state  = CLEAN_STATE_WITH_SHIFT(event);
 
     if (keyval == GDK_Return) {
-        hints_fire(hints->focusNum);
+        hints_fire();
         return TRUE;
     }
     if (keyval == GDK_BackSpace && (state & GDK_SHIFT_MASK) && (state & GDK_CONTROL_MASK)) {
@@ -545,15 +222,3 @@ static gboolean hints_keypress_callback(WebKitWebView* webview, GdkEventKey* eve
 
     return FALSE;
 }
-
-static gboolean hints_num_has_prefix(gulong num, gulong prefix)
-{
-    if (prefix == num) {
-        return TRUE;
-    }
-    if (num >= 10) {
-        return hints_num_has_prefix(num / 10, prefix);
-    }
-
-    return FALSE;
-}
index 9264765..a9131c6 100644 (file)
 
 #include "main.h"
 
-#define HINTS_GET_TYPE(type)       ((type) & (HINTS_TYPE_LAST))
-#define HINTS_GET_PROCESSING(type) ((type) & ~(HINTS_TYPE_LAST | HINTS_PROCESS | HINTS_TARGET_BLANK))
-
-/*
-bits 1 and 2 form the hint type
-3:  0 = click hint       1 = process source
-4:  0 = open current     1 = open in new window
-all further bits are used for processing types
-*/
+#define HINTS_GET_TYPE(type)       ((type) & (HINTS_TYPE_LINK | HINTS_TYPE_IMAGE))
+#define HINTS_GET_PROCESSING(type) ((type) & ~(HINTS_TYPE_LINK | HINTS_TYPE_IMAGE | HINTS_PROCESS | HINTS_TARGET_BLANK))
+
 typedef enum {
-    HINTS_TYPE_LINK,
-    HINTS_TYPE_IMAGE,
-    HINTS_TYPE_DEFAULT,
-    HINTS_TYPE_FORM,
-    HINTS_TYPE_LAST = HINTS_TYPE_FORM,
+    HINTS_TYPE_LINK  = (1 << 1),
+    HINTS_TYPE_IMAGE = (1 << 2),
+    HINTS_TYPE_LAST  = HINTS_TYPE_IMAGE,
 } HintsType;
 
 enum {
-    HINTS_CLICK,
-    HINTS_PROCESS = (1 << 2)
-};
-
-enum {
-    HINTS_TARGET_CURRENT,
-    HINTS_TARGET_BLANK = (1 << 3)
+    HINTS_PROCESS      = (1 << 3),
+    HINTS_TARGET_BLANK = (1 << 4)
 };
 
 typedef enum {
-    HINTS_PROCESS_INPUT = (1 << 4),
-    HINTS_PROCESS_YANK  = (1 << 5),
+    HINTS_PROCESS_INPUT = (1 << 5),
+    HINTS_PROCESS_YANK  = (1 << 6),
 } HintsProcess;
 
 void hints_init(void);
diff --git a/src/js2h.sh b/src/js2h.sh
new file mode 100755 (executable)
index 0000000..80ce526
--- /dev/null
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+echo -n '#define HINTS_JS "'
+cat $1 | \
+    sed -e "s|/\*[^*]*\*\+\([^/][^*]*\*\+\)*/||g" | \
+    tr '\n\r\t' ' ' | \
+    sed -e "s| \+| |g" \
+    -e "s|^//.*$||" \
+    -e "s| \([-?<>:=(){};+\&\"',\|]\)|\1|g" \
+    -e "s|\([-?<>:=(){};+\&\"',\|]\) |\1|g" \
+    -e 's|"|\\"|g'
+echo -n "\"\n"
index 40a3378..c54daa4 100644 (file)
@@ -123,6 +123,9 @@ static void vp_webview_load_status_cb(WebKitWebView* view, GParamSpec* pspec, gp
                 vp_set_status(VP_STATUS_NORMAL);
             }
 
+            /* inject the hinting javascript */
+            hints_init();
+
             /* run user script file */
             vp_run_user_script();
 
@@ -665,9 +668,6 @@ static void vp_init(void)
     /* initialize the keybindings */
     keybind_init();
 
-    /* initialize the hints */
-    hints_init();
-
     /* initialize settings */
     setting_init();
 
index eb6df36..c93cc7e 100644 (file)
@@ -270,10 +270,10 @@ typedef struct {
     VpColor               comp_bg[VP_COMP_LAST];
     PangoFontDescription* comp_font[VP_COMP_LAST];
     /* hint style */
-    char*                hint_bg;
-    char*                hint_bg_focus;
-    char*                hint_fg;
-    char*                hint_style;
+    char*                 hint_bg;
+    char*                 hint_bg_focus;
+    char*                 hint_fg;
+    char*                 hint_style;
     /* status bar */
     VpColor               status_bg[VP_STATUS_LAST];
     VpColor               status_fg[VP_STATUS_LAST];
@@ -281,8 +281,6 @@ typedef struct {
 } Style;
 
 typedef struct {
-    GList* list;
-    gulong focusNum;
     gulong num;
     guint  mode;
     guint  prefixLength;
index 8a8a444..38e6898 100644 (file)
@@ -113,7 +113,7 @@ static Setting default_settings[] = {
     {NULL, "hint-bg", TYPE_CHAR, setting_hint_style, {.s = "#ff0"}},
     {NULL, "hint-bg-focus", TYPE_CHAR, setting_hint_style, {.s = "#8f0"}},
     {NULL, "hint-fg", TYPE_CHAR, setting_hint_style, {.s = "#000"}},
-    {NULL, "hint-style", TYPE_CHAR, setting_hint_style, {.s = "font-family:monospace;font-weight:bold;color:#000;background-color:#fff;margin:0;padding:0px 1px;border:1px solid #444;opacity:0.7;"}},
+    {NULL, "hint-style", TYPE_CHAR, setting_hint_style, {.s = "position:absolute;z-index:100000;font-family:monospace;font-weight:bold;font-size:10px;color:#000;background-color:#fff;margin:0;padding:0px 1px;border:1px solid #444;opacity:0.7;"}},
     {NULL, "strict-ssl", TYPE_BOOLEAN, setting_strict_ssl, {.i = 1}},
     {NULL, "ca-bundle", TYPE_CHAR, setting_ca_bundle, {.s = "/etc/ssl/certs/ca-certificates.crt"}},
     {NULL, "home-page", TYPE_CHAR, setting_home_page, {.s = "https://github.com/fanglingsu/vimp"}},