2 Copyright (c) 2009 Leon Winter
3 Copyright (c) 2009-2011 Hannes Schueller
4 Copyright (c) 2009-2010 Matto Fransen
5 Copyright (c) 2010-2011 Hans-Peter Deifel
6 Copyright (c) 2010-2011 Thomas Adam
7 Copyright (c) 2011 Albert Kim
8 Copyright (c) 2011 Daniel Carl
10 Permission is hereby granted, free of charge, to any person obtaining a copy
11 of this software and associated documentation files (the "Software"), to deal
12 in the Software without restriction, including without limitation the rights
13 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 copies of the Software, and to permit persons to whom the Software is
15 furnished to do so, subject to the following conditions:
17 The above copyright notice and this permission notice shall be included in
18 all copies or substantial portions of the Software.
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
31 hintCss: "z-index:100000;font-family:monospace;font-size:10px;"
32 + "font-weight:bold;color:white;background-color:red;"
33 + "padding:0px 1px;position:absolute;",
34 hintClass: "hinting_mode_hint",
35 hintClassFocus: "hinting_mode_hint_focus",
36 elemBackground: "#ff0",
37 elemBackgroundFocus: "#8f0",
42 var currentFocusNum = 1;
46 this.createHints = function(inputText, hintMode)
53 var top_height = topwin.innerHeight;
54 var top_width = topwin.innerWidth;
60 function helper (win, offsetX, offsetY) {
61 var doc = win.document;
63 var win_height = win.height;
64 var win_width = win.width;
67 var minX = offsetX < 0 ? -offsetX : 0;
68 var minY = offsetY < 0 ? -offsetY : 0;
69 var maxX = offsetX + win_width > top_width ? top_width - offsetX : top_width;
70 var maxY = offsetY + win_height > top_height ? top_height - offsetY : top_height;
72 var scrollX = win.scrollX;
73 var scrollY = win.scrollY;
75 hintContainer = doc.createElement("div");
76 hintContainer.id = "hint_container";
78 if (typeof(inputText) == "undefined" || inputText == "") {
79 xpath_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";
81 xpath_expr = "//*[(@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href) and contains(., '" + inputText + "')] | //input[not(@type='hidden') and contains(., '" + inputText + "')] | //a[@href and contains(., '" + inputText + "')] | //area[contains(., '" + inputText + "')] | //textarea[contains(., '" + inputText + "')] | //button[contains(@value, '" + inputText + "')] | //select[contains(., '" + inputText + "')]";
84 var res = doc.evaluate(xpath_expr, doc,
86 return "http://www.w3.org/1999/xhtml";
87 }, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
89 /* generate basic hint element which will be cloned and updated later */
90 var hintSpan = doc.createElement("span");
91 hintSpan.setAttribute("class", config.hintClass);
92 hintSpan.style.cssText = config.hintCss;
94 /* due to the different XPath result type, we will need two counter variables */
95 var rect, elem, text, node, show_text;
96 for (var i = 0; i < res.snapshotLength; i++)
98 if (hintCount >= config.maxAllowedHints)
101 elem = res.snapshotItem(i);
102 rect = elem.getBoundingClientRect();
103 if (!rect || rect.left > maxX || rect.right < minX || rect.top > maxY || rect.bottom < minY)
106 var style = topwin.getComputedStyle(elem, "");
107 if (style.display == "none" || style.visibility != "visible")
110 var leftpos = Math.max((rect.left + scrollX), scrollX);
111 var toppos = Math.max((rect.top + scrollY), scrollY);
113 /* making this block DOM compliant */
114 var hint = hintSpan.cloneNode(false);
115 hint.setAttribute("id", "vimprobablehint" + hintCount);
116 hint.style.left = leftpos + "px";
117 hint.style.top = toppos + "px";
118 text = doc.createTextNode(hintCount + 1);
119 hint.appendChild(text);
121 hintContainer.appendChild(hint);
127 background: elem.style.background,
128 foreground: elem.style.color}
131 /* make the link black to ensure it's readable */
132 elem.style.color = config.elemColor;
133 elem.style.background = config.elemBackground;
136 doc.documentElement.appendChild(hintContainer);
138 /* recurse into any iframe or frame element */
139 var frameTags = ["frame","iframe"];
140 for (var f = 0; f < frameTags.length; ++f) {
141 var frames = doc.getElementsByTagName(frameTags[f]);
142 for (var i = 0, nframes = frames.length; i < nframes; ++i) {
144 rect = elem.getBoundingClientRect();
145 if (!elem.contentWindow || !rect || rect.left > maxX || rect.right < minX || rect.top > maxY || rect.bottom < minY)
147 helper(elem.contentWindow, offsetX + rect.left, offsetY + rect.top);
152 helper(topwin, 0, 0);
156 if (hintCount == 1) {
157 /* just one hinted element - might as well follow it */
162 /* set focus on hint with given number */
163 this.focusHint = function(n)
165 /* reset previous focused hint */
166 var hint = _getHintByNumber(currentFocusNum);
168 hint.elem.className = hint.elem.className.replace(config.hintClassFocus, config.hintClass);
169 hint.elem.style.background = config.elemBackground;
174 /* mark new hint as focused */
175 var hint = _getHintByNumber(currentFocusNum);
177 hint.elem.className = hint.elem.className.replace(config.hintClass, config.hintClassFocus);
178 hint.elem.style.background = config.elemBackgroundFocus;
182 /* set focus to next avaiable hint */
183 this.focusNextHint = function()
185 var index = _getHintIdByNumber(currentFocusNum);
187 if (typeof(hints[index + 1]) != "undefined") {
188 this.focusHint(hints[index + 1].number);
190 this.focusHint(hints[0].number);
194 /* set focus to previous avaiable hint */
195 this.focusPreviousHint = function()
197 var index = _getHintIdByNumber(currentFocusNum);
198 if (index != 0 && typeof(hints[index - 1].number) != "undefined") {
199 this.focusHint(hints[index - 1].number);
201 this.focusHint(hints[hints.length - 1].number);
205 /* filters hints matching given number */
206 this.updateHints = function(n)
209 return this.createHints();
211 /* remove none matching hints */
213 for (var i = 0; i < hints.length; ++i) {
215 if (0 != hint.number.toString().indexOf(n.toString())) {
216 remove.push(hint.number);
220 for (var i = 0; i < remove.length; ++i) {
221 _removeHint(remove[i]);
224 if (hints.length === 1) {
225 return this.fire(hints[0].number);
227 return this.focusHint(n);
231 this.clearFocus = function()
233 if (document.activeElement && document.activeElement.blur) {
234 document.activeElement.blur();
238 /* remove all hints and set previous style to them */
239 this.clearHints = function()
241 if (hints.length == 0) {
244 for (var i = 0; i < hints.length; ++i) {
246 if (typeof(hint.elem) != "undefined") {
247 hint.elem.style.background = hint.background;
248 hint.elem.style.color = hint.foreground;
249 hint.span.parentNode.removeChild(hint.span);
253 hintContainer.parentNode.removeChild(hintContainer);
254 window.onkeyup = null;
257 /* fires the modeevent on hint with given number */
258 this.fire = function(n)
262 var n = currentFocusNum;
264 var hint = _getHintByNumber(n);
265 if (typeof(hint.elem) == "undefined")
269 var tag = el.nodeName.toLowerCase();
273 if (tag == "iframe" || tag == "frame" || tag == "textarea" || tag == "input" && (el.type == "text" || el.type == "password" || el.type == "checkbox" || el.type == "radio") || tag == "select") {
275 if (tag == "input" || tag == "textarea") {
283 case "f": result = _open(el); break;
284 case "F": result = _openNewWindow(el); break;
285 default: result = _getElemtSource(el);
291 this.focusInput = function()
293 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);
306 for (i = 0; i < r.snapshotLength; i++) {
307 var elem = r.snapshotItem(i);
309 if (elem.style.display != "none" && elem.style.visibility != "hidden") {
315 if (j == 1 && elem.style.display != "none" && elem.style.visibility != "hidden") {
317 var tag = elem.nodeName.toLowerCase();
318 if (tag == "textarea" || tag == "input") {
323 if (elem == document.activeElement) {
328 /* no appropriate field found focused - focus the first one */
329 if (j == 0 && first !== null) {
331 var tag = elem.nodeName.toLowerCase();
332 if (tag == "textarea" || tag == "input") {
338 /* retrieves text content fro given element */
339 function _getTextFromElement(el)
341 if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
343 } else if (el instanceof HTMLSelectElement) {
344 if (el.selectedIndex >= 0) {
345 text = el.item(el.selectedIndex).text;
350 text = el.textContent;
352 return text.toLowerCase();;
355 /* retrieves the hint for given hint number */
356 function _getHintByNumber(n)
358 var index = _getHintIdByNumber(n);
359 if (index !== null) {
365 /* retrieves the id of hint with given number */
366 function _getHintIdByNumber(n)
368 for (var i = 0; i < hints.length; ++i) {
370 if (hint.number === n) {
377 /* removes hint with given number from hints array */
378 function _removeHint(n)
380 var index = _getHintIdByNumber(n);
381 if (index === null) {
384 var hint = hints[index];
385 if (hint.number === n) {
386 hint.elem.style.background = hint.background;
387 hint.elem.style.color = hint.foreground;
388 hint.span.parentNode.removeChild(hint.span);
390 /* remove hints from all hints */
391 hints.splice(index, 1);
395 /* opens given element */
398 if (elem.target == "_blank") {
399 elem.removeAttribute("target");
405 /* opens given element into new window */
406 function _openNewWindow(elem)
408 var oldTarget = elem.target;
410 /* set target to open in new window */
411 elem.target = "_blank";
413 elem.target = oldTarget;
418 /* fire moudedown and click event on given element */
419 function _clickElement(elem)
421 doc = elem.ownerDocument;
422 view = elem.contentWindow;
424 var evObj = doc.createEvent("MouseEvents");
425 evObj.initMouseEvent("mousedown", true, true, view, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null);
426 elem.dispatchEvent(evObj);
428 var evObj = doc.createEvent("MouseEvents");
429 evObj.initMouseEvent("click", true, true, view, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null);
430 elem.dispatchEvent(evObj);
433 /* retrieves the url of given element */
434 function _getElemtSource(elem)
436 var url = elem.href || elem.src;