geometry_utils.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  1. /* This file is part of AToMPM - A Tool for Multi-Paradigm Modelling
  2. * Copyright 2011 by the AToMPM team and licensed under the LGPL
  3. * See COPYING.lesser and README.md in the root of this project for full details
  4. */
  5. GeometryUtils = function(){
  6. var geometryControlsOverlay = undefined;
  7. var transformationPreviewOverlay = undefined;
  8. /**
  9. * Determines whether or not geometric transformations are allowed. This only
  10. * applies if:
  11. * 1. Geometry controls are hidden
  12. * 2. If an edge is selected, its start and end icons are also selected
  13. */
  14. this.areTransformationsAllowed = function(){
  15. var seen = {};
  16. return (geometryControlsOverlay == undefined ||
  17. geometryControlsOverlay.css("display") == 'none') &&
  18. __selection['items'].every(
  19. function(it)
  20. {
  21. if( it in __edges )
  22. {
  23. var start = __edges[it]['start'],
  24. end = __edges[it]['end'];
  25. if( ! (start in seen) &&
  26. ! utils.contains(__selection['items'],start) )
  27. return false;
  28. if( ! (end in seen) &&
  29. ! utils.contains(__selection['items'],end) )
  30. return false;
  31. seen[start] = seen[end] = 1;
  32. }
  33. return true;
  34. });
  35. };
  36. /**
  37. * Hides the geometry controls overlay
  38. */
  39. this.hideGeometryControlsOverlay = function() {
  40. if( geometryControlsOverlay != undefined )
  41. geometryControlsOverlay.css("display", "none");
  42. __setCanvasScrolling(true);
  43. };
  44. /**
  45. * Hides the transformation preview overlay
  46. */
  47. this.hideTransformationPreviewOverlay = function() {
  48. if( transformationPreviewOverlay != undefined )
  49. {
  50. transformationPreviewOverlay.remove();
  51. transformationPreviewOverlay = undefined;
  52. }
  53. };
  54. /*
  55. NOTE:: _x and _y are used to remember the last 'confirmed' position which we
  56. to compute the relative parameters of calls to translate(..)
  57. NOTE:: the call to toBack() causes whatever is beneath the transformation
  58. preview overlay to become above it, thus becoming detectable by
  59. document.elementFromPoint()... this is used to distinguish between
  60. dropping selections on the canvas and on icons, with the latter
  61. possibly causing insertion */
  62. /**
  63. * Initializes a Raphael rectangle matching the selection bounding box.
  64. */
  65. this.initSelectionTransformationPreviewOverlay = function(x,y)
  66. {
  67. if( transformationPreviewOverlay != undefined )
  68. return;
  69. var bbox = __selection['bbox'];
  70. transformationPreviewOverlay = __bbox2rect(bbox,'transformation_preview');
  71. transformationPreviewOverlay.node.setAttribute('_x0',x);
  72. transformationPreviewOverlay.node.setAttribute('_y0',y);
  73. transformationPreviewOverlay.node.setAttribute('_x',x);
  74. transformationPreviewOverlay.node.setAttribute('_y',y);
  75. transformationPreviewOverlay.node.onmouseup =
  76. function(event)
  77. {
  78. if( event.button == 0 )
  79. transformationPreviewOverlay.toBack();
  80. var beneathTPO = document.elementFromPoint(event.clientX,event.clientY),
  81. _event;
  82. if ( transformationPreviewOverlay.node != beneathTPO &&
  83. beneathTPO != __selection['rect'].node )
  84. {
  85. _event = document.createEvent('MouseEvents');
  86. _event.initMouseEvent(
  87. event.type, event.canBubble, event.cancelable, event.view,
  88. event.detail, event.screenX, event.screenY, event.clientX,
  89. event.clientY, event.ctrlKey, event.altKey, event.shiftKey,
  90. event.metaKey, event.button, event.relatedTarget );
  91. beneathTPO.parentNode.dispatchEvent(_event);
  92. } else {
  93. BehaviorManager.handleUserEvent(__EVENT_LEFT_RELEASE_CANVAS,event);
  94. }
  95. };
  96. };
  97. /**
  98. * Applies the effects of the specified transformation to the preview overlay
  99. */
  100. this.previewSelectionTransformation = function(op,dir) {
  101. if (transformationPreviewOverlay == undefined)
  102. return;
  103. var bbox = __selection['bbox'],
  104. scale = (dir > 0 ? 1.05 : 0.95),
  105. angle = (dir > 0 ? 3 : -3);
  106. if( op == 'resize' )
  107. transformationPreviewOverlay.scale(scale,scale,bbox.x,bbox.y);
  108. else if( op == 'resizeH' )
  109. transformationPreviewOverlay.scale(1,scale,bbox.x,bbox.y);
  110. else if( op == 'resizeW' )
  111. transformationPreviewOverlay.scale(scale,1,bbox.x,bbox.y);
  112. else if( op == 'rotate' )
  113. transformationPreviewOverlay.rotate(angle,bbox.x,bbox.y);
  114. };
  115. /**
  116. * Moves the transformation preview overlay to the specified coordinates
  117. */
  118. this.previewSelectionTranslation = function(x,y) {
  119. if (transformationPreviewOverlay == undefined)
  120. return;
  121. var _x = parseInt(transformationPreviewOverlay.node.getAttribute('_x')),
  122. _y = parseInt(transformationPreviewOverlay.node.getAttribute('_y'));
  123. transformationPreviewOverlay.translate(x-_x,y-_y);
  124. transformationPreviewOverlay.node.setAttribute('_x',x);
  125. transformationPreviewOverlay.node.setAttribute('_y',y);
  126. };
  127. /*
  128. 0. exit on empty icon list
  129. 1. foreach non-link icon,
  130. a. loop back to step 1 if it has no container
  131. b. determine if it's bbox is fully inside, fully outside or intersects
  132. with its container's
  133. i. when fully inside, loop to step 1
  134. ii. when fully outside AND was actually contained (as opposed to
  135. to-be-inserted) AND dragouts are enabled, produce deletion
  136. request for containment link
  137. iii. otherwise, store needed changes to container position and size
  138. to fit icon... we do this (as opposed to producing a request)
  139. to lump together all changes to a given container (each which
  140. may originiate from different icons)
  141. 2. exit on empty request and container changes lists
  142. 3. convert container changes to CS update requests and append to existing
  143. deletion requests, if any
  144. 4. recurse with 'icons' set to any modified containers and 'context' set
  145. to their pending changes (computed in step 1biii) and append returned
  146. requests... the purpose of this step is for container resizing to have
  147. a cascading effect (i.e., a resized container triggers its parent's
  148. resizing if need be)
  149. 5. send batchEdit or return requests
  150. NOTE:: the 'context' parameter contains a list of pending changes computed by
  151. GeometryUtils.transformSelection() but not yet persisted onto the canvas, as well
  152. as a map of pending insertions, if any... this seemingly odd passing
  153. around of pending information is necessary to enable atomicity of icon
  154. transformations, insertions and container resizings */
  155. /**
  156. * Resizes the containers of icons (specified as uri array) that have moved within
  157. * them as required and uninsert dragged-out icons.
  158. */
  159. this.resizeContainers = function(icons,context,dryRun,disabledDragouts,reqs) {
  160. if( icons.length == 0 )
  161. return (dryRun ? [] : undefined);
  162. if( reqs == undefined )
  163. reqs = [];
  164. var requests = [],
  165. containers2changes = {},
  166. resizeContainer =
  167. function(c,clink,it)
  168. {
  169. var cbbox = __getBBox(
  170. c,utils.mergeDicts([context,containers2changes]) ),
  171. itbbox = __getBBox(it,context);
  172. if( __isBBoxInside(itbbox, cbbox) )
  173. return;
  174. else if( __isBBoxDisjoint(itbbox, cbbox) &&
  175. clink &&
  176. ! disabledDragouts )
  177. requests.push(
  178. {'method':'DELETE',
  179. 'uri':HttpUtils.url(clink,__NO_USERNAME+__NO_WID)});
  180. else
  181. {
  182. containers2changes[c] =
  183. containers2changes[c] ||
  184. utils.mergeDicts(
  185. [{'position':
  186. [parseFloat(__getIcon(c).getAttr('__x')),
  187. parseFloat(__getIcon(c).getAttr('__y'))],
  188. 'scale':
  189. [parseFloat(__getIcon(c).getAttr('__sx')),
  190. parseFloat(__getIcon(c).getAttr('__sy'))]},
  191. context[c]]);
  192. var padding = 20,
  193. overflow =
  194. {'right': (itbbox.x + itbbox.width) -
  195. (cbbox.x + cbbox.width) + padding,
  196. 'left': cbbox.x - itbbox.x + padding,
  197. 'top': cbbox.y - itbbox.y + padding,
  198. 'bottom': (itbbox.y + itbbox.height) -
  199. (cbbox.y + cbbox.height) + padding};
  200. if( overflow.left > 0 )
  201. {
  202. containers2changes[c]['position'][0] -= overflow.left;
  203. containers2changes[c]['scale'][0] *=
  204. (cbbox.width+overflow.left)/cbbox.width;
  205. cbbox.width *= containers2changes[c]['scale'][0];
  206. }
  207. if( overflow.right > 0 )
  208. containers2changes[c]['scale'][0] *=
  209. (cbbox.width+overflow.right)/cbbox.width;
  210. if( overflow.top > 0 )
  211. {
  212. containers2changes[c]['position'][1] -= overflow.top;
  213. containers2changes[c]['scale'][1] *=
  214. (cbbox.height+overflow.top)/cbbox.height;
  215. cbbox.height *= containers2changes[c]['scale'][1];
  216. }
  217. if( overflow.bottom > 0 )
  218. containers2changes[c]['scale'][1] *=
  219. (cbbox.height+overflow.bottom)/cbbox.height;
  220. }
  221. };
  222. icons.forEach(
  223. function(it)
  224. {
  225. if( !(it in __icons) || __isConnectionType(it) )
  226. return;
  227. __icons[it]['edgesIn'].forEach(
  228. function(edgeId)
  229. {
  230. var linkIn = __edgeId2ends(edgeId)[0];
  231. if( __isContainmentConnectionType(linkIn) ) {
  232. if ( reqs.map(function(_node) {return _node['uri'];}).indexOf(__edgeId2ends(__icons[linkIn]['edgesIn'][0])[0] + '.cs') < 0 ) {
  233. resizeContainer(
  234. __edgeId2ends(__icons[linkIn]['edgesIn'][0])[0],
  235. linkIn,
  236. it);
  237. }
  238. }
  239. });
  240. if( context.toBeInserted && it in context.toBeInserted )
  241. resizeContainer(context.toBeInserted[it],undefined,it);
  242. });
  243. if( utils.keys(containers2changes).length == 0 && requests.length == 0 )
  244. return (dryRun ? [] : undefined);
  245. for( var uri in containers2changes )
  246. requests.push(
  247. {'method':'PUT',
  248. 'uri':HttpUtils.url(uri+'.cs',__NO_USERNAME+__NO_WID),
  249. 'reqData':{'changes':containers2changes[uri]}});
  250. for (var req_id in requests) {
  251. var to_concat = utils.flatten(GeometryUtils.resizeContainers(
  252. utils.keys(containers2changes),
  253. containers2changes,
  254. true,
  255. false,
  256. requests)
  257. );
  258. requests = requests.concat(to_concat);
  259. }
  260. if( dryRun )
  261. return requests;
  262. else
  263. HttpUtils.httpReq(
  264. 'POST',
  265. HttpUtils.url('/batchEdit',__NO_USERNAME),
  266. requests);
  267. };
  268. /**
  269. * Shows the geometry controls overlay (positioning is based on the bounding box
  270. * of the current selection) and initializes the transformation preview overlay
  271. */
  272. this.showGeometryControlsOverlay = function() {
  273. var bbox = __selection['bbox'];
  274. if( geometryControlsOverlay == undefined )
  275. {
  276. geometryControlsOverlay = $('#div_geom_ctrls');
  277. ['resize','resizeH','resizeW','rotate'].forEach(
  278. function(x)
  279. {
  280. var img = $('<img>');
  281. img.attr('class', 'geometry_ctrl');
  282. img.attr('src', 'client/media/'+x+'.png');
  283. img.get(0).onmousewheel =
  284. function(event)
  285. {
  286. var dir = event.wheelDelta;
  287. GeometryUtils.previewSelectionTransformation(x,dir);
  288. return false;
  289. };
  290. geometryControlsOverlay.append(img);
  291. });
  292. var img = $('<img>');
  293. img.attr('class', 'geometry_ctrl');
  294. img.attr('src', 'client/media/ok.png');
  295. img.click(function(event) {GeometryUtils.transformSelection(__GEOM_TRANSF);});
  296. geometryControlsOverlay.append(img);
  297. }
  298. geometryControlsOverlay.css("top",
  299. bbox.y + bbox.height - $("#div_container").scrollTop() + "px"),
  300. geometryControlsOverlay.css("left",
  301. bbox.x + bbox.width/2 - __GEOM_CTRLS_WIDTH/2.0 - $("#div_container").scrollLeft() + "px");
  302. geometryControlsOverlay.css("display", "inline");
  303. GeometryUtils.initSelectionTransformationPreviewOverlay();
  304. __setCanvasScrolling(false);
  305. };
  306. /**
  307. * Snaps the top-left corner of the selection bounding box to the nearest
  308. * grid point
  309. */
  310. this.snapSelectionToGrid = function() {
  311. var bbox = __selection['bbox'],
  312. dx = bbox.x % __GRID_CELL_SIZE,
  313. dy = bbox.y % __GRID_CELL_SIZE;
  314. if( dx == 0 && dy == 0 )
  315. return;
  316. GeometryUtils.initSelectionTransformationPreviewOverlay(bbox.x,bbox.y);
  317. GeometryUtils.previewSelectionTranslation(
  318. bbox.x + (dx < __GRID_CELL_SIZE/2 ? -dx : __GRID_CELL_SIZE-dx),
  319. bbox.y + (dy < __GRID_CELL_SIZE/2 ? -dy : __GRID_CELL_SIZE-dy));
  320. GeometryUtils.transformSelection(__GEOM_TRANSF);
  321. };
  322. /* applies the transformation currently applied to the preview overlay to the
  323. selected icon(s)/edge(s) and removes the geometry controls and transformation
  324. preview overlays... if 'insertInfo' is specified, also inserts selection into
  325. it (see NOTE about why this is done from here)... this function doesn't
  326. actually transform the icons, it merely requests the update of the icon(s)'s
  327. 'transformation' and/or the link(s)'s $segments attributes on the csworker
  328. (i.e., a changelog triggers the actual transformation)
  329. 1. extract transformation and build up changes in 'uri2changes'
  330. 2. add $segments changes to 'uris2changes'
  331. 3. retrieve and compute all necessary requests
  332. a. retrieve insertion requests (+ provide DataUtils.insert() with data needed
  333. to compute bboxes of to-be-transformed icons, i.e., 'uris2changes')
  334. b. convert 'uri2changes' to icon transformation requests
  335. c. retrieve container resizing requests (+ provide GeometryUtils.resizeContainers()
  336. with 'uris2changes', a list of pending insertions from step 3a, and
  337. possibly a dragout prohibition)
  338. 4. send batchEdit with requests from step 3... note that requests from
  339. step 3a. are inserted last s.t. the event-flow is 1-something moved
  340. followed by 2-something inserted... this ordering is needed to ensure
  341. mappers and parsers are evaluated in a sensible order
  342. the following describes the algorithm for getting edge ends to follow their
  343. icons when these are transformed:
  344. 1. for each outgoing edge,
  345. z) do nothing if the edge's Link is in __selection
  346. a) fetch the edge's source xy
  347. b) apply transformation T on it to produce xy'
  348. c) 'move' the edge source and possibly its first control point (when
  349. they are colocated) to xy'... in reality, save the desired motion in
  350. connectedEdgesChanges
  351. 2. for each outgoing edge, apply similar logic but to edge's end and last
  352. control point
  353. NOTE:: to avoid race conditions between updates to different edges within a
  354. single Link's $segments, relevant changes are accumulated in
  355. connectedEdgesChanges s.t. those pertaining to the same Link end up
  356. bundled in a single update request
  357. NOTE:: to avoid race conditions between updates to $segments resulting from
  358. edge ends following connected icon and updates resulting from edges
  359. themselves being transformed (i.e., when they are within __selection),
  360. the former are ignored when we know the latter will be carried out
  361. NOTE:: because SVG transformations are always relative to the global (0,0),
  362. non-translate transformations still technically translate things...
  363. Raphael allows specifiying different origins for transformations...
  364. default SVG scale x2 :
  365. Rect(10,10,200,100) > Rect(20,20,400,200)
  366. Raphael scale with scale origin set to (10,10)
  367. Rect(10,10,200,100) > Rect(10,10,200,100)
  368. in the above example, Raphael's transformation matrix will report the
  369. translation from (20,20) back to (10,10) even though from my
  370. perspective, the figure hasn't moved and has only been scaled... to
  371. account for this, when decomposing the said matrix, we ignore tx,ty
  372. when r|sx|sy aren't 0|1|1 and vice-versa... this doesn't cause any
  373. problems because the client interface doesn't support scaling/rotating
  374. *and* translating without an intermediate call to this function...
  375. NOTE:: essentially, the above-explained ignored rotation/scaling translation
  376. components apply to the top-left corner of the selection bbox (i.e.,
  377. it's Raphael ensuring that the said corner does not move as a result
  378. of rotations/scalings 'centered' on it)... however, similar rotation/
  379. scaling translation components apply to contents of the selection...
  380. this is because the said contents are changing wrt. the top-left
  381. corner of the selection, not wrt. their own (x,y)... ignoring these
  382. 'internal' translation components would cause altering a selection to
  383. act like altering each selected item individually... long story short,
  384. we can not and do not ignore them... below is the algorithm we use to
  385. compute the internal translation components:
  386. 1. foreach selected icon
  387. [do nothing if no rotation or and no scaling]
  388. a) compute offset between icon's x,y and selection's top-left corner
  389. b) apply extracted (from transformation matrix) rotation and scale
  390. to a point whose coordinates are the x and y offsets from step a)
  391. c) determine translation from point from step a) to transformed
  392. point from step b)
  393. d) the icon's transformation is now the extracted rotation and
  394. scaling *and* the translation from step c)
  395. NOTE:: since the selection transformation should be an atomic operation,
  396. changes are accumulated in 'uris2changes' and are only actually sent
  397. to the csworker at the very end of this function... also, since
  398. insertions and container resizings and the selection transformations
  399. that triggered them should be atomic too, requests pertaining to the 2
  400. former tasks are computed and bundled with those that effect the
  401. latter... the results of this form the emitted batchEdit */
  402. this.transformSelection = function(callingContext,insertInfo) {
  403. if (transformationPreviewOverlay == undefined)
  404. return;
  405. var T = transformationPreviewOverlay.node.getAttribute('transform');
  406. if( T == null || T == 'matrix(1,0,0,1,0,0)' )
  407. {
  408. GeometryUtils.hideGeometryControlsOverlay();
  409. GeometryUtils.hideTransformationPreviewOverlay();
  410. return;
  411. }
  412. /** 1 **/
  413. var _T = __decomposeTransformationMatrix(T),
  414. connectedEdgesChanges = {},
  415. uris2changes = {};
  416. __selection['items'].forEach(
  417. function(it)
  418. {
  419. if( it in __icons )
  420. {
  421. var icon = __icons[it]['icon'],
  422. changes = {};
  423. if( _T.r == 0 &&
  424. Math.abs(1-_T.sx) <= 0.001 &&
  425. Math.abs(1-_T.sy) <= 0.001 )
  426. {
  427. /* translation only */
  428. if( _T.tx != 0 || _T.ty != 0 )
  429. changes['position'] =
  430. [_T.tx + parseFloat(icon.getAttr('__x')),
  431. _T.ty + parseFloat(icon.getAttr('__y'))];
  432. }
  433. else
  434. {
  435. /* rotation/scale only */
  436. var offset = [icon.getAttr('__x') - __selection['bbox'].x,
  437. icon.getAttr('__y') - __selection['bbox'].y],
  438. rsOffset = GeometryUtils.transformPoint(
  439. offset[0],
  440. offset[1],
  441. 'rotate('+_T.r+') scale('+_T.sx+','+_T.sy+')'),
  442. offsetTx = rsOffset[0] - offset[0],
  443. offsetTy = rsOffset[1] - offset[1];
  444. if( _T.r != 0 )
  445. changes['orientation'] =
  446. (parseFloat(icon.getAttr('__r')) + _T.r) % 360;
  447. if( Math.abs(1-_T.sx) > 0.001 || Math.abs(1-_T.sy) > 0.001 )
  448. changes['scale'] =
  449. [_T.sx * parseFloat(icon.getAttr('__sx')),
  450. _T.sy * parseFloat(icon.getAttr('__sy'))];
  451. if( offsetTx != 0 || offsetTy != 0 )
  452. changes['position'] =
  453. [offsetTx + parseFloat(icon.getAttr('__x')),
  454. offsetTy + parseFloat(icon.getAttr('__y'))];
  455. }
  456. uris2changes[it] = changes;
  457. if( ! __isConnectionType(it) )
  458. {
  459. /* have edge ends out follow */
  460. __icons[it]['edgesOut'].forEach(
  461. function(edgeId)
  462. {
  463. var linkuri = __edgeId2linkuri(edgeId);
  464. if( __isSelected(linkuri) )
  465. return;
  466. var segments = __edges[edgeId]['segments'],
  467. points = segments.match(/([\d\.]*,[\d\.]*)/g),
  468. xy = utils.head(points).split(','),
  469. newXY = GeometryUtils.transformPoint(xy[0],xy[1],T);
  470. connectedEdgesChanges[linkuri] =
  471. (connectedEdgesChanges[linkuri] || {});
  472. points.splice(0,1,newXY.join(','));
  473. connectedEdgesChanges[linkuri][edgeId] =
  474. 'M'+points.join('L');
  475. });
  476. /* have edge ends in follow */
  477. __icons[it]['edgesIn'].forEach(
  478. function(edgeId)
  479. {
  480. var linkuri = __edgeId2linkuri(edgeId);
  481. if( __isSelected(linkuri) )
  482. return;
  483. var segments = __edges[edgeId]['segments'],
  484. points = segments.match(/([\d\.]*,[\d\.]*)/g),
  485. xy = utils.tail(points).split(','),
  486. newXY = GeometryUtils.transformPoint(xy[0],xy[1],T);
  487. connectedEdgesChanges[linkuri] =
  488. (connectedEdgesChanges[linkuri] || {});
  489. points.splice(points.length-1,1,newXY.join(','));
  490. connectedEdgesChanges[linkuri][edgeId] =
  491. 'M'+points.join('L');
  492. });
  493. }
  494. else
  495. {
  496. /* transform entire edges */
  497. var __segments = __linkuri2segments(it),
  498. changes = {};
  499. for( var edgeId in __segments )
  500. {
  501. var segments = __segments[edgeId],
  502. points = segments.match(/([\d\.]*,[\d\.]*)/g),
  503. newPoints = points.map(
  504. function(p)
  505. {
  506. p = p.split(',');
  507. return GeometryUtils.transformPoint(p[0],p[1],T);
  508. });
  509. changes[edgeId] = 'M'+newPoints.join('L');
  510. }
  511. uris2changes[it]['$segments'] = changes;
  512. }
  513. }
  514. });
  515. /** 2 **/
  516. if( utils.keys(connectedEdgesChanges).length > 0 )
  517. for( var linkuri in connectedEdgesChanges )
  518. {
  519. if( !(linkuri in uris2changes) )
  520. uris2changes[linkuri] = {};
  521. if( !('$segments' in uris2changes[linkuri]) )
  522. uris2changes[linkuri]['$segments'] = __linkuri2segments(linkuri);
  523. uris2changes[linkuri]['$segments'] =
  524. utils.mergeDicts([
  525. uris2changes[linkuri]['$segments'],
  526. connectedEdgesChanges[linkuri]]);
  527. }
  528. /** 3-4 **/
  529. if( utils.keys(uris2changes).length > 0 )
  530. {
  531. var csRequests = [],
  532. insertRequests = [];
  533. if( insertInfo )
  534. {
  535. insertRequests = DataUtils.insert(
  536. insertInfo['dropTarget'].getAttribute('__csuri'),
  537. __selection['items'],
  538. insertInfo['connectionType'],
  539. uris2changes,
  540. true);
  541. var toBeInserted = {};
  542. insertRequests.forEach(
  543. function(r)
  544. {
  545. if ('reqData' in r)
  546. toBeInserted[r['reqData']['dest']] = r['reqData']['src'];
  547. });
  548. }
  549. for( var uri in uris2changes )
  550. if( utils.keys(uris2changes[uri]).length > 0 )
  551. csRequests.push(
  552. {'method':'PUT',
  553. 'uri':HttpUtils.url(uri+'.cs',__NO_USERNAME+__NO_WID),
  554. 'reqData':{'changes':uris2changes[uri]}});
  555. HttpUtils.httpReq(
  556. 'POST',
  557. HttpUtils.url('/batchEdit',__NO_USERNAME),
  558. csRequests.concat(
  559. GeometryUtils.resizeContainers(
  560. __selection['items'],
  561. utils.mergeDicts(
  562. [uris2changes, {'toBeInserted':toBeInserted}]),
  563. true,
  564. (callingContext == __GEOM_TRANSF)),
  565. insertRequests));
  566. }
  567. };
  568. /**
  569. * Apply the specified transformation to the given point and return
  570. * the resulting point
  571. */
  572. this.transformPoint = function(x,y,T) {
  573. var pt = __canvas.group();
  574. pt.push( __canvas.point(x,y) );
  575. pt.node.setAttribute('transform',T);
  576. var bbox = pt.getBBox();
  577. pt.remove();
  578. return [bbox.x+bbox.width/2,bbox.y+bbox.height/2];
  579. };
  580. return this;
  581. }();