docs.js revision 235f4d4f8580a56d553e19c1beab1f8c22132e84
1var classesNav;
2var devdocNav;
3var sidenav;
4var cookie_namespace = 'android_developer';
5var NAV_PREF_TREE = "tree";
6var NAV_PREF_PANELS = "panels";
7var nav_pref;
8var isMobile = false; // true if mobile, so we can adjust some layout
9
10var basePath = getBaseUri(location.pathname);
11var SITE_ROOT = toRoot + basePath.substring(1,basePath.indexOf("/",1));
12
13
14/******  ON LOAD SET UP STUFF *********/
15
16var navBarIsFixed = false;
17$(document).ready(function() {
18  // init the fullscreen toggle click event
19  $('#nav-swap .fullscreen').click(function(){
20    if ($(this).hasClass('disabled')) {
21      toggleFullscreen(true);
22    } else {
23      toggleFullscreen(false);
24    }
25  });
26
27  // initialize the divs with custom scrollbars
28  $('.scroll-pane').jScrollPane( {verticalGutter:0} );
29
30  // add HRs below all H2s (except for a few other h2 variants)
31  $('h2').not('#qv h2').not('#tb h2').not('.sidebox h2').not('#devdoc-nav h2').css({marginBottom:0}).after('<hr/>');
32
33  // set search's onkeyup handler here so we can show suggestions
34  // even while search results are visible
35  $("#search_autocomplete").keyup(function() {return search_changed(event, false, toRoot)});
36
37  // set up the search close button
38  $('.search .close').click(function() {
39    $searchInput = $('#search_autocomplete');
40    $searchInput.attr('value', '');
41    $(this).addClass("hide");
42    $("#search-container").removeClass('active');
43    $("#search_autocomplete").blur();
44    search_focus_changed($searchInput.get(), false);  // see search_autocomplete.js
45    hideResults();  // see search_autocomplete.js
46  });
47  $('.search').click(function() {
48    if (!$('#search_autocomplete').is(":focused")) {
49        $('#search_autocomplete').focus();
50    }
51  });
52
53  // Set up quicknav
54  var quicknav_open = false;
55  $("#btn-quicknav").click(function() {
56    if (quicknav_open) {
57      $(this).removeClass('active');
58      quicknav_open = false;
59      collapse();
60    } else {
61      $(this).addClass('active');
62      quicknav_open = true;
63      expand();
64    }
65  })
66
67  var expand = function() {
68   $('#header-wrap').addClass('quicknav');
69   $('#quicknav').stop().show().animate({opacity:'1'});
70  }
71
72  var collapse = function() {
73    $('#quicknav').stop().animate({opacity:'0'}, 100, function() {
74      $(this).hide();
75      $('#header-wrap').removeClass('quicknav');
76    });
77  }
78
79
80  //Set up search
81  $("#search_autocomplete").focus(function() {
82    $("#search-container").addClass('active');
83  })
84  $("#search-container").mouseover(function() {
85    $("#search-container").addClass('active');
86    $("#search_autocomplete").focus();
87  })
88  $("#search-container").mouseout(function() {
89    if ($("#search_autocomplete").is(":focus")) return;
90    if ($("#search_autocomplete").val() == '') {
91      setTimeout(function(){
92        $("#search-container").removeClass('active');
93        $("#search_autocomplete").blur();
94      },250);
95    }
96  })
97  $("#search_autocomplete").blur(function() {
98    if ($("#search_autocomplete").val() == '') {
99      $("#search-container").removeClass('active');
100    }
101  })
102
103
104  // prep nav expandos
105  var pagePath = document.location.pathname;
106  // account for intl docs by removing the intl/*/ path
107  if (pagePath.indexOf("/intl/") == 0) {
108    pagePath = pagePath.substr(pagePath.indexOf("/",6)); // start after intl/ to get last /
109  }
110
111  if (pagePath.indexOf(SITE_ROOT) == 0) {
112    if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') {
113      pagePath += 'index.html';
114    }
115  }
116
117  if (SITE_ROOT.match(/\.\.\//) || SITE_ROOT == '') {
118    // If running locally, SITE_ROOT will be a relative path, so account for that by
119    // finding the relative URL to this page. This will allow us to find links on the page
120    // leading back to this page.
121    var pathParts = pagePath.split('/');
122    var relativePagePathParts = [];
123    var upDirs = (SITE_ROOT.match(/(\.\.\/)+/) || [''])[0].length / 3;
124    for (var i = 0; i < upDirs; i++) {
125      relativePagePathParts.push('..');
126    }
127    for (var i = 0; i < upDirs; i++) {
128      relativePagePathParts.push(pathParts[pathParts.length - (upDirs - i) - 1]);
129    }
130    relativePagePathParts.push(pathParts[pathParts.length - 1]);
131    pagePath = relativePagePathParts.join('/');
132  } else {
133    // Otherwise the page path is already an absolute URL
134  }
135
136  // select current page in sidenav and set up prev/next links if they exist
137  var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
138  if ($selNavLink.length) {
139    $selListItem = $selNavLink.closest('li');
140
141    $selListItem.addClass('selected');
142    $selListItem.closest('li.nav-section').addClass('expanded');
143    $selListItem.closest('li.nav-section').children('ul').show();
144    $selListItem.closest('li.nav-section').parent().closest('li.nav-section').addClass('expanded');
145    $selListItem.closest('li.nav-section').parent().closest('ul').show();
146
147
148  //  $selListItem.closest('li.nav-section').closest('li.nav-section').addClass('expanded');
149  //  $selListItem.closest('li.nav-section').closest('li.nav-section').children('ul').show();
150
151    // set up prev links
152    var $prevLink = [];
153    var $prevListItem = $selListItem.prev('li');
154
155    var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
156false; // navigate across topic boundaries only in design docs
157    if ($prevListItem.length) {
158      if ($prevListItem.hasClass('nav-section')) {
159        if (crossBoundaries) {
160          // jump to last topic of previous section
161          $prevLink = $prevListItem.find('a:last');
162        }
163      } else {
164        // jump to previous topic in this section
165        $prevLink = $prevListItem.find('a:eq(0)');
166      }
167    } else {
168      // jump to this section's index page (if it exists)
169      var $parentListItem = $selListItem.parents('li');
170      $prevLink = $selListItem.parents('li').find('a');
171
172      // except if cross boundaries aren't allowed, and we're at the top of a section already
173      // (and there's another parent)
174      if (!crossBoundaries && $parentListItem.hasClass('nav-section')
175                           && $selListItem.hasClass('nav-section')) {
176        $prevLink = [];
177      }
178    }
179
180    if ($prevLink.length) {
181      var prevHref = $prevLink.attr('href');
182      if (prevHref == SITE_ROOT + 'index.html') {
183        // Don't show Previous when it leads to the homepage
184      } else {
185        $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
186      }
187    }
188
189    // set up next links
190    var $nextLink = [];
191    var startCourse = false;
192    var startClass = false;
193    var training = $(".next-class-link").length; // decides whether to provide "next class" link
194    var isCrossingBoundary = false;
195
196    if ($selListItem.hasClass('nav-section')) {
197      // we're on an index page, jump to the first topic
198      $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
199
200      // if there aren't any children, go to the next section (required for About pages)
201      if($nextLink.length == 0) {
202        $nextLink = $selListItem.next('li').find('a');
203      } else if ($('.topic-start-link').length) {
204        // as long as there's a child link and there is a "topic start link" (we're on a landing)
205        // then set the landing page "start link" text to be the first doc title
206        $('.topic-start-link').text($nextLink.text().toUpperCase());
207      }
208
209      // Handle some Training specialties
210      if ($selListItem.parent().is("#nav") && $(".start-course-link").length) {
211        // this means we're at the very top of the TOC hierarchy
212        startCourse = true;
213      } else if ($(".start-class-link").length) {
214        // this means this page has children but is not at the top (it's a class, not a course)
215        startClass = true;
216      }
217    } else {
218      // jump to the next topic in this section (if it exists)
219      $nextLink = $selListItem.next('li').find('a:eq(0)');
220      if (!$nextLink.length) {
221        if (crossBoundaries || training) {
222          // no more topics in this section, jump to the first topic in the next section
223          $nextLink = $selListItem.parents('li:eq(0)').next('li.nav-section').find('a:eq(0)');
224          isCrossingBoundary = true;
225        }
226      }
227    }
228    if ($nextLink.length) {
229      if (startCourse || startClass) {
230        if (startCourse) {
231          $('.start-course-link').attr('href', $nextLink.attr('href')).removeClass("hide");
232        } else {
233          $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
234        }
235        // if there's no training bar (below the start button),
236        // then we need to add a bottom border to button
237        if (!$("#tb").length) {
238          $('.start-course-link').css({'border-bottom':'1px solid #DADADA'});
239          $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
240        }
241      } else if (training && isCrossingBoundary) {
242        $('.content-footer.next-class').show();
243        $('.next-page-link').attr('href','')
244                            .removeClass("hide").addClass("disabled")
245                            .click(function() { return false; });
246
247        $('.next-class-link').attr('href',$nextLink.attr('href'))
248                            .removeClass("hide").append($nextLink.html());
249        $('.next-class-link').find('.new').empty();
250      } else {
251        $('.next-page-link').attr('href', $nextLink.attr('href')).removeClass("hide");
252      }
253    }
254
255  }
256
257
258
259  // Set up expand/collapse behavior
260  $('#nav li.nav-section .nav-section-header').click(function() {
261    var section = $(this).closest('li.nav-section');
262    if (section.hasClass('expanded')) {
263    /* hide me */
264    //  if (section.hasClass('selected') || section.find('li').hasClass('selected')) {
265   //   /* but not if myself or my descendents are selected */
266   //     return;
267    //  }
268      section.children('ul').slideUp(250, function() {
269        section.closest('li').removeClass('expanded');
270        resizeNav();
271      });
272    } else {
273    /* show me */
274      // first hide all other siblings
275      var $others = $('li.nav-section.expanded', $(this).closest('ul'));
276      $others.removeClass('expanded').children('ul').slideUp(250);
277
278      // now expand me
279      section.closest('li').addClass('expanded');
280      section.children('ul').slideDown(250, function() {
281        resizeNav();
282      });
283    }
284  });
285
286  $(".scroll-pane").scroll(function(event) {
287      event.preventDefault();
288      return false;
289  });
290
291  /* Resize nav height when window height changes */
292  $(window).resize(function() {
293    if ($('#side-nav').length == 0) return;
294    var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
295    setNavBarLeftPos(); // do this even if sidenav isn't fixed because it could become fixed
296    // make sidenav behave when resizing the window and side-scolling is a concern
297    if (navBarIsFixed) {
298      if ((stylesheet.attr("disabled") == "disabled") || stylesheet.length == 0) {
299        updateSideNavPosition();
300      } else {
301        updateSidenavFullscreenWidth();
302      }
303    }
304    resizeNav();
305  });
306
307
308  // Set up fixed navbar
309  var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
310  $(window).scroll(function(event) {
311    if ($('#side-nav').length == 0) return;
312    if (event.target.nodeName == "DIV") {
313      // Dump scroll event if the target is a DIV, because that means the event is coming
314      // from a scrollable div and so there's no need to make adjustments to our layout
315      return;
316    }
317    var scrollTop = $(window).scrollTop();
318    var headerHeight = $('#header').outerHeight();
319    var subheaderHeight = $('#nav-x').outerHeight();
320    var searchResultHeight = $('#searchResults').is(":visible") ?
321                             $('#searchResults').outerHeight() : 0;
322    var totalHeaderHeight = headerHeight + subheaderHeight + searchResultHeight;
323    var navBarShouldBeFixed = scrollTop > totalHeaderHeight;
324
325    var scrollLeft = $(window).scrollLeft();
326    // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
327    if (navBarIsFixed && (scrollLeft != prevScrollLeft)) {
328      updateSideNavPosition();
329      prevScrollLeft = scrollLeft;
330    }
331
332    // Don't continue if the header is sufficently far away
333    // (to avoid intensive resizing that slows scrolling)
334    if (navBarIsFixed && navBarShouldBeFixed) {
335      return;
336    }
337
338    if (navBarIsFixed != navBarShouldBeFixed) {
339      if (navBarShouldBeFixed) {
340        // make it fixed
341        var width = $('#devdoc-nav').width();
342        $('#devdoc-nav')
343            .addClass('fixed')
344            .css({'width':width+'px'})
345            .prependTo('#body-content');
346        // add neato "back to top" button
347        $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
348
349        // update the sidenaav position for side scrolling
350        updateSideNavPosition();
351      } else {
352        // make it static again
353        $('#devdoc-nav')
354            .removeClass('fixed')
355            .css({'width':'auto','margin':''})
356            .prependTo('#side-nav');
357        $('#devdoc-nav a.totop').hide();
358      }
359      navBarIsFixed = navBarShouldBeFixed;
360    }
361
362    resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
363  });
364
365
366  var navBarLeftPos;
367  if ($('#devdoc-nav').length) {
368    setNavBarLeftPos();
369  }
370
371
372  // Stop expand/collapse behavior when clicking on nav section links (since we're navigating away
373  // from the page)
374  $('.nav-section-header').find('a:eq(0)').click(function(evt) {
375    window.location.href = $(this).attr('href');
376    return false;
377  });
378
379  // Set up play-on-hover <video> tags.
380  $('video.play-on-hover').bind('click', function(){
381    $(this).get(0).load(); // in case the video isn't seekable
382    $(this).get(0).play();
383  });
384
385  // Set up tooltips
386  var TOOLTIP_MARGIN = 10;
387  $('acronym').each(function() {
388    var $target = $(this);
389    var $tooltip = $('<div>')
390        .addClass('tooltip-box')
391        .text($target.attr('title'))
392        .hide()
393        .appendTo('body');
394    $target.removeAttr('title');
395
396    $target.hover(function() {
397      // in
398      var targetRect = $target.offset();
399      targetRect.width = $target.width();
400      targetRect.height = $target.height();
401
402      $tooltip.css({
403        left: targetRect.left,
404        top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
405      });
406      $tooltip.addClass('below');
407      $tooltip.show();
408    }, function() {
409      // out
410      $tooltip.hide();
411    });
412  });
413
414  // Set up <h2> deeplinks
415  $('h2').click(function() {
416    var id = $(this).attr('id');
417    if (id) {
418      document.location.hash = id;
419    }
420  });
421
422  //Loads the +1 button
423  var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
424  po.src = 'https://apis.google.com/js/plusone.js';
425  var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
426
427
428  // Revise the sidenav widths to make room for the scrollbar
429  // which avoids the visible width from changing each time the bar appears
430  var $sidenav = $("#side-nav");
431  var sidenav_width = parseInt($sidenav.innerWidth());
432
433  $("#devdoc-nav  #nav").css("width", sidenav_width - 4 + "px"); // 4px is scrollbar width
434
435
436  $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
437
438  if ($(".scroll-pane").length > 1) {
439    // Check if there's a user preference for the panel heights
440    var cookieHeight = readCookie("reference_height");
441    if (cookieHeight) {
442      restoreHeight(cookieHeight);
443    }
444  }
445
446  resizeNav();
447
448
449});
450
451
452
453function toggleFullscreen(enable) {
454  var delay = 20;
455  var enabled = true;
456  var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
457  if (enable) {
458    // Currently NOT USING fullscreen; enable fullscreen
459    stylesheet.removeAttr('disabled');
460    $('#nav-swap .fullscreen').removeClass('disabled');
461    $('#devdoc-nav').css({left:''});
462    setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch
463    enabled = true;
464  } else {
465    // Currently USING fullscreen; disable fullscreen
466    stylesheet.attr('disabled', 'disabled');
467    $('#nav-swap .fullscreen').addClass('disabled');
468    setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch
469    enabled = false;
470  }
471  writeCookie("fullscreen", enabled, null, null);
472  setNavBarLeftPos();
473  resizeNav(delay);
474  updateSideNavPosition();
475  setTimeout(initSidenavHeightResize,delay);
476}
477
478
479function setNavBarLeftPos() {
480  navBarLeftPos = $('#body-content').offset().left;
481}
482
483
484function updateSideNavPosition() {
485  var newLeft = $(window).scrollLeft() - navBarLeftPos;
486  $('#devdoc-nav').css({left: -newLeft});
487  $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('margin-left')))});
488}
489
490
491
492
493
494
495
496
497// TODO: use $(document).ready instead
498function addLoadEvent(newfun) {
499  var current = window.onload;
500  if (typeof window.onload != 'function') {
501    window.onload = newfun;
502  } else {
503    window.onload = function() {
504      current();
505      newfun();
506    }
507  }
508}
509
510var agent = navigator['userAgent'].toLowerCase();
511// If a mobile phone, set flag and do mobile setup
512if ((agent.indexOf("mobile") != -1) ||      // android, iphone, ipod
513    (agent.indexOf("blackberry") != -1) ||
514    (agent.indexOf("webos") != -1) ||
515    (agent.indexOf("mini") != -1)) {        // opera mini browsers
516  isMobile = true;
517}
518
519
520/* loads the lists.js file to the page.
521Loading this in the head was slowing page load time */
522addLoadEvent( function() {
523  var lists = document.createElement("script");
524  lists.setAttribute("type","text/javascript");
525  lists.setAttribute("src", toRoot+"reference/lists.js");
526  document.getElementsByTagName("head")[0].appendChild(lists);
527} );
528
529
530addLoadEvent( function() {
531  $("pre:not(.no-pretty-print)").addClass("prettyprint");
532  prettyPrint();
533} );
534
535function init() {
536  //resizeNav();
537
538  resizePackagesNav = $("#resize-packages-nav");
539  classesNav = $("#classes-nav");
540  devdocNav = $("#devdoc-nav");
541
542  var cookiePath = "";
543  if (location.href.indexOf("/reference/") != -1) {
544    cookiePath = "reference_";
545  } else if (location.href.indexOf("/guide/") != -1) {
546    cookiePath = "guide_";
547  } else if (location.href.indexOf("/tools/") != -1) {
548    cookiePath = "tools_";
549  } else if (location.href.indexOf("/training/") != -1) {
550    cookiePath = "training_";
551  } else if (location.href.indexOf("/design/") != -1) {
552    cookiePath = "design_";
553  } else if (location.href.indexOf("/distribute/") != -1) {
554    cookiePath = "distribute_";
555  }
556}
557
558
559
560/* ######### RESIZE THE SIDENAV HEIGHT ########## */
561
562function resizeNav(delay) {
563  var $nav = $("#devdoc-nav");
564  var $window = $(window);
565  var navHeight;
566
567  // Get the height of entire window and the total header height.
568  // Then figure out based on scroll position whether the header is visible
569  var windowHeight = $window.height();
570  var scrollTop = $window.scrollTop();
571  var headerHeight = $('#header').outerHeight();
572  var subheaderHeight = $('#nav-x').outerHeight();
573  var headerVisible = (scrollTop < (headerHeight + subheaderHeight));
574
575  // get the height of space between nav and top of window.
576  // Could be either margin or top position, depending on whether the nav is fixed.
577  var topMargin = (parseInt($nav.css('margin-top')) || parseInt($nav.css('top'))) + 1;
578  // add 1 for the #side-nav bottom margin
579
580  // Depending on whether the header is visible, set the side nav's height.
581  if (headerVisible) {
582    // The sidenav height grows as the header goes off screen
583    navHeight = windowHeight - (headerHeight + subheaderHeight - scrollTop) - topMargin;
584  } else {
585    // Once header is off screen, the nav height is almost full window height
586    navHeight = windowHeight - topMargin;
587  }
588
589
590
591  $scrollPanes = $(".scroll-pane");
592  if ($scrollPanes.length > 1) {
593    // subtract the height of the api level widget and nav swapper from the available nav height
594    navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true));
595
596    $("#swapper").css({height:navHeight + "px"});
597    if ($("#nav-tree").is(":visible")) {
598      $("#nav-tree").css({height:navHeight});
599    }
600
601    var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px";
602    //subtract 10px to account for drag bar
603
604    // if the window becomes small enough to make the class panel height 0,
605    // then the package panel should begin to shrink
606    if (parseInt(classesHeight) <= 0) {
607      $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar
608      $("#packages-nav").css({height:navHeight - 10});
609    }
610
611    $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'});
612    $("#classes-nav .jspContainer").css({height:classesHeight});
613
614
615  } else {
616    $nav.height(navHeight);
617  }
618
619  if (delay) {
620    updateFromResize = true;
621    delayedReInitScrollbars(delay);
622  } else {
623    reInitScrollbars();
624  }
625
626}
627
628var updateScrollbars = false;
629var updateFromResize = false;
630
631/* Re-initialize the scrollbars to account for changed nav size.
632 * This method postpones the actual update by a 1/4 second in order to optimize the
633 * scroll performance while the header is still visible, because re-initializing the
634 * scroll panes is an intensive process.
635 */
636function delayedReInitScrollbars(delay) {
637  // If we're scheduled for an update, but have received another resize request
638  // before the scheduled resize has occured, just ignore the new request
639  // (and wait for the scheduled one).
640  if (updateScrollbars && updateFromResize) {
641    updateFromResize = false;
642    return;
643  }
644
645  // We're scheduled for an update and the update request came from this method's setTimeout
646  if (updateScrollbars && !updateFromResize) {
647    reInitScrollbars();
648    updateScrollbars = false;
649  } else {
650    updateScrollbars = true;
651    updateFromResize = false;
652    setTimeout('delayedReInitScrollbars()',delay);
653  }
654}
655
656/* Re-initialize the scrollbars to account for changed nav size. */
657function reInitScrollbars() {
658  var pane = $(".scroll-pane").each(function(){
659    var api = $(this).data('jsp');
660    if (!api) { setTimeout(reInitScrollbars,300); return;}
661    api.reinitialise( {verticalGutter:0} );
662  });
663  $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
664}
665
666
667/* Resize the height of the nav panels in the reference,
668 * and save the new size to a cookie */
669function saveNavPanels() {
670  var basePath = getBaseUri(location.pathname);
671  var section = basePath.substring(1,basePath.indexOf("/",1));
672  writeCookie("height", resizePackagesNav.css("height"), section, null);
673}
674
675
676
677function restoreHeight(packageHeight) {
678    $("#resize-packages-nav").height(packageHeight);
679    $("#packages-nav").height(packageHeight);
680  //  var classesHeight = navHeight - packageHeight;
681 //   $("#classes-nav").css({height:classesHeight});
682  //  $("#classes-nav .jspContainer").css({height:classesHeight});
683}
684
685
686
687/* ######### END RESIZE THE SIDENAV HEIGHT ########## */
688
689
690
691
692
693/** Scroll the jScrollPane to make the currently selected item visible
694    This is called when the page finished loading. */
695function scrollIntoView(nav) {
696  var $nav = $("#"+nav);
697  var element = $nav.jScrollPane({/* ...settings... */});
698  var api = element.data('jsp');
699
700  if ($nav.is(':visible')) {
701    var $selected = $(".selected", $nav);
702    if ($selected.length == 0) return;
703
704    var selectedOffset = $selected.position().top;
705    if (selectedOffset + 90 > $nav.height()) {  // add 90 so that we scroll up even
706                                                // if the current item is close to the bottom
707      api.scrollTo(0, selectedOffset - ($nav.height() / 4), false); // scroll the item into view
708                                                              // to be 1/4 of the way from the top
709    }
710  }
711}
712
713
714
715
716
717
718/* Show popup dialogs */
719function showDialog(id) {
720  $dialog = $("#"+id);
721  $dialog.prepend('<div class="box-border"><div class="top"> <div class="left"></div> <div class="right"></div></div><div class="bottom"> <div class="left"></div> <div class="right"></div> </div> </div>');
722  $dialog.wrapInner('<div/>');
723  $dialog.removeClass("hide");
724}
725
726
727
728
729
730/* #########    COOKIES!     ########## */
731
732function readCookie(cookie) {
733  var myCookie = cookie_namespace+"_"+cookie+"=";
734  if (document.cookie) {
735    var index = document.cookie.indexOf(myCookie);
736    if (index != -1) {
737      var valStart = index + myCookie.length;
738      var valEnd = document.cookie.indexOf(";", valStart);
739      if (valEnd == -1) {
740        valEnd = document.cookie.length;
741      }
742      var val = document.cookie.substring(valStart, valEnd);
743      return val;
744    }
745  }
746  return 0;
747}
748
749function writeCookie(cookie, val, section, expiration) {
750  if (val==undefined) return;
751  section = section == null ? "_" : "_"+section+"_";
752  if (expiration == null) {
753    var date = new Date();
754    date.setTime(date.getTime()+(10*365*24*60*60*1000)); // default expiration is one week
755    expiration = date.toGMTString();
756  }
757  var cookieValue = cookie_namespace + section + cookie + "=" + val
758                    + "; expires=" + expiration+"; path=/";
759  document.cookie = cookieValue;
760}
761
762/* #########     END COOKIES!     ########## */
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788/*
789
790REMEMBER THE PREVIOUS PAGE FOR EACH TAB
791
792function loadLast(cookiePath) {
793  var location = window.location.href;
794  if (location.indexOf("/"+cookiePath+"/") != -1) {
795    return true;
796  }
797  var lastPage = readCookie(cookiePath + "_lastpage");
798  if (lastPage) {
799    window.location = lastPage;
800    return false;
801  }
802  return true;
803}
804
805
806
807$(window).unload(function(){
808  var path = getBaseUri(location.pathname);
809  if (path.indexOf("/reference/") != -1) {
810    writeCookie("lastpage", path, "reference", null);
811  } else if (path.indexOf("/guide/") != -1) {
812    writeCookie("lastpage", path, "guide", null);
813  } else if ((path.indexOf("/resources/") != -1) || (path.indexOf("/training/") != -1)) {
814    writeCookie("lastpage", path, "resources", null);
815  }
816});
817
818*/
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833function toggle(obj, slide) {
834  var ul = $("ul:first", obj);
835  var li = ul.parent();
836  if (li.hasClass("closed")) {
837    if (slide) {
838      ul.slideDown("fast");
839    } else {
840      ul.show();
841    }
842    li.removeClass("closed");
843    li.addClass("open");
844    $(".toggle-img", li).attr("title", "hide pages");
845  } else {
846    ul.slideUp("fast");
847    li.removeClass("open");
848    li.addClass("closed");
849    $(".toggle-img", li).attr("title", "show pages");
850  }
851}
852
853
854
855
856
857function buildToggleLists() {
858  $(".toggle-list").each(
859    function(i) {
860      $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
861      $(this).addClass("closed");
862    });
863}
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896/*      REFERENCE NAV SWAP     */
897
898
899function getNavPref() {
900  var v = readCookie('reference_nav');
901  if (v != NAV_PREF_TREE) {
902    v = NAV_PREF_PANELS;
903  }
904  return v;
905}
906
907function chooseDefaultNav() {
908  nav_pref = getNavPref();
909  if (nav_pref == NAV_PREF_TREE) {
910    $("#nav-panels").toggle();
911    $("#panel-link").toggle();
912    $("#nav-tree").toggle();
913    $("#tree-link").toggle();
914  }
915}
916
917function swapNav() {
918  if (nav_pref == NAV_PREF_TREE) {
919    nav_pref = NAV_PREF_PANELS;
920  } else {
921    nav_pref = NAV_PREF_TREE;
922    init_default_navtree(toRoot);
923  }
924  var date = new Date();
925  date.setTime(date.getTime()+(10*365*24*60*60*1000)); // keep this for 10 years
926  writeCookie("nav", nav_pref, "reference", date.toGMTString());
927
928  $("#nav-panels").toggle();
929  $("#panel-link").toggle();
930  $("#nav-tree").toggle();
931  $("#tree-link").toggle();
932
933  resizeNav();
934
935  // Gross nasty hack to make tree view show up upon first swap by setting height manually
936  $("#nav-tree .jspContainer:visible")
937      .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
938  // Another nasty hack to make the scrollbar appear now that we have height
939  resizeNav();
940
941  if ($("#nav-tree").is(':visible')) {
942    scrollIntoView("nav-tree");
943  } else {
944    scrollIntoView("packages-nav");
945    scrollIntoView("classes-nav");
946  }
947}
948
949
950
951/* ############################################ */
952/* ##########     LOCALIZATION     ############ */
953/* ############################################ */
954
955function getBaseUri(uri) {
956  var intlUrl = (uri.substring(0,6) == "/intl/");
957  if (intlUrl) {
958    base = uri.substring(uri.indexOf('intl/')+5,uri.length);
959    base = base.substring(base.indexOf('/')+1, base.length);
960      //alert("intl, returning base url: /" + base);
961    return ("/" + base);
962  } else {
963      //alert("not intl, returning uri as found.");
964    return uri;
965  }
966}
967
968function requestAppendHL(uri) {
969//append "?hl=<lang> to an outgoing request (such as to blog)
970  var lang = getLangPref();
971  if (lang) {
972    var q = 'hl=' + lang;
973    uri += '?' + q;
974    window.location = uri;
975    return false;
976  } else {
977    return true;
978  }
979}
980
981
982function changeNavLang(lang) {
983  var $links = $("#devdoc-nav,#header,#nav-x,.training-nav-top,.content-footer").find("a["+lang+"-lang]");
984  $links.each(function(i){ // for each link with a translation
985    var $link = $(this);
986    if (lang != "en") { // No need to worry about English, because a language change invokes new request
987      // put the desired language from the attribute as the text
988      $link.text($link.attr(lang+"-lang"))
989    }
990  });
991}
992
993function changeDocLang(lang) {
994  changeNavLang(lang);
995}
996
997function changeLangPref(lang, refresh) {
998  var date = new Date();
999  expires = date.toGMTString(date.setTime(date.getTime()+(10*365*24*60*60*1000)));
1000  // keep this for 50 years
1001  //alert("expires: " + expires)
1002  writeCookie("pref_lang", lang, null, expires);
1003  changeDocLang(lang);
1004  if (refresh) {
1005    l = getBaseUri(location.pathname);
1006    window.location = l;
1007  }
1008}
1009
1010function loadLangPref() {
1011  var lang = readCookie("pref_lang");
1012  if (lang != 0) {
1013    $("#language").find("option[value='"+lang+"']").attr("selected",true);
1014  }
1015}
1016
1017function getLangPref() {
1018  var lang = $("#language").find(":selected").attr("value");
1019  if (!lang) {
1020    lang = readCookie("pref_lang");
1021  }
1022  return (lang != 0) ? lang : 'en';
1023}
1024
1025/* ##########     END LOCALIZATION     ############ */
1026
1027
1028
1029
1030
1031
1032/* Used to hide and reveal supplemental content, such as long code samples.
1033   See the companion CSS in android-developer-docs.css */
1034function toggleContent(obj) {
1035  var div = $(obj.parentNode.parentNode);
1036  var toggleMe = $(".toggle-content-toggleme",div);
1037  if (div.hasClass("closed")) { // if it's closed, open it
1038    toggleMe.slideDown();
1039    $(".toggle-content-text", obj).toggle();
1040    div.removeClass("closed").addClass("open");
1041    $(".toggle-content-img", div).attr("title", "hide").attr("src", toRoot
1042                  + "assets/images/triangle-opened.png");
1043  } else { // if it's open, close it
1044    toggleMe.slideUp('fast', function() {  // Wait until the animation is done before closing arrow
1045      $(".toggle-content-text", obj).toggle();
1046      div.removeClass("open").addClass("closed");
1047      $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
1048                  + "assets/images/triangle-closed.png");
1049    });
1050  }
1051  return false;
1052}
1053
1054
1055
1056
1057
1058
1059/*
1060 *  Slideshow 1.0
1061 *  Used on /index.html and /develop/index.html for carousel
1062 *
1063 *  Sample usage:
1064 *  HTML -
1065 *  <div class="slideshow-container">
1066 *   <a href="" class="slideshow-prev">Prev</a>
1067 *   <a href="" class="slideshow-next">Next</a>
1068 *   <ul>
1069 *       <li class="item"><img src="images/marquee1.jpg"></li>
1070 *       <li class="item"><img src="images/marquee2.jpg"></li>
1071 *       <li class="item"><img src="images/marquee3.jpg"></li>
1072 *       <li class="item"><img src="images/marquee4.jpg"></li>
1073 *   </ul>
1074 *  </div>
1075 *
1076 *   <script type="text/javascript">
1077 *   $('.slideshow-container').dacSlideshow({
1078 *       auto: true,
1079 *       btnPrev: '.slideshow-prev',
1080 *       btnNext: '.slideshow-next'
1081 *   });
1082 *   </script>
1083 *
1084 *  Options:
1085 *  btnPrev:    optional identifier for previous button
1086 *  btnNext:    optional identifier for next button
1087 *  auto:       whether or not to auto-proceed
1088 *  speed:      animation speed
1089 *  autoTime:   time between auto-rotation
1090 *  easing:     easing function for transition
1091 *  start:      item to select by default
1092 *  scroll:     direction to scroll in
1093 *  pagination: whether or not to include dotted pagination
1094 *
1095 */
1096
1097 (function($) {
1098 $.fn.dacSlideshow = function(o) {
1099
1100     //Options - see above
1101     o = $.extend({
1102         btnPrev:   null,
1103         btnNext:   null,
1104         auto:      true,
1105         speed:     500,
1106         autoTime:  12000,
1107         easing:    null,
1108         start:     0,
1109         scroll:    1,
1110         pagination: true
1111
1112     }, o || {});
1113
1114     //Set up a carousel for each
1115     return this.each(function() {
1116
1117         var running = false;
1118         var animCss = o.vertical ? "top" : "left";
1119         var sizeCss = o.vertical ? "height" : "width";
1120         var div = $(this);
1121         var ul = $("ul", div);
1122         var tLi = $("li", ul);
1123         var tl = tLi.size();
1124         var timer = null;
1125
1126         var li = $("li", ul);
1127         var itemLength = li.size();
1128         var curr = o.start;
1129
1130         li.css({float: o.vertical ? "none" : "left"});
1131         ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1132         div.css({position: "relative", "z-index": "2", left: "0px"});
1133
1134         var liSize = o.vertical ? height(li) : width(li);
1135         var ulSize = liSize * itemLength;
1136         var divSize = liSize;
1137
1138         li.css({width: li.width(), height: li.height()});
1139         ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1140
1141         div.css(sizeCss, divSize+"px");
1142
1143         //Pagination
1144         if (o.pagination) {
1145             var pagination = $("<div class='pagination'></div>");
1146             var pag_ul = $("<ul></ul>");
1147             if (tl > 1) {
1148               for (var i=0;i<tl;i++) {
1149                    var li = $("<li>"+i+"</li>");
1150                    pag_ul.append(li);
1151                    if (i==o.start) li.addClass('active');
1152                        li.click(function() {
1153                        go(parseInt($(this).text()));
1154                    })
1155                }
1156                pagination.append(pag_ul);
1157                div.append(pagination);
1158             }
1159         }
1160
1161         //Previous button
1162         if(o.btnPrev)
1163             $(o.btnPrev).click(function(e) {
1164                 e.preventDefault();
1165                 return go(curr-o.scroll);
1166             });
1167
1168         //Next button
1169         if(o.btnNext)
1170             $(o.btnNext).click(function(e) {
1171                 e.preventDefault();
1172                 return go(curr+o.scroll);
1173             });
1174
1175         //Auto rotation
1176         if(o.auto) startRotateTimer();
1177
1178         function startRotateTimer() {
1179             clearInterval(timer);
1180             timer = setInterval(function() {
1181                  if (curr == tl-1) {
1182                    go(0);
1183                  } else {
1184                    go(curr+o.scroll);
1185                  }
1186              }, o.autoTime);
1187         }
1188
1189         //Go to an item
1190         function go(to) {
1191             if(!running) {
1192
1193                 if(to<0) {
1194                    to = itemLength-1;
1195                 } else if (to>itemLength-1) {
1196                    to = 0;
1197                 }
1198                 curr = to;
1199
1200                 running = true;
1201
1202                 ul.animate(
1203                     animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1204                     function() {
1205                         running = false;
1206                     }
1207                 );
1208
1209                 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1210                 $( (curr-o.scroll<0 && o.btnPrev)
1211                     ||
1212                    (curr+o.scroll > itemLength && o.btnNext)
1213                     ||
1214                    []
1215                  ).addClass("disabled");
1216
1217
1218                 var nav_items = $('li', pagination);
1219                 nav_items.removeClass('active');
1220                 nav_items.eq(to).addClass('active');
1221
1222
1223             }
1224             if(o.auto) startRotateTimer();
1225             return false;
1226         };
1227     });
1228 };
1229
1230 function css(el, prop) {
1231     return parseInt($.css(el[0], prop)) || 0;
1232 };
1233 function width(el) {
1234     return  el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1235 };
1236 function height(el) {
1237     return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1238 };
1239
1240 })(jQuery);
1241
1242
1243/*
1244 *  dacSlideshow 1.0
1245 *  Used on develop/index.html for side-sliding tabs
1246 *
1247 *  Sample usage:
1248 *  HTML -
1249 *  <div class="slideshow-container">
1250 *   <a href="" class="slideshow-prev">Prev</a>
1251 *   <a href="" class="slideshow-next">Next</a>
1252 *   <ul>
1253 *       <li class="item"><img src="images/marquee1.jpg"></li>
1254 *       <li class="item"><img src="images/marquee2.jpg"></li>
1255 *       <li class="item"><img src="images/marquee3.jpg"></li>
1256 *       <li class="item"><img src="images/marquee4.jpg"></li>
1257 *   </ul>
1258 *  </div>
1259 *
1260 *   <script type="text/javascript">
1261 *   $('.slideshow-container').dacSlideshow({
1262 *       auto: true,
1263 *       btnPrev: '.slideshow-prev',
1264 *       btnNext: '.slideshow-next'
1265 *   });
1266 *   </script>
1267 *
1268 *  Options:
1269 *  btnPrev:    optional identifier for previous button
1270 *  btnNext:    optional identifier for next button
1271 *  auto:       whether or not to auto-proceed
1272 *  speed:      animation speed
1273 *  autoTime:   time between auto-rotation
1274 *  easing:     easing function for transition
1275 *  start:      item to select by default
1276 *  scroll:     direction to scroll in
1277 *  pagination: whether or not to include dotted pagination
1278 *
1279 */
1280 (function($) {
1281 $.fn.dacTabbedList = function(o) {
1282
1283     //Options - see above
1284     o = $.extend({
1285         speed : 250,
1286         easing: null,
1287         nav_id: null,
1288         frame_id: null
1289     }, o || {});
1290
1291     //Set up a carousel for each
1292     return this.each(function() {
1293
1294         var curr = 0;
1295         var running = false;
1296         var animCss = "margin-left";
1297         var sizeCss = "width";
1298         var div = $(this);
1299
1300         var nav = $(o.nav_id, div);
1301         var nav_li = $("li", nav);
1302         var nav_size = nav_li.size();
1303         var frame = div.find(o.frame_id);
1304         var content_width = $(frame).find('ul').width();
1305         //Buttons
1306         $(nav_li).click(function(e) {
1307           go($(nav_li).index($(this)));
1308         })
1309
1310         //Go to an item
1311         function go(to) {
1312             if(!running) {
1313                 curr = to;
1314                 running = true;
1315
1316                 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1317                     function() {
1318                         running = false;
1319                     }
1320                 );
1321
1322
1323                 nav_li.removeClass('active');
1324                 nav_li.eq(to).addClass('active');
1325
1326
1327             }
1328             return false;
1329         };
1330     });
1331 };
1332
1333 function css(el, prop) {
1334     return parseInt($.css(el[0], prop)) || 0;
1335 };
1336 function width(el) {
1337     return  el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1338 };
1339 function height(el) {
1340     return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1341 };
1342
1343 })(jQuery);
1344
1345
1346
1347
1348
1349/* ######################################################## */
1350/* ################  SEARCH SUGGESTIONS  ################## */
1351/* ######################################################## */
1352
1353
1354var gSelectedIndex = -1;
1355var gSelectedID = -1;
1356var gMatches = new Array();
1357var gLastText = "";
1358var ROW_COUNT = 20;
1359var gInitialized = false;
1360
1361function set_item_selected($li, selected)
1362{
1363    if (selected) {
1364        $li.attr('class','jd-autocomplete jd-selected');
1365    } else {
1366        $li.attr('class','jd-autocomplete');
1367    }
1368}
1369
1370function set_item_values(toroot, $li, match)
1371{
1372    var $link = $('a',$li);
1373    $link.html(match.__hilabel || match.label);
1374    $link.attr('href',toroot + match.link);
1375}
1376
1377function sync_selection_table(toroot)
1378{
1379    var $list = $("#search_filtered");
1380    var $li; //list item jquery object
1381    var i; //list item iterator
1382    gSelectedID = -1;
1383
1384    //initialize the table; draw it for the first time (but not visible).
1385    if (!gInitialized) {
1386        for (i=0; i<ROW_COUNT; i++) {
1387            var $li = $("<li class='jd-autocomplete'></li>");
1388            $list.append($li);
1389
1390            $li.mousedown(function() {
1391                window.location = this.firstChild.getAttribute("href");
1392            });
1393            $li.mouseover(function() {
1394                $('#search_filtered li').removeClass('jd-selected');
1395                $(this).addClass('jd-selected');
1396                gSelectedIndex = $('#search_filtered li').index(this);
1397            });
1398            $li.append('<a></a>');
1399        }
1400        gInitialized = true;
1401    }
1402
1403    //if we have results, make the table visible and initialize result info
1404    if (gMatches.length > 0) {
1405        $('#search_filtered_div').removeClass('no-display');
1406        var N = gMatches.length < ROW_COUNT ? gMatches.length : ROW_COUNT;
1407        for (i=0; i<N; i++) {
1408            $li = $('#search_filtered li:nth-child('+(i+1)+')');
1409            $li.attr('class','show-item');
1410            set_item_values(toroot, $li, gMatches[i]);
1411            set_item_selected($li, i == gSelectedIndex);
1412            if (i == gSelectedIndex) {
1413                gSelectedID = gMatches[i].id;
1414            }
1415        }
1416        //start hiding rows that are no longer matches
1417        for (; i<ROW_COUNT; i++) {
1418            $li = $('#search_filtered li:nth-child('+(i+1)+')');
1419            $li.attr('class','no-display');
1420        }
1421        //if there are more results we're not showing, so say so.
1422/*      if (gMatches.length > ROW_COUNT) {
1423            li = list.rows[ROW_COUNT];
1424            li.className = "show-item";
1425            c1 = li.cells[0];
1426            c1.innerHTML = "plus " + (gMatches.length-ROW_COUNT) + " more";
1427        } else {
1428            list.rows[ROW_COUNT].className = "hide-item";
1429        }*/
1430    //if we have no results, hide the table
1431    } else {
1432        $('#search_filtered_div').addClass('no-display');
1433    }
1434}
1435
1436function search_changed(e, kd, toroot)
1437{
1438    var search = document.getElementById("search_autocomplete");
1439    var text = search.value.replace(/(^ +)|( +$)/g, '');
1440
1441    // show/hide the close button
1442    if (text != '') {
1443        $(".search .close").removeClass("hide");
1444    } else {
1445        $(".search .close").addClass("hide");
1446    }
1447
1448    // 13 = enter
1449    if (e.keyCode == 13) {
1450        $('#search_filtered_div').addClass('no-display');
1451        if (!$('#search_filtered_div').hasClass('no-display') || (gSelectedIndex < 0)) {
1452            if ($("#searchResults").is(":hidden")) {
1453              // if results aren't showing, return true to allow search to execute
1454              return true;
1455            } else {
1456              // otherwise, results are already showing, so allow ajax to auto refresh the results
1457              // and ignore this Enter press to avoid the reload.
1458              return false;
1459            }
1460        } else if (kd && gSelectedIndex >= 0) {
1461            window.location = toroot + gMatches[gSelectedIndex].link;
1462            return false;
1463        }
1464    }
1465    // 38 -- arrow up
1466    else if (kd && (e.keyCode == 38)) {
1467        if (gSelectedIndex >= 0) {
1468            $('#search_filtered li').removeClass('jd-selected');
1469            gSelectedIndex--;
1470            $('#search_filtered li:nth-child('+(gSelectedIndex+1)+')').addClass('jd-selected');
1471        }
1472        return false;
1473    }
1474    // 40 -- arrow down
1475    else if (kd && (e.keyCode == 40)) {
1476        if (gSelectedIndex < gMatches.length-1
1477                        && gSelectedIndex < ROW_COUNT-1) {
1478            $('#search_filtered li').removeClass('jd-selected');
1479            gSelectedIndex++;
1480            $('#search_filtered li:nth-child('+(gSelectedIndex+1)+')').addClass('jd-selected');
1481        }
1482        return false;
1483    }
1484    else if (!kd && (e.keyCode != 40) && (e.keyCode != 38)) {
1485        gMatches = new Array();
1486        matchedCount = 0;
1487        gSelectedIndex = -1;
1488        for (var i=0; i<DATA.length; i++) {
1489            var s = DATA[i];
1490            if (text.length != 0 &&
1491                  s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
1492                gMatches[matchedCount] = s;
1493                matchedCount++;
1494            }
1495        }
1496        rank_autocomplete_results(text);
1497        for (var i=0; i<gMatches.length; i++) {
1498            var s = gMatches[i];
1499            if (gSelectedID == s.id) {
1500                gSelectedIndex = i;
1501            }
1502        }
1503        highlight_autocomplete_result_labels(text);
1504        sync_selection_table(toroot);
1505        return true; // allow the event to bubble up to the search api
1506    }
1507}
1508
1509function rank_autocomplete_results(query) {
1510    query = query || '';
1511    if (!gMatches || !gMatches.length)
1512      return;
1513
1514    // helper function that gets the last occurence index of the given regex
1515    // in the given string, or -1 if not found
1516    var _lastSearch = function(s, re) {
1517      if (s == '')
1518        return -1;
1519      var l = -1;
1520      var tmp;
1521      while ((tmp = s.search(re)) >= 0) {
1522        if (l < 0) l = 0;
1523        l += tmp;
1524        s = s.substr(tmp + 1);
1525      }
1526      return l;
1527    };
1528
1529    // helper function that counts the occurrences of a given character in
1530    // a given string
1531    var _countChar = function(s, c) {
1532      var n = 0;
1533      for (var i=0; i<s.length; i++)
1534        if (s.charAt(i) == c) ++n;
1535      return n;
1536    };
1537
1538    var queryLower = query.toLowerCase();
1539    var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
1540    var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
1541    var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
1542
1543    var _resultScoreFn = function(result) {
1544        // scores are calculated based on exact and prefix matches,
1545        // and then number of path separators (dots) from the last
1546        // match (i.e. favoring classes and deep package names)
1547        var score = 1.0;
1548        var labelLower = result.label.toLowerCase();
1549        var t;
1550        t = _lastSearch(labelLower, partExactAlnumRE);
1551        if (t >= 0) {
1552            // exact part match
1553            var partsAfter = _countChar(labelLower.substr(t + 1), '.');
1554            score *= 200 / (partsAfter + 1);
1555        } else {
1556            t = _lastSearch(labelLower, partPrefixAlnumRE);
1557            if (t >= 0) {
1558                // part prefix match
1559                var partsAfter = _countChar(labelLower.substr(t + 1), '.');
1560                score *= 20 / (partsAfter + 1);
1561            }
1562        }
1563
1564        return score;
1565    };
1566
1567    for (var i=0; i<gMatches.length; i++) {
1568        gMatches[i].__resultScore = _resultScoreFn(gMatches[i]);
1569    }
1570
1571    gMatches.sort(function(a,b){
1572        var n = b.__resultScore - a.__resultScore;
1573        if (n == 0) // lexicographical sort if scores are the same
1574            n = (a.label < b.label) ? -1 : 1;
1575        return n;
1576    });
1577}
1578
1579function highlight_autocomplete_result_labels(query) {
1580    query = query || '';
1581    if (!gMatches || !gMatches.length)
1582      return;
1583
1584    var queryLower = query.toLowerCase();
1585    var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
1586    var queryRE = new RegExp(
1587        '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
1588    for (var i=0; i<gMatches.length; i++) {
1589        gMatches[i].__hilabel = gMatches[i].label.replace(
1590            queryRE, '<b>$1</b>');
1591    }
1592}
1593
1594function search_focus_changed(obj, focused)
1595{
1596    if (!focused) {
1597        if(obj.value == ""){
1598          $(".search .close").addClass("hide");
1599        }
1600        document.getElementById("search_filtered_div").className = "no-display";
1601    }
1602}
1603
1604function submit_search() {
1605  var query = document.getElementById('search_autocomplete').value;
1606  location.hash = 'q=' + query;
1607  loadSearchResults();
1608  $("#searchResults").slideDown('slow');
1609  return false;
1610}
1611
1612
1613function hideResults() {
1614  $("#searchResults").slideUp();
1615  $(".search .close").addClass("hide");
1616  location.hash = '';
1617
1618  $("#search_autocomplete").val("").blur();
1619
1620  // reset the ajax search callback to nothing, so results don't appear unless ENTER
1621  searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
1622  return false;
1623}
1624
1625
1626
1627/* ########################################################## */
1628/* ################  CUSTOM SEARCH ENGINE  ################## */
1629/* ########################################################## */
1630
1631google.load('search', '1');
1632var searchControl;
1633
1634function loadSearchResults() {
1635  document.getElementById("search_autocomplete").style.color = "#000";
1636
1637  // create search control
1638  searchControl = new google.search.SearchControl();
1639
1640  // use our existing search form and use tabs when multiple searchers are used
1641  drawOptions = new google.search.DrawOptions();
1642  drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
1643  drawOptions.setInput(document.getElementById("search_autocomplete"));
1644
1645  // configure search result options
1646  searchOptions = new google.search.SearcherOptions();
1647  searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
1648
1649  // configure each of the searchers, for each tab
1650  devSiteSearcher = new google.search.WebSearch();
1651  devSiteSearcher.setUserDefinedLabel("All");
1652  devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
1653
1654  designSearcher = new google.search.WebSearch();
1655  designSearcher.setUserDefinedLabel("Design");
1656  designSearcher.setSiteRestriction("http://developer.android.com/design/");
1657
1658  trainingSearcher = new google.search.WebSearch();
1659  trainingSearcher.setUserDefinedLabel("Training");
1660  trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
1661
1662  guidesSearcher = new google.search.WebSearch();
1663  guidesSearcher.setUserDefinedLabel("Guides");
1664  guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
1665
1666  referenceSearcher = new google.search.WebSearch();
1667  referenceSearcher.setUserDefinedLabel("Reference");
1668  referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
1669
1670  blogSearcher = new google.search.WebSearch();
1671  blogSearcher.setUserDefinedLabel("Blog");
1672  blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
1673
1674  // add each searcher to the search control
1675  searchControl.addSearcher(devSiteSearcher, searchOptions);
1676  searchControl.addSearcher(designSearcher, searchOptions);
1677  searchControl.addSearcher(trainingSearcher, searchOptions);
1678  searchControl.addSearcher(guidesSearcher, searchOptions);
1679  searchControl.addSearcher(referenceSearcher, searchOptions);
1680  searchControl.addSearcher(blogSearcher, searchOptions);
1681
1682  // configure result options
1683  searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
1684  searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
1685  searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
1686  searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
1687
1688  // upon ajax search, refresh the url and search title
1689  searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
1690    updateResultTitle(query);
1691    var query = document.getElementById('search_autocomplete').value;
1692    location.hash = 'q=' + query;
1693  });
1694
1695  // draw the search results box
1696  searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
1697
1698  // get query and execute the search
1699  searchControl.execute(decodeURI(getQuery(location.hash)));
1700
1701  document.getElementById("search_autocomplete").focus();
1702  addTabListeners();
1703}
1704// End of loadSearchResults
1705
1706
1707google.setOnLoadCallback(function(){
1708  if (location.hash.indexOf("q=") == -1) {
1709    // if there's no query in the url, don't search and make sure results are hidden
1710    $('#searchResults').hide();
1711    return;
1712  } else {
1713    // first time loading search results for this page
1714    $('#searchResults').slideDown('slow');
1715    $(".search .close").removeClass("hide");
1716    loadSearchResults();
1717  }
1718}, true);
1719
1720// when an event on the browser history occurs (back, forward, load) requery hash and do search
1721$(window).hashchange( function(){
1722  // Exit if the hash isn't a search query or there's an error in the query
1723  if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
1724    // If the results pane is open, close it.
1725    if (!$("#searchResults").is(":hidden")) {
1726      hideResults();
1727    }
1728    return;
1729  }
1730
1731  // Otherwise, we have a search to do
1732  var query = decodeURI(getQuery(location.hash));
1733  searchControl.execute(query);
1734  $('#searchResults').slideDown('slow');
1735  $("#search_autocomplete").focus();
1736  $(".search .close").removeClass("hide");
1737
1738  updateResultTitle(query);
1739});
1740
1741function updateResultTitle(query) {
1742  $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
1743}
1744
1745// forcefully regain key-up event control (previously jacked by search api)
1746$("#search_autocomplete").keyup(function(event) {
1747  return search_changed(event, false, toRoot);
1748});
1749
1750// add event listeners to each tab so we can track the browser history
1751function addTabListeners() {
1752  var tabHeaders = $(".gsc-tabHeader");
1753  for (var i = 0; i < tabHeaders.length; i++) {
1754    $(tabHeaders[i]).attr("id",i).click(function() {
1755    /*
1756      // make a copy of the page numbers for the search left pane
1757      setTimeout(function() {
1758        // remove any residual page numbers
1759        $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
1760        // move the page numbers to the left position; make a clone,
1761        // because the element is drawn to the DOM only once
1762        // and because we're going to remove it (previous line),
1763        // we need it to be available to move again as the user navigates
1764        $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
1765                        .clone().appendTo('#searchResults .gsc-tabsArea');
1766        }, 200);
1767      */
1768    });
1769  }
1770  setTimeout(function(){$(tabHeaders[0]).click()},200);
1771}
1772
1773
1774function getQuery(hash) {
1775  var queryParts = hash.split('=');
1776  return queryParts[1];
1777}
1778
1779/* returns the given string with all HTML brackets converted to entities
1780    TODO: move this to the site's JS library */
1781function escapeHTML(string) {
1782  return string.replace(/</g,"&lt;")
1783                .replace(/>/g,"&gt;");
1784}
1785
1786
1787
1788
1789
1790
1791
1792/* ######################################################## */
1793/* #################  JAVADOC REFERENCE ################### */
1794/* ######################################################## */
1795
1796/* Initialize some droiddoc stuff, but only if we're in the reference */
1797if (location.pathname.indexOf("/reference") == 0) {
1798  $(document).ready(function() {
1799    // init available apis based on user pref
1800    changeApiLevel();
1801    initSidenavHeightResize()
1802  });
1803}
1804
1805var API_LEVEL_COOKIE = "api_level";
1806var minLevel = 1;
1807var maxLevel = 1;
1808
1809/******* SIDENAV DIMENSIONS ************/
1810
1811  function initSidenavHeightResize() {
1812    // Change the drag bar size to nicely fit the scrollbar positions
1813    var $dragBar = $(".ui-resizable-s");
1814    $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
1815
1816    $( "#resize-packages-nav" ).resizable({
1817      containment: "#nav-panels",
1818      handles: "s",
1819      alsoResize: "#packages-nav",
1820      resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
1821      stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie  */
1822      });
1823
1824  }
1825
1826function updateSidenavFixedWidth() {
1827  if (!navBarIsFixed) return;
1828  $('#devdoc-nav').css({
1829    'width' : $('#side-nav').css('width'),
1830    'margin' : $('#side-nav').css('margin')
1831  });
1832  $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
1833
1834  initSidenavHeightResize();
1835}
1836
1837function updateSidenavFullscreenWidth() {
1838  if (!navBarIsFixed) return;
1839  $('#devdoc-nav').css({
1840    'width' : $('#side-nav').css('width'),
1841    'margin' : $('#side-nav').css('margin')
1842  });
1843  $('#devdoc-nav .totop').css({'left': 'inherit'});
1844
1845  initSidenavHeightResize();
1846}
1847
1848function buildApiLevelSelector() {
1849  maxLevel = SINCE_DATA.length;
1850  var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
1851  userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
1852
1853  minLevel = parseInt($("#doc-api-level").attr("class"));
1854  // Handle provisional api levels; the provisional level will always be the highest possible level
1855  // Provisional api levels will also have a length; other stuff that's just missing a level won't,
1856  // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
1857  if (isNaN(minLevel) && minLevel.length) {
1858    minLevel = maxLevel;
1859  }
1860  var select = $("#apiLevelSelector").html("").change(changeApiLevel);
1861  for (var i = maxLevel-1; i >= 0; i--) {
1862    var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
1863  //  if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
1864    select.append(option);
1865  }
1866
1867  // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
1868  var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
1869  selectedLevelItem.setAttribute('selected',true);
1870}
1871
1872function changeApiLevel() {
1873  maxLevel = SINCE_DATA.length;
1874  var selectedLevel = maxLevel;
1875
1876  selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
1877  toggleVisisbleApis(selectedLevel, "body");
1878
1879  var date = new Date();
1880  date.setTime(date.getTime()+(10*365*24*60*60*1000)); // keep this for 10 years
1881  var expiration = date.toGMTString();
1882  writeCookie(API_LEVEL_COOKIE, selectedLevel, null, expiration);
1883
1884  if (selectedLevel < minLevel) {
1885    var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
1886    $("#naMessage").show().html("<div><p><strong>This " + thing + " is not available with API level " + selectedLevel + ".</strong></p>"
1887                              + "<p>To use this " + thing + ", you must develop your app using a build target "
1888                              + "that supports API level " + $("#doc-api-level").attr("class") + " or higher. To read these "
1889                              + "APIs, change the value of the API level filter above.</p>"
1890                              + "<p><a href='" +toRoot+ "guide/appendix/api-levels.html'>What is the API level?</a></p></div>");
1891  } else {
1892    $("#naMessage").hide();
1893  }
1894}
1895
1896function toggleVisisbleApis(selectedLevel, context) {
1897  var apis = $(".api",context);
1898  apis.each(function(i) {
1899    var obj = $(this);
1900    var className = obj.attr("class");
1901    var apiLevelIndex = className.lastIndexOf("-")+1;
1902    var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
1903    apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
1904    var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
1905    if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
1906      return;
1907    }
1908    apiLevel = parseInt(apiLevel);
1909
1910    // Handle provisional api levels; if this item's level is the provisional one, set it to the max
1911    var selectedLevelNum = parseInt(selectedLevel)
1912    var apiLevelNum = parseInt(apiLevel);
1913    if (isNaN(apiLevelNum)) {
1914        apiLevelNum = maxLevel;
1915    }
1916
1917    // Grey things out that aren't available and give a tooltip title
1918    if (apiLevelNum > selectedLevelNum) {
1919      obj.addClass("absent").attr("title","Requires API Level \""
1920            + apiLevel + "\" or higher");
1921    }
1922    else obj.removeClass("absent").removeAttr("title");
1923  });
1924}
1925
1926
1927
1928
1929/* #################  SIDENAV TREE VIEW ################### */
1930
1931function new_node(me, mom, text, link, children_data, api_level)
1932{
1933  var node = new Object();
1934  node.children = Array();
1935  node.children_data = children_data;
1936  node.depth = mom.depth + 1;
1937
1938  node.li = document.createElement("li");
1939  mom.get_children_ul().appendChild(node.li);
1940
1941  node.label_div = document.createElement("div");
1942  node.label_div.className = "label";
1943  if (api_level != null) {
1944    $(node.label_div).addClass("api");
1945    $(node.label_div).addClass("api-level-"+api_level);
1946  }
1947  node.li.appendChild(node.label_div);
1948
1949  if (children_data != null) {
1950    node.expand_toggle = document.createElement("a");
1951    node.expand_toggle.href = "javascript:void(0)";
1952    node.expand_toggle.onclick = function() {
1953          if (node.expanded) {
1954            $(node.get_children_ul()).slideUp("fast");
1955            node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
1956            node.expanded = false;
1957          } else {
1958            expand_node(me, node);
1959          }
1960       };
1961    node.label_div.appendChild(node.expand_toggle);
1962
1963    node.plus_img = document.createElement("img");
1964    node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
1965    node.plus_img.className = "plus";
1966    node.plus_img.width = "8";
1967    node.plus_img.border = "0";
1968    node.expand_toggle.appendChild(node.plus_img);
1969
1970    node.expanded = false;
1971  }
1972
1973  var a = document.createElement("a");
1974  node.label_div.appendChild(a);
1975  node.label = document.createTextNode(text);
1976  a.appendChild(node.label);
1977  if (link) {
1978    a.href = me.toroot + link;
1979  } else {
1980    if (children_data != null) {
1981      a.className = "nolink";
1982      a.href = "javascript:void(0)";
1983      a.onclick = node.expand_toggle.onclick;
1984      // This next line shouldn't be necessary.  I'll buy a beer for the first
1985      // person who figures out how to remove this line and have the link
1986      // toggle shut on the first try. --joeo@android.com
1987      node.expanded = false;
1988    }
1989  }
1990
1991
1992  node.children_ul = null;
1993  node.get_children_ul = function() {
1994      if (!node.children_ul) {
1995        node.children_ul = document.createElement("ul");
1996        node.children_ul.className = "children_ul";
1997        node.children_ul.style.display = "none";
1998        node.li.appendChild(node.children_ul);
1999      }
2000      return node.children_ul;
2001    };
2002
2003  return node;
2004}
2005
2006function expand_node(me, node)
2007{
2008  if (node.children_data && !node.expanded) {
2009    if (node.children_visited) {
2010      $(node.get_children_ul()).slideDown("fast");
2011    } else {
2012      get_node(me, node);
2013      if ($(node.label_div).hasClass("absent")) {
2014        $(node.get_children_ul()).addClass("absent");
2015      }
2016      $(node.get_children_ul()).slideDown("fast");
2017    }
2018    node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
2019    node.expanded = true;
2020
2021    // perform api level toggling because new nodes are new to the DOM
2022    var selectedLevel = $("#apiLevelSelector option:selected").val();
2023    toggleVisisbleApis(selectedLevel, "#side-nav");
2024  }
2025}
2026
2027function get_node(me, mom)
2028{
2029  mom.children_visited = true;
2030  for (var i in mom.children_data) {
2031    var node_data = mom.children_data[i];
2032    mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
2033        node_data[2], node_data[3]);
2034  }
2035}
2036
2037function this_page_relative(toroot)
2038{
2039  var full = document.location.pathname;
2040  var file = "";
2041  if (toroot.substr(0, 1) == "/") {
2042    if (full.substr(0, toroot.length) == toroot) {
2043      return full.substr(toroot.length);
2044    } else {
2045      // the file isn't under toroot.  Fail.
2046      return null;
2047    }
2048  } else {
2049    if (toroot != "./") {
2050      toroot = "./" + toroot;
2051    }
2052    do {
2053      if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
2054        var pos = full.lastIndexOf("/");
2055        file = full.substr(pos) + file;
2056        full = full.substr(0, pos);
2057        toroot = toroot.substr(0, toroot.length-3);
2058      }
2059    } while (toroot != "" && toroot != "/");
2060    return file.substr(1);
2061  }
2062}
2063
2064function find_page(url, data)
2065{
2066  var nodes = data;
2067  var result = null;
2068  for (var i in nodes) {
2069    var d = nodes[i];
2070    if (d[1] == url) {
2071      return new Array(i);
2072    }
2073    else if (d[2] != null) {
2074      result = find_page(url, d[2]);
2075      if (result != null) {
2076        return (new Array(i).concat(result));
2077      }
2078    }
2079  }
2080  return null;
2081}
2082
2083function load_navtree_data(toroot) {
2084  var navtreeData = document.createElement("script");
2085  navtreeData.setAttribute("type","text/javascript");
2086  navtreeData.setAttribute("src", toroot+"navtree_data.js");
2087  $("head").append($(navtreeData));
2088}
2089
2090function init_default_navtree(toroot) {
2091  init_navtree("tree-list", toroot, NAVTREE_DATA);
2092
2093  // perform api level toggling because because the whole tree is new to the DOM
2094  var selectedLevel = $("#apiLevelSelector option:selected").val();
2095  toggleVisisbleApis(selectedLevel, "#side-nav");
2096}
2097
2098function init_navtree(navtree_id, toroot, root_nodes)
2099{
2100  var me = new Object();
2101  me.toroot = toroot;
2102  me.node = new Object();
2103
2104  me.node.li = document.getElementById(navtree_id);
2105  me.node.children_data = root_nodes;
2106  me.node.children = new Array();
2107  me.node.children_ul = document.createElement("ul");
2108  me.node.get_children_ul = function() { return me.node.children_ul; };
2109  //me.node.children_ul.className = "children_ul";
2110  me.node.li.appendChild(me.node.children_ul);
2111  me.node.depth = 0;
2112
2113  get_node(me, me.node);
2114
2115  me.this_page = this_page_relative(toroot);
2116  me.breadcrumbs = find_page(me.this_page, root_nodes);
2117  if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
2118    var mom = me.node;
2119    for (var i in me.breadcrumbs) {
2120      var j = me.breadcrumbs[i];
2121      mom = mom.children[j];
2122      expand_node(me, mom);
2123    }
2124    mom.label_div.className = mom.label_div.className + " selected";
2125    addLoadEvent(function() {
2126      scrollIntoView("nav-tree");
2127      });
2128  }
2129}
2130
2131/* TOGGLE INHERITED MEMBERS */
2132
2133/* Toggle an inherited class (arrow toggle)
2134 * @param linkObj  The link that was clicked.
2135 * @param expand  'true' to ensure it's expanded. 'false' to ensure it's closed.
2136 *                'null' to simply toggle.
2137 */
2138function toggleInherited(linkObj, expand) {
2139    var base = linkObj.getAttribute("id");
2140    var list = document.getElementById(base + "-list");
2141    var summary = document.getElementById(base + "-summary");
2142    var trigger = document.getElementById(base + "-trigger");
2143    var a = $(linkObj);
2144    if ( (expand == null && a.hasClass("closed")) || expand ) {
2145        list.style.display = "none";
2146        summary.style.display = "block";
2147        trigger.src = toRoot + "assets/images/triangle-opened.png";
2148        a.removeClass("closed");
2149        a.addClass("opened");
2150    } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
2151        list.style.display = "block";
2152        summary.style.display = "none";
2153        trigger.src = toRoot + "assets/images/triangle-closed.png";
2154        a.removeClass("opened");
2155        a.addClass("closed");
2156    }
2157    return false;
2158}
2159
2160/* Toggle all inherited classes in a single table (e.g. all inherited methods)
2161 * @param linkObj  The link that was clicked.
2162 * @param expand  'true' to ensure it's expanded. 'false' to ensure it's closed.
2163 *                'null' to simply toggle.
2164 */
2165function toggleAllInherited(linkObj, expand) {
2166  var a = $(linkObj);
2167  var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
2168  var expandos = $(".jd-expando-trigger", table);
2169  if ( (expand == null && a.text() == "[Expand]") || expand ) {
2170    expandos.each(function(i) {
2171      toggleInherited(this, true);
2172    });
2173    a.text("[Collapse]");
2174  } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
2175    expandos.each(function(i) {
2176      toggleInherited(this, false);
2177    });
2178    a.text("[Expand]");
2179  }
2180  return false;
2181}
2182
2183/* Toggle all inherited members in the class (link in the class title)
2184 */
2185function toggleAllClassInherited() {
2186  var a = $("#toggleAllClassInherited"); // get toggle link from class title
2187  var toggles = $(".toggle-all", $("#body-content"));
2188  if (a.text() == "[Expand All]") {
2189    toggles.each(function(i) {
2190      toggleAllInherited(this, true);
2191    });
2192    a.text("[Collapse All]");
2193  } else {
2194    toggles.each(function(i) {
2195      toggleAllInherited(this, false);
2196    });
2197    a.text("[Expand All]");
2198  }
2199  return false;
2200}
2201
2202/* Expand all inherited members in the class. Used when initiating page search */
2203function ensureAllInheritedExpanded() {
2204  var toggles = $(".toggle-all", $("#body-content"));
2205  toggles.each(function(i) {
2206    toggleAllInherited(this, true);
2207  });
2208  $("#toggleAllClassInherited").text("[Collapse All]");
2209}
2210
2211
2212/* HANDLE KEY EVENTS
2213 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
2214 */
2215var agent = navigator['userAgent'].toLowerCase();
2216var mac = agent.indexOf("macintosh") != -1;
2217
2218$(document).keydown( function(e) {
2219var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
2220  if (control && e.which == 70) {  // 70 is "F"
2221    ensureAllInheritedExpanded();
2222  }
2223});
2224
2225
2226
2227