/*------------------------------------------------------------------------------

   Copyright (c) 2005 Tyrell Corporation.

    This file is part of the Locative Blog project.
    http://locblog.sourceforge.net/

   Author   : $Author: darkeye $
   Version  : $Revision: 1.1 $
   Location : $Source: /cvsroot/locblog/website/var/htdocs/rotatingGlobe/sample/RotatingGlobe.js,v $

   Abstract : 

    A rotating globe JavaScript object.

   Copyright notice:

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License  
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.
   
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of 
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
    GNU General Public License for more details.
   
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

------------------------------------------------------------------------------*/

/**
 *  Event handler for a preloaded image finishing loading.
 *  This function expects the rotating globe, for which preloaded images
 *  are checked, being available at the global variable named 'globe'.
 *
 *  @return true if all images are loaded, false otherwise.
 */
function globeImageLoaded()
{
    if (globe.imagesLoaded()) {
        globe.init();
        return true;
    }

    return false;
}

/**
 *  Start dragging the image by the mouse move.
 *  This function expects the rotating globe at a global variable named 'globe'
 */
function globeDragImage()
{
    globe.startDragImage();
    document.globe.onmousemove = globeTrackMouseMove;
}

/**
 *  Stop dragging the image by the mouse move.
 *  This function expects the rotating globe at a global variable named 'globe'
 */
function globeUndragImage()
{
    document.globe.onmousemove = null;
    globe.stopDragImage();
}

/**
 *  Track the mouse movement, and change the globe view if necessary.
 *  This function expects the rotating globe at a global variable named 'globe'
 *
 *  @param event the mouse movement event.
 */
function globeTrackMouseMove(event)
{
    // first tell the current mouse position, possibly in all browsers
    // thanks to http://www.quirksmode.org/js/events_properties.html
    var posx = 0;
    var posy = 0;
    if (!event) {
        var event = window.event;
    }
    if (event.pageX || event.pageY) {
        posx = event.pageX;
        posy = event.pageY;
    } else if (event.clientX || event.clientY) {
        posx = event.clientX + document.body.scrollLeft;
        posy = event.clientY + document.body.scrollTop;
    }

    // display the detected coordinates, for debug reasons
    document.test.x.value = posx;
    document.test.y.value = posy;

    globe.updateView(posx, posy);
}




/**
 *  The contructor class depicting the rotating globe.
 *
 *  @param globeImage the globe <img/> tag in the page,
 *         that holds the globe images.
 *  @param globeBorder the number of pixels the image has a border around
 *         the enclosed globe. it is expected that the image has a globe at
 *         its center, which is surrounded by a border on each side with
 *         this many pixels. thus the actual globe is
 *         globeImage.width - (2 * globeBorder) wide, and
 *         globeImage.height - (2 * globeBorder) in height.
 *  @param locations an array holding GeoLocation object, the list of
 *         interesting locations.
 */
function RotatingGlobe(globeImage, globeBorder, locations)
{
    // the <img/> element in the page, holding the globe images
    this.globeImage = globeImage;

    // the size of the border around the globe in the image
    this.globeBorder = globeBorder;

    // the interesting locations on the globe
    this.locations = locations;

    // the X coordinate of the upper-left corner of the globe image
    // in the browser window
    this.globeImageX = findPosX(document.globe);

    // the Y coordinate of the upper-left corner of the globe image
    // in the browser window
    this.globeImageY = findPosY(document.globe);

    // the real height of the globe itself, inside the image
    this.realGlobeHeight = this.globeImage.width - (2 * this.globeBorder);

    // the real width of the globe itself, inside the image
    this.realGlobeWidth  = this.globeImage.height - (2 * this.globeBorder);

    // the number of view positions available
    this.maxPosition = 36;

    // the current view position
    this.position = 0;

    // the prefix for each globe image
    // the image names are: imagePrefix + position + imagePostfix
    this.imagePrefix = "globeImages/globe";

    // the postfix for each globe image
    // the image names are: imagePrefix + position + imagePostfix
    this.imagePostfix = ".jpg";

    // the globe images, as an array
    this.images = Array(this.maxPosition);

    // the x coordinate, which if passed by the mouse, triggers a move to east
    this.eastThreshold = 0;

    // the x coordinate, which if passed by the mouse, triggers a move to west
    this.westThreshold = 0;

    // the size of the x coordinate window to trigger a move to east or west
    // by the mouse movement
    this.thresholdStep = 20;

    // a flag indicating if this object has already been initialized
    this.initialized = false;

    // preload all the images for the globe
    this.preloadImages = rotatingGlobePreloadImages;

    // tell if all images that are to be preloaded have completed loading.
    this.imagesLoaded = rotatingGlobeImagesLoaded;

    // initialize the globe, after all the images have been loaded
    this.init = rotatingGlobeInit;

    // start mouse dragging
    this.startDragImage = rotatingGlobeStartDragImage;

    // stop mouse dragging
    this.stopDragImage = rotatingGlobeStopDragImage;

    // update the view point according to the new mouse coordinates, if needed
    this.updateView = rotatingGlobeUpdateView;

    // function to return the name of an image for a specified position
    this.imageName = rotatingGlobeImageName;

    // rotate the viewpoint one step to the east
    this.rotateEast = rotatingGlobeRotateEast;

    // rotate the viewpoint one step to the west
    this.rotateWest = rotatingGlobeRotateWest;

    // reset the mouse movement thresholds triggering view point change
    this.resetThreshold = rotatingGlobeResetThreshold;

    // display the locations
    this.displayLocations = rotatingGlobeDisplayLocations;
}

/**
 *  Function returning the name of an image for a specified position
 *
 *  @param position the view position to get the image for, must be
 *         in the interval [0:maxPosition[
 *  @return the name of the appropariate globe image, as a string
 */
function rotatingGlobeImageName(position)
{
    return this.imagePrefix + position + this.imagePostfix;
}

/**
 *  Rotate the current viewpoint one step to the east.
 */
function rotatingGlobeRotateEast()
{
    this.position = (++this.position) % this.maxPosition;

    this.globeImage.src = this.images[this.position].src;
    this.displayLocations();
}

/**
 *  Rotate the current viewpoint one step to the east.
 */
function rotatingGlobeRotateWest()
{
    this.position = (--this.position) % this.maxPosition;
    if (this.position < 0) {
        this.position += this.maxPosition;
    }

    this.globeImage.src = this.images[this.position].src;
    this.displayLocations();
}

/**
 *  Preload all the images, so that they are in the browsers cache.
 */
function rotatingGlobePreloadImages()
{
    this.initialized = false;

    for (i = 0; i < this.maxPosition; ++i) {
        this.images[i] = new Image(this.globeImage.width,
                                   this.globeImage.height);
        this.images[i].onload = globeImageLoaded;
        this.images[i].src    = this.imageName(i);
    }
}

/**
 *  Tell if all images that are to be preloaded have completed loading.
 *
 *  @return true if all images have been loaded, false otherwise
 */
function rotatingGlobeImagesLoaded()
{
    var   allCompleted = true;

    for (i = 0; i < this.maxPosition; ++i) {
        if (!this.images[i].complete) {
            allCompleted = false;
            break;
        }
    }

    return allCompleted;
}

/**
 *  Initialize the globe, after all the images have been loaded.
 *  This means grabbing event handlers, etc.
 */
function rotatingGlobeInit()
{
    if (this.initialized) {
        return;
    }

    this.thresholdStep        = this.globeImage.width / (this.maxPosition + 1);
    this.globeImage.onmousedown = globeDragImage;
    this.globeImage.onmouseup   = globeUndragImage;
    this.globeImage.src         = this.images[0].src;
    this.initialized            = true;

    for (i = 0; i < locations.length; ++i) {
        var obj = new getObj(locations[i].id);
        obj.obj.onmouseover = locationShowPopup;
        obj.obj.onmouseout  = locationHidePopup;
    }

    this.displayLocations();
}

/**
 *  Find a location in an array of locations, by its id.
 *
 *  @param locations an array of GeoLocation objects.
 *  @param id the id of the location to look for
 *  @return the requested GeoLocation object, or null if not found.
 */
function getLocationById(locations, id) {
    for (i = 0; i < locations.length; ++i) {
        if (locations[i].id == id) {
            return locations[i];
        }
    }

    return null;
}

/**
 *  Event hanlder for showing the popup near a location.
 *
 *  @param event the event triggering this call. it is expected that the event
 *         target name is a valid GeoLocation id in the locations array.
 */
function locationShowPopup(event) {
    geoLocation = getLocationById(locations, event.target.name);
    if (geoLocation != null ) {
        geoLocation.showPopup();
    }
}

/**
 *  Event hanlder for hiding the popup near a location.
 *
 *  @param event the event triggering this call. it is expected that the event
 *         target name is a valid GeoLocation id in the locations array.
 */
function locationHidePopup(event) {
    geoLocation = getLocationById(locations, event.target.name);
    if (geoLocation != null ) {
        geoLocation.hidePopup();
    }
}

/**
 *  Reset the mouse movement thresholds. Set the new limits for when
 *  to change the view point, if the mouse move beyonds these limints.
 *
 *  @param posx the current position, from where the relative thresholds
 *         have to be set up.
 */
function rotatingGlobeResetThreshold(posx)
{
    this.eastThreshold = posx - this.thresholdStep;
    this.westThreshold = posx + this.thresholdStep;
}

/**
 *  Start mouse dragging
 */
function rotatingGlobeStartDragImage()
{
    this.eastThreshold = 0;
    this.westThreshold = 0;
}

/**
 *  Stop mouse dragging.
 */
function rotatingGlobeStopDragImage()
{
    this.eastThreshold = 0;
    this.westThreshold = 0;
}

/**
 *  Update the view point according to the new mouse coordinates, if needed.
 *
 *  @param x the new x coordinate for the mouse
 *  @param y the new y coordinate for the mouse
 */
function rotatingGlobeUpdateView(x, y)
{
    // if this is the first event after started dragging, initialize
    // the appropriate thresholds
    if (this.eastThreshold == 0 && this.westThreshold == 0) {
        this.resetThreshold(x);
    }

    // change the view point, if needed
    if (x > this.westThreshold) {
        this.rotateWest();
        this.resetThreshold(x);
    } else if (x < this.eastThreshold) {
        this.rotateEast();
        this.resetThreshold(x);
    }
}

/**
 *  Display all interesting locations on the map.
 */
function rotatingGlobeDisplayLocations()
{
    for (i = 0; i < locations.length; ++i) {
        this.locations[i].display(this);
    }
}


