source: Dev/trunk/src/client/dojox/drawing/manager/Stencil.js @ 532

Last change on this file since 532 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

  • Property svn:executable set to *
File size: 16.2 KB
Line 
1define(["dojo", "../util/oo", "../defaults"],
2function(dojo, oo, defaults){
3
4        var surface, surfaceNode;
5        //dojox.drawing.manager.Stencil =
6        return oo.declare(
7                function(options){
8
9                        // TODO: mixin props
10
11                        surface = options.surface;
12                        this.canvas = options.canvas;
13                       
14                        //this.defaults = defaults.copy();
15                        this.undo = options.undo;
16                        this.mouse = options.mouse;
17                        this.keys = options.keys;
18                        this.anchors = options.anchors;
19                        this.stencils = {};
20                        this.selectedStencils = {};
21                        this._mouseHandle = this.mouse.register(this);
22                       
23                        dojo.connect(this.keys, "onArrow", this, "onArrow");
24                        dojo.connect(this.keys, "onEsc", this, "deselect");
25                        dojo.connect(this.keys, "onDelete", this, "onDelete");
26                       
27                },
28                {
29                        // summary:
30                        //              The main class for tracking Stencils that are cretaed, added,
31                        //              selected, or deleted. Also handles selections, multiple
32                        //              selections, adding and removing from selections, and dragging
33                        //              selections. It's this class that triggers the anchors to
34                        //              appear on a Stencil and whther there are anchor on a multiple
35                        //              select or not (currently not)
36
37                        _dragBegun: false,
38                        _wasDragged:false,
39                        _secondClick:false,
40                        _isBusy:false,
41                       
42                        setRecentStencil: function(stencil){
43                                // summary:
44                                //              Keeps track of the most recent stencil interacted
45                                //              with, whether created or selected.
46                                this.recent = stencil;
47                        },
48                       
49                        getRecentStencil: function(){
50                                // summary:
51                                //              Returns the stencil most recently interacted
52                                //              with whether it's last created or last selected
53                                return this.recent;
54                        },
55                       
56                        register: function(/*Object*/stencil){
57                                // summary:
58                                //              Key method for adding Stencils. Stencils
59                                //              can be added to the canvas without adding
60                                //              them to this, but they won't have selection
61                                //              or drag ability.
62
63                                console.log("Selection.register ::::::", stencil.id);
64                                if(stencil.isText && !stencil.editMode && stencil.deleteEmptyCreate && !stencil.getText()){
65                                        // created empty text field
66                                        // defaults say to delete
67                                        console.warn("EMPTY CREATE DELETE", stencil);
68                                        stencil.destroy();
69                                        return false;
70                                }
71                               
72                                this.stencils[stencil.id] = stencil;
73                                this.setRecentStencil(stencil);
74                               
75                                if(stencil.execText){
76                                        if(stencil._text && !stencil.editMode){
77                                                console.log("select text");
78                                                this.selectItem(stencil);
79                                        }
80                                        stencil.connect("execText", this, function(){
81                                                if(stencil.isText && stencil.deleteEmptyModify && !stencil.getText()){
82                                                        console.warn("EMPTY MOD DELETE", stencil);
83                                                        // text deleted
84                                                        // defaults say to delete
85                                                        this.deleteItem(stencil);
86                                                }else if(stencil.selectOnExec){
87                                                        this.selectItem(stencil);
88                                                }
89                                        });
90                                }
91                               
92                                stencil.connect("deselect", this, function(){
93                                        if(!this._isBusy && this.isSelected(stencil)){
94                                                // called from within stencil. do action.
95                                                this.deselectItem(stencil);
96                                        }
97                                });
98                               
99                                stencil.connect("select", this, function(){
100                                        if(!this._isBusy && !this.isSelected(stencil)){
101                                                // called from within stencil. do action.
102                                                this.selectItem(stencil);
103                                        }
104                                });
105                               
106                                return stencil;
107                        },
108                        unregister: function(/*Object*/stencil){
109                                // summary:
110                                //              Method for removing Stencils from the manager.
111                                //              This doesn't delete them, only removes them from
112                                //              the list.
113
114                                console.log("Selection.unregister ::::::", stencil.id, "sel:", stencil.selected);
115                                if(stencil){
116                                        stencil.selected && this.onDeselect(stencil);
117                                        delete this.stencils[stencil.id];
118                                }
119                        },
120                       
121                        onArrow: function(/*Key Event*/evt){
122                                // summary:
123                                //              Moves selection based on keyboard arrow keys
124
125                                // FIXME: Check constraints
126                                if(this.hasSelected()){
127                                        this.saveThrottledState();
128                                        this.group.applyTransform({dx:evt.x, dy: evt.y});
129                                }
130                        },
131                       
132                        _throttleVrl:null,
133                        _throttle: false,
134                        throttleTime:400,
135                        _lastmxx:-1,
136                        _lastmxy:-1,
137                        saveMoveState: function(){
138                                // summary:
139                                //              Internal. Used for the prototype undo stack.
140                                //              Saves selection position.
141
142                                var mx = this.group.getTransform();
143                                if(mx.dx == this._lastmxx && mx.dy == this._lastmxy){ return; }
144                                this._lastmxx = mx.dx;
145                                this._lastmxy = mx.dy;
146                                //console.warn("SAVE MOVE!", mx.dx, mx.dy);
147                                this.undo.add({
148                                        before:dojo.hitch(this.group, "setTransform", mx)
149                                });
150                        },
151                       
152                        saveThrottledState: function(){
153                                // summary:
154                                //              Internal. Used for the prototype undo stack.
155                                //              Prevents an undo point on every mouse move.
156                                //              Only does a point when the mouse hesitates.
157
158                                clearTimeout(this._throttleVrl);
159                                clearInterval(this._throttleVrl);
160                                this._throttleVrl = setTimeout(dojo.hitch(this, function(){
161                                        this._throttle = false;
162                                        this.saveMoveState();
163                                }), this.throttleTime);
164                                if(this._throttle){ return; }
165                                this._throttle = true;
166                               
167                                this.saveMoveState();
168                               
169                        },
170                        unDelete: function(/*Array*/stencils){
171                                // summary:
172                                //              Undeletes a stencil. Used in undo stack.
173
174                                console.log("unDelete:", stencils);
175                                for(var s in stencils){
176                                        stencils[s].render();
177                                        this.onSelect(stencils[s]);
178                                }
179                        },
180                        onDelete: function(/*Boolean*/noundo){
181                                // summary:
182                                //              Event fired on deletion of a stencil
183
184                                console.log("Stencil onDelete", noundo);
185                                if(noundo!==true){
186                                        this.undo.add({
187                                                before:dojo.hitch(this, "unDelete", this.selectedStencils),
188                                                after:dojo.hitch(this, "onDelete", true)
189                                        });
190                                }
191                                this.withSelected(function(m){
192                                        this.anchors.remove(m);
193                                        var id = m.id;
194                                        console.log("delete:", m);
195                                        m.destroy();
196                                        delete this.stencils[id];
197                                });
198                                this.selectedStencils = {};
199                        },
200                       
201                        deleteItem: function(/*Object*/stencil){
202                                // summary:
203                                //              Deletes a stencil.
204                                //              NOTE: supports limited undo.
205
206                                // manipulating the selection to fire onDelete properly
207                                if(this.hasSelected()){
208                                        // there is a selection
209                                        var sids = [];
210                                        for(var m in this.selectedStencils){
211                                                if(this.selectedStencils.id == stencil.id){
212                                                        if(this.hasSelected()==1){
213                                                                // the deleting stencil is the only one selected
214                                                                this.onDelete();
215                                                                return;
216                                                        }
217                                                }else{
218                                                        sids.push(this.selectedStencils.id);
219                                                }
220                                        }
221                                        // remove selection, delete, restore selection
222                                        this.deselect();
223                                        this.selectItem(stencil);
224                                        this.onDelete();
225                                        dojo.forEach(sids, function(id){
226                                                this.selectItem(id);
227                                        }, this);
228                                }else{
229                                        // there is not a selection. select it, delete it
230                                        this.selectItem(stencil);
231                                        // now delete selection
232                                        this.onDelete();
233                                }
234                        },
235                       
236                        removeAll: function(){
237                                // summary:
238                                //              Deletes all Stencils on the canvas.
239                               
240                                this.selectAll();
241                                this._isBusy = true;
242                                this.onDelete();
243                                this.stencils = {};
244                                this._isBusy = false;
245                        },
246                       
247                        setSelectionGroup: function(){
248                                // summary:
249                                //              Internal. Creates a new selection group
250                                //              used to hold selected stencils.
251
252                                this.withSelected(function(m){
253                                        this.onDeselect(m, true);
254                                });
255                               
256                                if(this.group){
257                                        surface.remove(this.group);
258                                        this.group.removeShape();
259                                }
260                                this.group = surface.createGroup();
261                                this.group.setTransform({dx:0, dy: 0});
262                               
263                                this.withSelected(function(m){
264                                        this.group.add(m.container);
265                                        m.select();
266                                });
267                        },
268                       
269                        setConstraint: function(){
270                                // summary:
271                                //              Internal. Gets all selected stencils' coordinates
272                                //              and determines how far left and up the selection
273                                //              can go without going below zero
274
275                                var t = Infinity, l = Infinity;
276                                this.withSelected(function(m){
277                                        var o = m.getBounds();
278                                        t = Math.min(o.y1, t);
279                                        l = Math.min(o.x1, l);
280                                });
281                                this.constrain = {l:-l, t:-t};
282                        },
283                       
284                       
285                       
286                        onDeselect: function(stencil, keepObject){
287                                // summary:
288                                //              Event fired on deselection of a stencil
289
290                                if(!keepObject){
291                                        delete this.selectedStencils[stencil.id];
292                                }
293                                //console.log('onDeselect, keep:', keepObject, "stencil:", stencil.type)
294                               
295                                this.anchors.remove(stencil);
296                               
297                                surface.add(stencil.container);
298                                stencil.selected && stencil.deselect();
299                                stencil.applyTransform(this.group.getTransform());
300                        },
301                       
302                        deselectItem: function(/*Object*/stencil){
303                                // summary:
304                                //              Deselect passed stencil
305
306                                // note: just keeping with standardized methods
307                                this.onDeselect(stencil);
308                        },
309                       
310                        deselect: function(){ // all stencils
311                                // summary:
312                                //              Deselect all stencils
313
314                                this.withSelected(function(m){
315                                        this.onDeselect(m);
316                                });
317                                this._dragBegun = false;
318                                this._wasDragged = false;
319                        },
320                       
321                        onSelect: function(/*Object*/stencil){
322                                // summary:
323                                //              Event fired on selection of a stencil
324
325                                //console.log("stencil.onSelect", stencil);
326                                if(!stencil){
327                                        console.error("null stencil is not selected:", this.stencils)
328                                }
329                                if(this.selectedStencils[stencil.id]){ return; }
330                                this.selectedStencils[stencil.id] = stencil;
331                                this.group.add(stencil.container);
332                                stencil.select();
333                                if(this.hasSelected()==1){
334                                        this.anchors.add(stencil, this.group);
335                                }
336                        },
337                       
338                        selectAll: function(){
339                                // summary:
340                                //              Selects all items
341                                this._isBusy = true;
342                                for(var m in this.stencils){
343                                        //if(!this.stencils[m].selected){
344                                                this.selectItem(m);
345                                        //}
346                                }
347                                this._isBusy = false;
348                        },
349                       
350                        selectItem: function(/*String|Object*/ idOrItem){
351                                // summary:
352                                //              Method used to select a stencil.
353
354                                var id = typeof(idOrItem)=="string" ? idOrItem : idOrItem.id;
355                                var stencil = this.stencils[id];
356                                this.setSelectionGroup();
357                                this.onSelect(stencil);
358                                this.group.moveToFront();
359                                this.setConstraint();
360                        },
361                       
362                        onLabelDoubleClick: function(/*EventObject*/obj){
363                                // summary:
364                                //              Event to connect a textbox to
365                                //              for label edits
366                                console.info("mgr.onLabelDoubleClick:", obj);
367                                if(this.selectedStencils[obj.id]){
368                                        this.deselect();
369                                }
370                        },
371                       
372                        onStencilDoubleClick: function(/*EventObject*/obj){
373                                // summary:
374                                //              Event fired on the double-click of a stencil
375
376                                console.info("mgr.onStencilDoubleClick:", obj);
377                                if(this.selectedStencils[obj.id]){
378                                        if(this.selectedStencils[obj.id].edit){
379                                                console.info("Mgr Stencil Edit -> ", this.selectedStencils[obj.id]);
380                                                var m = this.selectedStencils[obj.id];
381                                                // deselect must happen first to set the transform
382                                                // then edit knows where to set the text box
383                                                m.editMode = true;
384                                                this.deselect();
385                                                m.edit();
386                                        }
387                                }
388                               
389                        },
390                       
391                        onAnchorUp: function(){
392                                // summary:
393                                //              Event fire on mouseup off of an anchor point
394                                this.setConstraint();
395                        },
396                       
397                        onStencilDown: function(/*EventObject*/obj, evt){
398                                // summary:
399                                //              Event fired on mousedown on a stencil
400
401                                console.info(" >>> onStencilDown:", obj.id, this.keys.meta);
402                                if(!this.stencils[obj.id]){ return; }
403                                this.setRecentStencil(this.stencils[obj.id]);
404                                this._isBusy = true;
405                               
406                               
407                                if(this.selectedStencils[obj.id] && this.keys.meta){
408                                        if(dojo.isMac && this.keys.cmmd){
409                                                // block context menu
410                                               
411                                        }
412                                        console.log("    shift remove");
413                                        this.onDeselect(this.selectedStencils[obj.id]);
414                                        if(this.hasSelected()==1){
415                                                this.withSelected(function(m){
416                                                        this.anchors.add(m, this.group);
417                                                });
418                                        }
419                                        this.group.moveToFront();
420                                        this.setConstraint();
421                                        return;
422                               
423                                }else if(this.selectedStencils[obj.id]){
424                                        console.log("    clicked on selected");
425                                        // clicking on same selected item(s)
426                                        // RESET OFFSETS
427                                        var mx = this.group.getTransform();
428                                        this._offx = obj.x - mx.dx;
429                                        this._offy = obj.y - mx.dy;
430                                        return;
431                               
432                                }else if(!this.keys.meta){
433                                       
434                                        console.log("    deselect all");
435                                        this.deselect();
436                               
437                                }else{
438                                        // meta-key add
439                                        //console.log("reset sel and add stencil")
440                                }
441                                console.log("    add stencil to selection");
442                                // add a stencil
443                                this.selectItem(obj.id);
444                               
445                                mx = this.group.getTransform();
446                                this._offx = obj.x - mx.dx;
447                                this._offy = obj.y - mx.dx;
448                               
449                                this.orgx = obj.x;
450                                this.orgy = obj.y;
451                               
452                                this._isBusy = false;
453                               
454                                // TODO:
455                                //      dojo.style(surfaceNode, "cursor", "pointer");
456                               
457                                // TODO:
458                                this.undo.add({
459                                        before:function(){
460                                               
461                                        },
462                                        after: function(){
463                                               
464                                        }
465                                });
466                        },
467                       
468                        onLabelDown: function(/*EventObject*/obj, evt){
469                                // summary:
470                                //              Event fired on mousedown of a stencil's label
471                                //              Because it's an annotation the id will be the
472                                //              master stencil.
473                               
474                                //console.info("===============>>>Label click: ",obj, " evt: ",evt);
475                                this.onStencilDown(obj,evt);
476                        },
477                       
478                        onStencilUp: function(/*EventObject*/obj){
479                                // summary:
480                                //              Event fired on mouseup off of a stencil
481
482                        },
483                       
484                        onLabelUp: function(/*EventObject*/obj){
485                                this.onStencilUp(obj);
486                        },
487                       
488                        onStencilDrag: function(/*EventObject*/obj){
489                                // summary:
490                                //              Event fired on every mousemove of a stencil drag
491
492                                if(!this._dragBegun){
493                                        // bug, in FF anyway - first mouse move shows x=0
494                                        // the 'else' fixes it
495                                        this.onBeginDrag(obj);
496                                        this._dragBegun = true;
497                                }else{
498                                        this.saveThrottledState();
499                                       
500                                        var x = obj.x - obj.last.x,
501                                                y = obj.y - obj.last.y,
502                                                c = this.constrain,
503                                                mz = defaults.anchors.marginZero;
504                                       
505                                       
506                                        x = obj.x - this._offx;
507                                        y = obj.y - this._offy;
508                                       
509                                        if(x < c.l + mz){
510                                                x = c.l + mz;
511                                        }
512                                        if(y < c.t + mz){
513                                                y = c.t + mz;
514                                        }
515                                       
516                                        this.group.setTransform({
517                                                dx: x,
518                                                dy: y
519                                        });
520                                       
521                                       
522                                }
523                        },
524                       
525                        onLabelDrag: function(/*EventObject*/obj){
526                                this.onStencilDrag(obj);
527                        },
528                       
529                        onDragEnd: function(/*EventObject*/obj){
530                                // summary:
531                                //              Event fired at the end of a stencil drag
532
533                                this._dragBegun = false;
534                        },
535                        onBeginDrag: function(/*EventObject*/obj){
536                                // summary:
537                                //              Event fired at the beginning of a stencil drag
538
539                                this._wasDragged = true;
540                        },
541                       
542                        onDown: function(/*EventObject*/obj){
543                                // summary:
544                                //              Event fired on mousedown on the canvas
545
546                                this.deselect();
547                        },
548                                               
549                       
550                        onStencilOver: function(obj){
551                                // summary:
552                                //              This changes the cursor when hovering over
553                                //              a selectable stencil.
554                               
555                                //console.log("OVER")
556                                dojo.style(obj.id, "cursor", "move");
557                        },
558
559                        onStencilOut: function(obj){
560                                // summary:
561                                //              This restores the cursor.
562                               
563                                //console.log("OUT")
564                                dojo.style(obj.id, "cursor", "crosshair");
565                        },
566                       
567                        exporter: function(){
568                                // summary:
569                                //              Collects all Stencil data and returns an
570                                //              Array of objects.
571                                var items = [];
572                                for(var m in this.stencils){
573                                        this.stencils[m].enabled && items.push(this.stencils[m].exporter());
574                                }
575                                return items; // Array
576                        },
577                       
578                        listStencils: function(){
579                                return this.stencils;
580                        },
581                       
582                        toSelected: function(/*String*/func){
583                                // summary:
584                                //              Convenience function calls function *within*
585                                //              all selected stencils
586                                var args = Array.prototype.slice.call(arguments).splice(1);
587                                for(var m in this.selectedStencils){
588                                        var item = this.selectedStencils[m];
589                                        item[func].apply(item, args);
590                                }
591                        },
592                       
593                        withSelected: function(/*Function*/func){
594                                // summary:
595                                //              Convenience function calls function on
596                                //              all selected stencils
597                                var f = dojo.hitch(this, func);
598                                for(var m in this.selectedStencils){
599                                        f(this.selectedStencils[m]);
600                                }
601                        },
602                       
603                        withUnselected: function(/*Function*/func){
604                                // summary:
605                                //              Convenience function calls function on
606                                //              all stencils that are not selected
607                                var f = dojo.hitch(this, func);
608                                for(var m in this.stencils){
609                                        !this.stencils[m].selected && f(this.stencils[m]);
610                                }
611                        },
612                       
613                        withStencils: function(/*Function*/func){
614                                // summary:
615                                //              Convenience function calls function on
616                                //              all stencils
617                                var f = dojo.hitch(this, func);
618                                for(var m in this.stencils){
619                                        f(this.stencils[m]);
620                                }
621                        },
622                       
623                        hasSelected: function(){
624                                // summary:
625                                //              Returns number of selected (generally used
626                                //              as truthy or falsey)
627
628                                // FIXME: should be areSelected?
629                                var ln = 0;
630                                for(var m in this.selectedStencils){ ln++; }
631                                return ln; // Number
632                        },
633                       
634                        isSelected: function(/*Object*/stencil){
635                                // summary:
636                                //              Returns if passed stencil is selected or not
637                                //              based on internal collection, not on stencil
638                                //              boolean
639                                return !!this.selectedStencils[stencil.id]; // Boolean
640                        }
641                }
642               
643        );
644});
Note: See TracBrowser for help on using the repository browser.