Browse Source

initial commit (v0.5.4)

Simon Van Mierlo 7 years ago
parent
commit
cdc03fc724
100 changed files with 25206 additions and 0 deletions
  1. 166 0
      .etc/REFS
  2. BIN
      .etc/tcorepaper.pdf
  3. BIN
      .manual/figures/atompm.pdf
  4. BIN
      .manual/figures/forestCSMM1.pdf
  5. BIN
      .manual/figures/forestCSMM2.pdf
  6. BIN
      .manual/figures/forestMM.pdf
  7. BIN
      .manual/figures/geomctrl_ok.pdf
  8. BIN
      .manual/figures/geomctrl_resize.pdf
  9. BIN
      .manual/figures/geomctrl_resizeH.pdf
  10. BIN
      .manual/figures/geomctrl_resizeW.pdf
  11. BIN
      .manual/figures/geomctrl_rotate.pdf
  12. BIN
      .manual/figures/icon_cloudmgmt.pdf
  13. 148 0
      .manual/figures/icon_compileToASMM.pdf
  14. 150 0
      .manual/figures/icon_compileToCSMM.pdf
  15. BIN
      .manual/figures/icon_compileToPatternMM.pdf
  16. BIN
      .manual/figures/icon_copy.pdf
  17. BIN
      .manual/figures/icon_download.pdf
  18. BIN
      .manual/figures/icon_dropToolbar.pdf
  19. BIN
      .manual/figures/icon_editprefs.pdf
  20. BIN
      .manual/figures/icon_exportSVG.pdf
  21. BIN
      .manual/figures/icon_insertModel.pdf
  22. BIN
      .manual/figures/icon_load.pdf
  23. 150 0
      .manual/figures/icon_loadModel.pdf
  24. BIN
      .manual/figures/icon_loadToolbar.pdf
  25. BIN
      .manual/figures/icon_newTab.pdf
  26. BIN
      .manual/figures/icon_paste.pdf
  27. BIN
      .manual/figures/icon_pause.pdf
  28. BIN
      .manual/figures/icon_play.pdf
  29. 149 0
      .manual/figures/icon_redo.pdf
  30. BIN
      .manual/figures/icon_saveModel.pdf
  31. 152 0
      .manual/figures/icon_saveModelAs.pdf
  32. BIN
      .manual/figures/icon_step.pdf
  33. BIN
      .manual/figures/icon_stop.pdf
  34. BIN
      .manual/figures/icon_toggleVisibleContainmentLinks.pdf
  35. BIN
      .manual/figures/icon_toggledebug.pdf
  36. BIN
      .manual/figures/icon_togglemm.pdf
  37. BIN
      .manual/figures/icon_undo.pdf
  38. BIN
      .manual/figures/icon_validateM.pdf
  39. BIN
      .manual/figures/mainmenum.pdf
  40. 120 0
      .manual/manual.aux
  41. 73 0
      .manual/manual.out
  42. BIN
      .manual/manual.pdf
  43. BIN
      .manual/manual.synctex.gz
  44. 931 0
      .manual/manual.tex
  45. 73 0
      .manual/manual.toc
  46. 674 0
      COPYING
  47. 165 0
      COPYING.LESSER
  48. 56 0
      ___dataurize.js
  49. 189 0
      ___do.js
  50. 190 0
      ___fs++.js
  51. 859 0
      __worker.js
  52. 511 0
      asworker.js
  53. 2 0
      client/3rd_party_libs/jquery-1.8.2.min.js
  54. 398 0
      client/3rd_party_libs/raphael/dr.css
  55. 373 0
      client/3rd_party_libs/raphael/plugins/group.js
  56. 3 0
      client/3rd_party_libs/raphael/plugins/point.js
  57. 100 0
      client/3rd_party_libs/raphael/plugins/raphael.primitives.js
  58. 8 0
      client/3rd_party_libs/raphael/raphael-min.js
  59. 5985 0
      client/3rd_party_libs/raphael/raphael-src.html
  60. 6319 0
      client/3rd_party_libs/raphael/raphael.js
  61. 1441 0
      client/3rd_party_libs/raphael/reference.html
  62. 179 0
      client/3rd_party_libs/sha1.js
  63. 174 0
      client/atompm.html
  64. 62 0
      client/behaviourmanager.js
  65. 442 0
      client/behavioursc_canvas.js
  66. 103 0
      client/behavioursc_dialog.js
  67. 198 0
      client/behavioursc_inputbar.js
  68. 773 0
      client/client.js
  69. 112 0
      client/collaboration.js
  70. 393 0
      client/compile_utils.js
  71. 478 0
      client/connection_utils.js
  72. 116 0
      client/constants.js
  73. 381 0
      client/data_utils.js
  74. 337 0
      client/edit_utils.js
  75. 640 0
      client/geometry_utils.js
  76. 69 0
      client/globalVariables.js
  77. 774 0
      client/gui_utils.js
  78. 240 0
      client/http_utils.js
  79. 209 0
      client/init.js
  80. 33 0
      client/input_bar_utils.js
  81. BIN
      client/media/cursor_crosshair.png
  82. BIN
      client/media/cursor_default.png
  83. BIN
      client/media/cursor_move.png
  84. BIN
      client/media/cursor_not-allowed.png
  85. BIN
      client/media/cursor_pointer.png
  86. BIN
      client/media/cursor_text.png
  87. BIN
      client/media/download.png
  88. BIN
      client/media/fileb_asmm.png
  89. BIN
      client/media/fileb_csmm.png
  90. BIN
      client/media/fileb_folder.png
  91. BIN
      client/media/fileb_m.png
  92. BIN
      client/media/fileb_newf.png
  93. BIN
      client/media/fileb_patternmm.png
  94. BIN
      client/media/fileb_unknown.png
  95. BIN
      client/media/gridbg.png
  96. 108 0
      client/media/gridbg.svg
  97. BIN
      client/media/ok.png
  98. BIN
      client/media/resize.png
  99. BIN
      client/media/resizeH.png
  100. 0 0
      client/media/resizeW.png

+ 166 - 0
.etc/REFS

@@ -0,0 +1,166 @@
+NODE.JS
+	http://npmjs.org/
+	http://nodejs.org/docs/v0.2.6/api.html
+	http://jsconf.eu/2009/video_nodejs_by_ryan_dahl.html
+	http://jnjnjn.com/113/node-js-for-noobs-grabbing-post-content/
+	http://net.tutsplus.com/tutorials/javascript-ajax/learning-serverside-javascript-with-node-js/
+	http://developer.yahoo.com/blogs/ydn/posts/2010/07/multicore_http_server_with_nodejs/
+	http://www.stoimen.com/blog/2010/11/19/diving-into-node-js-very-first-app/
+	http://jmesnil.net/weblog/2010/11/24/html5-web-application-for-iphone-and-ipad-with-node-js/
+	http://stackoverflow.com/questions/5978361/image-server-using-node-js-and-ni-framework
+	http://snippets.dzone.com/posts/show/12069
+
+DO
+	http://howtonode.org/68a949da2e5524ebfe31e06e8351bf75282cc02c/do-it-fast
+	https://github.com/creationix/do/blob/master/lib/do.js
+	http://howtonode.org/control-flow
+	http://howtonode.org/control-flow-part-ii
+
+WEBWORKERS
+	https://developer.mozilla.org/En/Using_web_workers
+	http://www.nczonline.net/blog/2009/08/25/web-workers-errors-and-debugging/
+	http://www.whatwg.org/specs/web-workers/current-work/
+	http://blog.std.in/2010/07/08/nodejs-webworker-design/
+	https://github.com/pgriess/node-webworker/issues/11
+
+
+JS...
+	http://wiki.commonjs.org/wiki/Modules/1.1#Module_Identifiers
+	http://stackoverflow.com/questions/254200/access-parent-property-in-jquery-callback
+	http://stackoverflow.com/questions/208105/how-to-remove-a-property-from-a-javascript-object
+	http://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-clone-a-javascript-object
+	http://www.componentart.com/community/blogs/jovan/archive/2007/10/17/multiline-strings-in-javascript.aspx
+	http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/
+	https://developer.mozilla.org/En/XMLHttpRequest/Using_XMLHttpRequest#Example.3a_Asynchronous_request
+	http://www.w3.org/TR/2006/WD-XMLHttpRequest-20060405/
+	http://stackoverflow.com/questions/3543319/right-click-event-onclick-in-chrome-opera-and-ie-javascript
+	http://stackoverflow.com/questions/57803/how-to-convert-decimal-to-hex-in-javascript
+	http://stackoverflow.com/questions/3277369/simulate-a-click-by-using-x-y-coordinates-javascript
+	http://www.eggheadcafe.com/community/aspnet/3/10088543/how-to-disable-document-body-from-scrolling.aspx
+	http://webdomino.blogspot.com/2007/04/tips-warning-before-navigate-away-from.html
+	http://stackoverflow.com/questions/1314450/jquery-how-to-capture-the-tab-keypress-within-a-textbox
+	http://simonwillison.net/2006/Jan/20/escape/
+	http://caffeinatedcode.wordpress.com/2008/04/08/simple-javascript-progress-bar/
+	http://stackoverflow.com/questions/246801/how-can-you-encode-to-base64-using-javascript
+	http://stackoverflow.com/questions/383402/is-javascript-s-new-keyword-considered-harmful
+	http://javascriptweblog.wordpress.com/2010/06/07/understanding-javascript-prototypes/
+	http://stackoverflow.com/questions/3770627/multiple-inheritance-in-javascript
+
+
+WEBSOCKETS 
+	http://slides.html5rocks.com/#web-sockets
+	http://dev.w3.org/html5/websockets/
+
+
+SOCKET.IO
+	http://howtonode.org/websockets-socketio
+	http://socket.io/
+	https://github.com/LearnBoost/socket.io
+	https://github.com/LearnBoost/Socket.IO/wiki/Configuring-Socket.IO
+	https://github.com/LearnBoost/socket.io-client
+	https://github.com/LearnBoost/socket.io-spec
+	XX		https://github.com/remy/Socket.io-node-client
+
+
+REST
+	http://www.suryasuravarapu.com/2009/10/rest-delete-operation-and-tunneling.html
+	http://stackoverflow.com/questions/4088350/is-rest-delete-really-idempotent
+
+
+PYTHON...
+	http://docs.python.org/distutils/introduction.html#distutils-simple-example
+	http://stackoverflow.com/questions/336866/how-to-implement-a-minimal-server-for-ajax-in-python
+	http://stackoverflow.com/questions/2398144/python-basehttpserver-httpserver-concurrency-threading
+	http://stackoverflow.com/questions/1099981/why-cant-python-find-shared-objects-that-are-in-directories-in-sys-path
+	http://stackoverflow.com/questions/1562759/can-python-print-a-function-definition
+	http://stackoverflow.com/questions/576169/understanding-python-super
+	http://stackoverflow.com/questions/463643/python-doing-absolute-imports-from-a-subfolder
+	http://psung.blogspot.com/2007/12/for-else-in-python.html
+	http://stackoverflow.com/questions/656155/why-did-python-2-6-add-a-global-next-function
+	http://wiki.python.org/moin/HowTo/Sorting/
+	http://stackoverflow.com/questions/850795/clearing-python-lists
+	http://stackoverflow.com/questions/952914/making-a-flat-list-out-of-list-of-lists-in-python
+	http://stackoverflow.com/questions/2576534/does-pythons-httplib-httpconnection-block
+
+
+PYTHON-WEBSOCKET
+	http://freeasinbeard.org/post/1008785379/python-websocket-a-websocket-client-library-for
+	https://github.com/mtah/python-websocket
+
+
+PYTHON-SPIDERMONKEY
+	http://code.google.com/p/python-spidermonkey/
+
+
+IGRAPH
+	http://www.cs.rhul.ac.uk/home/tamas/development/igraph/tutorial/tutorial.html
+
+HTML5 DRAGNDROP
+	http://www.thebuzzmedia.com/html5-drag-and-drop-and-file-api-tutorial/
+	http://help.dottoro.com/ljoiuehq.php
+	http://stackoverflow.com/questions/5369928/html5-drag-drop-behaviour
+	http://www.html5rocks.com/en/tutorials/casestudies/box_dnd_download/
+	http://www.html5rocks.com/en/tutorials/dnd/basics/
+
+
+HTML+CSS
+	http://www.highdots.com/forums/cascading-style-sheets/adapt-width-div-content-258048.html
+	http://forums.devshed.com/css-help-116/div-height-grow-as-content-grows-501491.html
+	http://www.bluerobot.com/web/css/center1.html
+	http://www.w3schools.com/cssref/pr_pos_z-index.asp
+	http://www.adipalaz.com/linksbg.html
+	http://stackoverflow.com/questions/2091168/disable-a-link-using-css
+	http://stackoverflow.com/questions/826782/css-rule-to-disable-text-selection-highlighting
+	http://www.frontpagewebmaster.com/m-134791/tm.htm
+	http://stackoverflow.com/questions/3066356/multiple-css-classes-properties-overlapping-based-on-the-order-defined
+	http://girliemac.com/blog/2011/11/27/html5-input-events/
+
+
+SVG
+	http://www.w3.org/TR/SVG/paths.html#PathDataGeneralInformation
+	http://www.w3.org/TR/SVGTiny12/text.html#text-edit
+	http://en.wikipedia.org/wiki/Comparison_of_layout_engines_(Scalable_Vector_Graphics)
+	http://tutorials.jenkov.com/svg/g-element.html
+	http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined
+	http://stackoverflow.com/questions/1301685/fixed-stroke-width-in-svg
+		http://www.w3.org/TR/SVGTiny12/painting.html#NonScalingStroke
+	http://stackoverflow.com/questions/3561270/error-on-line-39-at-column-26-namespace-prefix-xlink-for-href-on-script-is-not
+
+
+COPY-PASTE
+	http://code.google.com/p/zeroclipboard/
+	http://davidwalsh.name/clipboard
+	http://help.dottoro.com/ljuimtmq.php
+	http://help.dottoro.com/ljwexqxl.php
+	https://developer.mozilla.org/en/document.cookie
+	http://www.quirksmode.org/js/cookies.html
+	https://developer.mozilla.org/en/DOM/Storage
+
+
+OTHER
+	http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
+	http://stephenjungels.com/jungels.net/articles/diff-patch-ten-minutes.html
+	http://www.gentleface.com/free_icon_set.html
+	http://stackoverflow.com/questions/872206/http-status-code-0-what-does-this-mean-in-ms-xmlhttp
+	http://www.javapractices.com/topic/TopicAction.do?Id=19
+	http://stackoverflow.com/questions/5802974/android-intent-action-sendto-displays-activity-com-android-mms-ui-conversatio
+
+
+DATA URIs
+	http://stackoverflow.com/questions/5294470/node-js-writing-image-to-local-server
+	https://gist.github.com/799195
+	http://www.nczonline.net/blog/2009/10/27/data-uris-explained/
+	http://stackoverflow.com/questions/934012/get-image-data-in-javascript
+
+
+CLIENT SIDE DOWNLOAD, DATA URIs 
+# download via hand-built data uris is no longer supported by chrome... you have to put data in a blob and download via the blob url
+	http://jsfiddle.net/estelle/SJjJb/
+	http://stackoverflow.com/questions/3665115/create-a-file-in-memory-for-user-to-download-not-through-server
+	http://html5-demos.appspot.com/static/a.download.html
+	http://updates.html5rocks.com/2011/08/Downloading-resources-in-HTML5-a-download
+	http://stackoverflow.com/questions/6076047/create-a-download-link-from-a-blob-url/10988709#10988709
+
+	
+	
+

BIN
.etc/tcorepaper.pdf


BIN
.manual/figures/atompm.pdf


BIN
.manual/figures/forestCSMM1.pdf


BIN
.manual/figures/forestCSMM2.pdf


BIN
.manual/figures/forestMM.pdf


BIN
.manual/figures/geomctrl_ok.pdf


BIN
.manual/figures/geomctrl_resize.pdf


BIN
.manual/figures/geomctrl_resizeH.pdf


BIN
.manual/figures/geomctrl_resizeW.pdf


BIN
.manual/figures/geomctrl_rotate.pdf


BIN
.manual/figures/icon_cloudmgmt.pdf


+ 148 - 0
.manual/figures/icon_compileToASMM.pdf

@@ -0,0 +1,148 @@
+%PDF-1.4 
+1 0 obj
+<<
+/Pages 2 0 R
+/Type /Catalog
+>>
+endobj
+2 0 obj
+<<
+/Type /Pages
+/Kids [ 3 0 R ]
+/Count 1
+>>
+endobj
+3 0 obj
+<<
+/Type /Page
+/Parent 2 0 R
+/Resources <<
+/XObject << /Im0 8 0 R >>
+/ProcSet 6 0 R >>
+/MediaBox [0 0 32 32]
+/CropBox [0 0 32 32]
+/Contents 4 0 R
+/Thumb 11 0 R
+>>
+endobj
+4 0 obj
+<<
+/Length 5 0 R
+>>
+stream
+q
+32 0 0 32 0 0 cm
+/Im0 Do
+Q
+endstream
+endobj
+5 0 obj
+29
+endobj
+6 0 obj
+[ /PDF /Text /ImageC ]
+endobj
+7 0 obj
+<<
+>>
+endobj
+8 0 obj
+<<
+/Type /XObject
+/Subtype /Image
+/Name /Im0
+/Filter [ /FlateDecode ]
+/Width 32
+/Height 32
+/ColorSpace 10 0 R
+/BitsPerComponent 8
+/SMask 15 0 R
+/Length 9 0 R
+>>
+stream
+xÚ…Ï‹‚0ЊF|‹ŠDå55ÆWÄÿÿ7×n#q]Ø	in¹d@Ó|;4ÍÇãíñòx¸<Q¼»P
oªÊWÝ拃é1×̹ªu�[ÅœªJÕ´-€CÈVÚKD�Di+�~_QäyaÉ$ó½�fL*™úpH™½Ôö{n'µçï¸é:Ù&LŒ¨¥´�-›kÀ©Ž£+ÈK¡5²-—‘îi]psó;¸ÌL�yÊML�ËØEÕGaÈ�yè1ðè{½NÁÖ–W
+endstream
+endobj
+9 0 obj
+206
+endobj
+10 0 obj
+/DeviceGray
+endobj
+11 0 obj
+<<
+/Filter [ /FlateDecode ]
+/Width 32
+/Height 32
+/ColorSpace 10 0 R
+/BitsPerComponent 8
+/Length 12 0 R
+>>
+stream
+xÚ…Ï‹‚0ЊF|‹ŠDå55ÆWÄÿÿ7×n#q]Ø	in¹d@Ó|;4ÍÇãíñòx¸<Q¼»P
oªÊWÝ拃é1×̹ªu�[ÅœªJÕ´-€CÈVÚKD�Di+�~_QäyaÉ$ó½�fL*™úpH™½Ôö{n'µçï¸é:Ù&LŒ¨¥´�-›kÀ©Ž£+ÈK¡5²-—‘îi]psó;¸ÌL�yÊML�ËØEÕGaÈ�yè1ðè{½NÁÖ–W
+endstream
+endobj
+12 0 obj
+206
+endobj
+13 0 obj
+endobj
+14 0 obj
+206
+endobj
+15 0 obj
+<<
+/Type /XObject
+/Subtype /Image
+/Name /Ma0
+/Filter [ /FlateDecode ]
+/Width 32
+/Height 32
+/ColorSpace /DeviceGray
+/BitsPerComponent 8
+/Length 16 0 R
+>>
+stream
+xÚµ“QÄ0D‘€„H@B$TB¥œƒ“€„J‰„HˆJÚ´w7ä£sû—<†,³„ªªb-�?hCèb�§Cvp91 ÃyppFJ!/˜SÀ7\%ûþÞÚ¢`æÏåaWÏ+ZC�~P,Ö›—_ ßœš<â^ÁÌß÷|¶Ã[¯žúˆí^Ü ÊŸq‰ó/ç¯bÞ&\(ÞOÍ?ýäÿDu‚ã\iX(¡
+endstream
+endobj
+16 0 obj
+167
+endobj
+17 0 obj
+<<
+/Title (compileToASMM.icon.png.pdf)
+/CreationDate (D:20120323152053)
+/ModDate (D:20120323152053)
+/Producer (ImageMagick 6.6.0-4 2011-06-15 Q16 http://www.imagemagick.org)
+>>
+endobj
+xref
+0 18
+0000000000 65535 f 
+0000000010 00000 n 
+0000000059 00000 n 
+0000000118 00000 n 
+0000000296 00000 n 
+0000000377 00000 n 
+0000000395 00000 n 
+0000000433 00000 n 
+0000000454 00000 n 
+0000000854 00000 n 
+0000000873 00000 n 
+0000000901 00000 n 
+0000001247 00000 n 
+0000001267 00000 n 
+0000001283 00000 n 
+0000001303 00000 n 
+0000001657 00000 n 
+0000001677 00000 n 
+trailer
+<<
+/Size 18
+/Info 17 0 R
+/Root 1 0 R
+>>
+startxref
+1870
+%%EOF

+ 150 - 0
.manual/figures/icon_compileToCSMM.pdf

@@ -0,0 +1,150 @@
+%PDF-1.4 
+1 0 obj
+<<
+/Pages 2 0 R
+/Type /Catalog
+>>
+endobj
+2 0 obj
+<<
+/Type /Pages
+/Kids [ 3 0 R ]
+/Count 1
+>>
+endobj
+3 0 obj
+<<
+/Type /Page
+/Parent 2 0 R
+/Resources <<
+/XObject << /Im0 8 0 R >>
+/ProcSet 6 0 R >>
+/MediaBox [0 0 32 32]
+/CropBox [0 0 32 32]
+/Contents 4 0 R
+/Thumb 11 0 R
+>>
+endobj
+4 0 obj
+<<
+/Length 5 0 R
+>>
+stream
+q
+32 0 0 32 0 0 cm
+/Im0 Do
+Q
+endstream
+endobj
+5 0 obj
+29
+endobj
+6 0 obj
+[ /PDF /Text /ImageC ]
+endobj
+7 0 obj
+<<
+>>
+endobj
+8 0 obj
+<<
+/Type /XObject
+/Subtype /Image
+/Name /Im0
+/Filter [ /FlateDecode ]
+/Width 32
+/Height 32
+/ColorSpace 10 0 R
+/BitsPerComponent 8
+/SMask 15 0 R
+/Length 9 0 R
+>>
+stream
+xÚmÒÛ‚0EÑ#ŠÑhŒä&¢hŒF#þÿ¿	b;-œõ2�ìÇNó!hoJú‹’þ¤¤?(éwJú�’~¥¤_É8Û궓
UUu³}?šPš[Ùõƒ(Íå§í…@^ä@¦å�TC’Ž$Ø+ò²Ä
+¢˜ˆB!((_�OyÊΣä'¶”ô
%}MI_QÆ),	óT„uKó‘Á±¹3‹‹¡©„3ùsúý+ÛNÞ
+endstream
+endobj
+9 0 obj
+173
+endobj
+10 0 obj
+/DeviceGray
+endobj
+11 0 obj
+<<
+/Filter [ /FlateDecode ]
+/Width 32
+/Height 32
+/ColorSpace 10 0 R
+/BitsPerComponent 8
+/Length 12 0 R
+>>
+stream
+xÚmÒÛ‚0EÑ#ŠÑhŒä&¢hŒF#þÿ¿	b;-œõ2�ìÇNó!hoJú‹’þ¤¤?(éwJú�’~¥¤_É8Û궓
UUu³}?šPš[Ùõƒ(Íå§í…@^ä@¦å�TC’Ž$Ø+ò²Ä
+¢˜ˆB!((_�OyÊΣä'¶”ô
%}MI_QÆ),	óT„uKó‘Á±¹3‹‹¡©„3ùsúý+ÛNÞ
+endstream
+endobj
+12 0 obj
+173
+endobj
+13 0 obj
+endobj
+14 0 obj
+173
+endobj
+15 0 obj
+<<
+/Type /XObject
+/Subtype /Image
+/Name /Ma0
+/Filter [ /FlateDecode ]
+/Width 32
+/Height 32
+/ColorSpace /DeviceGray
+/BitsPerComponent 8
+/Length 16 0 R
+>>
+stream
+xÚÕ’A
D!DG�€$ )8©¤ ¡øÉÏ(Ù½î»ÑGHi§tBÁ SþÜëÝ«äqó-;XŒ‡ í½(áô¾ÎSõ›ÏHjZIÆÒÞlZ|s^w¯ËëµÔá�MjÂü(f4æG1^6õÍ—O{Îsÿ{xÓóÿMÏç³D�Îw�îg…í×^8óa9óµ³çóv~Éô
+endstream
+endobj
+16 0 obj
+157
+endobj
+17 0 obj
+<<
+/Title (compileToCSMM.icon.png.pdf)
+/CreationDate (D:20120323152053)
+/ModDate (D:20120323152053)
+/Producer (ImageMagick 6.6.0-4 2011-06-15 Q16 http://www.imagemagick.org)
+>>
+endobj
+xref
+0 18
+0000000000 65535 f 
+0000000010 00000 n 
+0000000059 00000 n 
+0000000118 00000 n 
+0000000296 00000 n 
+0000000377 00000 n 
+0000000395 00000 n 
+0000000433 00000 n 
+0000000454 00000 n 
+0000000821 00000 n 
+0000000840 00000 n 
+0000000868 00000 n 
+0000001181 00000 n 
+0000001201 00000 n 
+0000001217 00000 n 
+0000001237 00000 n 
+0000001581 00000 n 
+0000001601 00000 n 
+trailer
+<<
+/Size 18
+/Info 17 0 R
+/Root 1 0 R
+>>
+startxref
+1794
+%%EOF

BIN
.manual/figures/icon_compileToPatternMM.pdf


BIN
.manual/figures/icon_copy.pdf


BIN
.manual/figures/icon_download.pdf


BIN
.manual/figures/icon_dropToolbar.pdf


BIN
.manual/figures/icon_editprefs.pdf


BIN
.manual/figures/icon_exportSVG.pdf


BIN
.manual/figures/icon_insertModel.pdf


BIN
.manual/figures/icon_load.pdf


+ 150 - 0
.manual/figures/icon_loadModel.pdf

@@ -0,0 +1,150 @@
+%PDF-1.4 
+1 0 obj
+<<
+/Pages 2 0 R
+/Type /Catalog
+>>
+endobj
+2 0 obj
+<<
+/Type /Pages
+/Kids [ 3 0 R ]
+/Count 1
+>>
+endobj
+3 0 obj
+<<
+/Type /Page
+/Parent 2 0 R
+/Resources <<
+/XObject << /Im0 8 0 R >>
+/ProcSet 6 0 R >>
+/MediaBox [0 0 32 32]
+/CropBox [0 0 32 32]
+/Contents 4 0 R
+/Thumb 11 0 R
+>>
+endobj
+4 0 obj
+<<
+/Length 5 0 R
+>>
+stream
+q
+32 0 0 32 0 0 cm
+/Im0 Do
+Q
+endstream
+endobj
+5 0 obj
+29
+endobj
+6 0 obj
+[ /PDF /Text /ImageC ]
+endobj
+7 0 obj
+<<
+>>
+endobj
+8 0 obj
+<<
+/Type /XObject
+/Subtype /Image
+/Name /Im0
+/Filter [ /FlateDecode ]
+/Width 32
+/Height 32
+/ColorSpace 10 0 R
+/BitsPerComponent 8
+/SMask 15 0 R
+/Length 9 0 R
+>>
+stream
+xÚ­ÏÍ
+‚`…á‰~Ȥ¢Å4-Š>-³û¿·<cA¡gV=‹Ù¼ÌÀˆ4Ï·FÕ��z(ß¿Üú¹úÕë®dœC¿Ð/ô³ýÔCQ­,ynfÉ2»§i7™äÐæ„‹±¾�©=âBô�Cè;NÜrÚ7òjÍ¡/9=ïsÈÞ‚CŸsz~ÆiŸL)ícNûˆ’?xÝc;0
+endstream
+endobj
+9 0 obj
+151
+endobj
+10 0 obj
+/DeviceGray
+endobj
+11 0 obj
+<<
+/Filter [ /FlateDecode ]
+/Width 32
+/Height 32
+/ColorSpace 10 0 R
+/BitsPerComponent 8
+/Length 12 0 R
+>>
+stream
+xÚ­ÏÍ
+‚`…á‰~Ȥ¢Å4-Š>-³û¿·<cA¡gV=‹Ù¼ÌÀˆ4Ï·FÕ��z(ß¿Üú¹úÕë®dœC¿Ð/ô³ýÔCQ­,ynfÉ2»§i7™äÐæ„‹±¾�©=âBô�Cè;NÜrÚ7òjÍ¡/9=ïsÈÞ‚CŸsz~ÆiŸL)ícNûˆ’?xÝc;0
+endstream
+endobj
+12 0 obj
+151
+endobj
+13 0 obj
+endobj
+14 0 obj
+151
+endobj
+15 0 obj
+<<
+/Type /XObject
+/Subtype /Image
+/Name /Ma0
+/Filter [ /FlateDecode ]
+/Width 32
+/Height 32
+/ColorSpace /DeviceGray
+/BitsPerComponent 8
+/Length 16 0 R
+>>
+stream
+xÚ­ÓA„0Ð/¡�P	H¨”JÀA%T�P	•€„0awg™ÒŸràzyC“f䛌nêÏ%öx‘ü�Ý~ñênž…eŸÔ…gyÇó#"&C*lÏzÖc™ç�(úy¤\“úJ={H'ÏÜ]»�î°QOxP>p?Ë'ÊÌá~6ÀñëçAùÁ¯ÍÿÓ&^ïíö^È!¨{3
+endstream
+endobj
+16 0 obj
+159
+endobj
+17 0 obj
+<<
+/Title (loadModel.icon.png.pdf)
+/CreationDate (D:20120322115414)
+/ModDate (D:20120322115414)
+/Producer (ImageMagick 6.6.0-4 2011-06-15 Q16 http://www.imagemagick.org)
+>>
+endobj
+xref
+0 18
+0000000000 65535 f 
+0000000010 00000 n 
+0000000059 00000 n 
+0000000118 00000 n 
+0000000296 00000 n 
+0000000377 00000 n 
+0000000395 00000 n 
+0000000433 00000 n 
+0000000454 00000 n 
+0000000799 00000 n 
+0000000818 00000 n 
+0000000846 00000 n 
+0000001137 00000 n 
+0000001157 00000 n 
+0000001173 00000 n 
+0000001193 00000 n 
+0000001539 00000 n 
+0000001559 00000 n 
+trailer
+<<
+/Size 18
+/Info 17 0 R
+/Root 1 0 R
+>>
+startxref
+1748
+%%EOF

BIN
.manual/figures/icon_loadToolbar.pdf


BIN
.manual/figures/icon_newTab.pdf


BIN
.manual/figures/icon_paste.pdf


BIN
.manual/figures/icon_pause.pdf


BIN
.manual/figures/icon_play.pdf


+ 149 - 0
.manual/figures/icon_redo.pdf

@@ -0,0 +1,149 @@
+%PDF-1.4 
+1 0 obj
+<<
+/Pages 2 0 R
+/Type /Catalog
+>>
+endobj
+2 0 obj
+<<
+/Type /Pages
+/Kids [ 3 0 R ]
+/Count 1
+>>
+endobj
+3 0 obj
+<<
+/Type /Page
+/Parent 2 0 R
+/Resources <<
+/XObject << /Im0 8 0 R >>
+/ProcSet 6 0 R >>
+/MediaBox [0 0 32 32]
+/CropBox [0 0 32 32]
+/Contents 4 0 R
+/Thumb 11 0 R
+>>
+endobj
+4 0 obj
+<<
+/Length 5 0 R
+>>
+stream
+q
+32 0 0 32 0 0 cm
+/Im0 Do
+Q
+endstream
+endobj
+5 0 obj
+29
+endobj
+6 0 obj
+[ /PDF /Text /ImageC ]
+endobj
+7 0 obj
+<<
+>>
+endobj
+8 0 obj
+<<
+/Type /XObject
+/Subtype /Image
+/Name /Im0
+/Filter [ /FlateDecode ]
+/Width 32
+/Height 32
+/ColorSpace 10 0 R
+/BitsPerComponent 8
+/SMask 15 0 R
+/Length 9 0 R
+>>
+stream
+xÚÝÒQ‚0Àñ“$R33L͈[¶¾ÿ‡ëvÓÙFÛkÐïepÿ=mð/8Mø—:>?�r6ªÌ:ÆhƦ<ÜMÂCæÞŠòÍŽúÕNäîbÕ‰Þ*çÖ„¹ž4�¸ÜÔ:€J:ÍOQi <Jê©Êy"”P�ƒÊûB9Qy—ë –Ê)YzjˆQb~v"Äd‹"ËÆDhƒ\[µCçÖAà쾿rvσŸzи14
+endstream
+endobj
+9 0 obj
+173
+endobj
+10 0 obj
+/DeviceGray
+endobj
+11 0 obj
+<<
+/Filter [ /FlateDecode ]
+/Width 32
+/Height 32
+/ColorSpace 10 0 R
+/BitsPerComponent 8
+/Length 12 0 R
+>>
+stream
+xÚÝÒQ‚0Àñ“$R33L͈[¶¾ÿ‡ëvÓÙFÛkÐïepÿ=mð/8Mø—:>?�r6ªÌ:ÆhƦ<ÜMÂCæÞŠòÍŽúÕNäîbÕ‰Þ*çÖ„¹ž4�¸ÜÔ:€J:ÍOQi <Jê©Êy"”P�ƒÊûB9Qy—ë –Ê)YzjˆQb~v"Äd‹"ËÆDhƒ\[µCçÖAà쾿rvσŸzи14
+endstream
+endobj
+12 0 obj
+173
+endobj
+13 0 obj
+endobj
+14 0 obj
+173
+endobj
+15 0 obj
+<<
+/Type /XObject
+/Subtype /Image
+/Name /Ma0
+/Filter [ /FlateDecode ]
+/Width 32
+/Height 32
+/ColorSpace /DeviceGray
+/BitsPerComponent 8
+/Length 16 0 R
+>>
+stream
+xÚÝ’QÄ D‘€$ 	•P	HˆƒJ@B% 	H@B.4@¡wðÛ™ÛOÞ&d3â_¤¬Ç¬è¶T:¼w~Ó
›„ƒ¼"P¼ãSIFÃxÃo%Â<Y‰Ž‹ûF?ë^ƒdë�\xºr�WÐñ3
+xr±©«èEU‰{o²Oc…-¾†‡€ÛÃSÀ“uŽóS¦¬Ò‡�½ÁÝ«É@µQš('C¶|3ûêªïxª¼‚%OrÉ=ˆWõ‘µdÞ
+endstream
+endobj
+16 0 obj
+194
+endobj
+17 0 obj
+<<
+/Title (redo.icon.png.pdf)
+/CreationDate (D:20120322115414)
+/ModDate (D:20120322115414)
+/Producer (ImageMagick 6.6.0-4 2011-06-15 Q16 http://www.imagemagick.org)
+>>
+endobj
+xref
+0 18
+0000000000 65535 f 
+0000000010 00000 n 
+0000000059 00000 n 
+0000000118 00000 n 
+0000000296 00000 n 
+0000000377 00000 n 
+0000000395 00000 n 
+0000000433 00000 n 
+0000000454 00000 n 
+0000000821 00000 n 
+0000000840 00000 n 
+0000000868 00000 n 
+0000001181 00000 n 
+0000001201 00000 n 
+0000001217 00000 n 
+0000001237 00000 n 
+0000001618 00000 n 
+0000001638 00000 n 
+trailer
+<<
+/Size 18
+/Info 17 0 R
+/Root 1 0 R
+>>
+startxref
+1822
+%%EOF

BIN
.manual/figures/icon_saveModel.pdf


+ 152 - 0
.manual/figures/icon_saveModelAs.pdf

@@ -0,0 +1,152 @@
+%PDF-1.4 
+1 0 obj
+<<
+/Pages 2 0 R
+/Type /Catalog
+>>
+endobj
+2 0 obj
+<<
+/Type /Pages
+/Kids [ 3 0 R ]
+/Count 1
+>>
+endobj
+3 0 obj
+<<
+/Type /Page
+/Parent 2 0 R
+/Resources <<
+/XObject << /Im0 8 0 R >>
+/ProcSet 6 0 R >>
+/MediaBox [0 0 31.996 31.996]
+/CropBox [0 0 31.996 31.996]
+/Contents 4 0 R
+/Thumb 11 0 R
+>>
+endobj
+4 0 obj
+<<
+/Length 5 0 R
+>>
+stream
+q
+31.996 0 0 31.996 0 0 cm
+/Im0 Do
+Q
+endstream
+endobj
+5 0 obj
+37
+endobj
+6 0 obj
+[ /PDF /Text /ImageC ]
+endobj
+7 0 obj
+<<
+>>
+endobj
+8 0 obj
+<<
+/Type /XObject
+/Subtype /Image
+/Name /Im0
+/Filter [ /FlateDecode ]
+/Width 32
+/Height 32
+/ColorSpace 10 0 R
+/BitsPerComponent 8
+/SMask 15 0 R
+/Length 9 0 R
+>>
+stream
+xÚ…Ñ‹r‚0„áíÝ­T[´Vð"¢8¥ïÿnÝ%N&´|c<˜P�ú÷¶º~þ‡s‡ªÃ‘N8Z'à¤yÇ’€Ò̧Ë(
+KÝ�9¹¥}wîØYÚwgJ@jiß�[¶–öݹ&`m­€•Ù3»K–-êš	I‹ºæ‚pƒÚ7µKݼ«}Q+WçJCmF^+@ó|¥ZD^/öû|߬Bíƒü/ϲ4Õ+ƒÚ„ü¾¹üS¨½“OqÌ»O’8^¨…ääyssð†oäžÏKžòŠ£h:›©äÿþxbŒ¡6$¿�Bcµ>yy¯A³šÖ£·÷\:g Ô{¾êÉ=^uo�:ÜuøMN³
+endstream
+endobj
+9 0 obj
+271
+endobj
+10 0 obj
+/DeviceGray
+endobj
+11 0 obj
+<<
+/Filter [ /FlateDecode ]
+/Width 32
+/Height 32
+/ColorSpace 10 0 R
+/BitsPerComponent 8
+/Length 12 0 R
+>>
+stream
+xÚ…Ñ‹r‚0„áíÝ­T[´Vð"¢8¥ïÿnÝ%N&´|c<˜P�ú÷¶º~þ‡s‡ªÃ‘N8Z'à¤yÇ’€Ò̧Ë(
+KÝ�9¹¥}wîØYÚwgJ@jiß�[¶–öݹ&`m­€•Ù3»K–-êš	I‹ºæ‚pƒÚ7µKݼ«}Q+WçJCmF^+@ó|¥ZD^/öû|߬Bíƒü/ϲ4Õ+ƒÚ„ü¾¹üS¨½“OqÌ»O’8^¨…ääyssð†oäžÏKžòŠ£h:›©äÿþxbŒ¡6$¿�Bcµ>yy¯A³šÖ£·÷\:g Ô{¾êÉ=^uo�:ÜuøMN³
+endstream
+endobj
+12 0 obj
+271
+endobj
+13 0 obj
+endobj
+14 0 obj
+271
+endobj
+15 0 obj
+<<
+/Type /XObject
+/Subtype /Image
+/Name /Ma0
+/Filter [ /FlateDecode ]
+/Width 32
+/Height 32
+/ColorSpace /DeviceGray
+/BitsPerComponent 8
+/Length 16 0 R
+>>
+stream
+xÚÍ‘Û
à E=‚GÉ�<Š7@ê"ŒÀŒÐá–EIZÚŸ*	ƒ�ð‹Š±êB„Ké„Cñ…Â!JK¹¬–(<7ß±
+¢~k†'œîÈ÷þ|þ§ŸëÇל¹¯1·kÎà!W!¬›º\�Ø`œ ~ü„Ò ?ä¶
+»œÅbVÒÍ‹oÝ‘Oýáý4ÿ´~§ÿàÏï�?¨Nð‚á�ó
+endstream
+endobj
+16 0 obj
+159
+endobj
+17 0 obj
+<<
+/Title (saveModelAs.icon.png.pdf)
+/CreationDate (D:20120322115414)
+/ModDate (D:20120322115414)
+/Producer (ImageMagick 6.6.0-4 2011-06-15 Q16 http://www.imagemagick.org)
+>>
+endobj
+xref
+0 18
+0000000000 65535 f 
+0000000010 00000 n 
+0000000059 00000 n 
+0000000118 00000 n 
+0000000312 00000 n 
+0000000401 00000 n 
+0000000419 00000 n 
+0000000457 00000 n 
+0000000478 00000 n 
+0000000943 00000 n 
+0000000962 00000 n 
+0000000990 00000 n 
+0000001401 00000 n 
+0000001421 00000 n 
+0000001437 00000 n 
+0000001457 00000 n 
+0000001803 00000 n 
+0000001823 00000 n 
+trailer
+<<
+/Size 18
+/Info 17 0 R
+/Root 1 0 R
+>>
+startxref
+2014
+%%EOF

BIN
.manual/figures/icon_step.pdf


BIN
.manual/figures/icon_stop.pdf


BIN
.manual/figures/icon_toggleVisibleContainmentLinks.pdf


BIN
.manual/figures/icon_toggledebug.pdf


BIN
.manual/figures/icon_togglemm.pdf


BIN
.manual/figures/icon_undo.pdf


BIN
.manual/figures/icon_validateM.pdf


BIN
.manual/figures/mainmenum.pdf


+ 120 - 0
.manual/manual.aux

@@ -0,0 +1,120 @@
+\relax 
+\providecommand\hyper@newdestlabel[2]{}
+\providecommand\HyperFirstAtBeginDocument{\AtBeginDocument}
+\HyperFirstAtBeginDocument{\ifx\hyper@anchor\@undefined
+\global\let\oldcontentsline\contentsline
+\gdef\contentsline#1#2#3#4{\oldcontentsline{#1}{#2}{#3}}
+\global\let\oldnewlabel\newlabel
+\gdef\newlabel#1#2{\newlabelxx{#1}#2}
+\gdef\newlabelxx#1#2#3#4#5#6{\oldnewlabel{#1}{{#2}{#3}}}
+\AtEndDocument{\ifx\hyper@anchor\@undefined
+\let\contentsline\oldcontentsline
+\let\newlabel\oldnewlabel
+\fi}
+\fi}
+\global\let\hyper@last\relax 
+\gdef\HyperFirstAtBeginDocument#1{#1}
+\providecommand\HyField@AuxAddToFields[1]{}
+\providecommand\HyField@AuxAddToCoFields[2]{}
+\@writefile{toc}{\contentsline {section}{\numberline {1}Client Overview}{4}{section.1}}
+\@writefile{toc}{\contentsline {subsection}{\numberline {1.1}Launching the AToMPM Client}{4}{subsection.1.1}}
+\@writefile{toc}{\contentsline {subsection}{\numberline {1.2}The Interface Components}{4}{subsection.1.2}}
+\@writefile{lof}{\contentsline {figure}{\numberline {1.1}{\ignorespaces The AToMPM client, with the \textit  {MainMenu} and \textit  {TransformationController} button toolbars loaded.\relax }}{4}{figure.caption.2}}
+\providecommand*\caption@xref[2]{\@setref\relax\@undefined{#1}}
+\newlabel{fig:atompm}{{1.1}{4}{The AToMPM client, with the \textit {MainMenu} and \textit {TransformationController} button toolbars loaded.\relax }{figure.caption.2}{}}
+\@writefile{toc}{\contentsline {subsection}{\numberline {1.3}The \textit  {MainMenu} Toolbar}{5}{subsection.1.3}}
+\newlabel{ssec:mainmenutb}{{1.3}{5}{The \textit {MainMenu} Toolbar}{subsection.1.3}{}}
+\@writefile{toc}{\contentsline {subsection}{\numberline {1.4}The \textit  {Utilities} Toolbar}{5}{subsection.1.4}}
+\newlabel{ssec:utilitiestb}{{1.4}{5}{The \textit {Utilities} Toolbar}{subsection.1.4}{}}
+\@writefile{toc}{\contentsline {subsection}{\numberline {1.5}The Canvas}{6}{subsection.1.5}}
+\newlabel{ssec:canvas}{{1.5}{6}{The Canvas}{subsection.1.5}{}}
+\@writefile{toc}{\contentsline {subsection}{\numberline {1.6}Collaboration}{8}{subsection.1.6}}
+\@writefile{toc}{\contentsline {subsection}{\numberline {1.7}Tweaking Default Settings}{8}{subsection.1.7}}
+\newlabel{ssec:prefs}{{1.7}{8}{Tweaking Default Settings}{subsection.1.7}{}}
+\@writefile{toc}{\contentsline {section}{\numberline {2}Modelling}{10}{section.2}}
+\newlabel{sec:m}{{2}{10}{Modelling}{section.2}{}}
+\@writefile{toc}{\contentsline {subsection}{\numberline {2.1}Creating Button Toolbar Models}{10}{subsection.2.1}}
+\@writefile{lof}{\contentsline {figure}{\numberline {2.1}{\ignorespaces The model that defines the MainMenu button toolbar.\relax }}{10}{figure.caption.3}}
+\newlabel{fig:mainmenum}{{2.1}{10}{The model that defines the MainMenu button toolbar.\relax }{figure.caption.3}{}}
+\@writefile{toc}{\contentsline {subsection}{\numberline {2.2}Client API}{10}{subsection.2.2}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function \_compileToASMM(fname)}}{10}{section*.4}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function \_compileToCSMM(fname)}}{11}{section*.4}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function \_compileToPatternMM(fname)}}{11}{section*.4}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function \_copy()}}{11}{section*.4}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function \_exportSVG(fname)}}{11}{section*.4}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function \_getUserPreferences(callback[,subset])}}{11}{section*.4}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function \_httpReq(method,url,params,onresponse[,sync])}}{11}{section*.4}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function \_insertModel(fname)}}{11}{section*.4}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function \_loadModel(fname)}}{12}{section*.4}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function \_loadToolbar(fname)}}{12}{section*.4}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function \_openDialog(type,args,callback)}}{12}{section*.4}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function \_paste()}}{14}{section*.4}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function \_redo()}}{14}{section*.4}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function \_saveModel([fname,backup])}}{14}{section*.4}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function \_setInvisibleMetamodels(mms)}}{14}{section*.4}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function \_setUserPreferences(prefs[,callback])}}{14}{section*.4}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function \_setTypeToCreate(fulltype)}}{14}{section*.4}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function \_spawnClient(fname,callbackURL)}}{14}{section*.4}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function \_spawnHeadlessClient(context,onready,onchlog)}}{14}{section*.4}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function \_undo()}}{15}{section*.4}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function \_unloadToolbar(tb)}}{15}{section*.4}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function \_validate()}}{15}{section*.4}}
+\@writefile{toc}{\contentsline {subsection}{\numberline {2.3}Remote API}{15}{subsection.2.3}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function \_highlight(args)}}{15}{section*.5}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function \_loadModelInNewWindow(args)}}{15}{section*.5}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function \_tag(args)}}{16}{section*.5}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function \_updateAttr(args)}}{16}{section*.5}}
+\@writefile{toc}{\contentsline {section}{\numberline {3}Specifying and Compiling Formalism Syntax Models}{17}{section.3}}
+\newlabel{sec:mm}{{3}{17}{Specifying and Compiling Formalism Syntax Models}{section.3}{}}
+\@writefile{toc}{\contentsline {subsection}{\numberline {3.1}Defining Abstract Syntax}{17}{subsection.3.1}}
+\@writefile{toc}{\contentsline {subsubsection}{\numberline {3.1.1}Types}{17}{subsubsection.3.1.1}}
+\@writefile{toc}{\contentsline {subsection}{\numberline {3.2}Defining Concrete Syntax}{17}{subsection.3.2}}
+\newlabel{fig:forestMM}{{3.1a}{18}{Subfigure 3.1a}{subfigure.3.1.1}{}}
+\newlabel{sub@fig:forestMM}{{(a)}{a}{Subfigure 3.1a\relax }{subfigure.3.1.1}{}}
+\newlabel{fig:forestCSMM1}{{3.1b}{18}{Subfigure 3.1b}{subfigure.3.1.2}{}}
+\newlabel{sub@fig:forestCSMM1}{{(b)}{b}{Subfigure 3.1b\relax }{subfigure.3.1.2}{}}
+\newlabel{fig:forestCSMM2}{{3.1c}{18}{Subfigure 3.1c}{subfigure.3.1.3}{}}
+\newlabel{sub@fig:forestCSMM2}{{(c)}{c}{Subfigure 3.1c\relax }{subfigure.3.1.3}{}}
+\@writefile{lof}{\contentsline {figure}{\numberline {3.1}{\ignorespaces (a) A very simple abstract syntax model and (b-c) two associated concrete syntax (or icon definition) models.\relax }}{18}{figure.caption.6}}
+\newlabel{fig:forestMM+CSMMs}{{3.1}{18}{(a) A very simple abstract syntax model and (b-c) two associated concrete syntax (or icon definition) models.\relax }{figure.caption.6}{}}
+\@writefile{lof}{\contentsline {subfigure}{\numberline{(a)}{\ignorespaces {}}}{18}{subfigure.1.1}}
+\@writefile{lof}{\contentsline {subfigure}{\numberline{(b)}{\ignorespaces {}}}{18}{subfigure.1.2}}
+\@writefile{lof}{\contentsline {subfigure}{\numberline{(c)}{\ignorespaces {}}}{18}{subfigure.1.3}}
+\@writefile{toc}{\contentsline {subsection}{\numberline {3.3}Meta-Modelling API}{19}{subsection.3.3}}
+\newlabel{ssec:mmmkapi}{{3.3}{19}{Meta-Modelling API}{subsection.3.3}{}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function getAttr(\_attr[,\_id])}}{19}{section*.7}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function getAllNodes([\_fulltypes])}}{19}{section*.7}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function getNeighbors(\_dir[,\_type,\_id])}}{19}{section*.7}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function hasAttr(\_attr[,\_id])}}{19}{section*.7}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function print(str)}}{19}{section*.7}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function setAttr(\_attr,\_val[,\_id])}}{20}{section*.7}}
+\@writefile{toc}{\contentsline {subsection}{\numberline {3.4}The \textit  {CompileMenu} Toolbar}{20}{subsection.3.4}}
+\newlabel{ssec:compiletb}{{3.4}{20}{The \textit {CompileMenu} Toolbar}{subsection.3.4}{}}
+\@writefile{toc}{\contentsline {section}{\numberline {4}Specifying and Executing Model Transformations}{21}{section.4}}
+\newlabel{sec:mt}{{4}{21}{Specifying and Executing Model Transformations}{section.4}{}}
+\@writefile{toc}{\contentsline {subsection}{\numberline {4.1}Specifying Transformation Rule Models}{21}{subsection.4.1}}
+\newlabel{ssec:trmm}{{4.1}{21}{Specifying Transformation Rule Models}{subsection.4.1}{}}
+\@writefile{toc}{\contentsline {subsection}{\numberline {4.2}Specifying Transformation Models}{21}{subsection.4.2}}
+\newlabel{ssec:tmm}{{4.2}{21}{Specifying Transformation Models}{subsection.4.2}{}}
+\@writefile{toc}{\contentsline {subsection}{\numberline {4.3}Transformation Rule API}{22}{subsection.4.3}}
+\newlabel{ssec:trapi}{{4.3}{22}{Transformation Rule API}{subsection.4.3}{}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function getAttr(\_attr[,\_id])}}{22}{section*.8}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function getAllNodes([\_fulltypes])}}{22}{section*.8}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function getNeighbors(\_dir[,\_type,\_id])}}{22}{section*.8}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function hasAttr(\_attr[,\_id])}}{22}{section*.8}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function print(str)}}{22}{section*.8}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function setAttr(\_attr,\_val[,\_id])}}{22}{section*.8}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function httpReq(method,host,url,data)}}{22}{section*.8}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function isConnectionType(\_id)}}{22}{section*.8}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function session\_get(\_key)}}{22}{section*.8}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function session\_put(\_key,\_val)}}{23}{section*.8}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function sys\_call(\_args)}}{23}{section*.8}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function sys\_mkdir(\_path)}}{23}{section*.8}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function sys\_readf(\_path)}}{23}{section*.8}}
+\@writefile{toc}{\contentsline {subsubsection}{\hskip 1em\relax \texttt  {function sys\_writef(\_path,\_content[,\_append])}}{23}{section*.8}}
+\@writefile{toc}{\contentsline {subsection}{\numberline {4.4}The \textit  {TransformationController} Toolbar}{23}{subsection.4.4}}
+\newlabel{ssec:transfctrltb}{{4.4}{23}{The \textit {TransformationController} Toolbar}{subsection.4.4}{}}
+\@writefile{toc}{\contentsline {section}{\numberline {5}Extending AToMPM \textit  {(To be completed) }}{24}{section.5}}
+\@writefile{toc}{\contentsline {section}{\numberline {A}Setting up the AToMPM back-end}{25}{appendix.A}}
+\@writefile{toc}{\contentsline {subsection}{\numberline {A.1}Installation}{25}{subsection.A.1}}
+\@writefile{toc}{\contentsline {subsection}{\numberline {A.2}Launch}{25}{subsection.A.2}}

+ 73 - 0
.manual/manual.out

@@ -0,0 +1,73 @@
+\BOOKMARK [1][-]{section.1}{Client Overview}{}% 1
+\BOOKMARK [2][-]{subsection.1.1}{Launching the AToMPM Client}{section.1}% 2
+\BOOKMARK [2][-]{subsection.1.2}{The Interface Components}{section.1}% 3
+\BOOKMARK [2][-]{subsection.1.3}{The MainMenu Toolbar}{section.1}% 4
+\BOOKMARK [2][-]{subsection.1.4}{The Utilities Toolbar}{section.1}% 5
+\BOOKMARK [2][-]{subsection.1.5}{The Canvas}{section.1}% 6
+\BOOKMARK [2][-]{subsection.1.6}{Collaboration}{section.1}% 7
+\BOOKMARK [2][-]{subsection.1.7}{Tweaking Default Settings}{section.1}% 8
+\BOOKMARK [1][-]{section.2}{Modelling}{}% 9
+\BOOKMARK [2][-]{subsection.2.1}{Creating Button Toolbar Models}{section.2}% 10
+\BOOKMARK [2][-]{subsection.2.2}{Client API}{section.2}% 11
+\BOOKMARK [3][-]{section*.4}{function \137compileToASMM\(fname\)}{subsection.2.2}% 12
+\BOOKMARK [3][-]{section*.4}{function \137compileToCSMM\(fname\)}{subsection.2.2}% 13
+\BOOKMARK [3][-]{section*.4}{function \137compileToPatternMM\(fname\)}{subsection.2.2}% 14
+\BOOKMARK [3][-]{section*.4}{function \137copy\(\)}{subsection.2.2}% 15
+\BOOKMARK [3][-]{section*.4}{function \137exportSVG\(fname\)}{subsection.2.2}% 16
+\BOOKMARK [3][-]{section*.4}{function \137getUserPreferences\(callback[,subset]\)}{subsection.2.2}% 17
+\BOOKMARK [3][-]{section*.4}{function \137httpReq\(method,url,params,onresponse[,sync]\)}{subsection.2.2}% 18
+\BOOKMARK [3][-]{section*.4}{function \137insertModel\(fname\)}{subsection.2.2}% 19
+\BOOKMARK [3][-]{section*.4}{function \137loadModel\(fname\)}{subsection.2.2}% 20
+\BOOKMARK [3][-]{section*.4}{function \137loadToolbar\(fname\)}{subsection.2.2}% 21
+\BOOKMARK [3][-]{section*.4}{function \137openDialog\(type,args,callback\)}{subsection.2.2}% 22
+\BOOKMARK [3][-]{section*.4}{function \137paste\(\)}{subsection.2.2}% 23
+\BOOKMARK [3][-]{section*.4}{function \137redo\(\)}{subsection.2.2}% 24
+\BOOKMARK [3][-]{section*.4}{function \137saveModel\([fname,backup]\)}{subsection.2.2}% 25
+\BOOKMARK [3][-]{section*.4}{function \137setInvisibleMetamodels\(mms\)}{subsection.2.2}% 26
+\BOOKMARK [3][-]{section*.4}{function \137setUserPreferences\(prefs[,callback]\)}{subsection.2.2}% 27
+\BOOKMARK [3][-]{section*.4}{function \137setTypeToCreate\(fulltype\)}{subsection.2.2}% 28
+\BOOKMARK [3][-]{section*.4}{function \137spawnClient\(fname,callbackURL\)}{subsection.2.2}% 29
+\BOOKMARK [3][-]{section*.4}{function \137spawnHeadlessClient\(context,onready,onchlog\)}{subsection.2.2}% 30
+\BOOKMARK [3][-]{section*.4}{function \137undo\(\)}{subsection.2.2}% 31
+\BOOKMARK [3][-]{section*.4}{function \137unloadToolbar\(tb\)}{subsection.2.2}% 32
+\BOOKMARK [3][-]{section*.4}{function \137validate\(\)}{subsection.2.2}% 33
+\BOOKMARK [2][-]{subsection.2.3}{Remote API}{section.2}% 34
+\BOOKMARK [3][-]{section*.5}{function \137highlight\(args\)}{subsection.2.3}% 35
+\BOOKMARK [3][-]{section*.5}{function \137loadModelInNewWindow\(args\)}{subsection.2.3}% 36
+\BOOKMARK [3][-]{section*.5}{function \137tag\(args\)}{subsection.2.3}% 37
+\BOOKMARK [3][-]{section*.5}{function \137updateAttr\(args\)}{subsection.2.3}% 38
+\BOOKMARK [1][-]{section.3}{Specifying and Compiling Formalism Syntax Models}{}% 39
+\BOOKMARK [2][-]{subsection.3.1}{Defining Abstract Syntax}{section.3}% 40
+\BOOKMARK [3][-]{subsubsection.3.1.1}{Types}{subsection.3.1}% 41
+\BOOKMARK [2][-]{subsection.3.2}{Defining Concrete Syntax}{section.3}% 42
+\BOOKMARK [2][-]{subsection.3.3}{Meta-Modelling API}{section.3}% 43
+\BOOKMARK [3][-]{section*.7}{function getAttr\(\137attr[,\137id]\)}{subsection.3.3}% 44
+\BOOKMARK [3][-]{section*.7}{function getAllNodes\([\137fulltypes]\)}{subsection.3.3}% 45
+\BOOKMARK [3][-]{section*.7}{function getNeighbors\(\137dir[,\137type,\137id]\)}{subsection.3.3}% 46
+\BOOKMARK [3][-]{section*.7}{function hasAttr\(\137attr[,\137id]\)}{subsection.3.3}% 47
+\BOOKMARK [3][-]{section*.7}{function print\(str\)}{subsection.3.3}% 48
+\BOOKMARK [3][-]{section*.7}{function setAttr\(\137attr,\137val[,\137id]\)}{subsection.3.3}% 49
+\BOOKMARK [2][-]{subsection.3.4}{The CompileMenu Toolbar}{section.3}% 50
+\BOOKMARK [1][-]{section.4}{Specifying and Executing Model Transformations}{}% 51
+\BOOKMARK [2][-]{subsection.4.1}{Specifying Transformation Rule Models}{section.4}% 52
+\BOOKMARK [2][-]{subsection.4.2}{Specifying Transformation Models}{section.4}% 53
+\BOOKMARK [2][-]{subsection.4.3}{Transformation Rule API}{section.4}% 54
+\BOOKMARK [3][-]{section*.8}{function getAttr\(\137attr[,\137id]\)}{subsection.4.3}% 55
+\BOOKMARK [3][-]{section*.8}{function getAllNodes\([\137fulltypes]\)}{subsection.4.3}% 56
+\BOOKMARK [3][-]{section*.8}{function getNeighbors\(\137dir[,\137type,\137id]\)}{subsection.4.3}% 57
+\BOOKMARK [3][-]{section*.8}{function hasAttr\(\137attr[,\137id]\)}{subsection.4.3}% 58
+\BOOKMARK [3][-]{section*.8}{function print\(str\)}{subsection.4.3}% 59
+\BOOKMARK [3][-]{section*.8}{function setAttr\(\137attr,\137val[,\137id]\)}{subsection.4.3}% 60
+\BOOKMARK [3][-]{section*.8}{function httpReq\(method,host,url,data\)}{subsection.4.3}% 61
+\BOOKMARK [3][-]{section*.8}{function isConnectionType\(\137id\)}{subsection.4.3}% 62
+\BOOKMARK [3][-]{section*.8}{function session\137get\(\137key\)}{subsection.4.3}% 63
+\BOOKMARK [3][-]{section*.8}{function session\137put\(\137key,\137val\)}{subsection.4.3}% 64
+\BOOKMARK [3][-]{section*.8}{function sys\137call\(\137args\)}{subsection.4.3}% 65
+\BOOKMARK [3][-]{section*.8}{function sys\137mkdir\(\137path\)}{subsection.4.3}% 66
+\BOOKMARK [3][-]{section*.8}{function sys\137readf\(\137path\)}{subsection.4.3}% 67
+\BOOKMARK [3][-]{section*.8}{function sys\137writef\(\137path,\137content[,\137append]\)}{subsection.4.3}% 68
+\BOOKMARK [2][-]{subsection.4.4}{The TransformationController Toolbar}{section.4}% 69
+\BOOKMARK [1][-]{section.5}{Extending AToMPM \(To be completed\) }{}% 70
+\BOOKMARK [1][-]{appendix.A}{Setting up the AToMPM back-end}{}% 71
+\BOOKMARK [2][-]{subsection.A.1}{Installation}{appendix.A}% 72
+\BOOKMARK [2][-]{subsection.A.2}{Launch}{appendix.A}% 73

BIN
.manual/manual.pdf


BIN
.manual/manual.synctex.gz


File diff suppressed because it is too large
+ 931 - 0
.manual/manual.tex


+ 73 - 0
.manual/manual.toc

@@ -0,0 +1,73 @@
+\contentsline {section}{\numberline {1}Client Overview}{4}{section.1}
+\contentsline {subsection}{\numberline {1.1}Launching the AToMPM Client}{4}{subsection.1.1}
+\contentsline {subsection}{\numberline {1.2}The Interface Components}{4}{subsection.1.2}
+\contentsline {subsection}{\numberline {1.3}The \textit {MainMenu} Toolbar}{5}{subsection.1.3}
+\contentsline {subsection}{\numberline {1.4}The \textit {Utilities} Toolbar}{5}{subsection.1.4}
+\contentsline {subsection}{\numberline {1.5}The Canvas}{6}{subsection.1.5}
+\contentsline {subsection}{\numberline {1.6}Collaboration}{8}{subsection.1.6}
+\contentsline {subsection}{\numberline {1.7}Tweaking Default Settings}{8}{subsection.1.7}
+\contentsline {section}{\numberline {2}Modelling}{10}{section.2}
+\contentsline {subsection}{\numberline {2.1}Creating Button Toolbar Models}{10}{subsection.2.1}
+\contentsline {subsection}{\numberline {2.2}Client API}{10}{subsection.2.2}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function \_compileToASMM(fname)}}{10}{section*.4}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function \_compileToCSMM(fname)}}{11}{section*.4}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function \_compileToPatternMM(fname)}}{11}{section*.4}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function \_copy()}}{11}{section*.4}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function \_exportSVG(fname)}}{11}{section*.4}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function \_getUserPreferences(callback[,subset])}}{11}{section*.4}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function \_httpReq(method,url,params,onresponse[,sync])}}{11}{section*.4}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function \_insertModel(fname)}}{11}{section*.4}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function \_loadModel(fname)}}{12}{section*.4}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function \_loadToolbar(fname)}}{12}{section*.4}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function \_openDialog(type,args,callback)}}{12}{section*.4}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function \_paste()}}{14}{section*.4}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function \_redo()}}{14}{section*.4}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function \_saveModel([fname,backup])}}{14}{section*.4}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function \_setInvisibleMetamodels(mms)}}{14}{section*.4}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function \_setUserPreferences(prefs[,callback])}}{14}{section*.4}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function \_setTypeToCreate(fulltype)}}{14}{section*.4}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function \_spawnClient(fname,callbackURL)}}{14}{section*.4}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function \_spawnHeadlessClient(context,onready,onchlog)}}{14}{section*.4}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function \_undo()}}{15}{section*.4}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function \_unloadToolbar(tb)}}{15}{section*.4}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function \_validate()}}{15}{section*.4}
+\contentsline {subsection}{\numberline {2.3}Remote API}{15}{subsection.2.3}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function \_highlight(args)}}{15}{section*.5}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function \_loadModelInNewWindow(args)}}{15}{section*.5}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function \_tag(args)}}{16}{section*.5}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function \_updateAttr(args)}}{16}{section*.5}
+\contentsline {section}{\numberline {3}Specifying and Compiling Formalism Syntax Models}{17}{section.3}
+\contentsline {subsection}{\numberline {3.1}Defining Abstract Syntax}{17}{subsection.3.1}
+\contentsline {subsubsection}{\numberline {3.1.1}Types}{17}{subsubsection.3.1.1}
+\contentsline {subsection}{\numberline {3.2}Defining Concrete Syntax}{17}{subsection.3.2}
+\contentsline {subsection}{\numberline {3.3}Meta-Modelling API}{19}{subsection.3.3}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function getAttr(\_attr[,\_id])}}{19}{section*.7}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function getAllNodes([\_fulltypes])}}{19}{section*.7}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function getNeighbors(\_dir[,\_type,\_id])}}{19}{section*.7}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function hasAttr(\_attr[,\_id])}}{19}{section*.7}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function print(str)}}{19}{section*.7}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function setAttr(\_attr,\_val[,\_id])}}{20}{section*.7}
+\contentsline {subsection}{\numberline {3.4}The \textit {CompileMenu} Toolbar}{20}{subsection.3.4}
+\contentsline {section}{\numberline {4}Specifying and Executing Model Transformations}{21}{section.4}
+\contentsline {subsection}{\numberline {4.1}Specifying Transformation Rule Models}{21}{subsection.4.1}
+\contentsline {subsection}{\numberline {4.2}Specifying Transformation Models}{21}{subsection.4.2}
+\contentsline {subsection}{\numberline {4.3}Transformation Rule API}{22}{subsection.4.3}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function getAttr(\_attr[,\_id])}}{22}{section*.8}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function getAllNodes([\_fulltypes])}}{22}{section*.8}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function getNeighbors(\_dir[,\_type,\_id])}}{22}{section*.8}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function hasAttr(\_attr[,\_id])}}{22}{section*.8}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function print(str)}}{22}{section*.8}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function setAttr(\_attr,\_val[,\_id])}}{22}{section*.8}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function httpReq(method,host,url,data)}}{22}{section*.8}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function isConnectionType(\_id)}}{22}{section*.8}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function session\_get(\_key)}}{22}{section*.8}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function session\_put(\_key,\_val)}}{23}{section*.8}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function sys\_call(\_args)}}{23}{section*.8}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function sys\_mkdir(\_path)}}{23}{section*.8}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function sys\_readf(\_path)}}{23}{section*.8}
+\contentsline {subsubsection}{\hskip 1em\relax \texttt {function sys\_writef(\_path,\_content[,\_append])}}{23}{section*.8}
+\contentsline {subsection}{\numberline {4.4}The \textit {TransformationController} Toolbar}{23}{subsection.4.4}
+\contentsline {section}{\numberline {5}Extending AToMPM \textit {(To be completed) }}{24}{section.5}
+\contentsline {section}{\numberline {A}Setting up the AToMPM back-end}{25}{appendix.A}
+\contentsline {subsection}{\numberline {A.1}Installation}{25}{subsection.A.1}
+\contentsline {subsection}{\numberline {A.2}Launch}{25}{subsection.A.2}

+ 674 - 0
COPYING

@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.

+ 165 - 0
COPYING.LESSER

@@ -0,0 +1,165 @@
+                   GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+  This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+  0. Additional Definitions.
+
+  As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+  "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+  An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+  A "Combined Work" is a work produced by combining or linking an
+Application with the Library.  The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+  The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+  The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+  1. Exception to Section 3 of the GNU GPL.
+
+  You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+  2. Conveying Modified Versions.
+
+  If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+   a) under this License, provided that you make a good faith effort to
+   ensure that, in the event an Application does not supply the
+   function or data, the facility still operates, and performs
+   whatever part of its purpose remains meaningful, or
+
+   b) under the GNU GPL, with none of the additional permissions of
+   this License applicable to that copy.
+
+  3. Object Code Incorporating Material from Library Header Files.
+
+  The object code form of an Application may incorporate material from
+a header file that is part of the Library.  You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+   a) Give prominent notice with each copy of the object code that the
+   Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the object code with a copy of the GNU GPL and this license
+   document.
+
+  4. Combined Works.
+
+  You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+   a) Give prominent notice with each copy of the Combined Work that
+   the Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
+   document.
+
+   c) For a Combined Work that displays copyright notices during
+   execution, include the copyright notice for the Library among
+   these notices, as well as a reference directing the user to the
+   copies of the GNU GPL and this license document.
+
+   d) Do one of the following:
+
+       0) Convey the Minimal Corresponding Source under the terms of this
+       License, and the Corresponding Application Code in a form
+       suitable for, and under terms that permit, the user to
+       recombine or relink the Application with a modified version of
+       the Linked Version to produce a modified Combined Work, in the
+       manner specified by section 6 of the GNU GPL for conveying
+       Corresponding Source.
+
+       1) Use a suitable shared library mechanism for linking with the
+       Library.  A suitable mechanism is one that (a) uses at run time
+       a copy of the Library already present on the user's computer
+       system, and (b) will operate properly with a modified version
+       of the Library that is interface-compatible with the Linked
+       Version.
+
+   e) Provide Installation Information, but only if you would otherwise
+   be required to provide such information under section 6 of the
+   GNU GPL, and only to the extent that such information is
+   necessary to install and execute a modified version of the
+   Combined Work produced by recombining or relinking the
+   Application with a modified version of the Linked Version. (If
+   you use option 4d0, the Installation Information must accompany
+   the Minimal Corresponding Source and Corresponding Application
+   Code. If you use option 4d1, you must provide the Installation
+   Information in the manner specified by section 6 of the GNU GPL
+   for conveying Corresponding Source.)
+
+  5. Combined Libraries.
+
+  You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+   a) Accompany the combined library with a copy of the same work based
+   on the Library, uncombined with any other library facilities,
+   conveyed under the terms of this License.
+
+   b) Give prominent notice with the combined library that part of it
+   is a work based on the Library, and explaining where to find the
+   accompanying uncombined form of the same work.
+
+  6. Revised Versions of the GNU Lesser General Public License.
+
+  The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+  Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+  If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.

+ 56 - 0
___dataurize.js

@@ -0,0 +1,56 @@
+/*******************************************************************************
+AToMPM - A Tool for Multi-Paradigm Modelling
+
+Copyright (c) 2011 Raphael Mannadiar (raphael.mannadiar@mail.mcgill.ca)
+
+This file is part of AToMPM.
+
+AToMPM is free software: you can redistribute it and/or modify it under the
+terms of the GNU Lesser General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later 
+version.
+
+AToMPM is distributed in the hope that it will be useful, but WITHOUT ANY 
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with AToMPM.  If not, see <http://www.gnu.org/licenses/>.
+*******************************************************************************/
+
+
+/* return a datauri encoding of the resource at the given url */
+exports.dataurize = 
+	function(url,callback)
+	{
+		var request = 
+			require('http').request(
+				{'host':url.hostname || 'localhost', 
+				 'port':url.port 		|| 80, 
+				 'path':url.path 		|| '/'},
+				function(resp)
+				{
+					var data = '';
+					resp.setEncoding('binary');
+					resp.on('data', function(chunk) {data += chunk;});
+					resp.on('end',
+						function()
+						{
+							if( resp.statusCode == 200 )
+								callback(
+									undefined,
+									'data:'+resp.headers['content-type']+';base64,'+
+										new Buffer(data,'binary').toString('base64'));
+							else
+								callback({'statusCode':resp.statusCode, 'reason':data});
+						});
+				});
+			 request.on('error',
+				 function(err)
+				 {
+					 callback({'statusCode':0, 'reason':err});
+				 });
+	
+			 request.end();
+		};
+

+ 189 - 0
___do.js

@@ -0,0 +1,189 @@
+/*******************************************************************************
+AToMPM - A Tool for Multi-Paradigm Modelling
+REF:: https://github.com/creationix/do (+ .setup/do.js.patch)
+
+Copyright (c) 2011 Raphael Mannadiar (raphael.mannadiar@mail.mcgill.ca)
+
+This file is part of AToMPM.
+
+AToMPM is free software: you can redistribute it and/or modify it under the
+terms of the GNU Lesser General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later 
+version.
+
+AToMPM is distributed in the hope that it will be useful, but WITHOUT ANY 
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with AToMPM.  If not, see <http://www.gnu.org/licenses/>.
+*******************************************************************************/
+
+// Takes an array of actions and runs them all in parallel.
+// You can either pass in an array of actions, or several actions
+// as function arguments.
+// If you pass in an array, then the output will be an array of all the results
+// If you pass in separate arguments, then the output will have several arguments.
+exports.parallel = function parallel(actions) {
+  if (!(Object.prototype.toString.call(actions) === '[object Array]')) {
+    actions = Array.prototype.slice.call(arguments);
+    var direct = true;
+  }
+  return function(callback, errback) {
+    var results = [],
+        counter = actions.length;
+    actions.forEach(function (action, i) {
+      action(function (result) {
+        results[i] = result;
+        counter--;
+        if (counter <= 0) {
+          if (direct) {
+            callback.apply(null, results);
+          } else {
+            callback.call(null, results);
+          }
+        }
+      }, errback);
+    });
+  }
+};
+
+// Chains together several actions feeding the output of the first to the
+// input of the second and the final output to the callback
+exports.chain = function chain(actions) {
+  if (!(Object.prototype.toString.call(actions) === '[object Array]')) {
+    actions = Array.prototype.slice.call(arguments);
+  }
+  return function(callback, errback) {
+    var pos = 0;
+    var length = actions.length;
+    function loop(result) {
+      pos++;
+      if (pos >= length) {
+        callback(result);
+      } else {
+        actions[pos](result)(loop, errback);
+      }
+    }
+    actions[pos](loop, errback);
+  }
+}
+
+// Takes an array and does an array map over it using the async callback `fn`
+// The signature of `fn` is `function fn(item, callback, errback)`
+exports.map = function map(array, fn) { return function (callback, errback) {
+  var counter = array.length;
+  var new_array = [];
+  array.forEach(function (item, index) {
+    var local_callback = function (result) {
+      new_array[index] = result;
+      counter--;
+      if (counter <= 0) {
+        new_array.length = array.length
+        callback(new_array);
+      }
+    };
+    var cont = fn(item, local_callback, errback);
+    if (typeof cont === 'function') {
+      cont(local_callback, errback);
+    }
+  });
+}}
+
+// Takes an array and does an array filter over it using the async callback `fn`
+// The signature of `fn` is `function fn(item, callback, errback)`
+exports.filter = function filter(array, fn) { return function (callback, errback) {
+  var counter = array.length;
+  var valid = {};
+  array.forEach(function (item, index) {
+    var local_callback = function (result) {
+      valid[index] = result;
+      counter--;
+      if (counter <= 0) {
+        var result = [];
+        array.forEach(function (item, index) {
+          if (valid[index]) {
+            result.push(item);
+          }
+        });
+        callback(result);
+      }
+    };
+    var cont = fn(item, local_callback, errback);
+    if (typeof cont === 'function') {
+      cont(local_callback, errback);
+    }
+  });
+}}
+
+// Takes an array and does a combined filter and map over it.  If the result
+// of an item is undefined, then it's filtered out, otherwise it's mapped in.
+// The signature of `fn` is `function fn(item, callback, errback)`
+exports.filterMap = function filterMap(array, fn) { return function (callback, errback) {
+  var counter = array.length;
+  var new_array = [];
+  array.forEach(function (item, index) {
+    var local_callback = function (result) {
+      new_array[index] = result;
+      counter--;
+      if (counter <= 0) {
+        new_array.length = array.length;
+        callback(new_array.filter(function (item) {
+          return typeof item !== 'undefined';
+        }));
+      }
+    };
+    var cont = fn(item, local_callback, errback);
+    if (typeof cont === 'function') {
+      cont(local_callback, errback);
+    }
+  });
+}};
+
+// Allows to group several callbacks.
+exports.combo = function combo(callback) {
+  var items = 0,
+      results = [];
+  function add() {
+    var id = items;
+    items++;
+    return function () {
+      check(id, arguments);
+    };
+  }
+  function check(id, arguments) {
+    results[id] = Array.prototype.slice.call(arguments);
+    items--;
+    if (items == 0) {
+      callback.apply(this, results);
+    }
+  }
+  return { add: add, check: check };
+}
+
+
+// Takes any async lib that uses callback based signatures and converts
+// the specified names to continuable style and returns the new library.
+exports.convert = function (lib, names) {
+  var newlib = {};
+  Object.keys(lib).forEach(function (key) {
+    if (names.indexOf(key) < 0) {
+      return newlib[key] = lib[key];
+    }
+    newlib[key] = function () {
+      var args = Array.prototype.slice.call(arguments);
+      return function (callback, errback) {
+        args.push(function (err, val) {
+          if (err) {
+            errback(err);
+          } else {
+            callback(val);
+          }
+        });
+        lib[key].apply(lib, args)
+      }
+    }
+  });
+  return newlib;
+}
+

+ 190 - 0
___fs++.js

@@ -0,0 +1,190 @@
+/*******************************************************************************
+AToMPM - A Tool for Multi-Paradigm Modelling
+
+Copyright (c) 2011 Raphael Mannadiar (raphael.mannadiar@mail.mcgill.ca)
+
+This file is part of AToMPM.
+
+AToMPM is free software: you can redistribute it and/or modify it under the
+terms of the GNU Lesser General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later 
+version.
+
+AToMPM is distributed in the hope that it will be useful, but WITHOUT ANY 
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with AToMPM.  If not, see <http://www.gnu.org/licenses/>.
+*******************************************************************************/
+
+
+/* this file contains platform-oblivious wrappers for various windows and unix 
+	commands... it's purpose is to hide ugly platform-specific details and to 
+  	provide higher-level functions that those provided by nodejs' fs module */
+
+var _cp = require('child_process'),
+	 _os = require('os');
+
+
+/* NOTE:: because microsoft has apparently diversified into hiring comedians,
+			 robocopy can succeed with non-0 exit codes */
+exports.cp =
+	function(src,dest,callback)
+	{
+		switch(_os.type())
+		{
+			case 'Windows_NT' :
+				_cp.exec('robocopy "'+src+'" "'+dest+'" /e',
+					function(err,stdout,stderr)
+					{
+						if( err && err.code == 1 )
+							callback(undefined,stdout,stderr);
+						else
+							callback(err,stdout,stderr);
+					});
+				break;
+				
+			case 'Linux'  :
+			case 'Darwin' :
+				_cp.exec('cp -R "'+src+'" "'+dest+'"',callback);
+				break;
+
+			default:
+				throw 'unsupported OS :: '+_os.type();
+		}
+	};
+
+
+
+/* NOTE :: in MS-DOS dir, results are absolute windows paths... these must be 
+			  converted to unix paths and relativized to the atompm root 
+ 
+ 	NOTE :: in both cases, the return value's last entry is '\n'... we slice it
+			  off to avoid dumb problems when later on when splitting on '\n' */
+exports.findfiles =
+	function(dir,callback)
+	{
+		switch(_os.type())
+		{
+			case 'Windows_NT' :
+				_cp.exec('dir /s /b /a:-d "'+dir+'"',
+					function(err, stdout, stderr)
+					{
+						if( err )
+							callback(err,stdout,stderr);
+						else
+						{
+							var windir = (dir.charAt(0)=='.' ? dir.substring(1) : dir).
+												replace(/\//g,'\\'),
+								 paths  = stdout.split('\r\n').map(
+									function(path)
+									{
+										return dir+path.substring(
+														path.indexOf(windir)+windir.length).
+													  replace(/\\/g,'/');
+									});
+							paths.pop();
+							callback(err,paths.join('\n'),stderr);
+						}
+					});
+				break;
+				
+			case 'Linux'  :
+			case 'Darwin' :
+				_cp.exec('find "'+dir+'" -type f',
+					function(err, stdout, stderr)
+					{
+						if( err )
+							callback(err,stdout,stderr);
+						else
+							callback(err,stdout.slice(0,-1),stderr);
+					});
+				break;
+
+			default:
+				throw 'unsupported OS :: '+_os.type();
+		}
+	};
+
+
+
+/* NOTE :: in MS-DOS mkdir, trying to make an already existing directory 
+			  triggers an error which we ignore */
+exports.mkdirs =
+	function(dir,callback)
+	{
+		switch(_os.type())
+		{
+			case 'Windows_NT' :
+				_cp.exec('mkdir "'+dir.replace(/\//g,'\\')+'"',
+					function(err,stdout,stderr)
+					{
+                        /* 	TODO :: This is language specific. If a user is using a version of Windows which is in another language, the save will fail.
+							This function should only be executed if the folder does not exist. */
+						if( String(err).match(
+			/Error: Command failed: A subdirectory or file .* already exists./) )
+							callback(undefined,stdout,stderr);
+						else
+							callback(err,stdout,stderr);
+					});
+				break;
+				
+			case 'Linux'  :
+			case 'Darwin' :
+				_cp.exec('mkdir -p "'+dir+'"',callback);
+				break;
+
+			default:
+				throw 'unsupported OS :: '+_os.type();
+		}
+	};
+
+
+
+exports.mv =
+	function(src,dest,callback)
+	{
+		switch(_os.type())
+		{
+			case 'Windows_NT' :
+				_cp.exec('move /y "'+src+'" "'+dest+'"',callback);
+				break;
+				
+			case 'Linux'  :
+			case 'Darwin' :
+				_cp.exec('mv "'+src+'" "'+dest+'"',callback);
+				break;
+
+			default:
+				throw 'unsupported OS :: '+_os.type();
+		}
+	};
+
+
+
+exports.rmdirs =
+	function(dir,callback)
+	{
+		switch(_os.type())
+		{
+			case 'Windows_NT' :
+				_cp.exec('rmdir /s "'+dir+'"',callback);
+				break;
+				
+			case 'Linux'  :
+			case 'Darwin' :
+				_cp.exec('rm -rf "'+dir+'"',callback);
+				break;
+
+			default:
+				throw 'unsupported OS :: '+_os.type();
+		}
+	};
+
+
+
+
+	
+	
+

+ 859 - 0
__worker.js

@@ -0,0 +1,859 @@
+/*******************************************************************************
+AToMPM - A Tool for Multi-Paradigm Modelling
+
+Copyright (c) 2011 Raphael Mannadiar (raphael.mannadiar@mail.mcgill.ca)
+
+This file is part of AToMPM.
+
+AToMPM is free software: you can redistribute it and/or modify it under the
+terms of the GNU Lesser General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later 
+version.
+
+AToMPM is distributed in the hope that it will be useful, but WITHOUT ANY 
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with AToMPM.  If not, see <http://www.gnu.org/licenses/>.
+*******************************************************************************/
+
+/* NOTES: 
+	because of the *asynchronous* nature of numerous operations in our system, 
+	brought about by client requests coming to csworkers who asynchronously 
+	forward them to asworkers that asynchronously return changelogs to subscribed
+  	csworkers who later asynchronously return their own changelogs to subscribed 
+	clients, CONCURRENCY CONTROL is needed to ensure weird interleavings of 
+	operations and/or responses don't bring the system (either *worker or the
+	client) into incoherent or undesirable states... 
+
+	our approach is inspired by TCP and Readers-Writer locks :
+		1. 'read' requests increment the number of readers (read requests are
+			either GETs or have uris that start with "/GET/")
+		2. 'write' requests increment the number of writers
+		3. locking read requests also read-lock this *worker (the only such query
+			is 'POST /GET/batchRead')
+		4. locking write requests also write-lock this *worker (there is currently
+			no such query... see *)
+		5. before processing incoming requests, onmessage() calls __canProceed()..
+			this method succeeds when the current configuration of readers, writers
+			and locks allows the incoming query to run immediately... otherwise, 
+			(e.g., if there is more than one reader or writer and a write lock is
+			needed, or if the request queue is not-empty), this method fails, which
+			triggers the queueing of the incoming request for handling asap... this
+		  	essentially buys us a big 'synchronize' block around operations that 
+			change the state of _mmmk (and its journal), operations which are 
+			meant to be atomic...
+		6. individual requests that make up batchEdits bypass __canProceed(): 
+			these may need to write to a *worker that the initial 'POST /batchEdit'
+		  	locked... specifying a valid 'backstagePass' in the uri allows them to
+		  	skip over __canProceed()
+		7. other than possible delays in query handling, queriers (be they clients
+	  		or csworkers) are oblivious to the whole locking scheme... they are not
+			expected to ask for locks (these are granted automatically) or to 
+			explicitly release them (locks and increments to reader/writer counts 
+			produced by a request are appropriately 'adjusted' upon receiving a 
+			response to the said request)
+		*. although one might think that 'POST /batchEdit' requests would write-
+			lock this *worker, they don't... instead, they read-lock this *worker
+			and increment the number of writers... this has 2 side-effects 
+				a) no one can write to this *worker during a batchEdit
+				b) people can still read from this *worker... thus, the 
+					'intermediate' state of the system is visible to all
+			this is important since effects of batchEdits may include the re-
+			evaluation of VisualObject mapping functions... if the asworker were to
+		  	reject such reads, the user would need to 'refresh' after each 
+			batchEdit to sync up his icons... this all stems from the fact that we
+		  	preferred not to 'catch/queue' changelogs emitted by the asworker 
+			during batchEdits
+	TBI: 
+		perhaps the most important point to improve on here is that locking
+		queries could be made to lock individual objects (e.g., one or more AS
+		node) rather than the entire *worker... this is not necessarily difficult
+	  	to implement: before accessing any object for reading or writing in
+	  	_mmmk, check if its locked and/or lock it 
+
+
+	the use of our verbose URIs in HTTP requests is mostly useful for debugging 
+	and to enhance the RESTful feel of our HTTP exchanges: in most cases, IDs are
+  	sufficient to uniquely identify and refer to nodes, and their associated URIs
+  	can easily be computed... this fact, as well as performance concerns (e.g.,
+  	minimizing bandwith needs and string matching) is behind our decision not to
+	'URIZE' changelogs sent from asworker to csworker, i.e., changelogs refer to 
+	nodes by ID rather than by URI... although this is acceptable in the backend,
+	referring to nodes by ID in client-bound changelogs is not... thus, before
+	sending changelogs to the client, any IDs they may contain are replaced by 
+	URIs (via __urizeChangelog())
+
+ 
+	TBI:
+		eventually, it may be possible to entirely strip out the sequence 
+		numbering mechanism... this is contingent (at least) on the WebSocket 
+		protocol and its implementations guaranteeing that messages are always 
+		delivered in order
+	  
+	
+	supporting ATOMIC UNDO/REDO OF BATCHEDITS requires that we keep track of 
+	which operations happened as part of the same batchEdit... this can get hairy
+	in several cases, e.g., when:
+		1. batched requests are individually piped through the csworker to the 
+			asworker who thus has no way of knowing it should remember they're 
+			batched
+		2. certain requests are handled by the csworker while others are forwarded
+			to the asworker which prevents any of the workers of really knowing 
+			which are the first and last requests in the batchEdit
+	to address this, we place easily identifiable user-checkpoints in both the
+	asworker and csworker journals at the start and end of any batchEdit via POST
+	/batchCheckpoint (in practice, these are handled by asworkers who report
+	setting these checkpoints via changelogs such that all subscribed csworkers
+	can set them as well)... given that possibly dispersed and/or delocalized
+	operations now all reside between identically named user-checkpoints on both
+	asworker and csworker, we can undo/redo them atomically on both workers by 
+	undoing/redoing everything between the start and end checkpoints... this 
+	special undo/redo behavior is implemented in csworker.__undoredo (see its
+	comments for details) 
+	
+	
+	it is assumed that csworker, asworker, mmmk and libmt are only ever imported
+  	from this file and as such inherit all of its imported libraries */
+
+
+
+/**************************** LIBRARIES and GLOBALS ****************************/
+var  _util 	= require('util'),
+	 _path 	= require('path'),
+	 _http 	= require('http'),
+	 _do  	= require('./___do'),
+	 _fs 	 	= _do.convert(require('fs'), ['readFile', 'writeFile', 'readdir']),
+	 _fspp	= _do.convert(require('./___fs++'), ['mkdirs']),	 
+	 _siocl	= require('socket.io-client'),
+	 _utils	= require('./utils'),
+	 _styleinfo = require('./styleinfo'),
+	 _svg		= require('./libsvg').SVG,
+	 _wlib,
+	 _mmmk,
+	 _mt,
+	 _plugins,
+	 __wid,
+	 __wtype;
+var keepaliveAgent = new _http.Agent({keepAlive: true, maxSockets: 10, maxFreeSockets: 5}); // proposed by yentl to improve performance
+
+
+/*********************************** UTILS ************************************/
+/***************************** BASIC CONTINUABLES *****************************/
+/* return a failure continuable */
+function __errorContinuable(err)	
+{
+	return function(callback,errback) {errback(err);}
+}
+
+/* return a success continuable */
+function __successContinuable(arg)	
+{
+	return function(callback,errback) {callback(arg);}
+}
+
+
+
+/******************************* HTTP REQUESTS ********************************/
+/* make an HTTP request to localhost:port */
+function __httpReq(method,url,data,port)
+{
+	if( port == undefined )
+		port = 8124;
+
+	return function(callback,errback)
+			 {
+				 var options = {'port': port, 'path': url, 'method': method, 'agent': keepaliveAgent}; // agent proposed by yentl to improve performance
+				 if( data != undefined )
+				 {
+					 data = _utils.jsons(data);
+					 options['headers'] = {'Content-Length':unescape(encodeURIComponent(data)).length};
+				 }
+
+				 var request = 
+					 _http.request(options, 
+						 function(resp)
+						 {
+							 var resp_data = '';
+							 resp.on('data', 	function(chunk) {resp_data += chunk;});
+ 							 resp.on('end',
+								 function()
+								 {
+									 if( _utils.isHttpSuccessCode(resp.statusCode) )
+										 callback(resp_data);
+									 else
+										 errback(resp.statusCode+':'+resp_data);
+								 });
+						 });
+				 request.on('error',
+						 function(err)
+						 {
+						 	 errback('HTTP request ('+method+' '+url+':'+port+') '+
+								 		'failed on ::\n'+err);
+						 });
+				 request.end(data);
+			 };
+}
+
+
+/* make an http request to a *worker... this is basically just a wrapper than
+	takes into account the fact that *workers respond data in respData.data and
+	sometimes include sequence numbers in respData.sequence# */
+function __wHttpReq(method,url,data,port)
+{
+	return function(callback,errback)
+			 {
+				 __httpReq(method,url,data,port)(
+	 					 function(respData)
+	 					 {
+							 respData = eval('('+respData+')');
+							 respData = 
+								 (respData == undefined || 
+								  respData['sequence#'] != undefined ? 
+									  respData : 
+									  respData.data);
+							 callback(respData);
+						 },	
+	 					 function(respData)	 {errback(respData);}
+				 );
+			 };
+}
+
+
+
+/******************************* URI PROCESSING *******************************/
+/* optimize __id_to_uri() by remembering computed mappings */
+var __ids2uris = {};
+
+
+/* try to construct a uri from an instance id */
+function __id_to_uri(id)
+{
+	if( id == undefined )
+		return undefined;
+	else if( id in __ids2uris )
+		return __ids2uris[id];
+	else if( (res =_mmmk.read(id))['$err'] )
+		return res;
+
+	var uri = _utils.jsonp(res)['$type']+'/'+id+'.instance';
+	__ids2uris[id] = uri;
+	return uri;
+}
+
+
+/* try to extract an instance id from a uri */
+function __uri_to_id(uri)
+{
+	var matches = uri.match(/.*\/(.*).instance/);
+	if( matches != null )
+		return matches[1];
+	return {'$err':'bad instance uri :: '+uri};
+}
+
+
+/* replace ids in the given changelog by corresponding uris... see above NOTES
+  	on IDs vs. URIs for more on this 
+ 
+ 	NOTE:: when RESETM steps are encountered, we additionaly flush all currently
+  			 remembered id-to-uri mappings */
+function __urizeChangelog(chlog)
+{
+	chlog.forEach(
+		function(step)
+		{	
+			if( step['op'] == 'RESETM' )
+			{
+				__ids2uris = {};
+				var newModel = _utils.jsonp(step['new_model']);
+				for( var id in newModel.nodes )
+				{
+					newModel.nodes[__id_to_uri(id)] = newModel.nodes[id];
+					delete newModel.nodes[id];
+				}	
+				step['new_model'] = _utils.jsons(newModel);
+			}
+			else
+				['id','id1','id2'].forEach(
+					function(x)
+					{
+						if( x in step )
+							step[x] = __id_to_uri(step[x]);
+					});
+		});
+}
+
+
+
+/****************************** POST MESSAGE... *******************************/
+/* wrapper for : 400 Bad Request Syntax */
+function __postBadReqErrorMsg(respIndex,reason) 
+{
+	__postErrorMessage(respIndex,400,reason);
+}
+
+/* wrapper for all error messages */
+function __postErrorMessage(respIndex,statusCode,reason)
+{
+	__postMessage(
+			{'statusCode':statusCode,
+			 'reason':reason,
+			 'respIndex':respIndex});
+}
+
+/* wrapper for : 403 Forbidden */
+function __postForbiddenErrorMsg(respIndex,reason) 
+{
+	__postErrorMessage(respIndex,403,reason);
+}
+
+/* wrapper for : 500 Internal Server Error */
+function __postInternalErrorMsg(respIndex,reason)
+{
+	__postErrorMessage(respIndex,500,reason);
+}
+
+/* wrapper for all messages */
+function __postMessage(msg) 
+{
+	_util.debug("w#"+__wid+" << ("+msg.respIndex+") "+msg.statusCode+" "+
+			(msg.reason || 
+			 (typeof msg.data == 'object' ? 
+				  _utils.jsons(msg.data) : 
+				  msg.data)));
+
+	if( 'respIndex' in msg )
+		__onRequestResponse(msg.respIndex);
+
+	if( __wtype == '/csworker' && 'changelog' in msg )
+		__urizeChangelog(msg['changelog']);
+
+	process.send(msg);
+}
+
+/* wrapper for : 501 Not Implemented */
+function __postUnsupportedErrorMsg(respIndex)
+{
+	__postErrorMessage(respIndex,501);
+}
+
+
+
+/********************************** LOCKING ***********************************/
+var __wLocked    		  = false,
+	 __rLocks 	  		  = 0,
+	 __numWriters 		  = 0,
+	 __numReaders 		  = 0,
+	 __reqs2lockInfo 	  = {},
+	 __reqQueue			  = [],
+	 __NO_LOCK			  = 0,
+	 __LOCK				  = 1,
+	 __WLOCK				  = __LOCK | 2,
+	 __RLOCK				  = __LOCK | 4;
+
+/* determine whether this worker can proceed with the specified request given 
+	its current readers/writers/locks/queue... returns false if the worker can't
+	proceed... otherwise, grants needed locks, increments number of readers/
+	writers, and returns true
+
+	NOTE:: the 'ignoreQueue' parameter disables queue-emptyness as a condition
+			 for this function's success */
+function __canProceed(method,uri,respIndex,ignoreQueue)
+{
+	function __isRead(method,uri)
+	{
+		/* returns true if request is a read */
+		return (method == 'GET' || uri.match(/^\/GET\//));
+	}
+
+	function __needsLock(method,uri)
+	{
+		/* returns lock type needed by request */
+		if( method == 'POST' && uri.match(/^batch/) )
+			return __RLOCK;
+		return __NO_LOCK;
+	}
+
+
+	var isReader  = __isRead(method,uri),
+		 needsLock = __needsLock(method,uri);
+
+	/* disallow concurrent writes and queue if queue (see NOTES above) */	
+	if( (!isReader && __numWriters > 0) ||
+		 (!ignoreQueue && __reqQueue.length > 0) )
+		return false;
+
+	/* check current locks */
+	if( __wLocked 													|| 
+		 (__rLocks > 0			&& !isReader)					||
+		 (__numReaders > 0 	&& (needsLock &  __WLOCK)) ||
+		 (__numWriters > 0 	&& (needsLock & __LOCK)) 	)
+		return false;
+
+	/* access granted... */
+	if( needsLock & __RLOCK )			
+		__rLocks++;
+	else if( needsLock & __WLOCK )	
+		__wLocked = true;
+
+	if( isReader )		
+		__numReaders++;
+	else				
+		__numWriters++;
+
+	__reqs2lockInfo[respIndex] = {'isReader':isReader,'needsLock':needsLock};
+	return true;
+}
+
+
+/* unlock this *worker (if request had locked it), decrement number of readers/
+	writers, and launch queued requests, if any... ignore requests that have no
+  	entry in __reqs2lockInfo (i.e., requests with backstage passes) */
+function __onRequestResponse(respIndex)
+{	
+	if( (li = __reqs2lockInfo[respIndex]) == undefined )
+		return;
+
+	if( li['needsLock'] & __RLOCK )			
+		__rLocks = Math.max(--__rLocks,0);
+	else if( li['needsLock'] & __WLOCK )	
+		__wLocked = false;
+
+	if( li['isReader'])		
+		__numReaders = Math.max(--__numReaders,0);
+	else							
+		__numWriters = Math.max(--__numWriters,0);
+
+	__runQueuedRequests();
+}
+
+
+/* run proceedable queued requests in FIFO order until a non-proceedable request
+ 	is encountered... 
+	
+	NOTE:: this function doesn't wait for request responses, if all queued 
+			 responses can run concurrently (e.g., all reads), it will launch them
+			 all s.t. they can all be handled in parallel */
+function __runQueuedRequests()
+{
+	if( __reqQueue.length > 0 )
+	{
+		var head 	  = __reqQueue[0],
+			 uri		  = head['uri'],
+			 method	  = head['method'],
+			 reqData   = head['reqData'],
+			 respIndex = head['respIndex'];
+
+		if( __canProceed(method,uri,respIndex,true) )
+		{
+			__reqQueue.shift();
+			__handleClientRequest(uri,method,reqData,respIndex);
+			__runQueuedRequests();
+		}
+	}
+}
+
+
+/* push given request onto request queue for future handling */
+function __queueRequest(uri,method,reqData,respIndex)
+{
+	__reqQueue.push(
+			{'uri':uri,
+			 'method':method,
+			 'reqData':reqData,
+			 'respIndex':respIndex});
+}
+
+
+
+/****************************** SEQUENCE NUMBERS ******************************/
+var __nextSequenceNumber = 0;
+
+function __sequenceNumber(inc)
+{
+	if( inc == undefined || inc == 1 )
+		inc = 1;
+	else if( inc != 0 )
+		throw '__sequenceNumber increment must be 0, 1 or undefined';
+	return __wtype+'#'+(__nextSequenceNumber+=inc);
+}
+
+function __batchCheckpoint(id,start)
+{
+	return 'bchkpt@'+id+(start ? '>>' : '<<');
+}
+
+
+
+/*********************************** LOGIC ************************************/
+process.on('uncaughtException', 
+	function(error)
+	{					
+		/* there is no nice way of getting the response index associated with this
+		 	error... so we just dump it to the console (the worker will be killed 
+			due to timeouts)... in a release version, all errors should be properly
+		  	caught and this function should never be triggered */
+		if( error.message == 'Socket is not writable' )
+			_util.debug('ignoring failed re-send error');
+		else
+		{
+			_util.log(' ----------- FATAL WORKER ERROR ----------- ');
+			_util.log(error);
+			_util.log(' __________________________________________ ');
+			process.exit(1);
+		}
+	});
+
+
+process.on('message', 
+	function(msg)
+	{
+		_util.debug(">> "+JSON.stringify(msg));
+
+		/* parse msg */
+		var uri 		  = msg['uri'],
+			 method 	  = msg['method'],
+			 uriData	  = msg['uriData'],
+			 reqData   = msg['reqData'],
+			 respIndex = msg['respIndex'];
+
+
+
+		/* initial setup */
+		if( _wlib == undefined )
+		{
+			/** enable/disable debugging messages **/			
+			_util.debug = function() {};
+
+			__wtype = msg['workerType'];
+			__wid   = msg['workerId'];
+			_wlib   = eval('('+_fs.readFileSync('.'+__wtype+'.js', 'utf8')+')');
+			_mmmk   = eval('('+_fs.readFileSync('./mmmk.js', 'utf8')+')');
+			_mt  	  = eval('('+_fs.readFileSync('./libmt.js', 'utf8')+')');
+
+			_plugins = {};
+			_fs.readdirSync('./plugins').forEach(
+				function(p)
+				{
+					try 
+					{
+						if( ! p.match(/.*\.js$/) )
+							throw 'invalid plugin filename, see user\'s manual';
+
+						p = p.match(/(.*)\.js$/)[1];
+						_plugins[p] = eval(
+							'('+_fs.readFileSync('./plugins/'+p+'.js','utf8')+')');
+						if( ! ('interfaces' in _plugins[p]) ||
+							 ! ('csworker' in _plugins[p])  ||
+							 ! ('asworker' in _plugins[p]) )
+							throw 'invalid plugin specification, see user\'s manual';
+					}
+					catch(err)
+					{
+						_util.log('failed to load plugin ('+p+') on :: '+err);
+					}
+				});
+			return;
+		}
+
+
+		/* concurrent access control */
+		if( uriData != undefined && uriData['backstagePass'] != undefined )
+		{
+			if( uriData['backstagePass'] != __backstagePass )
+				return __postErrorMessage(respIndex,401,'invalid backstage pass');
+		}
+		else if( ! __canProceed(method,uri,respIndex) )
+			return __queueRequest(
+							uri,
+							method,
+							(method == 'GET' ? uriData : reqData),
+							respIndex);
+
+		/* handle client requests 
+			POST 		<>  create 
+			GET		<>  retrieve
+			PUT		<>  update
+			DELETE	<>  delete */
+		__handleClientRequest(
+				uri,
+				method,
+				(method == 'GET' ? uriData : reqData),
+				respIndex);	
+	});
+
+
+/* handle a request described by the given parameters */
+function __handleClientRequest(uri,method,reqData,respIndex)
+{
+	/********************** SHARED AS-CS WORKER BEHAVIOR ***********************/
+	if( method == 'GET' && uri.match(/^\/current.model$/) )
+		GET__current_model(respIndex);
+
+	else if( method == 'GET' && uri.match(/^\/current.state$/) )
+		GET__current_state(respIndex);
+
+	else if( method == 'POST' && uri.match(/^\/GET\/batchRead$/) )
+		POST_GET_batchread(respIndex,reqData);
+
+	else if( method == 'POST' && uri.match(/^\/batchEdit$/) )
+		POST_batchedit(respIndex,reqData);
+
+
+	/********************* DISTINCT AS-CS WORKER BEHAVIOR **********************/
+	else if( (method == 'DELETE'	&& uri.match(/\.metamodel$/))	 				||
+  				(method == 'POST' 	&& uri.match(/\.type$/)) 						||
+  				(method == 'GET' 		&& uri.match(/\.instance$/)) 					||
+  				(method == 'PUT' 		&& uri.match(/\.instance$/)) 					||
+  				(method == 'DELETE' 	&& uri.match(/\.instance$/)) 					||
+  				(method == 'PUT' 		&& uri.match(/\.instance.cs$/)) 				||
+  				(method == 'PUT' 		&& uri.match(/\.vobject$/)) 					||
+				(method == 'POST' 	&& uri.match(/^\/GET\/.*\.mappings$/))		||
+				(method == 'PUT' 		&& uri.match(/^\/GET\/.*\.metamodel$/))	||
+				(method == 'PUT' 		&& uri.match(/^\/GET\/.*\.model$/))			)
+	{
+		var func = method+' *'+uri.match(/.*(\..*)$/)[1];
+		if( _wlib[func] == undefined )
+			return __postUnsupportedErrorMsg(respIndex);
+		_wlib[func](respIndex,uri,reqData);
+	}
+
+	else if( (method == 'GET' 	&& uri.match(/^\/internal.state$/)) 		||
+				(method == 'PUT' 	&& uri.match(/^\/aswSubscription$/)) 		||
+				(method == 'PUT' 	&& uri.match(/^\/current.metamodels$/))	||
+				(method == 'PUT' 	&& uri.match(/^\/current.model$/))	 		||
+				(method == 'GET' 	&& uri.match(/^\/validatem$/))		 		||
+				(method == 'POST' && uri.match(/^\/undo$/))				 		||
+				(method == 'POST' && uri.match(/^\/redo$/))				 		||
+				(method == 'PUT'  && uri.match(/^\/GET\/console$/))		 	||
+				(method == 'POST' && uri.match(/^\/batchCheckpoint$/))		)
+	{
+		var func = method+' '+uri;
+		if( _wlib[func] == undefined )
+			return __postUnsupportedErrorMsg(respIndex);
+		_wlib[func](respIndex,uri,reqData);
+	}
+
+	else if( uri.match(/^\/__mt\/.*$/) )
+		_wlib.mtwRequest(respIndex,method,uri,reqData);
+
+	/* plugin request */
+	else if( uri.match(/^\/plugins\/.*$/) )
+	{
+		var matches = uri.match(/^\/plugins\/(.*?)(\/.*)$/),
+			 plugin	= matches[1],
+			 requrl	= matches[2],
+			 self    = this;
+
+		if( ! (plugin in _plugins) ||
+			 ! _plugins[plugin].interfaces.some(
+ 				 function(ifc)
+ 				 {
+					 if( method == ifc.method &&
+						  ('url=' in ifc && ifc['url='] == requrl) ||
+ 						  ('urlm' in ifc && requrl.match(ifc['urlm'])) )
+ 					 {
+						 _plugins[plugin][__wtype.substring(1)](
+ 							 respIndex,
+							 method,
+ 							 uri,
+							 reqData,
+ 							 _wlib)
+ 						 return true;
+	 				 }
+	 			 }) )
+			__postUnsupportedErrorMsg(respIndex);
+	}
+
+	/* unsupported request */
+	else
+		__postUnsupportedErrorMsg(respIndex);
+}
+
+
+/************************ SHARED AS-CS WORKER BEHAVIOR ************************/
+/*	returns the current model to the querier
+	1. ask _mmmk for a copy of the current model
+	2. return said copy to the querier */
+function GET__current_model(resp)
+{
+	if( (res = _mmmk.read())['$err'] )
+		__postInternalErrorMsg(resp,res['$err']);
+	else
+		__postMessage(
+			{'statusCode':200,
+			 'data':res,
+			 'sequence#':__sequenceNumber(0),					 
+			 'respIndex':resp});
+}
+
+/*	returns the current 'state' of this *worker's _mmmk (i.e., its model,
+  	loaded metamodels, current sequence#, and next expected sequence#, if any)
+  	to the querier
+	1. ask _mmmk for a copy of its model, loaded metamodels and name
+	2. return said copies to the querier */
+function GET__current_state(resp)
+{
+	if( (mms = _mmmk.readMetamodels())['$err'] )
+		__postInternalErrorMsg(resp,mms['$err']);
+	else if( (m = _mmmk.read())['$err'] )
+		__postInternalErrorMsg(resp,m['$err']);
+	else
+		__postMessage(
+			{'statusCode':200,
+			 'data':{'mms':mms,
+						'm':m,
+						'name':_mmmk.readName(),
+						'asn':_wlib['__nextASWSequenceNumber'],
+						'asw':_wlib['__aswid']},
+			 'sequence#':__sequenceNumber(0),
+			 'respIndex':resp});
+}
+
+/*	returns an array containing the results of a number of bundled read
+  	requests */
+function POST_GET_batchread(resp,reqData)
+{
+	var actions = [__successContinuable()],
+		 results = [];
+		
+	reqData.forEach(
+			function(r)
+			{
+				actions.push(
+					function()		
+					{
+						return __wHttpReq(r['method'],r['uri']+'?wid='+__wid);
+					},
+					function(res)
+					{
+						results.push(res);
+						return __successContinuable();
+					});
+			});
+
+	_do.chain(actions)(
+			function()	 	
+			{
+				__postMessage(
+					{'statusCode':200,
+					 'data':{'results':results},
+					 'sequence#':__sequenceNumber(0),
+					 'respIndex':resp});
+			},
+			function(err)	{__postInternalErrorMsg(resp,err);}
+	);
+}
+
+/*	returns an array containing the results of a number of bundled edit
+  	requests (these results are mostly just statusCodes)... if any of the
+ 	requests fail, every preceding request is undone (this is facilitated by 
+	setting a user-checkpoint before beginning)
+
+	NOTE: requests may refer to the results of previously completed requests
+			in their uri and reqData : all occurrences of '$i$' are replaced by 
+			the result of request #i 
+
+	NOTE: to enable undoing/redoing batchEdits atomically, easily identifiable 
+			user-checkpoints are set before performing any of the batched requests
+		  	and after they've all been completed... more on this in NOTES above
+ 
+ 	NOTE:	nested batchEdits are not supported */
+function POST_batchedit(resp,reqData)
+{
+	for( var i in reqData )
+		if( reqData[i]['method'] == 'POST' && 
+			 reqData[i]['uri'].match(/^\/batchEdit$/) )
+			return __postBadReqErrorMsg(
+				'nested batchEdit requests are not supported');
+
+	var results    = [],
+		 currtime   = Date.now(),
+		 startchkpt = __batchCheckpoint(currtime,true),
+		 endchkpt   = __batchCheckpoint(currtime),
+		 setbchkpt  = 
+			 function(name)
+ 			 {
+				 return function()
+					 	  {
+			 				  __backstagePass = Math.random();
+			 				  return __wHttpReq(
+		 								  'POST',
+			 							  '/batchCheckpoint?wid='+__wid+
+	  										  '&backstagePass='+__backstagePass,
+				 						  {'name':name});
+						  }
+	 		 },
+		 actions = [__successContinuable(), setbchkpt(startchkpt)];
+
+	reqData.forEach(
+		function(r)
+		{
+			actions.push(
+				function()		
+				{
+					__backstagePass = Math.random();
+					var replace = function(s,p1) {return results[p1]['data'];},
+						 uri 		= r['uri'].replace(/\$(\d+)\$/g,replace);
+					if( r['reqData'] != undefined )
+						var reqData = 
+								_utils.jsonp(
+									_utils.jsons(r['reqData']).
+										replace(/\$(\d+)\$/g,replace) );
+					return __wHttpReq(
+									r['method'],
+									uri+'?wid='+__wid+
+												'&backstagePass='+__backstagePass,
+									reqData);
+				},
+				function(res)
+				{
+					results.push(res);
+					return __successContinuable();
+				});
+		});
+	actions.push(setbchkpt(endchkpt));
+
+	_do.chain(actions)(
+			function()	 	
+			{
+				__backstagePass = undefined;
+				__postMessage(
+					{'statusCode':200,
+					 'data':{'results':results},
+					 'sequence#':__sequenceNumber(0),
+					 'respIndex':resp});
+			},
+			function(err) 
+			{
+				var undoActions = 
+					[__successContinuable(),
+					 function()		
+					 {
+						 if( results.length == 0 )
+							 return __successContinuable();
+ 						 return __wHttpReq(
+									'POST',
+									'/undo?wid='+__wid+'&backstagePass='+__backstagePass,
+									{'undoUntil':startchkpt,
+									 'hitchhiker':{'undo':startchkpt}});
+					 }];
+				
+				_do.chain(undoActions)(
+					function()	
+					{
+						__backstagePass = undefined;
+						__postInternalErrorMsg(resp,err)
+					},
+					function(undoErr)	
+					{	
+						__backstagePass = undefined;
+						__postInternalErrorMsg(
+							resp,
+							'unexpected error occured on rollback :: '+undoErr);
+					}
+				);	
+			}
+	);
+}

+ 511 - 0
asworker.js

@@ -0,0 +1,511 @@
+/*******************************************************************************
+AToMPM - A Tool for Multi-Paradigm Modelling
+
+Copyright (c) 2011 Raphael Mannadiar (raphael.mannadiar@mail.mcgill.ca)
+
+This file is part of AToMPM.
+
+AToMPM is free software: you can redistribute it and/or modify it under the
+terms of the GNU Lesser General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later 
+version.
+
+AToMPM is distributed in the hope that it will be useful, but WITHOUT ANY 
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with AToMPM.  If not, see <http://www.gnu.org/licenses/>.
+*******************************************************************************/
+
+{
+	/************************** REST REQUEST HANDLING **************************/
+	/* INTENT :
+			ask our mtworker to do something (e.g., change transformation execution
+		  	mode) 
+		IN PRACTICE: 
+			adjust uri and forward to mtworker
+ 
+	1. setup sync/async action chaining
+		a) if this asworker doesn't already have an mtworker 
+			i.   create mtworker
+			ii.  subscribe it to this asworker
+			iii. send it this asworker's current model
+		b) ask asworker to forward initial request to its mtworker
+	2. launch chain... return success code or error */
+	'__mtwid':undefined,
+	'mtwRequest' :
+		function(resp,method,uri,reqData)
+		{
+			var self	   = this,
+				 actions = 
+				 	[__successContinuable(),
+	 				 function()
+	 				 {
+						 uri = uri.substring('/__mt'.length);
+	 					 return __httpReq(
+								 		method,
+										uri+'?wid='+self.__mtwid,
+										reqData,
+										8125);
+	 				 }];
+
+			if( this.__mtwid == undefined )
+			{
+				if( reqData == undefined || 
+					 reqData['transfs'] == undefined ||
+					 reqData['transfs'].length == 0 )
+					return __postInternalErrorMsg(resp,'missing transformations');
+				else if( reqData == undefined || 
+  							reqData['username'] == undefined ||
+	  						reqData['username'].length == 0 )
+					return __postInternalErrorMsg(resp,'missing username');
+
+				actions.splice(1,0,
+						function()
+						{
+							return __httpReq(
+								'PUT',
+								'/GET/console?wid='+__wid,
+								{'text':'please wait while model transformation module'+
+										  ' initializes (this may take a few seconds)'});
+						},
+  						function()
+  						{
+							return __httpReq('POST','/mtworker',undefined,8125);
+	  					},
+  						function(mtwid)
+  						{
+							self.__mtwid = mtwid;
+  							return __httpReq(
+										'PUT',
+  										'/aswSubscription?wid='+self.__mtwid,
+  										{'aswid':__wid},
+  										8125);
+	  					},
+						function()
+						{
+							return _fs.readFile('./users/'+reqData['username']+
+															'/prefs','utf8');
+						},
+  						function(prefs)
+  						{
+							try 			{prefs = eval('('+prefs+')');}
+							catch(err)	{
+								return __errorContinuable(
+									'an error occurred while reading your preferences '+
+									'(you must restart your client before successful '+
+									'model transformation module initialization becomes'+
+									' possible :: '+err);} 
+
+  							return __httpReq(
+										'PUT',
+  										'/envvars?wid='+self.__mtwid,
+  										{'username':reqData['username'],
+										 'defaultDCL':prefs['default-mt-dcl']['value']},
+  										8125);
+	  					},
+  						function()
+  						{
+							if( (mms = _mmmk.readMetamodels())['$err'] )
+								return __errorContinuable(mms['$err']);
+	  						else if( (m = _mmmk.read())['$err'] )
+								return __errorContinuable(m['$err']);
+								
+  							return __httpReq(
+										'PUT',
+  										'/current.model?wid='+self.__mtwid,
+										{'mms':mms,
+										 'm':m, 
+										 'sequence#':__sequenceNumber(0)},
+  										8125);
+  						},
+  						function()
+  						{
+  							return __httpReq(
+										'PUT',
+  										'/current.transform?wid='+self.__mtwid,
+  										{'transfs':reqData['transfs']},
+  										8125);
+	  					},
+						function()
+						{
+							return __httpReq(
+								'PUT',
+								'/GET/console?wid='+__wid,
+								{'text':'model tranformation module is ready to go!'});
+						}						
+				);
+			}
+
+
+
+			_do.chain(actions)(
+					function() 
+					{
+						__postMessage({'statusCode':200, 'respIndex':resp});
+					},
+					function(err) 	{__postInternalErrorMsg(resp,err);}
+			);
+		},
+
+		
+	/* load a metamodel
+
+		1. setup sync/async action chaining
+			a) if no hitchhiker containing CSMM information is provided, or if only
+				a CSMM name is provided, setup hitchihiker construction/completion 
+			b) read specified mm from disk 
+		2. launch chain... on success, load requested metamodel into _mmmk and
+			return success code and 'hitchhiker' (see notes at top of csworker.js
+		  	for more about 'hitchhikers')... on error, return error */ 
+	'PUT /current.metamodels' :
+		function(resp,uri,reqData/*mm,hitchhiker*/)
+		{
+			var actions = [__successContinuable()]; 
+
+			if( reqData['hitchhiker'] == undefined ||
+				 'path' in reqData['hitchhiker'] )
+				actions.push(
+					function()
+					{
+						var ext  = (reqData['mm'].match(/\.pattern\.metamodel$/) ?
+											'.pattern.metamodel' :
+											'.metamodel'),
+							 path = 
+							(reqData['hitchhiker'] && 'path' in reqData['hitchhiker'] ?
+								reqData['hitchhiker']['path'] :
+								reqData['mm'].match(/(.*)\.metamodel/)[1]+
+										'.defaultIcons'+ext),
+							 name = path.match(/.+?(\/.*)\.metamodel/)[1];
+
+						reqData['hitchhiker'] = {};											
+						reqData['hitchhiker']['name'] = name;
+  						return _fs.readFile('./users'+path,'utf8');
+					},
+ 					function(csmmData)
+ 					{
+						reqData['hitchhiker']['csmm'] = csmmData;
+ 						return __successContinuable();
+	 				});
+
+			actions.push(
+					function()
+					{
+						return _fs.readFile('./users'+reqData['mm'],'utf8');
+					});		
+
+			_do.chain(actions)(
+					function(asmmData) 
+					{
+  						var mm  = reqData['mm'].match(/.+?(\/.*)\.metamodel/)[1],
+							 res = _mmmk.loadMetamodel(mm,asmmData);
+						__postMessage(
+							{'statusCode':200, 
+	  						 'changelog':res['changelog'],
+							 'sequence#':__sequenceNumber(),
+							 'hitchhiker':reqData['hitchhiker'],
+							 'respIndex':resp});
+					},
+					function(err) 	{__postInternalErrorMsg(resp,err);}
+			);
+		},
+
+
+	/* unload a metamodel (deletes all entities from that metamodel) */
+	'DELETE *.metamodel' :
+		function(resp,uri)
+		{
+			var mm  = uri.match(/(.*)\.metamodel/)[1],
+				 res = _mmmk.unloadMetamodel(mm);
+			__postMessage(
+					{'statusCode':200, 
+					 'changelog':res['changelog'],
+					 'sequence#':__sequenceNumber(),
+					 'respIndex':resp});
+		},
+
+
+	/* load a model */
+	'PUT /current.model' :
+		function(resp,uri,reqData/*m,name,insert,hitchhiker*/)
+		{
+			if( (res = _mmmk.loadModel(
+										reqData['name'],
+										reqData['m'],
+										reqData['insert']))['$err'] )
+				__postInternalErrorMsg(resp,res['$err']);
+			else
+				__postMessage(
+						{'statusCode':200, 
+  						 'changelog':res['changelog'],	
+					 	 'sequence#':__sequenceNumber(),
+						 'hitchhiker':reqData['hitchhiker'],
+						 'respIndex':resp});
+		},
+
+
+	/* create a new instance of type... if reqData has 'src' and 'dest' fields,
+	  	type is a connector
+
+		1. if *.type is a node, ask _mmmk to create it
+		1. if *.type is a connector, ask _mmmk to create it and connect 
+				its endpoints
+		2. return success or error code
+
+		NOTE:: since create() is given 'attrs' (i.e., these aren't given to
+				 update() after creation), it is important that post-edit 
+				 constraints also be made post-create constraints... otherwise, it
+				 would be possible to create nodes via copy-paste that would 
+				 otherwise be blocked by post-edit constraints */
+	'POST *.type' :
+		function(resp,uri,reqData/*[src,dest],hitchhiker,attrs*/)
+		{
+			var matches 	= uri.match(/(.*)\.type/),
+				 fulltype 	= matches[1],
+				 res  		= (reqData == undefined || reqData['src'] == undefined ?
+									 _mmmk.create(fulltype,reqData['attrs']) :
+									 _mmmk.connect(
+										 __uri_to_id(reqData['src']),
+										 __uri_to_id(reqData['dest']),
+										 fulltype,
+										 reqData['attrs']));
+
+			if( '$err' in res )
+				__postInternalErrorMsg(resp,res['$err']);
+			else
+				__postMessage(
+					{'statusCode':200, 
+					 'changelog':res['changelog'],
+					 'data':res['id'],					 
+					 'sequence#':__sequenceNumber(),
+					 'hitchhiker':reqData['hitchhiker'],
+					 'respIndex':resp});
+		},
+
+
+	/* return an instance */
+	'GET *.instance' :
+		function(resp,uri)
+		{
+			var id = __uri_to_id(uri);
+
+			if( (res = _mmmk.read(id))['$err'] )
+				__postInternalErrorMsg(resp,res['$err']);
+			else
+				__postMessage(
+					{'statusCode':200, 
+					 'data':res, 
+					 'sequence#':__sequenceNumber(0),
+					 'respIndex':resp});
+		},
+
+
+	/* updates an instance 
+
+		NOTE:: if this update produces no changelog *and* to-do-cschanges are 
+				 bundled, we return a dummy changelog that ensures the said to-do-
+				 cschanges get handled by csworker.__applyASWChanges().CHATTR */
+	'PUT *.instance' :
+		function(resp,uri,reqData/*changes[,hitchhiker]*/)
+		{
+			var id = __uri_to_id(uri);
+
+			if( (res = _mmmk.update(id,reqData['changes']))['$err'] )
+				__postInternalErrorMsg(resp,res['$err']);
+			else
+			{
+				var changelog = 
+						(res['changelog'].length == 0 && reqData['hitchhiker'] ?
+						 	[{'op':'CHATTR','id':id}] :
+							res['changelog']);
+				__postMessage(
+					{'statusCode':200, 
+					 'changelog':changelog,
+					 'sequence#':__sequenceNumber(),
+					 'hitchhiker':reqData['hitchhiker'],
+					 'respIndex':resp});
+			}
+		},
+
+
+	'POST *.instance.click' :
+		function(id,vid)
+		{
+			//TODO
+		},
+
+	/* delete an instance 
+
+		NOTE:: this function does not return asworker errors to the client (see 
+				 NOTE for csworker.DELETE *.instance with the difference that in 
+				 this context, the client is the mtworker) */
+	'DELETE *.instance' :
+		function(resp,uri)
+		{
+			var id = __uri_to_id(uri);
+			if( (res = _mmmk.read(id))['$err'] )
+				__postMessage({'statusCode':200, 'respIndex':resp});
+			else if( (res = _mmmk['delete'](id))['$err'] )
+				__postInternalErrorMsg(resp,res['$err']);
+			else
+				__postMessage(
+					{'statusCode':200, 
+					 'changelog':res['changelog'],
+					 'sequence#':__sequenceNumber(),
+					 'respIndex':resp});
+		},
+
+
+	/* generate a metamodel from the current model and write it to disk 
+	 
+		1. generate metamodel
+		1. setup sync/async action chaining
+			a) write specified mm to disk 
+		2. launch chain... return success or error code */ 
+	'PUT *.metamodel' :
+		function(resp,uri,reqData/*[csm]*/)
+		{
+			if( uri.match(/(.*)\..*Icons\.metamodel/) && 
+				 (res = _mmmk.
+						compileToIconDefinitionMetamodel(reqData['csm']))['$err'] )
+				__postInternalErrorMsg(resp,res['$err']);
+			else if( ! uri.match(/(.*)\..*Icons\.metamodel/) &&
+						(res = _mmmk.compileToMetamodel())['$err'] )
+				__postInternalErrorMsg(resp,res['$err']);
+			else
+			{
+				var uri 		= uri.substring('/GET'.length),
+					 actions = [_fs.writeFile('./users'+uri,res)];
+				_do.chain(actions)(
+						function() 
+						{
+							__postMessage(
+								{'statusCode':200, 
+								 'respIndex':resp});
+						},
+						function(err) 	{__postInternalErrorMsg(resp,err);}
+				);
+			}
+		},
+
+
+	/* validate a model */
+	'GET /validatem' :
+		function(resp)
+		{
+			var err = _mmmk.validateModel();
+			if( err )
+				__postInternalErrorMsg(resp,err['$err']);
+			else
+				__postMessage(
+					{'statusCode':200,
+					 'respIndex':resp});
+		},
+
+
+	/* undo the effects of a csworker's last not yet undone action
+			OR
+		undo until the specified user-checkpoint */
+	'POST /undo' :
+		function(resp,uri,reqData/*[undoUntil],hitchhiker*/)
+		{
+			__postMessage(
+				{'statusCode':200,
+				 'changelog':_mmmk.undo(reqData['undoUntil'])['changelog'],
+				 'sequence#':__sequenceNumber(),
+				 'hitchhiker':reqData['hitchhiker'],
+				 'respIndex':resp});
+		},
+	
+
+	/* redo the effects of a csworker's last undone action
+			OR
+		redo until the specified user-checkpoint  */
+	'POST /redo' :
+		function(resp,uri,reqData/*[redoUntil],hitchhiker*/)
+		{
+			__postMessage(
+				{'statusCode':200,
+				 'changelog':_mmmk.redo(reqData['redoUntil'])['changelog'],
+				 'sequence#':__sequenceNumber(),
+				 'hitchhiker':reqData['hitchhiker'],
+				 'respIndex':resp});
+		},
+
+
+	/* evaluate a set of mapping functions (i.e., those functions that compute
+		the values of coded VisualObject attributes based on the AS model)... of
+	  	interest is that this function 'succeeds' even if one or more of the given
+		mapping function produces an error: in such cases, the new value of the 
+		associated attribute will be the produced error 
+
+		NOTE: 
+			this request is actually a GET : it doesn't change anything anywhere...
+			however, due to the possibly large amount of reqData it requires, we're
+			forced to make it a POST */
+	'POST *.mappings' :
+		function(resp,uri,reqData/*{...,fullvid:mapper,...}*/)
+		{
+			var id  		 = __uri_to_id(uri),
+				 attrVals = {};
+
+			for( var fullvid in reqData )
+			{
+				var res = _mmmk.runDesignerAccessorCode(
+									 reqData[fullvid],
+									 'mapper evaluation ('+uri+')',
+ 									 id);
+				if( res == undefined )
+					continue; 
+				else if( !_utils.isObject(res) )
+				{
+					attrVals = 
+						{'$err':
+						 'mapper returned non-dictionary type :: '+(typeof res)};
+					break;
+				}
+				else if( '$err' in res )
+				{
+					attrVals = res;
+					break;
+				}
+				else
+					for( var attr in res )
+						attrVals[fullvid+attr] = res[attr];
+			}
+
+			__postMessage(
+					{'statusCode':200,
+					 'data':attrVals,
+					 'respIndex':resp});
+		},
+
+
+	/*	little hack that allows pretty much anyone to send some text to the client
+	  	for console output... this is currently used by mtworkers to post feedback
+		about the state of a running transformation... eventually, it could also 
+		be used to implement inter-collaborator chatting */
+	'PUT /GET/console' :
+		function(resp,uri,reqData/*text*/)
+		{
+			__postMessage(
+					{'statusCode':200,
+					 'changelog':[{'op':'SYSOUT','text':reqData['text']}],
+					 'sequence#':__sequenceNumber(),
+					 'respIndex':resp});
+		},
+
+
+	/* place an easily identifiable user-checkpoint in the journal */
+	'POST /batchCheckpoint' :
+		function(resp,uri,reqData)
+		{
+			_mmmk.setUserCheckpoint(reqData['name']);
+			__postMessage(
+					{'statusCode':200,
+					 'changelog':[{'op':'MKBTCCHKPT','name':reqData['name']}],
+					 'sequence#':__sequenceNumber(),
+					 'respIndex':resp});
+		}	
+}

File diff suppressed because it is too large
+ 2 - 0
client/3rd_party_libs/jquery-1.8.2.min.js


+ 398 - 0
client/3rd_party_libs/raphael/dr.css

@@ -0,0 +1,398 @@
+html, body {
+    height: 100%;
+    margin: 0;
+    padding: 0;
+}
+#dr-js {
+    background: #999;
+    margin: 0;
+    padding: 0;
+    overflow-y: hidden;
+}
+#src-dr-js {
+    background: #000;
+    margin: 1em;
+    padding: 0;
+}
+.dr-doc {
+    background: #eee;
+    border-right: solid #eee 3px;
+    float: right;
+    font: 300 16px/1.4 "Myriad Pro", "Helvetica Neue", Helvetica, "Arial Unicode MS", Arial, sans-serif;
+    height: 100%;
+    margin: 0;
+    overflow: auto;
+    padding: 0 30px;
+    width: 800px;
+}
+.dr-toc {
+    margin: 0;
+    padding: 0 16px;
+    background: #ddd;
+    list-style: none;
+    font-family: Menlo, Consolas, Monaco, "Lucida Console", monospace;
+    overflow: auto;
+    border-right: solid #ddd 3px;
+    height: 100%;
+    float: right;
+    width: 240px;
+}
+#dr {
+    margin: 0 auto;
+    width: 1152px;
+    height: 100%;
+}
+::-moz-selection {
+    background: #c00;
+    color: #fff;
+}
+::selection {
+    background: #c00;
+    color: #fff;
+}
+.dr-doc code,
+.dr-type em,
+.dr-returns em,
+.dr-property em {
+    font-family: Menlo, Consolas, Monaco, "Lucida Console", monospace;
+}
+pre.code code {
+    color: #fff;
+}
+pre.code {
+    background: #333;
+    color: #fff;
+    overflow-x: auto;
+    padding: 16px 30px;
+    margin: 0 -30px;
+}
+code b {
+    color: #e9df8f;
+    font-weight: normal;
+}
+code i,
+code i *,
+code i .d {
+    color: #8b9967;
+    font-style: normal;
+}
+code .s {
+    color: #e7be61;
+}
+code .d {
+    color: #cf6a4c;
+}
+code .c,
+code .c * {
+    color: #999;
+    font-style: italic;
+}
+em.amp {
+    font-family: Baskerville, "Goudy Old Style", Palatino, "Book Antiqua", serif;
+    font-style: italic;
+}
+dl.dr-parameters {
+    margin: 8px 0;
+}
+dt.dr-param {
+    color: #666;
+    font-weight: 400;
+    float: left;
+    margin-right: 16px;
+    min-width: 160px;
+}
+dd.dr-type {
+    margin: 0;
+}
+dd.dr-description {
+    display: table;
+    min-height: 24px;
+    border: solid 1px #eee;
+}
+.dr-type {
+    float: left;
+}
+.dr-type em,
+.dr-returns em,
+.dr-property em {
+    -moz-border-radius: 5px;
+    -webkit-border-radius: 5px;
+    background: #ccc;
+    border-radius: 5px;
+    float: left;
+    font-size: .75em;
+    font-style: normal;
+    font-weight: 700;
+    margin: 0 8px 0 0;
+    min-width: 80px;
+    padding: 2px 5px;
+    text-align: center;
+}
+.dr-type em.amp,
+.dr-returns em.amp,
+.dr-property em.amp {
+    float: none;
+    background: none;
+    font-size: 1em;
+    font-weight: 400;
+    font-style: italic;
+    margin: 0;
+    padding: 0;
+    min-width: 0;
+}
+.dr-property em.dr-type {
+    margin: 4px 16px 0 0;
+}
+em.dr-type-string {
+    background: #e1edb1;
+    color: #3d4c00;
+}
+em.dr-type-object {
+    background: #edb1b1;
+    color: #4c0000;
+}
+em.dr-type-function {
+    background: #cfb1ed;
+    color: #26004c;
+}
+em.dr-type-number {
+    background: #b1c9ed;
+    color: #001e4c;
+}
+em.dr-type-boolean {
+    background: #b1edc9;
+    color: #004c1e;
+}
+em.dr-type-array {
+    background: #edd5b1;
+    color: #4c2d00;
+}
+dd.dr-optional {
+    display: none;
+}
+ol.dr-json {
+    background: #ddd;
+    list-style: none;
+    margin: 0 -30px;
+    padding: 16px 30px;
+}
+ol.dr-json .dr-json-key {
+    float: left;
+    min-width: 50px;
+    margin-right: 16px;
+}
+ol.dr-json .dr-json-description {
+    display: table;
+}
+ol.dr-json ol.dr-json  {
+    margin: 0;
+    padding: 0 0 0 50px;
+}
+h1 {
+    font-weight: 400;
+    font-size: 2.6em;
+    margin: 0;
+}
+h2, h3, h4, h5 {
+    margin: 1em 0 .5em 0;
+    padding: 6px 0 0;
+    font-weight: 600;
+    position: relative;
+}
+h5 {
+    font-size: 18px;
+}
+h4 {
+    font-size: 20px;
+}
+h3 {
+    font-size: 28px;
+}
+h2 {
+    font-size: 38px;
+}
+h2.dr-method,
+h3.dr-method,
+h4.dr-method,
+h5.dr-method {
+    color: #900;
+}
+h2.dr-property,
+h3.dr-property,
+h4.dr-property,
+h5.dr-property {
+    color: #009;
+}
+i.dr-trixie {
+    border: solid 10px #eee;
+    border-left-color: #999;
+    height: 0;
+    margin-left: -30px;
+    margin-top: -10px;
+    overflow: hidden;
+    position: absolute;
+    top: 50%;
+    width: 0;
+}
+p.header {
+    font-size: 19px;
+    font-weight: 600;
+    margin: 1em 0 .3em;
+}
+.dr-returns {
+    margin-top: 16px;
+}
+.dr-returns .dr-title {
+    float: left;
+    margin-right: 16px;
+    width: 160px;
+}
+.dr-toc a {
+    display: block;
+    text-decoration: none;
+    color: #333;
+    padding-top: 8px;
+    position: relative;
+    z-index: 1000;
+}
+.dr-toc li.dr-lvl1 a {
+    padding-left: 1em;
+}
+.dr-toc li.dr-lvl2 a {
+    padding-left: 2em;
+}
+.dr-toc li.dr-lvl3 a {
+    padding-left: 3em;
+}
+.dr-toc li.dr-lvl4 a {
+    padding-left: 4em;
+}
+.dr-toc li.dr-lvl5 a {
+    padding-left: 5em;
+}
+.dr-toc li.dr-lvl6 a {
+    padding-left: 6em;
+}
+.dr-toc a.dr-property {
+    color: #339;
+}
+.dr-toc a.dr-method {
+    color: #933;
+}
+.dr-toc a:hover {
+    text-shadow: 0 0 2px #333;
+}
+.dr-toc a.dr-property:hover {
+    text-shadow: 0 0 2px #66c;
+}
+.dr-toc a.dr-method:hover {
+    text-shadow: 0 0 2px #c66;
+}
+a.dr-hash,
+a.dr-sourceline {
+    -webkit-transition: opacity 0.2s linear;
+    color: #333;
+    font-family: Menlo, "Arial Unicode MS", sans-serif;
+    margin: 0 0 0 .3em;
+    opacity: 0;
+    text-decoration: none;
+}
+a.dr-link {
+    position: relative;
+    color: #3875c0;
+}
+a.dr-link:hover {
+    text-shadow: 0 1px 2px #999;
+    bottom: 1px;
+    padding-bottom: 1px;
+}
+a.dr-link:visited {
+    color: #7051bc;
+}
+h2:hover a.dr-hash,
+h3:hover a.dr-hash,
+h4:hover a.dr-hash,
+h5:hover a.dr-hash,
+h2:hover a.dr-sourceline,
+h3:hover a.dr-sourceline,
+h4:hover a.dr-sourceline,
+h5:hover a.dr-sourceline {
+    opacity: 1;
+}
+p {
+    margin: 0 0 .5em;
+}
+
+.dr-source-line {
+    margin: 0;
+}
+.dr-source-line a {
+    -webkit-border-radius: 5px;
+    -webkit-border-radius: 5px;
+    -moz-border-radius: 5px;
+    -moz-border-radius: 5px;
+    border-radius: 5px;
+    border-radius: 5px;
+    color: #000;
+    background: #999;
+    font-weight: 400;
+    font-size: .75em;
+    text-decoration: none;
+    padding: 5px;
+}
+#src-dr-js code {
+    background: #000;
+    display: block;
+    color: #fff;
+    font-family: Menlo, Consolas, Monaco, "Lucida Console", monospace;
+    white-space: pre;
+    padding-left: 100px;
+    position: relative;
+    line-height: 1.2;
+}
+#src-dr-js code:hover {
+    background: #333;
+}
+#src-dr-js code:hover .ln {
+    color: #fff;
+}
+#src-dr-js code .ln {
+    position: absolute;
+    left: 0;
+    color: #333;
+    width: 70px;
+    text-align: right;
+}
+
+/*Scrollbar*/
+.dr-doc::-webkit-scrollbar,
+.dr-toc::-webkit-scrollbar {
+    width: 7px;
+    height: 9px;
+}
+.dr-doc::-webkit-scrollbar-button:start:decrement, 
+.dr-doc::-webkit-scrollbar-button:end:increment,
+.dr-toc::-webkit-scrollbar-button:start:decrement, 
+.dr-toc::-webkit-scrollbar-button:end:increment {
+    display: block;
+    height: 0;
+    background-color: transparent;
+}
+.dr-doc::-webkit-scrollbar-track-piece,
+.dr-toc::-webkit-scrollbar-track-piece {
+    -webkit-border-radius: 0;
+    -webkit-border-bottom-right-radius: 4px;
+    -webkit-border-bottom-left-radius: 4px;
+}
+.dr-doc::-webkit-scrollbar-thumb:vertical,
+.dr-toc::-webkit-scrollbar-thumb:vertical {
+    height: 50px;
+    background-color: rgba(0, 0, 0, 0.2);
+    -webkit-border-radius: 4px;
+}
+.dr-doc::-webkit-scrollbar-thumb:horizontal,
+.dr-toc::-webkit-scrollbar-thumb:horizontal {
+    width: 50px;
+    background-color: rgba(0, 0, 0, 0.2);
+    -webkit-border-radius: 4px;
+}

+ 373 - 0
client/3rd_party_libs/raphael/plugins/group.js

@@ -0,0 +1,373 @@
+/*******************************************************************************
+AToMPM - A Tool for Multi-Paradigm Modelling
+
+Copyright (c) 2011 Raphael Mannadiar (raphael.mannadiar@mail.mcgill.ca)
+
+This file is part of AToMPM.
+
+AToMPM is free software: you can redistribute it and/or modify it under the
+terms of the GNU Lesser General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later 
+version.
+
+AToMPM is distributed in the hope that it will be useful, but WITHOUT ANY 
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with AToMPM.  If not, see <http://www.gnu.org/licenses/>.
+*******************************************************************************/
+
+/* inspired by https://github.com/rhyolight/Raphael-Plugins (raphael.group.js) */
+
+Raphael.fn.group = function() {
+	
+	var r = this;
+	
+	function Group() 
+	{
+		var inst,
+ 			 set 	 	= r.set(),
+ 			 group 	= r.raphael.vml ? 
+						 	document.createElement("group") : 
+		 				 	document.createElementNS("http://www.w3.org/2000/svg", "g"),
+			 overlay = r.raphael.vml ? 
+						 	document.createElement("group") : 
+		 				 	document.createElementNS("http://www.w3.org/2000/svg", "g");
+
+
+		 /* transform a series of translate|rotate|scale commands into t|r|s
+			 NOTE :: the parameters for 'SVG.scale()' are not exactly the same
+						as those for 'Raphael.scale()'... this because the former
+					  	scales wrt. (0,0) while the latter defaults to scaling wrt.
+					  	the center of the element... for consistency, we force Raphael
+					  	transform() to scale around (0,0)
+		 	NOTE ::	the same goes for SVG.rotate() and Raphael.rotate() */
+		function svg2raphaelTransformString()
+		{
+			var rT = '';
+			group.getAttribute('transform').split(' ').forEach(
+					function(svgt)
+					{
+						if( (args = svgt.match(/translate\((.*),(.*)\)/)) )
+							rT += 't'+args[1]+','+args[2];
+						else if( (args = svgt.match(/rotate\((.*),(.*),(.*)\)/)) )
+							rT += 'r'+args[1]+','+args[2]+','+args[3];
+						else if( (args = svgt.match(/rotate\((.*)\)/)) )
+							rT += 'r'+args[1]+',0,0';
+						else if( (args = svgt.match(/scale\((.*),(.*)\)/)) )
+							rT += 's'+args[1]+','+args[2]+',0,0';
+						else if( (args = svgt.match(/scale\((.*)\)/)) )
+							rT += 's'+args[1]+','+args[1]+',0,0';
+						else if( (args = svgt.match(
+										/matrix\((.*),(.*),(.*),(.*),(.*),(.*)\)/)) )
+							rT += 'm'+args[1]+','+args[2]+','+args[3]+','+
+										 args[4]+','+args[5]+','+args[6];
+					});
+			return rT;
+		}
+
+
+		r.canvas.appendChild(group);
+		group.setAttribute('transform','');
+
+		inst = 
+			{'forEach':
+				function(callback)
+				{
+					for( var i=0; i<group.childNodes.length; i++ )
+						callback(group.childNodes[i],i,group.childNodes);
+				},
+
+			 'getAttr':
+				 function(attr)
+				 {
+ 					 return group.getAttribute(attr);
+				 },
+
+
+				/* NOTE :: set.getBBox() doesn't include group's transformations... to
+				 			  get an appropriate bbox, i do a little hack 
+								1. create a Rect identical to set.getBBox()
+								2. apply the groups transformations on that Rect
+								3. save the Rect's bbox
+								4. delete the Rect
+								5. return the computed bbox
+
+					the above procedure yields imperfect results when shapes are 
+					rotated... we end up returning the bbox of the shapes post-
+					transform bbox... to correct this, we would need a way to get
+					the 'world bbox' (i.e., parallel to screen) of a transformed 
+					shape 
+				 
+					NOTE :: Paths can produce bboxes with 0 height or width... to 
+							  avoid this, before proceeding with step 1. (above), we
+							  ensure that the result of set.getBBox() has at least 1px
+							  width and height */
+			 'getBBox': 
+				function() 
+				{
+					var bbox = set.getBBox();
+					if( bbox.height == 0 )
+					{
+						bbox.y -= 0.5;
+						bbox.height = 1;
+					}
+					if( bbox.width == 0 )
+					{
+						bbox.x -= 0.5;
+						bbox.width = 1;
+					}
+
+					var rect = r.rect(bbox.x,bbox.y,bbox.width,bbox.height);
+					rect.transform( svg2raphaelTransformString() );
+					bbox = rect.getBBox();
+					rect.remove();
+					return bbox;
+				},
+
+
+			 'hasAttr':
+				 function(attr)
+				 {
+ 					 return group.hasAttribute(attr);
+				 },
+
+
+			 'hide':
+				 function()
+				 {
+					 group.style.display = 'none';
+				 },
+
+
+			 /* render a highlighting effect 
+			  		1. remove current highlighting if any 
+			  		2. extract effect arguments 
+			  		3. construct rectangle matching set bbox (not group's because we
+						want bbox in group coordinates, not world coordinates)
+			 		4. setup rectangle appearance based on step 2.
+					5. add rectangle to group.overlay  */
+			 'highlight':
+				 function(args)
+				 {
+					 this.unhighlight();
+
+					 var color	 = args['color'],
+						  opacity = args['opacity'] || 0.3,
+						  width	 = args['width'] || 20,
+						  bbox 	 = set.getBBox(),
+					 	  rect 	 = r.rect(bbox.x,bbox.y,bbox.width,bbox.height,10);
+
+					 rect.attr('stroke',color);
+					 rect.attr('opacity',opacity);
+					 rect.attr('stroke-width',width);
+					 rect.node.setAttribute('__highlight',1);
+					 if( args['fill'] )
+						 rect.attr('fill',color);
+					 
+					 overlay.appendChild(rect.node);
+					 if( group.lastChild != overlay )
+						 group.appendChild(overlay);
+				 },
+
+
+			 'insertBefore':
+				 function(obj)
+				 {
+					 for( var i=0; i<r.canvas.childNodes.length; i++ )
+						 if( r.canvas.childNodes[i] == obj )
+							 return r.canvas.insertBefore(
+									 					r.canvas.removeChild(group), obj);
+					 throw 'can\'t insert before unknown element :: '+obj ;
+				 },
+
+
+			 'isVisible':
+				 function()
+				 {
+					 return group.style.display != 'none';
+				 },
+
+
+			 'matrixTransform':
+				 function(a,b,c,d,e,f)
+				 {
+					 var mstr = 
+							 (b == undefined ?
+							  	a :
+	  							'matrix('+a+','+b+','+c+','+d+','+e+','+f+')');
+ 					 group.setAttribute(
+ 							 'transform', 
+ 							 mstr+' '+group.getAttribute('transform'));
+				 },
+
+
+			 'node': group,
+
+
+			 'pop': 
+			 	function()
+				{
+					var res = set.pop();
+					group.removeChild(group.lastChild);
+					return res;					
+				},
+
+
+			 'push': 
+ 				function(item) 
+				{
+					function __push(it)
+					{
+						if( it.type == 'set' ) 
+							it.items.forEach( __push );
+						else 
+						{
+							group.appendChild(it.node);
+							set.push(it);
+						}
+					}
+					__push(item);
+				},
+
+
+			 'remove':
+				function()
+				{
+					group.parentNode.removeChild(group);
+				},
+
+
+ 			 'rotate': 
+ 				function(deg,cx,cy) 
+				{
+					var params =
+						(deg + (cx == undefined || cy == undefined ?
+						 	'' :
+							','+cx+','+cy));
+					group.setAttribute(
+							'transform', 
+							'rotate('+params+') '+group.getAttribute('transform'));
+				},
+
+
+			 'scale': 
+			 	function(sx,sy) 
+				{
+					var params = 
+						(sx + (sy == undefined ?
+	 							 ','+sx :
+	 							 ','+sy ));
+					group.setAttribute(
+							'transform', 
+							'scale('+params+') '+group.getAttribute('transform'));
+				},
+
+
+			 'setAttr':
+				function(attr,val)
+				{
+					group.setAttribute(attr,val);
+				},
+
+
+			 'show':
+				 function()
+				 {
+					 group.style.display = 'inline';
+				 },	
+
+
+			 /* render a tag 
+ 					 1. if 'text' is empty or 'append' is false, remove any current 
+					 	 tags
+					 1. otherwise, compute total height of current tags (to know what
+					 	 Y-offset to give to future tag)
+					 2. if 'text' is not empty, setup new tag given 'text' and 
+					 	 'style' and add it to group.overlay */
+			 'tag':
+				 function(text,style,append)
+				 {
+					 var offsetY = 0;
+					 if( !append || !text )
+					 {
+					 	for( var i=overlay.childNodes.length-1; i>=0; i-- )
+							 if( overlay.childNodes[i].hasAttribute('__tag') )
+ 								 overlay.removeChild(overlay.childNodes[i]);
+					 }
+					 else
+					 {
+ 						 for( var i=0; i<overlay.childNodes.length; i++ )
+							 if( overlay.childNodes[i].hasAttribute('__tag') )
+								 offsetY += overlay.childNodes[i].offsetHeight;
+					 }
+
+					 if( text )
+					 {
+						 var t = __canvas.text(0,offsetY,text);
+					 	 t.attr('text-anchor','start');
+					 	 t.attr(style);
+						 t.node.setAttribute('__tag',1);
+					 	 t.node.setAttribute('class','tag unselectable');
+ 						 overlay.appendChild(t.node);
+					 }
+
+					 if( group.lastChild != overlay )
+						 group.appendChild(overlay);
+				 },
+
+
+			 /* make the current group the first/last element of the canvas 
+				 (i.e., the first/last one to be rendered) */
+			 'toBack':
+				function()
+				{
+					var firstChild = group.parentNode.firstChild;
+					if( firstChild == group )
+						return;	
+					else 
+						group.parentNode.insertBefore(
+							group.parentNode.removeChild(group), 
+							firstChild);
+				},
+
+
+ 			 'toFront' :
+				function()
+				{
+					if( group.parentNode.lastChild == group )
+						return;	
+					else 
+						group.parentNode.appendChild(
+							group.parentNode.removeChild(group));
+				},
+
+
+			 'translate': 
+				function(x,y) 
+				{
+					group.setAttribute(
+							'transform', 
+							'translate('+x+','+y+') '+group.getAttribute('transform'));
+				},
+			 'type': 'group',
+
+
+			 /* remove current highlighting effect, if any */
+			 'unhighlight':
+				 function(args)
+				 {
+					 for( var i=0; i < overlay.childNodes.length; i++ )
+						 if( overlay.childNodes[i].hasAttribute('__highlight') )
+						 {
+							 overlay.removeChild(overlay.childNodes[i]);
+							 break;
+						 }
+				 }};
+	 	 return inst;
+	}
+
+	return Group();
+};
+

+ 3 - 0
client/3rd_party_libs/raphael/plugins/point.js

@@ -0,0 +1,3 @@
+Raphael.fn.point = function(x, y, r) {
+	return this.circle(x,y,r || 0.001);
+};

+ 100 - 0
client/3rd_party_libs/raphael/plugins/raphael.primitives.js

@@ -0,0 +1,100 @@
+/*!
+ * Raphael Primitives Plugin 0.2
+ *
+ * Copyright (c) 2009 Dmitry Baranovskiy (http://raphaeljs.com)
+ * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
+ */
+
+Raphael.fn.star = function (cx, cy, r, r2, rays) {
+    r2 = r2 || r * .382;
+    rays = rays || 5;
+    var points = ["M", cx, cy + r2, "L"],
+        R;
+    for (var i = 1; i < rays * 2; i++) {
+        R = i % 2 ? r : r2;
+        points = points.concat([(cx + R * Math.sin(i * Math.PI / rays)), (cy + R * Math.cos(i * Math.PI / rays))]);
+    }
+    points.push("z");
+    return this.path(points.join());
+};
+Raphael.fn.flower = function (cx, cy, rout, rin, n) {
+    rin = rin || rout * .5;
+    n = +n < 3 || !n ? 5 : n;
+    var points = ["M", cx, cy + rin, "Q"],
+        R;
+    for (var i = 1; i < n * 2 + 1; i++) {
+        R = i % 2 ? rout : rin;
+        points = points.concat([+(cx + R * Math.sin(i * Math.PI / n)).toFixed(3), +(cy + R * Math.cos(i * Math.PI / n)).toFixed(3)]);
+    }
+    points.push("z");
+    return this.path(points);
+};
+Raphael.fn.spike = function (cx, cy, rout, rin, n) {
+    rin = rin || rout * .5;
+    n = +n < 3 || !n ? 5 : n;
+    var points = ["M", cx, cy - rout, "Q"],
+        R;
+    for (var i = 1; i < n * 2 + 1; i++) {
+        R = i % 2 ? rin : rout;
+        points = points.concat([cx + R * Math.sin(i * Math.PI / n - Math.PI), cy + R * Math.cos(i * Math.PI / n - Math.PI)]);
+    }
+    points.push("z");
+    return this.path(points);
+};
+Raphael.fn.polyline = function () {
+    var points = "M".concat(arguments[0] || 0, ",", arguments[1] || 0, "L");
+    for (var i = 2, ii = arguments.length - 1; i < ii; i++) {
+        points += arguments[i] + "," + arguments[++i];
+    }
+    arguments[ii].toLowerCase() == "z" && (points += "z");
+    return this.path(points);
+};
+Raphael.fn.polygon = function (cx, cy, r, n) {
+    n = +n < 3 || !n ? 5 : n;
+    var points = ["M", cx, cy - r, "L"],
+        R;
+    for (var i = 1; i < n; i++) {
+        points = points.concat([cx + r * Math.sin(i * Math.PI * 2 / n - Math.PI), cy + r * Math.cos(i * Math.PI * 2 / n - Math.PI)]);
+    }
+    points.push("z");
+    return this.path(points);
+};
+Raphael.fn.line = function (x1, y1, x2, y2) {
+    return this.path(["M", x1, y1, "L", x2, y2]);
+};
+Raphael.fn.drawGrid = function (x, y, w, h, wv, hv, color) {
+    color = color || "#000";
+    var path = ["M", x, y, "L", x + w, y, x + w, y + h, x, y + h, x, y],
+        rowHeight = h / hv,
+        columnWidth = w / wv;
+    for (var i = 1; i < hv; i++) {
+        path = path.concat(["M", x, y + i * rowHeight, "L", x + w, y + i * rowHeight]);
+    }
+    for (var i = 1; i < wv; i++) {
+        path = path.concat(["M", x + i * columnWidth, y, "L", x + i * columnWidth, y + h]);
+    }
+    return this.path(path.join(",")).attr({stroke: color});
+};
+Raphael.fn.square = function (cx, cy, r) {
+    r = r * .7;
+    return this.rect(cx - r, cy - r, 2 * r, 2 * r);
+};
+Raphael.fn.triangle = function (cx, cy, r) {
+    r *= 1.75;
+    return this.path("M".concat(cx, ",", cy, "m0-", r * .58, "l", r * .5, ",", r * .87, "-", r, ",0z"));
+};
+Raphael.fn.diamond = function (cx, cy, r) {
+    return this.path(["M", cx, cy - r, "l", r, r, -r, r, -r, -r, r, -r, "z"]);
+};
+Raphael.fn.cross = function (cx, cy, r) {
+    r = r / 2.5;
+    return this.path("M".concat(cx - r, ",", cy, "l", [-r, -r, r, -r, r, r, r, -r, r, r, -r, r, r, r, -r, r, -r, -r, -r, r, -r, -r, "z"]));
+};
+Raphael.fn.plus = function (cx, cy, r) {
+    r = r / 2;
+    return this.path("M".concat(cx - r / 2, ",", cy - r / 2, "l", [0, -r, r, 0, 0, r, r, 0, 0, r, -r, 0, 0, r, -r, 0, 0, -r, -r, 0, 0, -r, "z"]));
+};
+Raphael.fn.arrow = function (cx, cy, r) {
+    return this.path("M".concat(cx - r * .7, ",", cy - r * .4, "l", [r * .6, 0, 0, -r * .4, r, r * .8, -r, r * .8, 0, -r * .4, -r * .6, 0], "z"));
+};
+

File diff suppressed because it is too large
+ 8 - 0
client/3rd_party_libs/raphael/raphael-min.js


File diff suppressed because it is too large
+ 5985 - 0
client/3rd_party_libs/raphael/raphael-src.html


File diff suppressed because it is too large
+ 6319 - 0
client/3rd_party_libs/raphael/raphael.js


File diff suppressed because it is too large
+ 1441 - 0
client/3rd_party_libs/raphael/reference.html


+ 179 - 0
client/3rd_party_libs/sha1.js

@@ -0,0 +1,179 @@
+//REF:: http://www.movable-type.co.uk/scripts/sha1.html
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
+/*  SHA-1 implementation in JavaScript | (c) Chris Veness 2002-2010 | www.movable-type.co.uk      */
+/*   - see http://csrc.nist.gov/groups/ST/toolkit/secure_hashing.html                             */
+/*         http://csrc.nist.gov/groups/ST/toolkit/examples.html                                   */
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
+
+var Sha1 = {};  // Sha1 namespace
+
+/**
+ * Generates SHA-1 hash of string
+ *
+ * @param {String} msg                String to be hashed
+ * @param {Boolean} [utf8encode=true] Encode msg as UTF-8 before generating hash
+ * @returns {String}                  Hash of msg as hex character string
+ */
+Sha1.hash = function(msg, utf8encode) {
+  utf8encode =  (typeof utf8encode == 'undefined') ? true : utf8encode;
+  
+  // convert string to UTF-8, as SHA only deals with byte-streams
+  if (utf8encode) msg = Utf8.encode(msg);
+  
+  // constants [§4.2.1]
+  var K = [0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6];
+  
+  // PREPROCESSING 
+  
+  msg += String.fromCharCode(0x80);  // add trailing '1' bit (+ 0's padding) to string [§5.1.1]
+  
+  // convert string msg into 512-bit/16-integer blocks arrays of ints [§5.2.1]
+  var l = msg.length/4 + 2;  // length (in 32-bit integers) of msg + ‘1’ + appended length
+  var N = Math.ceil(l/16);   // number of 16-integer-blocks required to hold 'l' ints
+  var M = new Array(N);
+  
+  for (var i=0; i<N; i++) {
+    M[i] = new Array(16);
+    for (var j=0; j<16; j++) {  // encode 4 chars per integer, big-endian encoding
+      M[i][j] = (msg.charCodeAt(i*64+j*4)<<24) | (msg.charCodeAt(i*64+j*4+1)<<16) | 
+        (msg.charCodeAt(i*64+j*4+2)<<8) | (msg.charCodeAt(i*64+j*4+3));
+    } // note running off the end of msg is ok 'cos bitwise ops on NaN return 0
+  }
+  // add length (in bits) into final pair of 32-bit integers (big-endian) [§5.1.1]
+  // note: most significant word would be (len-1)*8 >>> 32, but since JS converts
+  // bitwise-op args to 32 bits, we need to simulate this by arithmetic operators
+  M[N-1][14] = ((msg.length-1)*8) / Math.pow(2, 32); M[N-1][14] = Math.floor(M[N-1][14])
+  M[N-1][15] = ((msg.length-1)*8) & 0xffffffff;
+  
+  // set initial hash value [§5.3.1]
+  var H0 = 0x67452301;
+  var H1 = 0xefcdab89;
+  var H2 = 0x98badcfe;
+  var H3 = 0x10325476;
+  var H4 = 0xc3d2e1f0;
+  
+  // HASH COMPUTATION [§6.1.2]
+  
+  var W = new Array(80); var a, b, c, d, e;
+  for (var i=0; i<N; i++) {
+  
+    // 1 - prepare message schedule 'W'
+    for (var t=0;  t<16; t++) W[t] = M[i][t];
+    for (var t=16; t<80; t++) W[t] = Sha1.ROTL(W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16], 1);
+    
+    // 2 - initialise five working variables a, b, c, d, e with previous hash value
+    a = H0; b = H1; c = H2; d = H3; e = H4;
+    
+    // 3 - main loop
+    for (var t=0; t<80; t++) {
+      var s = Math.floor(t/20); // seq for blocks of 'f' functions and 'K' constants
+      var T = (Sha1.ROTL(a,5) + Sha1.f(s,b,c,d) + e + K[s] + W[t]) & 0xffffffff;
+      e = d;
+      d = c;
+      c = Sha1.ROTL(b, 30);
+      b = a;
+      a = T;
+    }
+    
+    // 4 - compute the new intermediate hash value
+    H0 = (H0+a) & 0xffffffff;  // note 'addition modulo 2^32'
+    H1 = (H1+b) & 0xffffffff; 
+    H2 = (H2+c) & 0xffffffff; 
+    H3 = (H3+d) & 0xffffffff; 
+    H4 = (H4+e) & 0xffffffff;
+  }
+
+  return Sha1.toHexStr(H0) + Sha1.toHexStr(H1) + 
+    Sha1.toHexStr(H2) + Sha1.toHexStr(H3) + Sha1.toHexStr(H4);
+}
+
+//
+// function 'f' [§4.1.1]
+//
+Sha1.f = function(s, x, y, z)  {
+  switch (s) {
+  case 0: return (x & y) ^ (~x & z);           // Ch()
+  case 1: return x ^ y ^ z;                    // Parity()
+  case 2: return (x & y) ^ (x & z) ^ (y & z);  // Maj()
+  case 3: return x ^ y ^ z;                    // Parity()
+  }
+}
+
+//
+// rotate left (circular left shift) value x by n positions [§3.2.5]
+//
+Sha1.ROTL = function(x, n) {
+  return (x<<n) | (x>>>(32-n));
+}
+
+//
+// hexadecimal representation of a number 
+//   (note toString(16) is implementation-dependant, and  
+//   in IE returns signed numbers when used on full words)
+//
+Sha1.toHexStr = function(n) {
+  var s="", v;
+  for (var i=7; i>=0; i--) { v = (n>>>(i*4)) & 0xf; s += v.toString(16); }
+  return s;
+}
+
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
+/*  Utf8 class: encode / decode between multi-byte Unicode characters and UTF-8 multiple          */
+/*              single-byte character encoding (c) Chris Veness 2002-2010                         */
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
+
+var Utf8 = {};  // Utf8 namespace
+
+/**
+ * Encode multi-byte Unicode string into utf-8 multiple single-byte characters 
+ * (BMP / basic multilingual plane only)
+ *
+ * Chars in range U+0080 - U+07FF are encoded in 2 chars, U+0800 - U+FFFF in 3 chars
+ *
+ * @param {String} strUni Unicode string to be encoded as UTF-8
+ * @returns {String} encoded string
+ */
+Utf8.encode = function(strUni) {
+  // use regular expressions & String.replace callback function for better efficiency 
+  // than procedural approaches
+  var strUtf = strUni.replace(
+      /[\u0080-\u07ff]/g,  // U+0080 - U+07FF => 2 bytes 110yyyyy, 10zzzzzz
+      function(c) { 
+        var cc = c.charCodeAt(0);
+        return String.fromCharCode(0xc0 | cc>>6, 0x80 | cc&0x3f); }
+    );
+  strUtf = strUtf.replace(
+      /[\u0800-\uffff]/g,  // U+0800 - U+FFFF => 3 bytes 1110xxxx, 10yyyyyy, 10zzzzzz
+      function(c) { 
+        var cc = c.charCodeAt(0); 
+        return String.fromCharCode(0xe0 | cc>>12, 0x80 | cc>>6&0x3F, 0x80 | cc&0x3f); }
+    );
+  return strUtf;
+}
+
+/**
+ * Decode utf-8 encoded string back into multi-byte Unicode characters
+ *
+ * @param {String} strUtf UTF-8 string to be decoded back to Unicode
+ * @returns {String} decoded string
+ */
+Utf8.decode = function(strUtf) {
+  // note: decode 3-byte chars first as decoded 2-byte strings could appear to be 3-byte char!
+  var strUni = strUtf.replace(
+      /[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g,  // 3-byte chars
+      function(c) {  // (note parentheses for precence)
+        var cc = ((c.charCodeAt(0)&0x0f)<<12) | ((c.charCodeAt(1)&0x3f)<<6) | ( c.charCodeAt(2)&0x3f); 
+        return String.fromCharCode(cc); }
+    );
+  strUni = strUni.replace(
+      /[\u00c0-\u00df][\u0080-\u00bf]/g,                 // 2-byte chars
+      function(c) {  // (note parentheses for precence)
+        var cc = (c.charCodeAt(0)&0x1f)<<6 | c.charCodeAt(1)&0x3f;
+        return String.fromCharCode(cc); }
+    );
+  return strUni;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

+ 174 - 0
client/atompm.html

@@ -0,0 +1,174 @@
+<!--****************************************************************************
+AToMPM - A Tool for Multi-Paradigm Modelling
+
+Copyright (c) 2011 Raphael Mannadiar (raphael.mannadiar@mail.mcgill.ca)
+
+This file is part of AToMPM.
+
+AToMPM is free software: you can redistribute it and/or modify it under the
+terms of the GNU Lesser General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later 
+version.
+
+AToMPM is distributed in the hope that it will be useful, but WITHOUT ANY 
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with AToMPM.  If not, see <http://www.gnu.org/licenses/>.
+*****************************************************************************-->
+
+<html>
+	<head>
+		<meta charset="utf-8">
+		<link rel="stylesheet" type="text/css" href="client/styles.css"/>
+		<script text="text/javascript" src="client/globalVariables.js"></script>
+		<script text="text/javascript" src="/socket.io/socket.io.js"></script>
+		<script text="text/javascript" src="client/3rd_party_libs/sha1.js"></script>		
+		<script text="text/javascript" src="client/3rd_party_libs/raphael/raphael.js"></script>		
+		<script text="text/javascript" src="client/3rd_party_libs/raphael/plugins/point.js"></script>				
+		<script text="text/javascript" src="client/3rd_party_libs/raphael/plugins/group.js"></script>		
+		<script text="text/javascript" src="client/3rd_party_libs/raphael/plugins/raphael.primitives.js"></script>
+		<script text="text/javascript" src="client/3rd_party_libs/jquery-1.8.2.min.js"></script>	
+        <script src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
+		<script text="text/javascript" src="client/constants.js"></script>		
+		<script text="text/javascript" src="types.js"></script>		
+		<script text="text/javascript" src="utils.js"></script>	
+		<script text="text/javascript" src="client/behaviourmanager.js"></script>
+		<script text="text/javascript" src="client/behavioursc_inputbar.js"></script>
+		<script text="text/javascript" src="client/behavioursc_canvas.js"></script>
+		<script text="text/javascript" src="client/behavioursc_dialog.js"></script>
+		<script text="text/javascript" src="client/http_utils.js"></script>
+		<script text="text/javascript" src="client/window_event.js"></script>
+		<script text="text/javascript" src="client/user_management.js"></script>
+		<script text="text/javascript" src="client/init.js"></script>
+		<script text="text/javascript" src="client/input_bar_utils.js"></script>
+		<script text="text/javascript" src="client/gui_utils.js"></script>
+		<script text="text/javascript" src="client/data_utils.js"></script>
+		<script text="text/javascript" src="client/edit_utils.js"></script>
+		<script text="text/javascript" src="client/compile_utils.js"></script>
+		<script text="text/javascript" src="client/svg_utils.js"></script>
+		<script text="text/javascript" src="client/mmm_utils.js"></script>
+		<script text="text/javascript" src="client/selection_utils.js"></script>
+		<script text="text/javascript" src="client/geometry_utils.js"></script>
+		<script text="text/javascript" src="client/connection_utils.js"></script>
+		<script text="text/javascript" src="client/window_management.js"></script>
+		<script text="text/javascript" src="client/query_response.js"></script>
+		<script text="text/javascript" src="client/collaboration.js"></script>
+		<script text="text/javascript" src="client/client.js"></script>
+		<script text="text/javascript" src="styleinfo.js"></script>
+		<script>
+			$(document).ready(function(){
+				/**
+				 * Initiate the default Window Event actions
+				 */
+				//WindowEventHelper.initDefault();
+			});
+		</script>
+	</head>
+	<body class="default_style">
+		<div id="rootDiv" class="rootDiv">
+			<div id="commandHistory" class="commandHistory">
+			</div>
+			<div id="inputDiv" class="inputDiv">
+				<input placeholder="type 'help' to get information about querying" name="mainInput" type="text" class="mainInput" id="mainInput" onkeyup="InputBarUtils.processKey( event )"/>
+			</div>
+			<div id="contentDiv" class="contentDiv">
+				<div id="div_container" class="container">
+					<div id="div_canvas" class="canvas"></div>
+				</div>
+				<div class="header" align="right">
+					<a id="a_logout" class="enabled_link unselectable" onclick="UserManagement.logout(); return false;"><b>logout</b></a>
+				</div>
+				<div id="div_dock" class="dock"></div>
+			</div>
+		</div>
+
+		<div class="footer" align="right">
+			<a class="enabled_link unselectable" id="showChat" href=
+			"javascript:Collaboration.toggleChat();">Show Chat</a><br>
+
+			<div id="chat">
+			  <div id="menu">
+				<p class="welcome">Welcome,</p>
+
+				<div class="welcome" id="chatName"></div>
+
+				<p class="logout"><a id="hideChat" href=
+				"javascript:Collaboration.toggleChat();">Hide Chat</a></p>
+
+				<div style="clear:both"></div>
+			  </div>
+
+			  <div id="chatbox"></div>
+
+			  <form name="message" action="javascript:Collaboration.sendText();" style=
+			  "padding-right:30px">
+				<input name="usermsg" type="text" id="usermsg" size="63">
+				<input name="submitmsg" type="submit" id="submitmsg" value=
+				"Send">
+			  </form>
+			</div>
+			<a id="a_screenshare" class="disabled_link unselectable" target="_blank">send <b>screenshare</b> invitation</a></br> 
+			<a id="a_modelshare" class="disabled_link unselectable" target="_blank">send <b>modelshare</b> invitation</a>
+		</div>
+
+		<div id="div_login" class="dark_bg" style="display:none;width:100%;height=100%">
+			<div class="dark_bg"></div>
+			<div class="login" style="text-align:center">
+				<h3>Log in to use AToMPM</h3>
+					<form onsubmit="return false;">
+						<table class="default_style">
+							<tr><td>username</td><td><input id="input_username" class="default_style" type="text"/></td></tr>
+							<tr><td>password</td><td><input id="input_password" class="default_style" type="password"/></td></tr>
+						</table></br>
+						<div id="div_login_error" class="error"></div>
+						<a href="#" class="enabled_link" 
+							onclick="
+								UserManagement.validateCredentials($('#input_username').val(), $('#input_password').val()); 
+								return false;"><h3>Log in</h3></a><br/>
+					   (don't have an account? <a href="#" class="enabled_link" 
+						 	onclick="
+								$('#form_signup').css('display', 'inline');
+								return false;">create one now</a>)
+					</form><br/>
+
+					<form id="form_signup" onsubmit="return false;" style="display:none">
+						<table class="default_style">
+							<tr><td>choose username</td><td><input id="new_username" class="default_style" type="text"/></td></tr>
+							<tr><td>choose password</td><td><input id="new_password" class="default_style" type="password"/></td></tr>
+							<tr><td>re-type password</td><td><input id="new_password2" class="default_style" type="password"/></td></tr>
+						</table></br>
+						<div id="div_signup_error" class="error"></div>
+						<a href="#" class="enabled_link" 
+							onclick="
+								$('#div_signup_error').html( '' );
+								if( $('#new_password').val() != $('#new_password2').val() )
+									$('#div_signup_error').html( 'passwords don\'t match' );
+								else if( $('#new_password').val() == '' )
+									$('#div_signup_error').html( 'you must specify a password' );									
+								else if( ! $('#new_username').val().match(/^[0-9a-zA-Z]*$/) )
+									$('#div_signup_error').html( 'usernames must be alphanumeric' );							
+								else
+									UserManagement.signup($('#new_username').val(), $('#new_password').val()); 
+                                $('#pleaseWaitNote').show();
+                                var dots = window.setInterval( function() {
+                                    var wait = document.getElementById('waitNoteDots');
+                                    if ( wait.innerHTML.length > 10 ) 
+                                        wait.innerHTML = '';
+                                    else 
+                                        wait.innerHTML += '.';
+                                    }, 500);
+								return false;"><h3>Create account</h3></a>
+                         <span id="pleaseWaitNote" style="display:none">User is being created, please wait<span id="waitNoteDots">.</span></span>
+					</form>						
+			</div>		
+		</div>
+		
+
+		<div id="div_dim_bg" class="dim_bg" style="display:none"></div>
+		<div id="div_dialog" class="dialog" style="display:none;text-align:center"></div>		
+		<div id="div_geom_ctrls" class="geometry_ctrls" style="display:none"></div>
+	</body>
+</html>
+

+ 62 - 0
client/behaviourmanager.js

@@ -0,0 +1,62 @@
+/*******************************************************************************
+AToMPM - A Tool for Multi-Paradigm Modelling
+
+Copyright (c) 2011 Raphael Mannadiar (raphael.mannadiar@mail.mcgill.ca)
+Modified by Conner Hansen (chansen@crimson.ua.edu)
+
+This file is part of AToMPM.
+
+AToMPM is free software: you can redistribute it and/or modify it under the
+terms of the GNU Lesser General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later 
+version.
+
+AToMPM is distributed in the hope that it will be useful, but WITHOUT ANY 
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with AToMPM.  If not, see <http://www.gnu.org/licenses/>.
+*******************************************************************************/
+
+BehaviorManager = new function(){
+	var activeBehaviourStatechart = undefined;
+	
+	/**
+	 * Sends the current event to the currently active state chart, if 
+	 * it exists
+	 */
+	this.handleUserEvent = function( name, event ){
+		if( activeBehaviourStatechart == undefined ) {
+			console.warn('There is no active behaviour statechart to process the event. ' +
+					'If this event was triggered immediately after a page load, ' + 
+					'then the statechart may just not be loaded yet.');
+		} else {
+			activeBehaviourStatechart.handleUserEvent(name,event);
+		}
+	};
+	
+	/**
+	 * Returns whether or not there is a state chart currently loaded
+	 */
+	this.isStatechartLoaded = function() {
+		return activeBehaviourStatechart != undefined;
+	};
+	
+	/**
+	 * Sets the currently active state chart
+	 */
+	this.setActiveBehaviourStatechart = function(sc, init){
+		if( sc == __SC_DOCK )
+			throw 'Dock behaviour is not [yet] described by a statechart';
+		else if( sc == __SC_CANVAS )
+			activeBehaviourStatechart = __canvasBehaviourStatechart;
+		else if( sc == __SC_DIALOG )
+			activeBehaviourStatechart = __dialogBehaviourStatechart;
+
+		if( init )
+			activeBehaviourStatechart.init();
+	};
+	
+	return this;
+}();

+ 442 - 0
client/behavioursc_canvas.js

@@ -0,0 +1,442 @@
+/*******************************************************************************
+AToMPM - A Tool for Multi-Paradigm Modelling
+
+Copyright (c) 2011 Raphael Mannadiar (raphael.mannadiar@mail.mcgill.ca)
+
+This file is part of AToMPM.
+
+AToMPM is free software: you can redistribute it and/or modify it under the
+terms of the GNU Lesser General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later 
+version.
+
+AToMPM is distributed in the hope that it will be useful, but WITHOUT ANY 
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with AToMPM.  If not, see <http://www.gnu.org/licenses/>.
+*******************************************************************************/
+
+__canvasBehaviourStatechart = {
+	'__STATE_IDLE'	 				 		 				: 0,
+	'__STATE_CANVAS_SELECTING'	 						: 1,
+	'__STATE_SOMETHING_SELECTED' 						: 2,
+	'__STATE_DRAGGING_SELECTION'		 				: 3,
+	'__STATE_DRAWING_EDGE'				 				: 4,
+	'__STATE_EDITING_CONNECTION_PATHS' 				: 5,
+	'__STATE_DRAGGING_CONNECTION_PATH_CTRL_POINT': 6,
+	'__currentState' 						 				: undefined,
+
+	'__entryActions':{
+		1:
+			function(event)
+			{
+				GUIUtils.disableDock();
+				__initCanvasSelectionOverlay(GUIUtils.convertToCanvasX(event.pageX), GUIUtils.convertToCanvasY(event.pageY));
+			},
+		3:
+			function(event)	
+			{
+				GUIUtils.disableDock();
+				GeometryUtils.initSelectionTransformationPreviewOverlay(GUIUtils.convertToCanvasX(event.pageX), GUIUtils.convertToCanvasY(event.pageY));
+			},
+		4:
+			function(event)	
+			{
+				GUIUtils.disableDock();
+				ConnectionUtils.initConnectionPath(GUIUtils.convertToCanvasX(event.pageX), GUIUtils.convertToCanvasY(event.pageY),event.target);
+			},
+		6:
+			function(event)	
+			{
+				GUIUtils.disableDock();
+			} 
+		},
+
+	'__exitActions' :{
+		1:
+			function(event)	
+			{
+				GUIUtils.enableDock();
+			},
+		3:
+			function(event)	
+			{
+				GUIUtils.enableDock();
+			},
+		4:
+			function(event)	
+			{
+				GUIUtils.enableDock();
+			},
+		6:
+			function(event)	
+			{
+				GUIUtils.enableDock();
+			}
+		},
+
+	/* transition to specified  state */
+	'__T' : 
+		function(s,event)
+		{
+			if( this.__currentState in this.__exitActions )
+				this.__exitActions[this.__currentState](event);
+		
+			this.__currentState = s;
+		
+			if( s in this.__entryActions )
+				this.__entryActions[s](event);
+		},
+
+	/* initialise the statechart */
+	'init':
+		function()
+		{
+			this.__currentState = this.__STATE_IDLE;
+		},
+
+	/* handle an event... only discarded events are allowed to propagate to parent
+	  	HTML element
+			name: 	internal name of the event
+			event:	the javascript event */
+	'handleUserEvent':
+		function(name,event)
+		{
+			if( this.__currentState == this.__STATE_IDLE )
+			{
+				if( name == __EVENT_RIGHT_RELEASE_CANVAS )
+					DataUtils.create(GUIUtils.convertToCanvasX(event.pageX), GUIUtils.convertToCanvasY(event.pageY));
+			
+				else if( name == __EVENT_LEFT_PRESS_CANVAS )
+					this.__T(this.__STATE_CANVAS_SELECTING,event);
+		
+				else if( name == __EVENT_MIDDLE_RELEASE_ICON )
+				{
+					__select( event.target );
+					WindowManagement.openDialog(_ENTITY_EDITOR);
+					this.__T(this.__STATE_SOMETHING_SELECTED,event);
+				}
+				
+				else if( name == __EVENT_SHIFT_MIDDLE_RELEASE_ICON )
+					WindowManagement.openDialog(__SVG_TEXT_EDITOR, event.target);
+				
+				else if( name == __EVENT_LEFT_RELEASE_ICON )
+				{
+					__select( event.target );
+					this.__T(this.__STATE_SOMETHING_SELECTED,event);
+				}
+
+				else if( name == __EVENT_SHIFT_LEFT_RELEASE_ICON )
+				{
+					__select( event.target, true );
+					WindowManagement.openDialog(_ENTITY_EDITOR);
+					this.__T(this.__STATE_SOMETHING_SELECTED,event);
+				}
+				
+				else if( name == __EVENT_CODED_SELECTION )
+				{
+					__select(event);
+					this.__T(this.__STATE_SOMETHING_SELECTED,event);
+				}
+				
+				else if( name == __EVENT_RIGHT_PRESS_ICON )
+					this.__T(this.__STATE_DRAWING_EDGE,event);
+				
+				else if( name == __EVENT_SHIFT_WHEEL_ICON )
+				{
+					if( event.wheelDelta > 0 )
+						__iconToFront(event.target);
+					else
+						__iconToBack(event.target);
+				}
+				
+				else
+					return;
+				
+				if( event && event.stopPropagation )
+				{
+					event.stopPropagation();
+					event.preventDefault();
+				}
+			}
+			
+			
+			else if( this.__currentState == this.__STATE_CANVAS_SELECTING )
+			{
+				if( name == __EVENT_MOUSE_MOVE ){
+					__updateCanvasSelectionOverlay(GUIUtils.convertToCanvasX(event.pageX), GUIUtils.convertToCanvasY(event.pageY));
+				}
+				
+				else if( name == __EVENT_LEFT_RELEASE_CANVAS ||
+							name == __EVENT_LEFT_RELEASE_ICON )
+				{
+					if( ! __selectSelection() )
+						this.__T(this.__STATE_IDLE,event);
+					else
+						this.__T(this.__STATE_SOMETHING_SELECTED,event);
+				}	
+				
+				else
+					return;
+				
+				if( event  && event.stopPropagation )
+				{
+					event.stopPropagation();
+					event.preventDefault();
+				}
+			}
+
+
+			else if( this.__currentState == this.__STATE_SOMETHING_SELECTED )
+			{
+				if( name == __EVENT_KEYUP_DEL )
+				{
+					DataUtils.del();
+					__select();
+					this.__T(this.__STATE_IDLE,event);			
+				}
+				
+				else if( name == __EVENT_KEYUP_ESC			  			||
+							name == __EVENT_LEFT_PRESS_CANVAS  			||
+							name == __EVENT_LEFT_PRESS_ICON 	  			||
+							name == __EVENT_CODED_CANVAS_EDIT  			||
+							name == __EVENT_MIDDLE_RELEASE_CANVAS 		||
+							name == __EVENT_MIDDLE_RELEASE_ICON 		||
+							name == __EVENT_SHIFT_MIDDLE_RELEASE_ICON ||
+							name == __EVENT_RIGHT_RELEASE_CANVAS 		||
+							name == __EVENT_RIGHT_RELEASE_ICON )
+				{
+					__select();
+					this.__T(this.__STATE_IDLE,event);
+				}
+				
+				else if( name == __EVENT_LEFT_PRESS_SELECTION )
+				{
+					if( ! GeometryUtils.areTransformationsAllowed() )
+						console.warn('the selection dragging mode can only be activated if '+
+										 'all of the ends of selected edges are also selected, '+
+										 'and if the geometry controls are inactive')
+					else			
+						this.__T(this.__STATE_DRAGGING_SELECTION,event);
+				}
+				
+				else if( name == __EVENT_KEYUP_CTRL )
+				{
+					if( ! GeometryUtils.areTransformationsAllowed() )
+						console.warn('the geometry controls can only be activated if all '+
+										 'of the ends of selected edges are also selected, and'+
+										 'if the geometry controls aren\'t already active')
+					else			
+						GeometryUtils.showGeometryControlsOverlay();
+				}
+				
+				else if( name == __EVENT_KEYUP_ALT )
+				{
+					GeometryUtils.hideGeometryControlsOverlay();
+					GeometryUtils.hideTransformationPreviewOverlay();
+				}
+				
+				else if( name == __EVENT_KEYUP_SHIFT && 
+							__selectionContainsType(__EDGETYPE) )
+				{
+					ConnectionUtils.showConnectionPathEditingOverlay();
+					__select();
+					this.__T(this.__STATE_EDITING_CONNECTION_PATHS);
+				}
+
+				else if( name == __EVENT_KEYUP_INS ||
+							name == __EVENT_KEYUP_COMMAND )
+				{
+					WindowManagement.openDialog(_ENTITY_EDITOR);
+				}
+				
+				else if( name == __EVENT_CODED_SELECTION )
+				{
+					__select(event);
+				}
+
+				else if( name == __EVENT_KEYUP_TAB )
+				{
+					if( ! GeometryUtils.areTransformationsAllowed() )
+						console.warn('selection snapping is only enabled if all of '+
+										 'the ends of selected edges are also selected, '+
+										 'and if the geometry controls are inactive');
+					else
+						GeometryUtils.snapSelectionToGrid();
+				}
+				
+				else
+					return;
+				
+				if( event  && event.stopPropagation )
+				{
+					event.stopPropagation();
+					event.preventDefault();
+				}
+			}
+			
+			
+			else if( this.__currentState == this.__STATE_DRAGGING_SELECTION )
+			{
+				if( name == __EVENT_MOUSE_MOVE )
+					GeometryUtils.previewSelectionTranslation(GUIUtils.convertToCanvasX(event.pageX), GUIUtils.convertToCanvasY(event.pageY));
+				
+				else if( name == __EVENT_KEYUP_ESC )
+				{
+					GeometryUtils.hideTransformationPreviewOverlay();
+					this.__T(this.__STATE_SOMETHING_SELECTED,event);
+				}
+				
+				else if( name == __EVENT_LEFT_RELEASE_CANVAS	   ||
+							name == __EVENT_LEFT_RELEASE_SELECTION )
+				{
+					GeometryUtils.transformSelection(__SELECTION_DRAG);
+					this.__T(this.__STATE_SOMETHING_SELECTED,event);
+				}
+				else if( name == __EVENT_LEFT_RELEASE_ICON )
+				{
+					DataUtils.getInsertConnectionType(
+							event.target,
+							undefined,
+							function(connectionType) 
+							{
+								if( connectionType )
+									GeometryUtils.transformSelection(
+										__SELECTION_DRAG,
+										{'dropTarget':event.target,
+										 'connectionType':connectionType});
+								else
+								{
+									console.warn('no containment relationship was created');
+									GeometryUtils.transformSelection(__SELECTION_DRAG);
+								}
+							});
+					this.__T(this.__STATE_SOMETHING_SELECTED,event);
+				}
+				
+				else
+					return;
+				
+				if( event  && event.stopPropagation )
+				{
+					event.stopPropagation();
+					event.preventDefault();
+				}
+			}
+			
+			
+			else if( this.__currentState == this.__STATE_DRAWING_EDGE )
+			{
+				if( name == __EVENT_MOUSE_MOVE ){
+					ConnectionUtils.updateConnectionSegment(GUIUtils.convertToCanvasX(event.pageX), GUIUtils.convertToCanvasY(event.pageY));
+				}
+				else if( name == __EVENT_KEYUP_ESC ||
+							name == __EVENT_RIGHT_RELEASE_CANVAS )
+				{
+					ConnectionUtils.hideConnectionPath();		
+					this.__T(this.__STATE_IDLE,event);
+				}
+
+				else if( name == __EVENT_LEFT_RELEASE_CANVAS ||
+							name == __EVENT_LEFT_RELEASE_ICON	||
+							name == __EVENT_KEYUP_CTRL )
+					ConnectionUtils.addConnectionSegment();
+				
+				else if( name == __EVENT_MIDDLE_RELEASE_CANVAS ||
+							name == __EVENT_MIDDLE_RELEASE_ICON	  ||
+							name == __EVENT_KEYUP_ALT )
+					ConnectionUtils.deleteConnectionSegment();
+				
+				else if( name == __EVENT_KEYUP_TAB )
+					ConnectionUtils.snapConnectionSegment();
+				
+				else if( name == __EVENT_RIGHT_RELEASE_ICON )
+				{
+					if( ConnectionUtils.getConnectionPath().getTotalLength() <= 5 )
+						console.warn('to avoid accidental path creations, paths must '+
+										 'measure at least 5px');
+					else
+						DataUtils.connect(event.target);
+					this.__T(this.__STATE_IDLE,event);
+				}
+				
+				else
+					return;
+				
+				if( event  && event.stopPropagation )
+				{
+					event.stopPropagation();
+					event.preventDefault();
+				}
+			}
+
+
+			else if( this.__currentState == this.__STATE_EDITING_CONNECTION_PATHS )
+			{
+				if( name == __EVENT_KEYUP_ESC 			 ||
+					 name == __EVENT_LEFT_RELEASE_CANVAS ||
+					 name == __EVENT_LEFT_RELEASE_ICON 	 ||
+					 name	== __EVENT_CODED_CANVAS_EDIT	 )
+				{
+					ConnectionUtils.hideConnectionPathEditingOverlay();
+					this.__T(this.__STATE_IDLE,event);
+				}
+		
+				else if( name == __EVENT_LEFT_PRESS_CTRL_POINT )
+				{
+					ConnectionUtils.initControlPointTranslation(event.target);
+					this.__T(this.__STATE_DRAGGING_CONNECTION_PATH_CTRL_POINT,event);		
+				}
+		
+				else if( name == __EVENT_MIDDLE_RELEASE_CTRL_POINT )
+					ConnectionUtils.deleteControlPoint(event.target);
+		
+				else if( name == __EVENT_RIGHT_RELEASE_CTRL_POINT )
+					ConnectionUtils.addControlPoint(GUIUtils.convertToCanvasX(event.pageX), GUIUtils.convertToCanvasY(event.pageY),event.target);
+						
+				else if( name == __EVENT_KEYUP_TAB )
+					ConnectionUtils.snapControlPoint();
+		
+				else if( name == __EVENT_CODED_SELECTION )
+				{
+					ConnectionUtils.hideConnectionPathEditingOverlay();
+					__select(event);
+					this.__T(this.__STATE_SOMETHING_SELECTED,event);
+				}
+		
+				else
+					return;
+
+				if( event  && event.stopPropagation )
+				{
+					event.stopPropagation();
+					event.preventDefault();
+				}
+			}
+
+
+			else if( this.__currentState == this.__STATE_DRAGGING_CONNECTION_PATH_CTRL_POINT )
+			{
+				if( name == __EVENT_MOUSE_MOVE )
+					ConnectionUtils.previewControlPointTranslation(GUIUtils.convertToCanvasX(event.pageX), GUIUtils.convertToCanvasY(event.pageY));
+		
+				else if( name == __EVENT_LEFT_RELEASE_CTRL_POINT )
+				{
+					ConnectionUtils.updateConnectionPath();
+					this.__T(this.__STATE_EDITING_CONNECTION_PATHS,event);
+				}
+		
+				else
+					return;
+		
+				if( event  && event.stopPropagation )
+				{
+					event.stopPropagation();
+					event.preventDefault();
+				}
+			}
+		}
+	}
+

+ 103 - 0
client/behavioursc_dialog.js

@@ -0,0 +1,103 @@
+/*******************************************************************************
+AToMPM - A Tool for Multi-Paradigm Modelling
+
+Copyright (c) 2011 Raphael Mannadiar (raphael.mannadiar@mail.mcgill.ca)
+
+This file is part of AToMPM.
+
+AToMPM is free software: you can redistribute it and/or modify it under the
+terms of the GNU Lesser General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later 
+version.
+
+AToMPM is distributed in the hope that it will be useful, but WITHOUT ANY 
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with AToMPM.  If not, see <http://www.gnu.org/licenses/>.
+*******************************************************************************/
+
+__dialogBehaviourStatechart = {
+	'__STATE_OPEN'	 : 0,
+	'__STATE_CLOSED': 1,
+	'__currentState': undefined,
+
+	'__entryActions':{
+		0:
+			function(event)	
+			{
+				WindowManagement.showDialog();
+			},
+		1:
+			function(event)	
+			{
+				WindowManagement.closeDialog(event); // HUSEYIN-ENTER
+				//WindowManagement.closeDialog();
+			}	
+		},
+
+	'__exitActions':{},
+	
+	/* transition to specified  state */
+	'__T' : 
+		function(s,event)
+		{
+			if( this.__currentState in this.__exitActions )
+				this.__exitActions[this.__currentState](event);
+		
+			this.__currentState = s;
+		
+			if( s in this.__entryActions )
+				this.__entryActions[s](event);
+		},
+
+	/* initialise the statechart */
+	'init':
+		function()
+		{
+			this.__currentState = this.__STATE_CLOSED;
+		},
+
+	/* handle an event... only discarded events are allowed to propagate to parent
+	  	HTML element
+			name: 	internal name of the event
+			event:	the javascript event */
+	'handleUserEvent':
+		function(name,event)
+		{
+			if( this.__currentState == this.__STATE_OPEN )
+			{
+				if( name == __EVENT_KEYUP_ESC ||
+					 name == __EVENT_CANCELED_DIALOG ||
+					 name == __EVENT_KEYUP_ENTER || // HUSEYIN-ENTER
+					 name == __EVENT_OKAYED_DIALOG )
+					this.__T(this.__STATE_CLOSED,event);
+		
+				else
+					return;
+		
+				if( event && event.stopPropagation )
+				{
+					event.stopPropagation();
+					event.preventDefault();
+				}
+			}
+
+			
+			else if( this.__currentState == this.__STATE_CLOSED )
+			{
+				if( name == __EVENT_SHOW_DIALOG )
+					this.__T(this.__STATE_OPEN,event);
+		
+				else
+					return;
+		
+				if( event && event.stopPropagation )
+				{
+					event.stopPropagation();
+					event.preventDefault();
+				}
+			}
+		}	
+}

+ 198 - 0
client/behavioursc_inputbar.js

@@ -0,0 +1,198 @@
+/*******************************************************************************
+AToMPM - A Tool for Multi-Paradigm Modelling
+
+Copyright (c) 2012 Conner Hansen (chansen@crimson.ua.edu)
+
+This file is part of AToMPM.
+
+AToMPM is free software: you can redistribute it and/or modify it under the
+terms of the GNU Lesser General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later 
+version.
+
+AToMPM is distributed in the hope that it will be useful, but WITHOUT ANY 
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with AToMPM.  If not, see <http://www.gnu.org/licenses/>.
+*******************************************************************************/
+
+/**
+ * This object defines my DummyStateChart formalism. It has
+ * State and Transition methods.
+ */
+var DummyStateChart = function(){
+	var states = new Array();
+	var transitions = new Array();
+	var currentState = null;
+	
+	this.State = function(name){
+		var out = new Array();
+		
+		this.addTransition = function( transition ){
+			out.push( transition );
+		};
+		
+		this.fire = function( trigger ){
+			for(var i=0; i<out.length; i++){
+				if( out[i].isTrigger( trigger ) ){
+					return out[i];
+				}
+			}
+			
+			return false;
+		};
+		
+		this.onEntry = function(){
+			//console.debug("OnEntry: " + name);
+		};
+		
+		this.onExit = function(){
+			//console.debug("OnExit: " + name);
+		};
+		
+		return this;
+	};
+	
+	this.Transition = function(trigger, from, to){
+		this.fire = function(){
+			return to;
+		};
+		
+		this.getTrigger = function(){
+			return trigger;
+		};
+		
+		this.isTrigger = function( input ){
+			return input == trigger;
+		};
+		
+		from.addTransition( this );
+		
+		return this;
+	};
+	
+	this.addState = function( state ){
+		states.push( state );
+	};
+	
+	this.addTransition = function( transition ){
+		transitions.push( transition );
+	};
+	
+	this.fireEvent = function( event ){
+		var transition = currentState.fire( event );
+		
+		if( transition ){
+			// Fire the on exit event
+			currentState.onExit();
+			
+			// Move to the new state and
+			// fire the entry event
+			currentState = transition.fire( event );
+			currentState.onEntry();
+		}
+	};
+	
+	this.getCurrentState = function(){
+		return currentState;
+	};
+	
+	this.setInitialState = function( index ){
+		currentState = states[index];
+		currentState.onEntry();
+	};
+	
+	return this;
+};
+
+var DummyBNF = function(){
+	
+};
+
+InputBarStateChart = function(){
+	var sc = new DummyStateChart();
+	var storage = null;
+	var triggers = new Array(
+			"inputEntered",
+			"validInput",
+			"invalidInput",
+			"errorDisplayed",
+			"noCommandFound",
+			"commandFound",
+			"executedCommand");
+	////////////////////////////////////////
+	// STATES
+	////////////////////////////////////////
+	var STATE_WAIT = new sc.State( "Wait" );
+	var STATE_PROCESS_INPUT = new sc.State( "ProcessInput" );
+	var STATE_SHOW_ERROR = new sc.State( "ShowError" );
+	var STATE_MATCH_COMMAND = new sc.State( "MatchCommand" );
+	var STATE_EXECUTE_COMMAND = new sc.State( "ExecuteCommand" );
+	
+	STATE_PROCESS_INPUT.onEntry = function(){
+		// split on any amount of whitespace
+		storage = $('#mainInput').value.split(/[ ]+/);
+		$('#mainInput').className.replace("error", "");
+		
+		// always return valid, since we don't yet have
+		// a BNF to define what is good/bad input
+		return sc.fireEvent( triggers[1] );
+	};
+	
+	STATE_SHOW_ERROR.onEntry = function(){
+		$('#mainInput').className += " error";
+		
+		return sc.fireEvent( triggers[3] );
+	};
+	
+	STATE_MATCH_COMMAND.onEntry = function(){
+		// No BNF, no command matching
+		return sc.fireEvent( triggers[5] );
+	};
+	
+	STATE_EXECUTE_COMMAND.onEntry = function(){
+		// Stop gap measure until the BNF is implemented
+		eval( $('#mainInput').value );
+		$('#mainInput').value = "";
+		return sc.fireEvent( triggers[6] );
+	};
+	
+	////////////////////////////////////////
+	// TRANSITIONS
+	////////////////////////////////////////	
+	var TRANS_INPUT_ENTERED = new sc.Transition( triggers[0], STATE_WAIT, STATE_PROCESS_INPUT);
+	var TRANS_VALID_INPUT = new sc.Transition( triggers[1], STATE_PROCESS_INPUT, STATE_MATCH_COMMAND);
+	var TRANS_INVALID_INPUT = new sc.Transition( triggers[2], STATE_PROCESS_INPUT, STATE_SHOW_ERROR);
+	var TRANS_ERROR_DISPLAYED = new sc.Transition( triggers[3], STATE_SHOW_ERROR, STATE_WAIT);
+	var TRANS_NO_COMMAND_FOUND = new sc.Transition( triggers[4], STATE_PROCESS_INPUT, STATE_SHOW_ERROR);
+	var TRANS_COMMAND_FOUND = new sc.Transition( triggers[5], STATE_MATCH_COMMAND, STATE_EXECUTE_COMMAND);
+	var TRANS_EXECUTE_COMMAND = new sc.Transition( triggers[6], STATE_EXECUTE_COMMAND, STATE_WAIT);
+
+	////////////////////////////////////////
+	// ADD ELEMENTS
+	////////////////////////////////////////
+	sc.addState(STATE_WAIT);
+	sc.addState(STATE_PROCESS_INPUT);
+	sc.addState(STATE_SHOW_ERROR);
+	sc.addState(STATE_MATCH_COMMAND);
+	sc.addState(STATE_EXECUTE_COMMAND);
+	
+	sc.addTransition(TRANS_INPUT_ENTERED);
+	sc.addTransition(TRANS_VALID_INPUT);
+	sc.addTransition(TRANS_INVALID_INPUT);
+	sc.addTransition(TRANS_ERROR_DISPLAYED);
+	sc.addTransition(TRANS_NO_COMMAND_FOUND);
+	sc.addTransition(TRANS_COMMAND_FOUND);
+	sc.addTransition(TRANS_EXECUTE_COMMAND);
+	
+	sc.setInitialState(0);
+	
+	this.fireEvent = function( trigger ){
+		sc.fireEvent( trigger );
+	};
+	
+	return this;
+}();
+

+ 773 - 0
client/client.js

@@ -0,0 +1,773 @@
+/*******************************************************************************
+AToMPM - A Tool for Multi-Paradigm Modelling
+
+Copyright (c) 2011 Raphael Mannadiar (raphael.mannadiar@mail.mcgill.ca)
+Modified by Conner Hansen (chansen@crimson.ua.edu)
+
+This file is part of AToMPM.
+
+AToMPM is free software: you can redistribute it and/or modify it under the
+terms of the GNU Lesser General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later 
+version.
+
+AToMPM is distributed in the hope that it will be useful, but WITHOUT ANY 
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with AToMPM.  If not, see <http://www.gnu.org/licenses/>.
+*******************************************************************************/
+
+/*TODO: look into reworking naming convention to be more intuitive
+*ie: 
+* button: btn_someFunc
+* window: wnd_someFunc
+* private: _someFunc
+* etc
+*/
+
+/* NOTES: 
+
+	NAMING CONVENTION...	functions and variables starting with '_' are meant to 
+	be used from within Button code (most of them are directly associated to a 
+	native back-end operation)... functions starting with '__' are meant to be 
+	private and should only be used from within application code 
+
+	WINDOW TITLE... window titles are set to pretty-print '__saveas'... if any
+	changelogs have been received since we loaded/saved the model, the title is 
+	changed to explicit this
+
+	the BACKDOOR API section contains methods that are accessible from the 
+	backend (or from anyone who has access to the backend, e.g., a synthesized
+  	application)... they are all void (i.e., they don't return anything or any
+	feedback to the 'caller') and should only be accessed remotely, via 
+	'PUT /GET/console {CLIENT_BDAPI:...}' to the backend
+ 
+	TBI:: add caching mechanism to avoid recompiling the same icon models into 
+			the same SVG over and over 
+
+	TBI:: add helper functions for parsing and manipulating path strings (see 
+			Raphael.parsePathString()... not only would this elevate abstraction,
+		  	but a lot of our connection-related operations could be optimized 
+			(including less string matching and splitting) if segments were arrays
+			of points rather than strings 
+
+
+	TBI:: when SVG 1.2 gets Chrome support, the __SVG_TEXT_EDITOR dialog could be
+			removed in favor of native SVG text editing facilities */
+
+
+/******************************** GLOBAL VARS *********************************/
+var __user = undefined,
+	 __wid,
+	 __aswid,
+	 __prefs,
+	 __typeToCreate,
+	 __loadedToolbars = {},
+	 __icons = {},
+	 __edges = {},
+	 __canvas,
+	 __saveas;
+/******************************** GLOBAL VARS *********************************/
+
+AtomPMClient = function(){
+	
+	/**
+	 * Log deprecated function calls
+	 */
+	this.alertDeprecatedFunctionCall = function( funcName ){
+		console.warn("Deprecated function call: " + funcName);
+	};
+	
+	return this;
+}();
+
+
+/**
+ * Automatically saves the current model.
+ * 
+ * If mode == backup, save the current model into a backup file
+ * If mode == overwrite, overwrite the current file model
+ */
+function __autosave(mode)
+{
+	if( mode == 'backup' )
+	{
+		if( __saveas )
+		{
+			var matches = __saveas.match(/(.*\/)(.*)/);
+			_saveModel(matches[1]+'.autosave.'+matches[2],true,true);
+		}
+		else
+			_saveModel(undefined,true,true);
+	}
+	else if( mode == 'overwrite' )
+		_saveModel(undefined,false,true);
+}
+
+
+/**
+ * Launches the autosave daemon, which automatically tries to
+ * save the model based on the interval stored in the preferences
+ * array
+ */
+function __launchAutosave()
+{
+	if( __prefs['autosave-delay']['value'] > 0 )
+		window.setInterval(
+				__autosave, 
+				__prefs['autosave-delay']['value'] * 1000,
+				__prefs['autosave-mode']['value']);
+}
+
+
+
+/********************************* 'USER' API *********************************/
+
+/**
+ * Creates an SVG blob out of the current SVG elements on the canvas
+ * 
+ * Note 1: an alternative to the current implementation (though less
+ *  efficient) would be to POST the data to the server and then have
+ *  the returned file be downloadable
+ * 
+ * Note 2: a click is simulated instead of using 
+ *  window.location.assign() so that the target filename can be 
+ *  chosen
+ *  
+ * Note 3: the output width of the image is altered in order to fit
+ *  the content. This would be fixed if we utilized a dynamically 
+ *  expanding canvas
+ * 
+ * Note 4: the 'href' tags are changed to 'xlink:href' tags in the 
+ *  generated SVG file. All images are embedded as data uris in the
+ *  output in order to increase robustness.
+ */
+function _exportSVG(fname)
+{
+	var BlobBuilder = window.WebKitBlobBuilder || window.MozBlobBuilder,
+		 URL			 = window.URL || window.webkitURL,
+		 a  			 = $('<a>'),
+		 bb 			 = undefined,
+		 iconsbbox	 = __getGlobalBBox( 
+				 					utils.keys(__icons).concat(utils.keys(__edges)) ),
+		 svg			 =  $('#div_canvas').html().
+			 					replace(/^(<svg.*? width=")(.*?)(".*?>)/,
+											'$1'+(2*iconsbbox.x+iconsbbox.width)+'$3').
+								replace(/^(<svg.*? height=")(.*?)(".*?>)/,
+											'$1'+(2*iconsbbox.y+iconsbbox.height)+'$3'),
+
+		 exportSVG	= 
+			 function()
+			 {
+				 bb = new Blob([svg], {"type": "text\/xml"});
+			 	 a.attr("href", URL.createObjectURL(bb));
+			 	 a.attr("download", fname || "model.svg");
+			 	 a.get(0).click();
+			 	 URL.revokeObjectURL(a.href);
+			 };
+
+	if( (images = svg.match(/<image.*? href=".*?".*?>/g)) )
+	{
+		var datauris = [];
+		images.forEach(
+			function(image,i)
+			{
+				HttpUtils.imageToDataURI(
+					image.match(/^<image.*? href="(.*?)".*?>$/)[1],
+					function(datauri)	
+					{
+						 datauris[i] = datauri;
+						 if( datauris.length == images.length &&
+							  ! utils.contains(datauris,undefined) )
+						 {
+							 svg = svg.replace(
+								 /(<image.*? )href=".*?"(.*?>)/g,
+								 function(p0,p1,p2)
+								 {
+									 return p1+' xlink:href="'+datauris.shift()+'"'+p2;
+	 							 });
+							 exportSVG();
+						 }
+					});
+			});
+	}
+	else
+		exportSVG();
+}
+
+/**
+ * Retrieves the value that matches the subset, and then passes
+ * it back into the callback function
+ * 
+ * @param callback the function that the value is passed to
+ * @param subset the matching preference entry
+ */
+function _getUserPreferences(callback,subset)
+{
+	console.debug("Get User Preferences");
+	console.debug(subset);
+	HttpUtils.httpReq(
+		'GET',
+		HttpUtils.url('/prefs',__NO_WID),
+		(subset == undefined ? 
+		 	undefined : 
+			'?subset='+encodeURIComponent(utils.jsons(subset))),
+		function(statusCode,resp)
+		{
+			console.debug("Callback Get User Preferences");
+			console.debug(statusCode);
+			console.debug(resp);
+			if( ! utils.isHttpSuccessCode(statusCode) )
+				WindowManagement.openDialog(_ERROR, 'failed to retrieve user preferences :: '+resp);
+			else
+				callback(utils.jsonp(resp));
+		});
+}
+
+/**
+ * Generates an HTTP request
+ * 
+ * @param method GET/DELETE/POST/PUT
+ * @param url the URL to hit
+ * @param params the parameters to pass in
+ * @param onresponse the callback function to perform on response
+ * @param sync whether or not this request is synchronous
+ */
+function _httpReq(method,url,params,onresponse,sync)
+{
+	if( method != 'GET' )
+		BehaviorManager.handleUserEvent(__EVENT_CODED_CANVAS_EDIT);
+	HttpUtils.httpReq(method,url,params,onresponse,sync);
+}
+
+
+/**
+ * Inserts another model into the current canvas
+ */
+function _insertModel(fname)	
+{
+	if( ! __isModel(fname) )
+		WindowManagement.openDialog(
+			_ERROR,
+			'invalid extension... loadable models are "*.model" files');
+	else
+		DataUtils.loadm(fname,true);
+}
+
+/**
+ * Loads a model from the selected file name. This automatically
+ * strips out the .autosave portion of the filename, if it is
+ * present
+ * 
+ * @param fname the name of the file to load
+ */
+function _loadModel(fname)	
+{
+	if( ! __isModel(fname) )
+		WindowManagement.openDialog(
+			_ERROR,
+			'invalid extension... loadable models are "*.model" files');
+	else
+	{
+		if( (matches = fname.match(/(.*)\.autosave\.(.+\.model)/)) )
+			__saveas = matches[1]+matches[2];
+		else
+			__saveas = fname;
+		DataUtils.loadm(fname);
+	}
+}
+
+/**
+ * Loads a new toolbar onto the current canvas
+ * @param fname the name of the file to load
+ */
+function _loadToolbar(fname)	
+{
+	if( __isButtonModel(fname) )
+		DataUtils.loadbm(fname);
+	else if( __isIconMetamodel(fname) )
+		DataUtils.loadmm(fname);
+}
+
+/* save model
+
+	1. if no filename is specified,
+		a) if autosave is specified,
+			- if __saveas is specified, use it
+			- otherwise, use __DEFAULT_SAVEAS
+		b) else, ask the user to choose a file to save to (first time user saves a model)
+	2. otherwise, if an incorrect filename is specified, show error and return
+	3. save model 
+	4. if this isn't an automated backup (i.e., the backup flag is unset), 
+		remember filename in __saveas and adjust window title to reflect fact that
+		all changes are saved */
+function _saveModel(fname,backup,autosave)
+{
+	if( fname == undefined ) {
+		if (!autosave && (__saveas == undefined || __saveas == null)) {
+			var options = {'extensions':['\\.model'],
+						   'multipleChoice':false,
+						   'manualInput':true,
+						   'title':'specify target model',
+						   'startDir':'model'},
+				callback =
+					function(fnames)
+					{
+						_saveModel(fnames[0]);
+					};
+			WindowManagement.openDialog(_FILE_BROWSER,options,callback);
+			return;
+		} else {
+			fname = (__saveas || __DEFAULT_SAVEAS);
+		}
+	} else if( ! __isModel(fname) )	{
+		WindowManagement.openDialog(
+			_ERROR,
+			'invalid extension... models must be saved as "*.model" files');
+		return;
+	}
+
+	DataUtils.savem(fname);
+	if( ! backup )
+	{
+		__saveas = fname;
+		WindowManagement.setWindowTitle();
+	}
+}
+
+
+/* TBI:: 
+	. unselect invisible items
+	. remember visibility settings s.t. newly created items (e.g., by 
+	  collaborator) inherit them */
+function _setInvisibleMetamodels(mms)
+{
+	mms = mms.map( function(mm) {return mm.match(/(.*)\.metamodel/)[1]} );
+
+	function hideOrShow(uri,icon)
+	{
+		if( ! mms.some(
+					function(mm)
+					{
+						if( uri.match(mm+'/') )
+						{
+							icon.hide();
+							return true;
+						}
+					}) )
+			icon.show();		
+	}
+
+	for( var uri in __icons )
+		hideOrShow(uri,__icons[uri]['icon']);
+	for( var uri in __edges )
+		hideOrShow(uri,__edges[uri]['icon']);	
+}
+
+
+/**
+ * Updates the current user preferences and then
+ * executes the passed in callback function
+ * 
+ * @param prefs the new user preferences
+ * @param callback the function to be executed
+ */
+function _setUserPreferences(prefs,callback)
+{
+	HttpUtils.httpReq(
+		'PUT',
+		HttpUtils.url('/prefs',__NO_WID),
+		prefs,
+		function(statusCode,resp)
+		{
+			if( ! utils.isHttpSuccessCode(statusCode) )
+				WindowManagement.openDialog(_ERROR, 'failed to update user preferences :: '+resp);
+			else if( callback )
+				callback();
+		});
+}
+
+/**
+ * Sets the current type of entity to be created
+ * @param fulltype the type to be created
+ */
+function _setTypeToCreate(fulltype)
+{
+	__typeToCreate = fulltype;
+}
+
+/**
+ * Unloads the selected toolbar from the current canvas
+ * @param tb the toolbar to be unloaded
+ */
+function _unloadToolbar(tb)
+{
+	if( __isButtonModel(tb) )
+		DataUtils.unloadbm(tb);
+	else if( __isIconMetamodel(tb) )
+		DataUtils.unloadmm(tb);
+}
+
+/**
+ * Validates the current model
+ */
+function _validate()
+{
+	HttpUtils.httpReq(
+			'GET',
+			HttpUtils.url('/validatem',__NO_USERNAME));	
+}
+
+/******************************* 'BACKDOOR' API *******************************/
+/* highlight the specified node or unhighlight any highlighted nodes... the
+ 	'followCrossFormalismLinks' parameter indicates whether or not (and which) 
+	neighbors along cross-formalism links should also be highlighted... the 
+	'timeout' parameter, if specified, indicates the duration of the highlight */
+function _highlight(args/*asid[,followCrossFormalismLinks,timeout]*/)
+{
+	if( args == undefined )
+		__unhighlight();
+	else
+	{
+		var uri		= __asid2csuri(args['asid']),
+	 		 fcfl		= args['followCrossFormalismLinks'],
+			 timeout	= args['timeout'];
+		__highlight(uri,fcfl,timeout);
+	}
+}
+
+
+/* interface to WindowManagement.spawnClient() 'USER' API function */
+function _loadModelInNewWindow(args/*fname[,callback-url]*/)
+{
+	WindowManagement.spawnClient(args['fname'],args['callback-url']);
+}
+
+
+/* tag the specified node with some text, possibly appending it to an existing 
+	tag...  the 'timeout' parameter, if specified, indicates how long the tag 
+	should be displayed */
+function _tag(args/*asid,text[,style,append,timeout]*/)
+{
+	var uri		= __asid2csuri(args['asid']),
+		 text		= args['text'],
+		 style	= utils.mergeDicts(
+				 		[{'font-size':'16px', 'font-style':'italic', 'fill':'#ffffff'},
+						 args['style']]),
+		 append	= args['append'],
+		 timeout	= args['timeout'];
+	__tag(uri,text,style,append,timeout);
+}
+
+
+/* update an attribute of the specified node, possibly highlighting the node to
+	indicate the change (note that this doesn't unhighlight any currently 
+	highlighted nodes) */
+function _updateAttr(args/*asid,attr,val[,highlight]*/)
+{
+	var uri		= __asid2csuri(args['asid']),
+		 changes = {};
+	changes[args['attr']] = args['val'];
+	DataUtils.update(uri,changes);
+
+	if( args['highlight'] )
+		__flash(uri);
+}
+
+
+
+/******************************** CRUD QUERIES ********************************/
+
+/*************************** HANDLE QUERY RESPONSE ****************************/
+
+/***************************** EDIT CONFLICTS... ******************************/
+var __watchList = {};
+
+
+function __changed(uri,set)
+{
+	if( set )
+		__watchList[uri] = __EDIT_CONFLICT;
+	else
+		return __watchList[uri] == __EDIT_CONFLICT;
+}
+
+
+//TBC place calls to this appropriately (with CS/AS uris...)
+function __unwatch(uri)
+{
+	delete __watchList[uri];
+}
+
+
+//TBC place calls to this appropriately (with CS/AS uris...)
+function __watch(uri)
+{
+	__watchList[uri] = __NO_CONFLICT;
+}
+
+
+function __watching(uri)
+{
+	return uri in __watchList;
+}
+
+
+
+/***************************** MMM-RELATED LOGIC ******************************/
+
+/**************************** CANVAS BEHAVIOUR... *****************************/
+/*------------------------ BEHAVIOUR-RELATED UTILITIES -----------------------*/
+
+/*------------------------- SELECTING ICONS/EDGES... -------------------------*/
+
+/*--------------------------- DRAWING CONNECTIONS ----------------------------*/
+
+/*------------------------------ HIGHLIGHTING --------------------------------*/
+
+/*--------------------------------- TAGGING ----------------------------------*/
+/* tag the specified node and setup delayed tag removal, when applicable */
+function __tag(uri,text,style,append,timeout)
+{
+	__icons[uri]['icon'].tag(text,style,append);
+
+	if( timeout != undefined )
+		window.setTimeout(__icons[uri]['icon'].tag,timeout,'');
+}
+
+
+/*------------------------------- LAYERING... --------------------------------*/
+function __iconToBack(tgt)
+{
+	__icons[__vobj2uri(tgt)]['icon'].toBack();
+}
+
+function __iconToFront(tgt)
+{
+	__icons[__vobj2uri(tgt)]['icon'].toFront();
+}
+
+
+/*---------------------------- SELECTION OVERLAY -----------------------------*/
+
+/*---------------- GEOMETRY CONTROLS AND TRANSFORMATIONS... ------------------*/
+
+/*-------------------------- CONNECTION EDITING... ---------------------------*/
+
+/************************* GRAPH TRAVERSAL UTILITIES **************************/
+/* return the ids of edges connected to the specified node */
+function __getConnectedEdges(uri)
+{
+	var edgeIds = [];
+	for( var edgeId in __edges )
+		if( __edges[edgeId]['start'] == uri ||
+			 __edges[edgeId]['end'] == uri )
+			edgeIds.push(edgeId);
+	return edgeIds;
+}
+
+
+/* given an edge, returns an array containing that edge's start and/or end
+	linktype(s), and its(their) connected edges (which include the given edge) */
+function __getConnectionParticipants(edgeId)
+{
+	var start = __edges[edgeId]['start'],
+		 end 	 = __edges[edgeId]['end'],
+		 cm	 = [];
+	if( __isConnectionType(start) )
+	  cm = cm.concat(start, __getConnectedEdges(start));
+	if( __isConnectionType(end) )
+	  cm = cm.concat(end, __getConnectedEdges(end));
+	return cm;
+}
+
+
+/* return all of the edges and nodes directly or indirectly connected to 'uri' 
+	via cross-formalism links in direction 'dir'... the meaning of 'dir' follows
+	from the convention that cross-formalism link go from higher-level constructs
+  	to lower-level ones
+ 	
+	1. filter __edges keeping only cross-formalism ones
+	2. if dir is '*' or 'DOWN', recursively navigate the edges from step 1. 
+		out of 'uri' marking appropriate nodes and edges
+	3. if dir is '*' or 'UP', recursively navigate the edges from step 1. 
+		into 'uri' marking appropriate nodes and edges */
+function __getCrossFormalismNeighbors(uri,dir)
+{
+	var crossFormalismEdges = [];
+	for( var edgeId in __edges )
+		if( __getMetamodel(__edges[edgeId]['start']) !=
+			 __getMetamodel(__edges[edgeId]['end']) )
+	  		crossFormalismEdges.push(edgeId);
+
+
+	function _(neighbors,lookfor,append)
+	{
+		var tovisit	= [uri];
+		while( tovisit.length > 0 )
+		{
+			var curr = tovisit.shift();
+	
+			neighbors.nodes.push(curr);
+			crossFormalismEdges.forEach(
+				function(edgeId)
+				{
+					if( __edges[edgeId][lookfor] == curr )
+					{
+						var ext = __edges[edgeId][append];
+						if( ! utils.contains(neighbors.nodes,ext) )
+							tovisit.push(ext);
+						if( ! utils.contains(neighbors.edges,edgeId) )
+							neighbors.edges.push(edgeId);
+					}
+				});
+		}
+		return neighbors;
+	}
+
+	var dn = {'nodes':[],'edges':[]}, un = {'nodes':[],'edges':[]};
+	if( dir == '*' || dir == 'DOWN' )
+		_(dn,'start','end');
+	if( dir == '*' || dir == 'UP' )
+		_(un,'end','start');
+
+	return {'nodes':utils.toSet(dn.nodes.concat(un.nodes)),
+			  'edges':utils.toSet(dn.nodes.concat(un.nodes))};
+}
+
+
+
+/***************************** HTML-GUI UTILITIES *****************************/
+
+/****************************** SVG... UTILITIES ******************************/
+
+/******************************* MISC UTILITIES *******************************/
+/* returns the csuri associated to the given asid */
+function __asid2csuri(asid)
+{
+	for( var uri in __icons )
+		if( __icons[uri]['icon'].getAttr('__asuri').
+				match(/.*\/(.*)\.instance/)[1] == asid )
+			return uri;
+}
+
+/* returns the edgeId associated to the given edge DOM element */
+function __edge2edgeId(edge)
+{
+	return edge.parentNode.getAttribute('__edgeId');
+}
+
+
+/* returns both ends contained in the given edgeId */
+function __edgeId2ends(edgeId)
+{
+	if( edgeId in __edges )
+		return [__edges[edgeId]['start'],__edges[edgeId]['end']];
+	else
+		return edgeId.match(/^(.*\.instance)--(.*\.instance)$/).slice(1);
+}
+
+/* returns the linkuri associated to the given edgeId */
+function __edgeId2linkuri(edgeId)
+{
+	return __edges[edgeId]['icon'].getAttr('__linkuri');
+}
+
+
+/* filter a list of filenames given the specified extensions */
+function __filterFilenamesByExtension(fnames,extensions)
+{
+	var ffnames = fnames.filter(
+			function(fname)
+			{
+				return extensions.some( 
+							function(ext) 
+							{
+								return fname.match(ext+'$');
+							});
+			});
+	return ffnames;
+}
+
+
+/* return the icon associated to the given csuri or edgeid */
+function __getIcon(_)
+{
+	return (_ in __icons ?
+				__icons[_]['icon'] :
+				(_ in __edges ?
+					 __edges[_]['icon'] :
+					 undefined));
+}
+
+
+/* return true if the current model contains no unsaved changes */
+function __isSaved()
+{
+	return ! document.title.match(__TITLE+' - \\+');
+}
+
+
+/* return true if the given element is a toolbar or inside a toolbar */
+function __isAToolbar(el)
+{
+	return Boolean(
+				(el.id && el.id.match(/^div_toolbar_/)) 		||
+			 	(el.parentNode && __isAToolbar(el.parentNode)) );
+}
+
+
+/* return true if the given element is the canvas or something drawn on it */
+function __isCanvasElement(el)
+{
+	return el.attr("id") == 'div_canvas' ||
+			 ( (el.parent().length > 0) && __isCanvasElement(el.parent()));
+}
+
+
+/* returns the $segments hash associated to the given edgeId */
+function __linkuri2segments(linkuri)
+{
+	return utils.jsonp(__icons[linkuri]['icon'].getAttr('__segments'));
+}
+
+
+/* truncate './users/<username>' from a list of filenames */
+function __localizeFilenames(fnames)
+{
+	return fnames.map( 
+			function(n) 
+			{
+				return n.match(/^\.\/users\/.*?(\/.*)/)[1];
+			});
+}
+
+
+/* modify a URL as needed to ensure GETting it will produce desired result:
+  	. prepend username to user files
+	. do nothing for WWW files */
+function __relativizeURL(url)
+{
+	if( url.charAt(0) == '.' || url.charAt(0) == '/' )
+		return HttpUtils.url(url,__NO_WID);
+	return url;
+}
+
+
+/* returns the csuri of the icon that contains the specified VisualObject */
+function __vobj2uri(vobj)
+{
+	if( vobj != document.body )
+		return vobj.parentNode.getAttribute('__csuri') ||
+				 __vobj2uri(vobj.parentNode);
+}
+
+function __getRecentDir(name) {
+	return utils.readCookie('recentDir'+name);
+}
+
+function __setRecentDir(name,value) {
+	utils.createCookie('recentDir'+name,value);
+}

+ 112 - 0
client/collaboration.js

@@ -0,0 +1,112 @@
+/*******************************************************************************
+AToMPM - A Tool for Multi-Paradigm Modelling
+
+Copyright (c) 2011 Raphael Mannadiar (raphael.mannadiar@mail.mcgill.ca)
+Modified by Conner Hansen (chansen@crimson.ua.edu)
+
+This file is part of AToMPM.
+
+AToMPM is free software: you can redistribute it and/or modify it under the
+terms of the GNU Lesser General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later 
+version.
+
+AToMPM is distributed in the hope that it will be useful, but WITHOUT ANY 
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with AToMPM.  If not, see <http://www.gnu.org/licenses/>.
+*******************************************************************************/
+
+Collaboration = function(){
+	//css/html based on http://net.tutsplus.com/tutorials/javascript-ajax/how-to-create-a-simple-web-based-chat-application/ 
+	
+	/**
+	 * This enables the collaboration links so that they can be selected
+	 */
+	this.enableCollaborationLinks = function()
+	{
+		$('#a_screenshare').attr('href', 'mailto:?'+
+			'subject='+encodeURIComponent('atompm screenshare invitation')+
+			'&body='+encodeURIComponent('follow this link \n\t'+
+					window.location.href+'?cswid='+__wid+'&host='+__user+											
+					'\nto join '+__user+' in an atompm shared-screen session'));
+		$('#a_screenshare').attr('class', 'enabled_link unselectable');
+
+		$('#a_modelshare').attr('href', 'mailto:?'+
+			'subject='+encodeURIComponent('atompm modelshare invitation')+
+			'&body='+encodeURIComponent('follow this link \n\t'+
+				window.location.href+'?cswid='+__wid+'&aswid='+__aswid+'&host='+__user+
+				'\nto join '+__user+' in an atompm shared-model session'));
+		$('#a_modelshare').attr('class', 'enabled_link unselectable');
+	};
+	
+	/**
+	 * Toggles the chat window between the opened and closed states
+	 * 
+	 * Credits to Maris Jukss
+	 */
+	this.toggleChat = function() {
+		var name = $("#chatName");
+		var chat = $("#chat");
+		var text = $("#showChat");
+		
+		if(chat.css("display") == "block") {
+	        chat.css("display", "none");
+			text.html("Open Chat");
+	  	}
+		else {
+			chat.css("display", "block");
+			text.html("");
+			name.html(window.localStorage.getItem('user'));
+		}
+	};
+	
+	/**
+	 * Sends the entered text to the other user and then
+	 * clears the chat window
+	 */
+	this.sendText = function(){
+//		var text = document.getElementById("usermsg").value;
+		var userMsg = $("#usermsg");
+		var text = userMsg.val();
+		
+		if (text) {
+			HttpUtils.httpReq(
+					'POST',
+					'plugins/chat/chat?wid='+__wid,
+					'<b>'+window.localStorage.getItem('user')+'</b>: '+text,
+					function(statusCode,resp)
+					{
+						//call back here
+					});
+			userMsg.val("");
+		}
+	};
+	
+	/**
+	 * Updates the chat window
+	 */
+	this.updateChat = function( text, name ){
+		var chatbox = $("#chatbox");
+		var currentTime = new Date();
+		var hours = currentTime.getHours();
+		var minutes = currentTime.getMinutes();
+		var oldH = chatbox.scrollHeight-20;
+		
+		if (minutes < 10)
+			minutes = "0" + minutes;
+		
+		// Include the text in the chatbox
+		chatbox.html( chatbox.html() + "<div class='msgln'>("+hours+":"+minutes+") "+text+"<br></div>" );
+		
+		// Get the new height
+		var newH = chatbox.prop("scrollHeight") - 20;
+		if (newH > oldH) {
+			chatbox.attr("scrollTop", 100000 + "px");
+		}
+	};
+	
+	return this;
+}();

+ 393 - 0
client/compile_utils.js

@@ -0,0 +1,393 @@
+/*******************************************************************************
+AToMPM - A Tool for Multi-Paradigm Modelling
+
+Copyright (c) 2011 Raphael Mannadiar (raphael.mannadiar@mail.mcgill.ca)
+Modified by Conner Hansen (chansen@crimson.ua.edu)
+
+This file is part of AToMPM.
+
+AToMPM is free software: you can redistribute it and/or modify it under the
+terms of the GNU Lesser General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later 
+version.
+
+AToMPM is distributed in the hope that it will be useful, but WITHOUT ANY 
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with AToMPM.  If not, see <http://www.gnu.org/licenses/>.
+*******************************************************************************/
+
+///////////////////////////////////////////////////////////////////////////////
+// DEPRECATED FUNCTIONS
+///////////////////////////////////////////////////////////////////////////////
+function _compileToASMM(fname){
+	AtomPMClient.alertDeprecatedFunctionCall("_compileToASMM");
+	CompileUtils.compileToASMM(fname);
+}
+
+function _compileToCSMM(fname){
+	AtomPMClient.alertDeprecatedFunctionCall("_compileToCSMM");
+	CompileUtils.compileToCSMM(fname);
+}
+
+function _compileToPatternMM(fname){
+	AtomPMClient.alertDeprecatedFunctionCall("_compileToPatternMM");
+	CompileUtils.compileToPatternMM(fname);
+}
+///////////////////////////////////////////////////////////////////////////////
+// DEPRECATED FUNCTIONS
+///////////////////////////////////////////////////////////////////////////////
+
+/* draw the icon specified by the given icon model on the provided canvas 
+	0. initialize a group that will contain the 'compiled' icon
+	1. for every ConcreteSyntax primitive (e.g., Rectangle, Text) contained 
+		within the given icon model
+		a. create appropriate Raphael-SVG objects
+		b. style these and position/orient/scale them
+		c. initialize various attributes and remember various values that are used
+			to manipulate and reason about vobjects later on
+		d. apply layout constraint solver and/or link decorator positioner 
+			transformations... to remain backward-compatible with existing 
+			pre-lcs/ldp models, default values are provided if needed
+		e. add objects to group from step 0.
+	2. apply options:
+			size:			force the icon to have a certain size
+			wrap: 		wrap the canvas around the icon
+			id:			this icon is a canvas icon (not a toolbar icon)... save it 
+							and its vobjects in __icons and give it some default 
+							parameters
+			attrs:		add extra attributes to icon
+			behaviour:	indicates that the icon should have event listeners
+
+ 	NOTE:: because text's origin defaults to (0,height/2), we move text elements
+			 by (0,height/2) (via __valignText()) to ensure their top-left corner 
+			 lands on the specified (x,y)... for the computed height to be correct,
+			 we must style the text element beforehand
+
+	NOTE:: because (x,y) are not parameters of Raphael.Paths, we translate paths
+			 according to vobj['position'] manually
+
+	NOTE:: when im.nodes is empty (this may occur when a Link defines no 
+			 decorators), a Raphael.point is used to ensure the icon is not empty 
+ 
+	NOTE:: text is made non-selectable to avoid default browser mouse dragging 
+			 behaviour (select any encoutered text) and subsequent default browser
+			 selected text dragging behaviour 
+ 
+	NOTE:: throught this function, we use *.initial and *.latest values... the 
+			 former are attributes of vobjects as they were drawn by the user...
+			 the latter are added translations/rotations/scalings applied by the
+			 layout constraint solver and link decorator positioner */
+
+CompileUtils = function(){
+	
+	/* draw the edge specified by the given segments
+  	1. draw the edge given the specified segments and style
+	2. encapsulate it within a group, remember the uris of the start and end
+  		icons, and save the edge and segments in __edges
+	3. give the edge appropriate event handlers and return it 
+ 
+	NOTE:: we use insertBefore() to ensure that edges get drawn beneath link
+			 decorators, if any */
+	/**
+	 * Draws the edge specified by the given segments
+	 * 
+	 * 1. Draws the edge given the specified segments and style
+	 * 2. Encapsulates it within a group and remembers the URIs of the start and
+	 *    	end icons, then saves the edge and segments in the AtomPMClient edges
+	 *    	variable
+	 * 3. Assigns the appropriate event handles to the edge and then returns
+	 */
+	this.compileAndDrawEdge = function(segments, linkStyle, start, end, linkuri){
+		var path   = __canvas.path(segments),
+			edge   = __canvas.group(),
+			edgeId = start+'--'+end;
+
+		edge.insertBefore(__getIcon(linkuri).node);
+		path.attr(linkStyle);
+		edge.push(path);
+		edge.setAttr('__edgeId',edgeId);
+		edge.setAttr('__linkuri',linkuri);	
+		edge.setAttr('class','clickable');
+		__edges[edgeId] = 
+			{'icon':edge,
+			 'start':start,
+			 'end':end,
+			 'segments':segments};
+	
+		edge.node.onmousedown = 
+			function(event)
+			{
+				if( event.button == 0 )
+					BehaviorManager.handleUserEvent(__EVENT_LEFT_PRESS_ICON,event);
+				else if( event.button == 2 )
+					BehaviorManager.handleUserEvent(__EVENT_RIGHT_PRESS_ICON,event);
+			};
+		edge.node.onmouseup = 
+			function(event)
+			{
+				if( event.button == 0 )
+					BehaviorManager.handleUserEvent(__EVENT_LEFT_RELEASE_ICON,event);
+				else if( event.button == 2 )
+					BehaviorManager.handleUserEvent(__EVENT_RIGHT_RELEASE_ICON,event);
+				else if( event.button == 1 )
+					BehaviorManager.handleUserEvent(__EVENT_MIDDLE_RELEASE_ICON,event);
+			};
+		/*edge.node.onmouseover = 
+			function(event)
+			{
+				BehaviorManager.handleUserEvent(__EVENT_MOUSE_OVER_ICON,event);
+			}*/
+	
+		return edge;
+	};
+	
+	// TODO: split this function up
+	/**
+	 * Compiles the icon and returns it in order to be placed
+	 * on the canvas
+	 */
+	this.compileAndDrawIconModel = function( im, canvas, options ){
+		var icon = canvas.group(),
+			vobjects = {};
+
+		for( var vid in im.nodes )
+		{
+			var vobj = im.nodes[vid],
+				 x		= __getVobjGeomAttrVal(vobj['position']['value'][0]),
+				 y		= __getVobjGeomAttrVal(vobj['position']['value'][1]);
+	
+			if( vobj['$type'].match(/\/Text$/) )
+			{
+				var tc = vobj['textContent']['value'];
+				vobjects[vid] = canvas.text(x.initial,y.initial,tc);
+				vobjects[vid].attr('text-anchor','start');
+				vobjects[vid].attr(vobj['style']['value']);
+				__valignText(vobjects[vid]);
+				vobjects[vid].node.setAttribute('class','unselectable');			
+			}
+			else if( vobj['$type'].match(/\/Rectangle$/) )
+			{
+				var w	 = parseFloat(vobj['width']['value']),
+					 h	 = parseFloat(vobj['height']['value']),
+					 cr = parseFloat(vobj['cornerRadius']['value']);
+				vobjects[vid] = canvas.rect(x.initial,y.initial,w,h,cr);
+			}
+			else if( vobj['$type'].match(/\/Circle$/) )
+			{
+				var r	= parseFloat(vobj['r']['value']);
+				vobjects[vid] = canvas.circle(x.initial+r,y.initial+r,r);
+			}
+			else if( vobj['$type'].match(/\/Ellipse$/) )
+			{
+				var rx = parseFloat(vobj['rx']['value']),
+					 ry = parseFloat(vobj['ry']['value']);
+				vobjects[vid] = canvas.ellipse(x.initial+rx,y.initial+ry,rx,ry);
+			}
+			else if( vobj['$type'].match(/\/Polygon$/) )
+			{
+				var r 	 = parseFloat(vobj['r']['value']),
+					 sides = parseInt(vobj['sides']['value']);
+				vobjects[vid] = __drawPolygon(canvas,x.initial,y.initial,r,sides);
+			}
+			else if( vobj['$type'].match(/\/Star$/) )
+			{
+				var r 	= parseFloat(vobj['r']['value']),
+					 rays = parseInt(vobj['rays']['value']);
+				vobjects[vid] = __drawStar(canvas,x.initial,y.initial,r,rays);
+			}		
+			else if( vobj['$type'].match(/\/Path$/) )
+			{
+				var segments = vobj['segments']['value'];
+				vobjects[vid] = canvas.path(segments);
+				__translatePath(vobjects[vid],x.initial,y.initial);
+			}		
+			else if( vobj['$type'].match(/\/Image$/) )
+			{
+				var w	  = parseFloat(vobj['width']['value']),
+					 h	  = parseFloat(vobj['height']['value']),
+					 src = __relativizeURL(vobj['src']['value']);
+				vobjects[vid] = canvas.image(src,x.initial,y.initial,w,h);
+			}
+	
+			else
+				/* ignore layout constraints */
+				continue;
+	
+			var r  = __getVobjGeomAttrVal(vobj['orientation']['value']),
+				 sx = __getVobjGeomAttrVal(vobj['scale']['value'][0])
+				 sy = __getVobjGeomAttrVal(vobj['scale']['value'][1]);
+			vobjects[vid].attr(vobj['style']['value']);
+			vobjects[vid].transform(
+					'r'+r.initial+','+x.initial+','+y.initial+
+					's'+sx.initial+','+sy.initial+','+x.initial+','+y.initial);
+	
+			vobjects[vid].node.setAttribute('__vobjuri',vid);
+			vobjects[vid].node.setAttribute('__x',	 
+					utils.buildVobjGeomAttrVal(x.initial, x.latest || 0));
+			vobjects[vid].node.setAttribute('__y',  
+					utils.buildVobjGeomAttrVal(y.initial, y.latest || 0));
+			vobjects[vid].node.setAttribute('__r',  
+					utils.buildVobjGeomAttrVal(r.initial, r.latest || 0));
+			vobjects[vid].node.setAttribute('__sx', 
+					utils.buildVobjGeomAttrVal(sx.initial, sx.latest || 1));
+			vobjects[vid].node.setAttribute('__sy', 
+					utils.buildVobjGeomAttrVal(sy.initial, sy.latest || 1));
+			__setVobjectTransform(vobjects[vid]);												
+	
+			icon.push(vobjects[vid]);
+		}
+	
+		if( utils.keys(im.nodes).length == 0 )
+		{
+			icon.push( __canvas.point(0,0) );
+			icon.setAttr('class','empty_icon');
+		}
+	
+		if( options != undefined )
+		{
+			if( 'size' in options )
+			{
+				var size		= options['size'],
+					 bbox		= icon.getBBox();
+				scaleBy = Math.min(size/bbox.width,size/bbox.height);
+				icon.scale( scaleBy, scaleBy );
+			}
+		
+			if( 'wrap' in options )
+			{
+				var bbox = icon.getBBox(),
+					 size = Math.max(bbox.width, bbox.height);
+				icon.translate( -bbox.x, -bbox.y );
+			
+				bbox = icon.getBBox();
+				if( bbox.width > bbox.height )
+					icon.translate( 0, (size/2 - bbox.height/2) );
+				else
+					icon.translate( (size/2 - bbox.width/2), 0 );
+				icon.translate( 1, 1 );
+				canvas.setSize( size+2, size+2);
+			}
+		
+			if( 'id' in options )
+			{
+				var id = options['id'];
+				__icons[id] = 
+					{'icon':icon, 
+					 'vobjects':vobjects,
+					 'edgesIn':[],
+					 'edgesOut':[]};
+				icon.setAttr('__csuri',id);
+				icon.setAttr('__x',0);
+				icon.setAttr('__y',0);
+				icon.setAttr('__r',0);			
+				icon.setAttr('__sx',1);
+				icon.setAttr('__sy',1);
+			}
+	
+			if( 'attrs' in options )
+			{
+				var attrs = options['attrs'];
+				for( var attr in attrs )
+					icon.setAttr(attr,attrs[attr]);
+			}
+	
+			if( 'behaviours' in options )
+			{
+				icon.setAttr('class','clickable');
+				icon.node.onmousedown = 
+					function(event)
+					{
+						if( event.button == 0 )
+							BehaviorManager.handleUserEvent(__EVENT_LEFT_PRESS_ICON,event);
+						else if( event.button == 2 )
+							BehaviorManager.handleUserEvent(__EVENT_RIGHT_PRESS_ICON,event);
+					};
+				icon.node.onmouseup = 
+					function(event)
+					{
+						if( event.button == 0 )
+						{
+							if( event.shiftKey )
+								BehaviorManager.handleUserEvent(__EVENT_SHIFT_LEFT_RELEASE_ICON,event);
+							else
+								BehaviorManager.handleUserEvent(__EVENT_LEFT_RELEASE_ICON,event);							
+						}
+						else if( event.button == 2 )
+							BehaviorManager.handleUserEvent(__EVENT_RIGHT_RELEASE_ICON,event);
+						else if( event.button == 1 )
+						{
+							if( event.shiftKey )
+								BehaviorManager.handleUserEvent(__EVENT_SHIFT_MIDDLE_RELEASE_ICON,event);
+							else
+								BehaviorManager.handleUserEvent(__EVENT_MIDDLE_RELEASE_ICON,event);
+						}
+					};
+				icon.node.onmousewheel = 
+					function(event)
+					{
+						if( event.shiftKey )
+						{
+							BehaviorManager.handleUserEvent(__EVENT_SHIFT_WHEEL_ICON,event);
+							return false;
+						}
+					};
+				/*icon.node.onmouseover = 
+					function(event)
+					{
+						BehaviorManager.handleUserEvent(__EVENT_MOUSE_OVER_ICON,event);
+					};
+				icon.node.onmouseout = 
+					function(event)
+					{
+						BehaviorManager.handleUserEvent(__EVENT_MOUSE_OUT_ICON,event);
+					};*/
+			}
+		}
+	
+		return icon;
+	};
+	
+	/**
+	 * Compile the current model to an Abstract Syntax Metamodel
+	 */
+	this.compileToASMM = function(fname){
+		if( ! __isAbstractSyntaxMetamodel(fname) )
+			WindowManagement.openDialog(
+				_ERROR,
+				'invalid extension... abstract syntax metamodels are "*.metamodel" files');
+		else
+			HttpUtils.httpReq('PUT', HttpUtils.url(fname,__FORCE_GET));
+	};
+	
+	/**
+	 * Compile the current model to a Concrete Syntax Metamodel
+	 */
+	this.compileToCSMM = function(fname){
+		if( ! __isIconMetamodel(fname) )
+			WindowManagement.openDialog(
+				_ERROR,
+				'invalid extension... icon definition metamodels are "*Icons.metamodel" files');
+		else
+			HttpUtils.httpReq('PUT', HttpUtils.url(fname,__FORCE_GET));
+	};
+	
+	/**
+	 * Compiles the current model to an Icon Pattern Metamodel
+	 */
+	this.compileToPatternMM = function(fname){
+		if( ! __isAbstractSyntaxMetamodel(fname) )
+			WindowManagement.openDialog(
+				_ERROR,
+				'invalid extension... abstract syntax metamodels are "*.metamodel" files');
+		else
+		{
+			var patternmm = fname.substring(0,fname.length-'metamodel'.length)+'pattern.metamodel';
+			HttpUtils.httpReq('PUT', HttpUtils.url(patternmm,__FORCE_GET));
+		}
+	};
+	
+	return this;
+}();

+ 478 - 0
client/connection_utils.js

@@ -0,0 +1,478 @@
+/*******************************************************************************
+AToMPM - A Tool for Multi-Paradigm Modelling
+
+Copyright (c) 2011 Raphael Mannadiar (raphael.mannadiar@mail.mcgill.ca)
+Modified by Conner Hansen (chansen@crimson.ua.edu)
+
+This file is part of AToMPM.
+
+AToMPM is free software: you can redistribute it and/or modify it under the
+terms of the GNU Lesser General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later 
+version.
+
+AToMPM is distributed in the hope that it will be useful, but WITHOUT ANY 
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with AToMPM.  If not, see <http://www.gnu.org/licenses/>.
+*******************************************************************************/
+
+ConnectionUtils = function(){
+	var connectionPathEditingOverlay = {};
+	var currentControlPoint = undefined;
+	var connectionSource = undefined;
+	var connectionPath = undefined;
+	
+	this.getConnectionSource = function(){
+		return connectionSource;
+	};
+	
+	this.getConnectionPath = function(){
+		return connectionPath;
+	};
+	
+	/**
+	 * "Confirm"s the entire current connection path.
+	 * TODO: update this documentation
+	 */
+	this.addConnectionSegment = function(){
+		connectionPath.node.setAttribute('_d',connectionPath.attr('path'));
+	};
+	
+	/**
+	 * Adds a new control point to the current path
+	 * @param x the x-coordinate
+	 * @param y the y-coordinate
+	 * @param overlay 
+	 */
+	this.addControlPoint = function(x,y,overlay) {
+		ConnectionUtils.addOrDeleteControlPoint('+',overlay,x,y);
+	};
+	
+	/* Explanation of the Add/Delete algorithm:
+		
+		addition:
+			. clicked overlay corresponds to Lx2,y2
+	  		Mx0,y0 Lx1,y1 Lx2,y2 Lx3,y3
+				becomes
+	  		Mx0,y0 Lx1,y1 Lx2,y2 Lx2,y2 Lx3,y3
+	
+		deletion:
+			. clicked overlay corresponds to Lx2,y2
+	  		Mx0,y0 Lx1,y1 Lx2,y2 Lx3,y3
+				becomes
+	  		Mx0,y0 Lx1,y1 Lx3,y3
+	
+		after making the described modifications to the corresponding edge's segments
+		property, 
+		1 the edge is redrawn
+		2 a request is sent to the csworker to update the edge's Link's $segments 
+		  property 
+		3 the connection path editing overlay is refreshed (this will cause newly 
+		  added control points to appear, and deleted ones to disappear) 
+	 
+		NOTE:: the first and last control points can never be deleted */
+	/**
+	 * Adds or deletes the control point associated with the given overlay.
+	 */
+	this.addOrDeleteControlPoint = function(op,overlay,x,y){
+		if( ! overlay.hasAttribute('__edgeId') )
+			return;
+		
+		var edgeId = overlay.getAttribute('__edgeId'),
+			 num = parseInt( overlay.getAttribute('__num') ),
+			 offset = parseInt( overlay.getAttribute('__offset') ),
+			 segments = __edges[edgeId]['segments'],
+			 points = segments.match(/([\d\.]*,[\d\.]*)/g);
+	
+		if( op == '-' )
+			/* delete a control point */
+		{
+			if( num+offset == 0 || num+offset == points.length-1 )
+				return;
+			points.splice(num+offset,1);
+		}
+		else
+			/* add a control point */
+			points.splice(num+offset,0,x+','+y);
+	
+		var newpath = 'M'+points.join('L'),
+			 edgeIds = utils.keys(connectionPathEditingOverlay),
+			 linkuri = __edgeId2linkuri(edgeId),
+			 changes = {};
+		
+		changes[edgeId] = newpath;
+		__redrawEdge(edgeId,newpath);
+		DataUtils.updatecs(
+			linkuri, 
+			{'$segments':utils.mergeDicts([__linkuri2segments(linkuri),changes])});
+		ConnectionUtils.hideConnectionPathEditingOverlay();
+		ConnectionUtils.showConnectionPathEditingOverlay(edgeIds);	
+	};
+	
+	/**
+	 * Removes the current control point
+	 * @param overlay - the overlay to be used to identify the control point
+	 */
+	this.deleteControlPoint = function(overlay)	{
+		ConnectionUtils.addOrDeleteControlPoint('-',overlay);
+	};
+	
+	/**
+	 * "Unconfirm" the last segment of the connection path (ie
+	 * remove it). Do nothing if all segments have been "confirmed"
+	 */
+	this.deleteConnectionSegment = function() {
+		var d = String(connectionPath.attr('path')),
+		matches = d.match(/(M.*,.*)L(.*),(.*)/),
+		_d = connectionPath.node.getAttribute('_d'),
+		_matches = _d.match(/(M.*,.*)L.*,.*/);
+
+		if( ! _matches )
+			; // do nothing
+		else if( matches )
+		{
+			var x = matches[2], y = matches[3];
+			connectionPath.node.setAttribute('_d',_matches[1]);
+			ConnectionUtils.updateConnectionSegment(x,y);
+		}
+	};
+	
+	/* 
+	NOTE:: connectionSource is used to remember the uri of the icon at the 
+			 start of the path
+	NOTE:: _d is used ro remember the 'confirmed' portions of the path
+	NOTE:: the call to connectionPath.toBack() causes mouse events that occur
+			 on top of icons to be captured by those (as opposed to by the in-
+			 progress connection path which would capture them otherwise) 
+	 */
+	/**
+	 * Initializes a Raphael Path starting at (x, y) and that reports the
+	 * mouseup event as if it were the canvas
+	 */
+	this.initConnectionPath = function(x,y,target){
+		if( connectionPath != undefined )
+			return;
+
+		connectionSource = __vobj2uri(target);
+		connectionPath = __canvas.path('M'+x+','+y);
+		connectionPath.node.setAttribute('_d','M'+x+','+y);
+		connectionPath.toBack();		
+		connectionPath.node.onmouseup = function(event) {
+			if( event.button == 0 )
+				BehaviorManager.handleUserEvent(__EVENT_LEFT_RELEASE_CANVAS,event);
+			else if( event.button == 1 )
+				BehaviorManager.handleUserEvent(__EVENT_MIDDLE_RELEASE_CANVAS,event);
+			else if( event.button == 2 )
+				BehaviorManager.handleUserEvent(__EVENT_RIGHT_RELEASE_CANVAS,event);
+		};
+	};
+	
+	/**
+	 * Saves the Raphael element associated with the specific overlay as the current
+	 * control point. 
+	 * 
+	 * This provides a more robust defense against moving the mouse so quickly that it
+	 * exits the overlay we're dragging.
+	 */
+	this.initControlPointTranslation = function(overlay){
+		if( overlay.hasAttribute('__edgeId') )
+			/* set currentControlPoint to normal overlay */								
+		{
+			var edgeId = overlay.getAttribute('__edgeId'),
+				 num	  = overlay.getAttribute('__num');
+			currentControlPoint = connectionPathEditingOverlay[edgeId][num];
+		}
+		else
+			/* set currentControlPoint to central overlay */								
+		{
+			var linkuri = overlay.getAttribute('__linkuri');
+			currentControlPoint = connectionPathEditingOverlay[linkuri][0];
+		}
+	};
+	
+	/**
+	 * Hide and delete the connection path
+	 */
+	this.hideConnectionPath = function(){
+		connectionPath.remove();
+		connectionPath = undefined;
+		connectionSource = undefined;
+	};
+	
+	/**
+	 * Hides the current connection path overlay
+	 */
+	this.hideConnectionPathEditingOverlay = function(){
+		for( var _ in connectionPathEditingOverlay )
+			connectionPathEditingOverlay[_].forEach(
+					function(overlay)
+					{
+						overlay.remove();
+					});
+		
+		connectionPathEditingOverlay = {};
+		currentControlPoint = undefined;
+	};
+	
+	/**
+	 * Moves the control point and its overlay to the specified coordinates
+	 */
+	this.previewControlPointTranslation = function(x,y){
+		var _x = parseInt( currentControlPoint.node.getAttribute('_x') ),
+			 _y = parseInt( currentControlPoint.node.getAttribute('_y') );
+		currentControlPoint.translate(x-_x,y-_y);
+		currentControlPoint.node.setAttribute('_x',x);
+		currentControlPoint.node.setAttribute('_y',y);
+		ConnectionUtils.updateConnectionPath(true);
+	};
+	
+	/**
+	 * Show the connection path editing overlay. This shows draggable circles
+	 * above every control point along the selected edges. 
+	 */
+	this.showConnectionPathEditingOverlay = function(_edgeIds){
+		var edgeIds 	 = 
+				(_edgeIds ? _edgeIds : __selection['items']).
+					filter( function(it)	{return it in __edges;} ),
+			 onmousedown = 
+				function(event)
+				{
+					if( event.button == 0 )
+						BehaviorManager.handleUserEvent(__EVENT_LEFT_PRESS_CTRL_POINT,event);
+				},
+			 onmouseup = 
+			 	function(event)
+				{
+					if( event.button == 0 )
+						BehaviorManager.handleUserEvent(__EVENT_LEFT_RELEASE_CTRL_POINT,event);
+					else if( event.button == 1 )
+						BehaviorManager.handleUserEvent(__EVENT_MIDDLE_RELEASE_CTRL_POINT,event);
+					else if( event.button == 2 )
+						BehaviorManager.handleUserEvent(__EVENT_RIGHT_RELEASE_CTRL_POINT,event);
+				};
+	
+		edgeIds.forEach(
+			function(edgeId)
+			{
+				var points = __edges[edgeId]['segments'].match(/([\d\.]*,[\d\.]*)/g),
+					linkuri = __edgeId2linkuri(edgeId),
+					edgeToLink = edgeId.match(linkuri+'$');
+	
+				/* setup normal overlay */								
+				connectionPathEditingOverlay[edgeId] = [];
+				(edgeToLink ? 
+				 	points.slice(0,points.length-1) :
+					points.slice(1)).forEach(
+					function(p)
+					{
+						var xy		= p.split(','),
+							 x 		= xy[0],
+							 y 		= xy[1],
+							 overlay	= __canvas.circle(x,y,5);
+						overlay.node.setAttribute('class','ctrl_point_overlay');
+						overlay.node.setAttribute('__edgeId',edgeId);
+						overlay.node.setAttribute('__offset',(edgeToLink ? 0 : 1));
+						overlay.node.setAttribute('__num',
+								connectionPathEditingOverlay[edgeId].length);
+						overlay.node.setAttribute('_x',x);
+						overlay.node.setAttribute('_y',y);
+						overlay.node.onmouseup = onmouseup;
+						overlay.node.onmousedown = onmousedown;
+						connectionPathEditingOverlay[edgeId].push(overlay);
+					});
+	
+				/* enhance start/end */							
+				if( edgeToLink )
+					utils.head(connectionPathEditingOverlay[edgeId]).node.
+						setAttribute('__start', __edges[edgeId]['start']);
+				else
+					utils.tail(connectionPathEditingOverlay[edgeId]).node.
+						setAttribute('__end', __edges[edgeId]['end']);
+	
+				/* setup central overlay */	
+				var edgeListAttr = (edgeToLink ? '__edgesTo' : '__edgesFrom');
+				if( ! (linkuri in connectionPathEditingOverlay) )
+				{
+					var xy			= (edgeToLink ?
+				__edges[edgeId]['segments'].match(/.*L(.*)/) :
+				__edges[edgeId]['segments'].match(/M([\d\.]*,[\d\.]*)/))[1].split(','),
+						 x 			= xy[0],
+						 y 			= xy[1],
+						 overlay	= __canvas.circle(x,y,8);
+					overlay.node.setAttribute('class','ctrl_point_center_overlay');
+					overlay.node.setAttribute('_x',x);
+					overlay.node.setAttribute('_y',y);
+					overlay.node.setAttribute('_x0',x);
+					overlay.node.setAttribute('_y0',y);
+					overlay.node.setAttribute('__linkuri',linkuri);
+					overlay.node.setAttribute('__edgesTo',utils.jsons([]));
+					overlay.node.setAttribute('__edgesFrom',utils.jsons([]));
+					overlay.node.onmouseup = onmouseup;
+					overlay.node.onmousedown = onmousedown;
+					connectionPathEditingOverlay[linkuri] = [overlay];
+				}
+	
+				var centerOverlay	= connectionPathEditingOverlay[linkuri][0],
+					 edgeList 		= utils.jsonp(centerOverlay.node.getAttribute(edgeListAttr));
+				edgeList.push(edgeId);
+				centerOverlay.node.setAttribute(edgeListAttr,utils.jsons(edgeList));
+			});
+	};
+	
+	/**
+	 * Snaps the current segment to the x or y axis depending on its proximity
+	 * to both axes
+	 */
+	this.snapConnectionSegment = function(x,y){
+		var _d		 = connectionPath.node.getAttribute('_d'),
+			 _matches = _d.match(/.*[L|M](.*),(.*)/),
+			 _x 		 = parseInt( _matches[1] ),
+			 _y 		 = parseInt( _matches[2] ),
+			 d		 	 = String(connectionPath.attr('path')),
+			 matches  = d.match(/.*[L|M](.*),(.*)/),
+			 x 		 = parseInt( matches[1] ),
+			 y 		 = parseInt( matches[2] );
+		
+		if( Math.abs(x-_x) > Math.abs(y-_y) )
+			y = _y;
+		else
+			x = _x;
+		ConnectionUtils.updateConnectionSegment(x,y);
+	};
+	
+	/**
+	 * Snap the current control point, if any
+	 */
+	this.snapControlPoint = function(){
+		if( currentControlPoint == undefined )
+			return;
+	
+		var cpn = currentControlPoint.node,
+			 _x  = cpn.getAttribute('_x'),
+			 _y  = cpn.getAttribute('_y');
+	
+		if( cpn.hasAttribute('__edgeId') )
+			/* snapping normal overlay */				
+		{
+			var edgeId = cpn.getAttribute('__edgeId'),
+				 num	  = parseInt(cpn.getAttribute('__num')),
+				 offset = parseInt(cpn.getAttribute('__offset')),
+				 points = __edges[edgeId]['segments'].match(/([\d\.]*,[\d\.]*)/g),
+				 prevXY = points[num+offset-1];
+			if( num+offset == 0 || num+offset == points.length-1 )
+				/* don't snap end points */
+				return;
+		}
+		else
+			/* snapping central overlay */				
+			var edgeId = utils.jsonp(cpn.getAttribute('__edgesTo'))[0],
+				 points = __edges[edgeId]['segments'].match(/([\d\.]*,[\d\.]*)/g),
+				 prevXY = points[points.length-2];
+	
+		prevXY = prevXY.split(',');
+		if( Math.abs(prevXY[0]-_x) > Math.abs(prevXY[1]-_y) )
+			_y = prevXY[1];
+		else
+			_x = prevXY[0];
+		ConnectionUtils.previewControlPointTranslation(_x,_y);
+		ConnectionUtils.updateConnectionPath();
+	};
+	
+	/* 	NOTE:: when 'local' is false/omitted, edge and center-piece alterations are
+		not merely displayed, but also persisted to the csworker 
+	*/
+	/**
+	 * Alters edges and/or center-pieces to ensure they follow the changes
+	 * effected to their overlays by ConnectionUtils.previewControlPointTranslation()
+	 * and ConnectionUtils.snapConnectionSegment(). This function redraws edges and/or
+	 * moves center pieces
+	 */
+	this.updateConnectionPath = function(local){
+		var cpn = currentControlPoint.node,
+			 _x  = cpn.getAttribute('_x'),
+			 _y  = cpn.getAttribute('_y');
+	
+		function updatedCenterPiecePosition()
+		{
+			var linkuri = cpn.getAttribute('__linkuri'),
+				 x0 		= parseInt( cpn.getAttribute('_x0') ),
+				 y0 		= parseInt( cpn.getAttribute('_y0') ),
+				 icon		= __icons[linkuri]['icon'];
+			cpn.setAttribute('_x0',_x);
+			cpn.setAttribute('_y0',_y);
+			return [(_x-x0) + parseFloat(icon.getAttr('__x')), 
+					  (_y-y0) + parseFloat(icon.getAttr('__y'))];
+		}
+	
+		function updateEdgeExtremity(edgeId,start)
+		{
+			var matches	= __edges[edgeId]['segments'].
+					 				match(/(M[\d\.]*,[\d\.]*)(.*)(L.*)/),
+				 newpath	= (start ?
+						 			'M'+_x+','+_y+matches[2]+matches[3] :
+									matches[1]+matches[2]+'L'+_x+','+_y);
+			__redrawEdge(edgeId,newpath);
+			return newpath;
+		}
+	
+		function updateInnerEdge(edgeId,idx)
+		{
+			var points = __edges[edgeId]['segments'].match(/([\d\.]*,[\d\.]*)/g);
+			points.splice(idx,1,_x+','+_y);
+			var newpath = 'M'+points.join('L');
+			__redrawEdge(edgeId,newpath);
+			return newpath;
+		}
+	
+	
+		if( cpn.hasAttribute('__edgeId') )
+			/* dragging normal overlay */		
+		{
+			var edgeId 	= cpn.getAttribute('__edgeId'),
+				 num		= cpn.getAttribute('__num'),
+				 offset	= cpn.getAttribute('__offset'),
+				 linkuri = __edgeId2linkuri(edgeId),
+				 changes	= {};
+			changes[edgeId] = updateInnerEdge(edgeId,parseInt(num)+parseInt(offset));
+		}
+		else	
+			/* dragging central overlay */
+		{
+			var linkuri = cpn.getAttribute('__linkuri'),
+				 changes = {};
+			utils.jsonp( cpn.getAttribute('__edgesTo') ).forEach(
+					function(edgeId)
+					{
+						changes[edgeId] = updateEdgeExtremity(edgeId,false);
+					});
+			utils.jsonp( cpn.getAttribute('__edgesFrom') ).forEach(
+					function(edgeId)
+					{
+						changes[edgeId] = updateEdgeExtremity(edgeId,true);
+					});
+		}
+		
+		if( ! local )
+			DataUtils.updatecs(
+				linkuri,
+				utils.mergeDicts([
+					{'$segments':utils.mergeDicts(
+												[__linkuri2segments(linkuri),changes])},
+					(cpn.hasAttribute('__linkuri') ?
+						 {'position' :updatedCenterPiecePosition()} : {})]));
+	};
+	
+	/**
+	 * Redraws the current segment such that its end is at (x, y)
+	 */
+	this.updateConnectionSegment = function(x,y){
+		connectionPath.attr(
+			'path',
+			connectionPath.node.getAttribute('_d')+'L'+x+','+y);
+	};
+
+	return this;
+}();

File diff suppressed because it is too large
+ 116 - 0
client/constants.js


+ 381 - 0
client/data_utils.js

@@ -0,0 +1,381 @@
+/*******************************************************************************
+AToMPM - A Tool for Multi-Paradigm Modelling
+
+Copyright (c) 2011 Raphael Mannadiar (raphael.mannadiar@mail.mcgill.ca)
+Modified by Conner Hansen (chansen@crimson.ua.edu)
+
+This file is part of AToMPM.
+
+AToMPM is free software: you can redistribute it and/or modify it under the
+terms of the GNU Lesser General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later 
+version.
+
+AToMPM is distributed in the hope that it will be useful, but WITHOUT ANY 
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with AToMPM.  If not, see <http://www.gnu.org/licenses/>.
+*******************************************************************************/
+
+/* delete specified file/folder */
+
+DataUtils = function(){
+	/* 	
+		NOTE:: information about the pathString is bundled with the request... if the
+	  			 request succeeds, it is returned along with the changelog and used to 
+				 draw the requested connection [and center-piece]
+		NOTE:: DataUtils.connect() may be called from the behaviour statechart with a single
+	  			 parameter (i.e., event.target)... in such cases, we construct an 
+				 appropriate 2-parameter call to DataUtils.connect() and recurse 
+	 */
+	/**
+	 * Requests a connection of specified instances with center-piece (if and when any)
+	 * at center of ConnectionUtils.getConnectionPath(). In practice, before this request
+	 * is sent, the user is prompted to either choose a connection type (this choice is
+	 * made for him when exactly one type is available) or to be told that no legal
+	 * connection exists
+	 */
+	this.connect = function(uri1,uri2){
+		if( uri2 == undefined )
+			return DataUtils.connect(ConnectionUtils.getConnectionSource(), __vobj2uri(uri1));
+	
+		var segments 	= __path2segments(ConnectionUtils.getConnectionPath()),
+			 pathCenter	= segments.splice(1,1)[0],
+	 		 callback 	= 
+				 function(connectionType)
+				 {
+			 		 HttpUtils.httpReq(
+			 				 'POST',
+			 				 HttpUtils.url(connectionType,__NO_USERNAME),
+			 				 {'src':uri1,
+							  'dest':uri2,
+			 				  'pos':[pathCenter.x,pathCenter.y],
+							  'segments':segments});
+				 };
+	
+		ConnectionUtils.hideConnectionPath();
+		WindowManagement.openDialog(
+				_LEGAL_CONNECTIONS,
+				{'uri1':uri1,'uri2':uri2,'ctype':__VISUAL_LINK},
+				callback);
+	};
+	
+	/**
+	 * Request creation of an instance of __typeToCreate at the specified
+	 * x and y coordinates
+	 */
+	this.create = function(x,y){
+		if( __typeToCreate != undefined )
+			HttpUtils.httpReq(
+					'POST',
+					HttpUtils.url(__typeToCreate+'.type',__NO_USERNAME),
+					{'pos':[x,y]});
+		else
+			WindowManagement.openDialog(_ERROR,'you must select a type to create');
+	};
+	
+	/**
+	 * Deletes the current selection entities
+	 */
+	this.del = function(){
+		var requests = [];
+		__selection['items'].forEach(
+			function(it)
+			{
+				if( it in __icons )
+					requests.push(
+						{'method':'DELETE', 
+						 'uri':HttpUtils.url(it,__NO_USERNAME+__NO_WID)});
+			});
+
+		HttpUtils.httpReq(
+			'POST',
+			HttpUtils.url('/batchEdit',__NO_USERNAME),
+			requests);
+	};
+	
+	/**
+	 * Deletes the file from the cloud storage by URI
+	 * 
+	 * @param fileuri - the file to upload
+	 * @param callback - the callback function after the operation
+	 * has completed
+	 */
+	this.deleteFromCloud = function(fileuri,callback){
+		HttpUtils.httpReq(
+			'DELETE',
+			fileuri,
+			undefined,
+			callback);
+	};
+	
+	/*
+		1. retrieve and validate uri associated to 'into'
+		2. validate and/or setup 'items' (see NOTE)
+		3. prompt for connection type
+		*. return connection type or undefined via 'callback'
+	
+		NOTE:: when 'items' is undefined, __selection.items is used as the default 
+	*/
+	/**
+	 * Prompt user to select a containment link type (or chooses it for him if there
+	 * is only one) and pass the choice to the callback function. 
+	 * 
+	 * @param into - where to put the connection
+	 * @param items - items to insert
+	 * @param callback - function to call when finished with the request
+	 */
+	this.getInsertConnectionType = function(into,items,callback){
+		var intouri = into.getAttribute('__csuri'); 
+		if( intouri == undefined  || 
+			 !(intouri in __icons) || 
+			 __isConnectionType(intouri) )
+			return callback();
+		if( (items == undefined || items.length == 0) &&
+			 (__selection == undefined	|| __selection['items'].length == 0) )
+			return callback();
+		items = (items || __selection['items']);
+	
+		WindowManagement.openDialog(
+				_LEGAL_CONNECTIONS,
+				{'uri1':intouri,
+				 'uri2':items[0],
+				 'ctype':__CONTAINMENT_LINK,
+				 'forceCallback':true},
+				function(ctype)
+				{
+					if( utils.isObject(ctype) && '$err' in ctype )
+						callback();
+					else
+						callback(ctype);
+				});
+	};
+	
+	/*
+	1. foreach non-connection type icon in 'items' that is not already inside 
+		'into' or any other icon in 'items'
+		a. synthesize a path from 'into''s top-left to the icon's center
+		b. save that path's center and 2 halves
+		c. remove the path
+		d. remember connection request
+	2. send all requests from step 1d as a single batchEdit or return them
+
+	NOTE:: similarly to what is done in DataUtils.connect(), each connection request is 
+			 bundled with 'pos' and 'segments'... here however, since the user drew
+			 no path, these are both synthesic... this serves 2 purposes: first, it
+			 shields the csworker from receiving different queries for containment
+			 and visual connections, but most importantly, it ensures that 
+			 containment links have an existing visual representation if one is 
+			 needed
+
+ 	NOTE:: the 'context' parameter contains a list of pending changes computed by 
+			 GeometryUtils.transformSelection() but not yet persisted onto the canvas... this 
+			 seemingly odd passing around of pending information is necessary to 
+			 enable atomicity of icon transformations and insertions 
+	 */
+	/**
+	 * Inserts 'items' into 'into' (connects them via a containment link of type
+	 * 'connectionType'). This sends a bundle of batched connection requets to csworker.
+	 */
+	this.insert = function(into,items,connectionType,context,dryRun) {	
+		var intobbox = __getBBox(into,context),
+			 requests = [];
+	
+		items.forEach(
+			function(it)
+			{
+				if( ! (it in __icons) 					 ||
+					__isConnectionType(it)				 ||
+					__isDirectlyContainedIn(it,into)  ||
+					items.some( 
+						function(_it)
+						{
+							return __isDirectlyContainedIn(it,_it);
+						}) )
+					return;
+	
+				var itbbox	 	 = __getBBox(it,context),
+					 itcenter 	 = [itbbox.x+itbbox.width/2, itbbox.y+itbbox.height/2],
+					  path	 	 = __canvas.path(
+						  					'M'+intobbox.x+','+intobbox.y+'L'+itcenter),
+					  segments 	 = __path2segments(path),
+					  pathCenter = segments.splice(1,1)[0];
+				path.remove();
+				
+				requests.push(
+						{'method':'POST',
+						 'uri':HttpUtils.url(connectionType,__NO_USERNAME+__NO_WID),
+						 'reqData':
+							 {'src':into,
+							  'dest':it,
+							  'pos':[pathCenter.x,pathCenter.y],
+							  'segments':segments}});
+			});
+		
+		if( dryRun )
+			return requests;
+		else if( requests.length > 0 )
+			HttpUtils.httpReq(
+					'POST',
+					HttpUtils.url('/batchEdit',__NO_USERNAME),
+					requests);
+	};
+	
+	/**
+	 * Loads the Button Model
+	 * 
+	 * @param bm - the button model to load
+	 */
+	this.loadbm = function(bm){
+		HttpUtils.httpReq(
+				'GET',
+				HttpUtils.url(bm,true),
+				undefined,
+				function(statusCode,resp)
+				{
+					GUIUtils.setupAndShowToolbar(
+						bm,
+						eval('('+resp+')'),
+						__BUTTON_TOOLBAR);					
+				});	
+	};
+	
+	/* 
+		1. does the deed
+		2. if a 'missing metamodel' error is returned
+			a. request that the missing metamodel be loaded
+				i.  if an error is returned, show it
+				ii. otherwise, return to step 1. */
+	/**
+	 * Request that the specified model be loaded
+	 */
+	this.loadm = function(fname,insert) {
+		HttpUtils.httpReq(
+				'PUT',
+				HttpUtils.url('/current.model', __NO_USERNAME),
+				{'m':HttpUtils.url(fname,__NO_WID),
+				 'insert':insert},
+				function(statusCode,resp)
+				{
+					if( ! utils.isHttpSuccessCode(statusCode) )
+					{
+						if( (matches = resp.match(/metamodel not loaded :: (.*)/)) )
+						{
+							var missing = matches[1]+'.metamodel';
+							console.warn('auto-loading missing metamodel :: '+missing);
+							DataUtils.loadmm(
+									missing,
+									function(_statusCode,_resp)
+									{
+										if( ! utils.isHttpSuccessCode(_statusCode) )
+											WindowManagement.openDialog(_ERROR,_resp);
+										else
+											DataUtils.loadm(fname,insert);
+									});
+						}
+						else
+							WindowManagement.openDialog(_ERROR,resp);
+					}
+					else
+						WindowManagement.setWindowTitle();
+				});				
+	};
+	
+	/*
+	CASE 1: asmm is already loaded but with a different csmm
+		> request params only contain csmm
+		> triggers back-end CS-switch
+	
+	CASE 2: asmm is not loaded or is loaded with the specified csmm
+		> request params contain asmm and csmm
+		> triggers back-end metamodel (re-)load */
+	/**
+	 * Loads (or reloads) the specified metamodel
+	 */
+	this.loadmm = function(imm,callback){
+		var asmm = __iconMetamodelToMetamodel(imm),
+		sameASMM = function(mm) {
+					 return __isIconMetamodel(mm) && 
+						 	  __iconMetamodelToMetamodel(mm) == asmm;
+				 },
+			 params;
+		if( !(imm in __loadedToolbars) && 
+			 	utils.keys(__loadedToolbars).some(sameASMM) )
+			params = {'csmm':HttpUtils.url(imm,__NO_WID)};
+		else
+			params = {'csmm':HttpUtils.url(imm,__NO_WID), 'asmm':HttpUtils.url(asmm,__NO_WID)};
+		
+		HttpUtils.httpReq(
+			'PUT',
+			HttpUtils.url('/current.metamodels', __NO_USERNAME),
+			params,
+			callback);
+	};
+	
+	/**
+	 * Saves the current model to the specified file
+	 */
+	this.savem = function(fname){
+		HttpUtils.httpReq(
+				'PUT',
+				HttpUtils.url(fname,__FORCE_GET));	
+	};
+	
+	/**
+	 * Unloads the selected button model
+	 */
+	this.unloadbm = function(bm){
+		GUIUtils.removeToolbar(bm);
+	};
+	
+	/**
+	 * Unloads the selected metamodel
+	 */
+	this.unloadmm = function(mm){
+		HttpUtils.httpReq(
+			'DELETE',
+			HttpUtils.url(mm,__NO_USERNAME));
+	};
+	
+	/**
+	 * Updates the current model with the listed
+	 * changes
+	 */
+	this.update = function(uri,changes){
+		if( utils.keys(changes).length > 0 )
+			HttpUtils.httpReq(
+					'PUT',
+					HttpUtils.url(uri,__NO_USERNAME),
+					{'changes':changes});
+	};
+	
+	/**
+	 * Updates using a worker thread?
+	 */
+	this.updatecs = function(uri,changes){
+		HttpUtils.httpReq(
+				'PUT',
+				HttpUtils.url(uri+'.cs',__NO_USERNAME),
+				{'changes':changes});
+	};
+	
+	/**
+	 * Uploads the data to a file specified by the tofolder entry
+	 * 
+	 * @param tofolder - the folder to upload the data to
+	 * @param data - the data to upload
+	 * @param callback - the callback function after the operation
+	 * has completed
+	 */
+	this.uploadToCloud = function(tofolder,data,callback){
+		HttpUtils.httpReq(
+				'PUT',
+				HttpUtils.url(tofolder+'.file',__NO_WID),
+				data,
+				callback);	
+	};
+	
+	return this;
+}();

+ 337 - 0
client/edit_utils.js

@@ -0,0 +1,337 @@
+/*******************************************************************************
+AToMPM - A Tool for Multi-Paradigm Modelling
+
+Copyright (c) 2011 Raphael Mannadiar (raphael.mannadiar@mail.mcgill.ca)
+Modified by Conner Hansen (chansen@crimson.ua.edu)
+
+This file is part of AToMPM.
+
+AToMPM is free software: you can redistribute it and/or modify it under the
+terms of the GNU Lesser General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later 
+version.
+
+AToMPM is distributed in the hope that it will be useful, but WITHOUT ANY 
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with AToMPM.  If not, see <http://www.gnu.org/licenses/>.
+*******************************************************************************/
+
+// DEPRECATED FUNCTIONS
+///////////////////////////////////////////////////////////////////////////////
+function _copy(){
+	AtomPMClient.alertDeprecatedFunctionCall("_copy");
+	EditUtils.copy();
+}
+
+function _paste(){
+	AtomPMClient.alertDeprecatedFunctionCall("_paste");
+	EditUtils.paste();
+}
+
+function _redo(){
+	AtomPMClient.alertDeprecatedFunctionCall("_redo");
+	EditUtils.redo();
+}
+
+function _undo(){
+	AtomPMClient.alertDeprecatedFunctionCall("_undo");
+	EditUtils.undo();
+}
+///////////////////////////////////////////////////////////////////////////////
+// DEPRECATED FUNCTIONS
+///////////////////////////////////////////////////////////////////////////////
+
+/* copies the full AS and CS of selected icons into a cross-tab/window storage
+  	area 
+ 
+ 	NOTE: the use of window.localStorage enables copy-pasting across atompm
+ 			instances... this could also have been accomplished via cookies albeit
+			less efficiently since cookies are logged around during every backend 
+			request/response */
+
+EditUtils = function(){
+	
+	/**
+	 * This copies the currently selected element(s) to the local
+	 * clipboard object
+	 */
+	this.copy = function() {
+		if( __selection == undefined || __selection['items'].length == 0 )
+			return;
+		else if( ! GeometryUtils.areTransformationsAllowed() )
+		{
+			console.warn('copy-pasting is only enabled if all of the ends of selected'
+				  			 +' edges are also selected, and if the geometry controls are'
+							 +' inactive');
+			return;
+		}
+		
+		var icons  = 
+				__selection['items'].filter( function(it) {return it in __icons;} ),
+			 todo   = icons.length,
+			 cpdata = {};
+		icons.forEach(
+			function(uri)
+			{
+				if( cpdata == undefined )
+					return;
+
+				HttpUtils.httpReq(
+					'GET',
+					HttpUtils.url(uri,__NO_USERNAME),
+					'&full=1',
+					function(statusCode,resp)
+					{
+						if( ! utils.isHttpSuccessCode(statusCode) )
+						{
+							WindowManagement.openDialog(_ERROR,'failed to retrieve copy data :: '+resp);
+							cpdata = undefined;
+							return;
+						}
+						else
+						{ 
+							var data 	 = utils.jsonp(resp)['data'],
+								 cs 		=  utils.jsonp(data['cs']),
+								 as 		=  utils.jsonp(data['as']),
+								 csattrs = {},
+								 asattrs = {};		
+
+							 for( var attr in cs )
+								 csattrs[attr] = cs[attr]['value'];
+							 for( var attr in as )
+								 asattrs[attr] = as[attr]['value'];
+							 delete csattrs['$type'];
+							 delete csattrs['$asuri'];
+							 delete asattrs['$type'];
+	 						 cpdata[uri] = {'cs':csattrs,'as':asattrs};
+						}
+		
+						if( --todo == 0 )
+							window.localStorage.setItem('clipboard',utils.jsons(cpdata));
+					});
+			});
+	};
+	
+	/* paste clipboard contents by building [and sending] batchEdit query filled 
+	with node creation/connection requests
+
+	0. produce metamodel loading requests for missing clipboard entry metamodels,
+			if any
+	1. sort contents such that non-Links get handled first... this is merely an
+			optimization that optimizes step 2.
+	2. while clipboard items remain, 
+			.) shift first item
+			a) if item is a non-connection type, produce a creation request for it
+		  		and remember its index in 'it2creq'
+			a) otherwise, 
+				i.  if both connection ends have entries in 'it2creq', produce a
+					 creation request for item and remember its index in 'it2creq'
+				i. otherwise, push item at the end of the list of remaining items 
+		clipboard data for the given item is used to populate the request's 
+		'attrs', ['src', 'dest'] and 'hitchhiker.clone' parameters
+	3. send off the batchEdit request
+	4. on completion, select newly created nodes and edges
+
+	NOTE 1:: to connect to-be created nodes and to select them, we need their ids
+				... unfortunately, the csworker can not return the csid of a newly 
+					requested node (this csid will only come to be when the csworker 
+					handles the asworker's  MKNODE)... for this reason, paste batchEdits
+					are directed to our asworker (who responds to creation requests with
+					the asids of newly created nodes)
+
+	NOTE 2:: the 'it2creq' data structure is used to remember which request is
+				associated to which to-be node... this is needed for connection 
+				requests with parameterized source and destination ids
+
+	NOTE 3:: a limitation of the current implementation (which could fairly 
+				easily be overcome) is that Link '$segments' are expected to always
+				contain exactly 2 entries 
+
+	NOTE 4:: selection of pasted elements requires that all icons be created...
+				the batchEdit response only indicates that all asworker entities 
+				have been created: their associated icons might not all exist yet...
+			  	to this end, a timed-loop checks for all the icons to be created 
+				before triggering selection... this works but it isn't perfect for 
+				3 reasons
+					1. there might be a visible lag before selecting
+					2. a malicious user could cause the timed-loop to run forever
+						by deleting pasted entities before the loop detects their
+						creation 
+						3. the loop almost always runs in O(||__icons||) */
+	/**
+	 * Pastes the current contents of the clipboard.
+	 */
+	this.paste = function() {
+		var clipboard = utils.jsonp(window.localStorage.getItem('clipboard'));
+		if( clipboard == null )
+		{
+			console.warn('clipboard is empty');
+			return;
+		}
+		
+		var toload	 = {},
+			 requests = [],
+			 it2creq	 = {},
+			 tmpasuri = 
+				 function(csuri,reqi)
+				 {
+					 var matches = csuri.match(
+			 /^(.*)\..*Icons(\.pattern){0,1}(\/.*)Link\/[a-zA-Z0-9]*\.instance$/) ||
+					 		  			csuri.match(
+				 /^(.*)\..*Icons(\.pattern){0,1}(\/.*)Icon\/[a-zA-Z0-9]*\.instance$/);
+					 return matches[1]+
+						 	  (matches[2] || '')+
+							  matches[3]+'/$'+reqi+'$.instance';
+				 },
+			 cburis = utils.keys(clipboard);
+		
+		cburis.forEach(
+				function(uri)
+				{
+					var imm  = __getMetamodel(uri)+'.metamodel',
+						 asmm = __iconMetamodelToMetamodel(imm);
+					if( (!(asmm in __loadedToolbars) || !(imm in __loadedToolbars)) &&
+					 	 !(imm in toload) )
+					{
+						requests.push(
+							{'method':'PUT',
+							 'uri':HttpUtils.url('/current.metamodels', __NO_USERNAME+__NO_WID),
+							 'reqData':
+							 	{'mm':HttpUtils.url(asmm,__NO_WID),
+								 'hitchhiker':{'path':HttpUtils.url(imm,__NO_WID)}}});	
+						toload[imm] = 1;
+					}
+				});
+		
+		cburis.sort( function(a,b) {return (__isConnectionType(a) ? 1 : -1);} );
+		while( cburis.length > 0 )
+		{
+			var uri		= cburis.shift(),
+				 matches = uri.match(
+			 /^(.*)\..*Icons(\.pattern){0,1}(\/.*)Link\/[a-zA-Z0-9]*\.instance$/) ||
+					 		  uri.match(
+				 /^(.*)\..*Icons(\.pattern){0,1}(\/.*)Icon\/[a-zA-Z0-9]*\.instance$/),
+				 type		= matches[1]+(matches[2] || '')+matches[3]+'.type';
+			
+			if( ! __isConnectionType(uri) )
+			{
+				it2creq[uri] = requests.length;
+				requests.push(
+					{'method':'POST', 
+					 'uri':HttpUtils.url(type,__NO_USERNAME+__NO_WID),
+					 'reqData':
+					 	{'attrs':clipboard[uri]['as'],
+						 'hitchhiker':{'clone':clipboard[uri]['cs']}}});
+			}
+			else
+			{
+				var segments = clipboard[uri]['cs']['$segments'],
+					 src	 	 = undefined,
+					 dest		 = undefined,
+					 asSrc,
+					 asDest;
+			
+				for( var edgeId in segments )
+				{
+					var ends = __edgeId2ends(edgeId);
+					if( ends[0] == uri )
+						dest = ends[1];
+					else
+						src = ends[0];
+				}
+				src    = (src  || uri);
+				dest   = (dest || uri);
+		
+				if( it2creq[src] == undefined || it2creq[dest] == undefined )
+					cburis.push(uri);
+				else
+				{
+					delete clipboard[uri]['cs']['$segments'];
+					it2creq[uri] = requests.length;
+					asSrc  = tmpasuri(src,it2creq[src]);
+					asDest = tmpasuri(dest,it2creq[dest]);
+					requests.push(
+						{'method':'POST', 
+						 'uri':HttpUtils.url(type,__NO_USERNAME+__NO_WID),
+						 'reqData':
+						 	{'attrs':clipboard[uri]['as'],
+							 'src':	asSrc,
+							 'dest':	asDest,
+							 'hitchhiker':
+							 	{'clone':clipboard[uri]['cs'],
+								 'asSrc':asSrc,
+								 'asDest':asDest,
+								 'segments':
+								 	[segments[src+'--'+uri],
+									 segments[uri+'--'+dest]]}}});
+				}
+			}
+		}
+		
+		HttpUtils.httpReq(
+			'POST',
+			HttpUtils.url('/batchEdit',__NO_USERNAME+__NO_WID)+'?wid='+__aswid,
+			requests,
+			function(statusCode,resp)
+			{
+				if( ! utils.isHttpSuccessCode(statusCode) )
+				{
+					WindowManagement.openDialog(_ERROR, 'paste failed on :: '+resp);
+					return;
+				}
+		
+				var results = utils.jsonp(resp)['data']['results'],
+					 asids	= results.filter(function(r) {return 'data' in r;}).
+					 							map(function(r) {return ''+r['data'];}),
+					 csuris	= [],
+		 			 selectResults =
+					 	function()
+						{
+								for( var uri in __icons )
+		  					{
+		  						var id = __icons[uri]['icon'].getAttr('__asuri').
+											match(/.*\/(.*)\.instance/)[1];
+		
+		  						if( (idx=asids.indexOf(id)) > -1 )
+		  						{
+		  							if( __isConnectionType(uri) )
+		  								csuris = csuris.concat(
+												__icons[uri]['edgesIn'],__icons[uri]['edgesOut']);
+		  							csuris.push(uri);
+		  							asids.splice(idx,1);
+		  						}
+		  						
+		  						if( asids.length == 0 )
+									return BehaviorManager.handleUserEvent(__EVENT_CODED_SELECTION,csuris);
+		  					}
+							window.setTimeout(selectResults,100);
+						};
+				selectResults();
+			});
+	};
+	
+	/**
+	 * Redo the last undone action
+	 */
+	this.redo = function(){		  
+		BehaviorManager.handleUserEvent(__EVENT_CODED_CANVAS_EDIT);
+		HttpUtils.httpReq(
+				'POST',
+				HttpUtils.url('/redo',__NO_USERNAME));	
+	};
+	
+	/**
+	 * Undo the last performed action
+	 */
+	this.undo = function() {				  
+		BehaviorManager.handleUserEvent(__EVENT_CODED_CANVAS_EDIT);
+		HttpUtils.httpReq(
+				'POST',
+				HttpUtils.url('/undo',__NO_USERNAME));	
+	};
+	
+	return this;
+}();

+ 640 - 0
client/geometry_utils.js

@@ -0,0 +1,640 @@
+/*******************************************************************************
+AToMPM - A Tool for Multi-Paradigm Modelling
+
+Copyright (c) 2011 Raphael Mannadiar (raphael.mannadiar@mail.mcgill.ca)
+Modified by Conner Hansen (chansen@crimson.ua.edu)
+
+This file is part of AToMPM.
+
+AToMPM is free software: you can redistribute it and/or modify it under the
+terms of the GNU Lesser General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later 
+version.
+
+AToMPM is distributed in the hope that it will be useful, but WITHOUT ANY 
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with AToMPM.  If not, see <http://www.gnu.org/licenses/>.
+*******************************************************************************/
+
+GeometryUtils = function(){
+	
+	var geometryControlsOverlay = undefined;
+	var transformationPreviewOverlay = undefined;
+	
+	/**
+	 * Determines whether or not geometric transformations are allowed. This only
+	 * applies if:
+	 * 1. Geometry controls are hidden
+	 * 2. If an edge is selected, its start and end icons are also selected
+	 */
+	this.areTransformationsAllowed = function(){
+		var seen = {};
+		return (geometryControlsOverlay == undefined ||
+					geometryControlsOverlay.css("display") == 'none') &&
+				 __selection['items'].every(
+					function(it)
+					{
+						if( it in __edges )
+						{
+							var start = __edges[it]['start'],
+								 end	 = __edges[it]['end'];
+							if( ! (start in seen) && 
+								 ! utils.contains(__selection['items'],start) )
+								return false;
+							if( ! (end in seen) && 
+								 ! utils.contains(__selection['items'],end) )
+								return false;
+							seen[start] = seen[end] = 1;
+						}
+						return true;
+					});
+	};
+	
+	/**
+	 * Hides the geometry controls overlay
+	 */
+	this.hideGeometryControlsOverlay = function() {
+		if( geometryControlsOverlay != undefined )
+			geometryControlsOverlay.css("display", "none");
+		__setCanvasScrolling(true);	
+	};
+	
+	/**
+	 * Hides the transformation preview overlay
+	 */
+	this.hideTransformationPreviewOverlay = function() {
+		if( transformationPreviewOverlay != undefined )
+		{
+			transformationPreviewOverlay.remove();
+			transformationPreviewOverlay = undefined;
+		}
+	};
+	
+	/*
+		NOTE:: _x and _y are used to remember the last 'confirmed' position which we
+				 to compute the relative parameters of calls to translate(..)
+		NOTE:: the call to toBack() causes whatever is beneath the transformation 
+				 preview overlay to become above it, thus becoming detectable by 
+				 document.elementFromPoint()... this is used to distinguish between 
+				 dropping selections on the canvas and on icons, with the latter 
+				 possibly causing insertion */
+	/**
+	 * Initializes a Raphael rectangle matching the selection bounding box.
+	 */
+	this.initSelectionTransformationPreviewOverlay = function(x,y)
+	{
+		if( transformationPreviewOverlay != undefined )
+			return;
+	
+		var bbox = __selection['bbox'];
+		transformationPreviewOverlay = __bbox2rect(bbox,'transformation_preview');
+		transformationPreviewOverlay.node.setAttribute('_x0',x);
+		transformationPreviewOverlay.node.setAttribute('_y0',y);
+		transformationPreviewOverlay.node.setAttribute('_x',x);
+		transformationPreviewOverlay.node.setAttribute('_y',y);
+		transformationPreviewOverlay.node.onmouseup = 
+			function(event)
+			{
+				transformationPreviewOverlay.toBack();
+				var beneathTPO = document.elementFromPoint(event.x,event.y),
+					 _event;
+	
+				if( transformationPreviewOverlay.node != beneathTPO &&
+					 beneathTPO != __selection['rect'].node )
+				{
+					_event = document.createEvent('MouseEvents');
+					_event.initMouseEvent(
+							event.type, event.canBubble, event.cancelable, event.view,
+							event.detail, event.screenX, event.screenY, event.clientX,
+						  	event.clientY, event.ctrlKey, event.altKey, event.shiftKey,
+							event.metaKey, event.button, event.relatedTarget );
+					beneathTPO.parentNode.dispatchEvent(_event);
+				}
+				else if( event.button == 0 )
+					BehaviorManager.handleUserEvent(__EVENT_LEFT_RELEASE_CANVAS,event);
+			};
+	};
+	
+	/**
+	 * Applies the effects of the specified transformation to the preview overlay
+	 */
+	this.previewSelectionTransformation = function(op,dir) {
+		var bbox  = __selection['bbox'],
+			 scale = (dir > 0 ? 1.05 : 0.95),
+			 angle = (dir > 0 ? 3 : -3);
+		if( op == 'resize' )
+			transformationPreviewOverlay.scale(scale,scale,bbox.x,bbox.y);
+		else if( op == 'resizeH' )
+			transformationPreviewOverlay.scale(1,scale,bbox.x,bbox.y);
+		else if( op == 'resizeW' )
+			transformationPreviewOverlay.scale(scale,1,bbox.x,bbox.y);
+		else if( op == 'rotate' )
+			transformationPreviewOverlay.rotate(angle,bbox.x,bbox.y);
+	};
+	
+	/**
+	 * Moves the transformation preview overlay to the specified coordinates
+	 */
+	this.previewSelectionTranslation = function(x,y) {
+		var _x = parseInt(transformationPreviewOverlay.node.getAttribute('_x')),
+			 _y = parseInt(transformationPreviewOverlay.node.getAttribute('_y'));
+		transformationPreviewOverlay.translate(x-_x,y-_y);
+		transformationPreviewOverlay.node.setAttribute('_x',x);
+		transformationPreviewOverlay.node.setAttribute('_y',y);
+	};
+	
+	/* 
+			0. exit on empty icon list
+			1. foreach non-link icon, 
+				a. loop back to step 1 if it has no container
+				b. determine if it's bbox is fully inside, fully outside or intersects
+					with its container's
+					i.   when fully inside, loop to step 1
+					ii.  when fully outside AND was actually contained (as opposed to
+						  to-be-inserted) AND dragouts are enabled, produce deletion 
+						  request for containment link
+					iii. otherwise, store needed changes to container position and size
+				  		  to fit icon... we do this (as opposed to producing a request)
+						  to lump together all changes to a given container (each which 
+						  may originiate from different icons)
+			2. exit on empty request and container changes lists
+			3. convert container changes to CS update requests and append to existing
+				deletion requests, if any			
+			4. recurse with 'icons' set to any modified containers and 'context' set
+				to their pending changes (computed in step 1biii) and append returned
+				requests... the purpose of this step is for container resizing to have
+				a cascading effect (i.e., a resized container triggers its parent's
+				resizing if need be)
+			5. send batchEdit or return requests 
+	 
+		NOTE:: the 'context' parameter contains a list of pending changes computed by
+				 GeometryUtils.transformSelection() but not yet persisted onto the canvas, as well
+				 as a map of pending insertions, if any... this seemingly odd passing 
+				 around of pending information is necessary to enable atomicity of icon
+				 transformations, insertions and container resizings */
+	/**
+	 * Resizes the containers of icons (specified as uri array) that have moved within
+	 * them as required and uninsert dragged-out icons.
+	 */
+	this.resizeContainers = function(icons,context,dryRun,disabledDragouts) {
+		if( icons.length == 0 )
+			return (dryRun ? [] : undefined);
+	
+		var requests 			  = [],
+			 containers2changes = {},
+			 resizeContainer	  =
+				 function(c,clink,it)
+				 {
+					 var cbbox  = __getBBox(
+							 c,utils.mergeDicts([context,containers2changes]) ),
+					 	  itbbox = __getBBox(it,context);
+	 				 if( __isBBoxInside(itbbox, cbbox) )
+	 					 return;
+	 				 else if( __isBBoxDisjoint(itbbox, cbbox) && 
+							 	 clink && 
+								 ! disabledDragouts )
+	 					 requests.push(
+							 {'method':'DELETE',
+							  'uri':HttpUtils.url(clink,__NO_USERNAME+__NO_WID)});
+	 				 else
+	 				 {
+	 					 containers2changes[c] = 
+	 						 containers2changes[c] || 
+	 						 utils.mergeDicts(
+								 [{'position':
+									  [parseFloat(__getIcon(c).getAttr('__x')),
+									   parseFloat(__getIcon(c).getAttr('__y'))],
+		 							'scale': 
+										[parseFloat(__getIcon(c).getAttr('__sx')),
+										 parseFloat(__getIcon(c).getAttr('__sy'))]},
+								  context[c]]);
+	
+	 					 var padding  = 20,
+	 						  overflow = 
+								  {'right':	 (itbbox.x + itbbox.width) - 
+													(cbbox.x + cbbox.width) + padding,
+								   'left': 	 cbbox.x - itbbox.x + padding,
+	  								'top':	 cbbox.y - itbbox.y + padding,
+	  								'bottom': (itbbox.y + itbbox.height) - 
+													(cbbox.y + cbbox.height) + padding};
+	
+						 if( overflow.left > 0 )
+	 					 {
+	 						 containers2changes[c]['position'][0] -= overflow.left;
+	 						 containers2changes[c]['scale'][0] *= 
+	 							 (cbbox.width+overflow.left)/cbbox.width;
+	 						 cbbox.width *= containers2changes[c]['scale'][0];
+	 					 }
+	 					 if( overflow.right > 0 )
+	 						 containers2changes[c]['scale'][0] *= 
+	 							 (cbbox.width+overflow.right)/cbbox.width;
+						 
+	 					 if( overflow.top > 0 )
+	 					 {
+	 						 containers2changes[c]['position'][1] -= overflow.top;
+	 						 containers2changes[c]['scale'][1] *= 
+	 							 (cbbox.height+overflow.top)/cbbox.height;
+	 						 cbbox.height *= containers2changes[c]['scale'][1];
+	 					 }
+	 					 if( overflow.bottom > 0 )
+	 						 containers2changes[c]['scale'][1] *= 
+	 							 (cbbox.height+overflow.bottom)/cbbox.height;
+	 				 }
+				 };
+	
+		icons.forEach(
+			function(it)
+			{
+				if( !(it in __icons) || __isConnectionType(it) )
+					return;
+			
+				__icons[it]['edgesIn'].forEach(
+					function(edgeId)
+					{
+						var linkIn = __edgeId2ends(edgeId)[0];
+						if( __isContainmentConnectionType(linkIn) )
+							resizeContainer(
+								__edgeId2ends(__icons[linkIn]['edgesIn'][0])[0],
+								linkIn,
+								it);
+					});
+	
+				if( context.toBeInserted && it in context.toBeInserted )
+					resizeContainer(context.toBeInserted[it],undefined,it);			
+			});
+	
+	
+		if( utils.keys(containers2changes).length == 0 && requests.length == 0 )
+			return (dryRun ? [] : undefined);
+	
+		for( var uri in containers2changes )
+			requests.push(
+					{'method':'PUT', 
+					 'uri':HttpUtils.url(uri+'.cs',__NO_USERNAME+__NO_WID),
+					 'reqData':{'changes':containers2changes[uri]}});
+	
+		requests = 
+			requests.concat(
+				utils.flatten(
+					GeometryUtils.resizeContainers(
+						 utils.keys(containers2changes),
+						 containers2changes,
+						 true)));
+	
+		if( dryRun )
+			return requests;	
+		else
+			HttpUtils.httpReq(
+					'POST',
+					HttpUtils.url('/batchEdit',__NO_USERNAME),
+					requests);	
+	};
+	
+	/**
+	 * Shows the geometry controls overlay (positioning is based on the bounding box
+	 * of the current selection) and initializes the transformation preview overlay
+	 */
+	this.showGeometryControlsOverlay = function() {
+		var bbox = __selection['bbox'];
+	
+		if( geometryControlsOverlay == undefined )
+		{
+			geometryControlsOverlay = $('#div_geom_ctrls');
+			['resize','resizeH','resizeW','rotate'].forEach(
+				function(x)
+				{
+					var img = $('<img>');
+					img.attr('class', 'geometry_ctrl');
+					img.attr('src', 'client/media/'+x+'.png');
+					img.get(0).onmousewheel = 
+						function(event)	
+						{
+							var dir = event.wheelDelta;
+							GeometryUtils.previewSelectionTransformation(x,dir);
+							return false;
+						};
+					geometryControlsOverlay.append(img);
+				});
+			var img = $('<img>');
+			img.attr('class', 'geometry_ctrl');
+			img.attr('src', 'client/media/ok.png');
+			img.click(function(event) {GeometryUtils.transformSelection(__GEOM_TRANSF);});
+			geometryControlsOverlay.append(img);
+		}
+			
+		geometryControlsOverlay.css("top", 
+			bbox.y + bbox.height + 2 - document.body.scrollTop + "px"),
+		geometryControlsOverlay.css("left", 
+			bbox.x + bbox.width/2 - __GEOM_CTRLS_WIDTH/2.0 -  document.body.scrollLeft + "px");
+		geometryControlsOverlay.css("display", "inline");
+	
+		GeometryUtils.initSelectionTransformationPreviewOverlay();
+		__setCanvasScrolling(false);
+	};
+	
+	/**
+	 * Snaps the top-left corner of the selection bounding box to the nearest
+	 * grid point
+	 */
+	this.snapSelectionToGrid = function() {
+		var bbox = __selection['bbox'],
+			 dx	= bbox.x % __GRID_CELL_SIZE,
+			 dy	= bbox.y % __GRID_CELL_SIZE;
+		
+		if( dx == 0 && dy == 0 )
+			return;
+
+		GeometryUtils.initSelectionTransformationPreviewOverlay(bbox.x,bbox.y);
+		GeometryUtils.previewSelectionTranslation(
+			bbox.x + (dx < __GRID_CELL_SIZE/2 ? -dx : __GRID_CELL_SIZE-dx),
+			bbox.y + (dy < __GRID_CELL_SIZE/2 ? -dy : __GRID_CELL_SIZE-dy));
+		GeometryUtils.transformSelection(__GEOM_TRANSF);
+	};
+	
+	/* applies the transformation currently applied to the preview overlay to the 
+	 	selected icon(s)/edge(s) and removes the geometry controls and transformation
+	  	preview overlays... if 'insertInfo' is specified, also inserts selection into
+		it (see NOTE about why this is done from here)... this function doesn't 
+		actually transform the icons, it merely requests the update of the icon(s)'s
+	  	'transformation' and/or the link(s)'s $segments attributes on the csworker
+		(i.e., a changelog triggers the actual transformation)
+			
+			1. extract transformation and build up changes in 'uri2changes'
+			2. add $segments changes to 'uris2changes'
+			3. retrieve and compute all necessary requests 
+				a. retrieve insertion requests (+ provide DataUtils.insert() with data needed
+					to compute bboxes of to-be-transformed icons, i.e., 'uris2changes')
+				b. convert 'uri2changes' to icon transformation requests
+				c. retrieve container resizing requests (+ provide GeometryUtils.resizeContainers()
+					with 'uris2changes', a list of pending insertions from step	3a, and
+					possibly a dragout prohibition)
+			4. send batchEdit with requests from step 3... note that requests from 
+				step 3a. are inserted last s.t. the event-flow is 1-something moved
+				followed by 2-something inserted... this ordering is needed to ensure 
+				mappers and parsers are evaluated in a sensible order
+	
+		the following describes the algorithm for getting edge ends to follow their
+	  	icons when these are transformed:
+			1. for each outgoing edge,
+				z)	do nothing if the edge's Link is in __selection
+				a) fetch the edge's source xy
+				b) apply transformation T on it to produce xy'
+				c) 'move' the edge source and possibly its first control point (when 
+					they are colocated) to xy'... in reality, save the desired motion in
+					connectedEdgesChanges
+			2. for each outgoing edge, apply similar logic but to edge's end and last
+				control point
+	
+		NOTE:: to avoid race conditions between updates to different edges within a
+				 single Link's $segments, relevant changes are accumulated in 
+				 connectedEdgesChanges s.t. those pertaining to the same Link end up
+				 bundled in a single update request
+	
+		NOTE:: to avoid race conditions between updates to $segments resulting from
+				 edge ends following connected icon and updates resulting from edges 
+				 themselves being transformed (i.e., when they are within __selection),
+				 the former are ignored when we know the latter will be carried out 
+	 
+		NOTE:: because SVG transformations are always relative to the global (0,0),
+				 non-translate transformations still technically translate things... 
+				 Raphael allows specifiying different origins for transformations...
+					default SVG scale x2 :
+						Rect(10,10,200,100)  >  Rect(20,20,400,200)
+					Raphael scale with scale origin set to (10,10)
+						Rect(10,10,200,100)  >  Rect(10,10,200,100)
+				 in the above example, Raphael's transformation matrix will report the
+	 			 translation from (20,20) back to (10,10) even though from my 
+	 			 perspective, the figure hasn't moved and has only been scaled... to 
+	 			 account for this, when decomposing the said matrix, we ignore tx,ty
+				 when r|sx|sy aren't 0|1|1 and vice-versa... this doesn't cause any 
+				 problems because the client interface doesn't support scaling/rotating
+				 *and* translating without an intermediate call to this function... 
+	
+		NOTE:: essentially, the above-explained ignored rotation/scaling translation
+	  			 components apply to the top-left corner of the selection bbox (i.e., 
+				 it's Raphael ensuring that the said corner does not move as a result 
+				 of rotations/scalings 'centered' on it)... however, similar rotation/
+				 scaling translation components apply to contents of the selection... 
+				 this is because the said contents are changing wrt. the top-left 
+				 corner of the selection, not wrt. their own (x,y)... ignoring these 
+				 'internal' translation components would cause altering a selection to
+				 act like altering each selected item individually... long story short,
+				 we can not and do not ignore them... below is the algorithm we use to
+				 compute the internal translation components:
+				 
+				 1. foreach selected icon
+					[do nothing if no rotation or and no scaling]			 
+					a) compute offset between icon's x,y and selection's top-left corner
+					b) apply extracted (from transformation matrix) rotation and scale 
+						to a point whose coordinates are the x and y offsets from step a)
+					c) determine translation from point from step a) to transformed 
+						point from step b)
+					d) the icon's transformation is now the extracted rotation and 
+						scaling *and* the translation from step c) 
+	 
+		NOTE:: since the selection transformation should be an atomic operation, 
+				 changes are accumulated in 'uris2changes' and are only actually sent
+				 to the csworker at the very end of this function... also, since 
+	 			 insertions and container resizings and the selection transformations
+				 that triggered them should be atomic too, requests pertaining to the 2
+				 former tasks are computed and bundled with those that effect the 
+				 latter... the results of this form the emitted batchEdit */
+	this.transformSelection = function(callingContext,insertInfo) {
+		var T = transformationPreviewOverlay.node.getAttribute('transform');
+		if( T == null || T == 'matrix(1,0,0,1,0,0)' )
+		{
+			GeometryUtils.hideGeometryControlsOverlay();
+			GeometryUtils.hideTransformationPreviewOverlay();
+			return;
+		}
+	
+		/** 1 **/
+		var _T   					  = __decomposeTransformationMatrix(T),
+			 connectedEdgesChanges = {},
+			 uris2changes			  = {};
+		__selection['items'].forEach(
+			function(it)
+			{
+				if( it in __icons )
+				{
+					var icon		= __icons[it]['icon'],
+						 changes = {};
+					if( _T.r == 0 && 
+						 Math.abs(1-_T.sx) <= 0.001 &&
+						 Math.abs(1-_T.sy) <= 0.001 )
+					{
+						/* translation only */ 		
+						if( _T.tx != 0 || _T.ty != 0 )
+							changes['position'] = 
+								[_T.tx + parseFloat(icon.getAttr('__x')), 
+		  						 _T.ty + parseFloat(icon.getAttr('__y'))];
+					}
+					else
+					{
+						/* rotation/scale only */ 						
+						var offset	 = [icon.getAttr('__x') - __selection['bbox'].x,
+						 				 	 icon.getAttr('__y') - __selection['bbox'].y],
+							 rsOffset = GeometryUtils.transformPoint(
+												offset[0],
+												offset[1],
+												'rotate('+_T.r+') scale('+_T.sx+','+_T.sy+')'),
+						  	 offsetTx = rsOffset[0] - offset[0],
+							 offsetTy = rsOffset[1] - offset[1];
+	
+						if( _T.r != 0 )
+							changes['orientation'] = 
+								(parseFloat(icon.getAttr('__r')) + _T.r) % 360;
+	
+						if( Math.abs(1-_T.sx) > 0.001 || Math.abs(1-_T.sy) > 0.001  )
+							changes['scale'] = 
+									[_T.sx * parseFloat(icon.getAttr('__sx')), 
+		  							 _T.sy * parseFloat(icon.getAttr('__sy'))];
+	
+						if( offsetTx != 0 || offsetTy != 0 )
+							changes['position'] = 
+								[offsetTx + parseFloat(icon.getAttr('__x')), 
+		  						 offsetTy + parseFloat(icon.getAttr('__y'))];
+					}
+	 				uris2changes[it] = changes;
+	
+	
+					if( ! __isConnectionType(it) )
+					{
+						/* have edge ends out follow */
+						__icons[it]['edgesOut'].forEach(
+							function(edgeId)
+							{
+								var linkuri = __edgeId2linkuri(edgeId);
+								if( __isSelected(linkuri) )
+									return;
+					
+								var segments = __edges[edgeId]['segments'],
+									 points	 = segments.match(/([\d\.]*,[\d\.]*)/g),
+									 xy 		 = utils.head(points).split(','),
+									 newXY 	 = GeometryUtils.transformPoint(xy[0],xy[1],T);
+										 
+								connectedEdgesChanges[linkuri] = 
+									(connectedEdgesChanges[linkuri] || {});
+								points.splice(0,1,newXY.join(','));
+								connectedEdgesChanges[linkuri][edgeId] = 
+									'M'+points.join('L');
+							});
+	
+						/* have edge ends in follow */
+						__icons[it]['edgesIn'].forEach(
+							function(edgeId)
+							{
+								var linkuri = __edgeId2linkuri(edgeId);
+								if( __isSelected(linkuri) )
+									return;
+				
+								var segments = __edges[edgeId]['segments'],
+									 points	 = segments.match(/([\d\.]*,[\d\.]*)/g),
+									 xy 		 = utils.tail(points).split(','),
+									 newXY 	 = GeometryUtils.transformPoint(xy[0],xy[1],T);
+								connectedEdgesChanges[linkuri] = 
+									(connectedEdgesChanges[linkuri] || {});
+								points.splice(points.length-1,1,newXY.join(','));
+								connectedEdgesChanges[linkuri][edgeId] = 
+									'M'+points.join('L');
+							});
+					}
+					else
+					{
+						/* transform entire edges */					
+						var __segments = __linkuri2segments(it),
+							 changes		 = {};
+						for( var edgeId in __segments )
+						{
+							var segments  = __segments[edgeId],
+								 points	  = segments.match(/([\d\.]*,[\d\.]*)/g),
+								 newPoints = points.map(
+			 										 function(p)
+							 						 {
+														 p = p.split(',');
+														 return GeometryUtils.transformPoint(p[0],p[1],T);
+													 });
+							changes[edgeId] = 'M'+newPoints.join('L');
+						}
+						uris2changes[it]['$segments'] = changes;
+					}
+				}
+			});
+	
+		/** 2 **/
+		if( utils.keys(connectedEdgesChanges).length > 0 )
+			for( var linkuri in connectedEdgesChanges )
+			{	
+				if( !(linkuri in uris2changes) )
+					uris2changes[linkuri] = {};
+				if( !('$segments' in uris2changes[linkuri]) )
+					uris2changes[linkuri]['$segments'] = __linkuri2segments(linkuri);
+	
+				uris2changes[linkuri]['$segments'] = 
+					utils.mergeDicts([
+						uris2changes[linkuri]['$segments'],
+						connectedEdgesChanges[linkuri]]);
+			}
+	
+		/** 3-4 **/
+		if( utils.keys(uris2changes).length > 0 )
+		{
+			var csRequests 	 = [],
+				 insertRequests = [];
+			if( insertInfo )
+			{
+				insertRequests = DataUtils.insert(
+											insertInfo['dropTarget'].getAttribute('__csuri'),
+											__selection['items'],
+											insertInfo['connectionType'],
+											uris2changes,
+											true);
+	
+				var toBeInserted = {};
+				insertRequests.forEach(
+					function(r)
+					{
+						toBeInserted[r['reqData']['dest']] = r['reqData']['src'];
+					});
+			}
+	
+			for( var uri in uris2changes )
+				if( utils.keys(uris2changes[uri]).length > 0 )
+					csRequests.push(
+						{'method':'PUT', 
+						 'uri':HttpUtils.url(uri+'.cs',__NO_USERNAME+__NO_WID),
+						 'reqData':{'changes':uris2changes[uri]}});
+			
+			HttpUtils.httpReq(
+				'POST',
+				HttpUtils.url('/batchEdit',__NO_USERNAME),
+				csRequests.concat(
+					GeometryUtils.resizeContainers(
+						__selection['items'],
+						utils.mergeDicts(
+							[uris2changes, {'toBeInserted':toBeInserted}]),
+						true,
+						(callingContext == __GEOM_TRANSF)),
+					insertRequests));
+		}
+	};
+	
+	/**
+	 * Apply the specified transformation to the given point and return
+	 * the resulting point
+	 */
+	this.transformPoint = function(x,y,T) {
+		var pt = __canvas.group();
+		pt.push( __canvas.point(x,y) );
+		pt.node.setAttribute('transform',T);
+	
+		var bbox	 = pt.getBBox();
+		pt.remove();
+		return [bbox.x+bbox.width/2,bbox.y+bbox.height/2];
+	};
+	
+	return this;
+}();

+ 69 - 0
client/globalVariables.js

@@ -0,0 +1,69 @@
+/*******************************************************************************
+AToMPM - A Tool for Multi-Paradigm Modelling
+
+Copyright (c) 2012 Conner Hansen (chansen@crimson.ua.edu)
+
+This file is part of AToMPM.
+
+AToMPM is free software: you can redistribute it and/or modify it under the
+terms of the GNU Lesser General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later 
+version.
+
+AToMPM is distributed in the hope that it will be useful, but WITHOUT ANY 
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with AToMPM.  If not, see <http://www.gnu.org/licenses/>.
+*******************************************************************************/
+
+
+/**
+ * The purpose of this file is to insert the needed global variables
+ * into the namespace. This allows for functions that require access
+ * to the global objects that would otherwise be defined after that
+ * objects creation to still be accessible
+ */
+var WindowManagement; /* The Window Management object. This handles management of the display, such as the popup dialogs. Could be merged with GUIUtils */
+var WindowEventHelper; /* The Window Event Helper object. This processes window events. */
+var HttpUtils; /* The HTTP Utilities object. This handles communication with the server. */
+var UserManagement; /* User Management object. This handles user operations, such as log in, log out, and registration. */
+var AtomPMClient; /* The AToMPM client object. This handles many core operations of the client. */
+var InputBarStateChart; /* The Input Bar state chart object. To be removed */
+var InputBarUtils; /* The Input Bar Utilities object. To be removed. */
+var GUIUtils; /* The GUI Utilities object. This handles GUI manipulation and display. Could be merged with WindowManagement. */
+var Collaboration; /* The Collaboration object. This handles the chat windows */
+var BehaviorManager; /* The Behavior Manager object. This drives the original state charts */
+var CompileUtils; /* The Compile Utilities object. This handles the compilation of the model into a metamodel */
+var ConnectionUtils; /* The Connection Utilities object. This handles edges, connections, control points, etc. */
+var DataUtils; /* The Data Utilities object. Handles the loaded objects, edges, toolbars, etc */
+var EditUtils; /* Handles the Copy, Paste, Undo, and Redo options. Consider merging this with GUIUtils */
+var GeometryUtils; /* Handles transformations and translations. */
+var MMMUtils;
+
+var currentKeys = [];
+
+// Command Keys
+var KEY_TAB = 9,
+	KEY_ENTER = 13,
+	KEY_SHIFT = 16,
+	KEY_CTRL = 17,
+	KEY_ALT = 18,
+	KEY_ESC = 27,
+	KEY_INS = 45,
+	KEY_DEL = 46,
+	KEY_CMD1 = 91, 
+	KEY_CMD2 = 92, 
+	KEY_CMD3 = 224;
+
+// Arrow Keys
+var KEY_RIGHT_ARROW = 39, 
+	KEY_LEFT_ARROW = 37, 
+	KEY_UP_ARROW = 38, 
+	KEY_DOWN_ARROW = 40;
+
+// Mouse Buttons
+var MOUSE_RIGHT = 2,
+	MOUSE_LEFT = 0,
+	MOUSE_MIDDLE = 1;

+ 774 - 0
client/gui_utils.js

@@ -0,0 +1,774 @@
+/*******************************************************************************
+AToMPM - A Tool for Multi-Paradigm Modelling
+
+Copyright (c) 2011 Raphael Mannadiar (raphael.mannadiar@mail.mcgill.ca)
+Modified by Conner Hansen (chansen@crimson.ua.edu)
+
+This file is part of AToMPM.
+
+AToMPM is free software: you can redistribute it and/or modify it under the
+terms of the GNU Lesser General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later 
+version.
+
+AToMPM is distributed in the hope that it will be useful, but WITHOUT ANY 
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with AToMPM.  If not, see <http://www.gnu.org/licenses/>.
+*******************************************************************************/
+
+GUIUtils = function(){
+	
+	/**
+	 * This is the old getElementById function
+	 */
+	this.$$ = function( id ){
+		return document.getElementById( id );
+	};
+	
+	/**
+	 * Converts from page centric X coordinates to canvas centric X coordinates
+	 */
+	this.convertToCanvasX = function(pageX){
+		return pageX + $('#div_container').scrollLeft() - $('#contentDiv').offset().left;
+	};
+
+	/**
+	 * Converts from page centric Y coordinates to canvas centric Y coordinates
+	 */	
+	this.convertToCanvasY = function(pageY){
+		return pageY + $('#div_container').scrollTop() - $('#contentDiv').offset().top;
+	};
+	
+	/**
+	 * Disables the dock bar
+	 */
+	this.disableDock = function(){
+		$('#div_dock').attr('class', 'dock disabled_dock');
+	};
+	
+	/**
+	 * Enables the dock bar
+	 */
+	this.enableDock = function(){
+		$('#div_dock').attr('class', 'dock');
+	};
+	
+	/**
+	 * Constructs and returns a checked checkbox
+	 */
+	this.getCheckbox = function(checked){
+		var cb = $('<input>');
+		cb.attr("type", 'checkbox');
+		cb.prop("checked", checked);
+		return cb;
+	};
+	
+	/**
+	 * Returns a <div> with an interactive file browser within it
+	 * 
+	 * @param fnames - a complete list of all files in the directory tree
+	 * structure
+	 * @param draggable - when true, files and folders can be meaningfully
+	 * dragged
+	 * @param newfile when true, an editable new file icon is present
+	 * @param startfolder if set, starts navigation at the specified folder
+	 */
+	this.getFileBrowser = function(fnames, draggable, newfile, startfolder){
+		var maxFnameLength = utils.max(fnames,function(_) {return _.length;}),
+			 fileb = $("<div>"),
+			 navdiv = $("<div>"),
+			 input = $("<input>"),
+			 selection = undefined,
+			 currfolder = '/',
+			 clearSelection = 
+				 function()
+				 {
+					 if( selection )
+					 {
+						 selection.attr("class", 'fileb_icon');
+						 selection = undefined;
+						 input.value = '';
+					 }
+				 },
+			 navbuttononclick	=
+				 /* 1 construct the full path associated to the clicked button
+					 2 remove 'deeper' buttons
+					 3 chdir to selected folder */
+				 function(ev)
+				 {
+					 var path = '';
+					 for( var i=0; i<navdiv.children("button").length; i++ )
+					 {
+						 path += $(navdiv.children()[i]).html() +'/';
+						 if( $(navdiv.children()[i]).html() == $(ev.target).html() )
+							 break;
+					 }
+					 
+					 setCurrentFileBrowserFolder(path.substring(1), fnames);
+				 },
+			 setCurrentFileBrowserFolder = 
+				 /* 1 determine files and folders from the given folder
+					 2 produce icons for them and add them to to-be content div
+					 3 create navigation toolbar for complete directory hierarchy
+					 4 replace previous content div, if any, with new one 
+				  	 5 clear past selection, if any, and remember current folder */
+				 function(folder,fnames) 
+				 {
+					 var div = $('#div_fileb-contents'),
+						  folders		  = [],
+						  files			  = [],
+						  maxFnameLength = 0,
+						  exists = false;
+					 
+					 // If it already exists, remove everything!
+					 if( div.length > 0 ) {
+						 $('#div_fileb-contents').remove();
+	//					 exists = true;
+					 }
+					 
+					 div = $("<div>");
+						 
+					 div.attr("class", 'fileb_pane')
+					 	.attr("id", 'div_fileb-contents');
+					 
+					 fnames.forEach( function(fname) {
+						 var _folder = utils.regexpe(folder);
+						 if( (matches=fname.match('^'+_folder+'(.+?/)')) )
+						 {
+							 if( ! utils.contains(folders,matches[1]) )
+								 folders.push(matches[1]);
+							 else
+								 return;
+						 }
+						 else if( (matches=fname.match('^'+_folder+'(.*)')) )
+							 files.push(matches[1]);
+						 else
+							 return;
+							 
+						 maxFnameLength = 
+							 Math.max(maxFnameLength,matches[1].length);
+					 });
+							 
+	//				 var tmpDiv = $("<div>");
+					 folders.concat(files).forEach( function(fname) {
+						 var icon = HttpUtils.getFileIcon(fname);
+						 if( icon )
+						 {
+							 icon.css("width", 8+maxFnameLength+'ex');
+							 icon.click( function(ev) {
+							 	 clearSelection();								 	
+	
+								 if( fname.match(/\/$/) )
+									 setCurrentFileBrowserFolder(folder+fname,fnames);
+								 else
+								 {
+									 input.val( folder+fname );
+									 selection = icon;
+									 selection.attr("class", 'fileb_icon_selected');
+								 }
+							 });
+				 			 
+				 			 if( draggable )
+							 {
+								 //icon.setAttribute('draggable',true);
+				 				 icon.attr("draggable", true);
+								 icon.get(0).ondragstart = 
+									 function(event) 
+									 {
+										 var uri = HttpUtils.url(folder+fname+'.file',__NO_WID);
+			  							 event.dataTransfer.effectAllowed = 'copyMove';
+			  							 event.dataTransfer.setData(
+											 'DownloadURL', 
+											 'application/zip:'+fname+'.zip:'+
+												 window.location.origin+uri);
+										 event.dataTransfer.setData('uri',uri);
+									 };
+							 }
+	
+							 div.append(icon);
+						 }
+					 });
+					 
+					 if( newfile )
+					 {
+						 var icon = HttpUtils.getNewFileIcon( function(ev) {
+							 input.val( folder+ev.target.textContent );
+						 });
+						 icon.css("width", 8+maxFnameLength+'ex');
+						 div.append(icon);
+					 }
+					 
+					 navdiv.empty();
+					 
+	//				 while (navdiv.children().length > 0) {
+	//					navdiv.find(navdiv.lastChild).remove();
+	//				 }
+					 tmpDiv = $("<div>");
+					 var subfolders = folder.split('/').filter(function(subf) {return subf != '';});
+					 subfolders.unshift('/');
+					 subfolders.forEach(function(subfolder) {
+						var navbutton = $('<button>');
+						navbutton.html( subfolder );
+						navbutton.click(navbuttononclick);
+						navdiv.append(navbutton);
+					 });
+					 
+					 //navdiv.html( tmpDiv.html() );
+					 
+					 
+					 if( exists ) {
+	//					 $('#div_fileb-contents').html( div.html() );
+	//					 fileb.append(div);
+						 $('#div_fileb-contents').remove();
+						 fileb.append(div);
+					 } else
+						 fileb.append(div);
+	
+					 clearSelection();				 
+					 currfolder = folder;
+				 };
+	
+		fileb.css("width", maxFnameLength+'ex')
+			.css("maxWidth", '100%');
+		
+		navdiv.css("align",  'left');
+		
+		input.attr("type", 'hidden');
+		
+		fileb.append(navdiv);
+		fileb.append(input);
+		
+		setCurrentFileBrowserFolder(startfolder ? startfolder : '/',fnames);
+	
+		return {'filebrowser':	 fileb,
+				  'filepane':	 	 function() {return $('#div_fileb-contents');},
+				  'getcurrfolder': function() {return currfolder;},	
+				  'getselection':	 function()	{return input.val();}};
+	};
+	
+	// TODO: replace the bundled function with an actual object generation. The
+	// current method is sloppy.
+	/**
+	 * Constructs and returns an input field given an attribute's type and value,
+	 * bundled in the return value is a function to retrieve the input fields
+	 * content as well as its initial value
+	 * 
+	 * For Maps and Lists, onfocus/onblur toggle a default entry to be shown.
+	 */
+	this.getInputField = function (type,value){
+		/* recursively expand specialTypes, if any */
+		function explodeType(t)
+		{
+			var exploded = __specialTypes[t] || t;
+			while( exploded.match(/\$/) )
+				exploded = exploded.replace(
+									/(\$.+?)([,\]>])/g, 
+									function(s,p1,p2){return __specialTypes[p1]+p2;});
+			return exploded;
+		}
+	
+		/* return a default value for the given exploded type string */
+		function defaultEntry(et)
+		{
+			if( et == 'string' || et == 'code' )
+				return "";
+			else if( et == 'int' )
+				return 0;
+			else if( et == 'boolean' )
+				return true;
+			else if( et == 'double' )
+				return 0.0;
+			else if( et.match(/^ENUM/) )
+				return et;
+			else if( (matches=et.match(/^list<(.*)>$/)) )
+				return [defaultEntry(matches[1])];
+			else if( (matches=et.match(/^map<\[(.*?)\],\[(.*)\]>$/)) )
+			{
+				var m 	 = {},
+					 keys  = matches[1].split(','),
+					 types = [],
+					 depth = 0,
+					 index = 0;
+				for( var i=0; i<matches[2].length; i++ )
+				{
+					if( matches[2].charAt(i) == '(' ||
+						 matches[2].charAt(i) == '<' )
+						depth++;
+					else if( matches[2].charAt(i) == ')' ||
+						 		matches[2].charAt(i) == '>' )
+						depth--;
+	
+					if( matches[2].charAt(i) == ',' && depth == 0 )
+					{
+						types.push( matches[2].substring(index,i) );
+						index = i+1;
+					}
+				}
+				types.push( matches[2].substring(index) );
+	
+				for( var i=0; i<keys.length; i++ )
+					m[keys[i]] = defaultEntry(types[i]);
+				return m;
+			}
+			else if( (matches=et.match(/^map<(.*),(.*)>$/)) )
+			{
+				var m = {};
+				m[defaultEntry(matches[1])] = defaultEntry(matches[2]);
+				return m;
+			}
+		}
+	
+	
+		if( type == 'code' )
+			var input 	 = GUIUtils.getTextInput(utils.jsond(value,'\n')),
+				 getinput = function(_){return _.val();};
+		
+		else if( type.match(/^map/) || 
+					type.match(/^list/) )
+		{
+			var input 	 = GUIUtils.getTextInput(
+										utils.jsond(utils.jsons(value,undefined,'   '))),
+				 getinput = function(_){
+					 return utils.jsonp(utils.jsone(_.val()));
+				 };
+	
+			var de 	 = defaultEntry( explodeType(type) ),
+				 isMap = type.match(/^map/);
+			input.focus( 
+					(isMap ?
+						function()	
+						{
+						 	var newVal = utils.mergeDicts([getinput(input),de]);
+						 	input.val( utils.jsond(utils.jsons(newVal,undefined,'   ')));
+						} :
+						function()	
+						{
+						 	var newVal = getinput(input).concat(de);
+						 	input.val( utils.jsond(utils.jsons(newVal,undefined,'   ')));
+						}));
+			input.blur(
+					(isMap ?
+						function()	
+						{
+							var val = getinput(input);
+							utils.splitDict(val,utils.keys(de));
+						 	input.val( utils.jsond(utils.jsons(val,undefined,'   ')));
+						} :
+						function()	
+						{
+							var val = getinput(input);
+							if( utils.jsons(utils.tail(val)) == utils.jsons(de[0]) )
+								val.pop();
+						 	input.val( utils.jsond(utils.jsons(val,undefined,'   ')));
+						}));
+		}
+	
+		else if( type.match(/^ENUM/) )
+		{
+			var vals 	 = type.match(/^ENUM\((.*)\)$/)[1],
+				 input 	 = GUIUtils.getSelector(vals.split(','),false,[value]),
+				 getinput = 
+					 function(_){return HttpUtils.getSelectorSelection(_)[0]};
+		}
+	
+		else if( type.match(/^boolean$/) )
+		{
+			var input 	 = GUIUtils.getCheckbox(value),
+				 getinput = 
+					 function(_){return _.prop("checked");};
+		}
+	
+		else if( type.match(/^\$/) )
+			return GUIUtils.getInputField(__specialTypes[type],value);
+	
+		else
+			var input 	 = GUIUtils.getTextInput(value,undefined,1),
+				 getinput = (type == 'string' ?
+						 function(_){return _.val();} :
+						 function(_){return utils.jsonp(_.val());});
+		
+		input.title = explodeType(type);
+		return {'input':input, 'getinput':getinput, 'oldVal':getinput(input)};
+	};
+	
+	/**
+	 * Constructs and returns a <select> element with the choices list converted into
+	 * a list of <option> elements.
+	 * 
+	 * @param choices - the choices for the <select> element
+	 * @param multipleChoice - if true, allows for multiple options to be selected
+	 * @param defaultSelect - sets the default selection for the list
+	 * @param numVisibleOptions - sets the number of visible options
+	 */
+	this.getSelector = function(choices,multipleChoice,defaultSelection,numVisibleOptions){
+		var select = $('<select>');
+		select.attr("class", 'default_style');
+		select.attr("size", Math.min(choices.length,numVisibleOptions || __MAX_SELECT_OPTIONS));
+		select.attr("multiple", multipleChoice);
+	
+		choices.forEach(
+				function(choice)
+				{
+					var option = $('<option>');
+					option.val( choice ); 
+					option.html( choice );
+					select.append(option);
+					if( defaultSelection != undefined && 
+						 utils.contains(defaultSelection,choice) )
+						option.prop("selected", true);	
+				});
+	
+		return select;
+	};
+	
+	/* constructs an INPUT given some text and a CSS class */
+	/**
+	 * Constructs an <input> element
+	 * 
+	 * @param text - the default text for the input element
+	 * @param className - the default class name for the input element
+	 * @param width - the default width of the element. If this is omitted
+	 * then the <input> defaults to 400px wide.
+	 */
+	this.getStringInput = function(text,className,width){
+		var input = $('<input>');
+		input.attr("type", 'text');
+		input.attr("class", className || 'default_style');
+		input.val( text );
+		input.css("width", width || '400px');
+		return input;
+	};
+	
+	/**
+	 * Constructs a <textarea> element. In this element, Alt + Right Arrow
+	 * is treated as a tab.
+	 * 
+	 * @param code - the default code for this text area
+	 * @param className - the default class name for the <textarea>
+	 * @param rows - the default number of rows for this <textarea>
+	 */
+	this.getTextInput = function(code,className,rows){
+		var input = $('<textarea>');
+		input.attr("cols", 80);
+		input.attr("rows", (rows || 7));
+		input.val(code);
+		input.attr("class", className || 'code_style');
+		input.keydown( function(event) {
+			if( event.keyCode == KEY_RIGHT_ARROW /* This is to simulate a tab press */ ) {
+				if( currentKeys[ KEY_ALT ] == 1 && currentKeys[ KEY_CTRL ] != 1){
+					var cursorPos = event.target.selectionStart,
+						 lineStart = input.val().lastIndexOf('\n',cursorPos-1)+1,
+						 tabBy	  = __TAB_WIDTH - (cursorPos-lineStart)%__TAB_WIDTH,
+						 tab		  = '';
+					for(var i=0; i<tabBy; i++)	tab += ' ';
+						 
+					input.val( 
+						input.val().substring(0,cursorPos)+tab+
+					  	input.val().substring(cursorPos));
+					input.get(0).setSelectionRange(cursorPos+tabBy,cursorPos+tabBy);
+					return true;
+				}
+				
+				return true;
+			}
+			else if( event.keyCode == KEY_ENTER /* ENTER */ && currentKeys[ KEY_SHIFT ] == 1) // HUSEYIN-ENTER
+			{
+				var cursorPos = event.target.selectionStart;
+				input.val( 
+						input.val().substring(0,cursorPos)+'\r\n'+
+					  	input.val().substring(cursorPos));
+				input.get(0).setSelectionRange(cursorPos+1,cursorPos+1);
+				event.stopPropagation();
+				event.preventDefault();
+				return false;
+			}
+			else if( event.keyCode == KEY_ENTER ) // HUSEYIN-ENTER
+			{
+				event.stopPropagation();
+				event.preventDefault();
+				return false;
+			}
+		});
+		return input;
+	};
+	
+	/**
+	 * Constructs a <span> element.
+	 * 
+	 * @param text - the default text to be displayed
+	 * @param className - the default class name
+	 */
+	this.getTextSpan = function(text,className)
+	{
+		var span = $('<span>');
+		span.html( text.replace(/\n/g,'<br/>') );
+		span.attr("class", className || 'default_style');
+		return span;
+	};
+	
+	/**
+	 * Finds and removes the specified toolbar, if present
+	 * 
+	 * @param tb - the toolbar to be removed
+	 */
+	this.removeToolbar = function(tb){
+		tb_fixed = tb.replace(/\//g, "\\/");
+		if( $('#div_toolbar_'+tb_fixed) )
+		{
+			//Find the toolbar in the dock bar and remove it
+			//from the DOM
+			$("#div_dock").children("div").each( function() {
+				if( this.id == "div_toolbar_" + tb ){
+					$(this).remove();
+				}
+			});
+			
+			// Now delete it from the list of loaded toolbars
+			delete __loadedToolbars[tb];
+		}
+	};
+	
+	/**
+	 * Throw out the current canvas, replace it with a new one.
+	 * 1. Unselect any current selection
+	 * 2. Clear canvas
+	 * 3. Clear icon and edge data
+	 * 4. reset canvas statechart
+	 */
+	this.resetCanvas = function (){
+		__select();
+		__canvas.clear();
+		__icons = {};
+		__edges = {};
+		__canvasBehaviourStatechart.init();
+	};
+	
+	/**
+	 * Sets up a model popup window and displays it.
+	 * 
+	 * @param elements - DOM elements to be displayed in order
+	 * @param getinput - function that retrieves the desired user-input
+	 * from "elements". 
+	 * @param type - Can be __NO_BUTTONS, __ONE_BUTTON,
+	 * or __TWO_BUTTONS and controls the number of displayed buttons
+	 * @param title	 - the dialog title
+	 * @param callback	- called with the result of getinput when the ok
+	 * button is clicked
+	 */
+	this.setupAndShowDialog = function(elements,getinput,type,title,callback){
+		BehaviorManager.handleUserEvent(__EVENT_CANCELED_DIALOG);
+
+		var dialog 	  = $('#div_dialog'),
+			 dim_bg 	  = $('#div_dim_bg'),
+			 div_title = $('<div>');
+
+		div_title.attr("class", 'dialog_title');
+		div_title.append(GUIUtils.getTextSpan(title || ''));
+		dialog.append(div_title);
+
+		elements.forEach( 
+				function(e) 
+				{
+					dialog.append(e);
+					dialog.append( $('<br>') );
+				});
+		dialog.append( $('<br>') );
+
+
+		if( type != __NO_BUTTONS )
+		{
+			//var ok = $('<button>');
+			var ok = $('<button id="okbutton">'); // HUSEYIN-ENTER
+			ok.click( function(ev) {
+				if( getinput == undefined )
+				{
+					BehaviorManager.handleUserEvent(__EVENT_OKAYED_DIALOG);
+					if( callback != undefined )
+						callback();
+				}
+				else
+				{
+					try{
+						var input = getinput();
+					} catch(err) {
+						console.error('failed to retrieve dialog input :: '+err);
+						return;
+					}
+					input = (utils.isArray(input) ?
+									input.map(function(i) {return String(i);}) : 
+									input);
+					BehaviorManager.handleUserEvent(__EVENT_OKAYED_DIALOG);
+					callback(input); 
+				}
+			});
+			ok.html('ok');
+			dialog.append(ok);
+		}
+		
+		if( type == __TWO_BUTTONS )
+		{
+			var cancel = $('<button>');
+			cancel.click(function(ev) {
+				BehaviorManager.handleUserEvent(__EVENT_CANCELED_DIALOG);
+			});
+			cancel.html('cancel');
+			dialog.append(cancel);
+		}
+
+		BehaviorManager.setActiveBehaviourStatechart(__SC_DIALOG);
+		BehaviorManager.handleUserEvent(__EVENT_SHOW_DIALOG);
+	};
+	
+	/* 
+	  NOTE:: the sortedButtonNames() function sorts icon definition metamodels and
+				button models s.t. buttons appear in an order that reflects their
+				model... to be perfectly clean, this should be absorbed into icon
+				definition and button model compilers, but since button models aren't
+			  	compiled, it was simpler to just let this relatively simple logic live
+			  	here 
+  	*/
+	/**
+	 * Sets up and shows a toolbar according to the given button model or metamodel.
+	 * 
+	 * This method:
+	 * 1. Removes any old instance of the toolbar if it currently exists
+	 * 2. Creates a <div> to hold the buttons
+	 * 3. Creates each button and appends it to the <div>
+	 * 4. Add the <div> to the dock
+	 * 5. Map the toolbar to its data
+	 * 
+	 * @param tb - the toolbar to setup and show
+	 * @param data - the data to bind the toolbar to
+	 * @param type - the toolbar type, can be __BUTTON_TOOLBAR or __METAMODEL_TOOLBAR
+	 */
+	this.setupAndShowToolbar = function(tb,data,type)
+	{
+		var imgSrc = 
+				function(name) 
+				{
+					return (type == __BUTTON_TOOLBAR ?
+								tb.substring(0,tb.lastIndexOf('/')+1)+name+'.icon.png' :
+								'/Formalisms/default.icon.png');
+				},
+			 className = 
+				function()
+					{return (type == __BUTTON_TOOLBAR ? 'toolbar_bm' : 'toolbar_mm');},
+			 buttons = 
+				 (type == __BUTTON_TOOLBAR ?	data.asm.nodes : data.types),
+			 sortedButtons = 
+				 function()
+				 	{
+						return (type == __BUTTON_TOOLBAR ?
+							/* sort button names according to their position in their
+								associated buttons model */
+							utils.sortDict(data.csm.nodes,
+								function(b1,b2)
+								{
+									var pos1 = b1['position']['value'],
+										 pos2 = b2['position']['value'];
+										 
+	 								 if( (pos1[1] < pos2[1]) ||
+										  (pos1[1] == pos2[1] && pos1[0] < pos2[0]) )
+										 return -1;
+									 return 1;									
+								 }) :
+							utils.sortDict(data.types,
+							/* sort type names according to their IconIcon's position in 
+								the associated icon definition model */
+								function(b1,b2)
+								{
+									var pos1 = undefined,
+										 pos2 = undefined;
+	 								 b1.some( function(attr) 
+												 {
+													 if(attr['name'] == 'position')
+														 pos1 = attr['default'];
+	 												 return pos1;
+	 											 });
+									 b2.some( function(attr) 
+												 {
+													 if(attr['name'] == 'position')
+														 pos2 = attr['default'];
+	 												 return pos2;
+	 											 });
+
+										if( (pos1[1] < pos2[1]) ||
+										  	 (pos1[1] == pos2[1] && pos1[0] < pos2[0]) )
+											return -1;
+										return 1;									
+								}) );
+					},
+			 createButton = 
+			 	 function(name,tooltip,code)
+			 	 {
+			 		 var div = $('<div>'),
+						  img = $('<img>');
+					 div.addClass( 'toolbar_button' );
+					 div.attr("id", tb+'/'+name);
+			 		 div.attr("title", tooltip);
+			 		 div.click( function(ev){
+						 var res = HttpUtils.safeEval(code);
+		 				 if( res['$uerr'] )
+		 					 WindowManagement.openDialog(
+									 _ERROR,
+									 'unexpected error in button code ::\n '+res['$uerr']);
+						 else if( res['$err'] )
+		 					 WindowManagement.openDialog(
+									 _ERROR,
+									 'error in button code ::\n '+res['$err']);
+			 		 });
+//			 		 var url = HttpUtils.url(imgSrc(name),__NO_WID);
+			 		 img.attr("src", HttpUtils.url(imgSrc(name),__NO_WID));
+			 		 div.append(img);
+			 		 return div;
+			 	 };
+
+		GUIUtils.removeToolbar(tb);
+
+		var tb_div = $('<div>');
+		tb_div.attr("id", 'div_toolbar_'+tb);
+		tb_div.attr("class", className()+' toolbar unselectable' );
+		tb_div.attr("title", tb);
+
+		sortedButtons().forEach( 
+			function(b)
+			{
+				if( type == __METAMODEL_TOOLBAR && b.match(/(.*)Link$/) )
+					return;
+
+				var spc1 = $('<span>'),
+					 spc2 = $('<span>');
+//				spc1.className = spc2.className = 'toolbar_space';
+				spc1.attr("class", "toolbar_space" );
+				spc2.attr("class", "toolbar_space" );
+				tb_div.append(spc1);
+				if( type == __BUTTON_TOOLBAR )
+					tb_div.append(
+		 				 createButton(
+			 				 buttons[b]['name']['value'],						
+			 				 buttons[b]['tooltip']['value'],
+			 				 buttons[b]['code']['value']) );
+				else if( (matches = b.match(/(.*)Icon/)) )
+					tb_div.append(
+						 createButton(
+							 b,
+							 'create instance(s) of '+b.match(/(.*)Icon/)[1],
+							 '_setTypeToCreate("'+
+								 tb.substring(0,tb.length-'.metamodel'.length)+
+								 '/'+b+'");') );
+				tb_div.append(spc2);
+			} );
+
+		if( tb_div.children().length == 0 )
+			tb_div.append( GUIUtils.getTextSpan(tb,'toolbar_alt') );
+
+		$('#div_dock').append(tb_div);
+
+		__loadedToolbars[tb] = data;
+	};
+	
+	return this;
+}();

+ 240 - 0
client/http_utils.js

@@ -0,0 +1,240 @@
+/*******************************************************************************
+AToMPM - A Tool for Multi-Paradigm Modelling
+
+Copyright (c) 2011 Raphael Mannadiar (raphael.mannadiar@mail.mcgill.ca)
+Modified by Conner Hansen (chansen@crimson.ua.edu)
+
+This file is part of AToMPM.
+
+AToMPM is free software: you can redistribute it and/or modify it under the
+terms of the GNU Lesser General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later 
+version.
+
+AToMPM is distributed in the hope that it will be useful, but WITHOUT ANY 
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with AToMPM.  If not, see <http://www.gnu.org/licenses/>.
+*******************************************************************************/
+
+//Todo: replace this with JQuery
+//This remains outside for easier replacement later down the line
+/**
+ * Basic element getter
+ */
+//function $(id) 
+//{
+//  return document.getElementById(id);
+//}
+
+/**
+ * The HTTP Utils object is a utility function that allows for
+ * AToMPM to send HTTP requests
+ */
+HttpUtils = function(){
+	/**
+	 * Generates an HTTP request
+	 */
+	this.httpReq = function(method,url,params,onresponse,sync)
+	{
+		console.debug('??? '+method+' '+url, params);
+		var req = new XMLHttpRequest();
+		var onreadystatechange = function(ev) {
+			 if( req.readyState == 4 )
+			 {
+	 			 console.debug(method+' '+url+' >> '+req.status);
+				 if( req.status == 0 )
+	 				 WindowManagement.openDialog(__FATAL_ERROR,'lost connection to back-end');
+				 else if( onresponse )
+					 onresponse(req.status,req.responseText);
+				 else if( ! utils.isHttpSuccessCode(req.status) )
+	 				 WindowManagement.openDialog(_ERROR,req.responseText);
+			 }
+ 		 };
+	
+		if( method == 'GET' || method == 'DELETE' )
+		{
+//			console.log(method);
+//			console.log(url);
+//			console.log(params);
+//			console.log(onresponse);
+//			console.log(sync);
+			req.open(method, url+(params ? params : ''), !sync);
+			req.onreadystatechange = onreadystatechange;
+			req.send(null);  
+		}
+		else if( method == 'POST' || method == 'PUT' )
+		{
+//			console.debug(method);
+//			console.debug(url);
+//			console.debug(params);
+//			console.debug(onresponse);
+//			console.debug(sync);
+//			console.debug(utils.jsons(params));
+			req.open(method, url, !sync); 
+			req.onreadystatechange = onreadystatechange;
+			req.send(utils.jsons(params));
+		}
+	};
+	
+	
+	/**
+	 * Construct a complete and valid backend URL
+	 */
+	this.url = function(uri,options,wid)
+	{
+//		console.log("Calling url");
+		wid = (wid == undefined ? __wid : wid);
+		return (options & __FORCE_GET ? '/GET' : '')+
+				 (options & __NO_USERNAME ? '' : '/'+__user)+
+	 			 (uri.charAt(0) == '/' ? uri : '/'+uri)+
+	 			 (options & __NO_WID ? '' : '?wid='+wid);
+	};
+	
+	/**
+	 * Returns a file browser icon given the specified filename
+	 */
+	this.getFileIcon = function(fname)
+	{
+//		console.log("Calling getFileIcon " + fname);
+		var src = '';
+		if( fname.match(/\/$/) )
+			src = 'client/media/fileb_folder.png';
+		else if( fname.match(/\.pattern\.metamodel$/) )
+			src = 'client/media/fileb_patternmm.png';
+		else if( fname.match(/\..*Icons\.metamodel$/) )
+			src = 'client/media/fileb_csmm.png';
+		else if( fname.match(/\.metamodel$/) )
+			src = 'client/media/fileb_asmm.png';
+		else if( fname.match(/\.model$/) )
+			src = 'client/media/fileb_m.png';
+		else
+			src = 'client/media/fileb_unknown.png';
+	
+	
+		var span = $('<span>'),
+			 img  = $('<img>'),
+			 txt  = GUIUtils.getTextSpan( 
+						 fname.match(/\/$/) ? fname.substring(0,fname.length-1) : fname,
+	 					 'default_style clickable');
+		img.attr("src", src)
+			.attr("class", 'clickable');
+		
+		span.attr("class", 'fileb_icon');
+//		img.attr("class", 'clickable');
+		txt.css("padding", '5px');
+		
+		span.append(img);
+		span.append(txt);
+		
+		return span;
+	};
+	
+	/**
+	 * Returns the new file icon
+	 */
+	this.getNewFileIcon = function(oninput,caption)
+	{
+//		console.log("Calling get new file icon " + caption);
+		var span   = $('<span></span>'),
+			 img    = $('<img></img>'),
+			 txt  = GUIUtils.getTextSpan(
+							 caption || '&lt;new file&gt;',
+							 'default_style clickable');
+		img.attr("src", 'client/media/fileb_newf.png');
+		span.addClass('fileb_icon');
+		img.addClass('clickable');
+		txt.css("padding", '5px');
+		txt.css("fontStyle", 'italic');
+		txt.attr("contentEditable", true);
+		// JQuery does not support HTML5 oninput
+		txt.keyup( oninput );
+		span.append(img);
+		span.append(txt);
+		return span;
+	};
+	
+	/**
+	 * Gets the selected entry in a Select input
+	 */
+	this.getSelectorSelection = function(select)
+	{
+		var selection = [];
+		var options = select.children("option");
+		for( var i = 0; i < options.length; i++) 
+			if( $(options[i]).prop("selected") )
+				selection.push( $(options[i]).html() );
+		
+		return selection;
+	};
+	
+	
+	/* given a path to an image, produce a data uri that 'describes' the image
+	
+		NOTE:: due to browser restrictions, cross-domain images need to be processed
+				 by the backend */
+	this.imageToDataURI = function(url,callback)
+	{
+		if( url.match('^\/'+__user+'/') )
+		{
+			var canvas = $('<canvas>'),
+				 img	  = $('<img>');
+	
+			img.onload(
+				function()
+				{
+					canvas.attr("width", img.attr("width") );
+					canvas.attr("height", img.attr("height") );
+					canvas.get().getContext('2d').drawImage(img, 0, 0);
+					callback(canvas.get().toDataURL());
+				});
+			img.attr("src", url);
+		}
+		else
+			httpReq(
+				'GET',
+				'/datauri?target='+encodeURIComponent(url),
+				undefined,
+				function(statusCode,resp)
+				{
+					if( ! utils.isHttpSuccessCode(statusCode) )
+						callback(__DEFAULT_IMG_DATAURI);
+					else
+						callback(resp);
+				});
+	};
+	
+	/**
+	 * Removes all children from a node
+	 */
+	this.removeChildren = function(node) 
+	{
+		node.empty();
+//		while(node.children().length > 0 ) 
+//			node.first().remove();
+	};
+	
+	/**
+	 * Attempt to evaluate user code.
+	 * I don't understand why this is called "safeEval"
+	 * though, considering this will execute
+	 * anything passed in
+	 */
+	this.safeEval = function(code)
+	{
+		var _context = {
+				'username':__user,
+				'wid':__wid,
+				'mms':utils.keys(__loadedToolbars).filter(__isIconMetamodel)};
+		try			{eval(code); return {};}
+		catch(err)	
+		{
+			if( err['$err'] )	return err;
+			else 					return {'$uerr':err};
+		}
+	};
+	
+	return this;
+}();

+ 209 - 0
client/init.js

@@ -0,0 +1,209 @@
+/*******************************************************************************
+AToMPM - A Tool for Multi-Paradigm Modelling
+
+Copyright (c) 2011 Raphael Mannadiar (raphael.mannadiar@mail.mcgill.ca)
+Modified by Conner Hansen (chansen@crimson.ua.edu)
+
+This file is part of AToMPM.
+
+AToMPM is free software: you can redistribute it and/or modify it under the
+terms of the GNU Lesser General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later 
+version.
+
+AToMPM is distributed in the hope that it will be useful, but WITHOUT ANY 
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with AToMPM.  If not, see <http://www.gnu.org/licenses/>.
+*******************************************************************************/
+
+//TODO: shred initClient()
+/* initialize client 
+
+  PART 1: setup connection to backend
+  0. extract GET parameters, if any, from URL
+  1. setup socket and its event handlers (to handle changelogs etc.), then 
+  	  connect socket
+  2. on socket connection, 
+	  a) if no GET params or if modelshare params, 
+		  i.  spawn new csworker
+		  ii. on spawn, subscribe to new csworker changes
+	  a) otherwise (screenshare), subscribe to specified csworker changes
+  3. on reception of socket messages,
+	  a) if message has a status code (i.e., response to csworker subscription)
+		  i.   load mainmenu button model
+			 * CASE 1: no GET params *
+		 ii.  spawn and subscribe to new asworker
+		 iii. get user preferences and autoload toolbars and model, if any
+		 iv.  enable collaboration links
+		 v.	launch autosave daemon
+			 * CASE 2: modelshare *
+		 ii.  clone state of given csworker and subscribe to given asworker
+		 iii. update client state based on our csworker's updated state
+		 iv.  enable collaboration links
+		 v.	launch autosave daemon
+			 * CASE 3: screenshare *
+		 ii.  retrieve state of given csworker
+		 iii. update client state based on our csworker's updated state
+		 iv.  enable collaboration links
+		 v.	launch autosave daemon
+	 a) otherwise, handle changelog
+
+
+  PART 2: setup frontend
+  1. setup various GUI details and behaviour statecharts
+  2. setup exit prompt */
+function __initClient()
+{
+	/** PART 1 **/
+	var params = {};
+	window.location.search.substring(1).split('&').forEach(
+		function(arg)
+		{	
+			var _arg = arg.split('=');
+			params[_arg[0]] = _arg[1];
+		});
+	
+	var socket = io.connect(
+			window.location.hostname,{'port':8124,'reconnect':false});
+
+	socket.on('message', 
+		function(msg)	
+		{
+			console.debug(' >> '+utils.jsons(msg));
+			if( msg['statusCode'] != undefined )
+			{
+				if( msg['statusCode'] == 201 )	
+				{
+					_loadToolbar(__MAINMENU_PATH);
+					
+					if( window.location.search == '' )
+						HttpUtils.httpReq(
+							'PUT',
+							'/aswSubscription?wid='+__wid,
+							undefined,
+							function(statusCode,resp)
+							{
+								console.debug(statusCode);
+								console.debug(resp);
+								__aswid = utils.jsonp(resp)['data'];
+
+								_getUserPreferences(
+									function(prefs)
+									{
+										console.debug("Get User Preferences in Init.js (96)");
+										console.debug(prefs);
+										__prefs = prefs;
+										prefs['autoloaded-toolbars']['value'].
+											forEach(_loadToolbar);
+										if( prefs['autoloaded-model']['value'] != '' )
+											_loadModel(prefs['autoloaded-model']['value']);
+
+										Collaboration.enableCollaborationLinks();
+										__launchAutosave();
+									});
+						   });
+	
+					else if( 'aswid' in params && 'cswid' in params )
+					{
+						__user = params['host'];
+						HttpUtils.httpReq(
+								'PUT',
+								'/aswSubscription?wid='+__wid,
+								{'aswid':params['aswid'], 
+								 'cswid':params['cswid']},
+								 function(statusCode,resp)
+								 {
+									 resp = utils.jsonp(resp);
+									 __handleState(resp['data'],resp['sequence#']);
+									 Collaboration.enableCollaborationLinks();
+ 									 __launchAutosave();
+								 });
+					}
+					
+					else
+					{															
+						__wid  = params['cswid'];
+						__user = params['host'];
+						HttpUtils.httpReq(
+								'GET',
+								'/current.state?wid='+params['cswid'],
+								undefined,
+								function(statusCode,resp)
+								{
+									resp = utils.jsonp(resp);
+									__handleState(resp['data'],resp['sequence#']);
+									Collaboration.enableCollaborationLinks();
+									__launchAutosave();
+								});
+					}
+				}
+				else 
+					WindowManagement.openDialog(__FATAL_ERROR, 'failed to connect to back-end');
+			}
+
+			else
+				__handleChangelog(
+						msg['data']['changelog'],
+						msg['data']['sequence#'],
+						msg['data']['hitchhiker']);
+		});
+
+	socket.on('disconnect', 
+		function()
+		{  
+			WindowManagement.openDialog(__FATAL_ERROR, 'lost connection to back-end');
+		});
+
+	socket.on('connect', 
+		function()	
+		{  
+			if( window.location.search == '' ||
+				 ('aswid' in params && 'cswid' in params) )
+				HttpUtils.httpReq(
+					'POST',
+					'/csworker',
+					undefined,
+					function(statusCode,resp)
+					{
+						__wid = resp;
+						socket.emit(
+							'message',
+							{'method':'POST','url':'/changeListener?wid='+__wid});
+					});								
+				
+			else if( 'cswid' in params )
+				socket.emit(
+					'message',
+					{'method':'POST','url':'/changeListener?wid='+params['cswid']});
+
+			else
+				WindowManagement.openDialog(__FATAL_ERROR, 'invalid URL parameters '+
+						utils.jsons(params));
+		});
+		
+
+	/** PART 2 **/
+	$('#a_logout').title = 'logout '+__user;
+	__canvas = Raphael(GUIUtils.$$('div_canvas'),__CANVAS_SIZE,__CANVAS_SIZE);
+	__canvas.canvas.setAttribute('vector-effect','non-scaling-stroke');
+	__canvas.canvas.setAttribute('xmlns:xlink','http://www.w3.org/1999/xlink');
+	BehaviorManager.setActiveBehaviourStatechart(__SC_DIALOG,true);
+	BehaviorManager.setActiveBehaviourStatechart(__SC_CANVAS,true);
+	WindowManagement.setWindowTitle();
+		
+	window.onbeforeunload = 
+		/* prompt on non-logout exit */
+		function(ev) 
+		{
+			if( __user == undefined )
+				return;
+
+			else if( __prefs['confirm-exit']['value'] && ! __isSaved() )
+				return __EXITWARNING;
+		};
+	
+
+}

+ 33 - 0
client/input_bar_utils.js

@@ -0,0 +1,33 @@
+InputBarUtils = function(){
+	this.processKey = function( event ){
+		if( event.keyCode == KEY_ENTER ){
+			
+			var query = $('#mainInput')[0].value.split('(')[0].trim();
+			var acceptedQueries = ["getCount", "toggleIncUpdate"];
+			
+			if(query=="help"){
+				alert("See the text in the developer console (In Chrome, press F12)");
+                console.log("WARNING :: All commands can be issued after a transformation is loaded!");
+				console.log("INFO :: You can query the graph before/during/after a transformation using this system.");
+				console.log("INFO :: Available queries: "+acceptedQueries.toString());
+				console.log("INFO :: Query usage help");
+				console.log("getCount :: type 'getCount(\"TypeName\")' to get the number of a specific type in the graph");
+				console.log("getCount :: You can get the type names in the attribute popup of each element.");
+                console.log("toggleIncUpdate :: type 'toggleIncUpdate()' to toggle incremental updates (default is on, which will update the model at each step)");
+                console.log("toggleIncUpdate :: If you turn incremental updates off, the model will be updated at the end.");
+			}
+			else if ($.inArray(query, acceptedQueries) > -1) {	
+				console.log("Query '"+query+"' sent! Waiting for reply.");
+				HttpUtils.httpReq(
+						'PUT',
+						'/__mt/query.transform?wid='+__wid,
+						{'query':$('#mainInput')[0].value.trim()});
+			} else {
+				console.log("ERROR :: Query '"+query+"' is not accepted.\n\tAccepted queries: "+acceptedQueries.toString());
+			}
+			
+		}
+	}
+	
+	return this;
+}();

BIN
client/media/cursor_crosshair.png


BIN
client/media/cursor_default.png


BIN
client/media/cursor_move.png


BIN
client/media/cursor_not-allowed.png


BIN
client/media/cursor_pointer.png


BIN
client/media/cursor_text.png


BIN
client/media/download.png


BIN
client/media/fileb_asmm.png


BIN
client/media/fileb_csmm.png


BIN
client/media/fileb_folder.png


BIN
client/media/fileb_m.png


BIN
client/media/fileb_newf.png


BIN
client/media/fileb_patternmm.png


BIN
client/media/fileb_unknown.png


BIN
client/media/gridbg.png


+ 108 - 0
client/media/gridbg.svg

@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="744.09448819"
+   height="1052.3622047"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.0 r9654"
+   sodipodi:docname="gridbg.svg">
+  <defs
+     id="defs4">
+    <inkscape:path-effect
+       effect="skeletal"
+       id="path-effect3777"
+       is_visible="true"
+       pattern="M 0,0 1,0"
+       copytype="single_stretched"
+       prop_scale="1"
+       scale_y_rel="false"
+       spacing="0"
+       normal_offset="0"
+       tang_offset="0"
+       prop_units="false"
+       vertical_pattern="false"
+       fuse_tolerance="0" />
+    <inkscape:path-effect
+       effect="skeletal"
+       id="path-effect3763"
+       is_visible="true"
+       pattern="M 0,0 1,0"
+       copytype="single_stretched"
+       prop_scale="1"
+       scale_y_rel="false"
+       spacing="0"
+       normal_offset="0"
+       tang_offset="0"
+       prop_units="false"
+       vertical_pattern="false"
+       fuse_tolerance="0" />
+    <inkscape:path-effect
+       effect="skeletal"
+       id="path-effect2987"
+       is_visible="true"
+       pattern="M 0,0 1,0"
+       copytype="single_stretched"
+       prop_scale="1"
+       scale_y_rel="false"
+       spacing="0"
+       normal_offset="0"
+       tang_offset="0"
+       prop_units="false"
+       vertical_pattern="false"
+       fuse_tolerance="0" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="37.84"
+     inkscape:cx="13.581225"
+     inkscape:cy="513.24261"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="1918"
+     inkscape:window-height="1150"
+     inkscape:window-x="1"
+     inkscape:window-y="25"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1">
+    <rect
+       style="opacity:0.05;fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       id="rect2991"
+       width="24"
+       height="24"
+       x="1.5812253"
+       y="527.11957"
+       inkscape:export-xdpi="90"
+       inkscape:export-ydpi="90"
+       inkscape:export-filename="/home/raphael/_data_/devoirs/universite/phd/thesis/work/atompm/gridbg.png" />
+  </g>
+</svg>

BIN
client/media/ok.png


BIN
client/media/resize.png


BIN
client/media/resizeH.png


+ 0 - 0
client/media/resizeW.png


Some files were not shown because too many files changed in this diff