From 83cec60d012284c28eb8e8f23aa83dce8c128af2 Mon Sep 17 00:00:00 2001 From: Daniel Carl Date: Wed, 27 Feb 2013 23:53:13 +0100 Subject: Replaced hinting by javascript hinting. 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 | 7 +- config.mk | 1 + src/dom.c | 85 ---------- src/dom.h | 6 - src/hint.js | 386 +++++++++++++++++++++++++++++++++++++++++++ src/hints.c | 519 +++++++++++----------------------------------------------- src/hints.h | 33 ++-- src/js2h.sh | 12 ++ src/main.c | 6 +- src/main.h | 10 +- src/setting.c | 2 +- 11 files changed, 515 insertions(+), 552 deletions(-) create mode 100644 src/hint.js create mode 100755 src/js2h.sh diff --git a/Makefile b/Makefile index 4f032b7..b088b3c 100644 --- 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." diff --git a/config.mk b/config.mk index a3ad6a4..78b6638 100644 --- 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 diff --git a/src/dom.c b/src/dom.c index 9ebba04..8bccebd 100644 --- 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)) { diff --git a/src/dom.h b/src/dom.h index e08f462..88897f0 100644 --- 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 index 0000000..1738f78 --- /dev/null +++ b/src/hint.js @@ -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; + } +} diff --git a/src/hints.c b/src/hints.c index f1df38d..2042694 100644 --- a/src/hints.c +++ b/src/hints.c @@ -22,469 +22,146 @@ #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; -} diff --git a/src/hints.h b/src/hints.h index 9264765..a9131c6 100644 --- a/src/hints.h +++ b/src/hints.h @@ -22,36 +22,23 @@ #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 index 0000000..80ce526 --- /dev/null +++ b/src/js2h.sh @@ -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" diff --git a/src/main.c b/src/main.c index 40a3378..c54daa4 100644 --- a/src/main.c +++ b/src/main.c @@ -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(); diff --git a/src/main.h b/src/main.h index eb6df36..c93cc7e 100644 --- a/src/main.h +++ b/src/main.h @@ -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; diff --git a/src/setting.c b/src/setting.c index 8a8a444..38e6898 100644 --- a/src/setting.c +++ b/src/setting.c @@ -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"}}, -- cgit v1.2.3