Drawing.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  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.drawing.Drawing"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.drawing.Drawing"] = true;
  8. dojo.provide("dojox.drawing.Drawing");
  9. (function(){
  10. var _plugsInitialized = false;
  11. dojo.declare("dojox.drawing.Drawing", [], {
  12. // summary:
  13. // Drawing is a project that sits on top of DojoX GFX and uses SVG and
  14. // VML vector graphics to draw and display.
  15. // description:
  16. // Drawing is similar to DojoX Sketch, but is designed to be more versatile
  17. // extendable and customizable.
  18. // Drawing currently only initiates from HTML although it's technically not
  19. // a Dijit to keep the file size light. But if Dijit is available, Drawing
  20. // will register itself with it and can be accessed dijit.byId('myDrawing')
  21. //
  22. // NOTES:
  23. // Although not Drawing and Toolbar, all other objects are created with a custom
  24. // declare. See dojox.drawing.util.oo
  25. //
  26. //The files are laid out as such:
  27. // - Drawing
  28. // The master class. More than one instance of a Drawing can be placed
  29. // on a page at one time (although this has not yet been tested). Plugins
  30. // can be added in markup.
  31. // - Toolbar
  32. // Like Drawing, Toolbar is a psudeo Dijit that does not need Dijit. It is
  33. // optional. It can be oriented horizontal or vertical by placing one of
  34. // those params in the class (at least one is required). Plugins
  35. // can be added in markup. A drawingId is required to point toolbar to
  36. // the drawing.
  37. // - defaults
  38. // Contains the default styles and dimensions for Stencils. An individual
  39. // Stencil can be changed by calling stencil.att({color obj}); To change
  40. // all styles, a custom defaults file should be used.
  41. // -Stencils
  42. // Drawing uses a concept of 'Stencils' to avoid confusion between a
  43. // Dojox Shape and a Drawing Shape. The classes in the 'stencils' package
  44. // are display only, they are not used for actually drawing (see 'tools').
  45. // This package contains _Base from which stencils inherit most of their
  46. // methods.(Path and Image are display only and not found in Tools)
  47. // - Tools
  48. // The Tools package contains Stencils that are attached to mouse events
  49. // and can be used for drawing. Items in this package can also be selected
  50. // and modified.
  51. // - Tools / Custom
  52. // Holds tools that do not directly extend Stencil base classes and often
  53. // have very custom code.
  54. // - Library (not implemented)
  55. // The Library package, which is not yet implemented, will be the place to
  56. // hold stencils that have very specific data points that result in a picture.
  57. // Flag-like-banners, fancy borders, or other complex shapes would go here.
  58. // - Annotations
  59. // Annotations 'decorate' and attach to other Stencils, such as a 'Label'
  60. // that can show text on a stencil, or an 'Angle' that shows while dragging
  61. // or modifying a Vector, or an Arrow head that is attached to the beginning
  62. // or end of a line.
  63. // - Manager
  64. // Contains classes that control functionality of a Drawing.
  65. // - Plugins
  66. // Contains optional classes that are 'plugged into' a Drawing. There are two
  67. // types: 'drawing' plugins that modify the canvas, and 'tools' which would
  68. // show in the toolbar.
  69. // - Util
  70. // A collection of common tasks.
  71. //
  72. // example:
  73. // | <div dojoType="dojox.drawing.Drawing" id="drawing" defaults="myCustom.defaults"
  74. // | plugins="[{'name':'dojox.drawing.plugins.drawing.Grid', 'options':{gap:100}}]">
  75. // | </div>
  76. //
  77. // example:
  78. // | <div dojoType="dojox.drawing.Toolbar" drawingId="drawing" class="drawingToolbar vertical">
  79. // | <div tool="dojox.drawing.tools.Line" selected="false">Line</div>
  80. // | <div tool="dojox.drawing.tools.Rect" selected="false">Rect</div>
  81. // | <div tool="dojox.drawing.tools.Ellipse" selected="false">Ellipse</div>
  82. // | <div tool="dojox.drawing.tools.TextBlock" selected="false">Statement</div>
  83. // | <div tool="dojox.drawing.tools.custom.Equation" selected="false">Equation</div>
  84. // | <div plugin="dojox.drawing.plugins.tools.Pan" options="{}">Pan</div>
  85. // | <div plugin="dojox.drawing.plugins.tools.Zoom" options="{zoomInc:.1,minZoom:.5,maxZoom:2}">Zoom</div>
  86. // | </div>
  87. //
  88. //
  89. // ready: Boolean
  90. // Whether or not the canvas has been created and Stencils can be added
  91. ready:false,
  92. // mode: [optional] String
  93. // Changes the functionality of the drawing
  94. mode: "",
  95. // width: Number
  96. // Width of the canvas
  97. width:0,
  98. //
  99. // height: Number
  100. // Height of the canvas
  101. height:0,
  102. //
  103. // defaults : Object
  104. // Optional replacements for native defaults.
  105. // plugins: Object
  106. // Key values of plugins that apply to canvas.
  107. //
  108. constructor: function(/* Object */props, /* HTMLNode */node){
  109. // summary:
  110. // Drawing is not a Dijit. This is the master method.
  111. // NOTE:
  112. // props is always null since this is not a real widget
  113. // Will change when Drawing can be created programmatically.
  114. //
  115. var def = dojo.attr(node, "defaults");
  116. if(def){
  117. dojox.drawing.defaults = dojo.getObject(def);
  118. }
  119. this.defaults = dojox.drawing.defaults;
  120. this.id = node.id;
  121. dojox.drawing.register(this, "drawing");
  122. this.mode = (props.mode || dojo.attr(node, "mode") || "").toLowerCase();
  123. var box = dojo.contentBox(node);
  124. this.width = box.w;
  125. this.height = box.h;
  126. this.util = dojox.drawing.util.common;
  127. this.util.register(this); // So Toolbar can find this Drawing DEPRECATED
  128. this.keys = dojox.drawing.manager.keys;
  129. this.mouse = new dojox.drawing.manager.Mouse({util:this.util, keys:this.keys, id:this.mode=="ui"?"MUI":"mse"});
  130. this.mouse.setEventMode(this.mode);
  131. this.tools = {};
  132. this.stencilTypes = {};
  133. this.stencilTypeMap = {};
  134. this.srcRefNode = node; // need this?
  135. this.domNode = node;
  136. var str = dojo.attr(node, "plugins"); // FIXME: get this from props if available
  137. if(str){
  138. this.plugins = eval(str);
  139. }else{
  140. this.plugins = [];
  141. }
  142. this.widgetId = this.id;
  143. dojo.attr(this.domNode, "widgetId", this.widgetId);
  144. // If Dijit is available in the page, register with it
  145. if(dijit && dijit.registry){
  146. dijit.registry.add(this);
  147. console.log("using dijit")
  148. }else{
  149. // else fake dijit.byId
  150. // FIXME: This seems pretty hacky.
  151. // Maybe should just encourage jsId
  152. dijit.registry = {
  153. objs:{},
  154. add:function(obj){
  155. this.objs[obj.id] = obj;
  156. }
  157. };
  158. dijit.byId = function(id){
  159. return dijit.registry.objs[id];
  160. };
  161. dijit.registry.add(this);
  162. }
  163. var stencils = dojox.drawing.getRegistered("stencil");
  164. for(var nm in stencils){
  165. this.registerTool(stencils[nm].name);
  166. }
  167. var tools = dojox.drawing.getRegistered("tool");
  168. for(nm in tools){
  169. this.registerTool(tools[nm].name);
  170. }
  171. var plugs = dojox.drawing.getRegistered("plugin");
  172. for(nm in plugs){
  173. this.registerTool(plugs[nm].name);
  174. }
  175. this._createCanvas();
  176. },
  177. _createCanvas: function(){
  178. console.info("drawing create canvas...");
  179. this.canvas = new dojox.drawing.manager.Canvas({
  180. srcRefNode:this.domNode,
  181. util:this.util,
  182. mouse:this.mouse,
  183. callback: dojo.hitch(this, "onSurfaceReady")
  184. });
  185. this.initPlugins();
  186. },
  187. resize: function(/* Object */box){
  188. // summary:
  189. // Resizes the canvas.
  190. // If within a ContentPane this will get called automatically.
  191. // Can also be called directly.
  192. //
  193. box && dojo.style(this.domNode, {
  194. width:box.w+"px",
  195. height:box.h+"px"
  196. });
  197. if(!this.canvas){
  198. this._createCanvas();
  199. }else if(box){
  200. this.canvas.resize(box.w, box.h);
  201. }
  202. },
  203. startup: function(){
  204. //console.info("drawing startup")
  205. },
  206. getShapeProps: function(/* Object */data, mode){
  207. // summary:
  208. // The common objects that are mixed into
  209. // a new Stencil. Mostly internal, but could be used.
  210. //
  211. var surface = data.stencilType;
  212. var ui = this.mode=="ui" || mode=="ui";
  213. return dojo.mixin({
  214. container: ui && !surface ? this.canvas.overlay.createGroup() : this.canvas.surface.createGroup(),
  215. util:this.util,
  216. keys:this.keys,
  217. mouse:this.mouse,
  218. drawing:this,
  219. drawingType: ui && !surface ? "ui" : "stencil",
  220. style:this.defaults.copy()
  221. }, data || {});
  222. },
  223. addPlugin: function(/* Object */plugin){
  224. // summary:
  225. // Add a toolbar plugin object to plugins array
  226. // to be parsed
  227. this.plugins.push(plugin);
  228. if(this.canvas.surfaceReady){
  229. this.initPlugins();
  230. }
  231. },
  232. initPlugins: function(){
  233. // summary:
  234. // Called from Toolbar after a plugin has been loaded
  235. // The call to this coming from toobar is a bit funky as the timing
  236. // of IE for canvas load is different than other browsers
  237. if(!this.canvas || !this.canvas.surfaceReady){
  238. var c = dojo.connect(this, "onSurfaceReady", this, function(){
  239. dojo.disconnect(c);
  240. this.initPlugins();
  241. });
  242. return;
  243. }
  244. dojo.forEach(this.plugins, function(p, i){
  245. var props = dojo.mixin({
  246. util:this.util,
  247. keys:this.keys,
  248. mouse:this.mouse,
  249. drawing:this,
  250. stencils:this.stencils,
  251. anchors:this.anchors,
  252. canvas:this.canvas
  253. }, p.options || {});
  254. //console.log('drawing.plugin:::', p.name, props)
  255. this.registerTool(p.name, dojo.getObject(p.name));
  256. try{
  257. this.plugins[i] = new this.tools[p.name](props);
  258. }catch(e){
  259. console.error("Failed to initilaize plugin: " +p.name + ". Did you require it?");
  260. }
  261. }, this);
  262. this.plugins = [];
  263. _plugsInitialized = true;
  264. // In IE, because the timing is different we have to get the
  265. // canvas position after everything has drawn. *sigh*
  266. this.mouse.setCanvas();
  267. },
  268. onSurfaceReady: function(){
  269. // summary:
  270. // Event that to which can be connected.
  271. // Fired when the canvas is ready and can be drawn to.
  272. //
  273. this.ready = true;
  274. //console.info("Surface ready")
  275. this.mouse.init(this.canvas.domNode);
  276. this.undo = new dojox.drawing.manager.Undo({keys:this.keys});
  277. this.anchors = new dojox.drawing.manager.Anchors({drawing:this, mouse:this.mouse, undo:this.undo, util:this.util});
  278. if(this.mode == "ui"){
  279. this.uiStencils = new dojox.drawing.manager.StencilUI({canvas:this.canvas, surface:this.canvas.surface, mouse:this.mouse, keys:this.keys});
  280. }else{
  281. this.stencils = new dojox.drawing.manager.Stencil({canvas:this.canvas, surface:this.canvas.surface, mouse:this.mouse, undo:this.undo, keys:this.keys, anchors:this.anchors});
  282. this.uiStencils = new dojox.drawing.manager.StencilUI({canvas:this.canvas, surface:this.canvas.surface, mouse:this.mouse, keys:this.keys});
  283. }
  284. if(dojox.gfx.renderer=="silverlight"){
  285. try{
  286. new dojox.drawing.plugins.drawing.Silverlight({util:this.util, mouse:this.mouse, stencils:this.stencils, anchors:this.anchors, canvas:this.canvas});
  287. }catch(e){
  288. throw new Error("Attempted to install the Silverlight plugin, but it was not found.");
  289. }
  290. }
  291. dojo.forEach(this.plugins, function(p){
  292. p.onSurfaceReady && p.onSurfaceReady();
  293. });
  294. },
  295. addUI: function(/* String */type, /* Object */options){
  296. // summary:
  297. // Use this method to programmatically add Stencils that display on
  298. // the canvas.
  299. // FIXME: Currently only supports Stencils that have been registered,
  300. // which is items in the toolbar, and the additional Stencils at the
  301. // end of onSurfaceReady. This covers all Stencils, but you can't
  302. // use 'display only' Stencils for Line, Rect, and Ellipse.
  303. // arguments:
  304. // type: String
  305. // The final name of the tool, lower case: 'image', 'line', 'textBlock'
  306. // options:
  307. // type: Object
  308. // The parameters used to draw the object. See stencil._Base and each
  309. // tool for specific parameters of teh data or points objects.
  310. //
  311. if(!this.ready){
  312. var c = dojo.connect(this, "onSurfaceReady", this, function(){
  313. dojo.disconnect(c);
  314. this.addUI(type, options);
  315. });
  316. return false;
  317. }
  318. if(options && !options.data && !options.points){
  319. options = {data:options}
  320. }
  321. if(!this.stencilTypes[type]){
  322. if(type != "tooltip"){
  323. console.warn("Not registered:", type);
  324. }
  325. return null;
  326. }
  327. var s = this.uiStencils.register( new this.stencilTypes[type](this.getShapeProps(options, "ui")));
  328. return s;
  329. },
  330. addStencil: function(/* String */type, /* Object */options){
  331. // summary:
  332. // Use this method to programmatically add Stencils that display on
  333. // the canvas.
  334. // FIXME: Currently only supports Stencils that have been registered,
  335. // which is items in the toolbar, and the additional Stencils at the
  336. // end of onSurfaceReady. This covers all Stencils, but you can't
  337. // use 'display only' Stencils for Line, Rect, and Ellipse.
  338. // arguments:
  339. // type: String
  340. // The final name of the tool, lower case: 'image', 'line', 'textBlock'
  341. // options:
  342. // type: Object
  343. // The parameters used to draw the object. See stencil._Base and each
  344. // tool for specific parameters of teh data or points objects.
  345. //
  346. if(!this.ready){
  347. var c = dojo.connect(this, "onSurfaceReady", this, function(){
  348. dojo.disconnect(c);
  349. this.addStencil(type, options);
  350. });
  351. return false;
  352. }
  353. if(options && !options.data && !options.points){
  354. options = {data:options}
  355. }
  356. var s = this.stencils.register( new this.stencilTypes[type](this.getShapeProps(options)));
  357. // need this or not?
  358. //s.connect(s, "destroy", this, "onDeleteStencil");
  359. this.currentStencil && this.currentStencil.moveToFront();
  360. return s;
  361. },
  362. removeStencil: function(/* Object */stencil){
  363. // summary:
  364. // Use this method to programmatically remove Stencils from the canvas.
  365. // arguments:
  366. // Stencil: Object
  367. // The Stencil to be removed
  368. //
  369. this.stencils.unregister(stencil);
  370. stencil.destroy();
  371. },
  372. removeAll: function(){
  373. // summary:
  374. // Deletes all Stencils on the canvas.
  375. this.stencils.removeAll();
  376. },
  377. selectAll: function(){
  378. // summary:
  379. // Selects all stencils
  380. this.stencils.selectAll();
  381. },
  382. toSelected: function(/*String*/func /*[args, ...]*/){
  383. // summary:
  384. // Call a function within all selected Stencils
  385. // like attr()
  386. // example:
  387. // | myDrawing.toSelected('attr', {x:10})
  388. //
  389. this.stencils.toSelected.apply(this.stencils, arguments);
  390. },
  391. exporter: function(){
  392. // summary:
  393. // Collects all Stencil data and returns an
  394. // Array of objects.
  395. console.log("this.stencils", this.stencils);
  396. return this.stencils.exporter(); //Array
  397. },
  398. importer: function(/* Array */objects){
  399. // summary:
  400. // Handles an Array of stencil data and imports the objects
  401. // to the drawing.
  402. dojo.forEach(objects, function(m){
  403. this.addStencil(m.type, m);
  404. }, this);
  405. },
  406. changeDefaults: function(/*Object*/newStyle,/*boolean*/value){
  407. // summary:
  408. // Change the defaults so that all Stencils from this
  409. // point on will use the newly changed style.
  410. // arguments:
  411. // newStyle: Object
  412. // An object that represents one of the objects in
  413. // drawing.style that will be mixed in. Not all
  414. // properties are necessary. Only one object may
  415. // be changed at a time. The object boolean parameter
  416. // is not required and if not set objects will automatically
  417. // be changed.
  418. // Changing non-objects like angleSnap requires value
  419. // to be true.
  420. // example:
  421. // | myDrawing.changeDefaults({
  422. // | norm:{
  423. // | fill:"#0000ff",
  424. // | width:5,
  425. // | color:"#ffff00"
  426. // | }
  427. // | });
  428. //
  429. //console.log("----->>> changeDefault: ",newStyle, " value?: ",value);
  430. if(value!=undefined && value){
  431. for(var nm in newStyle){
  432. this.defaults[nm] = newStyle[nm];
  433. }
  434. }else{
  435. for(var nm in newStyle){
  436. for(var n in newStyle[nm]){
  437. //console.log(" copy", nm, n, " to: ", newStyle[nm][n]);
  438. this.defaults[nm][n] = newStyle[nm][n];
  439. }
  440. }
  441. }
  442. if(this.currentStencil!=undefined && (!this.currentStencil.created || this.defaults.clickMode)){
  443. this.unSetTool();
  444. this.setTool(this.currentType);
  445. }
  446. },
  447. onRenderStencil: function(/* Object */stencil){
  448. // summary:
  449. // Event that fires when a stencil is drawn. Does not fire from
  450. // 'addStencil'.
  451. //
  452. //console.info("--------------------------------------dojox.drawing.onRenderStencil:", stencil.id);
  453. this.stencils.register(stencil);
  454. this.unSetTool();
  455. if(!this.defaults.clickMode){
  456. this.setTool(this.currentType);
  457. }else{
  458. this.defaults.clickable = true;
  459. }
  460. },
  461. onDeleteStencil: function(/* Object */stencil){
  462. // summary:
  463. // Event fired from a stencil that has destroyed itself
  464. // will also be called when it is removed by "removeStencil"
  465. // or stencils.onDelete.
  466. //
  467. this.stencils.unregister(stencil);
  468. },
  469. registerTool: function(/* String */type){
  470. // summary:
  471. // Registers a tool that can be accessed. Internal.
  472. if(this.tools[type]){ return; }
  473. var constr = dojo.getObject(type);
  474. //console.log("constr:", type)
  475. this.tools[type] = constr;
  476. var abbr = this.util.abbr(type);
  477. this.stencilTypes[abbr] = constr;
  478. this.stencilTypeMap[abbr] = type;
  479. },
  480. getConstructor: function(/*String*/abbr){
  481. // summary:
  482. // Returns a Stencil constructor base on
  483. // abbreviation
  484. return this.stencilTypes[abbr];
  485. },
  486. setTool: function(/* String */type){
  487. // summary:
  488. // Sets up a new class to be used to draw. Called from Toolbar,
  489. // and this class... after a tool is used a new one of the same
  490. // type is initialized. Could be called externally.
  491. //
  492. if(this.mode=="ui"){ return; }
  493. if(!this.canvas || !this.canvas.surface){
  494. var c = dojo.connect(this, "onSurfaceReady", this, function(){
  495. dojo.disconnect(c);
  496. this.setTool(type);
  497. });
  498. return;
  499. }
  500. if(this.currentStencil){
  501. this.unSetTool();
  502. }
  503. this.currentType = this.tools[type] ? type : this.stencilTypeMap[type];
  504. //console.log("new tool arg:", type, "curr:", this.currentType, "mode:", this.mode, "tools:", this.tools)
  505. try{
  506. this.currentStencil = new this.tools[this.currentType]({container:this.canvas.surface.createGroup(), util:this.util, mouse:this.mouse, keys:this.keys});
  507. console.log("new tool is:", this.currentStencil.id, this.currentStencil);
  508. if(this.defaults.clickMode){ this.defaults.clickable = false; }
  509. this.currentStencil.connect(this.currentStencil, "onRender", this, "onRenderStencil");
  510. this.currentStencil.connect(this.currentStencil, "destroy", this, "onDeleteStencil");
  511. }catch(e){
  512. console.error("dojox.drawing.setTool Error:", e);
  513. console.error(this.currentType + " is not a constructor: ", this.tools[this.currentType]);
  514. //console.trace();
  515. }
  516. },
  517. unSetTool: function(){
  518. // summary:
  519. // Destroys current tool
  520. if(!this.currentStencil.created){
  521. this.currentStencil.destroy();
  522. }
  523. }
  524. });
  525. })();
  526. }