Fix for UBSan build
[platform/upstream/doxygen.git] / src / search.js
1 function convertToId(search)
2 {
3   var result = '';
4   for (i=0;i<search.length;i++)
5   {
6     var c = search.charAt(i);
7     var cn = c.charCodeAt(0);
8     if (c.match(/[a-z0-9]/))
9     {
10       result+=c;
11     }
12     else if (cn<16) 
13     {
14       result+="_0"+cn.toString(16);
15     }
16     else 
17     {
18       result+="_"+cn.toString(16);
19     }
20   }
21   return result;
22 }
23
24 function getXPos(item)
25 {
26   var x = 0;
27   if (item.offsetWidth)
28   {
29     while (item && item!=document.body)
30     {
31       x   += item.offsetLeft;
32       item = item.offsetParent;
33     }
34   }
35   return x;
36 }
37
38 function getYPos(item)
39 {
40   var y = 0;
41   if (item.offsetWidth)
42   {
43      while (item && item!=document.body)
44      {
45        y   += item.offsetTop;
46        item = item.offsetParent;
47      }
48   }
49   return y;
50 }
51
52 /* A class handling everything associated with the search panel.
53
54    Parameters:
55    name - The name of the global variable that will be 
56           storing this instance.  Is needed to be able to set timeouts.
57    resultPath - path to use for external files
58 */
59 function SearchBox(name, resultsPath, inFrame, label)
60 {
61   if (!name || !resultsPath) {  alert("Missing parameters to SearchBox."); }
62    
63   // ---------- Instance variables
64   this.name                  = name;
65   this.resultsPath           = resultsPath;
66   this.keyTimeout            = 0;
67   this.keyTimeoutLength      = 500;
68   this.closeSelectionTimeout = 300;
69   this.lastSearchValue       = "";
70   this.lastResultsPage       = "";
71   this.hideTimeout           = 0;
72   this.searchIndex           = 0;
73   this.searchActive          = false;
74   this.insideFrame           = inFrame;
75   this.searchLabel           = label;
76
77   // ----------- DOM Elements
78
79   this.DOMSearchField = function()
80   {  return document.getElementById("MSearchField");  }
81
82   this.DOMSearchSelect = function()
83   {  return document.getElementById("MSearchSelect");  }
84
85   this.DOMSearchSelectWindow = function()
86   {  return document.getElementById("MSearchSelectWindow");  }
87
88   this.DOMPopupSearchResults = function()
89   {  return document.getElementById("MSearchResults");  }
90
91   this.DOMPopupSearchResultsWindow = function()
92   {  return document.getElementById("MSearchResultsWindow");  }
93
94   this.DOMSearchClose = function()
95   {  return document.getElementById("MSearchClose"); }
96
97   this.DOMSearchBox = function()
98   {  return document.getElementById("MSearchBox");  }
99
100   // ------------ Event Handlers
101
102   // Called when focus is added or removed from the search field.
103   this.OnSearchFieldFocus = function(isActive)
104   {
105     this.Activate(isActive);
106   }
107
108   this.OnSearchSelectShow = function()
109   {
110     var searchSelectWindow = this.DOMSearchSelectWindow();
111     var searchField        = this.DOMSearchSelect();
112
113     if (this.insideFrame)
114     {
115       var left = getXPos(searchField);
116       var top  = getYPos(searchField);
117       left += searchField.offsetWidth + 6;
118       top += searchField.offsetHeight;
119
120       // show search selection popup
121       searchSelectWindow.style.display='block';
122       left -= searchSelectWindow.offsetWidth;
123       searchSelectWindow.style.left =  left + 'px';
124       searchSelectWindow.style.top  =  top  + 'px';
125     }
126     else
127     {
128       var left = getXPos(searchField);
129       var top  = getYPos(searchField);
130       top += searchField.offsetHeight;
131
132       // show search selection popup
133       searchSelectWindow.style.display='block';
134       searchSelectWindow.style.left =  left + 'px';
135       searchSelectWindow.style.top  =  top  + 'px';
136     }
137
138     // stop selection hide timer
139     if (this.hideTimeout) 
140     {
141       clearTimeout(this.hideTimeout);
142       this.hideTimeout=0;
143     }
144     return false; // to avoid "image drag" default event
145   }
146
147   this.OnSearchSelectHide = function()
148   {
149     this.hideTimeout = setTimeout(this.name +".CloseSelectionWindow()",
150                                   this.closeSelectionTimeout);
151   }
152
153   // Called when the content of the search field is changed.
154   this.OnSearchFieldChange = function(evt)
155   {
156     if (this.keyTimeout) // kill running timer
157     {
158       clearTimeout(this.keyTimeout);
159       this.keyTimeout = 0;
160     }
161
162     var e  = (evt) ? evt : window.event; // for IE
163     if (e.keyCode==40 || e.keyCode==13)
164     {
165       if (e.shiftKey==1)
166       {
167         this.OnSearchSelectShow();
168         var win=this.DOMSearchSelectWindow(); 
169         for (i=0;i<win.childNodes.length;i++)
170         {
171           var child = win.childNodes[i]; // get span within a
172           if (child.className=='SelectItem')
173           {
174             child.focus();
175             return;
176           }
177         }
178         return;
179       }
180       else if (window.frames.MSearchResults.searchResults)
181       {
182         var elem = window.frames.MSearchResults.searchResults.NavNext(0);
183         if (elem) elem.focus();
184       }
185     }
186     else if (e.keyCode==27) // Escape out of the search field
187     {
188       this.DOMSearchField().blur();
189       this.DOMPopupSearchResultsWindow().style.display = 'none';
190       this.DOMSearchClose().style.display = 'none';
191       this.lastSearchValue = '';
192       this.Activate(false);
193       return;
194     }
195
196     // strip whitespaces
197     var searchValue = this.DOMSearchField().value.replace(/ +/g, "");
198
199     if (searchValue != this.lastSearchValue) // search value has changed
200     {
201       if (searchValue != "") // non-empty search
202       {
203         // set timer for search update
204         this.keyTimeout = setTimeout(this.name + '.Search()',
205                                      this.keyTimeoutLength);
206       }
207       else // empty search field
208       {
209         this.DOMPopupSearchResultsWindow().style.display = 'none';
210         this.DOMSearchClose().style.display = 'none';
211         this.lastSearchValue = '';
212       }
213     }
214   }
215
216   this.SelectItemCount = function(id)
217   {
218     var count=0;
219     var win=this.DOMSearchSelectWindow(); 
220     for (i=0;i<win.childNodes.length;i++)
221     {
222       var child = win.childNodes[i]; // get span within a
223       if (child.className=='SelectItem')
224       {
225         count++;
226       }
227     }
228     return count;
229   }
230
231   this.SelectItemSet = function(id)
232   {
233     var i,j=0;
234     var win=this.DOMSearchSelectWindow(); 
235     for (i=0;i<win.childNodes.length;i++)
236     {
237       var child = win.childNodes[i]; // get span within a
238       if (child.className=='SelectItem')
239       {
240         var node = child.firstChild;
241         if (j==id)
242         {
243           node.innerHTML='&#8226;';
244         }
245         else
246         {
247           node.innerHTML='&#160;';
248         }
249         j++;
250       }
251     }
252   }
253
254   // Called when an search filter selection is made.
255   // set item with index id as the active item
256   this.OnSelectItem = function(id)
257   {
258     this.searchIndex = id;
259     this.SelectItemSet(id);
260     var searchValue = this.DOMSearchField().value.replace(/ +/g, "");
261     if (searchValue!="" && this.searchActive) // something was found -> do a search
262     {
263       this.Search();
264     }
265   }
266
267   this.OnSearchSelectKey = function(evt)
268   {
269     var e = (evt) ? evt : window.event; // for IE
270     if (e.keyCode==40 && this.searchIndex<this.SelectItemCount()) // Down
271     {
272       this.searchIndex++;
273       this.OnSelectItem(this.searchIndex);
274     }
275     else if (e.keyCode==38 && this.searchIndex>0) // Up
276     {
277       this.searchIndex--;
278       this.OnSelectItem(this.searchIndex);
279     }
280     else if (e.keyCode==13 || e.keyCode==27)
281     {
282       this.OnSelectItem(this.searchIndex);
283       this.CloseSelectionWindow();
284       this.DOMSearchField().focus();
285     }
286     return false;
287   }
288
289   // --------- Actions
290
291   // Closes the results window.
292   this.CloseResultsWindow = function()
293   {
294     this.DOMPopupSearchResultsWindow().style.display = 'none';
295     this.DOMSearchClose().style.display = 'none';
296     this.Activate(false);
297   }
298
299   this.CloseSelectionWindow = function()
300   {
301     this.DOMSearchSelectWindow().style.display = 'none';
302   }
303
304   // Performs a search.
305   this.Search = function()
306   {
307     this.keyTimeout = 0;
308
309     // strip leading whitespace
310     var searchValue = this.DOMSearchField().value.replace(/^ +/, "");
311
312     var code = searchValue.toLowerCase().charCodeAt(0);
313     var hexCode;
314     if (code<16) 
315     {
316       hexCode="0"+code.toString(16);
317     }
318     else 
319     {
320       hexCode=code.toString(16);
321     }
322
323     var resultsPage;
324     var resultsPageWithSearch;
325     var hasResultsPage;
326
327     if (indexSectionsWithContent[this.searchIndex].charAt(code) == '1')
328     {
329        resultsPage = this.resultsPath + '/' + indexSectionNames[this.searchIndex] + '_' + hexCode + '.html';
330        resultsPageWithSearch = resultsPage+'?'+escape(searchValue);
331        hasResultsPage = true;
332     }
333     else // nothing available for this search term
334     {
335        resultsPage = this.resultsPath + '/nomatches.html';
336        resultsPageWithSearch = resultsPage;
337        hasResultsPage = false;
338     }
339
340     window.frames.MSearchResults.location = resultsPageWithSearch;  
341     var domPopupSearchResultsWindow = this.DOMPopupSearchResultsWindow();
342
343     if (domPopupSearchResultsWindow.style.display!='block')
344     {
345        var domSearchBox = this.DOMSearchBox();
346        this.DOMSearchClose().style.display = 'inline';
347        if (this.insideFrame)
348        {
349          var domPopupSearchResults = this.DOMPopupSearchResults();
350          domPopupSearchResultsWindow.style.position = 'relative';
351          domPopupSearchResultsWindow.style.display  = 'block';
352          var width = document.body.clientWidth - 8; // the -8 is for IE :-(
353          domPopupSearchResultsWindow.style.width    = width + 'px';
354          domPopupSearchResults.style.width          = width + 'px';
355        }
356        else
357        {
358          var domPopupSearchResults = this.DOMPopupSearchResults();
359          var left = getXPos(domSearchBox) + 150; // domSearchBox.offsetWidth;
360          var top  = getYPos(domSearchBox) + 20;  // domSearchBox.offsetHeight + 1;
361          domPopupSearchResultsWindow.style.display = 'block';
362          left -= domPopupSearchResults.offsetWidth;
363          domPopupSearchResultsWindow.style.top     = top  + 'px';
364          domPopupSearchResultsWindow.style.left    = left + 'px';
365        }
366     }
367
368     this.lastSearchValue = searchValue;
369     this.lastResultsPage = resultsPage;
370   }
371
372   // -------- Activation Functions
373
374   // Activates or deactivates the search panel, resetting things to 
375   // their default values if necessary. 
376   this.Activate = function(isActive)
377   {
378     if (isActive || // open it
379         this.DOMPopupSearchResultsWindow().style.display == 'block' 
380        )
381     {
382       this.DOMSearchBox().className = 'MSearchBoxActive';
383
384       var searchField = this.DOMSearchField();
385
386       if (searchField.value == this.searchLabel) // clear "Search" term upon entry
387       {  
388         searchField.value = '';  
389         this.searchActive = true;
390       }
391     }
392     else if (!isActive) // directly remove the panel
393     {
394       this.DOMSearchBox().className = 'MSearchBoxInactive';
395       this.DOMSearchField().value   = this.searchLabel;
396       this.searchActive             = false;
397       this.lastSearchValue          = ''
398       this.lastResultsPage          = '';
399     }
400   }
401 }
402
403 // -----------------------------------------------------------------------
404
405 // The class that handles everything on the search results page.
406 function SearchResults(name)
407 {
408     // The number of matches from the last run of <Search()>.
409     this.lastMatchCount = 0;
410     this.lastKey = 0;
411     this.repeatOn = false;
412
413     // Toggles the visibility of the passed element ID.
414     this.FindChildElement = function(id)
415     {
416       var parentElement = document.getElementById(id);
417       var element = parentElement.firstChild;
418
419       while (element && element!=parentElement)
420       {
421         if (element.nodeName == 'DIV' && element.className == 'SRChildren')
422         {
423           return element;
424         }
425
426         if (element.nodeName == 'DIV' && element.hasChildNodes())
427         {  
428            element = element.firstChild;  
429         }
430         else if (element.nextSibling)
431         {  
432            element = element.nextSibling;  
433         }
434         else
435         {
436           do
437           {
438             element = element.parentNode;
439           }
440           while (element && element!=parentElement && !element.nextSibling);
441
442           if (element && element!=parentElement)
443           {  
444             element = element.nextSibling;  
445           }
446         }
447       }
448     }
449
450     this.Toggle = function(id)
451     {
452       var element = this.FindChildElement(id);
453       if (element)
454       {
455         if (element.style.display == 'block')
456         {
457           element.style.display = 'none';
458         }
459         else
460         {
461           element.style.display = 'block';
462         }
463       }
464     }
465
466     // Searches for the passed string.  If there is no parameter,
467     // it takes it from the URL query.
468     //
469     // Always returns true, since other documents may try to call it
470     // and that may or may not be possible.
471     this.Search = function(search)
472     {
473       if (!search) // get search word from URL
474       {
475         search = window.location.search;
476         search = search.substring(1);  // Remove the leading '?'
477         search = unescape(search);
478       }
479
480       search = search.replace(/^ +/, ""); // strip leading spaces
481       search = search.replace(/ +$/, ""); // strip trailing spaces
482       search = search.toLowerCase();
483       search = convertToId(search);
484
485       var resultRows = document.getElementsByTagName("div");
486       var matches = 0;
487
488       var i = 0;
489       while (i < resultRows.length)
490       {
491         var row = resultRows.item(i);
492         if (row.className == "SRResult")
493         {
494           var rowMatchName = row.id.toLowerCase();
495           rowMatchName = rowMatchName.replace(/^sr\d*_/, ''); // strip 'sr123_'
496
497           if (search.length<=rowMatchName.length && 
498              rowMatchName.substr(0, search.length)==search)
499           {
500             row.style.display = 'block';
501             matches++;
502           }
503           else
504           {
505             row.style.display = 'none';
506           }
507         }
508         i++;
509       }
510       document.getElementById("Searching").style.display='none';
511       if (matches == 0) // no results
512       {
513         document.getElementById("NoMatches").style.display='block';
514       }
515       else // at least one result
516       {
517         document.getElementById("NoMatches").style.display='none';
518       }
519       this.lastMatchCount = matches;
520       return true;
521     }
522
523     // return the first item with index index or higher that is visible
524     this.NavNext = function(index)
525     {
526       var focusItem;
527       while (1)
528       {
529         var focusName = 'Item'+index;
530         focusItem = document.getElementById(focusName);
531         if (focusItem && focusItem.parentNode.parentNode.style.display=='block')
532         {
533           break;
534         }
535         else if (!focusItem) // last element
536         {
537           break;
538         }
539         focusItem=null;
540         index++;
541       }
542       return focusItem;
543     }
544
545     this.NavPrev = function(index)
546     {
547       var focusItem;
548       while (1)
549       {
550         var focusName = 'Item'+index;
551         focusItem = document.getElementById(focusName);
552         if (focusItem && focusItem.parentNode.parentNode.style.display=='block')
553         {
554           break;
555         }
556         else if (!focusItem) // last element
557         {
558           break;
559         }
560         focusItem=null;
561         index--;
562       }
563       return focusItem;
564     }
565
566     this.ProcessKeys = function(e)
567     {
568       if (e.type == "keydown") 
569       {
570         this.repeatOn = false;
571         this.lastKey = e.keyCode;
572       }
573       else if (e.type == "keypress")
574       {
575         if (!this.repeatOn)
576         {
577           if (this.lastKey) this.repeatOn = true;
578           return false; // ignore first keypress after keydown
579         }
580       }
581       else if (e.type == "keyup")
582       {
583         this.lastKey = 0;
584         this.repeatOn = false;
585       }
586       return this.lastKey!=0;
587     }
588
589     this.Nav = function(evt,itemIndex) 
590     {
591       var e  = (evt) ? evt : window.event; // for IE
592       if (e.keyCode==13) return true;
593       if (!this.ProcessKeys(e)) return false;
594
595       if (this.lastKey==38) // Up
596       {
597         var newIndex = itemIndex-1;
598         var focusItem = this.NavPrev(newIndex);
599         if (focusItem)
600         {
601           var child = this.FindChildElement(focusItem.parentNode.parentNode.id);
602           if (child && child.style.display == 'block') // children visible
603           { 
604             var n=0;
605             var tmpElem;
606             while (1) // search for last child
607             {
608               tmpElem = document.getElementById('Item'+newIndex+'_c'+n);
609               if (tmpElem)
610               {
611                 focusItem = tmpElem;
612               }
613               else // found it!
614               {
615                 break;
616               }
617               n++;
618             }
619           }
620         }
621         if (focusItem)
622         {
623           focusItem.focus();
624         }
625         else // return focus to search field
626         {
627            parent.document.getElementById("MSearchField").focus();
628         }
629       }
630       else if (this.lastKey==40) // Down
631       {
632         var newIndex = itemIndex+1;
633         var focusItem;
634         var item = document.getElementById('Item'+itemIndex);
635         var elem = this.FindChildElement(item.parentNode.parentNode.id);
636         if (elem && elem.style.display == 'block') // children visible
637         {
638           focusItem = document.getElementById('Item'+itemIndex+'_c0');
639         }
640         if (!focusItem) focusItem = this.NavNext(newIndex);
641         if (focusItem)  focusItem.focus();
642       }
643       else if (this.lastKey==39) // Right
644       {
645         var item = document.getElementById('Item'+itemIndex);
646         var elem = this.FindChildElement(item.parentNode.parentNode.id);
647         if (elem) elem.style.display = 'block';
648       }
649       else if (this.lastKey==37) // Left
650       {
651         var item = document.getElementById('Item'+itemIndex);
652         var elem = this.FindChildElement(item.parentNode.parentNode.id);
653         if (elem) elem.style.display = 'none';
654       }
655       else if (this.lastKey==27) // Escape
656       {
657         parent.searchBox.CloseResultsWindow();
658         parent.document.getElementById("MSearchField").focus();
659       }
660       else if (this.lastKey==13) // Enter
661       {
662         return true;
663       }
664       return false;
665     }
666
667     this.NavChild = function(evt,itemIndex,childIndex)
668     {
669       var e  = (evt) ? evt : window.event; // for IE
670       if (e.keyCode==13) return true;
671       if (!this.ProcessKeys(e)) return false;
672
673       if (this.lastKey==38) // Up
674       {
675         if (childIndex>0)
676         {
677           var newIndex = childIndex-1;
678           document.getElementById('Item'+itemIndex+'_c'+newIndex).focus();
679         }
680         else // already at first child, jump to parent
681         {
682           document.getElementById('Item'+itemIndex).focus();
683         }
684       }
685       else if (this.lastKey==40) // Down
686       {
687         var newIndex = childIndex+1;
688         var elem = document.getElementById('Item'+itemIndex+'_c'+newIndex);
689         if (!elem) // last child, jump to parent next parent
690         {
691           elem = this.NavNext(itemIndex+1);
692         }
693         if (elem)
694         {
695           elem.focus();
696         } 
697       }
698       else if (this.lastKey==27) // Escape
699       {
700         parent.searchBox.CloseResultsWindow();
701         parent.document.getElementById("MSearchField").focus();
702       }
703       else if (this.lastKey==13) // Enter
704       {
705         return true;
706       }
707       return false;
708     }
709 }
710
711 function setKeyActions(elem,action)
712 {
713   elem.setAttribute('onkeydown',action);
714   elem.setAttribute('onkeypress',action);
715   elem.setAttribute('onkeyup',action);
716 }
717
718 function setClassAttr(elem,attr)
719 {
720   elem.setAttribute('class',attr);
721   elem.setAttribute('className',attr);
722 }
723
724 function createResults()
725 {
726   var results = document.getElementById("SRResults");
727   for (var e=0; e<searchData.length; e++)
728   {
729     var id = searchData[e][0];
730     var srResult = document.createElement('div');
731     srResult.setAttribute('id','SR_'+id);
732     setClassAttr(srResult,'SRResult');
733     var srEntry = document.createElement('div');
734     setClassAttr(srEntry,'SREntry');
735     var srLink = document.createElement('a');
736     srLink.setAttribute('id','Item'+e);
737     setKeyActions(srLink,'return searchResults.Nav(event,'+e+')');
738     setClassAttr(srLink,'SRSymbol');
739     srLink.innerHTML = searchData[e][1][0];
740     srEntry.appendChild(srLink);
741     if (searchData[e][1].length==2) // single result
742     {
743       srLink.setAttribute('href',searchData[e][1][1][0]);
744       if (searchData[e][1][1][1])
745       {
746        srLink.setAttribute('target','_parent');
747       }
748       var srScope = document.createElement('span');
749       setClassAttr(srScope,'SRScope');
750       srScope.innerHTML = searchData[e][1][1][2];
751       srEntry.appendChild(srScope);
752     }
753     else // multiple results
754     {
755       srLink.setAttribute('href','javascript:searchResults.Toggle("SR_'+id+'")');
756       var srChildren = document.createElement('div');
757       setClassAttr(srChildren,'SRChildren');
758       for (var c=0; c<searchData[e][1].length-1; c++)
759       {
760         var srChild = document.createElement('a');
761         srChild.setAttribute('id','Item'+e+'_c'+c);
762         setKeyActions(srChild,'return searchResults.NavChild(event,'+e+','+c+')');
763         setClassAttr(srChild,'SRScope');
764         srChild.setAttribute('href',searchData[e][1][c+1][0]);
765         if (searchData[e][1][c+1][1])
766         {
767          srChild.setAttribute('target','_parent');
768         }
769         srChild.innerHTML = searchData[e][1][c+1][2];
770         srChildren.appendChild(srChild);
771       }
772       srEntry.appendChild(srChildren);
773     }
774     srResult.appendChild(srEntry);
775     results.appendChild(srResult);
776   }
777 }
778