#include "keybind.h"
#include "setting.h"
#include "completion.h"
+#include "hints.h"
extern const char *inputbox_font[2];
extern const char *inputbox_fg[2];
{"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)
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;
+}
--- /dev/null
+#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;
+}