]> dev.renevier.net Git - syp.git/blob - openlayers/lib/OpenLayers/Handler/Feature.js
fixes notices
[syp.git] / openlayers / lib / OpenLayers / Handler / Feature.js
1 /* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
2  * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
3  * full text of the license. */
4
5
6 /**
7  * @requires OpenLayers/Handler.js
8  */
9
10 /**
11  * Class: OpenLayers.Handler.Feature 
12  * Handler to respond to mouse events related to a drawn feature.  Callbacks
13  *     with the following keys will be notified of the following events
14  *     associated with features: click, clickout, over, out, and dblclick.
15  *
16  * This handler stops event propagation for mousedown and mouseup if those
17  *     browser events target features that can be selected.
18  */
19 OpenLayers.Handler.Feature = OpenLayers.Class(OpenLayers.Handler, {
20
21     /**
22      * Property: EVENTMAP
23      * {Object} A object mapping the browser events to objects with callback
24      *     keys for in and out.
25      */
26     EVENTMAP: {
27         'click': {'in': 'click', 'out': 'clickout'},
28         'mousemove': {'in': 'over', 'out': 'out'},
29         'dblclick': {'in': 'dblclick', 'out': null},
30         'mousedown': {'in': null, 'out': null},
31         'mouseup': {'in': null, 'out': null}
32     },
33
34     /**
35      * Property: feature
36      * {<OpenLayers.Feature.Vector>} The last feature that was hovered.
37      */
38     feature: null,
39
40     /**
41      * Property: lastFeature
42      * {<OpenLayers.Feature.Vector>} The last feature that was handled.
43      */
44     lastFeature: null,
45
46     /**
47      * Property: down
48      * {<OpenLayers.Pixel>} The location of the last mousedown.
49      */
50     down: null,
51
52     /**
53      * Property: up
54      * {<OpenLayers.Pixel>} The location of the last mouseup.
55      */
56     up: null,
57     
58     /**
59      * Property: clickTolerance
60      * {Number} The number of pixels the mouse can move between mousedown
61      *     and mouseup for the event to still be considered a click.
62      *     Dragging the map should not trigger the click and clickout callbacks
63      *     unless the map is moved by less than this tolerance. Defaults to 4.
64      */
65     clickTolerance: 4,
66
67     /**
68      * Property: geometryTypes
69      * To restrict dragging to a limited set of geometry types, send a list
70      * of strings corresponding to the geometry class names.
71      * 
72      * @type Array(String)
73      */
74     geometryTypes: null,
75
76     /**
77      * Property: stopClick
78      * {Boolean} If stopClick is set to true, handled clicks do not
79      *      propagate to other click listeners. Otherwise, handled clicks
80      *      do propagate. Unhandled clicks always propagate, whatever the
81      *      value of stopClick. Defaults to true.
82      */
83     stopClick: true,
84
85     /**
86      * Property: stopDown
87      * {Boolean} If stopDown is set to true, handled mousedowns do not
88      *      propagate to other mousedown listeners. Otherwise, handled
89      *      mousedowns do propagate. Unhandled mousedowns always propagate,
90      *      whatever the value of stopDown. Defaults to true.
91      */
92     stopDown: true,
93
94     /**
95      * Property: stopUp
96      * {Boolean} If stopUp is set to true, handled mouseups do not
97      *      propagate to other mouseup listeners. Otherwise, handled mouseups
98      *      do propagate. Unhandled mouseups always propagate, whatever the
99      *      value of stopUp. Defaults to false.
100      */
101     stopUp: false,
102     
103     /**
104      * Constructor: OpenLayers.Handler.Feature
105      *
106      * Parameters:
107      * control - {<OpenLayers.Control>} 
108      * layer - {<OpenLayers.Layer.Vector>}
109      * callbacks - {Object} An object with a 'over' property whos value is
110      *     a function to be called when the mouse is over a feature. The 
111      *     callback should expect to recieve a single argument, the feature.
112      * options - {Object} 
113      */
114     initialize: function(control, layer, callbacks, options) {
115         OpenLayers.Handler.prototype.initialize.apply(this, [control, callbacks, options]);
116         this.layer = layer;
117     },
118
119
120     /**
121      * Method: mousedown
122      * Handle mouse down.  Stop propagation if a feature is targeted by this
123      *     event (stops map dragging during feature selection).
124      * 
125      * Parameters:
126      * evt - {Event} 
127      */
128     mousedown: function(evt) {
129         this.down = evt.xy;
130         return this.handle(evt) ? !this.stopDown : true;
131     },
132     
133     /**
134      * Method: mouseup
135      * Handle mouse up.  Stop propagation if a feature is targeted by this
136      *     event.
137      * 
138      * Parameters:
139      * evt - {Event} 
140      */
141     mouseup: function(evt) {
142         this.up = evt.xy;
143         return this.handle(evt) ? !this.stopUp : true;
144     },
145
146     /**
147      * Method: click
148      * Handle click.  Call the "click" callback if click on a feature,
149      *     or the "clickout" callback if click outside any feature.
150      * 
151      * Parameters:
152      * evt - {Event} 
153      *
154      * Returns:
155      * {Boolean}
156      */
157     click: function(evt) {
158         return this.handle(evt) ? !this.stopClick : true;
159     },
160         
161     /**
162      * Method: mousemove
163      * Handle mouse moves.  Call the "over" callback if moving in to a feature,
164      *     or the "out" callback if moving out of a feature.
165      * 
166      * Parameters:
167      * evt - {Event} 
168      *
169      * Returns:
170      * {Boolean}
171      */
172     mousemove: function(evt) {
173         if (!this.callbacks['over'] && !this.callbacks['out']) {
174             return true;
175         }     
176         this.handle(evt);
177         return true;
178     },
179     
180     /**
181      * Method: dblclick
182      * Handle dblclick.  Call the "dblclick" callback if dblclick on a feature.
183      *
184      * Parameters:
185      * evt - {Event} 
186      *
187      * Returns:
188      * {Boolean}
189      */
190     dblclick: function(evt) {
191         return !this.handle(evt);
192     },
193
194     /**
195      * Method: geometryTypeMatches
196      * Return true if the geometry type of the passed feature matches
197      *     one of the geometry types in the geometryTypes array.
198      *
199      * Parameters:
200      * feature - {<OpenLayers.Vector.Feature>}
201      *
202      * Returns:
203      * {Boolean}
204      */
205     geometryTypeMatches: function(feature) {
206         return this.geometryTypes == null ||
207             OpenLayers.Util.indexOf(this.geometryTypes,
208                                     feature.geometry.CLASS_NAME) > -1;
209     },
210
211     /**
212      * Method: handle
213      *
214      * Parameters:
215      * evt - {Event}
216      *
217      * Returns:
218      * {Boolean} The event occurred over a relevant feature.
219      */
220     handle: function(evt) {
221         if(this.feature && !this.feature.layer) {
222             // feature has been destroyed
223             this.feature = null;
224         }
225         var type = evt.type;
226         var handled = false;
227         var previouslyIn = !!(this.feature); // previously in a feature
228         var click = (type == "click" || type == "dblclick");
229         this.feature = this.layer.getFeatureFromEvent(evt);
230         if(this.feature && !this.feature.layer) {
231             // feature has been destroyed
232             this.feature = null;
233         }
234         if(this.lastFeature && !this.lastFeature.layer) {
235             // last feature has been destroyed
236             this.lastFeature = null;
237         }
238         if(this.feature) {
239             var inNew = (this.feature != this.lastFeature);
240             if(this.geometryTypeMatches(this.feature)) {
241                 // in to a feature
242                 if(previouslyIn && inNew) {
243                     // out of last feature and in to another
244                     if(this.lastFeature) {
245                         this.triggerCallback(type, 'out', [this.lastFeature]);
246                     }
247                     this.triggerCallback(type, 'in', [this.feature]);
248                 } else if(!previouslyIn || click) {
249                     // in feature for the first time
250                     this.triggerCallback(type, 'in', [this.feature]);
251                 }
252                 this.lastFeature = this.feature;
253                 handled = true;
254             } else {
255                 // not in to a feature
256                 if(this.lastFeature && (previouslyIn && inNew || click)) {
257                     // out of last feature for the first time
258                     this.triggerCallback(type, 'out', [this.lastFeature]);
259                 }
260                 // next time the mouse goes in a feature whose geometry type
261                 // doesn't match we don't want to call the 'out' callback
262                 // again, so let's set this.feature to null so that
263                 // previouslyIn will evaluate to false the next time
264                 // we enter handle. Yes, a bit hackish...
265                 this.feature = null;
266             }
267         } else {
268             if(this.lastFeature && (previouslyIn || click)) {
269                 this.triggerCallback(type, 'out', [this.lastFeature]);
270             }
271         }
272         return handled;
273     },
274     
275     /**
276      * Method: triggerCallback
277      * Call the callback keyed in the event map with the supplied arguments.
278      *     For click and clickout, the <clickTolerance> is checked first.
279      *
280      * Parameters:
281      * type - {String}
282      */
283     triggerCallback: function(type, mode, args) {
284         var key = this.EVENTMAP[type][mode];
285         if(key) {
286             if(type == 'click' && this.up && this.down) {
287                 // for click/clickout, only trigger callback if tolerance is met
288                 var dpx = Math.sqrt(
289                     Math.pow(this.up.x - this.down.x, 2) +
290                     Math.pow(this.up.y - this.down.y, 2)
291                 );
292                 if(dpx <= this.clickTolerance) {
293                     this.callback(key, args);
294                 }
295             } else {
296                 this.callback(key, args);
297             }
298         }
299     },
300
301     /**
302      * Method: activate 
303      * Turn on the handler.  Returns false if the handler was already active.
304      *
305      * Returns:
306      * {Boolean}
307      */
308     activate: function() {
309         var activated = false;
310         if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
311             this.moveLayerToTop();
312             this.map.events.on({
313                 "removelayer": this.handleMapEvents,
314                 "changelayer": this.handleMapEvents,
315                 scope: this
316             });
317             activated = true;
318         }
319         return activated;
320     },
321     
322     /**
323      * Method: deactivate 
324      * Turn off the handler.  Returns false if the handler was already active.
325      *
326      * Returns: 
327      * {Boolean}
328      */
329     deactivate: function() {
330         var deactivated = false;
331         if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
332             this.moveLayerBack();
333             this.feature = null;
334             this.lastFeature = null;
335             this.down = null;
336             this.up = null;
337             this.map.events.un({
338                 "removelayer": this.handleMapEvents,
339                 "changelayer": this.handleMapEvents,
340                 scope: this
341             });
342             deactivated = true;
343         }
344         return deactivated;
345     },
346     
347     /**
348      * Method handleMapEvents
349      * 
350      * Parameters:
351      * evt - {Object}
352      */
353     handleMapEvents: function(evt) {
354         if (!evt.property || evt.property == "order") {
355             this.moveLayerToTop();
356         }
357     },
358     
359     /**
360      * Method: moveLayerToTop
361      * Moves the layer for this handler to the top, so mouse events can reach
362      * it.
363      */
364     moveLayerToTop: function() {
365         var index = Math.max(this.map.Z_INDEX_BASE['Feature'] - 1,
366             this.layer.getZIndex()) + 1;
367         this.layer.setZIndex(index);
368         
369     },
370     
371     /**
372      * Method: moveLayerBack
373      * Moves the layer back to the position determined by the map's layers
374      * array.
375      */
376     moveLayerBack: function() {
377         var index = this.layer.getZIndex() - 1;
378         if (index >= this.map.Z_INDEX_BASE['Feature']) {
379             this.layer.setZIndex(index);
380         } else {
381             this.map.setLayerZIndex(this.layer,
382                 this.map.getLayerIndex(this.layer));
383         }
384     },
385
386     CLASS_NAME: "OpenLayers.Handler.Feature"
387 });