Figure.js 14 KB

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