﻿function Frame(filmstrip, frameNumber, mainImagePath, thumbImagePath) {
    this.filmstrip = filmstrip;
    var that = this;
    this.id = filmstrip.name + "_" + "frame" + frameNumber;
    this.clicktime = new Date().getTime();
    this.toggletime = null;
    this.trueMainImage = mainImagePath; // this will be preloaded into this.mainImage
    this.mainImage = mainImagePath;    // initally set mainImage to thumbImage so a low res version is available if high res hasn't finished loading
    this.thumbImage = thumbImagePath;
    this.div = createElement("div", { id: "frame" + frameNumber + "_div" }, { position: "absolute", top: "-200px" });

    this.upCtrlDiv = createElement("div",
    {
        id: this.id + "upCtrl",            
        onmouseover:function() {tooltip.show('Click to stop filmstrip');},
        onmouseout:function() {tooltip.hide();},
//        onmouseover: function() { that.filmstrip.projector.rollDown(); },   // rollUp(); }, // up/down version
        onmousedown: function() { that.filmstrip.projector.rollDown(); },   // rollUp(); }, // up/down version
//        onmouseout: function() { if (that.filmstrip.projector.running == false) that.filmstrip.projector.rollStop(); },
        onclick: function() { that.filmstrip.projector.frameClick(-1); },   // (-1); },     // up/down version
        ondblclick: function() { that.filmstrip.projector.frameClick(-1); }, // (-1); }      // up/down version
        tooltip: "Double-click to slow down"
    },
    {
        position: "absolute",
        left: "1px",
        top: "0px",
        width: "20px",
        height: "148px",
        cursor: "move",
        backgroundImage: "url(images/transparentpixel.gif)"
    });

    this.downCtrlDiv = createElement("div",
    {
        id: this.id + "downCtrl",
//        onmouseover: function() { that.filmstrip.projector.rollDown(); },
        onmousedown: function() { that.filmstrip.projector.rollDown(); },
//        onmouseout: function() { if (that.filmstrip.projector.running == false) that.filmstrip.projector.rollStop(); },
        onclick: function() { that.filmstrip.projector.frameClick(1); },
        ondblclick: function() { that.filmstrip.projector.frameClick(1); }
    },
    {
        position: "absolute",
        left: "120px",
        top: "0px",
        height: "148px",
        width: "20px",
        cursor: "move",
        backgroundImage: "url(images/transparentpixel.gif)"
    });

    this.filmImg = createElement("img",
    {
        id: this.id + "film",
        alt: "",
        src: "images/Film.png",
        onclick: function() { Projector.screen.swap("images/2255_girl_and_calf_1020.jpg") }
    },
    {
        position: "absolute",
        top: "0px",
        left: "0px",
        height: "148px",
        width: "146px"
    });    

    this.ImageImg = createElement("img",
    {
        id: this.id + "Img",
        alt: "",
        src: thumbImagePath
    },
    {
        position: "absolute",
        left: "21px",
        top: "6px",
        height: "135px",
        width: "100px",
        filter: "alpha(opacity=60)",    // IE approach
        opacity: "0.60"                 // other browsers
    });

    this.showImageDiv = createElement("div",
    {
        id: this.id + "showImage",
        onclick: function() { that.showImageClick(mainImagePath); },
        ondblclick: function() { that.showImageClick(mainImagePath); },
        fade: new Fader(that.ImageImg.id),
        onmouseover: function() { that.showImageDiv.fade.makeOpaque(); if (Projector.screen.swapOnmouseover) Projector.screen.swap(that.mainImage); },
        onmouseout: function() { that.showImageDiv.fade.makeTranslucent(); }
    },
    {
        position: "absolute",
        left: "21px",
        top: "6px",
        height: "134px",
        width: "98px",
        cursor: "pointer",
        backgroundImage: "url(images / transparentpixel.gif)"
    });

    disableSelection(this.div);
    disableSelection(this.upCtrlDiv);
    disableSelection(this.downCtrlDiv);
    disableSelection(this.showImageDiv);
    disableSelection(this.filmImg);
    disableSelection(this.ImageImg);

    // assemble pieces inside of frame
    this.div.appendChild(this.upCtrlDiv);
    this.div.appendChild(this.downCtrlDiv);
    this.div.appendChild(this.showImageDiv);
    this.div.appendChild(this.filmImg);
    this.div.appendChild(this.ImageImg);

    this.mouseunder = false;

//    tooltipManager.addTip("slower", this.upCtrlDiv, "Double-click to slow down", 4, 2)
//    tooltipManager.addTip("faster", this.downCtrlDiv, "Double-click to speed up", 4, 2)
//    tooltipManager.addTip("start", this.upCtrlDiv, "click to start scrolling", 4, 2)
//    tooltipManager.addTip("start", this.downCtrlDiv, "click to start scrolling", 4, 2)
//    tooltipManager.addTip("stop", this.upCtrlDiv, "click to stop scrolling", 3, 2)
//    tooltipManager.addTip("stop", this.downCtrlDiv, "click to stop scrolling", 3, 2)
//    tooltipManager.addTip("autoOn", this.showImageDiv, "Double-click to enable automatic image display", 4, 2)
//    tooltipManager.addTip("autoOff", this.showImageDiv, "Double-click to disable automatic image display", 4, 2)    
}

Frame.prototype.setZindex = function(z) {
    with (this) {
        div.style.zIndex = z;
        ImageImg.style.zIndex = z + 1;
        filmImg.style.zIndex = z + 2;
        upCtrlDiv.style.zIndex = z + 3;
        downCtrlDiv.style.zIndex = z + 3;
        showImageDiv.style.zIndex = z + 4;
    }
}

Frame.prototype.move = function(dx, dy) {
    with (this) {
        div.style.left = parseInt(div.style.left) + dx + "px";
        div.style.top = parseInt(div.style.top) + dy + "px";
        checkMouseunderImage();
    }
}

Frame.prototype.setPosition = function(x, y) {
    with (this) {
        div.style.left = x + "px";
        div.style.top = y + "px";
    }
}

// Returns true if another frame is covering this one at the current mouse location
Frame.prototype.isCovered = function() {
    // check through all other filmstrips to see if any is under the mouse
    for (var film in Filmstrip.collection) {
        var f = Filmstrip.collection[film];
        // don't compare to ourselves
        if (f !== this.filmstrip) {
            if (mouseUnder(f.container, true)) {
                // we've found another filmstrip that is under the mouse, now check to see if it's on top of us
                // if a sibling's zIndex equals ours then the younger sibling will be on top
                if (f.zIndex > this.filmstrip.zIndex ||
                    f.zIndex == this.filmstrip.zIndex && isYoungerSibling(this.filmstrip.container, f.container)) {
                    // we've found another filmstrip that is under the mouse and on top of us
                    // now we need to see if any of its frames are at our position
                    for (var i = 0; i < f.frames.length; i++)
                        if (mouseUnder(f.frames[i].filmImg))
                            // another frame is also over our position and is above us
                            return true;
                }
            }
        }
    }
    return false;
}

Frame.prototype.checkMouseunderImage = function() {
    if (mouseUnder(this.ImageImg)) {
        // we're under the mouse
        if (!this.mouseunder) {
            // we just moved under the mouse, so fire the moveunder event
            // but first check to see if there are other frames under the mouse and whether we're the top one
            if (!this.isCovered()) {
                this.mouseunder = true;
                this.showImageDiv.fade.makeOpaque();
                if (Projector.screen.swapOnmouseover)
                    Projector.screen.swap(this.mainImage);
            }
        }
    }
    else {
        // we're not under the mouse
        if (this.mouseunder) {
            // we just were under it, so fire the moveout event
            this.mouseunder = false;
            this.showImageDiv.fade.makeTranslucent();
        }
    }
}

Frame.prototype.showImageClick = function(imagePath) {
    // ignore clicks that are a side effect of the completion of a drag operation
    if (dragClick()) return;
    // look at elapsed time between this click and the previous click to check for double-click events
    var now = new Date().getTime();
    var elapsedTime = now - this.clicktime;
    this.clicktime = now;
    Projector.screen.swap(imagePath);
    // toggletime tracks last time Projector.screen.swapOnmouseover was changed to handle doubleclick events
    // for double clicks IE fires click then dblclick while FF fires click, click, dblclick
    if (elapsedTime < 2000 && now - this.toggletime > 1000) {
        if (Projector.screen.swapOnmouseover) {
            Projector.screen.swapOnmouseover = false;
            this.toggletime = now;
        }
        else {
            Projector.screen.swapOnmouseover = true;
            this.toggletime = now;
        }
    }
}

function Filmstrip(projector, name) {
    this.projector = projector;
    this.name = "Filmstrip" + name;
    // add reference for this Filmstrip object to associative array for reference by event handlers
    // event handlers only get the DOM element but can use its "id" attribute as an index in the Filmstrip.collection object
    // to grab the reference to this instance.
    Filmstrip.collection[this.name] = this; 
    this.zIndex = 0;
    this.frameCount = 0;    // total frames in filmstrip
    this.frames = [];       // frames on display
    this.frameQueue = [];   // double-ended queue of frames off of display
    this.pageHeight = 600; 
    this.frameHeight = 148;
    this.frameOverlap = 6;
    this.frameOffset = this.frameHeight - this.frameOverlap;
    this.loopDirection = 0;
    this.downPoppedFrames = 0;
    this.upPoppedFrames = 0;
    this.glidingAway = false;
    var that = this;

    this.container = createElement("div",
    {
        // id of this element must match id of Filmstrip instance so drag event handler can look up the filmstrip instance using the element's id
        id: this.name,
        onmousedown: drag
    },
    {
        position: "absolute",
        top: "0px",
        width: "148px",
        height: this.pageHeight + "px",
        display: "none",
        overflow: "hidden"
    });
    document.body.appendChild(this.container);
}

Filmstrip.prototype.show = function() {
    this.container.style.display = "block";
}

Filmstrip.prototype.hide = function() {
    this.container.style.display = "none";
}

Filmstrip.prototype.setLeftEdge = function(left) {
    this.container.style.left = left + "px";
}

Filmstrip.prototype.getLeftEdge = function() {
    return parseInt(this.container.style.left);
}

Filmstrip.prototype.slide = function(step) {
    this.setLeftEdge(this.getLeftEdge() + step);
    this.keepInViewport();
}

Filmstrip.prototype.keepInViewport = function() {
    var leftEdge = this.getLeftEdge();
    if (leftEdge < Filmstrip.minLeftEdge)
        this.setLeftEdge(Filmstrip.minLeftEdge);
    else if (leftEdge > Filmstrip.maxLeftEdge())
        this.setLeftEdge(Filmstrip.maxLeftEdge());
}

Filmstrip.prototype.setZindex = function(z) {
    this.zIndex = z;
    this.container.style.zIndex = z;
    for (var i = 0; i < this.frames.length; i++)
        this.frames[i].setZindex(z);
    for (var i = 0; i < this.frameQueue.length; i++)
        this.frameQueue[i].setZindex(z);
}

Filmstrip.prototype.retrieveDownFrame = function() {
    return this.frameQueue.pop();
}

Filmstrip.prototype.storeDownFrame = function(f) {
    this.frameQueue.unshift(f);
    f.div.style.top = 0 - this.frameHeight - 10 + "px";    // move frame above top of window so it's invisible even if window is stretched downward
}

Filmstrip.prototype.retrieveUpFrame = function() {
    return this.frameQueue.shift();
}

Filmstrip.prototype.storeUpFrame = function(f) {
    this.frameQueue.push(f);
}

Filmstrip.prototype.addFrame = function(mainImagePath, thumbImagePath) {
    this.frameCount++;
    var f = new Frame(this, this.frameCount, mainImagePath, thumbImagePath);
    f.setZindex(this.zIndex);
    f.setPosition(0, -this.frameHeight);
    this.frameQueue.push(f);
    this.container.appendChild(f.div);
}

Filmstrip.prototype.stepDown = function(stepsize) {
    for (var i = 0; i < this.frames.length; i++) {
        this.frames[i].move(0, Math.round(stepsize));
    }
    this.stepDownLoop();
}

Filmstrip.prototype.stepUp = function(stepsize) {
    for (var i = 0; i < this.frames.length; i++) {
        this.frames[i].move(0, Math.round(-stepsize));
    }
    this.stepUpLoop();
}

Filmstrip.prototype.stepDownLoop = function() {
    with (this) {
        if (frames.length == 0)
            loopDirection = 1;

        // accelerate filmstrips down off of the page if we're "glidingAway"
        if (glidingAway) projector.stepsize *= 1.05;
        // if filmstrip is gliding down off of the screen we don't want to add any new frames
        if (!glidingAway) {
            // add a new frame if there are none or if we're about to run out
            if (frames.length == 0 || parseInt(frames[0].div.style.top) > -20)
                addNextDownFrame();
        }
        // if there is at least one frame and the bottom frame is below the bottom of the page then pop it out of the queue
        if (frames.length > 0 && parseInt(frames[frames.length - 1].div.style.top) > pageHeight) {
            storeDownFrame(frames.pop());
            if (loopDirection == 1)
                downPoppedFrames++;
        }
        // if the last frame of this filmstrip has been popped away (it moved off of the bottom of the window)
        // then glidingAway is complete so shut down any movement, reset stepsize and turn off the glidingAway flag
        if (frames.length == 0) {
            projector.stop();
            upPoppedFrames = 0;
            downPoppedFrames = 0;
            updateSwitch();
            this.hide();    // hide this container 'div' so scroll bars beneath it are accessible
        }
    }
}

Filmstrip.prototype.stepUpLoop = function() {
    with (this) {
        if (frames.length == 0)
        // returning here instead of setting loopDirection to -1 so filmstrip never starts off heading up
            return;
        // -1 is used to start filmstrip heading up instead of down.  Have decided not to use this to simplify user interface
        // loopDirection = -1;

        if (frames.length == 0 || parseInt(frames[frames.length - 1].div.style.top) + frameHeight < pageHeight)
            addNextUpFrame();
        if (parseInt(frames[0].div.style.top) + frameHeight < 0) {
            storeUpFrame(frames.shift());
            if (loopDirection == -1)
                upPoppedFrames++;
            if (frames.length == 0)
                this.hide();    // hide this container 'div' so scroll bars beneath it are accessible
        }
    }
}

Filmstrip.prototype.addNextDownFrame = function() {
    with (this) {
        if (loopDirection == -1 && upPoppedFrames == 0) return;

        f = retrieveDownFrame();
        if (!f) return;

        if (frames.length == 0)
            var top = 0 - frameOffset;
        else
            top = parseInt(frames[0].div.style.top) - frameOffset;

        f.setPosition(0, top);
        frames.unshift(f);

        if (loopDirection == -1) upPoppedFrames--;

        show();     // make sure this filmstrip is visible
    }
}

Filmstrip.prototype.addNextUpFrame = function() {
    with (this) {
        if (loopDirection == 1 && downPoppedFrames == 0) return;

        f = retrieveUpFrame();
        if (!f) return;

        if (frames.length == 0)
            var top = pageHeight;
        else
            top = parseInt(frames[frames.length - 1].div.style.top) + frameOffset;

        f.setPosition(0, top);
        frames.push(f);

        if (loopDirection == 1) downPoppedFrames--;

        show();     // make sure this filmstrip is visible
    }
}

Filmstrip.prototype.isMousedownOnFrame = function() {
    for (var i = 0; i < this.frames.length; i++)
        if (mouseUnder(this.frames[i].filmImg))
        return true;
    return false;
}

Filmstrip.whoseOnTop = function(f1, f2) {
    if (f1.zIndex > f2.zIndex) return -1;
    if (f1.zIndex < f2.zIndex) return 1;
    if (isYoungerSibling(f1.container, f2.container)) return 1;
    else return -1;
}

Filmstrip.filmstripToDrag = function() {
    var filmArray = [];
    for (var film in Filmstrip.collection)
        filmArray.push(Filmstrip.collection[film]);
    // put filmstrips in order with topmost at beginning of array
    filmArray.sort(Filmstrip.whoseOnTop);
    // check through the filmstrips, starting with the topmost, to see if it has a frame under the mouse
    for (var i = 0; i < filmArray.length; i++)
        if (filmArray[i].isMousedownOnFrame())
        return filmArray[i];
    // return null if no filmstrips have a frame under the mouse
    return null;
}

Filmstrip.width = 148;

Filmstrip.minLeftEdge = 0;

Filmstrip.maxLeftEdge = function() {
    return Geometry.getViewportWidth() - Filmstrip.width;
}

Filmstrip.bringToFront = function(id) {
    var currentZ = Filmstrip.collection[id].zIndex;
    var maxZ = 0;
    for (var film in Filmstrip.collection) {
        // determine highest zIndex
        if (Filmstrip.collection[film].zIndex > maxZ)
            maxZ = Filmstrip.collection[film].zIndex;
        // lower zIndex of all filmstrips currently above or equal to us
        if (Filmstrip.collection[film].zIndex >= currentZ)
            Filmstrip.collection[film].setZindex(Filmstrip.collection[film].zIndex - 1);
    }
    // set our zIndex equal to the previous highest
    Filmstrip.collection[id].setZindex(Math.max(maxZ));
}

Filmstrip.collection = {};


function Projector() {
    this.name = null;
    this.stepinterval = 50;                 // milliseconds between steps
    this.stepsize = 4;                      // pixels per step
    this.lastStepsize = 4;
    this.glidesize = 0;
    this.glidefriction = .05;
    this.glideinterval = this.stepinterval; // glide is for up and down motion
    this.glidetimer = null;
    this.slideDestination = 0;  // position (in pixels) of style.left attribute at end of slide
    this.slideHalfwayPoint = 0; // position (in pixels) of point halfway between slide endpoints (determines point that accleration reverses)
    this.slideHalfwayReached = false;
    this.slidesize = 0;     // negative values slide left, positive values slide right
    this.slidefriction = .1;
    this.slideinterval = this.stepinterval; // slide is for left and right motion
    this.slidetimer = null;
    this.glideAwayTimer = null;
    this.filmstrip = null;
    this.rolltimer = null;
    this.clicktimer = null;
    this.frameClickTime = 0;
    this.speedUp = true;
    this.active = false;
    this.running = false;
    var that = this;

    this.on = function() {
        //    if (this.filmstrip.frames.length) // > 0 && !this.filmstrip.glidingAway)
        if (this.running && !this.filmstrip.glidingAway)
            return true;
        else
            return false;
    }

    this.off = function() {
        //    if (this.filmstrip.frames.length == 0) // || this.filmstrip.glidingAway)
        if (!this.running || this.filmstrip.glidingAway)
            return true;
        else
            return false;
    }
    
    this.stepDown = function() {
        that.filmstrip.stepDown(that.stepsize);
    }

    this.stepUp = function() {
        that.filmstrip.stepUp(that.stepsize);
    }

    this.startGlideDown = function(secondsTillStart, glidesize, glidefriction) {
        if (secondsTillStart == null) secondsTillStart = 0;
        setTimeout(function() {
            if (glidesize != null) that.glidesize = glidesize;
            if (glidefriction != null) that.glidefriction = glidefriction;
            that.glideDown();
            updateSwitch();
        },
        secondsTillStart * 1000);
    }

    this.glideDown = function() {
        that.filmstrip.stepDown(Math.round(that.glidesize));

        if (that.glidesize > 1) that.glidesize *= 1 - that.glidefriction;  // shorten step size
        else if (that.glideinterval < 100) that.glideinterval *= 1 + that.glidefriction;  // after step size is minimized, lengthen interval
        else return;  // glide is over

        that.glidetimer = setTimeout(that.glideDown, Math.round(that.glideinterval));
    }

    this.startGlideUp = function(secondsTillStart, glidesize, glidefriction) {
        if (secondsTillStart == null) secondsTillStart = 0;
        setTimeout(function() {
            if (glidesize != null) that.glidesize = glidesize;
            if (glidefriction != null) that.glidefriction = glidefriction;
            that.glideUp();
        },
        secondsTillStart * 1000);
    }
    
    this.glideUp = function() {
        that.filmstrip.stepUp(Math.round(that.glidesize));

        if (that.glidesize > 1) that.glidesize *= 1 - that.glidefriction;  // shorten step size
        else if (that.glideinterval < 100) that.glideinterval *= 1 + that.glidefriction;  // after step size is minimized, lengthen interval
        else return;  // glide is over

        that.glidetimer = setTimeout(that.glideUp, Math.round(that.glideinterval));
    }

    this.slideTo = function(secondsTillStart, destination) {
        this.slideDestination = destination;
        var currentPosition = parseInt(this.filmstrip.container.style.left);
        this.slideHalfwayPoint = (destination - currentPosition) / 2 + currentPosition;
        if (destination > currentPosition)
            this.slidesize = 1;
        else
            this.slidsize = -1;
        this.slideinterval = 100;
        this.startSlide(secondsTillStart);
    }

    this.startSlide = function(secondsTillStart, slidesize, slidefriction) {
        if (secondsTillStart == null) secondsTillStart = 0;
        setTimeout(function() {
            if (slidesize != null) that.slidesize = slidesize;
            if (slidefriction != null) that.slidefriction = slidefriction;
            that.slide();
        },
        secondsTillStart * 1000);
    }

    this.slide = function() {
        that.filmstrip.slide(Math.round(that.slidesize));

        if (!that.slideHalfwayReached) {
            if (that.slideinterval > that.stepinterval)
                that.slideinterval -= (150 - that.slideinterval) * that.slidefriction;
            else
                that.slidesize *= 1 + that.slidefriction;  // increase step size
        }
        else {  // decelerate
            if (Math.abs(that.slidesize) > 1) that.slidesize *= 1 - that.slidefriction;  // shorten step size
            else if (that.slideinterval < 100) that.slideinterval *= 1 + that.slidefriction;  // after step size is minimized, lengthen interval
            else { that.slideStop(); return; }   // slide is over
        }

        // we want to accelerate to the halfway point and decelerate from there to the endpoint
        // so first we have to see which side of the halfway point we are on
        var position = parseInt(that.filmstrip.container.style.left);
        if (that.slidesize > 0) { // we are sliding to the right
            if (position < that.slideHalfwayPoint) // we haven't reached the halfway point so keep accelerating
                that.slideHalfwayReached = false;
            else
                that.slideHalfwayReached = true;
        }
        else {// we are sliding to the left
            if (position > that.slideHalfwayPoint)  // we haven't reached the halfway point so keep accelerating
                that.slideHalfwayReached = false;
            else
                that.slideHalfwayReached = true;
        }
        
        that.slidetimer = setTimeout(that.slide, Math.round(that.slideinterval));
    }

    this.faster = function() {
        with (that) {
            stepsize *= 2;
            if (stepsize < 1)
                stepsize = 1;
            lastStepsize = stepsize;
        }
    }

    this.slower = function() {
        with (that) {
            clicktimer = null;
            if (stepsize > 1)
                stepsize = stepsize / 2;
            lastStepsize = stepsize;
        }
    }

    this.toggle = function(direction) {
        that.clicktimer = null;
        if (that.running == false)
            if (direction == 1)
            that.startRollDown();
        else
            that.startUp();
        else
            that.stop();
    }

    this.startRollDown = function(secondsTillStart, stepsize) {
        if (secondsTillStart == null) secondsTillStart = 0;
        setTimeout(function() {
            if (stepsize != null) {
                that.stepsize = stepsize;
                that.lastStepsize = stepsize;
            }
            that.running = true;
            that.rollDown();
            updateSwitch();
        },
        secondsTillStart * 1000);
        //        tooltipManager.enableTip("stop");        
    }

    this.startUp = function() {
        that.running = true;
        that.rollUp();
    }

    this.stop = function() {
        that.running = false;
        that.rollStop();
        that.slideStop();
        that.glideStop();
        that.glideAwayStop();
//        tooltipManager.enableTip("start");
    }

    this.glideAway = function(secondsTillStart) {
        this.glideAwayTimer = setTimeout(function() {
            that.filmstrip.glidingAway = true;
            if (!that.running) that.startRollDown();
            updateSwitch();            
        },
        secondsTillStart * 1000);
    }

    this.glideAwayStop = function() {
        // if we're still waiting on a countdown timer to start gliding away, cancel that timer
        clearTimeout(this.glideAwayTimer);
        // if we're currently glidingAway then stepsize has been increasing so reset it
        if (that.filmstrip.glidingAway) {
            that.filmstrip.glidingAway = false;
            that.stepsize = that.lastStepsize;
        }
        updateSwitch();
    }
}

Projector.prototype.move = function(distance) {
    if (distance > 0)
        this.filmstrip.stepDown(distance);
    else
        this.filmstrip.stepUp(-distance);
}

Projector.prototype.rollDown = function() {
    this.rollStop();
    this.rolltimer = setInterval(this.stepDown, this.stepinterval);
}

Projector.prototype.rollUp = function() {
    this.rollStop();
    this.rolltimer = setInterval(this.stepUp, this.stepinterval);
}

Projector.prototype.rollStop = function() {
    clearInterval(this.rolltimer);
}

Projector.prototype.glideStop = function() {
    clearTimeout(this.glidetimer);
    this.glideinterval = this.stepinterval;
}

Projector.prototype.slideStop = function() {
    clearTimeout(this.slidetimer);
    this.slideinterval = this.stepinterval;
}

Projector.prototype.changeSpeed = function(direction) {
    with (this) {
        if (direction > 0) {
            if (stepsize < 16)
                faster();
        }
        else {
            if (stepsize > 0.2)
                slower();
        }
    }
}

Projector.prototype.frameClick = function(direction) {
    // if this click is the by-product of a drag operation we want to ignore it so just return
    if (dragClick()) return;
    // ignore this click if it is the doubleclick part of a FireFox doubleclick event sequence (click-click-doubleclick)
    // we can identify this doubleclick event because it is automatically generated by the browser less than 10 milliseconds (typically 1 ms) 
    // after the previous click event, while a user's clicks are typically more than 100 milliseconds apart
    var now = new Date().getTime();
    if (now - this.frameClickTime < 10)
        return;
    else
        this.frameClickTime = now;
    with (this) {
        if (clicktimer == null) {
            // only a single-click so far
            // set single-click action to fire if second (double) click doesn't happen fast enough
            clicktimer = setTimeout(function() { toggle(1) }, 220);     // (direction) }, 200); up/down version
        }
        else {
            // we have a double-click
            // cancel single-click action (set to fire on Timeout)
            clearTimeout(clicktimer);
            clicktimer = null;
            changeSpeed(direction);
        }
    }
}

Projector.prototype.load = function(name, leftEdge, zIndex) {
    this.name = name;
    this.filmstrip = Projector.createFilm(this, name);
    this.filmstrip.setLeftEdge(leftEdge);
    this.filmstrip.setZindex(zIndex);
}

// This will be initialized after the necessary DOM elements have been created, but before the Projector objects are instantiated
Projector.screen = null;

Projector.createFilm = function(projector, name) {
    var film = new Filmstrip(projector, name);
    var mainImageSource = {
        path: "images/Cakes/" + name + "/Main/",
        type: ".jpg"
    };
    var thumbImageSource = {
        path: "images/Cakes/" + name + "/Thumb/",
        type: ".png"
    };
    var e = document.getElementById(name + 'FileList');
    var imageFileList = null;
    if (e.textContent) {    // FireFox
        imageFileList = e.textContent.match(/([\w\-]+)(?=\.jpg;)/g);
    }
    else {                  // IE
        imageFileList = e.innerText.match(/([\w\-]+)(?=\.jpg;)/g);
    }
    for (var i = 0; i < 2; i++) {  // double the film's length so short films aren't a problem by not filling the screen
        if (imageFileList) {
            for (var j = 0; j < imageFileList.length; j++) {
                var filename = imageFileList[j];
                film.addFrame(mainImageSource.path + filename + mainImageSource.type, thumbImageSource.path + filename + thumbImageSource.type);
            }
        }
    }
    return film;
}
