source: Dev/trunk/src/client/dojox/sketch/Figure.js @ 532

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

Added Dojo 1.9.3 release.

File size: 14.3 KB
Line 
1define([
2        "dojo/_base/kernel",
3        "dojo/_base/lang",
4        "dojo/_base/connect",
5        "dojo/_base/html",
6        "../gfx",
7        "../xml/DomParser",
8        "./UndoStack"
9], function(dojo){
10        dojo.experimental("dojox.sketch");
11
12        var ta=dojox.sketch;
13        ta.tools={};
14        ta.registerTool=function(type, fn){ ta.tools[type]=fn; };
15        ta.Figure = function(mixin){
16                var self=this;
17                this.annCounter=1;
18
19                this.shapes=[];
20                this.image=null;
21                this.imageSrc=null;
22                this.size={ w:0, h:0 };
23                this.surface=null;
24                this.group=null;
25                //node should have tabindex set, otherwise keyboard action does not work
26                this.node=null;
27
28                this.zoomFactor=1;      //      multiplier for zooming.
29               
30                this.tools=null;        //      toolbar reference.
31
32                this.obj={};            //      lookup table for shapes.  Not keen on this solution.
33
34                dojo.mixin(this,mixin);
35
36                //      what is selected.
37                this.selected=[];
38                this.hasSelections=function(){ return this.selected.length>0 };
39                this.isSelected=function(obj){
40                        for(var i=0; i<self.selected.length; i++){
41                                if(self.selected[i]==obj){ return true; }
42                        }
43                        return false;
44                };
45                this.select=function(obj){
46                        //      TODO: deal with multiple vs. single selections.
47                        if(!self.isSelected(obj)){
48                                //      force single select
49                                self.clearSelections();
50                                self.selected=[ obj ];
51                        }
52                        //      force a bbox update, regardless
53                        obj.setMode(ta.Annotation.Modes.View);
54                        obj.setMode(ta.Annotation.Modes.Edit);
55                };
56                this.deselect=function(obj){
57                        var idx=-1;
58                        for(var i=0; i<self.selected.length; i++){
59                                if(self.selected[i]==obj){
60                                        idx=i;
61                                        break;
62                                }
63                        }
64                        if(idx>-1){
65                                obj.setMode(ta.Annotation.Modes.View);
66                                self.selected.splice(idx,1);
67                        }
68                        return obj;
69                };
70                this.clearSelections=function(){
71                        for(var i=0; i<self.selected.length; i++){
72                                self.selected[i].setMode(ta.Annotation.Modes.View);
73                        }
74                        self.selected=[];
75                };
76                this.replaceSelection=function(n, o){
77                        if(!self.isSelected(o)){
78                                self.select(n);
79                                return;
80                        }
81                        var idx=-1;
82                        for(var i=0; i<self.selected.length; i++){
83                                if(self.selected[i]==o){
84                                        idx=i;
85                                        break;
86                                }
87                        }
88                        if(idx>-1){
89                                self.selected.splice(idx,1,n);
90                        }
91                };
92
93                //      for the drag and drop handlers.
94                this._c=null;           //      current shape
95                this._ctr=null; //      container measurements
96                this._lp=null;          //      last position
97                this._action=null;
98                this._prevState=null;
99                this._startPoint=null;  //      test to record a move.
100
101                //      if an object isn't selected and we're dragging anyways.
102                this._ctool=null;       //      hard code it.
103                this._start=null;
104                this._end=null;
105                this._absEnd=null;
106                this._cshape=null;
107
108                this._dblclick=function(e){
109                        var o=self._fromEvt(e);
110                        if(o){
111                                self.onDblClickShape(o,e);
112                        }
113                };
114
115                this._keydown=function(e){
116                        var prevent=false;
117                        if(e.ctrlKey){
118                                if(e.keyCode===90 || e.keyCode===122){ //ctrl+z
119                                        self.undo();
120                                        prevent = true;
121                                }
122                                else if(e.keyCode===89 || e.keyCode===121){ //ctrl+y
123                                        self.redo();
124                                        prevent = true;
125                                }
126                        }
127
128                        if(e.keyCode===46 || e.keyCode===8){ //delete or backspace
129                                self._delete(self.selected);
130                                prevent = true;
131                        }
132
133                        if(prevent){
134                                dojo.stopEvent(e);
135                        }
136                };
137       
138                //      drag handlers.
139                this._md=function(e){
140                        //in IE, when clicking into the drawing canvas, the node does not get focused,
141                        //do it manually here to force it, otherwise the keydown event listener is
142                        //never triggered in IE.
143                        if(dojox.gfx.renderer=='vml'){
144                                self.node.focus();
145                        }
146                        var o=self._fromEvt(e);
147                        self._startPoint={ x:e.pageX, y:e.pageY };
148
149                        self._ctr=dojo.position(self.node);
150                        //      figure out the coordinates taking scroll into account
151                        var scroll={x:self.node.scrollLeft,y:self.node.scrollTop};
152                        //var win = dojo.window.get(self.node.ownerDocument);
153                        //var scroll=dojo.withGlobal(win,dojo._docScroll);
154                        self._ctr={x:self._ctr.x-scroll.x, y:self._ctr.y-scroll.y};
155                        var X=e.clientX-self._ctr.x, Y=e.clientY-self._ctr.y;
156                        self._lp={ x:X, y:Y };
157
158                        //      capture it separately
159                        self._start={ x:X, y:Y };
160                        self._end={ x:X, y:Y };
161                        self._absEnd={ x:X, y:Y };
162                        if(!o){
163                                self.clearSelections();
164                                self._ctool.onMouseDown(e);
165                        }else{
166                                if(o.type && o.type()!="Anchor"){
167                                        if(!self.isSelected(o)){
168                                                self.select(o);
169                                                self._sameShapeSelected=false;
170                                        }else{
171                                                self._sameShapeSelected=true;
172                                        }
173                                }
174                                o.beginEdit();
175                                self._c=o;
176                        }
177                };
178                this._mm=function(e){
179                        if(!self._ctr){ return; }
180                        if(self._c && !self._c.shape){
181                                //the selected item is gone, cancel editing mode
182                                self._clearMouse();
183                                return;
184                        }
185                        var x=e.clientX-self._ctr.x;
186                        var y=e.clientY-self._ctr.y;
187                        var dx=x-self._lp.x;
188                        var dy=y-self._lp.y;
189                        self._absEnd={x:x, y:y};
190                        if(self._c){
191                                //self._c.doChange({dx:dx, dy:dy});
192                                self._c.setBinding({dx:dx/self.zoomFactor, dy:dy/self.zoomFactor});
193                                self._lp={x:x, y:y};
194                        } else {
195                                self._end={x:dx, y:dy};
196                                var rect={
197                                        x:Math.min(self._start.x,self._absEnd.x),
198                                        y:Math.min(self._start.y,self._absEnd.y),
199                                        width:Math.abs(self._start.x-self._absEnd.x),
200                                        height:Math.abs(self._start.y-self._absEnd.y)
201                                }
202                                if(rect.width && rect.height){
203                                        self._ctool.onMouseMove(e,rect);
204                                }
205                        }
206                };
207                this._mu=function(e){
208                        if(self._c){
209                                //      record the event.
210                                if(self._c.shape){
211                                        self._c.endEdit(); //make sure it's not deleted
212                                }
213                        }else{
214                                self._ctool.onMouseUp(e);
215                        }
216                        self._clearMouse();
217                };
218                this._clearMouse=function(){
219                        //      clear the stuff out.
220                        self._c=self._ctr=self._lp=self._action=self._prevState=self._startPoint=null;
221                        self._cshape=self._start=self._end=self._absEnd=null;
222                };
223
224                this.initUndoStack();
225        };
226
227        var p=ta.Figure.prototype;
228        p.initUndoStack=function(){
229                this.history=new ta.UndoStack(this);
230        };
231        p.setTool=function(/*dojox.sketch._Plugin*/t){
232                this._ctool=t;
233        };
234    //gridSize: int
235    //          if it is greater than 0, all new shapes placed on the drawing will have coordinates
236    //          snapped to the gridSize. For example, if gridSize is set to 10, all coordinates
237    //          (only including coordinates which specifies the x/y position of shape are affected
238    //          by this parameter) will be dividable by 10
239    p.gridSize=0;
240    p._calCol=function(v){
241        return this.gridSize?(Math.round(v/this.gridSize)*this.gridSize):v;
242    };
243        p._delete=function(arr,noundo){
244                for(var i=0; i<arr.length; i++){
245                        //var before=arr[i].serialize();
246                        arr[i].setMode(ta.Annotation.Modes.View);
247                        arr[i].destroy(noundo);
248                        this.remove(arr[i]);
249                        this._remove(arr[i]);
250                        if(!noundo){
251                                arr[i].onRemove();
252                        }
253                }
254                arr.splice(0,arr.length);
255        };
256        p.onDblClickShape=function(shape,e){
257                if(shape['onDblClick']){
258                        shape.onDblClick(e);
259                }
260        };
261
262        p.onCreateShape=function(shape){};
263        p.onBeforeCreateShape=function(shape){};
264        p.initialize=function(node){
265                this.node=node;
266                this.surface=dojox.gfx.createSurface(node, this.size.w, this.size.h);
267                //this.backgroundRect=this.surface.createRect({ x:0, y:0, width:this.size.w, height:this.size.h })
268                //      .setFill("white");
269                this.group=this.surface.createGroup();
270
271                this._cons=[];
272                var es=this.surface.getEventSource();
273                this._cons.push(
274                        //      kill any dragging events.
275                        //              for FF
276                        dojo.connect(es, "ondraggesture", dojo.stopEvent),
277                        dojo.connect(es, "ondragenter", dojo.stopEvent),
278                        dojo.connect(es, "ondragover", dojo.stopEvent),
279                        dojo.connect(es, "ondragexit", dojo.stopEvent),
280                        dojo.connect(es, "ondragstart", dojo.stopEvent),
281                        //              for IE
282                        dojo.connect(es, "onselectstart", dojo.stopEvent),
283                        //      hook up the drag system.
284                        dojo.connect(es, 'onmousedown', this._md),
285                        dojo.connect(es, 'onmousemove', this._mm),
286                        dojo.connect(es, 'onmouseup', this._mu),
287                        // misc hooks
288                        dojo.connect(es, 'onclick', this, 'onClick'),
289                        dojo.connect(es, 'ondblclick', this._dblclick),
290                        dojo.connect(node, 'onkeydown', this._keydown));
291               
292                this.image=this.group.createImage({ width:this.imageSize.w, height:this.imageSize.h, src:this.imageSrc });
293        };
294
295        p.destroy=function(isLoading){
296                if(!this.node){ return; }
297                if(!isLoading){
298                        if(this.history){ this.history.destroy(); }
299                        if(this._subscribed){
300                                dojo.unsubscribe(this._subscribed);
301                                delete this._subscribed;
302                        }
303                }
304                dojo.forEach(this._cons, dojo.disconnect);
305                this._cons=[];
306
307                //TODO: how to destroy a surface properly?
308                dojo.empty(this.node);
309
310                //this.node.removeChild(this.surface.getEventSource());
311                this.group=this.surface=null;
312                this.obj={};
313                this.shapes=[];
314        };
315        p.nextKey=function(){ return "annotation-"+this.annCounter++; };
316        p.draw=function(){ };
317        p.zoom=function(pct){
318                //      first get the new dimensions
319                this.zoomFactor=pct/100;
320                var w=this.size.w*this.zoomFactor;
321                var h=this.size.h*this.zoomFactor;
322                this.surface.setDimensions(w, h);
323                //      then scale it.
324                this.group.setTransform(dojox.gfx.matrix.scale(this.zoomFactor, this.zoomFactor));
325
326                for(var i=0; i<this.shapes.length; i++){
327                        this.shapes[i].zoom(this.zoomFactor);
328                }
329        };
330        p.getFit=function(){
331                //      assume fitting the parent node.
332//              var box=dojo.html.getContentBox(this.node.parentNode);
333                //the following should work under IE and FF, not sure about others though
334                var wF=(this.node.parentNode.offsetWidth-5)/this.size.w;
335                var hF=(this.node.parentNode.offsetHeight-5)/this.size.h;
336                return Math.min(wF, hF)*100;
337        };
338        p.unzoom=function(){
339                //      restore original size.
340                this.zoomFactor=1;
341                this.surface.setDimensions(this.size.w, this.size.h);
342                this.group.setTransform();
343        };
344
345        //      object registry for drag handling.
346        p._add=function(obj){ this.obj[obj._key]=obj; };
347        p._remove=function(obj){
348                if(this.obj[obj._key]){
349                        delete this.obj[obj._key];
350                }
351        };
352        p._get=function(key){
353                if(key&&key.indexOf("bounding")>-1){
354                        key=key.replace("-boundingBox","");
355                }else if(key&&key.indexOf("-labelShape")>-1){
356                        key=key.replace("-labelShape","");
357                }
358                return this.obj[key];
359        };
360        p._keyFromEvt=function(e){
361                var key=e.target.id+"";
362                if(key.length==0){
363                        //      ancestor tree until you get to the end (meaning this.surface)
364                        var p=e.target.parentNode;
365                        var node=this.surface.getEventSource();
366                        while(p && p.id.length==0 && p!=node){
367                                p=p.parentNode;
368                        }
369                        key=p.id;
370                }
371                return key;
372        };
373        p._fromEvt=function(e){
374                return this._get(this._keyFromEvt(e));
375        };
376
377        p.add=function(annotation){
378                for(var i=0; i<this.shapes.length; i++){
379                        if(this.shapes[i]==annotation){ return true; }
380                }
381                this.shapes.push(annotation);
382                return true;
383        };
384        p.remove=function(annotation){
385                var idx=-1;
386                for(var i=0; i<this.shapes.length; i++){
387                        if(this.shapes[i]==annotation){
388                                idx=i;
389                                break;
390                        }
391                }
392                if(idx>-1){ this.shapes.splice(idx, 1); }
393                return annotation;
394        };
395        p.getAnnotator=function(id){
396                for(var i=0; i<this.shapes.length; i++){
397                        if(this.shapes[i].id==id) {
398                                return this.shapes[i];
399                        }
400                }
401                return null;
402        };
403
404        p.convert=function(ann, t){
405                //      convert an existing annotation to a different kind of annotation
406                var ctor=t+"Annotation";
407                if(!ta[ctor]){ return; }
408                var type=ann.type(), id=ann.id, label=ann.label, mode=ann.mode, tokenId=ann.tokenId;
409                var start, end, control, transform;
410                switch(type){
411                        case "Preexisting":
412                        case "Lead":{
413                                transform={dx:ann.transform.dx, dy:ann.transform.dy };
414                                start={x:ann.start.x, y:ann.start.y};
415                                end={x:ann.end.x, y:ann.end.y };
416                                var cx=end.x-((end.x-start.x)/2);
417                                var cy=end.y-((end.y-start.y)/2);
418                                control={x:cx, y:cy};
419                                break;
420                        }
421                        case "SingleArrow":
422                        case "DoubleArrow":{
423                                transform={dx:ann.transform.dx, dy:ann.transform.dy };
424                                start={x:ann.start.x, y:ann.start.y};
425                                end={x:ann.end.x, y:ann.end.y };
426                                control={x:ann.control.x, y:ann.control.y};
427                                break;
428                        }
429                        case "Underline":{
430                                transform={dx:ann.transform.dx, dy:ann.transform.dy };
431                                start={x:ann.start.x, y:ann.start.y};
432                                control={x:start.x+50, y:start.y+50 };
433                                end={x:start.x+100, y:start.y+100 };
434                                break;
435                        }
436                        case "Brace":{ }
437                }
438                var n=new ta[ctor](this, id);
439
440                if(n.type()=="Underline"){
441                        //      special handling, since we never move the start point.
442                        n.transform={dx:transform.dx+start.x, dy:transform.dy+start.y };
443                } else {
444                        if(n.transform){ n.transform=transform; }
445                        if(n.start){ n.start=start; }
446                }
447                if(n.end){ n.end=end; }
448                if(n.control){ n.control=control; }
449                n.label=label;
450                n.token=dojo.lang.shallowCopy(ann.token);
451                n.initialize();
452
453                this.replaceSelection(n, ann);
454                this._remove(ann);
455                this.remove(ann);
456                ann.destroy();
457
458                //      this should do all the things we need it to do for getting it registered.
459                n.setMode(mode);
460        };
461        p.setValue=function(text){
462                var obj=dojox.xml.DomParser.parse(text);
463                var node=this.node;
464                this.load(obj,node);
465                //this.zoom(this.zoomFactor*100); //zoom to orignal scale
466        };
467        p.load=function(obj, n){
468                //      create from pseudo-DOM
469                if(this.surface){ this.destroy(true); }
470                var node=obj.documentElement;   //      should be either the document or the docElement
471                this.size={
472                        w:parseFloat(node.getAttribute('width'),10),
473                        h:parseFloat(node.getAttribute('height'),10)
474                };
475                var g=node.childrenByName("g")[0];
476                var img=g.childrenByName("image")[0];
477                this.imageSize={
478                        w:parseFloat(img.getAttribute('width'),10),
479                        h:parseFloat(img.getAttribute('height'),10)
480                };
481                this.imageSrc=img.getAttribute("xlink:href");
482                this.initialize(n);
483
484                //      now let's do the annotations.
485                var ann=g.childrenByName("g");
486                for(var i=0; i<ann.length; i++){ this._loadAnnotation(ann[i]); }
487                if(this._loadDeferred){
488                        this._loadDeferred.callback(this);
489                        this._loadDeferred=null;
490                }
491                this.onLoad();
492        };
493        p.onLoad=function(){};
494        p.onClick=function(){};
495        p._loadAnnotation=function(obj){
496                var ctor=obj.getAttribute('dojoxsketch:type')+"Annotation";
497                if(ta[ctor]){
498                        var a=new ta[ctor](this, obj.id);
499                        a.initialize(obj);
500                        this.nextKey();
501                        a.setMode(ta.Annotation.Modes.View);
502                        this._add(a);
503                        return a;
504                }
505                return null;
506        };
507       
508        p.onUndo=function(){};
509        p.onBeforeUndo=function(){};
510        p.onRedo=function(){};
511        p.onBeforeRedo=function(){};
512        p.undo=function(){
513                if(this.history){
514                        this.onBeforeUndo();
515                        this.history.undo();
516                        this.onUndo();
517                }
518        };
519        p.redo=function(){
520                if(this.history){
521                        this.onBeforeRedo();
522                        this.history.redo();
523                        this.onRedo();
524                }
525        };
526        p.serialize=function(){
527                var s='<svg xmlns="http://www.w3.org/2000/svg" '
528                        + 'xmlns:xlink="http://www.w3.org/1999/xlink" '
529                        + 'xmlns:dojoxsketch="http://dojotoolkit.org/dojox/sketch" '
530                        + 'width="' + this.size.w + '" height="' + this.size.h + '">'
531                        + '<g>'
532                        + '<image xlink:href="' + this.imageSrc + '" x="0" y="0" width="'
533                        + this.size.w + '" height="' + this.size.h + '" />';
534                for(var i=0; i<this.shapes.length; i++){ s+= this.shapes[i].serialize(); }
535                s += '</g></svg>';
536                return s;
537        };
538        p.getValue=p.serialize;
539
540        return dojox.sketch.Figure;
541});
Note: See TracBrowser for help on using the repository browser.