Figure.js 14 KB

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