1 | define(["dojo", "../util/oo", "../defaults"], |
---|
2 | function(dojo, oo, defaults){ |
---|
3 | |
---|
4 | //dojox.drawing.manager.Mouse = |
---|
5 | return oo.declare( |
---|
6 | function(/* Object */options){ |
---|
7 | this.util = options.util; |
---|
8 | this.keys = options.keys; |
---|
9 | this.id = options.id || this.util.uid("mouse"); |
---|
10 | this.currentNodeId = ""; |
---|
11 | this.registered = {}; |
---|
12 | }, |
---|
13 | |
---|
14 | { |
---|
15 | // summary: |
---|
16 | // Master object (instance) that tracks mouse |
---|
17 | // events. A new instance is created for each |
---|
18 | // Drawing object. |
---|
19 | // description: |
---|
20 | // You could connect to any method or event in this |
---|
21 | // class, but it is designed to have the object |
---|
22 | // 'registered'. All objects with the current event |
---|
23 | // will be called directly. |
---|
24 | // |
---|
25 | // Custom events are used often. In addition to |
---|
26 | // standard events onDown, onUp, onDrag, etc, if |
---|
27 | // a certain object is clicked upon (or dragged, etc), |
---|
28 | // that object's drawingType will create the custom event, |
---|
29 | // such as onAnchorDown, or onStencilDown. |
---|
30 | |
---|
31 | |
---|
32 | // doublClickSpeed: Number |
---|
33 | // Milliseconds between clicks to |
---|
34 | // register as for onDoubleClick |
---|
35 | doublClickSpeed:400, |
---|
36 | |
---|
37 | // private properties |
---|
38 | |
---|
39 | _lastx:0, |
---|
40 | _lasty:0, |
---|
41 | __reg:0, |
---|
42 | _downOnCanvas:false, |
---|
43 | |
---|
44 | /*===== |
---|
45 | CustomEventMethod: { |
---|
46 | // summary: |
---|
47 | // The custom event methods that an Object that has |
---|
48 | // registered with manager.Mouse can receive. |
---|
49 | // Can contain any or all of the following methods |
---|
50 | // and they will be called on mouse events. All events |
---|
51 | // will be sent a EventObject event object. |
---|
52 | // |
---|
53 | // NOTE: |
---|
54 | // Events happen anywhere in the document unless |
---|
55 | // otherwise noted. |
---|
56 | |
---|
57 | // onMove: Function |
---|
58 | // Fires on mousemove when mouse is up |
---|
59 | // onDown: Function |
---|
60 | // Fires on mousedown *on the canvas* |
---|
61 | // onDrag: Function |
---|
62 | // Fires on mousemove when mouse is down |
---|
63 | // onUp: Function |
---|
64 | // Fires on mouseup, anywhere in the document |
---|
65 | // onStencilDown: Function |
---|
66 | // Fired on mousedown on a Stencil |
---|
67 | // onStencilDrag: Function |
---|
68 | // Fired when mouse moves and mose is down on a Stencil |
---|
69 | // onStencilUp: Function |
---|
70 | // Fired on mouseup off of a Stencil |
---|
71 | // on[Custom]Up|Down|Move: Function |
---|
72 | // Custom events can bet set and fired by setting a |
---|
73 | // different drawingType on a Stencil, or by calling |
---|
74 | // setEventMode(customEventName) |
---|
75 | }, |
---|
76 | EventObject: function(){ |
---|
77 | // summary: |
---|
78 | // The custom event object that is sent to registered objects |
---|
79 | // and their respective methods. |
---|
80 | // |
---|
81 | // NOTE: Most event objects are the same with the exception |
---|
82 | // of the onDown events, which have fewer. |
---|
83 | |
---|
84 | // id: String |
---|
85 | // Id of the focused object (included in onDown) |
---|
86 | // pageX: Number |
---|
87 | // The X coordinate of the mouse from the left side of |
---|
88 | // the document. (included in onDown) |
---|
89 | // pageY: Number |
---|
90 | // The Y coordinate of the mouse from the top of |
---|
91 | // the document. (included in onDown) |
---|
92 | // x: Number |
---|
93 | // The X coordinate of the mouse from the left side |
---|
94 | // of the canvas (included in onDown) |
---|
95 | // y: Number |
---|
96 | // The Y coordinate of the mouse from the top |
---|
97 | // of the canvas (included in onDown) |
---|
98 | |
---|
99 | // last: Object |
---|
100 | // The x and y coordinates of the last mousemove |
---|
101 | // relative to the canvas (not included in onDown) |
---|
102 | // move: Object |
---|
103 | // The x and y amounts the mouse moved since the last event |
---|
104 | // (not included in onDown) |
---|
105 | // orgX: Number |
---|
106 | // The left side of the canvas from the side of the document (not included in onDown) |
---|
107 | // orgY: Number |
---|
108 | // The top of the canvas from the top of the document (not included in onDown) |
---|
109 | // scroll: Object |
---|
110 | // The 'top' and 'left' scroll amounts of the canvas. (not included in onDown) |
---|
111 | // start: Object |
---|
112 | // The x and y coordinates of the mousedown event (not included in onDown) |
---|
113 | // withinCanvas: Boolean |
---|
114 | // Whether the event happened within the Canvas or not (not included in onDown) |
---|
115 | }, |
---|
116 | =====*/ |
---|
117 | |
---|
118 | init: function(/* HTMLNode*/node){ |
---|
119 | // summary: |
---|
120 | // Internal. Initializes mouse. |
---|
121 | |
---|
122 | this.container = node; |
---|
123 | this.setCanvas(); |
---|
124 | var c; |
---|
125 | var _isDown = false; |
---|
126 | dojo.connect(this.container, "rightclick", this, function(evt){ |
---|
127 | console.warn("RIGHTCLICK") |
---|
128 | }); |
---|
129 | |
---|
130 | dojo.connect(document.body, "mousedown", this, function(evt){ |
---|
131 | //evt.preventDefault(); |
---|
132 | //dojo.stopEvent(evt); |
---|
133 | }); |
---|
134 | |
---|
135 | dojo.connect(this.container, "mousedown", this, function(evt){ |
---|
136 | this.down(evt); |
---|
137 | // Right click shouldn't trigger drag |
---|
138 | if(evt.button != dojo.mouseButtons.RIGHT){ |
---|
139 | _isDown = true; |
---|
140 | c = dojo.connect(document, "mousemove", this, "drag"); |
---|
141 | } |
---|
142 | }); |
---|
143 | dojo.connect(document, "mouseup", this, function(evt){ |
---|
144 | dojo.disconnect(c); |
---|
145 | _isDown = false; |
---|
146 | this.up(evt); |
---|
147 | }); |
---|
148 | dojo.connect(document, "mousemove", this, function(evt){ |
---|
149 | if(!_isDown){ |
---|
150 | this.move(evt); |
---|
151 | } |
---|
152 | }); |
---|
153 | dojo.connect(this.keys, "onEsc", this, function(evt){ |
---|
154 | this._dragged = false; |
---|
155 | }); |
---|
156 | }, |
---|
157 | |
---|
158 | setCanvas: function(){ |
---|
159 | // summary: |
---|
160 | // Internal. Sets canvas position |
---|
161 | var pos = dojo.position(this.container.parentNode); |
---|
162 | this.origin = dojo.clone(pos); |
---|
163 | }, |
---|
164 | |
---|
165 | scrollOffset: function(){ |
---|
166 | // summary: |
---|
167 | // Gets scroll offset of canvas |
---|
168 | return { |
---|
169 | top:this.container.parentNode.scrollTop, |
---|
170 | left:this.container.parentNode.scrollLeft |
---|
171 | }; // Object |
---|
172 | }, |
---|
173 | |
---|
174 | resize: function(width,height){ |
---|
175 | if(this.origin){ |
---|
176 | this.origin.w=width; |
---|
177 | this.origin.h=height; |
---|
178 | } |
---|
179 | }, |
---|
180 | |
---|
181 | register: function(/* CustomEventMethod*/scope){ |
---|
182 | // summary: |
---|
183 | // All objects (Stencils) should register here if they |
---|
184 | // use mouse events. When registering, the object will |
---|
185 | // be called if it has that method. |
---|
186 | // See: CustomEventMethod and EventObject |
---|
187 | // scope: |
---|
188 | // The object to be called |
---|
189 | // returns: handle |
---|
190 | // Keep the handle to be used for disconnection. |
---|
191 | |
---|
192 | var handle = scope.id || "reg_"+(this.__reg++); |
---|
193 | if(!this.registered[handle]){ this.registered[handle] = scope; } |
---|
194 | return handle; // String |
---|
195 | }, |
---|
196 | unregister: function(handle){ |
---|
197 | // summary: |
---|
198 | // Disconnects object. Mouse events are no longer |
---|
199 | // called for it. |
---|
200 | if(!this.registered[handle]){ return; } |
---|
201 | delete this.registered[handle]; |
---|
202 | }, |
---|
203 | |
---|
204 | _broadcastEvent:function(strEvt, obj){ |
---|
205 | // summary: |
---|
206 | // Fire events to all registered objects. |
---|
207 | |
---|
208 | //console.log("mouse.broadcast:", strEvt, obj) |
---|
209 | for(var nm in this.registered){ |
---|
210 | if(this.registered[nm][strEvt]) this.registered[nm][strEvt](obj); |
---|
211 | } |
---|
212 | }, |
---|
213 | |
---|
214 | onDown: function(obj){ |
---|
215 | // summary: |
---|
216 | // Create on[xx]Down event and send to broadcaster. |
---|
217 | // Could be connected to. |
---|
218 | |
---|
219 | //console.info("onDown:", this.eventName("down")) |
---|
220 | this._broadcastEvent(this.eventName("down"), obj); |
---|
221 | }, |
---|
222 | |
---|
223 | onDrag: function(obj){ |
---|
224 | // summary: |
---|
225 | // Create on[xx]Drag event and send to broadcaster. |
---|
226 | // Could be connected to. |
---|
227 | |
---|
228 | var nm = this.eventName("drag"); |
---|
229 | if(this._selected && nm == "onDrag"){ |
---|
230 | nm = "onStencilDrag" |
---|
231 | } |
---|
232 | this._broadcastEvent(nm, obj); |
---|
233 | }, |
---|
234 | |
---|
235 | onMove: function(obj){ |
---|
236 | // summary: |
---|
237 | // Create onMove event and send to broadcaster. |
---|
238 | // Could be connected to. |
---|
239 | // Note: onMove never uses a custom event |
---|
240 | // Note: onMove is currently not enabled in the app. |
---|
241 | |
---|
242 | this._broadcastEvent("onMove", obj); |
---|
243 | }, |
---|
244 | |
---|
245 | overName: function(obj,evt){ |
---|
246 | var nm = obj.id.split("."); |
---|
247 | evt = evt.charAt(0).toUpperCase() + evt.substring(1); |
---|
248 | if(nm[0] == "dojox" && (defaults.clickable || !defaults.clickMode)){ |
---|
249 | return "onStencil"+evt; |
---|
250 | }else{ |
---|
251 | return "on"+evt; |
---|
252 | } |
---|
253 | |
---|
254 | }, |
---|
255 | |
---|
256 | onOver: function(obj){ |
---|
257 | this._broadcastEvent(this.overName(obj,"over"), obj); |
---|
258 | }, |
---|
259 | |
---|
260 | onOut: function(obj){ |
---|
261 | this._broadcastEvent(this.overName(obj,"out"), obj); |
---|
262 | }, |
---|
263 | |
---|
264 | onUp: function(obj){ |
---|
265 | // summary: |
---|
266 | // Create on[xx]Up event and send to broadcaster. |
---|
267 | // Could be connected to. |
---|
268 | |
---|
269 | // blocking first click-off (deselect), largely for TextBlock |
---|
270 | // TODO: should have param to make this optional? |
---|
271 | var nm = this.eventName("up"); |
---|
272 | |
---|
273 | if(nm == "onStencilUp"){ |
---|
274 | this._selected = true; |
---|
275 | }else if(this._selected && nm == "onUp"){ ////////////////////////////////////////// |
---|
276 | nm = "onStencilUp"; |
---|
277 | this._selected = false; |
---|
278 | } |
---|
279 | |
---|
280 | console.info("Up Event:", this.id, nm, "id:", obj.id); |
---|
281 | this._broadcastEvent(nm, obj); |
---|
282 | |
---|
283 | // Silverlight double-click handled in Silverlight class |
---|
284 | if(dojox.gfx.renderer == "silverlight"){ return; } |
---|
285 | |
---|
286 | // Check Double Click |
---|
287 | // If a double click is detected, the onDoubleClick event fires, |
---|
288 | // but does not replace the normal event. They both fire. |
---|
289 | this._clickTime = new Date().getTime(); |
---|
290 | if(this._lastClickTime){ |
---|
291 | if(this._clickTime-this._lastClickTime<this.doublClickSpeed){ |
---|
292 | var dnm = this.eventName("doubleClick"); |
---|
293 | console.warn("DOUBLE CLICK", dnm, obj); |
---|
294 | this._broadcastEvent(dnm, obj); |
---|
295 | }else{ |
---|
296 | //console.log(" slow:", this._clickTime-this._lastClickTime) |
---|
297 | } |
---|
298 | } |
---|
299 | this._lastClickTime = this._clickTime; |
---|
300 | |
---|
301 | }, |
---|
302 | |
---|
303 | zoom: 1, |
---|
304 | setZoom: function(zoom){ |
---|
305 | // summary: |
---|
306 | // Internal. Sets the mouse zoom percentage to |
---|
307 | // that of the canvas |
---|
308 | this.zoom = 1/zoom; |
---|
309 | }, |
---|
310 | |
---|
311 | setEventMode: function(mode){ |
---|
312 | // summary: |
---|
313 | // Sets the mouse mode s that custom events can be called. |
---|
314 | // Also can 'disable' events by using a bogus mode: |
---|
315 | // | mouse.setEventMode("DISABLED") |
---|
316 | // (unless any object subscribes to this event, |
---|
317 | // it is effectively disabled) |
---|
318 | |
---|
319 | this.mode = mode ? "on" + mode.charAt(0).toUpperCase() + mode.substring(1) : ""; |
---|
320 | }, |
---|
321 | |
---|
322 | eventName: function(name){ |
---|
323 | // summary: |
---|
324 | // Internal. Determine the event name |
---|
325 | |
---|
326 | name = name.charAt(0).toUpperCase() + name.substring(1); |
---|
327 | if(this.mode){ |
---|
328 | if(this.mode == "onPathEdit"){ |
---|
329 | return "on"+name; |
---|
330 | } |
---|
331 | if(this.mode == "onUI"){ |
---|
332 | //return "on"+name; |
---|
333 | } |
---|
334 | return this.mode + name; |
---|
335 | }else{ |
---|
336 | //Allow a mode where stencils aren't clickable |
---|
337 | if(!defaults.clickable && defaults.clickMode){return "on"+name;} |
---|
338 | var dt = !this.drawingType || this.drawingType=="surface" || this.drawingType=="canvas" ? "" : this.drawingType; |
---|
339 | var t = !dt ? "" : dt.charAt(0).toUpperCase() + dt.substring(1); |
---|
340 | return "on"+t+name; |
---|
341 | } |
---|
342 | }, |
---|
343 | |
---|
344 | up: function(evt){ |
---|
345 | // summary: |
---|
346 | // Internal. Create onUp event |
---|
347 | |
---|
348 | this.onUp(this.create(evt)); |
---|
349 | }, |
---|
350 | |
---|
351 | down: function(evt){ |
---|
352 | // summary: |
---|
353 | // Internal. Create onDown event |
---|
354 | |
---|
355 | this._downOnCanvas = true; |
---|
356 | var sc = this.scrollOffset(); |
---|
357 | var dim = this._getXY(evt); |
---|
358 | this._lastpagex = dim.x; |
---|
359 | this._lastpagey = dim.y; |
---|
360 | var o = this.origin; |
---|
361 | var x = dim.x - o.x + sc.left; |
---|
362 | var y = dim.y - o.y + sc.top; |
---|
363 | |
---|
364 | var withinCanvas = x>=0 && y>=0 && x<=o.w && y<=o.h; |
---|
365 | x*= this.zoom; |
---|
366 | y*= this.zoom; |
---|
367 | |
---|
368 | o.startx = x; |
---|
369 | o.starty = y; |
---|
370 | this._lastx = x; |
---|
371 | this._lasty = y; |
---|
372 | |
---|
373 | this.drawingType = this.util.attr(evt, "drawingType") || ""; |
---|
374 | var id = this._getId(evt); |
---|
375 | //console.log("DOWN:", this.id, id, withinCanvas); |
---|
376 | //console.log("this.drawingType:", this.drawingType); |
---|
377 | |
---|
378 | if(evt.button == dojo.mouseButtons.RIGHT && this.id == "mse"){ |
---|
379 | //Allow right click events to bubble for context menus |
---|
380 | }else{ |
---|
381 | evt.preventDefault(); |
---|
382 | dojo.stopEvent(evt); |
---|
383 | } |
---|
384 | this.onDown({ |
---|
385 | mid:this.id, |
---|
386 | x:x, |
---|
387 | y:y, |
---|
388 | pageX:dim.x, |
---|
389 | pageY:dim.y, |
---|
390 | withinCanvas:withinCanvas, |
---|
391 | id:id |
---|
392 | }); |
---|
393 | |
---|
394 | }, |
---|
395 | over: function(obj){ |
---|
396 | // summary: |
---|
397 | // Internal. |
---|
398 | |
---|
399 | this.onOver(obj); |
---|
400 | }, |
---|
401 | out: function(obj){ |
---|
402 | // summary: |
---|
403 | // Internal. |
---|
404 | |
---|
405 | this.onOut(obj); |
---|
406 | }, |
---|
407 | move: function(evt){ |
---|
408 | // summary: |
---|
409 | // Internal. |
---|
410 | |
---|
411 | var obj = this.create(evt); |
---|
412 | if(this.id=="MUI"){ |
---|
413 | //console.log("obj.id:", obj.id, "was:", this.currentNodeId) |
---|
414 | } |
---|
415 | if(obj.id != this.currentNodeId){ |
---|
416 | // TODO: I wonder if an ID is good enough |
---|
417 | // that would avoid the mixin |
---|
418 | var outObj = {}; |
---|
419 | for(var nm in obj){ |
---|
420 | outObj[nm] = obj[nm]; |
---|
421 | } |
---|
422 | outObj.id = this.currentNodeId; |
---|
423 | this.currentNodeId && this.out(outObj); |
---|
424 | obj.id && this.over(obj); |
---|
425 | this.currentNodeId = obj.id; |
---|
426 | } |
---|
427 | this.onMove(obj); |
---|
428 | }, |
---|
429 | drag: function(evt){ |
---|
430 | // summary: |
---|
431 | // Internal. Create onDrag event |
---|
432 | this.onDrag(this.create(evt, true)); |
---|
433 | }, |
---|
434 | create: function(evt, squelchErrors){ |
---|
435 | // summary: |
---|
436 | // Internal. Create EventObject |
---|
437 | |
---|
438 | var sc = this.scrollOffset(); |
---|
439 | var dim = this._getXY(evt); |
---|
440 | |
---|
441 | var pagex = dim.x; |
---|
442 | var pagey = dim.y; |
---|
443 | |
---|
444 | var o = this.origin; |
---|
445 | var x = dim.x - o.x + sc.left; |
---|
446 | var y = dim.y - o.y + sc.top; |
---|
447 | |
---|
448 | var withinCanvas = x>=0 && y>=0 && x<=o.w && y<=o.h; |
---|
449 | x*= this.zoom; |
---|
450 | y*= this.zoom; |
---|
451 | |
---|
452 | var id = withinCanvas ? this._getId(evt, squelchErrors) : ""; |
---|
453 | var ret = { |
---|
454 | mid:this.id, |
---|
455 | x:x, |
---|
456 | y:y, |
---|
457 | pageX:dim.x, |
---|
458 | pageY:dim.y, |
---|
459 | page:{ |
---|
460 | x:dim.x, |
---|
461 | y:dim.y |
---|
462 | }, |
---|
463 | orgX:o.x, |
---|
464 | orgY:o.y, |
---|
465 | last:{ |
---|
466 | x: this._lastx, |
---|
467 | y: this._lasty |
---|
468 | }, |
---|
469 | start:{ |
---|
470 | x: this.origin.startx, //+ sc.left, |
---|
471 | y: this.origin.starty //+ sc.top |
---|
472 | }, |
---|
473 | move:{ |
---|
474 | x:pagex - this._lastpagex, |
---|
475 | y:pagey - this._lastpagey |
---|
476 | }, |
---|
477 | scroll:sc, |
---|
478 | id:id, |
---|
479 | withinCanvas:withinCanvas |
---|
480 | }; |
---|
481 | |
---|
482 | //console.warn("MSE LAST:", x-this._lastx, y-this._lasty) |
---|
483 | this._lastx = x; |
---|
484 | this._lasty = y; |
---|
485 | this._lastpagex = pagex; |
---|
486 | this._lastpagey = pagey; |
---|
487 | dojo.stopEvent(evt); |
---|
488 | return ret; //Object |
---|
489 | }, |
---|
490 | _getId: function(evt, squelchErrors){ |
---|
491 | // summary: |
---|
492 | // Internal. Gets ID of focused node. |
---|
493 | return this.util.attr(evt, "id", null, squelchErrors); // String |
---|
494 | }, |
---|
495 | _getXY: function(evt){ |
---|
496 | // summary: |
---|
497 | // Internal. Gets mouse coords to page. |
---|
498 | return {x:evt.pageX, y:evt.pageY}; // Object |
---|
499 | }, |
---|
500 | |
---|
501 | setCursor: function(cursor,/* HTMLNode*/node){ |
---|
502 | // summary: |
---|
503 | // Sets the cursor for a given node. If no |
---|
504 | // node is specified the containing node is used. |
---|
505 | if(!node){ |
---|
506 | dojo.style(this.container, "cursor", cursor); |
---|
507 | }else{ |
---|
508 | dojo.style(node, "cursor", cursor); |
---|
509 | } |
---|
510 | } |
---|
511 | } |
---|
512 | ); |
---|
513 | }); |
---|