source: Dev/branches/rest-dojo-ui/client/dojox/drawing/manager/Stencil.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

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