diff options
Diffstat (limited to 'misc/openlayers/lib/OpenLayers/Handler/Feature.js')
-rw-r--r-- | misc/openlayers/lib/OpenLayers/Handler/Feature.js | 434 |
1 files changed, 434 insertions, 0 deletions
diff --git a/misc/openlayers/lib/OpenLayers/Handler/Feature.js b/misc/openlayers/lib/OpenLayers/Handler/Feature.js new file mode 100644 index 0000000..39b86f2 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Handler/Feature.js @@ -0,0 +1,434 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Handler.js + */ + +/** + * Class: OpenLayers.Handler.Feature + * Handler to respond to mouse events related to a drawn feature. Callbacks + * with the following keys will be notified of the following events + * associated with features: click, clickout, over, out, and dblclick. + * + * This handler stops event propagation for mousedown and mouseup if those + * browser events target features that can be selected. + * + * Inherits from: + * - <OpenLayers.Handler> + */ +OpenLayers.Handler.Feature = OpenLayers.Class(OpenLayers.Handler, { + + /** + * Property: EVENTMAP + * {Object} A object mapping the browser events to objects with callback + * keys for in and out. + */ + EVENTMAP: { + 'click': {'in': 'click', 'out': 'clickout'}, + 'mousemove': {'in': 'over', 'out': 'out'}, + 'dblclick': {'in': 'dblclick', 'out': null}, + 'mousedown': {'in': null, 'out': null}, + 'mouseup': {'in': null, 'out': null}, + 'touchstart': {'in': 'click', 'out': 'clickout'} + }, + + /** + * Property: feature + * {<OpenLayers.Feature.Vector>} The last feature that was hovered. + */ + feature: null, + + /** + * Property: lastFeature + * {<OpenLayers.Feature.Vector>} The last feature that was handled. + */ + lastFeature: null, + + /** + * Property: down + * {<OpenLayers.Pixel>} The location of the last mousedown. + */ + down: null, + + /** + * Property: up + * {<OpenLayers.Pixel>} The location of the last mouseup. + */ + up: null, + + /** + * Property: clickTolerance + * {Number} The number of pixels the mouse can move between mousedown + * and mouseup for the event to still be considered a click. + * Dragging the map should not trigger the click and clickout callbacks + * unless the map is moved by less than this tolerance. Defaults to 4. + */ + clickTolerance: 4, + + /** + * Property: geometryTypes + * To restrict dragging to a limited set of geometry types, send a list + * of strings corresponding to the geometry class names. + * + * @type Array(String) + */ + geometryTypes: null, + + /** + * Property: stopClick + * {Boolean} If stopClick is set to true, handled clicks do not + * propagate to other click listeners. Otherwise, handled clicks + * do propagate. Unhandled clicks always propagate, whatever the + * value of stopClick. Defaults to true. + */ + stopClick: true, + + /** + * Property: stopDown + * {Boolean} If stopDown is set to true, handled mousedowns do not + * propagate to other mousedown listeners. Otherwise, handled + * mousedowns do propagate. Unhandled mousedowns always propagate, + * whatever the value of stopDown. Defaults to true. + */ + stopDown: true, + + /** + * Property: stopUp + * {Boolean} If stopUp is set to true, handled mouseups do not + * propagate to other mouseup listeners. Otherwise, handled mouseups + * do propagate. Unhandled mouseups always propagate, whatever the + * value of stopUp. Defaults to false. + */ + stopUp: false, + + /** + * Constructor: OpenLayers.Handler.Feature + * + * Parameters: + * control - {<OpenLayers.Control>} + * layer - {<OpenLayers.Layer.Vector>} + * callbacks - {Object} An object with a 'over' property whos value is + * a function to be called when the mouse is over a feature. The + * callback should expect to recieve a single argument, the feature. + * options - {Object} + */ + initialize: function(control, layer, callbacks, options) { + OpenLayers.Handler.prototype.initialize.apply(this, [control, callbacks, options]); + this.layer = layer; + }, + + /** + * Method: touchstart + * Handle touchstart events + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} Let the event propagate. + */ + touchstart: function(evt) { + this.startTouch(); + return OpenLayers.Event.isMultiTouch(evt) ? + true : this.mousedown(evt); + }, + + /** + * Method: touchmove + * Handle touchmove events. We just prevent the browser default behavior, + * for Android Webkit not to select text when moving the finger after + * selecting a feature. + * + * Parameters: + * evt - {Event} + */ + touchmove: function(evt) { + OpenLayers.Event.preventDefault(evt); + }, + + /** + * Method: mousedown + * Handle mouse down. Stop propagation if a feature is targeted by this + * event (stops map dragging during feature selection). + * + * Parameters: + * evt - {Event} + */ + mousedown: function(evt) { + // Feature selection is only done with a left click. Other handlers may stop the + // propagation of left-click mousedown events but not right-click mousedown events. + // This mismatch causes problems when comparing the location of the down and up + // events in the click function so it is important ignore right-clicks. + if (OpenLayers.Event.isLeftClick(evt) || OpenLayers.Event.isSingleTouch(evt)) { + this.down = evt.xy; + } + return this.handle(evt) ? !this.stopDown : true; + }, + + /** + * Method: mouseup + * Handle mouse up. Stop propagation if a feature is targeted by this + * event. + * + * Parameters: + * evt - {Event} + */ + mouseup: function(evt) { + this.up = evt.xy; + return this.handle(evt) ? !this.stopUp : true; + }, + + /** + * Method: click + * Handle click. Call the "click" callback if click on a feature, + * or the "clickout" callback if click outside any feature. + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} + */ + click: function(evt) { + return this.handle(evt) ? !this.stopClick : true; + }, + + /** + * Method: mousemove + * Handle mouse moves. Call the "over" callback if moving in to a feature, + * or the "out" callback if moving out of a feature. + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} + */ + mousemove: function(evt) { + if (!this.callbacks['over'] && !this.callbacks['out']) { + return true; + } + this.handle(evt); + return true; + }, + + /** + * Method: dblclick + * Handle dblclick. Call the "dblclick" callback if dblclick on a feature. + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} + */ + dblclick: function(evt) { + return !this.handle(evt); + }, + + /** + * Method: geometryTypeMatches + * Return true if the geometry type of the passed feature matches + * one of the geometry types in the geometryTypes array. + * + * Parameters: + * feature - {<OpenLayers.Vector.Feature>} + * + * Returns: + * {Boolean} + */ + geometryTypeMatches: function(feature) { + return this.geometryTypes == null || + OpenLayers.Util.indexOf(this.geometryTypes, + feature.geometry.CLASS_NAME) > -1; + }, + + /** + * Method: handle + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} The event occurred over a relevant feature. + */ + handle: function(evt) { + if(this.feature && !this.feature.layer) { + // feature has been destroyed + this.feature = null; + } + var type = evt.type; + var handled = false; + var previouslyIn = !!(this.feature); // previously in a feature + var click = (type == "click" || type == "dblclick" || type == "touchstart"); + this.feature = this.layer.getFeatureFromEvent(evt); + if(this.feature && !this.feature.layer) { + // feature has been destroyed + this.feature = null; + } + if(this.lastFeature && !this.lastFeature.layer) { + // last feature has been destroyed + this.lastFeature = null; + } + if(this.feature) { + if(type === "touchstart") { + // stop the event to prevent Android Webkit from + // "flashing" the map div + OpenLayers.Event.preventDefault(evt); + } + var inNew = (this.feature != this.lastFeature); + if(this.geometryTypeMatches(this.feature)) { + // in to a feature + if(previouslyIn && inNew) { + // out of last feature and in to another + if(this.lastFeature) { + this.triggerCallback(type, 'out', [this.lastFeature]); + } + this.triggerCallback(type, 'in', [this.feature]); + } else if(!previouslyIn || click) { + // in feature for the first time + this.triggerCallback(type, 'in', [this.feature]); + } + this.lastFeature = this.feature; + handled = true; + } else { + // not in to a feature + if(this.lastFeature && (previouslyIn && inNew || click)) { + // out of last feature for the first time + this.triggerCallback(type, 'out', [this.lastFeature]); + } + // next time the mouse goes in a feature whose geometry type + // doesn't match we don't want to call the 'out' callback + // again, so let's set this.feature to null so that + // previouslyIn will evaluate to false the next time + // we enter handle. Yes, a bit hackish... + this.feature = null; + } + } else if(this.lastFeature && (previouslyIn || click)) { + this.triggerCallback(type, 'out', [this.lastFeature]); + } + return handled; + }, + + /** + * Method: triggerCallback + * Call the callback keyed in the event map with the supplied arguments. + * For click and clickout, the <clickTolerance> is checked first. + * + * Parameters: + * type - {String} + */ + triggerCallback: function(type, mode, args) { + var key = this.EVENTMAP[type][mode]; + if(key) { + if(type == 'click' && this.up && this.down) { + // for click/clickout, only trigger callback if tolerance is met + var dpx = Math.sqrt( + Math.pow(this.up.x - this.down.x, 2) + + Math.pow(this.up.y - this.down.y, 2) + ); + if(dpx <= this.clickTolerance) { + this.callback(key, args); + } + // we're done with this set of events now: clear the cached + // positions so we can't trip over them later (this can occur + // if one of the up/down events gets eaten before it gets to us + // but we still get the click) + this.up = this.down = null; + } else { + this.callback(key, args); + } + } + }, + + /** + * Method: activate + * Turn on the handler. Returns false if the handler was already active. + * + * Returns: + * {Boolean} + */ + activate: function() { + var activated = false; + if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) { + this.moveLayerToTop(); + this.map.events.on({ + "removelayer": this.handleMapEvents, + "changelayer": this.handleMapEvents, + scope: this + }); + activated = true; + } + return activated; + }, + + /** + * Method: deactivate + * Turn off the handler. Returns false if the handler was already active. + * + * Returns: + * {Boolean} + */ + deactivate: function() { + var deactivated = false; + if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) { + this.moveLayerBack(); + this.feature = null; + this.lastFeature = null; + this.down = null; + this.up = null; + this.map.events.un({ + "removelayer": this.handleMapEvents, + "changelayer": this.handleMapEvents, + scope: this + }); + deactivated = true; + } + return deactivated; + }, + + /** + * Method: handleMapEvents + * + * Parameters: + * evt - {Object} + */ + handleMapEvents: function(evt) { + if (evt.type == "removelayer" || evt.property == "order") { + this.moveLayerToTop(); + } + }, + + /** + * Method: moveLayerToTop + * Moves the layer for this handler to the top, so mouse events can reach + * it. + */ + moveLayerToTop: function() { + var index = Math.max(this.map.Z_INDEX_BASE['Feature'] - 1, + this.layer.getZIndex()) + 1; + this.layer.setZIndex(index); + + }, + + /** + * Method: moveLayerBack + * Moves the layer back to the position determined by the map's layers + * array. + */ + moveLayerBack: function() { + var index = this.layer.getZIndex() - 1; + if (index >= this.map.Z_INDEX_BASE['Feature']) { + this.layer.setZIndex(index); + } else { + this.map.setLayerZIndex(this.layer, + this.map.getLayerIndex(this.layer)); + } + }, + + CLASS_NAME: "OpenLayers.Handler.Feature" +}); |