source: Dev/branches/rest-dojo-ui/client/dojox/sketch/Figure.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).

File size: 14.1 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                        var x=e.clientX-self._ctr.x;
181                        var y=e.clientY-self._ctr.y;
182                        var dx=x-self._lp.x;
183                        var dy=y-self._lp.y;
184                        self._absEnd={x:x, y:y};
185                        if(self._c){
186                                //self._c.doChange({dx:dx, dy:dy});
187                                self._c.setBinding({dx:dx/self.zoomFactor, dy:dy/self.zoomFactor});
188                                self._lp={x:x, y:y};
189                        } else {
190                                self._end={x:dx, y:dy};
191                                var rect={
192                                        x:Math.min(self._start.x,self._absEnd.x),
193                                        y:Math.min(self._start.y,self._absEnd.y),
194                                        width:Math.abs(self._start.x-self._absEnd.x),
195                                        height:Math.abs(self._start.y-self._absEnd.y)
196                                }
197                                if(rect.width && rect.height){
198                                        self._ctool.onMouseMove(e,rect);
199                                }
200                        }
201                };
202                this._mu=function(e){
203                        if(self._c){
204                                //      record the event.
205                                self._c.endEdit();
206                        }else{
207                                self._ctool.onMouseUp(e);
208                        }
209
210                        //      clear the stuff out.
211                        self._c=self._ctr=self._lp=self._action=self._prevState=self._startPoint=null;
212                        self._cshape=self._start=self._end=self._absEnd=null;
213                };
214
215                this.initUndoStack();
216        };
217
218        var p=ta.Figure.prototype;
219        p.initUndoStack=function(){
220                this.history=new ta.UndoStack(this);
221        };
222        p.setTool=function(/*dojox.sketch._Plugin*/t){
223                this._ctool=t;
224        };
225    //gridSize: int
226    //      if it is greater than 0, all new shapes placed on the drawing will have coordinates
227    //      snapped to the gridSize. For example, if gridSize is set to 10, all coordinates
228    //      (only including coordinates which specifies the x/y position of shape are affected
229    //      by this parameter) will be dividable by 10
230    p.gridSize=0;
231    p._calCol=function(v){
232        return this.gridSize?(Math.round(v/this.gridSize)*this.gridSize):v;
233    };
234        p._delete=function(arr,noundo){
235                for(var i=0; i<arr.length; i++){
236                        //var before=arr[i].serialize();
237                        arr[i].setMode(ta.Annotation.Modes.View);
238                        arr[i].destroy(noundo);
239                        this.remove(arr[i]);
240                        this._remove(arr[i]);
241                        if(!noundo){
242                                arr[i].onRemove();
243                        }
244                }
245                arr.splice(0,arr.length);
246        };
247        p.onDblClickShape=function(shape,e){
248                if(shape['onDblClick']){
249                        shape.onDblClick(e);
250                }
251        };
252
253        p.onCreateShape=function(shape){};
254        p.onBeforeCreateShape=function(shape){};
255        p.initialize=function(node){
256                this.node=node;
257                this.surface=dojox.gfx.createSurface(node, this.size.w, this.size.h);
258                //this.backgroundRect=this.surface.createRect({ x:0, y:0, width:this.size.w, height:this.size.h })
259                //      .setFill("white");
260                this.group=this.surface.createGroup();
261
262                this._cons=[];
263                var es=this.surface.getEventSource();
264                this._cons.push(
265                        //      kill any dragging events.
266                        //              for FF
267                        dojo.connect(es, "ondraggesture", dojo.stopEvent),
268                        dojo.connect(es, "ondragenter", dojo.stopEvent),
269                        dojo.connect(es, "ondragover", dojo.stopEvent),
270                        dojo.connect(es, "ondragexit", dojo.stopEvent),
271                        dojo.connect(es, "ondragstart", dojo.stopEvent),
272                        //              for IE
273                        dojo.connect(es, "onselectstart", dojo.stopEvent),
274                        //      hook up the drag system.
275                        dojo.connect(es, 'onmousedown', this._md),
276                        dojo.connect(es, 'onmousemove', this._mm),
277                        dojo.connect(es, 'onmouseup', this._mu),
278                        // misc hooks
279                        dojo.connect(es, 'onclick', this, 'onClick'),
280                        dojo.connect(es, 'ondblclick', this._dblclick),
281                        dojo.connect(node, 'onkeydown', this._keydown));
282               
283                this.image=this.group.createImage({ width:this.imageSize.w, height:this.imageSize.h, src:this.imageSrc });
284        };
285
286        p.destroy=function(isLoading){
287                if(!this.node){ return; }
288                if(!isLoading){
289                        if(this.history){ this.history.destroy(); }
290                        if(this._subscribed){
291                                dojo.unsubscribe(this._subscribed);
292                                delete this._subscribed;
293                        }
294                }
295                dojo.forEach(this._cons, dojo.disconnect);
296                this._cons=[];
297
298                //TODO: how to destroy a surface properly?
299                dojo.empty(this.node);
300
301                //this.node.removeChild(this.surface.getEventSource());
302                this.group=this.surface=null;
303                this.obj={};
304                this.shapes=[];
305        };
306        p.nextKey=function(){ return "annotation-"+this.annCounter++; };
307        p.draw=function(){ };
308        p.zoom=function(pct){
309                //      first get the new dimensions
310                this.zoomFactor=pct/100;
311                var w=this.size.w*this.zoomFactor;
312                var h=this.size.h*this.zoomFactor;
313                this.surface.setDimensions(w, h);
314                //      then scale it.
315                this.group.setTransform(dojox.gfx.matrix.scale(this.zoomFactor, this.zoomFactor));
316
317                for(var i=0; i<this.shapes.length; i++){
318                        this.shapes[i].zoom(this.zoomFactor);
319                }
320        };
321        p.getFit=function(){
322                //      assume fitting the parent node.
323//              var box=dojo.html.getContentBox(this.node.parentNode);
324                //the following should work under IE and FF, not sure about others though
325                var wF=(this.node.parentNode.offsetWidth-5)/this.size.w;
326                var hF=(this.node.parentNode.offsetHeight-5)/this.size.h;
327                return Math.min(wF, hF)*100;
328        };
329        p.unzoom=function(){
330                //      restore original size.
331                this.zoomFactor=1;
332                this.surface.setDimensions(this.size.w, this.size.h);
333                this.group.setTransform();
334        };
335
336        //      object registry for drag handling.
337        p._add=function(obj){ this.obj[obj._key]=obj; };
338        p._remove=function(obj){
339                if(this.obj[obj._key]){
340                        delete this.obj[obj._key];
341                }
342        };
343        p._get=function(key){
344                if(key&&key.indexOf("bounding")>-1){
345                        key=key.replace("-boundingBox","");
346                }else if(key&&key.indexOf("-labelShape")>-1){
347                        key=key.replace("-labelShape","");
348                }
349                return this.obj[key];
350        };
351        p._keyFromEvt=function(e){
352                var key=e.target.id+"";
353                if(key.length==0){
354                        //      ancestor tree until you get to the end (meaning this.surface)
355                        var p=e.target.parentNode;
356                        var node=this.surface.getEventSource();
357                        while(p && p.id.length==0 && p!=node){
358                                p=p.parentNode;
359                        }
360                        key=p.id;
361                }
362                return key;
363        };
364        p._fromEvt=function(e){
365                return this._get(this._keyFromEvt(e));
366        };
367
368        p.add=function(annotation){
369                for(var i=0; i<this.shapes.length; i++){
370                        if(this.shapes[i]==annotation){ return true; }
371                }
372                this.shapes.push(annotation);
373                return true;
374        };
375        p.remove=function(annotation){
376                var idx=-1;
377                for(var i=0; i<this.shapes.length; i++){
378                        if(this.shapes[i]==annotation){
379                                idx=i;
380                                break;
381                        }
382                }
383                if(idx>-1){ this.shapes.splice(idx, 1); }
384                return annotation;
385        };
386        p.getAnnotator=function(id){
387                for(var i=0; i<this.shapes.length; i++){
388                        if(this.shapes[i].id==id) {
389                                return this.shapes[i];
390                        }
391                }
392                return null;
393        };
394
395        p.convert=function(ann, t){
396                //      convert an existing annotation to a different kind of annotation
397                var ctor=t+"Annotation";
398                if(!ta[ctor]){ return; }
399                var type=ann.type(), id=ann.id, label=ann.label, mode=ann.mode, tokenId=ann.tokenId;
400                var start, end, control, transform;
401                switch(type){
402                        case "Preexisting":
403                        case "Lead":{
404                                transform={dx:ann.transform.dx, dy:ann.transform.dy };
405                                start={x:ann.start.x, y:ann.start.y};
406                                end={x:ann.end.x, y:ann.end.y };
407                                var cx=end.x-((end.x-start.x)/2);
408                                var cy=end.y-((end.y-start.y)/2);
409                                control={x:cx, y:cy};
410                                break;
411                        }
412                        case "SingleArrow":
413                        case "DoubleArrow":{
414                                transform={dx:ann.transform.dx, dy:ann.transform.dy };
415                                start={x:ann.start.x, y:ann.start.y};
416                                end={x:ann.end.x, y:ann.end.y };
417                                control={x:ann.control.x, y:ann.control.y};
418                                break;
419                        }
420                        case "Underline":{
421                                transform={dx:ann.transform.dx, dy:ann.transform.dy };
422                                start={x:ann.start.x, y:ann.start.y};
423                                control={x:start.x+50, y:start.y+50 };
424                                end={x:start.x+100, y:start.y+100 };
425                                break;
426                        }
427                        case "Brace":{ }
428                }
429                var n=new ta[ctor](this, id);
430
431                if(n.type()=="Underline"){
432                        //      special handling, since we never move the start point.
433                        n.transform={dx:transform.dx+start.x, dy:transform.dy+start.y };
434                } else {
435                        if(n.transform){ n.transform=transform; }
436                        if(n.start){ n.start=start; }
437                }
438                if(n.end){ n.end=end; }
439                if(n.control){ n.control=control; }
440                n.label=label;
441                n.token=dojo.lang.shallowCopy(ann.token);
442                n.initialize();
443
444                this.replaceSelection(n, ann);
445                this._remove(ann);
446                this.remove(ann);
447                ann.destroy();
448
449                //      this should do all the things we need it to do for getting it registered.
450                n.setMode(mode);
451        };
452        p.setValue=function(text){
453                var obj=dojox.xml.DomParser.parse(text);
454                var node=this.node;
455                this.load(obj,node);
456                //this.zoom(this.zoomFactor*100); //zoom to orignal scale
457        };
458        p.load=function(obj, n){
459                //      create from pseudo-DOM
460                if(this.surface){ this.destroy(true); }
461                var node=obj.documentElement;   //      should be either the document or the docElement
462                this.size={
463                        w:parseFloat(node.getAttribute('width'),10),
464                        h:parseFloat(node.getAttribute('height'),10)
465                };
466                var g=node.childrenByName("g")[0];
467                var img=g.childrenByName("image")[0];
468                this.imageSize={
469                        w:parseFloat(img.getAttribute('width'),10),
470                        h:parseFloat(img.getAttribute('height'),10)
471                };
472                this.imageSrc=img.getAttribute("xlink:href");
473                this.initialize(n);
474
475                //      now let's do the annotations.
476                var ann=g.childrenByName("g");
477                for(var i=0; i<ann.length; i++){ this._loadAnnotation(ann[i]); }
478                if(this._loadDeferred){
479                        this._loadDeferred.callback(this);
480                        this._loadDeferred=null;
481                }
482                this.onLoad();
483        };
484        p.onLoad=function(){};
485        p.onClick=function(){};
486        p._loadAnnotation=function(obj){
487                var ctor=obj.getAttribute('dojoxsketch:type')+"Annotation";
488                if(ta[ctor]){
489                        var a=new ta[ctor](this, obj.id);
490                        a.initialize(obj);
491                        this.nextKey();
492                        a.setMode(ta.Annotation.Modes.View);
493                        this._add(a);
494                        return a;
495                }
496                return null;
497        };
498       
499        p.onUndo=function(){};
500        p.onBeforeUndo=function(){};
501        p.onRedo=function(){};
502        p.onBeforeRedo=function(){};
503        p.undo=function(){
504                if(this.history){
505                        this.onBeforeUndo();
506                        this.history.undo();
507                        this.onUndo();
508                }
509        };
510        p.redo=function(){
511                if(this.history){
512                        this.onBeforeRedo();
513                        this.history.redo();
514                        this.onRedo();
515                }
516        };
517        p.serialize=function(){
518                var s='<svg xmlns="http://www.w3.org/2000/svg" '
519                        + 'xmlns:xlink="http://www.w3.org/1999/xlink" '
520                        + 'xmlns:dojoxsketch="http://dojotoolkit.org/dojox/sketch" '
521                        + 'width="' + this.size.w + '" height="' + this.size.h + '">'
522                        + '<g>'
523                        + '<image xlink:href="' + this.imageSrc + '" x="0" y="0" width="'
524                        + this.size.w + '" height="' + this.size.h + '" />';
525                for(var i=0; i<this.shapes.length; i++){ s+= this.shapes[i].serialize(); }
526                s += '</g></svg>';
527                return s;
528        };
529        p.getValue=p.serialize;
530
531        return dojox.sketch.Figure;
532});
Note: See TracBrowser for help on using the repository browser.