/**
 * @fileoverview 
 * <p>This library contains support functions for the viewer.
 * <ul>
 *  <li>The main container is a Gallery.</li>  
 *  <li>A Gallery may hold one or more albums.</li>
 *  <li>An Album may hold one or more Slides.</li>
 *  <li>Slides are generic and can really be anything, holding a full 
 *   presentation and possibly a thumb presentation.</li>
 *  <li>A specialized subclass of Slide called Photo is provided to help preload
 *    images for slides that contain images, and build the proper html.</li>
 *  <li>Multiple Galleries are allowed and should not conflict</li>
 *  <li>The user of this api is allowed to provide their own styles and icons</li>
 * </ul>
 * </p>
 * @author Robert D. Rice
 */

// vc_id = "$Id: viewer.js 11502 2008-05-05 20:58:48Z robert $"
 
// gallery registry
var GALLERIES = new Object( );

/**
 * registration method for a gallery
 * @param the gallery object to register
 */
function registerGallery( gallery ) {
  GALLERIES[ gallery.name ] = gallery;
}

/**
 * retrieve a gallery based on unique name
 * @param name of the gallery
 * @return gallery object
 */
function retrieveGallery( name ) {
  return GALLERIES[ name ];
}

/** gallery object and function */

/**
 * Constructor for a gallery.
 * @constructor
 * @param name for this gallery, required
 * @return a new Gallery object
 */
function Gallery( name ) {
  this.name = name;
  this.albums = new Array( );
  this.index = 0;
  this.loop = false;
  this.delay = 3000;
  this.thumbLoaded = false;
  this.albumLoaded = false;
  this.captionState = 1; // Default: Sticky partial display
  this.gridRows;
  this.gridCols;

  /** setup the default icons, can be customized */
  this.icons = new Object( );
  this.icons.increment = "+";
  this.icons.decrement = "-";
  this.icons.play = "&gt;";
  this.icons.pause = "&#61;";
  this.icons.select = "&uarr;";
  this.icons.pre = "";
  this.icons.delimiter = " ";
  this.icons.post = "";
  this.icons.index = new Array( "0","1","2","3","4","5","6","7","8","9" );
  this.icons.current = new Array( "0","1","2","3","4","5","6","7","8","9" );

  /** set the default classes, generic across galleries, can be customized */
  this.classes = new Object( );
  this.classes.full = "VIEWER_FULL";
  this.classes.thumb = "VIEWER_THUMB";
  this.classes.increment = "VIEWER_PLUS";
  this.classes.decrement = "VIEWER_MINUS";
  this.classes.play = "VIEWER_PLAY";
  this.classes.select = "VIEWER_SELECT";
  this.classes.index = "VIEWER_INDEX";
  this.classes.grid = "VIEWER_GRID";
  this.classes.current = "VIEWER_CURRENT";
  this.classes.albums = "VIEWER_ALBUMS";

  /** set the default menu properties, can be customized */
  this.menustyle = new Object( );
  // only provided due to ie lack of support for max-height in stylesheet
  this.menustyle.maxHeight = null;

  /** set the default tooltips, can be customized */
  this.tooltips = new Object( );
  this.tooltips.increment = "Next";
  this.tooltips.decrement = "Previous";
  this.tooltips.play = "Play";
  this.tooltips.pause = "Pause";
  this.tooltips.select = "Albums";
  
  /** set the element ids, specific to a gallery */
  this.ids = new Object( );
  this.ids.full = "VIEWER_FULL_" + name;
  this.ids.caption = "VIEWER_CAPTION_" + name;
  this.ids.thumb = "VIEWER_THUMB_" + name;
  this.ids.play = "VIEWER_PLAY_" + name;
  this.ids.select = "VIEWER_SELECT_" + name;
  this.ids.index = "VIEWER_INDEX_" + name;
  this.ids.grid = "VIEWER_GRID_" + name;
  
  /** gallery methods */
  this.addAlbum = GalleryAddAlbum;
  this.setAlbum = GallerySetAlbum;
  this.getFull = GalleryGetFull;
  this.getIndex = GalleryGetIndex;
  this.getGrid = GalleryGetGrid;
  this.getPlus = GalleryGetPlus;
  this.getMinus = GalleryGetMinus;
  this.getPlay = GalleryGetPlay;
  this.getSelect = GalleryGetSelect;
  this.getAlbum = GalleryGetAlbum;
  this.increment = GalleryIncrement;
  this.decrement = GalleryDecrement;
  this.select = GallerySelect;
  this.show = GalleryShow;
  this.describe = GalleryDescribe;
  this.caption = GalleryCaption;
  this.play = GalleryPlay;
  this.animate = GalleryAnimate;
  this.displayFull = GalleryDisplayFull;
  this.displayIndex = GalleryDisplayIndex;
  this.displayGrid = GalleryDisplayGrid;
  this.displayPlus = GalleryDisplayPlus;
  this.displayMinus = GalleryDisplayMinus;
  this.displayPlay = GalleryDisplayPlay;
  this.displaySelect = GalleryDisplaySelect;
  this.displayCaption = GalleryDisplayCaption;
  this.setIcons = GallerySetIcons;
  this.setClasses = GallerySetClasses;
  this.setTooltips = GallerySetTooltips;
  this.setMenustyle = GallerySetMenustyle;
  this.indexToIcon = GalleryIndexToIcon;
  this.setDelay = GallerySetDelay;
  this.position = GalleryPosition;
  this.loadAlbum = GalleryLoadAlbum;
  this.loadThumb = GalleryLoadThumb;
  this.getMenu = GalleryGetMenu;

  /** self registration upon construction */
  registerGallery( this );
}

/**
 * add an album to the gallery
 * @param album object
 */
function GalleryAddAlbum( album ) {
  this.albums[ this.albums.length ] = album;
}

/**
 * get the album
 * @param album index, optional, defaults to current
 * @return an album
 */
function GalleryGetAlbum( index ) {
  return ( index == null )  ? this.albums[ this.index ] : this.albums[ index ];
}

/**
 * set the current album index
 * @param index
 */
function GallerySetAlbum( index ) {
  this.index = index;
}

/**
 * get the full gallery slide
 * @return outputs html representing the gallery main
 */
function GalleryGetFull( ) {
  var album = this.getAlbum( );
  var result = "";
  if( album.slides[album.index] )
  {
    result = '<div id="' + this.ids.full + '" class="' + this.classes.full + '" onclick="retrieveGallery(\'' + this.name + '\').caption(true);return true;">' +
      album.slides[album.index].full + '</div>';
  }
  return result;
}

/**
 * Writes the current slide's caption to the caption holder
 * defined by GalleryDisplayCaption.
 * @param toggle if true, will toggle the visibility
 *        state of the caption. Cycles through hover-only
 *        partial display, sticky partial display (default),
 *        and full display.
 * @param temporary if true, the visibility state of the
 *        caption will not be toggled.
 */
function GalleryCaption( toggle, temporary ) {

  if( toggle && !temporary ) this.captionState = (this.captionState + 1) % 3;

  // If hint is true, then the caption is currently hidden from view...
  var hint = temporary && (this.captionState == 0);

  // If hint is true, then toggle effectively becomes a hide/show caption flag...
  // Otherwise, visible defaults to the current visibility state.
  var visible = hint ? (toggle ? 1 : 0) : this.captionState;

  var canvas = document.getElementById(this.ids.caption);

  if( canvas )
  {

    var album = this.getAlbum( );
    var slide = album.slides[album.index];

    // Only consider displaying the caption if it will contain valuable information...
    if( slide && ( ( slide.album && slide.album.description ) || slide.caption ) )
    {

      switch( visible )
      {

        // Visibility state: Full display
        case 2:
        {

          var caption = '';

          caption += '<span style="float:right;" onclick="retrieveGallery(\'' + this.name + '\').caption(true);return true;">(click to hide)</span>';

          if( slide.album.title ) caption += '<h3>' + slide.album.title + '</h3>';
          if( slide.caption ) caption += '<p class="slide-caption">' + slide.caption + '</p>';
          if( slide.album.description ) caption += '<p class="album-caption">' + slide.album.description + '</p>';

          canvas.style.height = 'auto';
          canvas.parentNode.style.display = "block";
          canvas.parentNode.parentNode.style.cursor = "pointer";

          canvas.innerHTML = caption;

          break;

        }

        // Visibility state: Partial display
        case 1:
        {

          // Hover mode...
          if( hint )
          {
            canvas.parentNode.style.filter = "Alpha(Opacity=50)";
            canvas.parentNode.style.opacity = "0.5";

          // Sticky mode...
          } else {
            canvas.parentNode.style.filter = "Alpha(Opacity=80)";
            canvas.parentNode.style.opacity = "0.8";
          }

          var caption = '';

          caption += '<span style="float:right;" onclick="retrieveGallery(\'' + this.name + '\').caption(true);return true;">(click to ' + ( hint ? 'sticky' : 'show' ) + ')</span>';

          caption += '<strong>' + slide.album.title + '.</strong> ';

          var preview = slide.caption ? slide.caption : slide.album.description;
          var MAX = 32;
          var limit = preview.indexOf(' ',MAX);
          if( limit < 0 || limit > MAX+(MAX/4) ) limit = MAX;
          caption += preview.substring( 0, limit );
          if( preview.length > limit ) caption += '...';

          canvas.style.height = 'auto';
          canvas.parentNode.style.display = "block";
          canvas.parentNode.parentNode.style.cursor = "pointer";

          canvas.innerHTML = caption;

          break;

        }

        // Visibility state: Hidden
        case 0:
        {
          canvas.parentNode.style.display = "none";
          canvas.style.height = "0px";
          break;
        }

      }

    // The caption cannot be displayed for this slide, as it would not contain valuable information...
    } else {
      canvas.parentNode.parentNode.style.cursor = "default";
      canvas.parentNode.style.display = "none";
      canvas.style.height = "0px";
    }

  }

}

/**
 * display the container for the slide caption
 * @note outputs html which will contain the slide caption
 */
function GalleryDisplayCaption( ) {
  document.write( '<div id="' + this.ids.caption + '"></div>' );
}

/**
 * display the full gallery slide
 * @note outputs html representing the gallery main
 */
function GalleryDisplayFull( ) {
  document.write( this.getFull( ) );
}

/**
 * get the gallery index
 * @return html representing the gallery index
 */
function GalleryGetIndex( ) {

  var msg = "";

  msg += this.icons.pre;
  for ( var i = 0; i < this.getAlbum().slides.length; i++ ) {

    var onclick = "retrieveGallery( '" + this.name + "' ).show(" + i + "); return false;";
    var onmouseover = "retrieveGallery( '" + this.name + "' ).describe( event, " + i + "); return false;";
    var onmouseout = "retrieveGallery( '" + this.name + "' ).describe( ); return false;";

    msg += i > 0 ? this.icons.delimiter : ""; 
    msg += '<a href="#" onclick="' + onclick + '" onmouseover="' + onmouseover + '" onmouseout="' + onmouseout + '" id="' + this.ids.index + "_" + i + '">';
    msg += this.indexToIcon( i + 1, this.getAlbum().index == i );
    msg += '</a>';

  }
  msg += this.icons.post;

  return msg;

}

/**
 * this function converts an integer index 
 * to a string icon representation
 * @param integer index
 * @param boolean true if this is selected
 * @return string icon
 */
function GalleryIndexToIcon( index, selected ) {
  var str = new String( index );
  var icon = "";
  for ( var i = 0; i < str.length; i++ ) {
    if ( selected ) {
      if ( i == 0 ) { icon = icon + '<span class="' + this.classes.current + '">'; }
      icon = icon + this.icons.current[ parseInt( str.charAt( i ) ) ];
      if ( i == ( str.length - 1 ) ) { icon = icon + '</span>'; }
    } else {
      icon = icon + this.icons.index[ parseInt( str.charAt( i ) ) ];
    }
  }
  return icon;
}


/**
 * get the gallery grid
 * @param rows, number of rows to constrain, 0 = unconstrained
 * @param cols, number of cols to constrain, 0 = unconstrained
 * @note outputs html representing the gallery grid
 */
function GalleryGetGrid( rows, cols ) {
    var grid = "";
    
    if ( rows || cols ) {
  var album = this.getAlbum( );
  var size = album.slides.length;

  // cache the settings for internal use
  this.gridRows = rows;
  this.gridCols = cols;

  // calculate any missing settings to construct grid
  if ( !cols ) {
      cols = Math.ceil( size / rows );
  } else if ( !rows ) {
      rows = Math.ceil( size / cols );
  }

  var i = 0;
  //grid += "<table>";
  grid += "<table id='" + this.ids.thumb + "'>";   // updated by Michael, didn't fix, but didn't break code
  
  for ( var row = 0; row < rows; row++ ) {
      grid += "<tr>";
      for ( var col = 0; col < cols; col++ ) {
    var slide = i < size ? album.slides[i] : null;
    var content = slide && slide.thumb ? slide.thumb : "";
    if ( slide && slide.album && slide.album.title ) {
        content = "<h3>" + slide.album.title + "</h3>" + content;
    }
    var onmouseover = "retrieveGallery( '" + this.name + "' ).show(" + i + "); return false;";
    var onclick = "retrieveGallery( '" + this.name + "' ).caption( true ); return false;";
    grid += '<td class="' + this.classes.thumb + '" id="' + this.ids.thumb + i + 
        '"><a href="#" onmouseover="' + onmouseover + '" onclick="' + onclick + '">' + content + '</a></td>';
    i++;
      } 
      grid += "</tr>";
  }
  grid += "</table>";
    }

    return grid;
}

/**
 * get the gallery thumb.
 * usually this is handled automatically for you, by calling displayIndex.
 * there are cases when positioning issues occur, that you will want to call 
 * this method first outside of the gallery layout.
 * @note outputs html representing the gallery thumb
 */
function GalleryLoadThumb( ) {
  document.write( '<div id="' + this.ids.thumb + 
       '" class="' + this.classes.thumb +
       '" style="position:absolute;z-index:100;top:0px;left:0px;display:none;"></div>' );
  this.thumbLoaded = true;
}

/**
 * get the gallery index
 * @note outputs html representing the gallery index
 */
function GalleryDisplayIndex( ) {
  document.write( '<div id="' + this.ids.index + '" class="' + this.classes.index +
       '">' + this.getIndex( ) + '</div>' );
  if ( !this.thumbLoaded ) { this.loadThumb( ); }
}

/**
 * get the gallery grid
 * @param rows, number of rows to constrain, 0 = unconstrained
 * @param cols, number of cols to constrain, 0 = unconstrained
 * @note outputs html representing the gallery grid
 */
function GalleryDisplayGrid( rows, cols ) {
    document.write( '<div id="' + this.ids.grid + '" class="' + this.classes.grid +
        '">' + this.getGrid( rows, cols ) + '</div>' );
}

/**
 * get the plus control
 * @return html representing the gallery plus control
 */
function GalleryGetPlus( ) {
    var onclick = "retrieveGallery( '" + this.name + "' ).increment( ); return false;";
    var onmouseover = ""
    return '<a href="#" title="' + this.tooltips.increment + '" onclick="' + onclick + '" onmouseover="' + onmouseover + 
           '" class="' + this.classes.increment + '"' +
           '">' + this.icons.increment + '</a>';
}

/**
 * get the plus control
 * @note outputs html representing the gallery plus control
 */
function GalleryDisplayPlus( ) {
    document.write( this.getPlus( ) );
}

/**
 * get the minus control
 * @return html representing the gallery minus control
 */
function GalleryGetMinus( ) {
    var onclick = "retrieveGallery( '" + this.name + "' ).decrement( ); return false;";
    var onmouseover = ""
    return '<a href="#" title="' + this.tooltips.decrement + '" onclick="' + onclick + '" onmouseover="' + onmouseover + 
           '" class="' + this.classes.decrement + '"' +
           '">' + this.icons.decrement + '</a>';
}

/**
 * get the minus control
 * @note outputs html representing the gallery minus control
 */
function GalleryDisplayMinus( ) {
    document.write( this.getMinus( ) );
}

/**
 * get the play control
 * @return html representing the gallery play control
 */
function GalleryGetPlay( ) {
    var onclick = "retrieveGallery( '" + this.name + "' ).play( ); return false;";
    var onmouseover = ""
    return '<a href="#" title="' + this.tooltips.play + '" onclick="' + onclick + '" onmouseover="' + onmouseover +
         '" class="' + this.classes.play + '"' +
         '" id="' + this.ids.play + '">' + this.icons.play + '</a>';
}

/**
 * get the play control
 * @note outputs html representing the gallery play control
 */
function GalleryDisplayPlay( ) {
    document.write( this.getPlay( ) );
}

/**
 * get the album select control
 * @return outputs html representing the gallery albums
 * @note known issue, some web browsers will remember last selection,
 *       regardless of 'selected' attribute, thus creating an inconsistency
 *       between the value of this field and the actual selected gallery when
 *       the page is reloaded.
 */
function GalleryGetSelect( ) {
    var select = "";
    select += '<select id="' + this.ids.select + '" name="album-select" onchange="retrieveGallery(\'' + this.name + '\').select(this.selectedIndex); return false;">';
    for( var i = 0; i < this.albums.length; i++ )
    {
    var album = this.getAlbum(i);
    var description = album.title ? album.title : album.slides.length + " slides";
    select += '  <option value="' + i + '"' + ( i == this.index ? ' selected="selected"' : '' ) + '>' + description + '</option>';
  }
    select += '</select>';
    return select;
}

/**
 * get the album menu
 * @return outputs html representing the gallery albums
 * @note As of 01/30/2008, obsoleted by GalleryGetSelect().
 */
function GalleryGetMenu( ) {
    var albums = "";
    var size = this.albums.length;
    albums = albums + '<ul>';
    for ( var i = 0; i < this.albums.length; i++ ) {
      var album = this.getAlbum( i );
      var description = "";
      var click = "retrieveGallery( '" + this.name + "' ).select( " + i + " ); return false;";
      var mouseover = "";
      if ( album.title ) {
        description = description + album.title;
      } else {
        description = description + album.slides.length + " slides";
      }

      albums = albums + '<li>' +
         '<a href="#" onclick="' + click + '" onmouseover="' + mouseover + '">' + description + '</a></li>';
    }
    albums = albums + '</ul>';
    this.albumLoaded = true;
    return '<div id="' + this.ids.select +
       '" class="' + this.classes.albums +
       '" style="position:absolute;z-index:100;top:0px;left:0px;display:none;">' +
       albums + '</div>';
}

/**
 * get the gallery album.
 * usually this is handled automatically for you, by calling displaySelect.
 * there are cases when positioning issues occur, that you will want to call
 * this method first outside of the gallery layout.
 * @note outputs html representing the gallery album
 */
function GalleryLoadAlbum( ) {
    document.write( this.getMenu( ) );
}

/**
 * get the album select control
 * @note outputs html representing the gallery albums
 */
function GalleryDisplaySelect( ) {
    document.write( this.getSelect( ) );
}

/**
 * select the album to see
 * @param event or album index, depending on use
 */
function GallerySelect( e ) {
    this.setAlbum( e );
    index = document.getElementById( this.ids.index );
    if ( index ) {
        index.innerHTML = this.getIndex( );
    }
    grid = document.getElementById( this.ids.grid );
    if ( grid ) {
        grid.innerHTML = this.getGrid( this.gridRows, this.gridCols );
    }
    this.show( this.getAlbum( ).index );
}

/**
 * describe the slide
 * @param event
 * @param slide index
 */
function GalleryDescribe( e, i ) 
{
    
    var el = document.getElementById( this.ids.thumb );   
    if ( e ) 
    {
        this.position( e, el );
        if ( i != null ) 
        {
            var slide = this.getAlbum( ).slides[ i ];
            if( slide && slide.thumb ) 
            {
                el.innerHTML = slide.thumb;
                if( slide.album && slide.album.title )
                el.innerHTML = "<h3>" + slide.album.title + "</h3>" + el.innerHTML;
            } else {
                el.style.display = "none";
            }
        }
   } else {
      el.style.display = "none";
   }
}

function f_scrollTop() {
  return f_filterResults (
    window.pageYOffset ? window.pageYOffset : 0,
    document.documentElement ? document.documentElement.scrollTop : 0,
    document.body ? document.body.scrollTop : 0
  );
}
function f_filterResults(n_win, n_docel, n_body) {
  var n_result = n_win ? n_win : 0;
  if (n_docel && (!n_result || (n_result > n_docel)))
    n_result = n_docel;
  return n_body && (!n_result || (n_result > n_body)) ? n_body : n_result;
}

/**
 * set the position of the element
 * @param event to pull position information
 * @param element to position
 */
function GalleryPosition( e, el ) {
    var target = e.target || e.srcElement;
    var xPosition = e.pageX || e.clientX;
    var yPosition = e.pageY || e.clientY;
    var xPositionTarget = e.layerX || e.offsetX ;
    var yPositionTarget = e.layerY || e.offsetY ;

    el.style.left = "" + xPosition + "px";
    el.style.display = "block";

    // it is unfortunate to introduce this control.  max-height is a standard
    // css property that ie does not honour.  emulation of it requires trickery.
    // max-height: 300px; /** non ie */
    // overflow:auto;
    // height:expression( this.scrollHeight > 300? "300px" : "auto" ); /** ie */
    // width:expression( this.scrollHeight > 300? "200px" : "auto" ); /** get rid of horizontal scroll */
    if ( this.menustyle.maxHeight ) 
    {
      if ( el.scrollHeight > this.menustyle.maxHeight ) 
      {
          el.style.height = this.menustyle.maxHeight + 'px';
          el.style.overflow = 'auto';
          if ( !e.pageX ) { el.style.width = ( el.clientWidth + 20 ) + 'px'; /** ie get rid of horiz scroll */ }
      } 
    } 
    
    // START ADD
    var browserName=navigator.appName; 
    if (browserName=="Microsoft Internet Explorer")
    {
        //alert( e.clientY + " " + document.body.scrollTop + " " + f_scrollTop() );
        var y = e.clientY + f_scrollTop() - 125;
        el.style.top = "" + y + "px";    
        
    }else{
       var y = yPosition - el.clientHeight - 5;
       el.style.top = "" + y + "px";
    }
    // END ADD

    //var y = yPosition - el.clientHeight - 5;
    //el.style.top = "" + y + "px";
}

/**
 * get the next slide
 */
function GalleryIncrement( ) {
  var album = this.getAlbum( );
  var index = album.index + 1;

  // wrap
  if ( index >= album.slides.length ) {
    index = 0;
  }

  this.show( index );
}

/**
 * get the previous slide
 */
function GalleryDecrement( ) {
  var album = this.getAlbum( );
  var index = album.index - 1;

  // wrap
  if ( index < 0 ) {
      index = album.slides.length - 1;
  }

  this.show( index );
}

/**
 * show a specific slide, in the current album
 * @param the index to show
 */
function GalleryShow( index ) {
  var album = this.getAlbum( );
  if ( index >= 0 && index < album.slides.length ) 
  {
    // refresh the content
    var full = document.getElementById( this.ids.full );
    if ( full ) {
      full.innerHTML = album.slides[index].full;
    }

    // conditionally manipulate the index
    if ( album.index != index ) 
    {
      var el = document.getElementById( this.ids.index + "_" + album.index );
      if ( el ) {
        // demphasize
        el.innerHTML = this.indexToIcon( album.index + 1, false );
      }
      album.index = index;
      el = document.getElementById( this.ids.index + "_" + album.index );
      if ( el ) {
        // emphasize
        el.innerHTML = this.indexToIcon( album.index + 1, true );
      }
    }
  }
  this.caption( );
}

/**
 * trigger the gallery to play/pause
 */
function GalleryPlay( ) {
  this.loop = !this.loop;
  var el = document.getElementById( this.ids.play );
  if ( el ) {
    el.innerHTML = this.loop ? this.icons.pause : this.icons.play;
    el.title = this.loop ? this.tooltips.pause : this.tooltips.play;
  }
  this.animate( );
}

/**
 * key animation function, handles delay, called internally
 */
function GalleryAnimate( ) {
  if ( this.loop ) {
    this.increment( );
    setTimeout( "retrieveGallery( '" + this.name + "' ).animate( )", this.delay );
  }
}

/**
 * Set the custom icons
 * @param object holding custom icons.
 *  does not have to be fully populated. defaults 
 *  will be used for any missing keys
 */
function GallerySetIcons( icons ) {
  for( key in icons ) {
    this.icons[ key ] = icons[ key ];
  }
}

/**
 * Set the custom style classes
 * @param object holding custom classes.
 *  does not have to be fully populated. defaults 
 *  will be used for any missing keys
 */
function GallerySetClasses( classes ) {
  for( key in classes ) {
    this.classes[ key ] = classes[ key ];
  }
}

/**
 * Set the custom style tooltips
 * @param object holding custom tooltips.
 *  does not have to be fully populated. defaults 
 *  will be used for any missing keys
 */
function GallerySetTooltips( tooltips ) {
  for( key in tooltips ) {
    this.tooltips[ key ] = tooltips[ key ];
  }
}

/**
 * Set the custom style menus
 * @param object holding custom menu info.
 *  does not have to be fully populated. defaults 
 *  will be used for any missing keys
 */
function GallerySetMenustyle( menustyle ) {
  for( key in menustyle ) {
    this.menustyle[ key ] = menustyle[ key ];
  }
}

/**
 * Setter for the animation delay
 * @param delay in millis
 */
function GallerySetDelay( delay ) {
  this.delay = delay;
}

/**
 * Setter for the menu max height
 * Normally, this would simply be set in a stylesheet using max-height.
 * Unfortunately, ie does not directly support this attribute.
 * @param max height in pixels
 */
function GallerySetMaxHeight( maxHeight ) {
  this.maxHeight = maxHeight;
}

/** album object, a gallery has one or more galleries */

/**
 * Constructor for an album
 * @constructor
 * @param title
 * @param description, optional
 * @return new Album instance
 */
function Album( title, description ) {
  this.title = title;
  this.description = description;
  this.slides = new Array( );
  this.index = 0; 

  /* album methods */
  this.addSlide = AlbumAddSlide;
}

/**
 * add a slide to the album
 * @param slide object
 */
function AlbumAddSlide( slide ) {
  this.slides[ this.slides.length ] = slide;
}

/** slide object, an album has one or more slides */

/**
 * Constructor for a slide.  This is really a
 * generic container for a full and thumb html presentation.
 * @constructor
 * @param full, html
 * @param thumb, html
 * @param album, Album, optional
 * @return new Slide instance
 */
function Slide( full, thumb, album ) {
  this.full = full ? full : null;
  this.thumb = thumb ? thumb : null;
  this.album = album ? album : null;
}

/**
 * Constructor for a photo slide.
 * Methods are provided to help build img html and preload.
 * This is a specialization of Slide.
 * @constructor
 * @param full, html, most likely img tag
 * @param thumb, html, most likely img tag
 * @param caption, html, most likely plaintext, optional
 * @return new Slide instance
 */
function Photo( full, thumb, caption ) {
  this.full = full ? full : null;
  this.thumb = thumb ? thumb : null;
  this.caption = caption ? caption : null;

  this.toTag = PhotoToTag;
  this.preload = PhotoPreload;
  this.setFullSrc = PhotoSetFullSrc;
  this.setThumbSrc = PhotoSetThumbSrc;
  this.setCaption = PhotoSetCaption;
}
Photo.prototype = new Slide( );

/**
 * utility method to build an image tag from
 * it's components.
 * @param src url
 * @param width, optional
 * @param height, optional
 * @return image tag
 */
function PhotoToTag( src, width, height ) {
  var msg = '<img src="' + src;
  if ( width ) {
    msg = msg + '" width="' + width;
  }
  if ( height ) {
    msg = msg + '" height="' + height;
  }
  msg = msg + '" border="0" />';
  return msg;
}

/**
 * utility method to preload an image
 * @param src ulr
 * @return image object
 */
function PhotoPreload( src ) {
  var image = new Image( );
  image.src = src;
  return image;
}

/**
 * method to set a full image tag from
 * it's components, and preload it.
 * @param src url
 * @param width, optional
 * @param height, optional
 */
function PhotoSetFullSrc( src, width, height ) {
  this.full = this.toTag( src, width, height );
  this.preload( src );
}

/**
 * method to set a thumb image tag from
 * it's components, and preload it.
 * @param src url
 * @param width, optional
 * @param height, optional
 */
function PhotoSetThumbSrc( src, width, height ) {
  this.thumb = this.toTag( src, width, height );
  this.preload( src );
}

/**
 * method to set a caption for the image
 * @param caption
 */
function PhotoSetCaption( caption ) {
  this.caption = caption;
}

