2011-12-06 07:50:25 +04:00
|
|
|
|
|
|
|
(function( $ ){
|
|
|
|
|
2011-12-14 05:04:38 +04:00
|
|
|
//Ensures we dont break existing instances of mousetracker if we are dumb
|
|
|
|
//enough to load openseadragon.js onto the page twice. I don't know how
|
|
|
|
//useful this pattern is, but if we decide to use it we should use it
|
|
|
|
//everywhere
|
2011-12-06 07:50:25 +04:00
|
|
|
if ($.MouseTracker) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-12-14 05:04:38 +04:00
|
|
|
var isIE = $.Utils.getBrowser() == $.Browser.IE,
|
|
|
|
buttonDownAny = false,
|
|
|
|
ieCapturingAny = false,
|
|
|
|
ieTrackersActive = {}, // dictionary from hash to MouseTracker
|
|
|
|
ieTrackersCapturing = []; // list of trackers interested in capture
|
2011-12-06 07:50:25 +04:00
|
|
|
|
|
|
|
|
|
|
|
$.MouseTracker = function (elmt, clickTimeThreshold, clickDistThreshold) {
|
|
|
|
//Start Thatcher - TODO: remove local function definitions in favor of
|
2011-12-20 16:39:02 +04:00
|
|
|
// - a global closure for MouseTracker so the number
|
2011-12-06 07:50:25 +04:00
|
|
|
// - of Viewers has less memory impact. Also use
|
|
|
|
// - prototype pattern instead of Singleton pattern.
|
|
|
|
//End Thatcher
|
2011-12-14 05:04:38 +04:00
|
|
|
var self = this,
|
|
|
|
ieSelf = null,
|
2011-12-06 07:50:25 +04:00
|
|
|
|
2011-12-14 05:04:38 +04:00
|
|
|
hash = Math.random(), // a unique hash for this tracker
|
|
|
|
elmt = $.Utils.getElement(elmt),
|
2011-12-06 07:50:25 +04:00
|
|
|
|
2011-12-14 05:04:38 +04:00
|
|
|
tracking = false,
|
|
|
|
capturing = false,
|
|
|
|
buttonDownElmt = false,
|
|
|
|
insideElmt = false,
|
2011-12-06 07:50:25 +04:00
|
|
|
|
2011-12-14 05:04:38 +04:00
|
|
|
lastPoint = null, // position of last mouse down/move
|
|
|
|
lastMouseDownTime = null, // time of last mouse down
|
|
|
|
lastMouseDownPoint = null, // position of last mouse down
|
|
|
|
clickTimeThreshold = clickTimeThreshold,
|
|
|
|
clickDistThreshold = clickDistThreshold;
|
2011-12-06 07:50:25 +04:00
|
|
|
|
|
|
|
|
2011-12-14 05:04:38 +04:00
|
|
|
this.target = elmt;
|
|
|
|
this.enterHandler = null; // function(tracker, position, buttonDownElmt, buttonDownAny)
|
|
|
|
this.exitHandler = null; // function(tracker, position, buttonDownElmt, buttonDownAny)
|
|
|
|
this.pressHandler = null; // function(tracker, position)
|
2011-12-06 07:50:25 +04:00
|
|
|
this.releaseHandler = null; // function(tracker, position, insideElmtPress, insideElmtRelease)
|
2011-12-14 05:04:38 +04:00
|
|
|
this.scrollHandler = null; // function(tracker, position, scroll, shift)
|
|
|
|
this.clickHandler = null; // function(tracker, position, quick, shift)
|
|
|
|
this.dragHandler = null; // function(tracker, position, delta, shift)
|
2011-12-06 07:50:25 +04:00
|
|
|
|
2011-12-14 05:04:38 +04:00
|
|
|
(function () {
|
|
|
|
ieSelf = {
|
|
|
|
hasMouse: hasMouse,
|
|
|
|
onMouseOver: onMouseOver,
|
|
|
|
onMouseOut: onMouseOut,
|
|
|
|
onMouseUp: onMouseUp,
|
|
|
|
onMouseMove: onMouseMove
|
|
|
|
};
|
|
|
|
})();
|
2011-12-06 07:50:25 +04:00
|
|
|
|
|
|
|
|
2011-12-14 05:04:38 +04:00
|
|
|
this.isTracking = function () {
|
|
|
|
return tracking;
|
|
|
|
};
|
|
|
|
|
|
|
|
this.setTracking = function (track) {
|
|
|
|
if (track) {
|
|
|
|
startTracking();
|
|
|
|
} else {
|
|
|
|
stopTracking();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2011-12-06 07:50:25 +04:00
|
|
|
function startTracking() {
|
|
|
|
if (!tracking) {
|
|
|
|
$.Utils.addEvent(elmt, "mouseover", onMouseOver, false);
|
|
|
|
$.Utils.addEvent(elmt, "mouseout", onMouseOut, false);
|
|
|
|
$.Utils.addEvent(elmt, "mousedown", onMouseDown, false);
|
|
|
|
$.Utils.addEvent(elmt, "mouseup", onMouseUp, false);
|
|
|
|
$.Utils.addEvent(elmt, "click", onMouseClick, false);
|
|
|
|
$.Utils.addEvent(elmt, "DOMMouseScroll", onMouseWheelSpin, false);
|
|
|
|
$.Utils.addEvent(elmt, "mousewheel", onMouseWheelSpin, false); // Firefox
|
|
|
|
|
|
|
|
tracking = true;
|
|
|
|
ieTrackersActive[hash] = ieSelf;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function stopTracking() {
|
|
|
|
if (tracking) {
|
|
|
|
$.Utils.removeEvent(elmt, "mouseover", onMouseOver, false);
|
|
|
|
$.Utils.removeEvent(elmt, "mouseout", onMouseOut, false);
|
|
|
|
$.Utils.removeEvent(elmt, "mousedown", onMouseDown, false);
|
|
|
|
$.Utils.removeEvent(elmt, "mouseup", onMouseUp, false);
|
|
|
|
$.Utils.removeEvent(elmt, "click", onMouseClick, false);
|
|
|
|
$.Utils.removeEvent(elmt, "DOMMouseScroll", onMouseWheelSpin, false);
|
|
|
|
$.Utils.removeEvent(elmt, "mousewheel", onMouseWheelSpin, false);
|
|
|
|
|
|
|
|
releaseMouse();
|
|
|
|
tracking = false;
|
|
|
|
delete ieTrackersActive[hash];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function captureMouse() {
|
|
|
|
if (!capturing) {
|
|
|
|
if (isIE) {
|
|
|
|
$.Utils.removeEvent(elmt, "mouseup", onMouseUp, false);
|
|
|
|
$.Utils.addEvent(elmt, "mouseup", onMouseUpIE, true);
|
|
|
|
$.Utils.addEvent(elmt, "mousemove", onMouseMoveIE, true);
|
|
|
|
} else {
|
|
|
|
$.Utils.addEvent(window, "mouseup", onMouseUpWindow, true);
|
|
|
|
$.Utils.addEvent(window, "mousemove", onMouseMove, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
capturing = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function releaseMouse() {
|
|
|
|
if (capturing) {
|
|
|
|
if (isIE) {
|
|
|
|
$.Utils.removeEvent(elmt, "mousemove", onMouseMoveIE, true);
|
|
|
|
$.Utils.removeEvent(elmt, "mouseup", onMouseUpIE, true);
|
|
|
|
$.Utils.addEvent(elmt, "mouseup", onMouseUp, false);
|
|
|
|
} else {
|
|
|
|
$.Utils.removeEvent(window, "mousemove", onMouseMove, true);
|
|
|
|
$.Utils.removeEvent(window, "mouseup", onMouseUpWindow, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
capturing = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function triggerOthers(eventName, event) {
|
|
|
|
var trackers = ieTrackersActive;
|
|
|
|
for (var otherHash in trackers) {
|
|
|
|
if (trackers.hasOwnProperty(otherHash) && hash != otherHash) {
|
|
|
|
trackers[otherHash][eventName](event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function hasMouse() {
|
|
|
|
return insideElmt;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function onMouseOver(event) {
|
|
|
|
var event = $.Utils.getEvent(event);
|
|
|
|
|
|
|
|
if (isIE && capturing && !isChild(event.srcElement, elmt)) {
|
|
|
|
triggerOthers("onMouseOver", event);
|
|
|
|
}
|
|
|
|
|
|
|
|
var to = event.target ? event.target : event.srcElement;
|
|
|
|
var from = event.relatedTarget ? event.relatedTarget : event.fromElement;
|
|
|
|
if (!isChild(elmt, to) || isChild(elmt, from)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
insideElmt = true;
|
|
|
|
|
|
|
|
if (typeof (self.enterHandler) == "function") {
|
|
|
|
try {
|
|
|
|
self.enterHandler(self, getMouseRelative(event, elmt),
|
|
|
|
buttonDownElmt, buttonDownAny);
|
|
|
|
} catch (e) {
|
|
|
|
$.Debug.error(e.name +
|
|
|
|
" while executing enter handler: " + e.message, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function onMouseOut(event) {
|
|
|
|
var event = $.Utils.getEvent(event);
|
|
|
|
|
|
|
|
if (isIE && capturing && !isChild(event.srcElement, elmt)) {
|
|
|
|
triggerOthers("onMouseOut", event);
|
|
|
|
}
|
|
|
|
|
|
|
|
var from = event.target ? event.target : event.srcElement;
|
|
|
|
var to = event.relatedTarget ? event.relatedTarget : event.toElement;
|
|
|
|
if (!isChild(elmt, from) || isChild(elmt, to)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
insideElmt = false;
|
|
|
|
|
|
|
|
if (typeof (self.exitHandler) == "function") {
|
|
|
|
try {
|
|
|
|
self.exitHandler(self, getMouseRelative(event, elmt),
|
|
|
|
buttonDownElmt, buttonDownAny);
|
|
|
|
} catch (e) {
|
|
|
|
$.Debug.error(e.name +
|
|
|
|
" while executing exit handler: " + e.message, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function onMouseDown(event) {
|
|
|
|
var event = $.Utils.getEvent(event);
|
|
|
|
|
|
|
|
if (event.button == 2) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
buttonDownElmt = true;
|
|
|
|
|
|
|
|
lastPoint = getMouseAbsolute(event);
|
|
|
|
lastMouseDownPoint = lastPoint;
|
|
|
|
lastMouseDownTime = new Date().getTime();
|
|
|
|
|
|
|
|
if (typeof (self.pressHandler) == "function") {
|
|
|
|
try {
|
|
|
|
self.pressHandler(self, getMouseRelative(event, elmt));
|
|
|
|
} catch (e) {
|
|
|
|
$.Debug.error(e.name +
|
|
|
|
" while executing press handler: " + e.message, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (self.pressHandler || self.dragHandler) {
|
|
|
|
$.Utils.cancelEvent(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isIE || !ieCapturingAny) {
|
|
|
|
captureMouse();
|
|
|
|
ieCapturingAny = true;
|
|
|
|
ieTrackersCapturing = [ieSelf]; // reset to empty & add us
|
|
|
|
} else if (isIE) {
|
|
|
|
ieTrackersCapturing.push(ieSelf); // add us to the list
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function onMouseUp(event) {
|
|
|
|
var event = $.Utils.getEvent(event);
|
|
|
|
var insideElmtPress = buttonDownElmt;
|
|
|
|
var insideElmtRelease = insideElmt;
|
|
|
|
|
|
|
|
if (event.button == 2) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
buttonDownElmt = false;
|
|
|
|
|
|
|
|
if (typeof (self.releaseHandler) == "function") {
|
|
|
|
try {
|
|
|
|
self.releaseHandler(self, getMouseRelative(event, elmt),
|
|
|
|
insideElmtPress, insideElmtRelease);
|
|
|
|
} catch (e) {
|
|
|
|
$.Debug.error(e.name +
|
|
|
|
" while executing release handler: " + e.message, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (insideElmtPress && insideElmtRelease) {
|
|
|
|
handleMouseClick(event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Only triggered once by the deepest element that initially received
|
|
|
|
* the mouse down event. We want to make sure THIS event doesn't bubble.
|
|
|
|
* Instead, we want to trigger the elements that initially received the
|
|
|
|
* mouse down event (including this one) only if the mouse is no longer
|
|
|
|
* inside them. Then, we want to release capture, and emulate a regular
|
|
|
|
* mouseup on the event that this event was meant for.
|
|
|
|
*/
|
|
|
|
function onMouseUpIE(event) {
|
|
|
|
var event = $.Utils.getEvent(event);
|
|
|
|
|
|
|
|
if (event.button == 2) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (var i = 0; i < ieTrackersCapturing.length; i++) {
|
|
|
|
var tracker = ieTrackersCapturing[i];
|
|
|
|
if (!tracker.hasMouse()) {
|
|
|
|
tracker.onMouseUp(event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
releaseMouse();
|
|
|
|
ieCapturingAny = false;
|
|
|
|
event.srcElement.fireEvent("on" + event.type,
|
|
|
|
document.createEventObject(event));
|
|
|
|
|
|
|
|
$.Utils.stopEvent(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Only triggered in W3C browsers by elements within which the mouse was
|
|
|
|
* initially pressed, since they are now listening to the window for
|
|
|
|
* mouseup during the capture phase. We shouldn't handle the mouseup
|
|
|
|
* here if the mouse is still inside this element, since the regular
|
|
|
|
* mouseup handler will still fire.
|
|
|
|
*/
|
|
|
|
function onMouseUpWindow(event) {
|
|
|
|
if (!insideElmt) {
|
|
|
|
onMouseUp(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
releaseMouse();
|
|
|
|
}
|
|
|
|
|
|
|
|
function onMouseClick(event) {
|
|
|
|
|
|
|
|
if (self.clickHandler) {
|
|
|
|
$.Utils.cancelEvent(event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function onMouseWheelSpin(event) {
|
|
|
|
var nDelta = 0;
|
|
|
|
if (!event) { // For IE, access the global (window) event object
|
|
|
|
event = window.event;
|
|
|
|
}
|
|
|
|
if (event.wheelDelta) { // IE and Opera
|
|
|
|
nDelta = event.wheelDelta;
|
|
|
|
if (window.opera) { // Opera has the values reversed
|
|
|
|
nDelta = -nDelta;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (event.detail) { // Mozilla FireFox
|
|
|
|
nDelta = -event.detail;
|
|
|
|
}
|
|
|
|
|
|
|
|
nDelta = nDelta > 0 ? 1 : -1;
|
|
|
|
|
|
|
|
if (typeof (self.scrollHandler) == "function") {
|
|
|
|
try {
|
|
|
|
self.scrollHandler(self, getMouseRelative(event, elmt), nDelta, event.shiftKey);
|
|
|
|
} catch (e) {
|
|
|
|
$.Debug.error(e.name +
|
|
|
|
" while executing scroll handler: " + e.message, e);
|
|
|
|
}
|
|
|
|
|
|
|
|
$.Utils.cancelEvent(event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleMouseClick(event) {
|
|
|
|
var event = $.Utils.getEvent(event);
|
|
|
|
|
|
|
|
if (event.button == 2) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var time = new Date().getTime() - lastMouseDownTime;
|
|
|
|
var point = getMouseAbsolute(event);
|
|
|
|
var distance = lastMouseDownPoint.distanceTo(point);
|
|
|
|
var quick = time <= clickTimeThreshold &&
|
|
|
|
distance <= clickDistThreshold;
|
|
|
|
|
|
|
|
if (typeof (self.clickHandler) == "function") {
|
|
|
|
try {
|
|
|
|
self.clickHandler(self, getMouseRelative(event, elmt),
|
|
|
|
quick, event.shiftKey);
|
|
|
|
} catch (e) {
|
|
|
|
$.Debug.error(e.name +
|
|
|
|
" while executing click handler: " + e.message, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function onMouseMove(event) {
|
|
|
|
var event = $.Utils.getEvent(event);
|
|
|
|
var point = getMouseAbsolute(event);
|
|
|
|
var delta = point.minus(lastPoint);
|
|
|
|
|
|
|
|
lastPoint = point;
|
|
|
|
|
|
|
|
if (typeof (self.dragHandler) == "function") {
|
|
|
|
try {
|
|
|
|
self.dragHandler(self, getMouseRelative(event, elmt),
|
|
|
|
delta, event.shiftKey);
|
|
|
|
} catch (e) {
|
|
|
|
$.Debug.error(e.name +
|
|
|
|
" while executing drag handler: " + e.message, e);
|
|
|
|
}
|
|
|
|
|
|
|
|
$.Utils.cancelEvent(event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Only triggered once by the deepest element that initially received
|
|
|
|
* the mouse down event. Since no other element has captured the mouse,
|
|
|
|
* we want to trigger the elements that initially received the mouse
|
|
|
|
* down event (including this one).
|
|
|
|
*/
|
|
|
|
function onMouseMoveIE(event) {
|
|
|
|
for (var i = 0; i < ieTrackersCapturing.length; i++) {
|
|
|
|
ieTrackersCapturing[i].onMouseMove(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
$.Utils.stopEvent(event);
|
|
|
|
}
|
|
|
|
|
2011-12-14 05:04:38 +04:00
|
|
|
};
|
2011-12-06 07:50:25 +04:00
|
|
|
|
2011-12-14 05:04:38 +04:00
|
|
|
function getMouseAbsolute( event ) {
|
|
|
|
return $.Utils.getMousePosition(event);
|
|
|
|
}
|
2011-12-06 07:50:25 +04:00
|
|
|
|
2011-12-14 05:04:38 +04:00
|
|
|
function getMouseRelative( event, elmt ) {
|
|
|
|
var mouse = $.Utils.getMousePosition(event);
|
|
|
|
var offset = $.Utils.getElementPosition(elmt);
|
2011-12-06 07:50:25 +04:00
|
|
|
|
2011-12-14 05:04:38 +04:00
|
|
|
return mouse.minus(offset);
|
|
|
|
}
|
2011-12-06 07:50:25 +04:00
|
|
|
|
2011-12-14 05:04:38 +04:00
|
|
|
/**
|
|
|
|
* Returns true if elmtB is a child node of elmtA, or if they're equal.
|
|
|
|
*/
|
|
|
|
function isChild( elmtA, elmtB ) {
|
|
|
|
var body = document.body;
|
|
|
|
while (elmtB && elmtA != elmtB && body != elmtB) {
|
|
|
|
try {
|
|
|
|
elmtB = elmtB.parentNode;
|
|
|
|
} catch (e) {
|
|
|
|
return false;
|
2011-12-06 07:50:25 +04:00
|
|
|
}
|
2011-12-14 05:04:38 +04:00
|
|
|
}
|
|
|
|
return elmtA == elmtB;
|
|
|
|
}
|
|
|
|
|
|
|
|
function onGlobalMouseDown() {
|
|
|
|
buttonDownAny = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
function onGlobalMouseUp() {
|
|
|
|
buttonDownAny = false;
|
|
|
|
}
|
2011-12-06 07:50:25 +04:00
|
|
|
|
|
|
|
|
2011-12-14 05:04:38 +04:00
|
|
|
(function () {
|
|
|
|
if (isIE) {
|
|
|
|
$.Utils.addEvent(document, "mousedown", onGlobalMouseDown, false);
|
|
|
|
$.Utils.addEvent(document, "mouseup", onGlobalMouseUp, false);
|
|
|
|
} else {
|
|
|
|
$.Utils.addEvent(window, "mousedown", onGlobalMouseDown, true);
|
|
|
|
$.Utils.addEvent(window, "mouseup", onGlobalMouseUp, true);
|
|
|
|
}
|
|
|
|
})();
|
2011-12-06 07:50:25 +04:00
|
|
|
|
|
|
|
}( OpenSeadragon ));
|