/////////////////////////////////////////////////////////////////////////
// Spa.SlideData
/////////////////////////////////////////////////////////////////////////
Spa.SlideData = function(p_xml)
{
 
 this.slides = new Array();
 this.slideGroups = new Array();
 var nodeSlideData = p_xml.selectSingleNode("slideData");
 if (nodeSlideData)
 {
 for (var i=0; i < nodeSlideData.childNodes.length; i++)
 {
 this.AddSlideGroup(nodeSlideData.childNodes[i]);
 }
 }
 else
 {
 var nodeSlides = p_xml.selectSingleNode("slides");
 if (nodeSlides)
 {
 this.AddSlideGroup(nodeSlides);
 }
 }
}
Spa.SlideData.prototype.AddSlideGroup = function(p_slideXml)
{
 var slideGroup = new Spa.SlideData.SlideGroup(p_slideXml, this.slides);
 this.slideGroups[this.slideGroups.length] = slideGroup;
 var iSpecifiedDataCount = slideGroup.fContainsData ? p_slideXml.childNodes.length : 0;
 var objSlide;
 for (var i=0; i < slideGroup.iCount; i++)
 {
 if (i < iSpecifiedDataCount)
 {
 this.slides[this.slides.length] = new Spa.SlideData.Slide(p_slideXml.childNodes[i]);
 }
 else
 {
 this.slides[this.slides.length] = new Spa.SlideData.Slide();
 }
 this.slides[this.slides.length]
 }
}
Spa.SlideData.prototype.GetSlideGroup = function(p_index)
{
 for (var i=0; i < this.slideGroups.length; i++)
 {
 if (this.slideGroups[i].ContainsIndex(p_index))
 {
 return(this.slideGroups[i]);
 }
 }
 return(null);
}
Spa.SlideData.SlideGroup = function(p_slidesNode, p_aobjSlides)
{
 this.aobjSlides = p_aobjSlides;
 this.iStart = Spa.Xml.IntAttribute(p_slidesNode, "start");
 if (this.iStart == null)
 {
 this.iStart = 0;
 this.iEnd = p_slidesNode.childNodes.length - 1;
 }
 else
 {
 this.iEnd = Spa.Xml.IntAttribute(p_slidesNode, "end");
 }
 this.iCount = this.iEnd - this.iStart + 1;
 this.strSrc = p_slidesNode.getAttribute("src");
 this.fContainsData = (p_slidesNode.childNodes.length > 0);
}
Spa.SlideData.SlideGroup.prototype.ContainsIndex = function(p_i)
{
 return((p_i >= this.iStart) && (p_i <= this.iEnd));
}
Spa.SlideData.SlideGroup.prototype.LoadXml = function(p_xml)
{
 if (p_xml)
 {
 var iCount = p_xml.childNodes.length;
 for (var i=0; i < this.iCount; i++)
 {
 if (i < iCount)
 {
 var slideData = this.aobjSlides[this.iStart + i];
 slideData.constructor(p_xml.childNodes[i]);
 }
 }
 }
}
Spa.SlideData.Slide = function()
{
 if (arguments.length == 1)
 {
 this.blnIsEmpty = false;
 var xml = arguments[0];
 this.strTitle = Spa.Xml.GetNodeText(xml.selectSingleNode("title"));
 var nodeImages = xml.selectSingleNode("images");
 if (nodeImages.childNodes.length > 0)
 {
 var objFirstImage = nodeImages.childNodes[0];
 this.iWidth = parseInt(objFirstImage.getAttribute("width"));
 this.iHeight = parseInt(objFirstImage.getAttribute("height"));
 this.strSrc = objFirstImage.text;
 }
 }
 else
 {
 this.blnIsEmpty = true;
 }
}
/////////////////////////////////////////////////////////////////////////
// Spa_ImageDisplayControl
/////////////////////////////////////////////////////////////////////////
// Displays an image, or text message, in a rectangular region
function Spa_ImageDisplayControl()
{
 // scales the current image so that it fits within the control
 // while preserving the aspect ratio of the image
 function ScaleImage()
 {
 if (strSrc)
 {
 // calculate the remaining avaialble width and height
 var iAvailableWidth = span.clientWidth;;
 var iAvailableHeight = span.clientHeight;
 // calculate the scale to use for the image. Note, the image
 // will never be stretched beyond it's actual width and height,
 // and thus the scale max is 1
 var flScale = Math.min(
 1,
 Math.min(
 (iAvailableWidth / iFullImageWidth),
 (iAvailableHeight / iFullImageHeight)
 )
 );
 // set the width of the image
 img.width = iScaledImageWidth = Math.round(iFullImageWidth * flScale);
 // set the height of the image
 img.height = iScaledImageHeight = Math.round(iFullImageHeight * flScale);
 // center the image by adjusting it's left and top values
 var iLeft = Math.floor((iAvailableWidth - iScaledImageWidth) / 2);
 var iTop = Math.floor((iAvailableHeight - iScaledImageHeight) / 2);
 img.style.left = iLeft;
 img.style.top = iTop;
 }
 }
 // Set a text message in place of the image
 this.SetMessage = function(p_strMessage)
 {
 objThis.SetImage(null, 0, 0);
 tcell.innerHTML = p_strMessage;
 tbl.style.display = "inline";
 }
 // Use the given image in this control
 // If the image is null, then nothing is displayed
 this.SetImage = function(p_strSrc, p_iWidth, p_iHeight)
 {
 // hide the table which contains the message
 tbl.style.display = "none";
 // set the actual full resolution of the image
 iFullImageWidth = p_iWidth;
 iFullImageHeight = p_iHeight;
 // cancel the events of the img object, if it exists
 function CancelEvents(p_img)
 {
 if (p_img)
 {
 p_img.onload = null;
 p_img.onerror = null;
 }
 }
 // clear the load event
 CancelEvents(imgPending);
 // set the image src (which may be null)
 strSrc = p_strSrc;
 function OnLoad()
 {
 // remove the old image
 spanContainer.removeChild(img);
 // cancel the events (avoid memory leaks)
 CancelEvents(img);
 // check the image back in
 imgPool.CheckIn(img);
 // make the pending image the main image
 img = imgPending;
 // scale the image
 ScaleImage();
 // add the image to the container
 spanContainer.appendChild(img);
 }
 if (p_strSrc)
 {
 imgPending = imgPool.CheckOut();
 imgPending.onload = OnLoad;
 imgPending.onerror = OnLoad;
 imgPending.src = p_strSrc;
 }
 // show the container if there is an image, hide it if there is not.
 spanContainer.style.display = p_strSrc ? "inline" : "none";
 }
 // called whenever the outer span gets resized
 function SpanResized()
 {
 // the additional space is added to the absolutely positioned
 // spanContainer to allow for the border
 var iAdditionalSpace = 2 * (iImageBorder + iShadowBorder);
 spanContainer.style.width = span.clientWidth + iAdditionalSpace;
 spanContainer.style.height = span.clientHeight + iAdditionalSpace;
 // scale the image using these new dimensions
 ScaleImage();
 }
 // create the image object for this display control
 function CreateImage()
 {
 var imgNew = Spa.Dom.CreateElement(null, "img",
 "position", "absolute",
 "border", "solid " + iImageBorder + " #000000",
 "filter", "progid:DXImageTransform.Microsoft.DropShadow(" + strDropShadowProperties + ")"
 );
 return(imgNew);
 }
 // this is the function that will get called after this component has
 // been added to the dom
 this.DomInit = SpanResized;
 var objThis = this;
 // a pool of image objects for this display control
 var imgPool = new Spa.Pool(CreateImage, null);
 // the size of the iamge border
 var iImageBorder = 1;
 // the size of the shadow border
 var iShadowBorder = 4;
 var strShadowColor = "#888888";
 // the main element
 var span = this.element = Spa.Dom.CreateElement(null, "span",
 "position", "relative",
 "width", "100%",
 "height", "100%"
 );
 Spa.Dom.Event.Set(span, "onresize", SpanResized);
 // the absolutely positioned container element
 var spanContainer = this.elementTransition = Spa.Dom.CreateElement(span, "span",
 "position", "absolute",
 "top", 0 - iImageBorder,
 "left", 0 - iImageBorder,
 "display", "none"
 );
 var strDropShadowProperties = "color=" + strShadowColor + ",offX=" + iShadowBorder + ",offY=" + iShadowBorder;
 // the main image object
 var img = imgPool.CheckOut();
 spanContainer.appendChild(img);
 // the image that is currently displaying
 var imgPending = null;
 // setting up the table which will display the text
 var tbl = document.createElement(
 "<table cellpadding=0 cellspacing=0 width=100% height=100% style='display:none'>"
 );
 span.appendChild(tbl);
 var tbody = Spa.Dom.CreateElement(tbl, "tbody");
 var trow = Spa.Dom.CreateElement(tbody, "tr");
 var tcell = document.createElement(
 "<td width=100% height=100% align=center valign=middle>"
 );
 trow.appendChild(tcell);
 // state information concerning the currently shown image
 var strSrc = null;
 var iScaledImageWidth;
 var iScaledImageHeight;
 var iFullImageWidth;
 var iFullImageHeight;
}
/////////////////////////////////////////////////////////////////////////
// Spa_ImageSlideDisplayer
/////////////////////////////////////////////////////////////////////////
function Spa_ImageSlideDisplayer()
{
 // if there is a pending message, this will cancel it
 function CancelDelay()
 {
 window.clearTimeout(idDelay);
 idDelay = null;
 }
 // shows either an image or a text message
 function Set(
 p_strSrc,
 p_strMessage,
 p_iWidth,
 p_iHeight,
 p_objTransitioner,
 p_fncDisplayed
 )
 {
 // stop the displayer if it is currently displaying
 objThis.Stop();
 // this state info is used to know when to fire the callback
 var blnTransitionComplete = !p_objTransitioner;
 var blnMadeDisplayCallback = false;
 // called when the filter's state has changed
 function FilterChangeHandler()
 {
 if (filterFade.status == 0)
 {
 // the filter has finished playing
 fncStop = null;
 nodeTransition.onfilterchange = null;
 blnTransitionComplete = true;
 ImageDisplayed();
 }
 }
 // Determines whether the image has finished loading
 // and playing, and fires the callback
 function ImageDisplayed()
 {
 if ((!blnMadeDisplayCallback) && blnTransitionComplete)
 {
 // ready to make callback
 // make sure we only make this once
 blnMadeDisplayCallback = true;
 Spa.Invoker(
 p_fncDisplayed,
 p_strSrc ? p_strSrc : p_strMessage,
 true
 );
 }
 }
 // stops the currently playing filter
 function Stop()
 {
 if (nodeTransition.filters[0])
 {
 nodeTransition.filters[0].Stop();
 }
 }
 var filterFade = nodeTransition.filters[0];
 if (p_objTransitioner)
 {
 // there is a transitioner, so apply the filter
 fncStop = Stop;
 filterFade.enabled = true;
 // hook up to the filter change event so we know when its done
 Spa.Dom.Event.Set(nodeTransition, "onfilterchange", FilterChangeHandler);
 filterFade.Apply();
 }
 else
 {
 // no transition, so disable the filter
 fncStop = null;
 filterFade.enabled = false;
 }
 if (p_strSrc)
 {
 // there's an image to display, so display it
 objSlideDisplay.SetImage(p_strSrc, p_iWidth, p_iHeight);
 }
 else
 {
 // there's text to display, so display it
 objSlideDisplay.SetMessage(p_strMessage);
 }
 ImageDisplayed();
 if (p_objTransitioner)
 {
 // if there's a transitioner, then play the filter
 filterFade.Play();
 }
 }
 // Stops the playing of the current filter and pendind text display
 this.Stop = function()
 {
 // cancel any pending text display
 CancelDelay();
 if (fncStop)
 {
 // if there is a stop function currently set, then call it
 fncStop();
 }
 }
 // Display the message
 this.DisplayMessage = function(
 p_strMessage,
 p_objTransitioner,
 p_iDelay,
 p_fncDisplayCallback
 )
 {
 // called when the mmessage is done displaying
 function Displayed(p_strMessage, p_blnSuccess)
 {
 Spa.Invoker(p_fncDisplayCallback, p_strMessage, p_blnSuccess);
 }
 // sets the message
 function SetMessage()
 {
 Set(null, p_strMessage, 0, 0, p_objTransitioner, Displayed);
 }
 // cancel any pending message to be displayed
 CancelDelay();
 // call SetMessage with the given delay
 idDelay = Spa.SetTimeout(SetMessage, p_iDelay);
 }
 // Display the slide
 this.DisplaySlide = function(
 p_objImageSlide,
 p_objTransitioner,
 p_fncDisplayCallback
 )
 {
 // called when the image is done displaying
 function Displayed(p_strSrc, p_blnSuccess)
 {
 Spa.Invoker(p_fncDisplayCallback, p_objImageSlide, p_blnSuccess);
 }
 // show the image
 Set(
 p_objImageSlide.strSrc,
 null,
 p_objImageSlide.iWidth,
 p_objImageSlide.iHeight,
 p_objTransitioner,
 Displayed
 );
 }
 var objThis = this;
 // the function to stop the current transition.
 // if it is null, then there is nothing to stop
 var fncStop = null;
 // the timerID for the current delayed message
 var idDelay = null;
 // The slide display control
 var objSlideDisplay = new Spa_ImageDisplayControl();
 // the node that will be transitioning
 var nodeTransition = objSlideDisplay.elementTransition;
 // setup the filter for this node
 nodeTransition.style.filter =
 "progid:DXImageTransform.Microsoft.Fade(duration=1,overlap=1,enabled=false)";
 // make the is the main element
 this.element = objSlideDisplay.element;
 // delegate the DomInit to objSlideDisplay
 this.DomInit = objSlideDisplay.DomInit;
}
/////////////////////////////////////////////////////////////////////////
// Spa_ImageSlideLoader
/////////////////////////////////////////////////////////////////////////
function Spa_ImageSlideLoader()
{
 function LoadInfo()
 {
 this.iLoadState = 0;
 this.eventOnLoadStateChange = new Spa.Event();
 this.iUseCount = 0;
 }
 function GetLoadInfo(p_objSlide)
 {
 var info = p_objSlide.__info;
 if (!info)
 {
 p_objSlide.__info = new LoadInfo();
 }
 return(p_objSlide.__info);
 }
 this.AttachLoadStateChange = function(p_objSlide, p_fncCallback)
 {
 var objLoadInfo = GetLoadInfo(p_objSlide);
 var id = objLoadInfo.eventOnLoadStateChange.Attach(p_fncCallback);
 return(id);
 }
 this.DetachLoadStateChange = function(p_objSlide, p_id)
 {
 var objLoadInfo = GetLoadInfo(p_objSlide);
 objLoadInfo.eventOnLoadStateChange.Detach(p_id);
 }
 this.GetLoadState = function(p_objSlide)
 {
 var objLoadInfo = GetLoadInfo(p_objSlide);
 return(objLoadInfo.iLoadState);
 }
 this.Load = function(p_objSlide)
 {
 function LoadHandler(p_blnSuccess)
 {
 objLoadInfo.iLoadState = p_blnSuccess ? 3 : 2;
 objLoadInfo.eventOnLoadStateChange.Fire(p_objSlide);
 }
 var objLoadInfo = GetLoadInfo(p_objSlide);
 switch (objLoadInfo.iLoadState)
 {
 case 0:
 objLoadInfo.iLoadState = 1;
 objLoadInfo.eventOnLoadStateChange.Fire(p_objSlide);
 Spa.Dom.Image.Cache(p_objSlide.strSrc, LoadHandler, 3);
 break;
 case 1:
 break;
 case 2:
 LoadHandler(false);
 break;
 case 3:
 LoadHandler(true);
 break;
 }
 }
}
/////////////////////////////////////////////////////////////////////////
// Spa_SlideShowIndexCalculator
/////////////////////////////////////////////////////////////////////////
function Spa_SlideShowIndexCalculator(p_blnLoop)
{
 this.IndexChangeEnd = function(p_iIndex)
 {
 blnIsChanging = false;
 iPreviousIndex = -1;
 iCurrentIndex = p_iIndex;
 }
 this.IndexChangeBegin = function(p_iIndex)
 {
 blnIsChanging = true;
 iPreviousIndex = iCurrentIndex;
 iCurrentIndex = p_iIndex;
 this.eventOnIndexChange.Fire(iCurrentIndex);
 }
 this.IndexChangeCancel = function()
 {
 if (blnIsChanging)
 {
 blnIsChanging = false;
 iCurrentIndex = iPreviousIndex;
 iPreviousIndex = -1;
 }
 }
 this.GetShiftedIndex = function(p_iShiftAmount)
 {
 if (p_iShiftAmount == 0)
 {
 // if the p_iShiftAmount is 0, then don't shift the current index
 // by anything
 return(iCurrentIndex);
 }
 else if (blnIsChanging)
 {
 // special cases if the index is currently changing
 if ((iPreviousIndex == -1) && (p_iShiftAmount < 0))
 {
 // it's changing from -1, and the shift amount is negative
 // do not circle past -1
 return(-1);
 }
 else if ((iCurrentIndex == -1) && (p_iShiftAmount > 0))
 {
 // it's changing to -1, and the shift amount is positive
 // do not circle past -1
 return(-1);
 }
 }
 var iShiftedIndex = objThis.NormalizeIndex(CalculateIndex(p_iShiftAmount));
 return(iShiftedIndex);
 }
 function CalculateIndex(p_iShiftAmount)
 {
 var iRelativeCurrentIndex = iCurrentIndex;
 if ((iRelativeCurrentIndex == -1) && (p_iShiftAmount <= 0))
 {
 iRelativeCurrentIndex = 0;
 }
 if (blnIsChanging)
 {
 if (p_iShiftAmount <= 0)
 {
 return(iPreviousIndex + p_iShiftAmount + 1);
 }
 else
 {
 return(iRelativeCurrentIndex + p_iShiftAmount - 1);
 }
 }
 else
 {
 return(iRelativeCurrentIndex + p_iShiftAmount);
 }
 }
 this.NormalizeIndex = function(p_iIndex)
 {
 var iNormalizedIndex = p_iIndex;
 if (objThis.blnLoop)
 {
 // simplify the number
 iNormalizedIndex = p_iIndex % objThis.iSlideshowLength;
 if (iNormalizedIndex < 0)
 {
 // if it is negative, start from the end and go to the left
 iNormalizedIndex += objThis.iSlideshowLength;
 }
 }
 else if ((p_iIndex < 0) || (p_iIndex >= objThis.iSlideshowLength))
 {
 // not loop and out of range is an automatic -1
 iNormalizedIndex = -1;
 }
 return(iNormalizedIndex);
 }
 this.eventOnIndexChange = new Spa.Event();
 var iCurrentIndex = -1;
 var iPreviousIndex = -1;
 var blnIsChanging = false;
 this.iSlideshowLength = 0;
 this.blnLoop = p_blnLoop;
 var objThis = this;
}
/////////////////////////////////////////////////////////////////////////
// Spa_SlidePreLoader
/////////////////////////////////////////////////////////////////////////
function Spa_SlidePreLoader(
 p_aobjSlides,
 p_objSlideLoader,
 objSlideShowIndexCalculator
 )
{
 // get the next slide that needs loading
 function GetNextNotLoadedSlide(p_iCurrentIndex)
 {
 if (p_aobjSlides.length == 0)
 {
 // no slides to load
 return(-1);
 }
 if ((p_iCurrentIndex < 0) || (p_iCurrentIndex >= p_aobjSlides.length))
 {
 // out of range, so just start from the beginning
 p_iCurrentIndex = 0;
 }
 var iIndex = p_iCurrentIndex;
 var iPasses = 0;
 while (p_objSlideLoader.GetLoadState(p_aobjSlides[iIndex]) != 0)
 {
 iPasses++;
 iIndex = (iIndex + 1) % p_aobjSlides.length;
 if ((iIndex == p_iCurrentIndex) || (iPasses > iMaxLoadAhead))
 {
 // we went all the way around, and none of them need loading
 // or we looked up to iMaxLoadAhead forward
 return(-1);
 }
 }
 return(iIndex);
 }
 function LoadDone()
 {
 function Next()
 {
 // load the next slide
 IndexChangeHandler(iCurrentIndex);
 }
 // call after 1 ms to avoid stack overflow
 window.setTimeout(Next, 1);
 }
 function IndexChangeHandler(p_iIndex)
 {
 // save the current index of the slideshow
 iCurrentIndex = p_iIndex;
 // get the index of the next slide to load
 var iNextIndex = GetNextNotLoadedSlide(p_iIndex);
 if (iNextIndex != -1)
 {
 // load the slide if there is a slide to load
 objSingleSlideLoader.Load(p_aobjSlides[iNextIndex], null, LoadDone, LoadDone);
 }
 }
 // attach to the index change event
 objSlideShowIndexCalculator.eventOnIndexChange.Attach(IndexChangeHandler);
 // the max number of slides to load ahead
 var iMaxLoadAhead = 2;
 // the slide loader
 var objSingleSlideLoader = new Spa_SingleSlideLoader(p_objSlideLoader);
 // the current index of the slideshow
 var iCurrentIndex = -1;
 // kick off the preloader at the beginning
 IndexChangeHandler(0);
}
function Spa_SingleSlideLoader(p_objSlideLoader)
{
 var objThis = this;
 var objCurrentlyLoadingSlide = null;
 var idEvent = null;
 function LoadingDone()
 {
 // unhook from events
 if (objCurrentlyLoadingSlide)
 {
 p_objSlideLoader.DetachLoadStateChange(
 objCurrentlyLoadingSlide,
 idEvent
 );
 objCurrentlyLoadingSlide = null;
 idEvent = null;
 }
 }
 this.Load = function(
 p_objSlide,
 p_fncOnLoading,
 p_fncOnLoadError,
 p_fncOnLoadSuccess
 )
 {
 if (objCurrentlyLoadingSlide)
 {
 LoadingDone();
 }
 function CheckLoadState()
 {
 var iLoadState = p_objSlideLoader.GetLoadState(p_objSlide);
 switch (iLoadState)
 {
 case 0 :
 // not loaded
 break;
 case 1 :
 // loading
 Spa.Invoker(p_fncOnLoading);
 break;
 case 2 :
 // error
 LoadingDone();
 Spa.Invoker(p_fncOnLoadError);
 return(true);
 case 3 :
 // success
 LoadingDone();
 Spa.Invoker(p_fncOnLoadSuccess);
 return(true);
 }
 return(false);
 }
 function LoadSlide()
 {
 objCurrentlyLoadingSlide = p_objSlide;
 if (!p_objSlide)
 {
 // there is no slide, so return success (we successfully loaded nothing)
 Spa.Invoker(p_fncOnLoadSuccess);
 return;
 }
 // increment use count
 idEvent = p_objSlideLoader.AttachLoadStateChange(
 p_objSlide,
 CheckLoadState
 );
 if (!CheckLoadState())
 {
 p_objSlideLoader.Load(p_objSlide);
 }
 }
 LoadSlide();
 }
}
function Spa_SingleSlideDisplayer(p_objSlideDisplayer, p_objSlideLoader)
{
 var objCurrentlyDisplayingSlide = null;
 var iCounter = 0;
 this.Display = function(
 p_objSlide,
 p_objTransitioner,
 p_fncOnDisplaying,
 p_fncOnDisplayError,
 p_fncOnDisplaySuccess
 )
 {
 iCounter++;
 var iLocalCounter = iCounter;
 if (objCurrentlyDisplayingSlide)
 {
 // cancel displaying of objCurrentlyDisplayingSlide
 p_objSlideDisplayer.Stop();
 }
 function DisplayDone(p_objSlideOrMessage, p_blnSuccess)
 {
 if (iLocalCounter == iCounter)
 {
 Spa.Invoker(
 p_blnSuccess ? p_fncOnDisplaySuccess : p_fncOnDisplayError
 );
 }
 }
 // Display slide
 // increment use count
 objCurrentlyDisplayingSlide = p_objSlide;
 if (p_objSlide)
 {
 Spa.Invoker(p_fncOnDisplaying);
 p_objSlideDisplayer.DisplaySlide(
 p_objSlide,
 p_objTransitioner,
 DisplayDone
 );
 }
 else
 {
 Spa.Invoker(p_fncOnDisplaying);
 p_objSlideDisplayer.DisplayMessage(
 "",
 p_objTransitioner,
 0,
 DisplayDone
 );
 }
 }
}
function Spa_SlideshowPlayer(
 p_objSlideShowIndexCalculator,
 p_aobjSlides,
 p_strLoadingText,
 p_strErrorText
)
{
 var objSlideshowDisplayer = new Spa_ImageSlideDisplayer();
 var objSlideLoader = new Spa_ImageSlideLoader();
 var objSingleSlideLoader = new Spa_SingleSlideLoader(objSlideLoader);
 var objSingleSlideDisplayer =
 new Spa_SingleSlideDisplayer(objSlideshowDisplayer, objSlideLoader);
 var objSlidePreLoader = new Spa_SlidePreLoader(
 p_aobjSlides,
 objSlideLoader,
 p_objSlideShowIndexCalculator
 );
 this.element = objSlideshowDisplayer.element;
 this.DomInit = objSlideshowDisplayer.DomInit;
 this.Clear = function(p_objTransitioner, p_strMessage)
 {
 function CompleteHandler()
 {
 ShowMessage(p_strMessage, p_objTransitioner, 0);
 }
 this.Advance(-1, false, true, false, null, CompleteHandler);
 }
 function ShowMessage(p_strMessage, p_objTransitioner, p_iDelay, p_fncDisplayCallback)
 {
 objSlideshowDisplayer.DisplayMessage(
 p_strMessage,
 p_objTransitioner,
 p_iDelay,
 p_fncDisplayCallback
 );
 }
 this.Advance = function(
 p_iIndex,
 p_blnIsRelative,
 p_blnAdvanceImmediately,
 p_blnSkipIfError,
 p_objTransitioner,
 p_fncOnComplete
 )
 {
 var iNextIndex = p_iIndex;
 var iErrorIncrement = p_blnSkipIfError ? 1 : 0;
 if (p_blnIsRelative)
 {
 iNextIndex = p_objSlideShowIndexCalculator.GetShiftedIndex(p_iIndex);
 iErrorIncrement = (p_blnSkipIfError ? (p_iIndex < 0 ? -1 : 1) : 0);
 }
 function LoadingHandler()
 {
 // display loading message using a delay
 if (p_blnAdvanceImmediately)
 {
 ShowMessage(p_strLoadingText, p_objTransitioner, 1000);
 }
 }
 function ErrorHandler()
 {
 // the slide we wanted to display failed
 if (iErrorIncrement == 0)
 {
 if (p_blnAdvanceImmediately)
 {
 // show the error message
 ShowMessage(p_strErrorText, p_objTransitioner);
 }
 Spa.Invoker(p_fncOnComplete, false);
 }
 else
 {
 if (!p_blnAdvanceImmediately)
 {
 // we were not suppose to advance at all, so
 // cancel the index changing
 p_objSlideShowIndexCalculator.IndexChangeCancel();
 }
 // adjust the next index by the error increment
 // and go again
 iNextIndex =
 p_objSlideShowIndexCalculator.NormalizeIndex(iNextIndex + iErrorIncrement);
 window.setTimeout(Go, 1);
 }
 }
 function DisplayingHandler()
 {
 if (!p_blnAdvanceImmediately)
 {
 // we did not advance immediately, so we're starting to
 // advance now. We will complete the advancement when
 // the display is done.
 p_objSlideShowIndexCalculator.IndexChangeBegin(iNextIndex);
 }
 }
 function DisplaySuccessHandler()
 {
 if (!p_blnAdvanceImmediately)
 {
 // we displayed this slide successfully
 p_objSlideShowIndexCalculator.IndexChangeEnd(iNextIndex);
 }
 // make p_fncOnComplete callback
 Spa.Invoker(p_fncOnComplete);
 }
 function Go()
 {
 var objSlide = p_aobjSlides[iNextIndex];
 if (p_blnAdvanceImmediately)
 {
 // advance immediately to this index
 p_objSlideShowIndexCalculator.IndexChangeEnd(iNextIndex);
 }
 function LoadSuccessHandler()
 {
 // display the slide
 objSingleSlideDisplayer.Display(
 objSlide,
 p_objTransitioner,
 DisplayingHandler,
 ErrorHandler,
 DisplaySuccessHandler
 );
 }
 objSingleSlideLoader.Load(
 objSlide,
 LoadingHandler,
 ErrorHandler,
 LoadSuccessHandler
 );
 }
 Go();
 }
}
function Spa_SlideShowController(
 p_blnLoop,
 p_iDuration,
 p_strLoadingText,
 p_strErrorText
 )
{
 p_iDuration = 5000;
 var objThis = this;
 var blnPlaying = false;
 var iDuration = p_iDuration;
 var idPlayTimer = null;
 var objSlideShowIndexCalculator = new Spa_SlideShowIndexCalculator(p_blnLoop);
 var aobjSlides = new Array();
 var objSlideshowPlayer = new Spa_SlideshowPlayer(
 objSlideShowIndexCalculator,
 aobjSlides,
 p_strLoadingText,
 p_strErrorText
 );
 var objFadeTransitioner = true;
 var objCutTransitioner = false;
 this.element = objSlideshowPlayer.element;
 this.DomInit = objSlideshowPlayer.DomInit;
 this.ClearSlides = function(p_strMessage)
 {
 for (var i=aobjSlides.length-1; i >= 0; i--)
 {
 delete(aobjSlides[i]);
 aobjSlides.shift();
 }
 objSlideshowPlayer.Clear(objCutTransitioner, p_strMessage ? p_strMessage : "");
 objSlideShowIndexCalculator.iSlideshowLength = 0;
 }
 this.AddSlide = function(p_objSlide)
 {
 aobjSlides[aobjSlides.length] = p_objSlide;
 objSlideShowIndexCalculator.iSlideshowLength = aobjSlides.length;
 }
 this.GotoIndex = function(p_iIndex)
 {
 objSlideshowPlayer.Advance(
 p_iIndex,
 false,
 true,
 false,
 objCutTransitioner,
 AutoAdvance
 );
 }
 this.AdvanceBy = function(p_iAmount)
 {
 objSlideshowPlayer.Advance(
 p_iAmount,
 true,
 true,
 true,
 objCutTransitioner,
 AutoAdvance
 );
 }
 function AutoAdvance(p_blnImmediate)
 {
 function Advance()
 {
 objSlideshowPlayer.Advance(
 1,
 true,
 false,
 true,
 objFadeTransitioner,
 AutoAdvance
 );
 AutoAdvance.iCount++;
 if (objSlideshowPlayer.element.clientWidth < 200)
 {
 if (AutoAdvance.iCount == 10)
 {
 iDuration = 20000;
 }
 }
 }
 if (blnPlaying)
 {
 CancelAutoAdvance();
 if (p_blnImmediate)
 {
 Advance();
 }
 else
 {
 // if we're in play mode, set the timer to advance
 idPlayTimer = window.setTimeout(Advance, iDuration);
 }
 }
 }
 AutoAdvance.iCount = 0;
 // cancel the pending auto advance
 function CancelAutoAdvance()
 {
 window.clearTimeout(idPlayTimer);
 idPlayTimer = null;
 }
 this.Play = function()
 {
 // start play mode
 blnPlaying = true;
 AutoAdvance(true);
 }
 this.Pause = function()
 {
 // cancel pending advance
 CancelAutoAdvance();
 // stop play mode
 blnPlaying = false;
 }
 this.Stop = function()
 {
 // cancel pending advance
 CancelAutoAdvance();
 // stop play mode
 blnPlaying = false;
 // go to 0
 objThis.GotoIndex(0);
 }
 this.Previous = function()
 {
 // cancel pending advance
 CancelAutoAdvance();
 // advance by -1 without waiting
 objThis.AdvanceBy(-1);
 }
 this.Next = function()
 {
 // cancel pending advance
 CancelAutoAdvance();
 // advance by +1 without waiting
 objThis.AdvanceBy(1);
 }
}
function SPA_SlideshowPlayerUserInterface(
 p_nodeParent,
 p_blnStartPlaying,
 p_blnDoLoop,
 p_iSpeed,
 p_imgPlay,
 p_imgPause,
 p_imgStop,
 p_imgPrevious,
 p_imgNext,
 p_strLoadingText,
 p_strErrorText,
 p_fncOnReady
 )
{
 // create and add the slideshow
 var objImageShow = new Spa_SlideShowController(
 p_blnDoLoop,
 p_iSpeed,
 p_strLoadingText,
 p_strErrorText
 );
 p_nodeParent.appendChild(objImageShow.element);
 var strPlay = p_imgPlay.title;
 var strPause = p_imgPause.title;
 var fPlaying = false;
 function Play()
 {
 fPlaying = !fPlaying;
 if (fPlaying)
 {
 objImageShow.Play();
 btnPlay.element.src = strPauseSrc;
 btnPlay.element.title = strPause;
 }
 else
 {
 objImageShow.Pause();
 btnPlay.element.src = strPlaySrc;
 btnPlay.element.title = strPlay;
 }
 }
 function Stop()
 {
 objImageShow.Stop();
 fPlaying = false;
 btnPlay.element.src = strPlaySrc;
 btnPlay.element.title = strPlay;
 }
 function Previous()
 {
 objImageShow.Previous();
 }
 function Next()
 {
 objImageShow.Next();
 }
 var strPlaySrc = p_imgPlay.src;
 var strPauseSrc = p_imgPause.src;
 var btnPlay = new Spa_ElementButton(p_imgPlay, Play, true, false);
 var btnStop = new Spa_ElementButton(p_imgStop, Stop, true, false);
 var btnPrevious = new Spa_ElementButton(p_imgPrevious, Previous, true, false);
 var btnNext = new Spa_ElementButton(p_imgNext, Next, true, false);
 function SetButtonEnabled(p_blnEnabled)
 {
 btnPlay.SetEnabled(p_blnEnabled);
 btnStop.SetEnabled(p_blnEnabled);
 btnPrevious.SetEnabled(p_blnEnabled);
 btnNext.SetEnabled(p_blnEnabled);
 }
 function LayoutCompleteHandler()
 {
 objImageShow.DomInit();
 Spa.Invoker(p_fncOnReady);
 }
 var objThis = this;
 var htXmlData = new Array();
 Spa.Dom.ExecuteWhenLayoutComplete(p_nodeParent, LayoutCompleteHandler);
 this.Clear = function(p_strText)
 {
 Stop();
 objImageShow.ClearSlides(p_strText)
 }
 function RunWithSlideData(p_objData)
 {
 // add the data to the objImageShow
 for (var i=0; i < p_objData.slides.length; i++)
 {
 objImageShow.AddSlide(p_objData.slides[i]);
 }
 SetButtonEnabled(p_objData.slides.length > 0);
 if (p_blnStartPlaying)
 {
 Play();
 }
 }
 this.RunWithXmlDocument = function(p_xmlDoc)
 {
 // parse the data
 var objSlideData = new Spa.SlideData(p_xmlDoc.documentElement);
 // clear the slides in the ImageShow
 this.Clear("");
 RunWithSlideData(objSlideData);
 }
 var iCount = 0;
 this.RunWithXmlSource = function(p_strSrc)
 {
 var iLocalCount = ++iCount;
 SetButtonEnabled(false);
 function XmlDoneLoading(p_xmlDoc)
 {
 if (iLocalCount == iCount)
 {
 if (p_xmlDoc)
 {
 // parse the data
 var objSlideData = new Spa.SlideData(p_xmlDoc.documentElement);
 htXmlData[p_strSrc] = objSlideData;
 RunWithSlideData(objSlideData);
 }
 else
 {
 objThis.Clear(p_strErrorText)
 }
 }
 }
 if (htXmlData[p_strSrc])
 {
 this.Clear("");
 RunWithSlideData(htXmlData[p_strSrc]);
 }
 else
 {
 this.Clear(p_strLoadingText);
 Spa.Xml.LoadUrl(p_strSrc, XmlDoneLoading);
 }
 }
}
//SPA_SlideshowViewerScriptLoaded();
window.defaultStatus = " ";
