电子档案
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1507 lines
68 KiB

  1. /* Licensed under the Apache License, Version 2.0 (the "License");
  2. * you may not use this file except in compliance with the License.
  3. * You may obtain a copy of the License at
  4. *
  5. * http://www.apache.org/licenses/LICENSE-2.0
  6. *
  7. * Unless required by applicable law or agreed to in writing, software
  8. * distributed under the License is distributed on an "AS IS" BASIS,
  9. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. * See the License for the specific language governing permissions and
  11. * limitations under the License.
  12. */
  13. 'use strict';
  14. angular.module('flowableModeler')
  15. .controller('StencilController', ['$rootScope', '$scope', '$http', '$modal', '$timeout', '$window', 'editorManager',
  16. function ($rootScope, $scope, $http, $modal, $timeout, $window, editorManager) {
  17. // Property window toggle state
  18. $scope.propertyWindowState = {'collapsed': false};
  19. // Add reference to global header-config
  20. $scope.headerConfig = FLOWABLE.HEADER_CONFIG;
  21. $scope.propertyWindowState.toggle = function () {
  22. $scope.propertyWindowState.collapsed = !$scope.propertyWindowState.collapsed;
  23. $timeout(function () {
  24. $window.dispatchEvent(new Event("resize"));
  25. }, 100);
  26. };
  27. // Code that is dependent on an initialised Editor is wrapped in a promise for the editor
  28. $scope.editorFactory.promise.then(function () {
  29. /* Build stencil item list */
  30. // Build simple json representation of stencil set
  31. var stencilItemGroups = [];
  32. // Helper method: find a group in an array
  33. var findGroup = function (name, groupArray) {
  34. for (var index = 0; index < groupArray.length; index++) {
  35. if (groupArray[index].name === name) {
  36. return groupArray[index];
  37. }
  38. }
  39. return null;
  40. };
  41. // Helper method: add a new group to an array of groups
  42. var addGroup = function (groupName, groupArray) {
  43. var group = { name: groupName, items: [], paletteItems: [], groups: [], visible: true };
  44. groupArray.push(group);
  45. return group;
  46. };
  47. /*
  48. StencilSet items
  49. */
  50. var data = editorManager.getStencilData();
  51. var quickMenuDefinition = undefined;
  52. var ignoreForPaletteDefinition = undefined;
  53. if (data.namespace == 'http://b3mn.org/stencilset/cmmn1.1#') {
  54. quickMenuDefinition = ['HumanTask', 'Association'];
  55. ignoreForPaletteDefinition = ['CasePlanModel'];
  56. } else if (data.namespace == 'http://b3mn.org/stencilset/dmn1.2#') {
  57. quickMenuDefinition = ['DecisionTableDecision', 'InformationRequirement', 'KnowledgeRequirement'];
  58. ignoreForPaletteDefinition = [];
  59. } else {
  60. quickMenuDefinition = ['UserTask', 'EndNoneEvent', 'ExclusiveGateway',
  61. 'CatchTimerEvent', 'ThrowNoneEvent', 'TextAnnotation',
  62. 'SequenceFlow', 'Association'];
  63. ignoreForPaletteDefinition = ['SequenceFlow', 'MessageFlow', 'Association', 'DataAssociation', 'DataStore', 'SendTask'];
  64. }
  65. var quickMenuItems = [];
  66. var morphRoles = [];
  67. for (var i = 0; i < data.rules.morphingRules.length; i++)
  68. {
  69. var role = data.rules.morphingRules[i].role;
  70. var roleItem = {'role': role, 'morphOptions': []};
  71. morphRoles.push(roleItem);
  72. }
  73. // Check all received items
  74. for (var stencilIndex = 0; stencilIndex < data.stencils.length; stencilIndex++) {
  75. // Check if the root group is the 'diagram' group. If so, this item should not be shown.
  76. var currentGroupName = data.stencils[stencilIndex].groups[0];
  77. if (currentGroupName === 'Diagram' || currentGroupName === 'BPMN.STENCILS.GROUPS.DIAGRAM' ||
  78. currentGroupName === 'CMMN.STENCILS.GROUPS.DIAGRAM' ||
  79. currentGroupName === 'DMN.STENCILS.GROUPS.DIAGRAM') {
  80. continue; // go to next item
  81. }
  82. var removed = false;
  83. if (data.stencils[stencilIndex].removed) {
  84. removed = true;
  85. }
  86. var currentGroup = undefined;
  87. if (!removed) {
  88. // Check if this group already exists. If not, we create a new one
  89. if (currentGroupName !== null && currentGroupName !== undefined && currentGroupName.length > 0) {
  90. currentGroup = findGroup(currentGroupName, stencilItemGroups); // Find group in root groups array
  91. if (currentGroup === null) {
  92. currentGroup = addGroup(currentGroupName, stencilItemGroups);
  93. }
  94. // Add all child groups (if any)
  95. for (var groupIndex = 1; groupIndex < data.stencils[stencilIndex].groups.length; groupIndex++) {
  96. var childGroupName = data.stencils[stencilIndex].groups[groupIndex];
  97. var childGroup = findGroup(childGroupName, currentGroup.groups);
  98. if (childGroup === null) {
  99. childGroup = addGroup(childGroupName, currentGroup.groups);
  100. }
  101. // The current group variable holds the parent of the next group (if any),
  102. // and is basically the last element in the array of groups defined in the stencil item
  103. currentGroup = childGroup;
  104. }
  105. }
  106. }
  107. // Construct the stencil item
  108. var stencilItem = {'id': data.stencils[stencilIndex].id,
  109. 'name': data.stencils[stencilIndex].title,
  110. 'description': data.stencils[stencilIndex].description,
  111. 'icon': data.stencils[stencilIndex].icon,
  112. 'type': data.stencils[stencilIndex].type,
  113. 'roles': data.stencils[stencilIndex].roles,
  114. 'removed': removed,
  115. 'customIcon': false,
  116. 'canConnect': false,
  117. 'canConnectTo': false,
  118. 'canConnectAssociation': false};
  119. if (data.stencils[stencilIndex].customIconId && data.stencils[stencilIndex].customIconId > 0) {
  120. stencilItem.customIcon = true;
  121. stencilItem.icon = data.stencils[stencilIndex].customIconId;
  122. }
  123. if (!removed) {
  124. if (quickMenuDefinition.indexOf(stencilItem.id) >= 0) {
  125. quickMenuItems[quickMenuDefinition.indexOf(stencilItem.id)] = stencilItem;
  126. }
  127. }
  128. if (stencilItem.id === 'TextAnnotation' || stencilItem.id === 'BoundaryCompensationEvent') {
  129. stencilItem.canConnectAssociation = true;
  130. }
  131. for (var i = 0; i < data.stencils[stencilIndex].roles.length; i++) {
  132. var stencilRole = data.stencils[stencilIndex].roles[i];
  133. if (data.namespace == 'http://b3mn.org/stencilset/cmmn1.1#') {
  134. if (stencilRole === 'association_start') {
  135. stencilItem.canConnect = true;
  136. } else if (stencilRole === 'association_end') {
  137. stencilItem.canConnectTo = true;
  138. }
  139. } else if (data.namespace == 'http://b3mn.org/stencilset/dmn1.2#') {
  140. if (stencilRole === 'information_requirement_start') {
  141. stencilItem.canConnect = true;
  142. } else if (stencilRole === 'information_requirement_end') {
  143. stencilItem.canConnectTo = true;
  144. }
  145. } else {
  146. if (stencilRole === 'sequence_start') {
  147. stencilItem.canConnect = true;
  148. } else if (stencilRole === 'sequence_end') {
  149. stencilItem.canConnectTo = true;
  150. }
  151. }
  152. for (var j = 0; j < morphRoles.length; j++) {
  153. if (stencilRole === morphRoles[j].role) {
  154. if (!removed) {
  155. morphRoles[j].morphOptions.push(stencilItem);
  156. }
  157. stencilItem.morphRole = morphRoles[j].role;
  158. break;
  159. }
  160. }
  161. }
  162. if (currentGroup) {
  163. // Add the stencil item to the correct group
  164. currentGroup.items.push(stencilItem);
  165. if (ignoreForPaletteDefinition.indexOf(stencilItem.id) < 0) {
  166. currentGroup.paletteItems.push(stencilItem);
  167. }
  168. } else {
  169. // It's a root stencil element
  170. if (!removed) {
  171. stencilItemGroups.push(stencilItem);
  172. }
  173. }
  174. }
  175. for (var i = 0; i < stencilItemGroups.length; i++) {
  176. if (stencilItemGroups[i].paletteItems && stencilItemGroups[i].paletteItems.length == 0) {
  177. stencilItemGroups[i].visible = false;
  178. }
  179. }
  180. $scope.stencilItemGroups = stencilItemGroups;
  181. var containmentRules = [];
  182. for (var i = 0; i < data.rules.containmentRules.length; i++) {
  183. var rule = data.rules.containmentRules[i];
  184. containmentRules.push(rule);
  185. }
  186. $scope.containmentRules = containmentRules;
  187. // remove quick menu items which are not available anymore due to custom pallette
  188. var availableQuickMenuItems = [];
  189. for (var i = 0; i < quickMenuItems.length; i++) {
  190. if (quickMenuItems[i]) {
  191. availableQuickMenuItems[availableQuickMenuItems.length] = quickMenuItems[i];
  192. }
  193. }
  194. $scope.quickMenuItems = availableQuickMenuItems;
  195. $scope.morphRoles = morphRoles;
  196. /*
  197. * Listen to selection change events: show properties
  198. */
  199. editorManager.registerOnEvent(ORYX.CONFIG.EVENT_SELECTION_CHANGED, function (event) {
  200. var shapes = event.elements;
  201. var canvasSelected = false;
  202. if (shapes && shapes.length == 0) {
  203. shapes = [editorManager.getCanvas()];
  204. canvasSelected = true;
  205. }
  206. if (shapes && shapes.length > 0) {
  207. var selectedShape = shapes.first();
  208. var stencil = selectedShape.getStencil();
  209. if ($rootScope.selectedElementBeforeScrolling && stencil.id().indexOf('BPMNDiagram') !== -1 && stencil.id().indexOf('CMMNDiagram') !== -1 &&
  210. stencil.id().indexOf('DMNDiagram') !== -1) {
  211. // ignore canvas event because of empty selection when scrolling stops
  212. return;
  213. }
  214. if ($rootScope.selectedElementBeforeScrolling && $rootScope.selectedElementBeforeScrolling.getId() === selectedShape.getId()) {
  215. $rootScope.selectedElementBeforeScrolling = null;
  216. return;
  217. }
  218. // Store previous selection
  219. $scope.previousSelectedShape = $scope.selectedShape;
  220. // Only do something if another element is selected (Oryx fires this event multiple times)
  221. if ($scope.selectedShape !== undefined && $scope.selectedShape.getId() === selectedShape.getId()) {
  222. if ($rootScope.forceSelectionRefresh) {
  223. // Switch the flag again, this run will force refresh
  224. $rootScope.forceSelectionRefresh = false;
  225. } else {
  226. // Selected the same element again, no need to update anything
  227. return;
  228. }
  229. }
  230. var selectedItem = {'title': '', 'properties': []};
  231. if (canvasSelected) {
  232. selectedItem.auditData = {
  233. 'author': $scope.modelData.createdByUser,
  234. 'createDate': $scope.modelData.createDate
  235. };
  236. }
  237. // Gather properties of selected item
  238. var properties = stencil.properties();
  239. for (var i = 0; i < properties.length; i++) {
  240. var property = properties[i];
  241. if (property.popular() == false) continue;
  242. var key = property.prefix() + "-" + property.id();
  243. if (key === 'oryx-name') {
  244. selectedItem.title = selectedShape.properties.get(key);
  245. }
  246. // First we check if there is a config for 'key-type' and then for 'type' alone
  247. var propertyConfig = FLOWABLE.PROPERTY_CONFIG[key + '-' + property.type()];
  248. if (propertyConfig === undefined || propertyConfig === null) {
  249. propertyConfig = FLOWABLE.PROPERTY_CONFIG[property.type()];
  250. }
  251. if (propertyConfig === undefined || propertyConfig === null) {
  252. console.log('WARNING: no property configuration defined for ' + key + ' of type ' + property.type());
  253. } else {
  254. if (selectedShape.properties.get(key) === 'true') {
  255. selectedShape.properties.set(key, true);
  256. }
  257. if (FLOWABLE.UI_CONFIG.showRemovedProperties == false && property.isHidden())
  258. {
  259. continue;
  260. }
  261. var currentProperty = {
  262. 'key': key,
  263. 'title': property.title(),
  264. 'description': property.description(),
  265. 'type': property.type(),
  266. 'mode': 'read',
  267. 'readonly': property.readonly(),
  268. 'hidden': property.isHidden(),
  269. 'value': selectedShape.properties.get(key)
  270. };
  271. if ((currentProperty.type === 'complex' || currentProperty.type === 'multiplecomplex') && currentProperty.value && currentProperty.value.length > 0) {
  272. try {
  273. currentProperty.value = JSON.parse(currentProperty.value);
  274. } catch (err) {
  275. // ignore
  276. }
  277. }
  278. if (propertyConfig.readModeTemplateUrl !== undefined && propertyConfig.readModeTemplateUrl !== null) {
  279. currentProperty.readModeTemplateUrl = propertyConfig.readModeTemplateUrl + '?version=' + $rootScope.staticIncludeVersion;
  280. }
  281. if (propertyConfig.writeModeTemplateUrl !== null && propertyConfig.writeModeTemplateUrl !== null) {
  282. currentProperty.writeModeTemplateUrl = propertyConfig.writeModeTemplateUrl + '?version=' + $rootScope.staticIncludeVersion;
  283. }
  284. if ((currentProperty.readonly && propertyConfig.templateUrl !== undefined && propertyConfig.templateUrl !== null) ||
  285. (currentProperty.readonly === undefined && propertyConfig.templateUrl !== undefined && propertyConfig.templateUrl !== null)) {
  286. currentProperty.templateUrl = propertyConfig.templateUrl + '?version=' + $rootScope.staticIncludeVersion;
  287. currentProperty.hasReadWriteMode = false;
  288. }
  289. else {
  290. currentProperty.hasReadWriteMode = true;
  291. }
  292. if (currentProperty.value === undefined
  293. || currentProperty.value === null
  294. || currentProperty.value.length == 0) {
  295. currentProperty.noValue = true;
  296. }
  297. selectedItem.properties.push(currentProperty);
  298. }
  299. }
  300. // Need to wrap this in an $apply block, see http://jimhoskins.com/2012/12/17/angularjs-and-apply.html
  301. $scope.safeApply(function () {
  302. $scope.selectedItem = selectedItem;
  303. $scope.selectedShape = selectedShape;
  304. });
  305. } else {
  306. $scope.safeApply(function () {
  307. $scope.selectedItem = {};
  308. $scope.selectedShape = null;
  309. });
  310. }
  311. });
  312. editorManager.registerOnEvent(ORYX.CONFIG.EVENT_SELECTION_CHANGED, function (event) {
  313. FLOWABLE.eventBus.dispatch(FLOWABLE.eventBus.EVENT_TYPE_HIDE_SHAPE_BUTTONS);
  314. var shapes = event.elements;
  315. if (shapes && shapes.length == 1) {
  316. var selectedShape = shapes.first();
  317. var a = editorManager.getCanvas().node.getScreenCTM();
  318. var absoluteXY = selectedShape.absoluteXY();
  319. absoluteXY.x *= a.a;
  320. absoluteXY.y *= a.d;
  321. var additionalIEZoom = 1;
  322. if (!isNaN(screen.logicalXDPI) && !isNaN(screen.systemXDPI)) {
  323. var ua = navigator.userAgent;
  324. if (ua.indexOf('MSIE') >= 0) {
  325. //IE 10 and below
  326. var zoom = Math.round((screen.deviceXDPI / screen.logicalXDPI) * 100);
  327. if (zoom !== 100) {
  328. additionalIEZoom = zoom / 100
  329. }
  330. }
  331. }
  332. if (additionalIEZoom === 1) {
  333. absoluteXY.y = absoluteXY.y - jQuery("#canvasSection").offset().top + 5;
  334. absoluteXY.x = absoluteXY.x - jQuery("#canvasSection").offset().left;
  335. } else {
  336. var canvasOffsetLeft = jQuery("#canvasSection").offset().left;
  337. var canvasScrollLeft = jQuery("#canvasSection").scrollLeft();
  338. var canvasScrollTop = jQuery("#canvasSection").scrollTop();
  339. var offset = a.e - (canvasOffsetLeft * additionalIEZoom);
  340. var additionaloffset = 0;
  341. if (offset > 10) {
  342. additionaloffset = (offset / additionalIEZoom) - offset;
  343. }
  344. absoluteXY.y = absoluteXY.y - (jQuery("#canvasSection").offset().top * additionalIEZoom) + 5 + ((canvasScrollTop * additionalIEZoom) - canvasScrollTop);
  345. absoluteXY.x = absoluteXY.x - (canvasOffsetLeft * additionalIEZoom) + additionaloffset + ((canvasScrollLeft * additionalIEZoom) - canvasScrollLeft);
  346. }
  347. var bounds = new ORYX.Core.Bounds(a.e + absoluteXY.x, a.f + absoluteXY.y, a.e + absoluteXY.x + a.a*selectedShape.bounds.width(), a.f + absoluteXY.y + a.d*selectedShape.bounds.height());
  348. var shapeXY = bounds.upperLeft();
  349. var stencilItem = $scope.getStencilItemById(selectedShape.getStencil().idWithoutNs());
  350. var morphShapes = [];
  351. if (stencilItem && stencilItem.morphRole) {
  352. for (var i = 0; i < $scope.morphRoles.length; i++) {
  353. if ($scope.morphRoles[i].role === stencilItem.morphRole) {
  354. morphShapes = $scope.morphRoles[i].morphOptions;
  355. }
  356. }
  357. }
  358. var x = shapeXY.x;
  359. if (bounds.width() < 48) {
  360. x -= 24;
  361. }
  362. if (morphShapes && morphShapes.length > 0) {
  363. // In case the element is not wide enough, start the 2 bottom-buttons more to the left
  364. // to prevent overflow in the right-menu
  365. var morphButton = document.getElementById('morph-button');
  366. morphButton.style.display = "block";
  367. morphButton.style.left = x + 24 +'px';
  368. morphButton.style.top = (shapeXY.y+bounds.height() + 2) + 'px';
  369. }
  370. var deleteButton = document.getElementById('delete-button');
  371. deleteButton.style.display = "block";
  372. deleteButton.style.left = x + 'px';
  373. deleteButton.style.top = (shapeXY.y+bounds.height() + 2) + 'px';
  374. var editable = selectedShape._stencil._jsonStencil.id.endsWith('CollapsedSubProcess') ;
  375. var editButton = document.getElementById('edit-button');
  376. if (editable) {
  377. editButton.style.display = "block";
  378. if (morphShapes && morphShapes.length > 0) {
  379. editButton.style.left = x + 24 + 24 + 'px';
  380. } else {
  381. editButton.style.left = x + 24 +'px';
  382. }
  383. editButton.style.top = (shapeXY.y+bounds.height() + 2) + 'px';
  384. } else {
  385. editButton.style.display = "none";
  386. }
  387. if (stencilItem && (stencilItem.canConnect || stencilItem.canConnectAssociation)) {
  388. var quickButtonCounter = 0;
  389. var quickButtonX = shapeXY.x+bounds.width() + 5;
  390. var quickButtonY = shapeXY.y;
  391. jQuery('.Oryx_button').each(function(i, obj) {
  392. if (obj.id !== 'morph-button' && obj.id != 'delete-button' && obj.id !== 'edit-button') {
  393. quickButtonCounter++;
  394. if (quickButtonCounter > 3) {
  395. quickButtonX = shapeXY.x+bounds.width() + 5;
  396. quickButtonY += 24;
  397. quickButtonCounter = 1;
  398. } else if (quickButtonCounter > 1) {
  399. quickButtonX += 24;
  400. }
  401. obj.style.display = "block";
  402. obj.style.left = quickButtonX + 'px';
  403. obj.style.top = quickButtonY + 'px';
  404. }
  405. });
  406. }
  407. }
  408. });
  409. if (!$rootScope.stencilInitialized) {
  410. FLOWABLE.eventBus.addListener(FLOWABLE.eventBus.EVENT_TYPE_HIDE_SHAPE_BUTTONS, function (event) {
  411. jQuery('.Oryx_button').each(function(i, obj) {
  412. obj.style.display = "none";
  413. });
  414. });
  415. /*
  416. * Listen to property updates and act upon them
  417. */
  418. FLOWABLE.eventBus.addListener(FLOWABLE.eventBus.EVENT_TYPE_PROPERTY_VALUE_CHANGED, function (event) {
  419. if (event.property && event.property.key) {
  420. // If the name property is been updated, we also need to change the title of the currently selected item
  421. if (event.property.key === 'oryx-name' && $scope.selectedItem !== undefined && $scope.selectedItem !== null) {
  422. $scope.selectedItem.title = event.newValue;
  423. }
  424. // Update "no value" flag
  425. event.property.noValue = (event.property.value === undefined
  426. || event.property.value === null
  427. || event.property.value.length == 0);
  428. }
  429. });
  430. FLOWABLE.eventBus.addListener(FLOWABLE.eventBus.EVENT_TYPE_SHOW_VALIDATION_POPUP, function (event) {
  431. // Method to open validation dialog
  432. var showValidationDialog = function() {
  433. $rootScope.currentValidationId = event.validationId;
  434. $rootScope.isOnProcessLevel = event.onProcessLevel;
  435. _internalCreateModal({template: 'editor-app/popups/validation-errors.html?version=' + Date.now()}, $modal, $rootScope);
  436. };
  437. showValidationDialog();
  438. });
  439. FLOWABLE.eventBus.addListener(FLOWABLE.eventBus.EVENT_TYPE_NAVIGATE_TO_PROCESS, function (event) {
  440. var modelMetaData = editorManager.getBaseModelData();
  441. $rootScope.editorHistory.push({
  442. id: modelMetaData.modelId,
  443. name: modelMetaData.name,
  444. type: 'bpmnmodel'
  445. });
  446. $window.location.href = "../editor/#/editor/" + event.processId;
  447. });
  448. $rootScope.stencilInitialized = true;
  449. }
  450. $scope.morphShape = function() {
  451. $scope.safeApply(function () {
  452. var shapes = editorManager.getSelection();
  453. if (shapes && shapes.length == 1) {
  454. $rootScope.currentSelectedShape = shapes.first();
  455. var stencilItem = $scope.getStencilItemById($rootScope.currentSelectedShape.getStencil().idWithoutNs());
  456. var morphShapes = [];
  457. for (var i = 0; i < $scope.morphRoles.length; i++) {
  458. if ($scope.morphRoles[i].role === stencilItem.morphRole) {
  459. morphShapes = $scope.morphRoles[i].morphOptions.slice();
  460. }
  461. }
  462. // Method to open shape select dialog (used later on)
  463. var showSelectShapeDialog = function()
  464. {
  465. $rootScope.morphShapes = morphShapes;
  466. _internalCreateModal({
  467. backdrop: false,
  468. keyboard: true,
  469. template: 'editor-app/popups/select-shape.html?version=' + Date.now()
  470. }, $modal, $rootScope);
  471. };
  472. showSelectShapeDialog();
  473. }
  474. });
  475. };
  476. $scope.deleteShape = function() {
  477. FLOWABLE.TOOLBAR.ACTIONS.deleteItem({'$scope': $scope, 'editorManager': editorManager});
  478. };
  479. $scope.quickAddItem = function(newItemId) {
  480. $scope.safeApply(function () {
  481. var shapes = editorManager.getSelection();
  482. if (shapes && shapes.length == 1) {
  483. $rootScope.currentSelectedShape = shapes.first();
  484. var containedStencil = undefined;
  485. var stencilSets = editorManager.getStencilSets().values();
  486. for (var i = 0; i < stencilSets.length; i++) {
  487. var stencilSet = stencilSets[i];
  488. var nodes = stencilSet.nodes();
  489. for (var j = 0; j < nodes.length; j++) {
  490. if (nodes[j].idWithoutNs() === newItemId) {
  491. containedStencil = nodes[j];
  492. break;
  493. }
  494. }
  495. }
  496. if (!containedStencil) return;
  497. var option = {type: $scope.currentSelectedShape.getStencil().namespace() + newItemId, namespace: $scope.currentSelectedShape.getStencil().namespace()};
  498. option['connectedShape'] = $rootScope.currentSelectedShape;
  499. option['parent'] = $rootScope.currentSelectedShape.parent;
  500. option['containedStencil'] = containedStencil;
  501. var args = { sourceShape: $rootScope.currentSelectedShape, targetStencil: containedStencil };
  502. var targetStencil = editorManager.getRules().connectMorph(args);
  503. // Check if there can be a target shape
  504. if (!targetStencil) {
  505. return;
  506. }
  507. option['connectingType'] = targetStencil.id();
  508. var command = new FLOWABLE.CreateCommand(option, undefined, undefined, editorManager.getEditor());
  509. editorManager.executeCommands([command]);
  510. }
  511. });
  512. };
  513. $scope.editShape = function(){
  514. editorManager.edit($scope.selectedShape.resourceId);
  515. };
  516. }); // end of $scope.editorFactory.promise block
  517. /* Click handler for clicking a property */
  518. $scope.propertyClicked = function (index) {
  519. if (!$scope.selectedItem.properties[index].hidden) {
  520. $scope.selectedItem.properties[index].mode = "write";
  521. }
  522. };
  523. /* Helper method to retrieve the template url for a property */
  524. $scope.getPropertyTemplateUrl = function (index) {
  525. return $scope.selectedItem.properties[index].templateUrl;
  526. };
  527. $scope.getPropertyReadModeTemplateUrl = function (index) {
  528. return $scope.selectedItem.properties[index].readModeTemplateUrl;
  529. };
  530. $scope.getPropertyWriteModeTemplateUrl = function (index) {
  531. return $scope.selectedItem.properties[index].writeModeTemplateUrl;
  532. };
  533. /* Method available to all sub controllers (for property controllers) to update the internal Oryx model */
  534. $scope.updatePropertyInModel = function (property, shapeId) {
  535. var shape = $scope.selectedShape;
  536. // Some updates may happen when selected shape is already changed, so when an additional
  537. // shapeId is supplied, we need to make sure the correct shape is updated (current or previous)
  538. if (shapeId) {
  539. if (shape.id != shapeId && $scope.previousSelectedShape && $scope.previousSelectedShape.id == shapeId) {
  540. shape = $scope.previousSelectedShape;
  541. } else {
  542. shape = null;
  543. }
  544. }
  545. if (!shape) {
  546. // When no shape is selected, or no shape is found for the alternative
  547. // shape ID, do nothing
  548. return;
  549. }
  550. var key = property.key;
  551. var newValue = property.value;
  552. var oldValue = shape.properties.get(key);
  553. if (newValue != oldValue) {
  554. var commandClass = ORYX.Core.Command.extend({
  555. construct: function () {
  556. this.key = key;
  557. this.oldValue = oldValue;
  558. this.newValue = newValue;
  559. this.shape = shape;
  560. this.facade = editorManager.getEditor();
  561. },
  562. execute: function () {
  563. this.shape.setProperty(this.key, this.newValue);
  564. this.facade.getCanvas().update();
  565. this.facade.updateSelection();
  566. },
  567. rollback: function () {
  568. this.shape.setProperty(this.key, this.oldValue);
  569. this.facade.getCanvas().update();
  570. this.facade.updateSelection();
  571. }
  572. });
  573. // Instantiate the class
  574. var command = new commandClass();
  575. // Execute the command
  576. editorManager.executeCommands([command]);
  577. editorManager.handleEvents({
  578. type: ORYX.CONFIG.EVENT_PROPWINDOW_PROP_CHANGED,
  579. elements: [shape],
  580. key: key
  581. });
  582. // Switch the property back to read mode, now the update is done
  583. property.mode = 'read';
  584. // Fire event to all who is interested
  585. // Fire event to all who want to know about this
  586. var event = {
  587. type: FLOWABLE.eventBus.EVENT_TYPE_PROPERTY_VALUE_CHANGED,
  588. property: property,
  589. oldValue: oldValue,
  590. newValue: newValue
  591. };
  592. FLOWABLE.eventBus.dispatch(event.type, event);
  593. } else {
  594. // Switch the property back to read mode, no update was needed
  595. property.mode = 'read';
  596. }
  597. };
  598. /**
  599. * Helper method that searches a group for an item with the given id.
  600. * If not found, will return undefined.
  601. */
  602. $scope.findStencilItemInGroup = function (stencilItemId, group) {
  603. var item;
  604. // Check all items directly in this group
  605. for (var j = 0; j < group.items.length; j++) {
  606. item = group.items[j];
  607. if (item.id === stencilItemId) {
  608. return item;
  609. }
  610. }
  611. // Check the child groups
  612. if (group.groups && group.groups.length > 0) {
  613. for (var k = 0; k < group.groups.length; k++) {
  614. item = $scope.findStencilItemInGroup(stencilItemId, group.groups[k]);
  615. if (item) {
  616. return item;
  617. }
  618. }
  619. }
  620. return undefined;
  621. };
  622. /**
  623. * Helper method to find a stencil item.
  624. */
  625. $scope.getStencilItemById = function (stencilItemId) {
  626. for (var i = 0; i < $scope.stencilItemGroups.length; i++) {
  627. var element = $scope.stencilItemGroups[i];
  628. // Real group
  629. if (element.items !== null && element.items !== undefined) {
  630. var item = $scope.findStencilItemInGroup(stencilItemId, element);
  631. if (item) {
  632. return item;
  633. }
  634. } else { // Root stencil item
  635. if (element.id === stencilItemId) {
  636. return element;
  637. }
  638. }
  639. }
  640. return undefined;
  641. };
  642. /*
  643. * DRAG AND DROP FUNCTIONALITY
  644. */
  645. $scope.dropCallback = function (event, ui) {
  646. editorManager.handleEvents({
  647. type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE,
  648. highlightId: "shapeRepo.attached"
  649. });
  650. editorManager.handleEvents({
  651. type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE,
  652. highlightId: "shapeRepo.added"
  653. });
  654. editorManager.handleEvents({
  655. type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE,
  656. highlightId: "shapeMenu"
  657. });
  658. FLOWABLE.eventBus.dispatch(FLOWABLE.eventBus.EVENT_TYPE_HIDE_SHAPE_BUTTONS);
  659. if ($scope.dragCanContain) {
  660. var item = $scope.getStencilItemById(ui.draggable[0].id);
  661. var pos = {x: event.pageX, y: event.pageY};
  662. var additionalIEZoom = 1;
  663. if (!isNaN(screen.logicalXDPI) && !isNaN(screen.systemXDPI)) {
  664. var ua = navigator.userAgent;
  665. if (ua.indexOf('MSIE') >= 0) {
  666. //IE 10 and below
  667. var zoom = Math.round((screen.deviceXDPI / screen.logicalXDPI) * 100);
  668. if (zoom !== 100) {
  669. additionalIEZoom = zoom / 100;
  670. }
  671. }
  672. }
  673. var screenCTM = editorManager.getCanvas().node.getScreenCTM();
  674. // Correcting the UpperLeft-Offset
  675. pos.x -= (screenCTM.e / additionalIEZoom);
  676. pos.y -= (screenCTM.f / additionalIEZoom);
  677. // Correcting the Zoom-Factor
  678. pos.x /= screenCTM.a;
  679. pos.y /= screenCTM.d;
  680. // Correcting the ScrollOffset
  681. pos.x -= document.documentElement.scrollLeft;
  682. pos.y -= document.documentElement.scrollTop;
  683. var parentAbs = $scope.dragCurrentParent.absoluteXY();
  684. pos.x -= parentAbs.x;
  685. pos.y -= parentAbs.y;
  686. var containedStencil = undefined;
  687. var stencilSets = editorManager.getStencilSets().values();
  688. for (var i = 0; i < stencilSets.length; i++) {
  689. var stencilSet = stencilSets[i];
  690. var nodes = stencilSet.nodes();
  691. for (var j = 0; j < nodes.length; j++) {
  692. if (nodes[j].idWithoutNs() === ui.draggable[0].id) {
  693. containedStencil = nodes[j];
  694. break;
  695. }
  696. }
  697. if (!containedStencil) {
  698. var edges = stencilSet.edges();
  699. for (var j = 0; j < edges.length; j++) {
  700. if (edges[j].idWithoutNs() === ui.draggable[0].id) {
  701. containedStencil = edges[j];
  702. break;
  703. }
  704. }
  705. }
  706. }
  707. if (!containedStencil) return;
  708. if ($scope.quickMenu) {
  709. var shapes = editorManager.getSelection();
  710. if (shapes && shapes.length == 1) {
  711. var currentSelectedShape = shapes.first();
  712. var option = {};
  713. option.type = currentSelectedShape.getStencil().namespace() + ui.draggable[0].id;
  714. option.namespace = currentSelectedShape.getStencil().namespace();
  715. option.connectedShape = currentSelectedShape;
  716. option.parent = $scope.dragCurrentParent;
  717. option.containedStencil = containedStencil;
  718. // If the ctrl key is not pressed,
  719. // snapp the new shape to the center
  720. // if it is near to the center of the other shape
  721. if (!event.ctrlKey) {
  722. // Get the center of the shape
  723. var cShape = currentSelectedShape.bounds.center();
  724. // Snapp +-20 Pixel horizontal to the center
  725. if (20 > Math.abs(cShape.x - pos.x)) {
  726. pos.x = cShape.x;
  727. }
  728. // Snapp +-20 Pixel vertical to the center
  729. if (20 > Math.abs(cShape.y - pos.y)) {
  730. pos.y = cShape.y;
  731. }
  732. }
  733. option.position = pos;
  734. if (containedStencil.idWithoutNs() !== 'SequenceFlow' && containedStencil.idWithoutNs() !== 'Association' &&
  735. containedStencil.idWithoutNs() !== 'MessageFlow' && containedStencil.idWithoutNs() !== 'DataAssociation' &&
  736. containedStencil.idWithoutNs() !== 'InformationRequirement' && containedStencil.idWithoutNs() !== 'KnowledgeRequirement') {
  737. var args = { sourceShape: currentSelectedShape, targetStencil: containedStencil };
  738. var targetStencil = editorManager.getRules().connectMorph(args);
  739. if (!targetStencil) { // Check if there can be a target shape
  740. return;
  741. }
  742. option.connectingType = targetStencil.id();
  743. }
  744. var command = new FLOWABLE.CreateCommand(option, $scope.dropTargetElement, pos, editorManager.getEditor());
  745. editorManager.executeCommands([command]);
  746. }
  747. } else {
  748. var canAttach = false;
  749. if (containedStencil.idWithoutNs() === 'BoundaryErrorEvent' || containedStencil.idWithoutNs() === 'BoundaryTimerEvent' ||
  750. containedStencil.idWithoutNs() === 'BoundarySignalEvent' || containedStencil.idWithoutNs() === 'BoundaryMessageEvent' ||
  751. containedStencil.idWithoutNs() === 'BoundaryCancelEvent' || containedStencil.idWithoutNs() === 'BoundaryCompensationEvent') {
  752. // Modify position, otherwise boundary event will get position related to left corner of the canvas instead of the container
  753. pos = editorManager.eventCoordinates( event );
  754. canAttach = true;
  755. }
  756. var option = {};
  757. option['type'] = $scope.modelData.model.stencilset.namespace + item.id;
  758. option['namespace'] = $scope.modelData.model.stencilset.namespace;
  759. option['position'] = pos;
  760. option['parent'] = $scope.dragCurrentParent;
  761. var commandClass = ORYX.Core.Command.extend({
  762. construct: function(option, dockedShape, canAttach, position, facade){
  763. this.option = option;
  764. this.docker = null;
  765. this.dockedShape = dockedShape;
  766. this.dockedShapeParent = dockedShape.parent || facade.getCanvas();
  767. this.position = position;
  768. this.facade = facade;
  769. this.selection = this.facade.getSelection();
  770. this.shape = null;
  771. this.parent = null;
  772. this.canAttach = canAttach;
  773. },
  774. execute: function(){
  775. if (!this.shape) {
  776. this.shape = this.facade.createShape(option);
  777. this.parent = this.shape.parent;
  778. } else if (this.parent) {
  779. this.parent.add(this.shape);
  780. }
  781. if (this.canAttach && this.shape.dockers && this.shape.dockers.length) {
  782. this.docker = this.shape.dockers[0];
  783. this.dockedShapeParent.add(this.docker.parent);
  784. // Set the Docker to the new Shape
  785. this.docker.setDockedShape(undefined);
  786. this.docker.bounds.centerMoveTo(this.position);
  787. if (this.dockedShape !== this.facade.getCanvas()) {
  788. this.docker.setDockedShape(this.dockedShape);
  789. }
  790. this.facade.setSelection( [this.docker.parent] );
  791. }
  792. this.facade.getCanvas().update();
  793. this.facade.updateSelection();
  794. },
  795. rollback: function(){
  796. if (this.shape) {
  797. this.facade.setSelection(this.selection.without(this.shape));
  798. this.facade.deleteShape(this.shape);
  799. }
  800. if (this.canAttach && this.docker) {
  801. this.docker.setDockedShape(undefined);
  802. }
  803. this.facade.getCanvas().update();
  804. this.facade.updateSelection();
  805. }
  806. });
  807. // Update canvas
  808. var command = new commandClass(option, $scope.dragCurrentParent, canAttach, pos, editorManager.getEditor());
  809. editorManager.executeCommands([command]);
  810. // Fire event to all who want to know about this
  811. var dropEvent = {
  812. type: FLOWABLE.eventBus.EVENT_TYPE_ITEM_DROPPED,
  813. droppedItem: item,
  814. position: pos
  815. };
  816. FLOWABLE.eventBus.dispatch(dropEvent.type, dropEvent);
  817. }
  818. }
  819. $scope.dragCurrentParent = undefined;
  820. $scope.dragCurrentParentId = undefined;
  821. $scope.dragCurrentParentStencil = undefined;
  822. $scope.dragCanContain = undefined;
  823. $scope.quickMenu = undefined;
  824. $scope.dropTargetElement = undefined;
  825. };
  826. $scope.overCallback = function (event, ui) {
  827. $scope.dragModeOver = true;
  828. };
  829. $scope.outCallback = function (event, ui) {
  830. $scope.dragModeOver = false;
  831. };
  832. $scope.startDragCallback = function (event, ui) {
  833. $scope.dragModeOver = false;
  834. $scope.quickMenu = false;
  835. if (!ui.helper.hasClass('stencil-item-dragged')) {
  836. ui.helper.addClass('stencil-item-dragged');
  837. }
  838. };
  839. $scope.startDragCallbackQuickMenu = function (event, ui) {
  840. $scope.dragModeOver = false;
  841. $scope.quickMenu = true;
  842. };
  843. $scope.dragCallback = function (event, ui) {
  844. if ($scope.dragModeOver != false) {
  845. var coord = editorManager.eventCoordinatesXY(event.pageX, event.pageY);
  846. var additionalIEZoom = 1;
  847. if (!isNaN(screen.logicalXDPI) && !isNaN(screen.systemXDPI)) {
  848. var ua = navigator.userAgent;
  849. if (ua.indexOf('MSIE') >= 0) {
  850. //IE 10 and below
  851. var zoom = Math.round((screen.deviceXDPI / screen.logicalXDPI) * 100);
  852. if (zoom !== 100) {
  853. additionalIEZoom = zoom / 100
  854. }
  855. }
  856. }
  857. if (additionalIEZoom !== 1) {
  858. coord.x = coord.x / additionalIEZoom;
  859. coord.y = coord.y / additionalIEZoom;
  860. }
  861. var aShapes = editorManager.getCanvas().getAbstractShapesAtPosition(coord);
  862. if (aShapes.length <= 0) {
  863. if (event.helper) {
  864. $scope.dragCanContain = false;
  865. return false;
  866. }
  867. }
  868. if (aShapes[0] instanceof ORYX.Core.Canvas) {
  869. editorManager.getCanvas().setHightlightStateBasedOnX(coord.x);
  870. }
  871. if (aShapes.length == 1 && aShapes[0] instanceof ORYX.Core.Canvas) {
  872. var item = $scope.getStencilItemById(event.target.id);
  873. var parentCandidate = aShapes[0];
  874. if (item.id === 'Lane' || item.id === 'BoundaryErrorEvent' || item.id === 'BoundaryMessageEvent' ||
  875. item.id === 'BoundarySignalEvent' || item.id === 'BoundaryTimerEvent' ||
  876. item.id === 'BoundaryCancelEvent' || item.id === 'BoundaryCompensationEvent' ||
  877. item.id === 'EntryCriterion') {
  878. $scope.dragCanContain = false;
  879. // Show Highlight
  880. editorManager.handleEvents({
  881. type: ORYX.CONFIG.EVENT_HIGHLIGHT_SHOW,
  882. highlightId: 'shapeRepo.added',
  883. elements: [parentCandidate],
  884. style: ORYX.CONFIG.SELECTION_HIGHLIGHT_STYLE_RECTANGLE,
  885. color: ORYX.CONFIG.SELECTION_INVALID_COLOR
  886. });
  887. } else {
  888. $scope.dragCanContain = true;
  889. $scope.dragCurrentParent = parentCandidate;
  890. $scope.dragCurrentParentId = parentCandidate.id;
  891. editorManager.handleEvents({
  892. type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE,
  893. highlightId: "shapeRepo.added"
  894. });
  895. }
  896. editorManager.handleEvents({
  897. type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE,
  898. highlightId: "shapeRepo.attached"
  899. });
  900. return false;
  901. } else {
  902. var item = $scope.getStencilItemById(event.target.id);
  903. var parentCandidate = aShapes.reverse().find(function (candidate) {
  904. return (candidate instanceof ORYX.Core.Canvas
  905. || candidate instanceof ORYX.Core.Node
  906. || candidate instanceof ORYX.Core.Edge);
  907. });
  908. if (!parentCandidate) {
  909. $scope.dragCanContain = false;
  910. return false;
  911. }
  912. if (item.type === "node") {
  913. // check if the draggable is a boundary event and the parent an Activity
  914. var _canContain = false;
  915. var parentStencilId = parentCandidate.getStencil().id();
  916. if ($scope.dragCurrentParentId && $scope.dragCurrentParentId === parentCandidate.id) {
  917. return false;
  918. }
  919. var parentItem = $scope.getStencilItemById(parentCandidate.getStencil().idWithoutNs());
  920. if (parentCandidate.getStencil().id().endsWith("DecisionServiceSection")) {
  921. if (item.id === 'Decision') {
  922. _canContain = true;
  923. }
  924. } else if (parentItem.roles.indexOf('Activity') > -1) {
  925. if (item.roles.indexOf('IntermediateEventOnActivityBoundary') > -1
  926. || item.roles.indexOf('EntryCriterionOnItemBoundary') > -1
  927. || item.roles.indexOf('ExitCriterionOnItemBoundary') > -1) {
  928. _canContain = true;
  929. }
  930. } else if(parentItem.roles.indexOf('StageActivity') > -1) {
  931. if (item.roles.indexOf('EntryCriterionOnItemBoundary') > -1
  932. || item.roles.indexOf('ExitCriterionOnItemBoundary') > -1) {
  933. _canContain = true;
  934. }
  935. } else if(parentItem.roles.indexOf('StageModelActivity') > -1) {
  936. if (item.roles.indexOf('ExitCriterionOnItemBoundary') > -1) {
  937. _canContain = true;
  938. }
  939. } else if (parentCandidate.getStencil().idWithoutNs() === 'Pool') {
  940. if (item.id === 'Lane') {
  941. _canContain = true;
  942. }
  943. }
  944. if (_canContain) {
  945. editorManager.handleEvents({
  946. type: ORYX.CONFIG.EVENT_HIGHLIGHT_SHOW,
  947. highlightId: "shapeRepo.attached",
  948. elements: [parentCandidate],
  949. style: ORYX.CONFIG.SELECTION_HIGHLIGHT_STYLE_RECTANGLE,
  950. color: ORYX.CONFIG.SELECTION_VALID_COLOR
  951. });
  952. editorManager.handleEvents({
  953. type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE,
  954. highlightId: "shapeRepo.added"
  955. });
  956. } else {
  957. for (var i = 0; i < $scope.containmentRules.length; i++) {
  958. var rule = $scope.containmentRules[i];
  959. if (rule.role === parentItem.id) {
  960. for (var j = 0; j < rule.contains.length; j++) {
  961. if (item.roles.indexOf(rule.contains[j]) > -1) {
  962. _canContain = true;
  963. break;
  964. }
  965. }
  966. if (_canContain) {
  967. break;
  968. }
  969. }
  970. }
  971. // Show Highlight
  972. editorManager.handleEvents({
  973. type: ORYX.CONFIG.EVENT_HIGHLIGHT_SHOW,
  974. highlightId: 'shapeRepo.added',
  975. elements: [parentCandidate],
  976. color: _canContain ? ORYX.CONFIG.SELECTION_VALID_COLOR : ORYX.CONFIG.SELECTION_INVALID_COLOR
  977. });
  978. editorManager.handleEvents({
  979. type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE,
  980. highlightId: "shapeRepo.attached"
  981. });
  982. }
  983. $scope.dragCurrentParent = parentCandidate;
  984. $scope.dragCurrentParentId = parentCandidate.id;
  985. $scope.dragCurrentParentStencil = parentStencilId;
  986. $scope.dragCanContain = _canContain;
  987. } else {
  988. var canvasCandidate = editorManager.getCanvas();
  989. var canConnect = false;
  990. var targetStencil = $scope.getStencilItemById(parentCandidate.getStencil().idWithoutNs());
  991. if (targetStencil) {
  992. var associationConnect = false;
  993. if (stencil.idWithoutNs() === 'Association' && (curCan.getStencil().idWithoutNs() === 'TextAnnotation' || curCan.getStencil().idWithoutNs() === 'BoundaryCompensationEvent')) {
  994. associationConnect = true;
  995. } else if (stencil.idWithoutNs() === 'DataAssociation' && curCan.getStencil().idWithoutNs() === 'DataStore') {
  996. associationConnect = true;
  997. }
  998. if (targetStencil.canConnectTo || associationConnect) {
  999. canConnect = true;
  1000. }
  1001. }
  1002. //Edge
  1003. $scope.dragCurrentParent = canvasCandidate;
  1004. $scope.dragCurrentParentId = canvasCandidate.id;
  1005. $scope.dragCurrentParentStencil = canvasCandidate.getStencil().id();
  1006. $scope.dragCanContain = canConnect;
  1007. // Show Highlight
  1008. editorManager.handleEvents({
  1009. type: ORYX.CONFIG.EVENT_HIGHLIGHT_SHOW,
  1010. highlightId: 'shapeRepo.added',
  1011. elements: [canvasCandidate],
  1012. color: ORYX.CONFIG.SELECTION_VALID_COLOR
  1013. });
  1014. editorManager.handleEvents({
  1015. type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE,
  1016. highlightId: "shapeRepo.attached"
  1017. });
  1018. }
  1019. }
  1020. }
  1021. };
  1022. $scope.dragCallbackQuickMenu = function (event, ui) {
  1023. if ($scope.dragModeOver != false) {
  1024. var coord = editorManager.eventCoordinatesXY(event.pageX, event.pageY);
  1025. var additionalIEZoom = 1;
  1026. if (!isNaN(screen.logicalXDPI) && !isNaN(screen.systemXDPI)) {
  1027. var ua = navigator.userAgent;
  1028. if (ua.indexOf('MSIE') >= 0) {
  1029. //IE 10 and below
  1030. var zoom = Math.round((screen.deviceXDPI / screen.logicalXDPI) * 100);
  1031. if (zoom !== 100) {
  1032. additionalIEZoom = zoom / 100
  1033. }
  1034. }
  1035. }
  1036. if (additionalIEZoom !== 1) {
  1037. coord.x = coord.x / additionalIEZoom;
  1038. coord.y = coord.y / additionalIEZoom;
  1039. }
  1040. var aShapes = editorManager.getCanvas().getAbstractShapesAtPosition(coord);
  1041. if (aShapes.length <= 0) {
  1042. if (event.helper) {
  1043. $scope.dragCanContain = false;
  1044. return false;
  1045. }
  1046. }
  1047. if (aShapes[0] instanceof ORYX.Core.Canvas) {
  1048. editorManager.getCanvas().setHightlightStateBasedOnX(coord.x);
  1049. }
  1050. var stencil = undefined;
  1051. var stencilSets = editorManager.getStencilSets().values();
  1052. for (var i = 0; i < stencilSets.length; i++) {
  1053. var stencilSet = stencilSets[i];
  1054. var nodes = stencilSet.nodes();
  1055. for (var j = 0; j < nodes.length; j++) {
  1056. if (nodes[j].idWithoutNs() === event.target.id) {
  1057. stencil = nodes[j];
  1058. break;
  1059. }
  1060. }
  1061. if (!stencil) {
  1062. var edges = stencilSet.edges();
  1063. for (var j = 0; j < edges.length; j++) {
  1064. if (edges[j].idWithoutNs() === event.target.id) {
  1065. stencil = edges[j];
  1066. break;
  1067. }
  1068. }
  1069. }
  1070. }
  1071. var candidate = aShapes.last();
  1072. var isValid = false;
  1073. if (stencil.type() === "node") {
  1074. //check containment rules
  1075. var canContain = editorManager.getRules().canContain({containingShape:candidate, containedStencil:stencil});
  1076. var parentCandidate = aShapes.reverse().find(function (candidate) {
  1077. return (candidate instanceof ORYX.Core.Canvas
  1078. || candidate instanceof ORYX.Core.Node
  1079. || candidate instanceof ORYX.Core.Edge);
  1080. });
  1081. if (!parentCandidate) {
  1082. $scope.dragCanContain = false;
  1083. return false;
  1084. }
  1085. $scope.dragCurrentParent = parentCandidate;
  1086. $scope.dragCurrentParentId = parentCandidate.id;
  1087. $scope.dragCurrentParentStencil = parentCandidate.getStencil().id();
  1088. $scope.dragCanContain = canContain;
  1089. $scope.dropTargetElement = parentCandidate;
  1090. isValid = canContain;
  1091. } else { //Edge
  1092. var shapes = editorManager.getSelection();
  1093. if (shapes && shapes.length == 1) {
  1094. var currentSelectedShape = shapes.first();
  1095. var curCan = candidate;
  1096. var canConnect = false;
  1097. var targetStencil = $scope.getStencilItemById(curCan.getStencil().idWithoutNs());
  1098. if (targetStencil) {
  1099. var associationConnect = false;
  1100. if (stencil.idWithoutNs() === 'Association' && (curCan.getStencil().idWithoutNs() === 'TextAnnotation' || curCan.getStencil().idWithoutNs() === 'BoundaryCompensationEvent')) {
  1101. associationConnect = true;
  1102. } else if (stencil.idWithoutNs() === 'DataAssociation' && curCan.getStencil().idWithoutNs() === 'DataStore') {
  1103. associationConnect = true;
  1104. }
  1105. if (targetStencil.canConnectTo || associationConnect) {
  1106. while (!canConnect && curCan && !(curCan instanceof ORYX.Core.Canvas)) {
  1107. candidate = curCan;
  1108. //check connection rules
  1109. canConnect = editorManager.getRules().canConnect({
  1110. sourceShape: currentSelectedShape,
  1111. edgeStencil: stencil,
  1112. targetShape: curCan
  1113. });
  1114. curCan = curCan.parent;
  1115. }
  1116. }
  1117. }
  1118. var parentCandidate = editorManager.getCanvas();
  1119. isValid = canConnect;
  1120. $scope.dragCurrentParent = parentCandidate;
  1121. $scope.dragCurrentParentId = parentCandidate.id;
  1122. $scope.dragCurrentParentStencil = parentCandidate.getStencil().id();
  1123. $scope.dragCanContain = canConnect;
  1124. $scope.dropTargetElement = candidate;
  1125. }
  1126. }
  1127. editorManager.handleEvents({
  1128. type: ORYX.CONFIG.EVENT_HIGHLIGHT_SHOW,
  1129. highlightId:'shapeMenu',
  1130. elements: [candidate],
  1131. color: isValid ? ORYX.CONFIG.SELECTION_VALID_COLOR : ORYX.CONFIG.SELECTION_INVALID_COLOR
  1132. });
  1133. }
  1134. };
  1135. }]);
  1136. var FLOWABLE = FLOWABLE || {};
  1137. //create command for undo/redo
  1138. FLOWABLE.CreateCommand = ORYX.Core.Command.extend({
  1139. construct: function(option, currentReference, position, facade){
  1140. this.option = option;
  1141. this.currentReference = currentReference;
  1142. this.position = position;
  1143. this.facade = facade;
  1144. this.shape;
  1145. this.edge;
  1146. this.targetRefPos;
  1147. this.sourceRefPos;
  1148. /*
  1149. * clone options parameters
  1150. */
  1151. this.connectedShape = option.connectedShape;
  1152. this.connectingType = option.connectingType;
  1153. this.namespace = option.namespace;
  1154. this.type = option.type;
  1155. this.containedStencil = option.containedStencil;
  1156. this.parent = option.parent;
  1157. this.currentReference = currentReference;
  1158. this.shapeOptions = option.shapeOptions;
  1159. },
  1160. execute: function(){
  1161. if (this.shape) {
  1162. if (this.shape instanceof ORYX.Core.Node) {
  1163. this.parent.add(this.shape);
  1164. if (this.edge) {
  1165. this.facade.getCanvas().add(this.edge);
  1166. this.edge.dockers.first().setDockedShape(this.connectedShape);
  1167. this.edge.dockers.first().setReferencePoint(this.sourceRefPos);
  1168. this.edge.dockers.last().setDockedShape(this.shape);
  1169. this.edge.dockers.last().setReferencePoint(this.targetRefPos);
  1170. }
  1171. this.facade.setSelection([this.shape]);
  1172. } else if (this.shape instanceof ORYX.Core.Edge) {
  1173. this.facade.getCanvas().add(this.shape);
  1174. this.shape.dockers.first().setDockedShape(this.connectedShape);
  1175. this.shape.dockers.first().setReferencePoint(this.sourceRefPos);
  1176. }
  1177. }
  1178. else {
  1179. this.shape = this.facade.createShape(this.option);
  1180. this.edge = (!(this.shape instanceof ORYX.Core.Edge)) ? this.shape.getIncomingShapes().first() : undefined;
  1181. }
  1182. if (this.currentReference && this.position) {
  1183. if (this.shape instanceof ORYX.Core.Edge) {
  1184. if (!(this.currentReference instanceof ORYX.Core.Canvas)) {
  1185. this.shape.dockers.last().setDockedShape(this.currentReference);
  1186. if (this.currentReference.getStencil().idWithoutNs() === 'TextAnnotation')
  1187. {
  1188. var midpoint = {};
  1189. midpoint.x = 0;
  1190. midpoint.y = this.currentReference.bounds.height() / 2;
  1191. this.shape.dockers.last().setReferencePoint(midpoint);
  1192. }
  1193. else
  1194. {
  1195. this.shape.dockers.last().setReferencePoint(this.currentReference.bounds.midPoint());
  1196. }
  1197. }
  1198. else {
  1199. this.shape.dockers.last().bounds.centerMoveTo(this.position);
  1200. }
  1201. this.sourceRefPos = this.shape.dockers.first().referencePoint;
  1202. this.targetRefPos = this.shape.dockers.last().referencePoint;
  1203. } else if (this.edge){
  1204. this.sourceRefPos = this.edge.dockers.first().referencePoint;
  1205. this.targetRefPos = this.edge.dockers.last().referencePoint;
  1206. }
  1207. } else {
  1208. var containedStencil = this.containedStencil;
  1209. var connectedShape = this.connectedShape;
  1210. var bc = connectedShape.bounds;
  1211. var bs = this.shape.bounds;
  1212. var pos = bc.center();
  1213. if(containedStencil.defaultAlign()==="north") {
  1214. pos.y -= (bc.height() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET + (bs.height()/2);
  1215. } else if(containedStencil.defaultAlign()==="northeast") {
  1216. pos.x += (bc.width() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.width()/2);
  1217. pos.y -= (bc.height() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.height()/2);
  1218. } else if(containedStencil.defaultAlign()==="southeast") {
  1219. pos.x += (bc.width() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.width()/2);
  1220. pos.y += (bc.height() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.height()/2);
  1221. } else if(containedStencil.defaultAlign()==="south") {
  1222. pos.y += (bc.height() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET + (bs.height()/2);
  1223. } else if(containedStencil.defaultAlign()==="southwest") {
  1224. pos.x -= (bc.width() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.width()/2);
  1225. pos.y += (bc.height() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.height()/2);
  1226. } else if(containedStencil.defaultAlign()==="west") {
  1227. pos.x -= (bc.width() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET + (bs.width()/2);
  1228. } else if(containedStencil.defaultAlign()==="northwest") {
  1229. pos.x -= (bc.width() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.width()/2);
  1230. pos.y -= (bc.height() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.height()/2);
  1231. } else {
  1232. pos.x += (bc.width() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET + (bs.width()/2);
  1233. }
  1234. // Move shape to the new position
  1235. this.shape.bounds.centerMoveTo(pos);
  1236. // Move all dockers of a node to the position
  1237. if (this.shape instanceof ORYX.Core.Node){
  1238. (this.shape.dockers||[]).each(function(docker){
  1239. docker.bounds.centerMoveTo(pos);
  1240. });
  1241. }
  1242. //this.shape.update();
  1243. this.position = pos;
  1244. if (this.edge){
  1245. this.sourceRefPos = this.edge.dockers.first().referencePoint;
  1246. this.targetRefPos = this.edge.dockers.last().referencePoint;
  1247. }
  1248. }
  1249. this.facade.getCanvas().update();
  1250. this.facade.updateSelection();
  1251. },
  1252. rollback: function(){
  1253. this.facade.deleteShape(this.shape);
  1254. if(this.edge) {
  1255. this.facade.deleteShape(this.edge);
  1256. }
  1257. //this.currentParent.update();
  1258. this.facade.setSelection(this.facade.getSelection().without(this.shape, this.edge));
  1259. }
  1260. });