/*
 *  (c) Copyright 2004 by Percipient Reverence
 *
 *       ------------ ThreadPages.js -------------
 *
 *  JavaScript Code for thread pages, e.g., ubb/Forum1/HTML/000001.html
 *
 *
 */

var userName = getCookie("UserName");
var password = getCookie("Password");
var status = getCookie("Class");
var moderatedForums = getCookie("ModeratedList");
var linkedUserName = getCookie("LinkedUserName");
var linkedPassword = getCookie("LinkedPassword");
var linkedStatus = getCookie("LinkedClass");
var linkedModeratedForums = getCookie("LinkedModeratedList");
var message, link;
var loggedinMsg = "You are logged in as";
var loggedoutMsg = "You are not logged in";
var loginLink = "[ <a href=cgi-bin/login.cgi?action=login>Login</a> ]";
var logoutLink = "[ <a href=cgi-bin/login.cgi?action=logout>Logout</a> ]";
var loggedin = false;
var userHTML = "";
var linkedHTML = "";

// There's a bug in Internet Explorer that doesn't allow
// document.body.appendChild before onLoad.  Supposedly this code gets
// around the bug.  We define the doLoad routine in the HTML document
// itself.  I've commented this out because it didn't seem to be
// happening at the right time, but I leave it here in case I
// eventually find it useful.
//
// if (window.addEventListener) {
//   window.addEventListener("load",doOnload,false);
// } else if (window.attachEvent) {
//   window.attachEvent("onload",doOnload);
// }

if (userName == null) {
  message = loggedoutMsg;
  status = "";
  userHTML = "";
  loginLink = loginLink;
} else {
  loggedin = true;
  message = loggedinMsg;
  loginLink = logoutLink;
  var regExp = / /g;
  var encodedUserName = userName.replace(regExp, "+");
  userHTML = 
      " <a href=cgi-bin/membermsglist.cgi" +
      "?action=ml&mn=" + encodedUserName + ">" + userName + "</a>";
  if (linkedUserName != null) {
    var encodedLinkedName = linkedUserName.replace(regExp, "+");
    linkedHTML =
      " (" + linkedStatus + " <a href=cgi-bin/membermsglist.cgi?action=ml&mn=" +
      encodedLinkedName + ">" + linkedUserName + "</a>)";
  }
}

// Start the interval timer that automatically keeps the data fields
// of the page up to date.  Uncomment out to turn on.  Decided not to
// use this.  The importance of keeping the "online now" and "chatting
// now" fields up to date on every page didn't seem very important
// compared to the overhead.  But now I know how to do make a CGI call
// to update individual fields instead of refreshing the whole page.  There 
// may be other opportunities to take advantage of this.  For example,
// the preview of messages - I could just update the preview box.  Or
// asking to see raw message text - I could just replace the rendered 
// message text with the raw message text and leave everything else alone.
// Or peek mode on the reply page, though I actually think the way I'm
// doing it now is better, shipping both versions to the browser and leting
// the user select which is displayed.
//
// var updateDataFieldsID = setInterval("requestDataFieldsUpdate()", 60000);

var updateReq;
function requestDataFieldsUpdate() {
  if (window.XMLHttpRequest) {
    try {
      updateReq = new XMLHttpRequest();
    }
    catch(e) {
      updateReq = null;
    }
  } else if (window.ActiveXObject) {
    try {
      updateReq = new ActiveXObject("Msxml2.XMLHTTP");
    }
    catch(e) {
      try {
        updateReq = new ActiveXObject("Microsoft.XMLHTTP");
      }
      catch(e) {
        updateReq = null;
      }
    }
  }
  if (updateReq) {
    var baseRef = location.href.substring(0, location.href.lastIndexOf('/') + 1);
    updateReq.onreadystatechange = updateDataFields;
    updateReq.open("GET", baseRef + 'Update.cgi?action=udf', true);
    updateReq.send(null);
  }
}

var updateCount = 0;
function updateDataFields() {
  if (updateReq.readyState == 4) {
    var responseText = updateReq.responseText;
    var dataFieldID = document.getElementById('onlinelist');
    dataFieldID.innerHTML = 'Response text: ' + responseText + ', Update count: ' + updateCount;
    updateCount++;
  }
}    

function pad(val) {
  if (val < 10) {
    return '0' + val;
  } else {
    return val;
  }
}
  
var days = new Array('Sun','Mon','Tue','Wed','Thu','Fri','Sat');
var longDays = new Array('Sunday','Monday','Tuesday','Wednesday',
                         'Thursday','Friday','Saturday');
var months = new Array('Jan','Feb','Mar','Apr','May','Jun',
                       'Jul','Aug','Sep','Oct','Nov','Dec');
var longMonths = new Array('January','February','March','April','May','June',
                           'July','August','September','October','November','December');

function FormatDate(theTime, dateFormat, dateSep, yearDig) {
  var year;
  if (yearDig == 4) {
    year = theTime.getFullYear();
  } else {
    year = (theTime.getFullYear() + '').substr(2);
  }
  var month = theTime.getMonth(); // 0-11
  var dayOfMonth = theTime.getDate(); // 1-31
  var dayOfWeek = theTime.getDay(); // 0-6
  var nameOfDayOfWeek = days[dayOfWeek] + ', ';
  switch(dateFormat) {
  case 'US':
    month = pad(month + 1);
    dayOfMonth = pad(dayOfMonth);
    return month + dateSep + dayOfMonth + dateSep + year;
  case 'USAM':
    month = months[month];
    dayOfMonth = pad(dayOfMonth);
    return month + dateSep + dayOfMonth + dateSep + year;
  case 'USFM':
    month = longMonths[month];
    dayOfMonth = pad(dayOfMonth);
    return month + dayOfMonth + year;
  case 'USADOW':
    month = pad(month + 1);
    dayOfMonth = pad(dayOfMonth);
    return nameOfDayOfWeek + month + dateSep + dayOfMonth + dateSep + year;
  case 'USFDOW':
    month =pad(month + 1);
    dayOfMonth = pad(dayOfMonth);
    nameOfDayOfWeek = longDays[dayOfWeek] + ', ';
    return nameOfDayOfWeek + month + dateSep + dayOfMonth + dateSep + year;
  case 'USAMADOW':
    month = months[month];
    dayOfMonth = pad(dayOfMonth);
    return nameOfDayOfWeek + month + dateSep + dayOfMonth + dateSep + year;
  case 'USAMFDOW':
    month = months[month];
    dayOfMonth = pad(dayOfMonth);
    nameOfDayOfWeek = longDays[dayOfWeek] + ', ';
    return nameOfDayOfWeek + month + dateSep + dayOfMonth + dateSep + year;
  case 'USFMADOW':
    month = longMonths[month];
    dayOfMonth = pad(dayOfMonth);
    return nameOfDayOfWeek + month + ' ' + dayOfMonth + ', ' + year;
  case 'USFMFDOW':
    month = longMonths[month];
    dayOfMonth = pad(dayOfMonth);
    nameOfDayOfWeek = longDays[dayOfWeek] + ', ';
    return nameOfDayOfWeek + month + ' ' + dayOfMonth + ', ' + year;
  case 'E':
    month = pad(month + 1);
    dayOfMonth = pad(dayOfMonth);
    return dayOfMonth + dateSep + month + dateSep + year;
  case 'EAM':
    month = months[month];
    dayOfMonth = pad(dayOfMonth);
    return dayOfMonth + dateSep + month + dateSep + year;
  case 'EFM':
    month = longMonths[month];
    dayOfMonth = pad(dayOfMonth);
    return dayOfMonth + dateSep + month + dateSep + year;
  case 'EADOW':
    month = pad(month + 1);
    dayOfMonth = pad(dayOfMonth);
    return nameOfDayOfWeek + dayOfMonth + dateSep + month + dateSep + year;
  case 'EFDOW':
    month = pad(month + 1);
    dayOfMonth = pad(dayOfMonth);
    nameOfDayOfWeek = longDays[dayOfWeek] + ', ';
    return nameOfDayOfWeek + dayOfMonth + dateSep + month + dateSep + year;
  case 'EAMADOW':
    month = months[month];
    dayOfMonth = pad(dayOfMonth);
    return nameOfDayOfWeek + dayOfMonth + dateSep + month + dateSep + year;
  case 'EAMFDOW':
    month = months[month];
    dayOfMonth = pad(dayOfMonth);
    nameOfDayOfWeek = longDays[dayOfWeek] + ', ';
    return nameOfDayOfWeek + dayOfMonth + dateSep + month + dateSep + year;
  case 'EFMADOW':
    month = longMonths[month];
    dayOfMonth = pad(dayOfMonth);
    return nameOfDayOfWeek + dayOfMonth + dateSep + month + dateSep + year;
  case 'EFMFDOW':
    month = longMonths[month];
    dayOfMonth = pad(dayOfMonth);
    nameOfDayOfWeek = longDays[dayOfWeek] + ', ';
    return nameOfDayOfWeek + dayOfMonth + dateSep + month + dateSep + year;
  case 'I':
    month = pad(month + 1);
    dayOfMonth = pad(dayOfMonth);
    return year + dateSep + month + dateSep + dayOfMonth;
  case 'IAM':
    month = months[month];
    dayOfMonth = pad(dayOfMonth);
    return year + dateSep + month + dateSep + dayOfMonth;
  case 'IFM':
    month = longMonths[month];
    dayOfMonth = pad(dayOfMonth);
    return year + dateSep + month + dateSep + dayOfMonth;
  case 'IADOW':
    month = pad(month + 1);
    dayOfMonth = pad(dayOfMonth);
    return nameOfDayOfWeek + year + dateSep + month + dateSep + dayOfMonth;
  case 'IFDOW':
    month = pad(month + 1);
    dayOfMonth = pad(dayOfMonth);
    nameOfDayOfWeek = longDays[dayOfWeek] + ', ';
    return nameOfDayOfWeek + year + dateSep + month + dateSep + dayOfMonth;
  case 'IAMADOW':
    month = months[month];
    dayOfMonth = pad(dayOfMonth);
    return nameOfDayOfWeek + year + dateSep + month + dateSep + dayOfMonth;
  case 'IAMFDOW':
    month = months[month];
    dayOfMonth = pad(dayOfMonth);
    nameOfDayOfWeek = longDays[dayOfWeek] + ', ';
    return nameOfDayOfWeek + year + dateSep + month + dateSep + dayOfMonth;
  case 'IFMADOW':
    month = longMonths[month];
    dayOfMonth = pad(dayOfMonth);
    return nameOfDayOfWeek + year + dateSep + month + dateSep + dayOfMonth;
  case 'IFMFDOW':
    month = longMonths[month];
    dayOfMonth = pad(dayOfMonth);
    nameOfDayOfWeek = longDays[dayOfWeek] + ', ';
    return nameOfDayOfWeek + year + dateSep + month + dateSep + dayOfMonth;
  }
  return '*** Bad Date Format Requested: ' + dateFormat + ' ***';
}

function FormatTime(theTime, timeFormat) {
  var hour = theTime.getHours();
  var min = theTime.getMinutes();
  var time;
  if (timeFormat == 'AMPM') {
    var ampm;
    if (hour < 12) {
      ampm = 'AM';
      if (hour == 0) {
        hour = 12;
      }
    } else {
      ampm = 'PM';
      if (hour > 12) {
        hour -= 12;
      }
    }
    return pad(hour) + ':' + pad(min) + ' ' + ampm;
  } else {
    return pad(hour) + ':' + pad(min);
  }
}

function getDateTime(utcTime, dateFormat, dateSep, yearDig, timeFormat) {
  var theTime;
  if (utcTime == 0) {
    theTime = new Date();
  } else {
    theTime = new Date(utcTime);
  }
  var date = FormatDate(theTime, dateFormat, dateSep, yearDig);
  var time = FormatTime(theTime, timeFormat);
  return date.toString() + ' ' + time.toString();
}

function printDateTime(utcTime, dateFormat, dateSep, yearDig, timeFormat) {
  document.write(getDateTime(utcTime, dateFormat, dateSep, yearDig, timeFormat));
}

function getDate(utcTime, dateFormat, dateSep, yearDig) {
  var theTime = new Date(utcTime);
  var date = FormatDate(theTime, dateFormat, dateSep, yearDig);
  return date.toString();
}

function printDate(utcTime, dateFormat, dateSep, yearDig) {
  document.write(getDate(utcTime, dateFormat, dateSep, yearDig));
}

function PrintRecurringCurrentTime(dateFormat, dateSep, yearDig, timeFormat) {
  var timeDiv = document.getElementById('DisplayCurrentDateTime');
  if (timeDiv) {
    var curDate = new Date();
    timeDiv.innerHTML = getDateTime(curDate.getTime(), dateFormat,
                                    dateSep, yearDig, timeFormat);
    var millisecondsTillNextUpdate = 60000 - curDate.getSeconds()*1000;
    setTimeout("PrintRecurringCurrentTime('"+dateFormat+"','"+dateSep+"',"+yearDig+",'"+
               timeFormat+"')", millisecondsTillNextUpdate);
  }
}

function suspensionText(reason, utcTime, dateFormat, dateSep, yearDig, timeFormat) {
  var dateTimeText;
  if (utcTime == 0) {
    // The full text for the hover box has been determined on the server side 
    return reason;
  }
  if (utcTime == 2147483647000) {
    dateTimeText = 'Indefinite';
  } else {
    var currentUtcTime = new Date;
    var displacementMilliseconds;
    if (utcTime < currentUtcTime.getTime()) {
      displacementMilliseconds = currentUtcTime.getTime() - utcTime;
    } else {
      displacementMilliseconds = utcTime - currentUtcTime.getTime();
    }
    var hours = parseInt((displacementMilliseconds / 3600000).toString(10));
    var minutes = parseInt(((displacementMilliseconds - (hours*3600000)) / 60000).toString(10));
    var diffText;
    if (utcTime <  currentUtcTime) {
      if (hours == 0) {
        diffText = minutes + 'm ago';
      } else {
        diffText = hours + 'h ' + minutes + 'm ago';
      }
    } else {
      if (hours == 0) {
        diffText = minutes + 'm from now';
      } else {
        diffText = hours + 'h ' + minutes + 'm from now';
      }
    }
    dateTimeText = getDateTime(utcTime, dateFormat, dateSep, yearDig, timeFormat) +
      ' (' + diffText + ')';
  }
  return 'Suspension expires: ' + dateTimeText + '<br>Reason: ' + reason;
}

function getCookie(name) {
   var cname = "; " + name + "=";
   var dc = "; " + document.cookie;
   if (dc.length > 0) {
      begin = dc.indexOf(cname);
      if (begin != -1) {
         begin += cname.length;
         end = dc.indexOf(";", begin);
         if (end == -1) end = dc.length;
         return unescape(dc.substring(begin, end));
      }
   }
   return null;
}

/* Hardwired to set expires date to a year in the future */
function setCookie(name, value, path) {
  var date = new Date();
  date.setFullYear(date.getFullYear() + 1);
  var expires = '; expires=' + date.toGMTString();
  if (path != null) {
    path = '; path=' + path;
  } else {
    path = '; path=';
  }
  document.cookie = name + '=' + value + expires + path;
}

function toggleThreadTrackerCookie(forumNum, threadNum, horizonHours, path) {
  var ttIcon;
  if (threadNum == null) {
    ttIcon = document.getElementById('tt' + forumNum);
  } else {
    ttIcon = document.getElementById('tt' + forumNum + '|' + threadNum);
  }
  var regexp = /_On./;
  if (ttIcon.src.match(regexp)) {
    addThreadToTrackerCookie(forumNum, threadNum, horizonHours, path);
    TurnOffThreadTrackerIcon(ttIcon);
  } else {
    eraseThreadFromTrackerCookie(forumNum, threadNum, path);
    TurnOnThreadTrackerIcon(ttIcon);
  }
}

function TurnOnThreadTrackerIcon(ttIcon) {
  var regexp = /_Off./;
  ttIcon.src = ttIcon.src.replace(regexp, "_On.");
}

function TurnOffThreadTrackerIcon(ttIcon) {
  var regexp = /_On./;
  ttIcon.src = ttIcon.src.replace(regexp, "_Off.");
}

function addThreadToTrackerCookie(forumNum, threadNum, horizonHours, path) {
  var trackerCookie = getCookie('ThreadTracker');
  var ForumThreads;
  if (trackerCookie == null || trackerCookie.length == 0) {
    for(var i=0; i<=forumNum; i++) {
      trackerCookie += '^';
    }
  }
  var ForumThreads = trackerCookie.split('^');
  while(ForumThreads.length < forumNum + 1) {
    ForumThreads.push('');
  }
  var Threads;
  if (ForumThreads[forumNum].length != 0) {
    Threads = ForumThreads[forumNum].split('|');
  } else {
    Threads = new Array();
  }
  var NewThreads = new Array();
  var horizonTime = new Date();
  var horizonSeconds = Math.round(horizonTime.getTime() / 1000);
  if (threadNum != null) {
    var addedThread = 0;
    if (Threads.length == 0) {
      NewThreads.push(threadNum + ',' + horizonSeconds);
      addedThread = 1;
    } else {
      for (var i=0; i<Threads.length; i++) {
        var fields = Threads[i].split(',');
        if (fields[0] < threadNum) {
          NewThreads.push(Threads[i]);
        } else if (fields[0] == threadNum) {
          NewThreads.push(threadNum + ',' + horizonSeconds);
          addedThread = 1;
        } else {
          if (!addedThread) {
            NewThreads.push(threadNum + ',' + horizonSeconds);
            addedThread = 1;
          }
          NewThreads.push(Threads[i]);
        }
      }
    }
    if (!addedThread) {
      NewThreads.push(threadNum + ',' + horizonSeconds);
    }
  } else {
    // Use thread number 0 to indicate all threads are read
    NewThreads.push('0,' + horizonSeconds);
  }
  var newThreads = NewThreads.join('|');
  ForumThreads[forumNum] = newThreads;
  var forumThreads = ForumThreads.join('^');
  setCookie('ThreadTracker', forumThreads, path);
}

function eraseThreadFromTrackerCookie(forumNum, threadNum, path) {
  var trackerCookie = getCookie('ThreadTracker');
  if (trackerCookie == null || trackerCookie.length == 0) {
    return;
  }
  var ForumThreads = trackerCookie.split('^');
  if (ForumThreads.length <= 0) {
    return;
  }
  var Threads = ForumThreads[forumNum].split('|');
  var NewThreads = new Array();
  var forumThreads;
  if (threadNum != null) {
    for (var i=0; i<Threads.length; i++) {
      var fields = Threads[i].split(',');
      if (fields[0] != threadNum) {
        NewThreads.push(Threads[i]);
    }
    }
    var newThreads = NewThreads.join('|');
    ForumThreads[forumNum] = newThreads;
    forumThreads = ForumThreads.join('^');
  } else {
    forumThreads = '';
  }
  setCookie('ThreadTracker', forumThreads, path);
}

// Probably not needed anymore
function OpenPeekTextWin(url) {
  var viewPeekMsgTxtWin =
    open(url, '_blank');
}

function OpenIPWin(url) {
  var viewIPWin = open(url, '_blank',
                       'directories=no,resizable=no,height=170,width=430');
}

function AdminModCheck(which, forumNum) {
  return(confirm(which + " this topic?"));
}

function ToggleDisplay(divName) {
  var div = document.getElementById(divName);
  if (div.style.display == 'none') {
    div.style.display = 'inline';
  } else {
    div.style.display = 'none';
  }
}

function JumpToForum(menu) {
  var index = menu.selectedIndex;
  var value = menu.options[index].value;
  if (value.substring(0,1) == 'c') {
    document.location='dBoard.cgi?c=' + value.slice(1);
  } else {
    document.location='Threads.cgi?action=tf&f=' + value;
  }
}

function ChatRoomObject(codeClause, primaryAlias) {
  var object = '<object ' + codeClause +
      '  codebase="http://client0.sigmachat.com/current/"' +
      '  archive="scclient_en.zip"' +
      '  standby="Connecting to chat room, please wait..."' +
      '  width=700 height=400 MAYSCRIPT>' +
      ' <param name="room" value="113348">' +
      ' <param name="cabbase" value="scclient_en.cab">' +
      ' <param name="autologin" value="yes">' +
      ' <param name="username" value="' + primaryAlias + '">' +
      ' <param name="password" value="chatr0om">' +
      ' This browser does not have a Java Plug-in, or the Plug-in version is not current.<br />' +
      ' <a href="http://java.sun.com/products/plugin/downloads/index.html">' +
      ' Get the latest Java Plug-in here.' +
      ' </a>' +
      ' </object>';
  document.write(object);
}

function AddMemberListArgs(link, includeSearchBox) {
  if (document.getElementById('IncludeAliases').checked) {
    link.href += '&aliases=1';
  }
  if (includeSearchBox) {
    var searchBox = document.getElementById('SearchBox');
    if (searchBox) {
      if (searchBox.value != '') {
        link.href += '&SearchBox=' + searchBox.value;
      }
    }
  }
}

// Implement my own hover boxes - differ from acronyms and alt in that
// they persist instead of disappearing after a few seconds.  This
// particular hover box is for displaying suspensions.  We will
// implement it just as required and then try to generalize from it to
// a generic hover box object type, modifying this comment, of course,
// to make it seem like I knew what I was doing all along.

var hoverBoxWidth = 250;
var suspendedHoverBoxesById = new Array();
var suspendedHoverBoxIds = new Array();
var suspendedTimeouts = new Array();
var currentlyDisplayedHoverBox = null;

function createHoverBox(textColor, bgColor) {
  var hoverBox = document.createElement('div');
  hoverBox.style.visibility = 'hidden';
  hoverBox.style.color = textColor;
  hoverBox.style.font = 'normal xx-small verdana';
  hoverBox.style.padding = '1px';
  hoverBox.style.background = bgColor;
  hoverBox.style.border = '1px solid black';
  hoverBox.style.position = 'absolute';
  hoverBox.style.top = 0;
  hoverBox.style.left = 0;
  hoverBox.style.width = hoverBoxWidth;
  hoverBox.style.zIndex = 10;
  return hoverBox;
}

var xDisplacement = 7;
var yDisplacement = 7;
function suspensionBox(positionObj, reason, textColor, bgColor,
                       utcTime, dateFormat, dateSep, yearDig, timeFormat) {
  this.positionObj = positionObj;
  this.reason = reason;
  this.utcTime = utcTime;
  this.dateFormat = dateFormat;
  this.dateSep = dateSep;
  this.yearDig = yearDig;
  this.timeFormat = timeFormat;
  this.hoverBox = createHoverBox(textColor, bgColor);
  this.hoverBoxInitialized = false; // Means has no parent yet
  this.offsetTop = 0;
  this.offsetLeft = 0;

  this.boxOn = function() {
    if (!this.hoverBoxInitialized) {
      this.hoverBox.innerHTML = suspensionText(this.reason, this.utcTime, this.dateFormat,
                                               this.dateSep, this.yearDig, this.timeFormat);
      // Have to append hover box child to document body, otherwise
      // the hover box height doesn't get defined.
      this.hoverBox = document.body.appendChild(this.hoverBox);
      var ascendObj = this.positionObj;
      while (ascendObj != null) {
        this.offsetTop += ascendObj.offsetTop;
        this.offsetLeft += ascendObj.offsetLeft;
        ascendObj = ascendObj.offsetParent;
      }
      var hoverLeft = this.offsetLeft + positionObj.offsetWidth + xDisplacement;
      // Check if too close to right
      if (hoverLeft + hoverBoxWidth > document.body.clientWidth) {
        // Go to the left
        hoverLeft = this.offsetLeft - hoverBoxWidth - xDisplacement;
      }
      var hoverTop = this.offsetTop + this.positionObj.offsetHeight + yDisplacement;
      // Check if too close to bottom
      if ((hoverTop + this.hoverBox.offsetHeight) >
          (document.body.clientHeight + document.body.scrollTop)) {
        hoverTop = this.offsetTop - this.hoverBox.offsetHeight - yDisplacement;
      }
      this.offsetTop = hoverTop;
      this.offsetLeft = hoverLeft;
      this.hoverBoxInitialized = true;
    }
    if (currentlyDisplayedHoverBox) {
      suspensionBoxOff(currentlyDisplayedHoverBox)
    }
    currentlyDisplayedHoverBox =this.hoverBox;
    if (this.hoverBox.addEventListener) {
      this.hoverBox.addEventListener("mouseover", suspendedCancelTimeout, false);
      this.hoverBox.addEventListener("mouseout", suspensionBoxOffDelayedEvent, false);
      this.hoverBox.addEventListener("click", suspensionBoxOffEvent, false);
    } else {
      this.hoverBox.attachEvent("onmouseover", suspendedCancelTimeout);
      this.hoverBox.attachEvent("onmouseout", suspensionBoxOffDelayedEvent);
      this.hoverBox.attachEvent("onclick", suspensionBoxOffEvent);
    }
    this.hoverBox.innerHTML = suspensionText(this.reason, this.utcTime, this.dateFormat,
                                             this.dateSep, this.yearDig, this.timeFormat);
    this.hoverBox.style.top = this.offsetTop;
    this.hoverBox.style.left = this.offsetLeft;
    this.hoverBox.style.visibility = 'visible';
  }

  this.boxOff = function() {
    // First detach the events
    if (this.hoverBox.removeEventListener) {
      this.hoverBox.removeEventListener("mouseover", suspendedCancelTimeout, false);
      this.hoverBox.removeEventListener("mouseout", suspensionBoxOffDelayedEvent, false);
      this.hoverBox.removeEventListener("click", suspensionBoxOffEvent, false);
    } else {
      this.hoverBox.detachEvent("onmouseover", suspendedCancelTimeout);
      this.hoverBox.detachEvent("onmouseout", suspensionBoxOffDelayedEvent);
      this.hoverBox.detachEvent("onclick", suspensionBoxOffEvent);
    }
    this.hoverBox.style.visibility = 'hidden';
    // Move outside visible region so doesn't affect resizing
    this.hoverBox.style.top = -1;
    this.hoverBox.style.left = -1;
  }

  this.boxOffDelayed = function(suspendedHoverBoxId) {
    suspendedHoverBoxesById[suspendedHoverBoxId] = this.hoverBox;
    suspendedHoverBoxIds[this.hoverBox] = suspendedHoverBoxId;
    suspendedTimeouts[this.hoverBox] =
      setTimeout("suspensionBoxOffId(" + suspendedHoverBoxId + ")", 500);
  }
}

function suspensionBoxOffDelayedEvent(mouseoutEvent) {
  var hoverBox, mouseX, mouseY;
  if (mouseoutEvent.target) { 
    hoverBox = mouseoutEvent.target;
    mouseX = mouseoutEvent.pageX;
    mouseY = mouseoutEvent.pageY;
  } else {
    hoverBox = mouseoutEvent.srcElement;
    mouseX = mouseoutEvent.clientX + document.body.scrollLeft;
    mouseY = mouseoutEvent.clientY + document.body.scrollTop;
  }
  // The mouseout event will fire for a link inside the hover box when
  // you move over or away from it, so check if the event object is
  // really the hover box, which has a tagname of DIV.
  if (hoverBox.tagName != 'DIV') {
    return;
  }
  // The event was for the hoverbox, but if we fired because of a link inside
  // the hoverbox then we may still be inside the hoverbox.
  if (mouseX >= hoverBox.offsetLeft && mouseX <= (hoverBox.offsetLeft + hoverBox.offsetWidth) &&
      mouseY >= hoverBox.offsetTop && mouseY <= (hoverBox.offsetTop + hoverBox.offsetHeight)) {
    return;
  }
  var suspendedHoverBoxId = suspendedHoverBoxIds[hoverBox];
  suspendedTimeouts[hoverBox] = 
    setTimeout("suspensionBoxOffId(" + suspendedHoverBoxId + ")", 500);
}

function suspendedCancelTimeout(mouseoverEvent) {
  var suspendedTimeoutId;
  if (mouseoverEvent.target) { 
    suspendedTimeoutId = suspendedTimeouts[mouseoverEvent.target];
  } else {
    suspendedTimeoutId = suspendedTimeouts[mouseoverEvent.srcElement];
  }
  clearTimeout(suspendedTimeoutId);
}

function suspensionBoxOffEvent(mouseoutEvent) {
  var hoverBox;
  if (mouseoutEvent.target) { 
    hoverBox = mouseoutEvent.target;
  } else {
    hoverBox = mouseoutEvent.srcElement;
  }
  suspensionBoxOff(hoverBox);
}

function suspensionBoxOffId(suspendedHoverBoxId) {
  suspensionBoxOff(suspendedHoverBoxesById[suspendedHoverBoxId]);
}

function suspensionBoxOff(hoverBox) {
  // First detach the events
  if (hoverBox.removeEventListener) {
    hoverBox.removeEventListener("mouseover", suspendedCancelTimeout, false);
    hoverBox.removeEventListener("mouseout", suspensionBoxOffDelayedEvent, false);
    hoverBox.removeEventListener("click", suspensionBoxOffEvent, false);
  } else {
    hoverBox.detachEvent("onmouseover", suspendedCancelTimeout);
    hoverBox.detachEvent("onmouseout", suspensionBoxOffDelayedEvent);
    hoverBox.detachEvent("onclick", suspensionBoxOffEvent);
  }
  hoverBox.style.visibility = 'hidden';
  // Move outside visible region so doesn't affect resizing
  hoverBox.style.top = -1;
  hoverBox.style.left = -1;
}

function displayHoverBox(obj, hoverBox) {
  // The first time we display the hoverbox we first have to 
  // append it as a child to the document body
  if (!hoverBox.parent) {
    document.body.appendChild(hoverBox);
  }
  var offsetTop = 0;
  var offsetLeft = 0;
  var thisObj = obj;
  while (thisObj != null) {
    offsetTop += thisObj.offsetTop;
    offsetLeft += thisObj.offsetLeft;
    thisObj = thisObj.offsetParent;
  }
  var hoverLeft = offsetLeft - hoverBoxWidth / 2.4;
  // Check if too close to left
  if (hoverLeft < 0) {
    hoverLeft = 0;
  } else {
    // Check if too close to right
    if ((hoverLeft + hoverBoxWidth) > document.body.clientWidth) {
      hoverLeft = document.body.clientWidth - hoverBoxWidth - 5;
    }
  }
  // Add it to the body, the hover box height doesn't get calculated unless we do
  var hoverTop = offsetTop + obj.offsetHeight + 20;
  // Check if too close to bottom
  if ((hoverTop + hoverBox.offsetHeight) >
      (document.body.clientHeight + document.body.scrollTop)) {
    hoverTop = offsetTop - hoverBox.offsetHeight - 15;
  }
  hoverBox.style.top = hoverTop;
  hoverBox.style.left = hoverLeft;
  hoverBox.style.visibility = 'visible';
}

function winopen(anywindow, bar, loc, mbar, scrbar, w, h, res) {
  if (w == 0 || h == 0) {
    // We assume a normal "^N" operation is wanted if no height or width is specified
    window.open (anywindow, "_blank");
  } else {
    window.open (anywindow, "_blank",
                 "toolbar="+bar+",location="+loc+",menubar="+mbar+",scrollbars="+scrbar+
                 ",width="+w+",height="+h+",resizeable="+res+",status=no"); 
  }
}

function Void() {
}

