From 319160f5b4800165a8480cc2dab95d287acbc81e Mon Sep 17 00:00:00 2001 From: Daniel Carl Date: Sat, 1 Dec 2012 22:50:23 +0100 Subject: [PATCH] Added basic logic for hints. --- doc/config | 3 + src/command.c | 20 ++++ src/command.h | 2 + src/dom.c | 17 ++- src/dom.h | 4 + src/hints.c | 290 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/hints.h | 18 ++++ src/main.c | 11 +- src/main.h | 1 + 9 files changed, 362 insertions(+), 4 deletions(-) create mode 100644 src/hints.c create mode 100644 src/hints.h diff --git a/doc/config b/doc/config index a7d2fcf..12703c9 100644 --- a/doc/config +++ b/doc/config @@ -21,5 +21,8 @@ nmap h scrollleft nmap l scrollright nmap k scrollup nmap j scrolldown +nmap f hint-link cmap complete cmap complete-back +hmap hint-focus-next +hmap hint-focus-prev diff --git a/src/command.c b/src/command.c index e1c7bb0..8939152 100644 --- a/src/command.c +++ b/src/command.c @@ -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; +} diff --git a/src/command.h b/src/command.h index 8d8d569..ebefee2 100644 --- a/src/command.h +++ b/src/command.h @@ -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 */ diff --git a/src/dom.c b/src/dom.c index ef04e42..6de41a4 100644 --- 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; } - diff --git a/src/dom.h b/src/dom.h index e58c4ee..3b3e539 100644 --- a/src/dom.h +++ b/src/dom.h @@ -20,6 +20,10 @@ #ifndef DOM_H #define DOM_H +#include + 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 index 0000000..0b9f607 --- /dev/null +++ b/src/hints.c @@ -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 index 0000000..f291876 --- /dev/null +++ b/src/hints.h @@ -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 */ diff --git a/src/main.c b/src/main.c index 63442aa..055c8cd 100644 --- a/src/main.c +++ b/src/main.c @@ -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, ""); diff --git a/src/main.h b/src/main.h index 8e507d2..f64b4e9 100644 --- a/src/main.h +++ b/src/main.h @@ -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 { -- 2.20.1