
function getX(obj)
{
   return( obj.offsetParent==null ? obj.offsetLeft : obj.offsetLeft+getX(obj.offsetParent) );
}

function getY(obj)
{
   return( obj.offsetParent==null ? obj.offsetTop : obj.offsetTop+getY(obj.offsetParent) );
}

function isvalidchar(achar,validstr)
{
   if ( !validstr ) validstr = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 `~!@#$%^&*()_-+={}[]|:;'<>,./?\\\"";
   return( validstr.toUpperCase().indexOf(achar.toString().toUpperCase(),0) >= 0 );
}

function getBubbleDiv()
{
   if ( document.getElementById )
      return document.getElementById("divComboBubble");
   else if ( document.all )
      return document.all.divComboBubble;
   else return null;
}


function createBubbleDiv(objSelect)
{
   if ( !getBubbleDiv() ) // if this is the first time this function is
   {                      // called then create the bubble text window.
      var bubbleDiv = "<DIV id='divComboBubble' style='position:absolute;top:0px;left:0px;visibility:hidden'></div>\n";

      if ( document.body.insertAdjacentHTML )
      {
         document.body.insertAdjacentHTML("beforeEnd",bubbleDiv);
      }
      else if ( document.body.innerHTML )
      {
         document.body.innerHTML += bubbleDiv;
      }

      // necessary for netscape (don't know why)
      var sel = "document." + objSelect.form.name + "." + objSelect.name
      setTimeout("setupComboBox("+sel+")",1);

      return true;
   }

   return false;
}


function setupComboBox(objSelect)
{
   if ( !document.all && !document.getElementById )
      return false;  // browser not supported

   if ( !objSelect || (""+objSelect.type).substring(0,6).toLowerCase()!="select" )
      return false;

   if ( createBubbleDiv(objSelect) )
      return true;

   // create/init data elements
   objSelect.X = getX(objSelect)+2;
   objSelect.Y = getY(objSelect)-20;
   objSelect.strKeyInBuf   = "";
   //objSelect.selectedIndex = -1;

   // setup event handlers
   objSelect.onfocus     = comboFocusHandler;
   objSelect.onblur      = comboBlurHandler;
   objSelect.onmouseover = comboMouseOverHandler;
   objSelect.onmouseout  = comboMouseOutHandler;
   objSelect.onkeydown   = comboKeyDownHandler;
   objSelect.onkeypress  = comboKeyPressHandler;
   objSelect.onkeyup     = null;

   return true;
}


function showBubbleText(objSelect)
{
  if ( !objSelect || !objSelect.X )  // for the onBlur event to clear out the bubble help
  {
     return(false);
  }

  var divComboBubble = getBubbleDiv();
  var s = divComboBubble.innerHTML = "";

  with ( objSelect )
  {
     if ( strKeyInBuf == "" )
     {
        s = '<FONT color="blue">' + title + '</font>';
     }
     else if ( ( selectedIndex >= 0 ) && ( strKeyInBuf == options[selectedIndex].text ) )  // unique match found
     {
        s = '<B>' + strKeyInBuf + '</b>';
     }
     else
     {
        var c = strKeyInBuf.substring(strKeyInBuf.length-1,strKeyInBuf.length);
        c = ( c == ' ' ) ? '&nbsp;' : '<B>' + c + '</b>';
        s = strKeyInBuf.substring(0,strKeyInBuf.length-1) + c;
     }

     divComboBubble.innerHTML = '<TABLE cellpadding=0 cellspacing=0 style="background-color:INFOBACKGROUND;'
       + 'font:8pt ms sans serif;padding:2px 2px 2px 2px;color:INFOTEXT;border:1px solid INFOTEXT">'
       + '<TR><TD align=left><NOBR>'+s+'</nobr></td></tr></table>';

     divComboBubble.style.posLeft = divComboBubble.style.left = X;
     divComboBubble.style.posTop  = divComboBubble.style.top  = Y;
     divComboBubble.style.visibility = "";

  } // end with

  return(true);
}


function hideBubbleText()
{
   var divComboBubble = getBubbleDiv();
   divComboBubble.innerHTML = "";
}


function findSelectEntry( objSelect, head, tail )  // uses divide-and-conquer search, assumes sorted list
{
  with ( objSelect )
  {
    if ( options.length <= 0 )
    {
       strKeyInBuf = ' <FONT color="red">No selections available.</font> ';
       showBubbleText(objSelect);
       top.status = strKeyInBuf = "";
       return(-1);
    }

    if ( strKeyInBuf == "" )
    {
       showBubbleText(objSelect);
       selectedIndex=0;  // set to top
       return( selectedIndex = options[selectedIndex].text.length ? -1 : 0 );
    }

    var mid = Math.round( (head+tail)/2 );

    if ( strKeyInBuf.toUpperCase() == options[mid].text.substring(0,strKeyInBuf.length).toUpperCase() )
    {
       while ( (mid>0)  &&  strKeyInBuf.toUpperCase() == options[mid-1].text.substring(0,strKeyInBuf.length).toUpperCase() )
       {
          mid--;  // get the topmost matching item in the list, not just the first found
       }

       selectedIndex=mid;

       top.status = strKeyInBuf = options[mid].text.substring(0,strKeyInBuf.length);

       if ( mid == Math.round( (head+tail)/2 ) )  // if the original mid is an exact match
       {
          if ( (mid==tail) || ( (mid<tail) && strKeyInBuf.toUpperCase() != options[mid+1].text.substring(0,strKeyInBuf.length).toUpperCase() ) )
          {
             top.status = strKeyInBuf = options[mid].text;  // unique match found
          }
       }

       showBubbleText(objSelect);

       return( selectedIndex );
    }

    if ( head >= tail )  // then since no exact match was found, go back to previous strKeyInBuf (thus the length-1)
    {
       strKeyInBuf = strKeyInBuf.substring(0,strKeyInBuf.length-1)
       return( findSelectEntry( objSelect, 0, options.length-1 ) );
    }

    if ( strKeyInBuf.toUpperCase() < options[mid].text.substring(0,strKeyInBuf.length).toUpperCase() )
    {
       return( findSelectEntry( objSelect, head, Math.max(head,mid-1) ) );
    }

    return( findSelectEntry( objSelect, Math.min(mid+1,tail), tail ) );
  }
}


function comboFocusHandler(e)
{
   var event = e ? e : window.event;  // to handle both NS4 and IE4 events
   var objSelect = event.target ? event.target : event.srcElement;
   comboFocusHandler.objSelect = objSelect;

   // update data elements
   objSelect.X = getX(objSelect)+2;
   objSelect.Y = getY(objSelect)-20;
   objSelect.strKeyInBuf = "";

   // display the bubble help (the title attribute by default)
   showBubbleText(objSelect);
}


function comboBlurHandler(e)
{
  var event = e ? e : window.event;  // to handle both NS4 and IE4 events
  var objSelect = event.target ? event.target : event.srcElement;
  comboFocusHandler.objSelect = null;

  objSelect.strKeyInBuf = "";
  top.status = (objSelect.selectedIndex>-1) ? objSelect.options[objSelect.selectedIndex].text : "";

  hideBubbleText();

  return(true);
}


function comboMouseOverHandler(e)
{
  var event = e ? e : window.event;  // to handle both NS4 and IE4 events
  var objSelect = event.target ? event.target : event.srcElement;

  showBubbleText(objSelect);

  return(true);
}


function comboMouseOutHandler(e)
{
  var event = e ? e : window.event;  // to handle both NS4 and IE4 events
  var objSelect = event.target ? event.target : event.srcElement;

  if ( objSelect==comboFocusHandler.objSelect || objSelect==document.activeElement )
     return(true);

  hideBubbleText();

  return(true);
}


function comboKeyDownHandler(e)
{
  var event = e ? e : window.event;  // to handle both NS4 and IE4 events
  var objSelect = event.target ? event.target : event.srcElement;
  var divComboBubble = getBubbleDiv();

  with ( objSelect )
  {
    // this is to correct the search bug when the list is left open after single-click
    if ( strKeyInBuf=="" && event.keyCode>40 )
    {
       objSelect.blur();
       objSelect.focus();
    }

    switch(event.keyCode)
    {
      case(8):  // backspace
      {
         if ( ( selectedIndex >= 0 ) && ( strKeyInBuf == options[selectedIndex].text ) )
         {
            top.status = strKeyInBuf = "";
         }
         else
         {
            top.status = strKeyInBuf = strKeyInBuf.substring(0,strKeyInBuf.length-1);
         }

         if ( strKeyInBuf.length==0 ) selectedIndex = -1;

         showBubbleText(objSelect);
         event.keyCode = 0;
         return(false);
      }

      case(9):   // tab
      case(13):  // enter
      {
         event.keyCode = 9;  // convert enter to tab
         return(true);
      }

      case(27):  // escape
      {
         top.status = strKeyInBuf = "";
         selectedIndex = -1;
         showBubbleText(objSelect);
         event.keyCode = 0;
         return(false);
      }

      case(32):  // space
      {
         top.status = strKeyInBuf += ' ';
         // this must be fired explicitely for spaces
         return( !comboKeyPressHandler(event) );
      }

      // I don't know if these are universal
      case(33):  // page up
      case(34):  // page down
      case(35):  // end
      case(36):  // home
      case(38):  // up arrow
      case(40):  // down arrow
      {
         top.status = strKeyInBuf = "";
         hideBubbleText();
         return(true);
      }

    }  // end switch
  }  // end with

  return(true);
}


function comboKeyPressHandler(e)
{
  var event = e ? e : window.event;  // to handle both NS4 and IE4 events
  var objSelect = event.target ? event.target : event.srcElement;
  var inputChar = String.fromCharCode(event.keyCode ? event.keyCode : event.charCode);

  if ( inputChar=='\n' || inputChar=='\r' )
  {
     top.status = objSelect.strKeyInBuf = ( objSelect.selectedIndex>-1 ? objSelect.options[objSelect.selectedIndex].text : "" );
  }

  if ( isvalidchar(inputChar) && event.charCode!=0 )
  {
     // must use setTimeout to override the default SELECT keypress event(s)
     objSelect.strKeyInBuf += inputChar;
     var strSel = 'document.forms["' + objSelect.form.name + '"].elements["' + objSelect.name + '"]';
     setTimeout( 'findSelectEntry(' + strSel + ',0,' + strSel + '.options.length-1);' , 1 );
  }

  return(true);
}