Remove unneeded clearHints() before createHints() in c-code.
[vimprobable2.git] / hinting.js
blobd620af65192d79332e1145912f6857c88bfbe8ed
1 /*
2 (c) 2009 by Leon Winter
3 (c) 2009, 2010 by Hannes Schueller
4 (c) 2010 by Hans-Peter Deifel
5 see LICENSE file
6 */
7 function Hints() {
8 const maxAllowedHints = 500;
10 var hintContainer;
11 var currentFocusNum = 1;
12 var hints = [];
13 var mode;
15 this.createHints = function(inputText, hintMode)
17 if (hintMode) {
18 mode = hintMode;
21 var topwin = window;
22 var top_height = topwin.innerHeight;
23 var top_width = topwin.innerWidth;
24 var xpath_expr;
26 var hintCount = 0;
27 this.clearHints();
29 function helper (win, offsetX, offsetY) {
30 var doc = win.document;
32 var win_height = win.height;
33 var win_width = win.width;
35 /* Bounds */
36 var minX = offsetX < 0 ? -offsetX : 0;
37 var minY = offsetY < 0 ? -offsetY : 0;
38 var maxX = offsetX + win_width > top_width ? top_width - offsetX : top_width;
39 var maxY = offsetY + win_height > top_height ? top_height - offsetY : top_height;
41 var scrollX = win.scrollX;
42 var scrollY = win.scrollY;
44 hintContainer = doc.createElement("div");
45 hintContainer.id = "hint_container";
47 if (typeof(inputText) == "undefined" || inputText == "") {
48 xpath_expr = "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href] | //input[not(@type='hidden')] | //a | //area | //textarea | //button | //select";
49 } else {
50 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[contains(., '" + inputText + "')] | //area[contains(., '" + inputText + "')] | //textarea[contains(., '" + inputText + "')] | //button[contains(@value, '" + inputText + "')] | //select[contains(., '" + inputText + "')]";
53 var res = doc.evaluate(xpath_expr, doc,
54 function (p) {
55 return "http://www.w3.org/1999/xhtml";
56 }, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
58 /* generate basic hint element which will be cloned and updated later */
59 var hintSpan = doc.createElement("span");
60 hintSpan.setAttribute("class", "hinting_mode_hint");
61 hintSpan.style.position = "absolute";
62 hintSpan.style.background = "red";
63 hintSpan.style.color = "#fff";
64 hintSpan.style.font = "bold 10px monospace";
65 hintSpan.style.zIndex = "10000000";
67 /* due to the different XPath result type, we will need two counter variables */
68 var rect, elem, text, node, show_text;
69 for (var i = 0; i < res.snapshotLength; i++)
71 if (hintCount >= maxAllowedHints)
72 break;
74 elem = res.snapshotItem(i);
75 rect = elem.getBoundingClientRect();
76 if (!rect || rect.left > maxX || rect.right < minX || rect.top > maxY || rect.bottom < minY)
77 continue;
79 var style = topwin.getComputedStyle(elem, "");
80 if (style.display == "none" || style.visibility != "visible")
81 continue;
83 var leftpos = Math.max((rect.left + scrollX), scrollX);
84 var toppos = Math.max((rect.top + scrollY), scrollY);
86 /* process elements text */
87 text = _getTextFromElement(elem);
89 /* making this block DOM compliant */
90 var hint = hintSpan.cloneNode(false);
91 hint.setAttribute("id", "vimprobablehint" + hintCount);
92 hint.style.left = leftpos + "px";
93 hint.style.top = toppos + "px";
94 text = doc.createTextNode(hintCount + 1);
95 hint.appendChild(text);
97 hintContainer.appendChild(hint);
98 hintCount++;
99 hints.push({
100 elem: elem,
101 number: hintCount,
102 text: text,
103 span: hint,
104 background: elem.style.background,
105 foreground: elem.style.color}
108 /* make the link black to ensure it's readable */
109 elem.style.color = "#000";
110 elem.style.background = "#ff0";
113 doc.documentElement.appendChild(hintContainer);
115 /* recurse into any iframe or frame element */
116 var frameTags = ["frame","iframe"];
117 for (var i in frameTags) {
118 var frames = doc.getElementsByTagName(frameTags[i]);
119 for (var i = 0, nframes = frames.length; i < nframes; ++i) {
120 elem = frames[i];
121 rect = elem.getBoundingClientRect();
122 if (!elem.contentWindow || !rect || rect.left > maxX || rect.right < minX || rect.top > maxY || rect.bottom < minY)
123 continue;
124 helper(elem.contentWindow, offsetX + rect.left, offsetY + rect.top);
129 helper(topwin, 0, 0);
131 this.clearFocus();
132 this.focusHint(1);
133 if (hintCount == 1) {
134 /* just one hinted element - might as well follow it */
135 return this.fire(1);
139 /* set focus on hint with given number */
140 this.focusHint = function(n)
142 /* reset previous focused hint */
143 var hint = _getHintByNumber(currentFocusNum);
144 if (hint !== null) {
145 hint.elem.className = hint.elem.className.replace("hinting_mode_hint_focus", "hinting_mode_hint");
146 hint.elem.style.background = "#ff0";
149 currentFocusNum = n;
151 /* mark new hint as focused */
152 var hint = _getHintByNumber(currentFocusNum);
153 if (hint !== null) {
154 hint.elem.className = hint.elem.className.replace("hinting_mode_hint", "hinting_mode_hint_focus");
155 hint.elem.style.background = "#8f0";
159 /* set focus to next avaiable hint */
160 this.focusNextHint = function()
162 var index = _getHintIdByNumber(currentFocusNum);
164 if (typeof(hints[index + 1]) != "undefined") {
165 this.focusHint(hints[index + 1].number);
166 } else {
167 this.focusHint(hints[0].number);
171 /* set focus to previous avaiable hint */
172 this.focusPreviousHint = function()
174 var index = _getHintIdByNumber(currentFocusNum);
175 if (index != 0 && typeof(hints[index - 1].number) != "undefined") {
176 this.focusHint(hints[index - 1].number);
177 } else {
178 this.focusHint(hints[hints.length - 1].number);
182 /* filters hints matching given number */
183 this.updateHints = function(n)
185 if (n == 0) {
186 this.createHints();
187 return;
189 /* remove none matching hints */
190 var remove = [];
191 for (e in hints) {
192 var hint = hints[e];
193 if (0 != hint.number.toString().indexOf(n.toString())) {
194 remove.push(hint.number);
198 for (var i = 0; i < remove.length; ++i) {
199 _removeHint(remove[i]);
202 if (hints.length === 1) {
203 this.fire(hints[0].number);
204 } else {
205 this.focusHint(n);
209 this.clearFocus = function()
211 if (document.activeElement && document.activeElement.blur) {
212 document.activeElement.blur();
216 /* remove all hints and set previous style to them */
217 this.clearHints = function()
219 if (hints.length == 0) {
220 return;
222 for (e in hints) {
223 var hint = hints[e];
224 if (typeof(hint.elem) != "undefined") {
225 hint.elem.style.background = hint.background;
226 hint.elem.style.color = hint.foreground;
227 hint.span.parentNode.removeChild(hint.span);
230 hints = [];
231 hintContainer.parentNode.removeChild(hintContainer);
232 window.onkeyup = null;
235 /* fires the modeevent on hint with given number */
236 this.fire = function(n)
238 var doc, result;
239 if (!n) {
240 var n = currentFocusNum;
242 var hint = _getHintByNumber(n);
243 if (typeof(hint.elem) == "undefined")
244 return "done;";
246 var el = hint.elem;
247 var tag = el.nodeName.toLowerCase();
249 this.clearHints();
251 if (tag == "iframe" || tag == "frame" || tag == "textarea" || tag == "input" && (el.type == "text" || el.type == "password" || el.type == "checkbox" || el.type == "radio") || tag == "select") {
252 el.focus();
253 return "done;";
256 switch (mode)
258 case "f": result = _open(el); break;
259 case "F": result = _openNewWindow(el); break;
260 default: result = _getElemtSource(el);
263 return result;
266 this.focusInput = function()
268 if (document.getElementsByTagName("body")[0] === null || typeof(document.getElementsByTagName("body")[0]) != "object")
269 return;
271 /* prefixing html: will result in namespace error */
272 var hinttags = "//input[@type='text'] | //input[@type='password'] | //textarea";
273 var r = document.evaluate(hinttags, document,
274 function(p) {
275 return "http://www.w3.org/1999/xhtml";
276 }, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
277 var i;
278 var j = 0;
279 var k = 0;
280 var first = null;
281 for (i = 0; i < r.snapshotLength; i++) {
282 var elem = r.snapshotItem(i);
283 if (k == 0) {
284 if (elem.style.display != "none" && elem.style.visibility != "hidden") {
285 first = elem;
286 } else {
287 k--;
290 if (j == 1 && elem.style.display != "none" && elem.style.visibility != "hidden") {
291 elem.focus();
292 break;
294 if (elem == document.activeElement) {
295 j = 1;
297 k++;
299 /* no appropriate field found focused - focus the first one */
300 if (j == 0 && first !== null)
301 first.focus();
304 /* retrieves text content fro given element */
305 function _getTextFromElement(el)
307 if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
308 text = el.value;
309 } else if (el instanceof HTMLSelectElement) {
310 if (el.selectedIndex >= 0) {
311 text = el.item(el.selectedIndex).text;
312 } else{
313 text = "";
315 } else {
316 text = el.textContent;
318 return text.toLowerCase();;
321 /* retrieves the hint for given hint number */
322 function _getHintByNumber(n)
324 var index = _getHintIdByNumber(n);
325 if (index !== null) {
326 return hints[index];
328 return null;
331 /* retrieves the id of hint with given number */
332 function _getHintIdByNumber(n)
334 for (var i = 0; i < hints.length; ++i) {
335 var hint = hints[i];
336 if (hint.number === n) {
337 return i;
340 return null;
343 /* removes hint with given number from hints array */
344 function _removeHint(n)
346 var index = _getHintIdByNumber(n);
347 if (index === null) {
348 return;
350 var hint = hints[index];
351 if (hint.number === n) {
352 hint.elem.style.background = hint.background;
353 hint.elem.style.color = hint.foreground;
354 hint.span.parentNode.removeChild(hint.span);
356 /* remove hints from all hints */
357 hints.splice(index, 1);
361 /* opens given element */
362 function _open(elem)
364 _clickElement(elem);
365 return "done;";
368 /* opens given element into new window */
369 function _openNewWindow(elem)
371 var oldTarget = elem.target;
373 /* set target to open in new window */
374 elem.target = "_blank";
375 _clickElement(elem);
376 elem.target = oldTarget;
378 return "done;";
381 /* fire moudedown and click event on given element */
382 function _clickElement(elem)
384 doc = elem.ownerDocument;
385 view = elem.contentWindow;
387 var evObj = doc.createEvent("MouseEvents");
388 evObj.initMouseEvent("mousedown", true, true, view, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null);
389 elem.dispatchEvent(evObj);
391 var evObj = doc.createEvent("MouseEvents");
392 evObj.initMouseEvent("click", true, true, view, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null);
393 elem.dispatchEvent(evObj);
396 /* retrieves the url of given element */
397 function _getElemtSource(elem)
399 var url = elem.href || elem.src;
400 return url;
403 hints = new Hints();