/* fadetext.js - fading text object - Eric Pridham - February, 2001
 */

var ft_g_aThisPointers = new Array(); // array of pointers to each FadingText object created
var ft_g_intNumHandles = 0;

function FadingText(p_hdlElem, p_strText)
/*
 * Constructor.
 *
 * p_hdlElem - Element to which the child TextNode will be appended.
 * p_strText - Content of the TextNode that will be created.
 */
{
  // make sure Element supports dynamic insertion
  if( !p_hdlElem.appendChild )
  {
    alert('FadingText Constructor: element object passed is not supported');
    return;
  }

  this.strText = ((p_strText)?p_strText:'');

  // text color
  this.intBaseRed   = 0;   // Base colors are the starting colors for the text
  this.intBaseGreen = 0;   // and used as the lower bound in fading.
  this.intBaseBlue  = 0;
  this.intMaxRed    = 255; // Max colors are used as the upper bound in fading.
  this.intMaxGreen  = 255;
  this.intMaxBlue   = 255;
  this.intCurRed    = 0;   // Current colors.
  this.intCurGreen  = 0;
  this.intCurBlue   = 0;

  // fade settings
  this.intRedInIncr    = 1; // Rates at which the colors fade in.
  this.intGreenInIncr  = 1;
  this.intBlueInIncr   = 1;
  this.intRedOutIncr   = 1; // Rates it which the colors fade out.
  this.intGreenOutIncr = 1;
  this.intBlueOutIncr  = 1;
  this.intTimerSpeed   = 50;

  // handles
  this.hdlThis     = ft_fRegisterThis(this);
  this.hdlElem     = p_hdlElem;            // document element that will contain the text node
  this.hdlTextNode = p_hdlElem.firstChild; // default to any text that may be in the element
                                           // NOTE: assumes at most one text node
  this.hdlTimer    = null;                 // returned by setTimeout()

  // object event handlers
  this.onFadeIn  = null; // called after fade-in finishes
  this.onFadeOut = null; // called after fade-out finishes

  // flags
  this.bStay = false; // does the text stay after fading out?
}

// utility functions
function ft_fRegisterThis(p_ptrThis)
/*
 * Store off the FadingText object pointer p_ptrThis to the array, and return the
 * index into the array for that object pointer.
 *
 * Here is why this is necessary (I'll try to keep it brief).  The FadingText object
 * has some member functions associated with it (which I define with 'prototype' at
 * the bottom of this file) that need to refer to the calling object.  Normally, all
 * one would need to do is use the 'this' pointer, which is a pointer to the object
 * associated with the function call.  However, to do the fading, I needed to use
 * the setTimeout routine, and there is no way to pass the 'func' parameter to it
 * such that it would keep the same 'this' context.  So, I have to store off these
 * pointers in an array, and save the index for that objects pointer as a property of
 * the object (see ft_fRegisterThis).  Using a simple integer for an index, I can easily
 * embed that index in the 'func' paramenter of setTimeout.
 *
 * So, the first time the fading function is called (using myfadingtext.fadeIn()), the
 * associated prototype function ft_fFadeIn() grabs the calling object using 'this'.
 * With that, it grabs the hdlThis property (which is the index into the array of object
 * pointers) and passes that to the 'recursive' setTimeout function call.  From then on,
 * each subsequent call to ft_fFadeIn() can use that index parameter to get the proper
 * object to use for it's 'this' context.
 *
 * Simple.
 */
{
  // index starting at 1
  ft_g_aThisPointers[++ft_g_intNumHandles] = p_ptrThis;
  return ft_g_intNumHandles;
}

function ft_fGetThis(p_hdlThis)
/*
 * Return the pointer to the FadingText object located at index location
 * p_hdlThis.
 */
{
  return ft_g_aThisPointers[p_hdlThis];
}

// member functions

// sets
function ft_fSetText(p_strText)
{
  this.strText = ((p_strText)?p_strText:'');
  ft_fDestroyTextNode(this);
  ft_fCreateTextNode(this);
}

function ft_fSetBase(p_intRed, p_intGreen, p_intBlue)
{
  this.intBaseRed   = ((p_intRed)  ? p_intRed   : 0);
  this.intBaseGreen = ((p_intGreen)? p_intGreen : 0);
  this.intBaseBlue  = ((p_intBlue) ? p_intBlue  : 0);

  // make sure the current colors are at or above the base
  ft_fAdjustCurToBase(this);
}

function ft_fSetMax(p_intRed, p_intGreen, p_intBlue)
{
  this.intMaxRed   = ((p_intRed)  ? p_intRed   : 0);
  this.intMaxGreen = ((p_intGreen)? p_intGreen : 0);
  this.intMaxBlue  = ((p_intBlue) ? p_intBlue  : 0);

  // make sure the current colors are at or below max
  ft_fAdjustCurToMax(this);
}

function ft_fSetCur(p_intRed, p_intGreen, p_intBlue)
{
  this.intCurRed   = ((p_intRed)  ? p_intRed   : 0);
  this.intCurGreen = ((p_intGreen)? p_intGreen : 0);
  this.intCurBlue  = ((p_intBlue) ? p_intBlue  : 0);

  // make sure the new current colors are within the base and max
  ft_fAdjustCurToRanges(this);
}

function ft_fSetInIncr(p_intRed, p_intGreen, p_intBlue)
{
  this.intRedInIncr   = ((p_intRed)  ? p_intRed   : 0);
  this.intGreenInIncr = ((p_intGreen)? p_intGreen : 0);
  this.intBlueInIncr  = ((p_intBlue) ? p_intBlue  : 0);
}

function ft_fSetOutIncr(p_intRed, p_intGreen, p_intBlue)
{
  this.intRedOutIncr   = ((p_intRed)  ? p_intRed   : 0);
  this.intGreenOutIncr = ((p_intGreen)? p_intGreen : 0);
  this.intBlueOutIncr  = ((p_intBlue) ? p_intBlue  : 0);
}

function ft_fSetTimerSpeed(p_intSpeed)
{
  this.intSpeed = ((p_intSpeed)?p_intSpeed:0);
}

function ft_fSetStay(p_bSetStay)
/*
 * Set the stay bit, and create/remove text node as necessary.
 */
{
  this.bStay = p_bSetStay;

  // if text is set to stay, create TextNode immediately
  if( this.bStay )
  {
    ft_fCreateTextNode(this);
    ft_fSetTextColor(this);
  }
  // otherwise, remove it
  else
  {
    ft_fDestroyTextNode(this);
  }
}

function ft_fFadeIn(p_hdlThis)
/*
 * Handles the fading in process.  See the ft_fRegisterThis() function comments
 * for an explaination of the 'this' pointer issue.
 */
{
  var bDone = true;
  var ptrThis;

  if( !p_hdlThis )
  {
    // if no pointer handle is passed, it is the first time being called for
    // this object, so we can use the 'this' pointer for our 'this' context
    ptrThis = this;
  }
  else
  {
    // otherwise we have to use our 'this' handle to retrieve our 'this'
    // context from the get function
    ptrThis = ft_fGetThis(p_hdlThis);
  }

  // create the text node upon initial call to fade if stay flag is set to false
  if( !ptrThis.bStay )
  {
    ft_fCreateTextNode(ptrThis);
  }

  // if there are any timeouts set for this object, clear them (basically,
  // this stops a fade out and a fade in from both happening at once).
  if( ptrThis.hdlTimer )
  {
    clearTimeout(ptrThis.hdlTimer);
  }

  if( ptrThis.intCurRed < ptrThis.intMaxRed )
  {
    ptrThis.intCurRed += ptrThis.intRedInIncr;
    if(ptrThis.intCurRed > ptrThis.intMaxRed)
      ptrThis.intCurRed = ptrThis.intMaxRed;
    bDone = false;
  }

  if( ptrThis.intCurGreen < ptrThis.intMaxGreen )
  {
    ptrThis.intCurGreen += ptrThis.intGreenInIncr;
    if(ptrThis.intCurGreen > ptrThis.intMaxGreen)
      ptrThis.intCurGreen = ptrThis.intMaxGreen;
    bDone = false;
  }

  if( ptrThis.intCurBlue < ptrThis.intMaxBlue )
  {
    ptrThis.intCurBlue  += ptrThis.intBlueInIncr;
    if(ptrThis.intCurBlue > ptrThis.intMaxBlue)
      ptrThis.intCurBlue = ptrThis.intMaxBlue;
    bDone = false;
  }

  // if we are done fading in, trigger the onFadeIn event
  if( bDone )
  {
    if( ptrThis.onFadeIn ) { ptrThis.onFadeIn() }
  }
  // otherwise, set the new color and continue the fade in process
  else
  {
    ptrThis.hdlTimer = setTimeout('ft_fFadeIn(' + ptrThis.hdlThis + ')', ptrThis.intTimerSpeed);
    ft_fSetTextColor(ptrThis);
  }
}

function ft_fFadeOut(p_hdlThis)
/*
 * Handles the fading out process.  See the ft_fRegisterThis() function comments
 * for an explaination of the 'this' pointer issue.
 */
{
  var bDone = true;
  var ptrThis;

  if( !p_hdlThis )
  {
    // if no pointer handle is passed, then we can use the 'this' pointer
    // for our 'this' context
    ptrThis = this;
  }
  else
  {
    // otherwise we have to use our 'this' handle to retrieve our 'this'
    // context from the get function
    ptrThis = ft_fGetThis(p_hdlThis);
  }

  // if there are any timeouts set for this object, clear them (basically,
  // this stops a fade out and a fade in from both happening at once).
  if( ptrThis.hdlTimer )
  {
    clearTimeout(ptrThis.hdlTimer);
  }

  if( ptrThis.intCurRed > ptrThis.intBaseRed   )
  {
    ptrThis.intCurRed -= ptrThis.intRedOutIncr;
    if( ptrThis.intCurRed < ptrThis.intBaseRed )
      ptrThis.intCurRed = ptrThis.intBaseRed;
    bDone = false;
  }

  if( ptrThis.intCurGreen > ptrThis.intBaseGreen )
  {
    ptrThis.intCurGreen -= ptrThis.intGreenOutIncr;
    if( ptrThis.intCurGreen < ptrThis.intBaseGreen )
      ptrThis.intCurGreen = ptrThis.intBaseGreen;
    bDone = false;
  }

  if( ptrThis.intCurBlue  > ptrThis.intBaseBlue  )
  {
    ptrThis.intCurBlue  -= ptrThis.intBlueOutIncr;
    if( ptrThis.intCurBlue < ptrThis.intBaseBlue )
      ptrThis.intCurBlue = ptrThis.intBaseBlue;
    bDone = false;
  }

  if( bDone )
  {
    // remove the TextNode upon fade out if stay flag is set to false
    if( !ptrThis.bStay )
    {
      ft_fDestroyTextNode(ptrThis);
    }
    if( ptrThis.onFadeOut ) { ptrThis.onFadeOut(); }
  }
  else
  {
    ptrThis.hdlTimer = setTimeout('ft_fFadeOut(' + ptrThis.hdlThis + ')', ptrThis.intTimerSpeed);
    ft_fSetTextColor(ptrThis);
  }
}

// utility functions
function ft_fSetTextColor(p_ptrThis)
{
  p_ptrThis.hdlElem.style.color = 'rgb(' + p_ptrThis.intCurRed + ',' + p_ptrThis.intCurGreen + ',' + p_ptrThis.intCurBlue + ')'
}

function ft_fCreateTextNode(p_ptrThis)
{
  if( !p_ptrThis.hdlTextNode )
  {
    p_ptrThis.hdlTextNode = document.createTextNode(p_ptrThis.strText);
    p_ptrThis.hdlElem.appendChild(p_ptrThis.hdlTextNode);
  }
}

function ft_fDestroyTextNode(p_ptrThis)
{
  if( p_ptrThis.hdlTextNode )
  {
    p_ptrThis.hdlElem.removeChild(p_ptrThis.hdlTextNode);
    p_ptrThis.hdlTextNode = null;
  }
}

function ft_fAdjusCurToRanges(p_ptrThis)
{
  ft_fAdjustCurToBase(p_ptrThis);
  ft_fAdjustCurToMax(p_ptrThis);
}

function ft_fAdjustCurToBase(p_ptrThis)
{
  if( p_ptrThis.intCurRed < p_ptrThis.intBaseRed )
  {
    p_ptrThis.intCurRed = p_ptrThis.intBaseRed;
  }
  if( p_ptrThis.intCurGreen < p_ptrThis.intBaseGreen )
  {
    p_ptrThis.intCurGreen = p_ptrThis.intBaseGreen;
  }
  if( p_ptrThis.intCurBlue < p_ptrThis.intBaseBlue )
  {
    p_ptrThis.intCurBlue = p_ptrThis.intBaseBlue;
  }
}

function ft_fAdjustCurToMax(p_ptrThis)
{
  if( p_ptrThis.intCurRed > p_ptrThis.intMaxRed )
  {
    p_ptrThis.intCurRed = p_ptrThis.intMaxRed;
  }
  if( p_ptrThis.intCurGreen > p_ptrThis.intMaxGreen )
  {
    p_ptrThis.intCurGreen = p_ptrThis.intMaxGreen;
  }
  if( p_ptrThis.intCurBlue > p_ptrThis.intMaxBlue )
  {
    p_ptrThis.intCurBlue = p_ptrThis.intMaxBlue;
  }
}

// connect member functions with object
FadingText.prototype.setText       = ft_fSetText;
FadingText.prototype.setBaseColor  = ft_fSetBase;
FadingText.prototype.setMaxColor   = ft_fSetMax;
FadingText.prototype.setCurColor   = ft_fSetCur;
FadingText.prototype.setInIncr     = ft_fSetInIncr;
FadingText.prototype.setOutIncr    = ft_fSetOutIncr;
FadingText.prototype.setTimerSpeed = ft_fSetTimerSpeed;
FadingText.prototype.setStay       = ft_fSetStay;

FadingText.prototype.fadeIn  = ft_fFadeIn;
FadingText.prototype.fadeOut = ft_fFadeOut;

