/* * Activiti Modeler component part of the Activiti project * Copyright 2005-2014 Alfresco Software, Ltd. All rights reserved. * * This library 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 2.1 of the License, or (at your option) any later version. * * This library 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 this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ 'use strict'; angular.module('activitiModeler') .controller('StencilController', ['$rootScope', '$scope', '$http', '$modal', '$timeout', function ($rootScope, $scope, $http, $modal, $timeout) { // Property window toggle state $scope.propertyWindowState = {'collapsed': false}; // Add reference to global header-config $scope.headerConfig = KISBPM.HEADER_CONFIG; $scope.propertyWindowState.toggle = function () { $scope.propertyWindowState.collapsed = !$scope.propertyWindowState.collapsed; $timeout(function () { jQuery(window).trigger('resize'); }); }; // Code that is dependent on an initialised Editor is wrapped in a promise for the editor $scope.editorFactory.promise.then(function () { /* Build stencil item list */ // Build simple json representation of stencil set var stencilItemGroups = []; // Helper method: find a group in an array var findGroup = function (name, groupArray) { for (var index = 0; index < groupArray.length; index++) { if (groupArray[index].name === name) { return groupArray[index]; } } return null; }; // Helper method: add a new group to an array of groups var addGroup = function (groupName, groupArray) { var group = { name: groupName, items: [], paletteItems: [], groups: [], visible: true }; groupArray.push(group); return group; }; /* StencilSet items */ $http({method: 'GET', url: KISBPM.URL.getStencilSet()}).success(function (data, status, headers, config) { var quickMenuDefinition = ['UserTask', 'EndNoneEvent', 'ExclusiveGateway', 'CatchTimerEvent', 'ThrowNoneEvent', 'TextAnnotation', 'SequenceFlow', 'Association']; var ignoreForPaletteDefinition = ['SequenceFlow', 'MessageFlow', 'Association', 'DataAssociation', 'DataStore', 'SendTask']; var quickMenuItems = []; var morphRoles = []; for (var i = 0; i < data.rules.morphingRules.length; i++) { var role = data.rules.morphingRules[i].role; var roleItem = {'role': role, 'morphOptions': []}; morphRoles.push(roleItem); } // Check all received items for (var stencilIndex = 0; stencilIndex < data.stencils.length; stencilIndex++) { // Check if the root group is the 'diagram' group. If so, this item should not be shown. var currentGroupName = data.stencils[stencilIndex].groups[0]; if (currentGroupName === 'Diagram' || currentGroupName === 'Form') { continue; // go to next item } var removed = false; if (data.stencils[stencilIndex].removed) { removed = true; } var currentGroup = undefined; if (!removed) { // Check if this group already exists. If not, we create a new one if (currentGroupName !== null && currentGroupName !== undefined && currentGroupName.length > 0) { currentGroup = findGroup(currentGroupName, stencilItemGroups); // Find group in root groups array if (currentGroup === null) { currentGroup = addGroup(currentGroupName, stencilItemGroups); } // Add all child groups (if any) for (var groupIndex = 1; groupIndex < data.stencils[stencilIndex].groups.length; groupIndex++) { var childGroupName = data.stencils[stencilIndex].groups[groupIndex]; var childGroup = findGroup(childGroupName, currentGroup.groups); if (childGroup === null) { childGroup = addGroup(childGroupName, currentGroup.groups); } // The current group variable holds the parent of the next group (if any), // and is basically the last element in the array of groups defined in the stencil item currentGroup = childGroup; } } } // Construct the stencil item var stencilItem = {'id': data.stencils[stencilIndex].id, 'name': data.stencils[stencilIndex].title, 'description': data.stencils[stencilIndex].description, 'icon': data.stencils[stencilIndex].icon, 'type': data.stencils[stencilIndex].type, 'roles': data.stencils[stencilIndex].roles, 'removed': removed, 'customIcon': false, 'canConnect': false, 'canConnectTo': false, 'canConnectAssociation': false}; if (data.stencils[stencilIndex].customIconId && data.stencils[stencilIndex].customIconId > 0) { stencilItem.customIcon = true; stencilItem.icon = data.stencils[stencilIndex].customIconId; } if (!removed) { if (quickMenuDefinition.indexOf(stencilItem.id) >= 0) { quickMenuItems[quickMenuDefinition.indexOf(stencilItem.id)] = stencilItem; } } if (stencilItem.id === 'TextAnnotation' || stencilItem.id === 'BoundaryCompensationEvent') { stencilItem.canConnectAssociation = true; } for (var i = 0; i < data.stencils[stencilIndex].roles.length; i++) { var stencilRole = data.stencils[stencilIndex].roles[i]; if (stencilRole === 'sequence_start') { stencilItem.canConnect = true; } else if (stencilRole === 'sequence_end') { stencilItem.canConnectTo = true; } for (var j = 0; j < morphRoles.length; j++) { if (stencilRole === morphRoles[j].role) { if (!removed) { morphRoles[j].morphOptions.push(stencilItem); } stencilItem.morphRole = morphRoles[j].role; break; } } } if (currentGroup) { // Add the stencil item to the correct group currentGroup.items.push(stencilItem); if (ignoreForPaletteDefinition.indexOf(stencilItem.id) < 0) { currentGroup.paletteItems.push(stencilItem); } } else { // It's a root stencil element if (!removed) { stencilItemGroups.push(stencilItem); } } } for (var i = 0; i < stencilItemGroups.length; i++) { if (stencilItemGroups[i].paletteItems && stencilItemGroups[i].paletteItems.length == 0) { stencilItemGroups[i].visible = false; } } $scope.stencilItemGroups = stencilItemGroups; var containmentRules = []; for (var i = 0; i < data.rules.containmentRules.length; i++) { var rule = data.rules.containmentRules[i]; containmentRules.push(rule); } $scope.containmentRules = containmentRules; // remove quick menu items which are not available anymore due to custom pallette var availableQuickMenuItems = []; for (var i = 0; i < quickMenuItems.length; i++) { if (quickMenuItems[i]) { availableQuickMenuItems[availableQuickMenuItems.length] = quickMenuItems[i]; } } $scope.quickMenuItems = availableQuickMenuItems; $scope.morphRoles = morphRoles; }). error(function (data, status, headers, config) { console.log('Something went wrong when fetching stencil items:' + JSON.stringify(data)); }); /* * Listen to selection change events: show properties */ $scope.editor.registerOnEvent(ORYX.CONFIG.EVENT_SELECTION_CHANGED, function (event) { var shapes = event.elements; var canvasSelected = false; if (shapes && shapes.length == 0) { shapes = [$scope.editor.getCanvas()]; canvasSelected = true; } if (shapes && shapes.length > 0) { var selectedShape = shapes.first(); var stencil = selectedShape.getStencil(); if ($rootScope.selectedElementBeforeScrolling && stencil.id().indexOf('BPMNDiagram') !== -1) { // ignore canvas event because of empty selection when scrolling stops return; } if ($rootScope.selectedElementBeforeScrolling && $rootScope.selectedElementBeforeScrolling.getId() === selectedShape.getId()) { $rootScope.selectedElementBeforeScrolling = null; return; } // Store previous selection $scope.previousSelectedShape = $scope.selectedShape; // Only do something if another element is selected (Oryx fires this event multiple times) if ($scope.selectedShape !== undefined && $scope.selectedShape.getId() === selectedShape.getId()) { if ($rootScope.forceSelectionRefresh) { // Switch the flag again, this run will force refresh $rootScope.forceSelectionRefresh = false; } else { // Selected the same element again, no need to update anything return; } } var selectedItem = {'title': '', 'properties': []}; if (canvasSelected) { selectedItem.auditData = { 'author': $scope.modelData.createdByUser, 'createDate': $scope.modelData.createDate }; } // Gather properties of selected item var properties = stencil.properties(); for (var i = 0; i < properties.length; i++) { var property = properties[i]; if (property.popular() == false) continue; var key = property.prefix() + "-" + property.id(); if (key === 'oryx-name') { selectedItem.title = selectedShape.properties[key]; } // First we check if there is a config for 'key-type' and then for 'type' alone var propertyConfig = KISBPM.PROPERTY_CONFIG[key + '-' + property.type()]; if (propertyConfig === undefined || propertyConfig === null) { propertyConfig = KISBPM.PROPERTY_CONFIG[property.type()]; } if (propertyConfig === undefined || propertyConfig === null) { console.log('WARNING: no property configuration defined for ' + key + ' of type ' + property.type()); } else { if (selectedShape.properties[key] === 'true') { selectedShape.properties[key] = true; } if (KISBPM.CONFIG.showRemovedProperties == false && property.isHidden()) { continue; } var currentProperty = { 'key': key, 'title': property.title(), 'type': property.type(), 'mode': 'read', 'hidden': property.isHidden(), 'value': selectedShape.properties[key] }; if ((currentProperty.type === 'complex' || currentProperty.type === 'multiplecomplex') && currentProperty.value && currentProperty.value.length > 0) { try { currentProperty.value = JSON.parse(currentProperty.value); } catch (err) { // ignore } } if (propertyConfig.readModeTemplateUrl !== undefined && propertyConfig.readModeTemplateUrl !== null) { currentProperty.readModeTemplateUrl = propertyConfig.readModeTemplateUrl + '?version=' + $rootScope.staticIncludeVersion; } if (propertyConfig.writeModeTemplateUrl !== null && propertyConfig.writeModeTemplateUrl !== null) { currentProperty.writeModeTemplateUrl = propertyConfig.writeModeTemplateUrl + '?version=' + $rootScope.staticIncludeVersion; } if (propertyConfig.templateUrl !== undefined && propertyConfig.templateUrl !== null) { currentProperty.templateUrl = propertyConfig.templateUrl + '?version=' + $rootScope.staticIncludeVersion; currentProperty.hasReadWriteMode = false; } else { currentProperty.hasReadWriteMode = true; } if (currentProperty.value === undefined || currentProperty.value === null || currentProperty.value.length == 0) { currentProperty.noValue = true; } selectedItem.properties.push(currentProperty); } } // Need to wrap this in an $apply block, see http://jimhoskins.com/2012/12/17/angularjs-and-apply.html $scope.safeApply(function () { $scope.selectedItem = selectedItem; $scope.selectedShape = selectedShape; }); } else { $scope.safeApply(function () { $scope.selectedItem = {}; $scope.selectedShape = null; }); } }); $scope.editor.registerOnEvent(ORYX.CONFIG.EVENT_SELECTION_CHANGED, function (event) { KISBPM.eventBus.dispatch(KISBPM.eventBus.EVENT_TYPE_HIDE_SHAPE_BUTTONS); var shapes = event.elements; if (shapes && shapes.length == 1) { var selectedShape = shapes.first(); var a = $scope.editor.getCanvas().node.getScreenCTM(); var absoluteXY = selectedShape.absoluteXY(); absoluteXY.x *= a.a; absoluteXY.y *= a.d; var additionalIEZoom = 1; if (!isNaN(screen.logicalXDPI) && !isNaN(screen.systemXDPI)) { var ua = navigator.userAgent; if (ua.indexOf('MSIE') >= 0) { //IE 10 and below var zoom = Math.round((screen.deviceXDPI / screen.logicalXDPI) * 100); if (zoom !== 100) { additionalIEZoom = zoom / 100 } } } if (additionalIEZoom === 1) { absoluteXY.y = absoluteXY.y - jQuery("#canvasSection").offset().top + 5; absoluteXY.x = absoluteXY.x - jQuery("#canvasSection").offset().left; } else { var canvasOffsetLeft = jQuery("#canvasSection").offset().left; var canvasScrollLeft = jQuery("#canvasSection").scrollLeft(); var canvasScrollTop = jQuery("#canvasSection").scrollTop(); var offset = a.e - (canvasOffsetLeft * additionalIEZoom); var additionaloffset = 0; if (offset > 10) { additionaloffset = (offset / additionalIEZoom) - offset; } absoluteXY.y = absoluteXY.y - (jQuery("#canvasSection").offset().top * additionalIEZoom) + 5 + ((canvasScrollTop * additionalIEZoom) - canvasScrollTop); absoluteXY.x = absoluteXY.x - (canvasOffsetLeft * additionalIEZoom) + additionaloffset + ((canvasScrollLeft * additionalIEZoom) - canvasScrollLeft); } 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()); var shapeXY = bounds.upperLeft(); var stencilItem = $scope.getStencilItemById(selectedShape.getStencil().idWithoutNs()); var morphShapes = []; if (stencilItem && stencilItem.morphRole) { for (var i = 0; i < $scope.morphRoles.length; i++) { if ($scope.morphRoles[i].role === stencilItem.morphRole) { morphShapes = $scope.morphRoles[i].morphOptions; } } } var x = shapeXY.x; if (bounds.width() < 48) { x -= 24; } if (morphShapes && morphShapes.length > 0) { // In case the element is not wide enough, start the 2 bottom-buttons more to the left // to prevent overflow in the right-menu var morphButton = document.getElementById('morph-button'); morphButton.style.display = "block"; morphButton.style.left = x + 24 +'px'; morphButton.style.top = (shapeXY.y+bounds.height() + 2) + 'px'; } var deleteButton = document.getElementById('delete-button'); deleteButton.style.display = "block"; deleteButton.style.left = x + 'px'; deleteButton.style.top = (shapeXY.y+bounds.height() + 2) + 'px'; if (stencilItem && (stencilItem.canConnect || stencilItem.canConnectAssociation)) { var quickButtonCounter = 0; var quickButtonX = shapeXY.x+bounds.width() + 5; var quickButtonY = shapeXY.y; jQuery('.Oryx_button').each(function(i, obj) { if (obj.id !== 'morph-button' && obj.id != 'delete-button') { quickButtonCounter++; if (quickButtonCounter > 3) { quickButtonX = shapeXY.x+bounds.width() + 5; quickButtonY += 24; quickButtonCounter = 1; } else if (quickButtonCounter > 1) { quickButtonX += 24; } obj.style.display = "block"; obj.style.left = quickButtonX + 'px'; obj.style.top = quickButtonY + 'px'; } }); } } }); if (!$rootScope.stencilInitialized) { KISBPM.eventBus.addListener(KISBPM.eventBus.EVENT_TYPE_HIDE_SHAPE_BUTTONS, function (event) { jQuery('.Oryx_button').each(function(i, obj) { obj.style.display = "none"; }); }); /* * Listen to property updates and act upon them */ KISBPM.eventBus.addListener(KISBPM.eventBus.EVENT_TYPE_PROPERTY_VALUE_CHANGED, function (event) { if (event.property && event.property.key) { // If the name property is been updated, we also need to change the title of the currently selected item if (event.property.key === 'oryx-name' && $scope.selectedItem !== undefined && $scope.selectedItem !== null) { $scope.selectedItem.title = event.newValue; } // Update "no value" flag event.property.noValue = (event.property.value === undefined || event.property.value === null || event.property.value.length == 0); } }); $rootScope.stencilInitialized = true; } $scope.morphShape = function() { $scope.safeApply(function () { var shapes = $rootScope.editor.getSelection(); if (shapes && shapes.length == 1) { $rootScope.currentSelectedShape = shapes.first(); var stencilItem = $scope.getStencilItemById($rootScope.currentSelectedShape.getStencil().idWithoutNs()); var morphShapes = []; for (var i = 0; i < $scope.morphRoles.length; i++) { if ($scope.morphRoles[i].role === stencilItem.morphRole) { morphShapes = $scope.morphRoles[i].morphOptions.slice(); } } // Method to open shape select dialog (used later on) var showSelectShapeDialog = function() { $rootScope.morphShapes = morphShapes; $modal({ backdrop: false, keyboard: true, template: 'editor-app/popups/select-shape.html?version=' + Date.now() }); }; showSelectShapeDialog(); } }); }; $scope.deleteShape = function() { KISBPM.TOOLBAR.ACTIONS.deleteItem({'$scope': $scope}); }; $scope.quickAddItem = function(newItemId) { $scope.safeApply(function () { var shapes = $rootScope.editor.getSelection(); if (shapes && shapes.length == 1) { $rootScope.currentSelectedShape = shapes.first(); var containedStencil = undefined; var stencilSets = $scope.editor.getStencilSets().values(); for (var i = 0; i < stencilSets.length; i++) { var stencilSet = stencilSets[i]; var nodes = stencilSet.nodes(); for (var j = 0; j < nodes.length; j++) { if (nodes[j].idWithoutNs() === newItemId) { containedStencil = nodes[j]; break; } } } if (!containedStencil) return; var option = {type: $scope.currentSelectedShape.getStencil().namespace() + newItemId, namespace: $scope.currentSelectedShape.getStencil().namespace()}; option['connectedShape'] = $rootScope.currentSelectedShape; option['parent'] = $rootScope.currentSelectedShape.parent; option['containedStencil'] = containedStencil; var args = { sourceShape: $rootScope.currentSelectedShape, targetStencil: containedStencil }; var targetStencil = $scope.editor.getRules().connectMorph(args); if (!targetStencil){ return; }// Check if there can be a target shape option['connectingType'] = targetStencil.id(); var command = new KISBPM.CreateCommand(option, undefined, undefined, $scope.editor); $scope.editor.executeCommands([command]); } }); }; }); // end of $scope.editorFactory.promise block /* Click handler for clicking a property */ $scope.propertyClicked = function (index) { if (!$scope.selectedItem.properties[index].hidden) { $scope.selectedItem.properties[index].mode = "write"; } }; /* Helper method to retrieve the template url for a property */ $scope.getPropertyTemplateUrl = function (index) { return $scope.selectedItem.properties[index].templateUrl; }; $scope.getPropertyReadModeTemplateUrl = function (index) { return $scope.selectedItem.properties[index].readModeTemplateUrl; }; $scope.getPropertyWriteModeTemplateUrl = function (index) { return $scope.selectedItem.properties[index].writeModeTemplateUrl; }; /* Method available to all sub controllers (for property controllers) to update the internal Oryx model */ $scope.updatePropertyInModel = function (property, shapeId) { var shape = $scope.selectedShape; // Some updates may happen when selected shape is already changed, so when an additional // shapeId is supplied, we need to make sure the correct shape is updated (current or previous) if (shapeId) { if (shape.id != shapeId && $scope.previousSelectedShape && $scope.previousSelectedShape.id == shapeId) { shape = $scope.previousSelectedShape; } else { shape = null; } } if (!shape) { // When no shape is selected, or no shape is found for the alternative // shape ID, do nothing return; } var key = property.key; var newValue = property.value; var oldValue = shape.properties[key]; if (newValue != oldValue) { var commandClass = ORYX.Core.Command.extend({ construct: function () { this.key = key; this.oldValue = oldValue; this.newValue = newValue; this.shape = shape; this.facade = $scope.editor; }, execute: function () { this.shape.setProperty(this.key, this.newValue); this.facade.getCanvas().update(); this.facade.updateSelection(); }, rollback: function () { this.shape.setProperty(this.key, this.oldValue); this.facade.getCanvas().update(); this.facade.updateSelection(); } }); // Instantiate the class var command = new commandClass(); // Execute the command $scope.editor.executeCommands([command]); $scope.editor.handleEvents({ type: ORYX.CONFIG.EVENT_PROPWINDOW_PROP_CHANGED, elements: [shape], key: key }); // Switch the property back to read mode, now the update is done property.mode = 'read'; // Fire event to all who is interested // Fire event to all who want to know about this var event = { type: KISBPM.eventBus.EVENT_TYPE_PROPERTY_VALUE_CHANGED, property: property, oldValue: oldValue, newValue: newValue }; KISBPM.eventBus.dispatch(event.type, event); } else { // Switch the property back to read mode, no update was needed property.mode = 'read'; } }; /** * Helper method that searches a group for an item with the given id. * If not found, will return undefined. */ $scope.findStencilItemInGroup = function (stencilItemId, group) { var item; // Check all items directly in this group for (var j = 0; j < group.items.length; j++) { item = group.items[j]; if (item.id === stencilItemId) { return item; } } // Check the child groups if (group.groups && group.groups.length > 0) { for (var k = 0; k < group.groups.length; k++) { item = $scope.findStencilItemInGroup(stencilItemId, group.groups[k]); if (item) { return item; } } } return undefined; }; /** * Helper method to find a stencil item. */ $scope.getStencilItemById = function (stencilItemId) { for (var i = 0; i < $scope.stencilItemGroups.length; i++) { var element = $scope.stencilItemGroups[i]; // Real group if (element.items !== null && element.items !== undefined) { var item = $scope.findStencilItemInGroup(stencilItemId, element); if (item) { return item; } } else { // Root stencil item if (element.id === stencilItemId) { return element; } } } return undefined; }; /* * DRAG AND DROP FUNCTIONALITY */ $scope.dropCallback = function (event, ui) { $scope.editor.handleEvents({ type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE, highlightId: "shapeRepo.attached" }); $scope.editor.handleEvents({ type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE, highlightId: "shapeRepo.added" }); $scope.editor.handleEvents({ type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE, highlightId: "shapeMenu" }); KISBPM.eventBus.dispatch(KISBPM.eventBus.EVENT_TYPE_HIDE_SHAPE_BUTTONS); if ($scope.dragCanContain) { var item = $scope.getStencilItemById(ui.draggable[0].id); var pos = {x: event.pageX, y: event.pageY}; var additionalIEZoom = 1; if (!isNaN(screen.logicalXDPI) && !isNaN(screen.systemXDPI)) { var ua = navigator.userAgent; if (ua.indexOf('MSIE') >= 0) { //IE 10 and below var zoom = Math.round((screen.deviceXDPI / screen.logicalXDPI) * 100); if (zoom !== 100) { additionalIEZoom = zoom / 100; } } } var screenCTM = $scope.editor.getCanvas().node.getScreenCTM(); // Correcting the UpperLeft-Offset pos.x -= (screenCTM.e / additionalIEZoom); pos.y -= (screenCTM.f / additionalIEZoom); // Correcting the Zoom-Factor pos.x /= screenCTM.a; pos.y /= screenCTM.d; // Correcting the ScrollOffset pos.x -= document.documentElement.scrollLeft; pos.y -= document.documentElement.scrollTop; var parentAbs = $scope.dragCurrentParent.absoluteXY(); pos.x -= parentAbs.x; pos.y -= parentAbs.y; var containedStencil = undefined; var stencilSets = $scope.editor.getStencilSets().values(); for (var i = 0; i < stencilSets.length; i++) { var stencilSet = stencilSets[i]; var nodes = stencilSet.nodes(); for (var j = 0; j < nodes.length; j++) { if (nodes[j].idWithoutNs() === ui.draggable[0].id) { containedStencil = nodes[j]; break; } } if (!containedStencil) { var edges = stencilSet.edges(); for (var j = 0; j < edges.length; j++) { if (edges[j].idWithoutNs() === ui.draggable[0].id) { containedStencil = edges[j]; break; } } } } if (!containedStencil) return; if ($scope.quickMenu) { var shapes = $scope.editor.getSelection(); if (shapes && shapes.length == 1) { var currentSelectedShape = shapes.first(); var option = {}; option.type = currentSelectedShape.getStencil().namespace() + ui.draggable[0].id; option.namespace = currentSelectedShape.getStencil().namespace(); option.connectedShape = currentSelectedShape; option.parent = $scope.dragCurrentParent; option.containedStencil = containedStencil; // If the ctrl key is not pressed, // snapp the new shape to the center // if it is near to the center of the other shape if (!event.ctrlKey){ // Get the center of the shape var cShape = currentSelectedShape.bounds.center(); // Snapp +-20 Pixel horizontal to the center if (20 > Math.abs(cShape.x - pos.x)){ pos.x = cShape.x; } // Snapp +-20 Pixel vertical to the center if (20 > Math.abs(cShape.y - pos.y)){ pos.y = cShape.y; } } option.position = pos; if (containedStencil.idWithoutNs() !== 'SequenceFlow' && containedStencil.idWithoutNs() !== 'Association' && containedStencil.idWithoutNs() !== 'MessageFlow' && containedStencil.idWithoutNs() !== 'DataAssociation') { var args = { sourceShape: currentSelectedShape, targetStencil: containedStencil }; var targetStencil = $scope.editor.getRules().connectMorph(args); if (!targetStencil){ return; }// Check if there can be a target shape option.connectingType = targetStencil.id(); } var command = new KISBPM.CreateCommand(option, $scope.dropTargetElement, pos, $scope.editor); $scope.editor.executeCommands([command]); } } else { var canAttach = false; if (containedStencil.idWithoutNs() === 'BoundaryErrorEvent' || containedStencil.idWithoutNs() === 'BoundaryTimerEvent' || containedStencil.idWithoutNs() === 'BoundarySignalEvent' || containedStencil.idWithoutNs() === 'BoundaryMessageEvent' || containedStencil.idWithoutNs() === 'BoundaryCancelEvent' || containedStencil.idWithoutNs() === 'BoundaryCompensationEvent') { // Modify position, otherwise boundary event will get position related to left corner of the canvas instead of the container pos = $scope.editor.eventCoordinates( event ); canAttach = true; } var option = {}; option['type'] = $scope.modelData.model.stencilset.namespace + item.id; option['namespace'] = $scope.modelData.model.stencilset.namespace; option['position'] = pos; option['parent'] = $scope.dragCurrentParent; var commandClass = ORYX.Core.Command.extend({ construct: function(option, dockedShape, canAttach, position, facade){ this.option = option; this.docker = null; this.dockedShape = dockedShape; this.dockedShapeParent = dockedShape.parent || facade.getCanvas(); this.position = position; this.facade = facade; this.selection = this.facade.getSelection(); this.shape = null; this.parent = null; this.canAttach = canAttach; }, execute: function(){ if (!this.shape) { this.shape = this.facade.createShape(option); this.parent = this.shape.parent; } else if (this.parent) { this.parent.add(this.shape); } if (this.canAttach && this.shape.dockers && this.shape.dockers.length) { this.docker = this.shape.dockers[0]; this.dockedShapeParent.add(this.docker.parent); // Set the Docker to the new Shape this.docker.setDockedShape(undefined); this.docker.bounds.centerMoveTo(this.position); if (this.dockedShape !== this.facade.getCanvas()) { this.docker.setDockedShape(this.dockedShape); } this.facade.setSelection( [this.docker.parent] ); } this.facade.getCanvas().update(); this.facade.updateSelection(); }, rollback: function(){ if (this.shape) { this.facade.setSelection(this.selection.without(this.shape)); this.facade.deleteShape(this.shape); } if (this.canAttach && this.docker) { this.docker.setDockedShape(undefined); } this.facade.getCanvas().update(); this.facade.updateSelection(); } }); // Update canvas var command = new commandClass(option, $scope.dragCurrentParent, canAttach, pos, $scope.editor); $scope.editor.executeCommands([command]); // Fire event to all who want to know about this var dropEvent = { type: KISBPM.eventBus.EVENT_TYPE_ITEM_DROPPED, droppedItem: item, position: pos }; KISBPM.eventBus.dispatch(dropEvent.type, dropEvent); } } $scope.dragCurrentParent = undefined; $scope.dragCurrentParentId = undefined; $scope.dragCurrentParentStencil = undefined; $scope.dragCanContain = undefined; $scope.quickMenu = undefined; $scope.dropTargetElement = undefined; }; $scope.overCallback = function (event, ui) { $scope.dragModeOver = true; }; $scope.outCallback = function (event, ui) { $scope.dragModeOver = false; }; $scope.startDragCallback = function (event, ui) { $scope.dragModeOver = false; $scope.quickMenu = false; if (!ui.helper.hasClass('stencil-item-dragged')) { ui.helper.addClass('stencil-item-dragged'); } }; $scope.startDragCallbackQuickMenu = function (event, ui) { $scope.dragModeOver = false; $scope.quickMenu = true; }; $scope.dragCallback = function (event, ui) { if ($scope.dragModeOver != false) { var coord = $scope.editor.eventCoordinatesXY(event.pageX, event.pageY); var additionalIEZoom = 1; if (!isNaN(screen.logicalXDPI) && !isNaN(screen.systemXDPI)) { var ua = navigator.userAgent; if (ua.indexOf('MSIE') >= 0) { //IE 10 and below var zoom = Math.round((screen.deviceXDPI / screen.logicalXDPI) * 100); if (zoom !== 100) { additionalIEZoom = zoom / 100 } } } if (additionalIEZoom !== 1) { coord.x = coord.x / additionalIEZoom; coord.y = coord.y / additionalIEZoom; } var aShapes = $scope.editor.getCanvas().getAbstractShapesAtPosition(coord); if (aShapes.length <= 0) { if (event.helper) { $scope.dragCanContain = false; return false; } } if (aShapes[0] instanceof ORYX.Core.Canvas) { $scope.editor.getCanvas().setHightlightStateBasedOnX(coord.x); } if (aShapes.length == 1 && aShapes[0] instanceof ORYX.Core.Canvas) { var parentCandidate = aShapes[0]; $scope.dragCanContain = true; $scope.dragCurrentParent = parentCandidate; $scope.dragCurrentParentId = parentCandidate.id; $scope.editor.handleEvents({ type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE, highlightId: "shapeRepo.attached" }); $scope.editor.handleEvents({ type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE, highlightId: "shapeRepo.added" }); return false; } else { var item = $scope.getStencilItemById(event.target.id); var parentCandidate = aShapes.reverse().find(function (candidate) { return (candidate instanceof ORYX.Core.Canvas || candidate instanceof ORYX.Core.Node || candidate instanceof ORYX.Core.Edge); }); if (!parentCandidate) { $scope.dragCanContain = false; return false; } if (item.type === "node") { // check if the draggable is a boundary event and the parent an Activity var _canContain = false; var parentStencilId = parentCandidate.getStencil().id(); if ($scope.dragCurrentParentId && $scope.dragCurrentParentId === parentCandidate.id) { return false; } var parentItem = $scope.getStencilItemById(parentCandidate.getStencil().idWithoutNs()); if (parentItem.roles.indexOf("Activity") > -1) { if (item.roles.indexOf("IntermediateEventOnActivityBoundary") > -1) { _canContain = true; } } else if (parentCandidate.getStencil().idWithoutNs() === 'Pool') { if (item.id === 'Lane') { _canContain = true; } } if (_canContain) { $scope.editor.handleEvents({ type: ORYX.CONFIG.EVENT_HIGHLIGHT_SHOW, highlightId: "shapeRepo.attached", elements: [parentCandidate], style: ORYX.CONFIG.SELECTION_HIGHLIGHT_STYLE_RECTANGLE, color: ORYX.CONFIG.SELECTION_VALID_COLOR }); $scope.editor.handleEvents({ type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE, highlightId: "shapeRepo.added" }); } else { for (var i = 0; i < $scope.containmentRules.length; i++) { var rule = $scope.containmentRules[i]; if (rule.role === parentItem.id) { for (var j = 0; j < rule.contains.length; j++) { if (item.roles.indexOf(rule.contains[j]) > -1) { _canContain = true; break; } } if (_canContain) { break; } } } // Show Highlight $scope.editor.handleEvents({ type: ORYX.CONFIG.EVENT_HIGHLIGHT_SHOW, highlightId: 'shapeRepo.added', elements: [parentCandidate], color: _canContain ? ORYX.CONFIG.SELECTION_VALID_COLOR : ORYX.CONFIG.SELECTION_INVALID_COLOR }); $scope.editor.handleEvents({ type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE, highlightId: "shapeRepo.attached" }); } $scope.dragCurrentParent = parentCandidate; $scope.dragCurrentParentId = parentCandidate.id; $scope.dragCurrentParentStencil = parentStencilId; $scope.dragCanContain = _canContain; } else { var canvasCandidate = $scope.editor.getCanvas(); var canConnect = false; var targetStencil = $scope.getStencilItemById(parentCandidate.getStencil().idWithoutNs()); if (targetStencil) { var associationConnect = false; if (stencil.idWithoutNs() === 'Association' && (curCan.getStencil().idWithoutNs() === 'TextAnnotation' || curCan.getStencil().idWithoutNs() === 'BoundaryCompensationEvent')) { associationConnect = true; } else if (stencil.idWithoutNs() === 'DataAssociation' && curCan.getStencil().idWithoutNs() === 'DataStore') { associationConnect = true; } if (targetStencil.canConnectTo || associationConnect) { canConnect = true; } } //Edge $scope.dragCurrentParent = canvasCandidate; $scope.dragCurrentParentId = canvasCandidate.id; $scope.dragCurrentParentStencil = canvasCandidate.getStencil().id(); $scope.dragCanContain = canConnect; // Show Highlight $scope.editor.handleEvents({ type: ORYX.CONFIG.EVENT_HIGHLIGHT_SHOW, highlightId: 'shapeRepo.added', elements: [canvasCandidate], color: ORYX.CONFIG.SELECTION_VALID_COLOR }); $scope.editor.handleEvents({ type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE, highlightId: "shapeRepo.attached" }); } } } }; $scope.dragCallbackQuickMenu = function (event, ui) { if ($scope.dragModeOver != false) { var coord = $scope.editor.eventCoordinatesXY(event.pageX, event.pageY); var additionalIEZoom = 1; if (!isNaN(screen.logicalXDPI) && !isNaN(screen.systemXDPI)) { var ua = navigator.userAgent; if (ua.indexOf('MSIE') >= 0) { //IE 10 and below var zoom = Math.round((screen.deviceXDPI / screen.logicalXDPI) * 100); if (zoom !== 100) { additionalIEZoom = zoom / 100 } } } if (additionalIEZoom !== 1) { coord.x = coord.x / additionalIEZoom; coord.y = coord.y / additionalIEZoom; } var aShapes = $scope.editor.getCanvas().getAbstractShapesAtPosition(coord); if (aShapes.length <= 0) { if (event.helper) { $scope.dragCanContain = false; return false; } } if (aShapes[0] instanceof ORYX.Core.Canvas) { $scope.editor.getCanvas().setHightlightStateBasedOnX(coord.x); } var stencil = undefined; var stencilSets = $scope.editor.getStencilSets().values(); for (var i = 0; i < stencilSets.length; i++) { var stencilSet = stencilSets[i]; var nodes = stencilSet.nodes(); for (var j = 0; j < nodes.length; j++) { if (nodes[j].idWithoutNs() === event.target.id) { stencil = nodes[j]; break; } } if (!stencil) { var edges = stencilSet.edges(); for (var j = 0; j < edges.length; j++) { if (edges[j].idWithoutNs() === event.target.id) { stencil = edges[j]; break; } } } } var candidate = aShapes.last(); var isValid = false; if (stencil.type() === "node") { //check containment rules var canContain = $scope.editor.getRules().canContain({containingShape:candidate, containedStencil:stencil}); var parentCandidate = aShapes.reverse().find(function (candidate) { return (candidate instanceof ORYX.Core.Canvas || candidate instanceof ORYX.Core.Node || candidate instanceof ORYX.Core.Edge); }); if (!parentCandidate) { $scope.dragCanContain = false; return false; } $scope.dragCurrentParent = parentCandidate; $scope.dragCurrentParentId = parentCandidate.id; $scope.dragCurrentParentStencil = parentCandidate.getStencil().id(); $scope.dragCanContain = canContain; $scope.dropTargetElement = parentCandidate; isValid = canContain; } else { //Edge var shapes = $scope.editor.getSelection(); if (shapes && shapes.length == 1) { var currentSelectedShape = shapes.first(); var curCan = candidate; var canConnect = false; var targetStencil = $scope.getStencilItemById(curCan.getStencil().idWithoutNs()); if (targetStencil) { var associationConnect = false; if (stencil.idWithoutNs() === 'Association' && (curCan.getStencil().idWithoutNs() === 'TextAnnotation' || curCan.getStencil().idWithoutNs() === 'BoundaryCompensationEvent')) { associationConnect = true; } else if (stencil.idWithoutNs() === 'DataAssociation' && curCan.getStencil().idWithoutNs() === 'DataStore') { associationConnect = true; } if (targetStencil.canConnectTo || associationConnect) { while (!canConnect && curCan && !(curCan instanceof ORYX.Core.Canvas)) { candidate = curCan; //check connection rules canConnect = $scope.editor.getRules().canConnect({ sourceShape: currentSelectedShape, edgeStencil: stencil, targetShape: curCan }); curCan = curCan.parent; } } } var parentCandidate = $scope.editor.getCanvas(); isValid = canConnect; $scope.dragCurrentParent = parentCandidate; $scope.dragCurrentParentId = parentCandidate.id; $scope.dragCurrentParentStencil = parentCandidate.getStencil().id(); $scope.dragCanContain = canConnect; $scope.dropTargetElement = candidate; } } $scope.editor.handleEvents({ type: ORYX.CONFIG.EVENT_HIGHLIGHT_SHOW, highlightId:'shapeMenu', elements: [candidate], color: isValid ? ORYX.CONFIG.SELECTION_VALID_COLOR : ORYX.CONFIG.SELECTION_INVALID_COLOR }); } }; }]); var KISBPM = KISBPM || {}; //create command for undo/redo KISBPM.CreateCommand = ORYX.Core.Command.extend({ construct: function(option, currentReference, position, facade){ this.option = option; this.currentReference = currentReference; this.position = position; this.facade = facade; this.shape; this.edge; this.targetRefPos; this.sourceRefPos; /* * clone options parameters */ this.connectedShape = option.connectedShape; this.connectingType = option.connectingType; this.namespace = option.namespace; this.type = option.type; this.containedStencil = option.containedStencil; this.parent = option.parent; this.currentReference = currentReference; this.shapeOptions = option.shapeOptions; }, execute: function(){ if (this.shape) { if (this.shape instanceof ORYX.Core.Node) { this.parent.add(this.shape); if (this.edge) { this.facade.getCanvas().add(this.edge); this.edge.dockers.first().setDockedShape(this.connectedShape); this.edge.dockers.first().setReferencePoint(this.sourceRefPos); this.edge.dockers.last().setDockedShape(this.shape); this.edge.dockers.last().setReferencePoint(this.targetRefPos); } this.facade.setSelection([this.shape]); } else if (this.shape instanceof ORYX.Core.Edge) { this.facade.getCanvas().add(this.shape); this.shape.dockers.first().setDockedShape(this.connectedShape); this.shape.dockers.first().setReferencePoint(this.sourceRefPos); } } else { this.shape = this.facade.createShape(this.option); this.edge = (!(this.shape instanceof ORYX.Core.Edge)) ? this.shape.getIncomingShapes().first() : undefined; } if (this.currentReference && this.position) { if (this.shape instanceof ORYX.Core.Edge) { if (!(this.currentReference instanceof ORYX.Core.Canvas)) { this.shape.dockers.last().setDockedShape(this.currentReference); if (this.currentReference.getStencil().idWithoutNs() === 'TextAnnotation') { var midpoint = {}; midpoint.x = 0; midpoint.y = this.currentReference.bounds.height() / 2; this.shape.dockers.last().setReferencePoint(midpoint); } else { this.shape.dockers.last().setReferencePoint(this.currentReference.bounds.midPoint()); } } else { this.shape.dockers.last().bounds.centerMoveTo(this.position); } this.sourceRefPos = this.shape.dockers.first().referencePoint; this.targetRefPos = this.shape.dockers.last().referencePoint; } else if (this.edge){ this.sourceRefPos = this.edge.dockers.first().referencePoint; this.targetRefPos = this.edge.dockers.last().referencePoint; } } else { var containedStencil = this.containedStencil; var connectedShape = this.connectedShape; var bc = connectedShape.bounds; var bs = this.shape.bounds; var pos = bc.center(); if(containedStencil.defaultAlign()==="north") { pos.y -= (bc.height() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET + (bs.height()/2); } else if(containedStencil.defaultAlign()==="northeast") { pos.x += (bc.width() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.width()/2); pos.y -= (bc.height() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.height()/2); } else if(containedStencil.defaultAlign()==="southeast") { pos.x += (bc.width() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.width()/2); pos.y += (bc.height() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.height()/2); } else if(containedStencil.defaultAlign()==="south") { pos.y += (bc.height() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET + (bs.height()/2); } else if(containedStencil.defaultAlign()==="southwest") { pos.x -= (bc.width() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.width()/2); pos.y += (bc.height() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.height()/2); } else if(containedStencil.defaultAlign()==="west") { pos.x -= (bc.width() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET + (bs.width()/2); } else if(containedStencil.defaultAlign()==="northwest") { pos.x -= (bc.width() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.width()/2); pos.y -= (bc.height() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.height()/2); } else { pos.x += (bc.width() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET + (bs.width()/2); } // Move shape to the new position this.shape.bounds.centerMoveTo(pos); // Move all dockers of a node to the position if (this.shape instanceof ORYX.Core.Node){ (this.shape.dockers||[]).each(function(docker){ docker.bounds.centerMoveTo(pos); }); } //this.shape.update(); this.position = pos; if (this.edge){ this.sourceRefPos = this.edge.dockers.first().referencePoint; this.targetRefPos = this.edge.dockers.last().referencePoint; } } this.facade.getCanvas().update(); this.facade.updateSelection(); }, rollback: function(){ this.facade.deleteShape(this.shape); if(this.edge) { this.facade.deleteShape(this.edge); } //this.currentParent.update(); this.facade.setSelection(this.facade.getSelection().without(this.shape, this.edge)); } });