2 (c) 2009 by Leon Winter
3 (c) 2009, 2010 by Hannes Schueller
4 (c) 2010 by Hans-Peter Deifel
5 (c) 2011 by Daniel Carl
11 hintCss: "z-index:100000;font-family:monospace;font-size:10px;"
12 + "font-weight:bold;color:white;background-color:red;"
13 + "padding:0px 1px;position:absolute;",
14 hintClass: "hinting_mode_hint",
15 hintClassFocus: "hinting_mode_hint_focus",
16 elemBackground: "#ff0",
17 elemBackgroundFocus: "#8f0",
22 var currentFocusNum = 1;
26 this.createHints = function(inputText, hintMode)
33 var top_height = topwin.innerHeight;
34 var top_width = topwin.innerWidth;
40 function helper (win, offsetX, offsetY) {
41 var doc = win.document;
43 var win_height = win.height;
44 var win_width = win.width;
47 var minX = offsetX < 0 ? -offsetX : 0;
48 var minY = offsetY < 0 ? -offsetY : 0;
49 var maxX = offsetX + win_width > top_width ? top_width - offsetX : top_width;
50 var maxY = offsetY + win_height > top_height ? top_height - offsetY : top_height;
52 var scrollX = win.scrollX;
53 var scrollY = win.scrollY;
55 hintContainer = doc.createElement("div");
56 hintContainer.id = "hint_container";
58 xpath_expr = _getXpathXpression(inputText);
60 var res = doc.evaluate(xpath_expr, doc,
62 return "http://www.w3.org/1999/xhtml";
63 }, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
65 /* generate basic hint element which will be cloned and updated later */
66 var hintSpan = doc.createElement("span");
67 hintSpan.setAttribute("class", config.hintClass);
68 hintSpan.style.cssText = config.hintCss;
70 /* due to the different XPath result type, we will need two counter variables */
71 var rect, elem, text, node, show_text;
72 for (var i = 0; i < res.snapshotLength; i++) {
73 if (hintCount >= config.maxAllowedHints) {
77 elem = res.snapshotItem(i);
78 rect = elem.getBoundingClientRect();
79 if (!rect || rect.left > maxX || rect.right < minX || rect.top > maxY || rect.bottom < minY) {
83 var style = topwin.getComputedStyle(elem, "");
84 if (style.display == "none" || style.visibility != "visible") {
87 if (mode == "y" || mode == "l") {
88 if (elem.getAttribute("href") == undefined && elem.getAttribute("role") != "link"
89 && elem.tagName.toLowerCase() != "img") {
95 var leftpos = Math.max((rect.left + scrollX), scrollX);
96 var toppos = Math.max((rect.top + scrollY), scrollY);
98 /* making this block DOM compliant */
99 var hint = hintSpan.cloneNode(false);
100 hint.setAttribute("id", "vimprobablehint" + hintCount);
101 hint.style.left = leftpos + "px";
102 hint.style.top = toppos + "px";
103 text = doc.createTextNode(hintCount + 1);
104 hint.appendChild(text);
106 hintContainer.appendChild(hint);
112 background: elem.style.background,
113 foreground: elem.style.color}
116 /* make the link black to ensure it's readable */
117 elem.style.color = config.elemColor;
118 elem.style.background = config.elemBackground;
121 doc.documentElement.appendChild(hintContainer);
123 /* recurse into any iframe or frame element */
125 for (i = 0; i < win.frames.length; i++) {
126 frame = win.frames[i];
127 elem = frame.frameElement;
128 rect = elem.getBoundingClientRect();
129 if (!elem.contentWindow || !rect || rect.left > maxX || rect.right < minX || rect.top > maxY || rect.bottom < minY) {
132 helper(elem.contentWindow, offsetX + rect.left, offsetY + rect.top);
136 helper(topwin, 0, 0);
140 if (hintCount == 1) {
141 /* just one hinted element - might as well follow it */
146 /* set focus on hint with given number */
147 this.focusHint = function(n)
149 /* reset previous focused hint */
150 var hint = _getHintByNumber(currentFocusNum);
152 hint.elem.className = hint.elem.className.replace(config.hintClassFocus, config.hintClass);
153 hint.elem.style.background = config.elemBackground;
158 /* mark new hint as focused */
159 hint = _getHintByNumber(currentFocusNum);
161 hint.elem.className = hint.elem.className.replace(config.hintClass, config.hintClassFocus);
162 hint.elem.style.background = config.elemBackgroundFocus;
166 /* set focus to next avaiable hint */
167 this.focusNextHint = function()
169 var index = _getHintIdByNumber(currentFocusNum);
171 if (typeof(hints[index + 1]) != "undefined") {
172 this.focusHint(hints[index + 1].number);
174 this.focusHint(hints[0].number);
178 /* set focus to previous avaiable hint */
179 this.focusPreviousHint = function()
181 var index = _getHintIdByNumber(currentFocusNum);
182 if (index !== 0 && typeof(hints[index - 1].number) != "undefined") {
183 this.focusHint(hints[index - 1].number);
185 this.focusHint(hints[hints.length - 1].number);
189 /* filters hints matching given number */
190 this.updateHints = function(n)
193 return this.createHints();
195 /* remove none matching hints */
197 for (i = 0; i < hints.length; ++i) {
199 if (0 !== hint.number.toString().indexOf(n.toString())) {
200 remove.push(hint.number);
204 for (i = 0; i < remove.length; ++i) {
205 _removeHint(remove[i]);
208 if (hints.length === 1) {
209 return this.fire(hints[0].number);
211 return this.focusHint(n);
215 this.clearFocus = function()
217 if (document.activeElement && document.activeElement.blur) {
218 document.activeElement.blur();
222 /* remove all hints and set previous style to them */
223 this.clearHints = function()
225 if (hints.length === 0) {
228 for (var i = 0; i < hints.length; ++i) {
230 if (typeof(hint.elem) != "undefined") {
231 hint.elem.style.background = hint.background;
232 hint.elem.style.color = hint.foreground;
233 hint.span.parentNode.removeChild(hint.span);
237 hintContainer.parentNode.removeChild(hintContainer);
238 window.onkeyup = null;
241 /* fires the modeevent on hint with given number */
242 this.fire = function(n)
245 n = n ? n : currentFocusNum;
246 var hint = _getHintByNumber(n);
247 if (typeof(hint.elem) == "undefined") {
252 var tag = el.nodeName.toLowerCase();
256 if (tag == "input" || tag == "textarea" || tag == "select") {
257 if (el.type == "radio" || el.type == "checkbox") {
262 if (el.type == "submit" || el.type == "reset" || el.type == "button" || el.type === "image") {
269 if (tag == "iframe" || tag == "frame") {
276 case "f": result = _open(el); break;
277 case "F": result = _openNewWindow(el); break;
278 case "i": result = "open;" + _getElemtSource(el); break;
279 case "I": result = "tabopen;" + _getElemtSource(el); break;
280 case "l": result = "show_link;" + _getElemtSource(el); break;
281 case "s": result = "save;" + _getElemtSource(el); break;
282 case "y": result = "yank;" + _getElemtSource(el); break;
283 case "O": result = "colon;" + _getElemtSource(el); break;
284 default: result = _getElemtSource(el); break;
290 this.focusInput = function()
292 if (document.getElementsByTagName("body")[0] === null || typeof(document.getElementsByTagName("body")[0]) != "object") {
296 /* prefixing html: will result in namespace error */
297 var hinttags = "//input[@type='text'] | //input[@type='password'] | //textarea";
298 var r = document.evaluate(hinttags, document,
300 return "http://www.w3.org/1999/xhtml";
301 }, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
307 for (i = 0; i < r.snapshotLength; i++) {
308 var elem = r.snapshotItem(i);
310 if (elem.style.display != "none" && elem.style.visibility != "hidden") {
316 if (j === 1 && elem.style.display != "none" && elem.style.visibility != "hidden") {
320 if (elem == document.activeElement) {
325 /* no appropriate field found focused - focus the first one */
326 if (j === 0 && first !== null) {
332 /* retrieves text content fro given element */
333 function _getTextFromElement(el)
335 if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
337 } else if (el instanceof HTMLSelectElement) {
338 if (el.selectedIndex >= 0) {
339 text = el.item(el.selectedIndex).text;
344 text = el.textContent;
346 return text.toLowerCase();
349 /* retrieves the hint for given hint number */
350 function _getHintByNumber(n)
352 var index = _getHintIdByNumber(n);
353 if (index !== null) {
359 /* retrieves the id of hint with given number */
360 function _getHintIdByNumber(n)
362 for (var i = 0; i < hints.length; ++i) {
364 if (hint.number === n) {
371 /* removes hint with given number from hints array */
372 function _removeHint(n)
374 var index = _getHintIdByNumber(n);
375 if (index === null) {
378 var hint = hints[index];
379 if (hint.number === n) {
380 hint.elem.style.background = hint.background;
381 hint.elem.style.color = hint.foreground;
382 hint.span.parentNode.removeChild(hint.span);
384 /* remove hints from all hints */
385 hints.splice(index, 1);
389 /* opens given element */
392 if (elem.target == "_blank") {
393 elem.removeAttribute("target");
399 /* opens given element into new window */
400 function _openNewWindow(elem)
402 var oldTarget = elem.target;
404 /* set target to open in new window */
405 elem.target = "_blank";
407 elem.target = oldTarget;
412 /* fire moudedown and click event on given element */
413 function _clickElement(elem)
415 doc = elem.ownerDocument;
416 view = elem.contentWindow;
418 var evObj = doc.createEvent("MouseEvents");
419 evObj.initMouseEvent("mousedown", true, true, view, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null);
420 elem.dispatchEvent(evObj);
422 evObj = doc.createEvent("MouseEvents");
423 evObj.initMouseEvent("click", true, true, view, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null);
424 elem.dispatchEvent(evObj);
427 /* retrieves the url of given element */
428 function _getElemtSource(elem)
430 var url = elem.href || elem.src;
434 /* retrieves the xpath expression according to mode */
435 function _getXpathXpression(text)
438 if (typeof(text) == "undefined") {
447 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";
449 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 + "')]";
455 expr = "//img[@src]";
457 expr = "//img[@src and contains(., '" + text + "')]";
462 expr = "//*[@role='link' or @href] | //a[href] | //area | //img[not(ancestor::a)]";
464 expr = "//*[(@role='link' or @href) and contains(., '" + text + "')] | //a[@href and contains(., '" + text + "')] | //area[contains(., '" + text + "')] | //img[not(ancestor::a) and contains(., '" + text + "')]";