/**************************************************************************** ** ** This file is part of yFiles FLEX 1.6. ** ** yWorks proprietary/confidential. Use is subject to license terms. ** ** Unauthorized redistribution of this file or of a byte-code version ** of this file is strictly forbidden. ** ** Copyright (c) 2006 - 2011 by yWorks GmbH, Vor dem Kreuzberg 28, ** 72070 Tuebingen, Germany. All rights reserved. ** ***************************************************************************/ package demo.businessProcessDiagram.bpd { import com.yworks.canvas.CanvasComponent; import com.yworks.canvas.drawing.IHitTestable; import com.yworks.canvas.geom.IRectangle; import com.yworks.canvas.geom.ISize; import com.yworks.canvas.geom.ImmutableRectangle; import com.yworks.canvas.geom.YRectangle; import com.yworks.canvas.input.EventRecognizers; import com.yworks.canvas.input.IInputModeContext; import com.yworks.canvas.input.IPositionHandler; import com.yworks.canvas.input.IReshapeHandleProvider; import com.yworks.canvas.input.IReshapeHandler; import com.yworks.canvas.input.ISizeConstraintProvider; import com.yworks.canvas.input.MoveViewportInputMode; import com.yworks.canvas.input.ReshapeHandleProvider; import com.yworks.graph.drawing.ILabelStyleRenderer; import com.yworks.graph.drawing.INodeInsetsProvider; import com.yworks.graph.drawing.SimpleLabelStyle; import com.yworks.graph.drawing.TemplateNodeStyle; import com.yworks.graph.input.GraphEditorInputMode; import com.yworks.graph.input.NodeDropInputMode; import com.yworks.graph.input.snapLines.GraphSnapContext; import com.yworks.graph.input.snapLines.OrthogonalEdgeEditingContext; import com.yworks.graph.model.DefaultGraph; import com.yworks.graph.model.EmptyPortsCandidateProvider; import com.yworks.graph.model.ExteriorLabelModel; import com.yworks.graph.model.GraphDecorator; import com.yworks.graph.model.GraphEvent; import com.yworks.graph.model.GraphEventKind; import com.yworks.graph.model.IBend; import com.yworks.graph.model.IBendList; import com.yworks.graph.model.IEdge; import com.yworks.graph.model.IEdgePortCandidateProvider; import com.yworks.graph.model.IGraph; import com.yworks.graph.model.IGroupedGraph; import com.yworks.graph.model.ILabel; import com.yworks.graph.model.ILabelModelParameter; import com.yworks.graph.model.INode; import com.yworks.graph.model.INodeHierarchy; import com.yworks.graph.model.IPortCandidateProvider; import com.yworks.graph.model.InteriorLabelModel; import com.yworks.graph.model.InteriorStretchLabelModel; import com.yworks.graph.model.RotatedSliderEdgeLabelModel; import com.yworks.graph.model.SelectionEvent; import com.yworks.io.SymbolicPackageNameRegistry; import com.yworks.io.graphml.GraphMLConstants; import com.yworks.io.graphml.reader.deserializer.FillDeserializer; import com.yworks.io.graphml.writer.serializer.FillSerializer; import com.yworks.remote.RoundtripEvent; import com.yworks.remote.RoundtripHandler; import com.yworks.remote.XMLResultEvent; import com.yworks.support.ArrayList; import com.yworks.support.DictionaryMapper; import com.yworks.support.ITagOwner; import com.yworks.support.Iterator; import com.yworks.ui.GraphCanvasComponent; import demo.businessProcessDiagram.model.BPActivity; import demo.businessProcessDiagram.model.BPArtifact; import demo.businessProcessDiagram.model.BPDeserializer; import demo.businessProcessDiagram.model.BPEvent; import demo.businessProcessDiagram.model.BPGateway; import demo.businessProcessDiagram.model.BPMNElements; import demo.businessProcessDiagram.model.BPPool; import demo.businessProcessDiagram.model.BPRelation; import demo.businessProcessDiagram.model.BPSerializer; import demo.businessProcessDiagram.model.BPSwimlane; import demo.businessProcessDiagram.model.IBPElement; import demo.input.MinimumNodeSizeConstraintProvider; import demo.input.MyMoveViewportInputMode; import demo.input.createPortRelocationHandleProvider; import flash.events.Event; import flash.events.IOErrorEvent; import mx.collections.ArrayCollection; import mx.controls.Alert; import mx.managers.CursorManager; import mx.rpc.Fault; import mx.rpc.events.FaultEvent; /** *
This class can be used to display and manipulate a business process diagram. The diagram consists of one or * several pools containing one or several swim lanes in which business process elements can be placed.
*/ public class BusinessProcessDiagramComponent extends GraphCanvasComponent { /* [Embed(systemFont='Dialog', fontName='dialog', mimeType='application/x-font', embedAsCFF='false', unicodeRange="U+0020-U+007E,U+00A0-U+00FF")] [Embed(systemFont='MS 明朝', fontName='MS 明朝', mimeType='application/x-font', embedAsCFF='false', unicodeRange="U+0020-U+007E,U+00A0-U+00FF")] */ private var Dialog:Class; public static const POOL_POSITION_KEY:String = "pool_position"; public static const SWIM_LANE_POSITION_KEY:String = "swimlane_position"; private static const MIN_POOL_HEIGHT_KEY:String = "min_pool_height"; private static const MIN_POOL_WIDTH_KEY:String = "min_pool_width"; private static const VERTICAL_POOL_DISTANCE_KEY:String = "vertical_pool_distance"; // ArrayList of the pool nodes. private var _pools:ArrayList = new ArrayList(); // Indicates if the order of the pools is in sync with their position in the pool position mapper. private var _poolsDirty:Boolean; // BPPool -> INode private var _poolMapper:DictionaryMapper = new DictionaryMapper(); // BPSwimlane -> INode private var _swimLaneMapper:DictionaryMapper = new DictionaryMapper(); public var _initialPoolToPoolDistance:uint = 100; public var _initialMinimalSwimLaneHeight:uint = 200; public var _initialMinimalSwimLaneWidth:uint = 800; private var _poolBoundsUpdater:PoolBoundsUpdater; private var _loadableGraphList:ArrayCollection; private var layoutHandler:RoundtripHandler; private var saveHandler:RoundtripHandler; private var loadHandler:RoundtripHandler; private var updateGraphListHandler:RoundtripHandler; private static const NEW_DIAGRAM_FILE:String = "ANewDiagram"; public static const NEW_DIAGRAM_LABEL:String = "New Diagram"; /** * Initializes a new BusinessProcessDiagramComponent. */ public function BusinessProcessDiagramComponent() { initGrouping(); initInputModes(); initDefaultStyles(); initGraph(); } /** * Initializes grouping related issues. */ protected function initGrouping():void { /* Allow grouping */ DefaultGraph(graph).groupingSupported = true; var gg:IGroupedGraph = graph.lookup(IGroupedGraph) as IGroupedGraph; // the bounds of group nodes shall not be adjusted automatically. The adjustment is done by the PoolBoundsUpdater. gg.autoAdjustGroupNodeBounds = false; this._poolBoundsUpdater = new PoolBoundsUpdater(this); graph.mapperRegistry.addMapper(BusinessProcessDiagramComponent.POOL_POSITION_KEY, new DictionaryMapper()); graph.mapperRegistry.addMapper(BusinessProcessDiagramComponent.SWIM_LANE_POSITION_KEY, new DictionaryMapper()); graph.mapperRegistry.addMapper(BusinessProcessDiagramComponent.MIN_POOL_HEIGHT_KEY, new DictionaryMapper()); graph.mapperRegistry.addMapper(BusinessProcessDiagramComponent.MIN_POOL_WIDTH_KEY, new DictionaryMapper()); graph.mapperRegistry.addMapper(BusinessProcessDiagramComponent.VERTICAL_POOL_DISTANCE_KEY, new DictionaryMapper()); poolToPoolDistance = _initialPoolToPoolDistance; minimalSwimLaneHeight = _initialMinimalSwimLaneHeight; minimalSwimLaneWidth = _initialMinimalSwimLaneWidth; } /** * Initializes the InputModes. */ protected function initInputModes():void { // Swim lane and pool nodes need to receive mouse click events for their buttons enableMouseChildren = CanvasComponent.MOUSE_CHILDREN_ALWAYS; var snapContext:GraphSnapContext = new GraphSnapContext(); snapContext.collectNodePairSnapLines = false; var mainInputMode:GraphEditorInputMode = new BPGraphEditorInputMode(graph, selection); var moveViewportInputMode:MoveViewportInputMode = new MyMoveViewportInputMode(); mainInputMode.addConcurrent(moveViewportInputMode, 0); inputMode = mainInputMode; mainInputMode.groupSelectionAllowed = false; mainInputMode.ungroupSelectionAllowed = false; mainInputMode.clipboardOperationsAllowed = false; mainInputMode.autoAdjustContentRect = true; mainInputMode.autoRemoveEmptyLabels = false; mainInputMode.nodeCreator = null; // new nodes shall be created via drag'n'drop mainInputMode.createEdgeInputMode.connectToCandidatesOnly = true; mainInputMode.createEdgeInputMode.orthogonalEdgeCreation = true; mainInputMode.orthogonalEdgeEditingContext = new OrthogonalEdgeEditingContext(); // A DropInputMode is used to start edge creation by dragging a relation from the palette var dropEdgeInputMode:DropEdgeInputMode = new DropEdgeInputMode(); dropEdgeInputMode.orthogonalEdgeCreation = true; mainInputMode.addConcurrent(dropEdgeInputMode, mainInputMode.createEdgeModePriority - 10); mainInputMode.moveInputMode.positionHandler = new PoolAdjustingPositionHandlerWrapper(mainInputMode.moveInputMode.positionHandler); mainInputMode.snapContext = snapContext; var nodeDropInputMode:NodeDropInputMode = new BPMNNodeDropInputMode("items"); nodeDropInputMode.validDropHitTestable = new ValidNodeDropHitTestable(this); nodeDropInputMode.nodeDropCreator = nodeDropCreator; mainInputMode.addConcurrent(nodeDropInputMode, -5); mouseWheelZoomFactor = 1.07; mouseWheelZoomCenteredRecognizer = EventRecognizers.MODIFIER_CTRL; mouseWheelZoomMouseLocationRecognizer = EventRecognizers.NO_MODIFIER_PRESSED; var graphDecorator:GraphDecorator = graph.lookup(GraphDecorator) as GraphDecorator; // Allow port/edge relocation graphDecorator.edgeDecorator.handleProviderDecorator.setFactory(createPortRelocationHandleProvider); // Allow to connect edges to ports of other nodes graphDecorator.edgeDecorator.edgePortCandidateProviderDecorator.setFactory( function (edge:IEdge):IEdgePortCandidateProvider { return new BPMNEdgePortCandidateProvider(edge); }); // Return a set of ports for a node depending on the edge that would connect to it. graphDecorator.nodeDecorator.portCandidateProviderDecorator.setFactory( function(node:INode):IPortCandidateProvider { return new BPMNPortCandidateProvider(node); }, function (node:INode):Boolean { var tagOwner:ITagOwner = node.lookup(ITagOwner) as ITagOwner; return tagOwner != null && tagOwner.tag is IBPElement; }); // No ports shall be returned for swim lane and pool nodes graphDecorator.nodeDecorator.portCandidateProviderDecorator.setFactory( function(node:INode):IPortCandidateProvider { return new EmptyPortsCandidateProvider(); }, function (node:INode):Boolean { var tagOwner:ITagOwner = node.lookup(ITagOwner) as ITagOwner; return tagOwner != null && (tagOwner.tag is BPPool || tagOwner.tag is BPSwimlane); }); /* Let the swim lane label contribute to its insets */ graphDecorator.nodeDecorator.insetsProviderDecorator.setFactory( function(node:INode):INodeInsetsProvider { return new SimpleInsetsProvider(new ImmutableRectangle(65, 5, 5, 5)); },function(node:INode):Boolean { var tagOwner:ITagOwner = node.lookup(ITagOwner) as ITagOwner; return tagOwner != null && tagOwner.tag is BPSwimlane; }); /* Let the pool node label contribute to its insets */ graphDecorator.nodeDecorator.insetsProviderDecorator.setFactory( function(node:INode):INodeInsetsProvider { return new SimpleInsetsProvider(new ImmutableRectangle(60, 1, 1, 1)); },function(node:INode):Boolean { var tagOwner:ITagOwner = node.lookup(ITagOwner) as ITagOwner; return tagOwner != null && tagOwner.tag is BPPool; }); // connects a PositionHandler to the nodes which adjust the nodes' locations after a reparenting gesture. graphDecorator.nodeDecorator.positionHandlerDecorator.setImplementationWrapper( function(node:INode, wrapped:IPositionHandler):IPositionHandler { return new ReparentingPositionHandlerWrapper(node, wrapped); }, function(node:INode):Boolean { var tagOwner:ITagOwner = node.lookup(ITagOwner) as ITagOwner; return tagOwner != null && tagOwner.tag is IBPElement; }); // connects a ReshapeHandleProvider to the nodes which adjusts the pool bounds after resizing graphDecorator.nodeDecorator.reshapeHandleProviderDecorator.setImplementationWrapper( function(node:INode, wrapped:IReshapeHandleProvider):IReshapeHandleProvider { if (wrapped is ReshapeHandleProvider) { var rhp:ReshapeHandleProvider = ReshapeHandleProvider(wrapped); var sc:ISizeConstraintProvider = new MinimumNodeSizeConstraintProvider(node.lookup(ISizeConstraintProvider) as ISizeConstraintProvider); if (sc != null) { rhp.maximumSize = sc.getMaximumSize(node); rhp.minimumSize = sc.getMinimumSize(node); rhp.minimumEnclosedArea = sc.getMinimumEnclosedArea(node); } } return new PoolAdjustingReshapeHandleProvider(wrapped); }); // no reshape handler shall be used for the group nodes var nh:INodeHierarchy = graph.lookup(INodeHierarchy) as INodeHierarchy; graphDecorator.nodeDecorator.reshapeHandlerDecorator.setImplementationWrapper(function(node:INode, wrapped:IReshapeHandler):IReshapeHandler { if (nh.isLeaf(node)) { return wrapped; } return null; }); // triggers opening the connected URLS if a node is double clicked inputModes.addItem(new BPMNDblClickInputMode()); selection.addEventListener(SelectionEvent.SELECT, selectionFilter); selection.addEventListener(SelectionEvent.SELECT, onNodeSelected); graph.addEventListener(GRAPH_CHANGED, onLabelChanged); if (graph is DefaultGraph) { DefaultGraph(graph).autoAdjustPreferredLabelSize = false; } // add event listener for SwimLaneEvent and PoolEvent which are dispatched by the SwimLaneTemplate and // PoolTemplate if any of their buttons are clicked addEventListener(SwimLaneEvent.ADD_SWIM_LANE, newSwimLaneButtonClicked); addEventListener(SwimLaneEvent.REMOVE_SWIM_LANE, removeSwimLaneButtonClicked); addEventListener(PoolEvent.ADD_POOL, newPoolButtonClicked); addEventListener(PoolEvent.REMOVE_POOL, removePoolButtonClicked); } /** * Called by the BPMNNodeDropInputMode when data is dropped onto the component. */ protected function nodeDropCreator(context:IInputModeContext, graph:IGraph, draggedNode:INode, newNodeLayout:IRectangle):INode { var swimLane:INode = getSwimLaneNodeAtCursor(newNodeLayout.x + newNodeLayout.width/2, newNodeLayout.y + newNodeLayout.height/2); if (swimLane != null) { var newX:Number = newNodeLayout.x; var newY:Number = newNodeLayout.y; // calculate swim lane content area var inip:INodeInsetsProvider = swimLane.lookup(INodeInsetsProvider) as INodeInsetsProvider; var insets:IRectangle; if (inip != null) { insets = inip.getInsets(swimLane); } else { insets = new YRectangle(0, 0, 0, 0); } var swimLaneContentArea:IRectangle = new YRectangle( swimLane.layout.x + insets.x, swimLane.layout.y + insets.y, swimLane.layout.x + swimLane.layout.width - insets.width, swimLane.layout.y + swimLane.layout.height - insets.height); // make sure the new node will be placed inside its containing swim lane if (newNodeLayout.x < swimLaneContentArea.x) { newX = swimLaneContentArea.x; } else if (newNodeLayout.x + newNodeLayout.width > swimLaneContentArea.width) { newX -= newNodeLayout.x + newNodeLayout.width - swimLaneContentArea.width; } if (newNodeLayout.y < swimLaneContentArea.y) { newY = swimLaneContentArea.y; } else if (newNodeLayout.y + newNodeLayout.height > swimLaneContentArea.height) { newY -= newNodeLayout.y + newNodeLayout.height - swimLaneContentArea.height; } // create node var bpmnStyle:BPMNNodeStyle = draggedNode.style as BPMNNodeStyle; if (bpmnStyle != null) { return createNode(BPMNElements.createBPElement(bpmnStyle.shapeNodeShape), YRectangle.create(newX, newY, newNodeLayout.width, newNodeLayout.height), bpmnStyle); } } return null; } /** * Initializes the default styles and layout related configurations. */ protected function initDefaultStyles():void { graph.defaultNodeStyle = new BPMNNodeStyle(BPMNElements.TASK); graph.defaultEdgeStyle = new BPMNEdgeStyle(BPRelation.SEQUENCE_FLOW); var labelStyle:SimpleLabelStyle = new SimpleLabelStyle(); labelStyle.textFormat.font = "dialog"; graph.defaultNodeLabelStyle = labelStyle; graph.defaultEdgeLabelStyle = labelStyle; graph.defaultNodeLabelModelParameter = InteriorStretchLabelModel.center; var edgeLabelModel:RotatedSliderEdgeLabelModel = new RotatedSliderEdgeLabelModel(-10,0,true,false); graph.defaultEdgeLabelModelParameter = edgeLabelModel.createDefaultParameter(); /* Set the default style for group nodes */ var gg:IGroupedGraph = graph.lookup(IGroupedGraph) as IGroupedGraph; if (gg != null) { gg.defaultGroupNodeStyle = new TemplateNodeStyle(SwimLaneTemplate); } } /** * Initializes the graph by adding a pool node containing a swim lane and fits the Canvas to the new graph bounds. */ protected function initGraph():void { addPool("Pool 0", 0); fitGraphBounds(); } /** * Sets graph.defaultNodeLabelModelParameter according to the type of the selected node's tag. * This is done because if a new Label is created for the node viaF2
this label will be
* positioned correctly concerning the nodes IBPElement type.
* @param evt
*/
protected function onNodeSelected(evt:SelectionEvent):void {
var node:INode = evt.item as INode;
if (node != null) {
var tagOwner:ITagOwner = node.lookup(ITagOwner) as ITagOwner;
if (tagOwner != null) {
var bpElement:IBPElement = tagOwner.tag as IBPElement;
if (bpElement != null) {
if (bpElement.type == BPMNElements.ARTIFACT_TEXT_ANNOTATION) {
graph.defaultNodeLabelModelParameter = InteriorLabelModel.center;
} else if (bpElement is BPGateway
|| bpElement is BPEvent) {
graph.defaultNodeLabelModelParameter = ExteriorLabelModel.south;
} else if (bpElement is BPActivity
|| bpElement.type == BPMNElements.ARTIFACT_DATA_OBJECT) {
var islm:InteriorStretchLabelModel = new InteriorStretchLabelModel();
islm.insets = new YRectangle(5, 5, 0, 0);
graph.defaultNodeLabelModelParameter = islm.createParameter(InteriorStretchLabelModel.POSITION_CENTER);
}
}
}
}
}
/**
* Deselects a newly selected node if its tag is of type BPSwimlane
or BPPool
.
* @param evt
*/
protected function selectionFilter(evt:SelectionEvent):void {
var node:INode = evt.item as INode;
if (node != null) {
var tagOwner:ITagOwner = node.lookup(ITagOwner) as ITagOwner;
if (tagOwner != null &&
(tagOwner.tag is BPSwimlane ||
tagOwner.tag is BPPool)) {
selection.setNodeSelected(node, false);
}
}
}
/*
* Update the label's preferred size to compensate different font sizes when zooming.
* Update the node's size if its tag is a BPArtifact with type == BPMNElements.ARTIFACT_TEXT_ANNOTATION
* to fit the label.
*/
protected function onLabelChanged(evt:GraphEvent):void {
if (evt.kind == GraphEventKind.LABEL_CHANGED
|| evt.kind == GraphEventKind.LABEL_ADDED) {
var label:ILabel = evt.item as ILabel;
var prefSize:ISize = label.preferredSize as ISize;
var prefRendererSize:ISize = ILabelStyleRenderer(label.style.styleRenderer).getPreferredSize(label, label.style);
var newWidth:uint = prefRendererSize.width * 1.06 + 5;
var newHeight:uint = prefRendererSize.height * 1.06 + 5;
if (prefSize.width != newWidth || prefSize.height != newHeight) {
graph.setPreferredSize(label, newWidth, newHeight);
}
var node:INode = label.owner as INode;
if (node != null) {
var tagOwner:ITagOwner = node.lookup(ITagOwner) as ITagOwner;
if (tagOwner != null) {
var bpElement:IBPElement = tagOwner.tag as IBPElement;
if (bpElement is BPArtifact && bpElement.type
== BPMNElements.ARTIFACT_TEXT_ANNOTATION) {
newWidth = (newWidth < 100) ? 100 : newWidth;
newHeight = (newHeight < 25) ? 25 : newHeight;
if (node.layout.width != newWidth || node.layout.height != newHeight) {
graph.setBounds(node, node.layout.x, node.layout.y, newWidth, newHeight);
}
}
}
}
}
}
/*
* Configure the roundtrips and updates the list of stored graphs.
*/
public function configure():void {
// use a custom fault event handler for service requests.
RoundtripHandler.globalFaultEventHandler = faultHandler;
SymbolicPackageNameRegistry.add("demo.businessProcessDiagram.bpd", "http://yworks.com/yfiles-flex/bpddemo/bpd");
configureLayoutHandler();
configureSaveHandler();
configureLoadHandler();
configureUpdateGraphListHandler();
updateGraphList();
}
protected function configureLayoutHandler():void {
layoutHandler = new RoundtripHandler(this, "businessProcessDiagram");
layoutHandler.sendOptions = GraphCanvasComponent.OPTION_COPY_NONE;
layoutHandler.updateOptions = GraphCanvasComponent.OPTION_COPY_NONE;
configureBaseHandler(layoutHandler);
layoutHandler.addEventListener(RoundtripEvent.ROUNDTRIP_COMPLETE, onLayoutRoundtripComplete);
var params:Object = new Object();
params.action = "layout";
layoutHandler.additionalParameters = params;
}
protected function configureSaveHandler():void {
saveHandler = new RoundtripHandler(this, "businessProcessDiagram");
saveHandler.sendOptions = GraphCanvasComponent.OPTION_COPY_NODE_STYLE;
saveHandler.updateOptions = GraphCanvasComponent.OPTION_COPY_NONE;
configureBaseHandler(saveHandler);
saveHandler.addEventListener(RoundtripEvent.ROUNDTRIP_COMPLETE, onSaveRoundtripComplete);
var params:Object = new Object();
params.action = "save";
saveHandler.additionalParameters = params;
}
protected function configureLoadHandler():void {
loadHandler = new RoundtripHandler(this, "businessProcessDiagram");
loadHandler.sendOptions = GraphCanvasComponent.OPTION_COPY_NONE;
loadHandler.updateOptions = GraphCanvasComponent.OPTION_COPY_ALL;
configureBaseHandler(loadHandler);
loadHandler.addEventListener(RoundtripEvent.ROUNDTRIP_COMPLETE, onLoadRoundtripComplete);
var params:Object = new Object();
params.action = "load";
loadHandler.additionalParameters = params;
loadHandler.animate = false;
}
protected function configureBaseHandler(handler:RoundtripHandler):void {
handler.addMapperAttribute(BusinessProcessDiagramComponent.POOL_POSITION_KEY,
BusinessProcessDiagramComponent.POOL_POSITION_KEY,
GraphMLConstants.SCOPE_NODE, GraphMLConstants.TYPE_STRING);
handler.addMapperAttribute(BusinessProcessDiagramComponent.SWIM_LANE_POSITION_KEY,
BusinessProcessDiagramComponent.SWIM_LANE_POSITION_KEY,
GraphMLConstants.SCOPE_NODE, GraphMLConstants.TYPE_STRING);
handler.addMapperAttribute(BusinessProcessDiagramComponent.MIN_POOL_WIDTH_KEY,
BusinessProcessDiagramComponent.MIN_POOL_WIDTH_KEY,
GraphMLConstants.SCOPE_GRAPH, GraphMLConstants.TYPE_INT);
handler.addMapperAttribute(BusinessProcessDiagramComponent.MIN_POOL_HEIGHT_KEY,
BusinessProcessDiagramComponent.MIN_POOL_HEIGHT_KEY,
GraphMLConstants.SCOPE_GRAPH, GraphMLConstants.TYPE_INT);
handler.addMapperAttribute(BusinessProcessDiagramComponent.VERTICAL_POOL_DISTANCE_KEY,
BusinessProcessDiagramComponent.VERTICAL_POOL_DISTANCE_KEY,
GraphMLConstants.SCOPE_GRAPH, GraphMLConstants.TYPE_INT);
handler.addSerializer(new BPSerializer());
handler.addDeserializer(new BPDeserializer());
handler.addSerializer(new FillSerializer());
handler.addDeserializer(new FillDeserializer());
handler.inputIOHandler.supportUserTags = true;
handler.outputIOHandler.supportUserTags = true;
handler.outputIOHandler.writeLabels = true;
handler.inputIOHandler.readLabels = true;
// The following lines can be inserted to trace the graphML returned from the server for debugging reasons
// handler.addEventListener(Event.COMPLETE, function(ev:XMLResultEvent):void {
// trace("GraphML sent by the server:\n" + ev.xmlResult);
// });
}
protected function configureUpdateGraphListHandler():void {
// RoundtripHandler that receives a list of available graphs from the server.
updateGraphListHandler = new RoundtripHandler(this, "listBPMGraphMLGraphs");
updateGraphListHandler.send = false;
updateGraphListHandler.update = false;
updateGraphListHandler.addEventListener(Event.COMPLETE, graphListReceived);
}
/**
* Custom fault handler for service requests
*/
protected function faultHandler(evt:FaultEvent):void {
var buf:String = "";
var titleString:String = "Service request failed";
var fault:Fault = evt.fault;
var ioErrorEvent:IOErrorEvent = fault.rootCause as IOErrorEvent;
if (null != ioErrorEvent) {
buf += "Please make sure that the server is up and running and that the URL " +
"specified in the error details below is correct.\n" +
"The base URL that is used for resolving service requests can be changed in the " +
"config.xml file.\n\n";
}
buf += "Error details: \n\n";
buf += fault.faultDetail != null ? fault.faultDetail : fault.faultString;
Alert.show(buf, titleString);
CursorManager.removeBusyCursor();
}
/**
* Run a layout roundtrip
*/
public function doLayout():void {
layoutHandler.run();
}
/**
* Called when a layout roundtrip is complete to fix the swimlane and pool position mapper.
* @param evt
*/
protected function onLayoutRoundtripComplete(evt:RoundtripEvent):void {
setPoolsDirty();
}
/**
* Saves the graph on the server under the given fileName
* @param fileName
*/
public function saveBusinessGraph(fileName:String):void {
saveHandler.additionalParameters.fileName = fileName;
saveHandler.run();
}
/**
* Saves the graph to a XML that is returned.
*/
public function saveBusinessGraphToXML():XML {
return saveHandler.outputIOHandler.write(graph);
}
/**
* Updates the list of stored graphs after a save roundtrip had taken place.
* @param evt
*/
protected function onSaveRoundtripComplete(evt:RoundtripEvent):void {
updateGraphList();
}
/**
* Loads the graph with the given fileName from the server
* @param fileName
*/
public function loadBusinessGraph(fileName:String):void {
loadHandler.additionalParameters.fileName = fileName == NEW_DIAGRAM_LABEL ? NEW_DIAGRAM_FILE : fileName;
loadHandler.run();
}
/**
* Loads the graph from the given XML. The XML is assumed to represent a GraphML graph which has all
* data elements attached that would also be written by the saveBusinessGraphToXML method for this graph.
* @param xml The graphML graph to load.
*/
public function loadBusinessGraphFromXML(xml:XML):void {
loadHandler.handleResult(xml);
}
/**
* Updates the pool and swim lane maps.
*/
protected function onLoadRoundtripComplete(evt:RoundtripEvent):void {
setPoolsDirty();
this._poolMapper.clear();
this._swimLaneMapper.clear();
this.selection.clear();
var nodeIt:Iterator = graph.nodes.iterator();
while (nodeIt.hasNext()) {
var node:INode = nodeIt.next() as INode;
var tagOwner:ITagOwner = node.lookup(ITagOwner) as ITagOwner;
if (tagOwner != null
&& tagOwner.tag != null) {
if (tagOwner.tag is BPSwimlane) {
this._swimLaneMapper.mapValue(tagOwner.tag, node);
} else if (tagOwner.tag is BPPool) {
this._poolMapper.mapValue(tagOwner.tag, node);
}
}
}
fixNewLinesInLabels();
}
/**
* Updates loadableGraphs
using a roundtrip.
*/
public function updateGraphList():void {
updateGraphListHandler.run();
}
/**
* Called when the response for the load graphs request arrives.
* Fills the combobox with a list of available graphs.
*/
private function graphListReceived(evt:XMLResultEvent):void {
var ac:ArrayCollection = new ArrayCollection();
var result:XML = evt.xmlResult;
var files:XMLList = result..file;
for (var i:int = 0; i < files.length(); i++) {
var child:XML = files[ i ];
var label:String = child.@label[0];
if (label == NEW_DIAGRAM_FILE) label = NEW_DIAGRAM_LABEL;
ac.addItem({path:child.@path[0],label:label});
}
loadableGraphs = ac;
dispatchEvent(new BPDemoEvent(BPDemoEvent.BP_GRAPH_LIST_UPDATED));
}
/*
* Due to a flex bug, '\r\n' are interpreted as two new lines in a label. Call this function
* to change '\r\n' into '\n'
* @return
*/
private function fixNewLinesInLabels():void {
var brokenPattern:RegExp = /\r\n/g;
var repairedString:String = "\n";
var it:Iterator = graph.nodeLabels.iterator();
while (it.hasNext()) {
var label:ILabel = it.next() as ILabel;
var oldPreferredSize:ISize = label.preferredSize;
var labelText:String = label.text;
labelText = labelText.replace(brokenPattern, repairedString);
graph.setLabelText(label, labelText);
graph.setPreferredSize(label, oldPreferredSize.width, oldPreferredSize.height);
}
}
/**
* Creates a new node and connects it to the swim lane below this position. If there is no swim lane below the
* given position, no node is created.
* @param bpElement The IBPElement
this node shall represent.
* @param bounds The bounds of the new node.
* @param style A BPMNNodeStyle
can be passed to customize the look of the new node. The values
* of style.shapeNodeShape and bpElement.type has to match if a style is passed. The width and height of the
* style are ignored as those of the given bounds are used.
* @return The newly created node.
*/
public function createNode(bpElement:IBPElement, bounds:IRectangle, style:BPMNNodeStyle = null):INode {
if (bpElement == null ||
(bpElement != null && style != null && bpElement.type != style.shapeNodeShape)) {
return null;
}
if (bpElement.url == null) {
bpElement.url = "http://www.yworks.com";
}
var swimLaneNode:INode = getSwimLaneNodeAtCursor(bounds.x, bounds.y);
var newNode:INode = graph.createNode(bounds, style);
var tagOwner:ITagOwner = newNode.lookup(ITagOwner) as ITagOwner;
if (tagOwner != null) {
tagOwner.tag = bpElement;
}
var gg:IGroupedGraph = graph.lookup(IGroupedGraph) as IGroupedGraph;
if (swimLaneNode != null && gg != null) {
gg.setParent(newNode, swimLaneNode);
}
// create label for Text Annotation
if (bpElement.type == BPMNElements.ARTIFACT_TEXT_ANNOTATION) {
var lmp:ILabelModelParameter = InteriorLabelModel.center; //InteriorStretchLabelModel.center;
graph.addLabel(newNode, "Annotation", lmp, graph.defaultNodeLabelStyle);
}
return newNode;
}
/**
* Returns the INode
of the swim lane or pool at the given view coordinates
* if there is any or null otherwise.
* @param x horizontal coordinate
* @param y vertical coordinate
* @return Returns the INode
of the swim lane or pool at the given view coordinates
* if there is any or null otherwise.
*/
public function getSwimLaneNodeAtCursor(x:Number, y:Number):INode {
var swimLaneNode:INode = null;
var gg:IGroupedGraph = graph.lookup(IGroupedGraph) as IGroupedGraph;
if (gg == null) return null;
var poolIt:Iterator = pools.iterator();
endLoops: while (poolIt.hasNext()) {
var pool:INode = poolIt.next() as INode;
var swimLaneIt:Iterator = gg.hierarchy.getChildren(pool).iterator();
while (swimLaneIt.hasNext()) {
var swimLane:INode = swimLaneIt.next() as INode;
var ht:IHitTestable = swimLane.lookup(IHitTestable) as IHitTestable;
var tagOwner:ITagOwner = swimLane.lookup(ITagOwner) as ITagOwner;
if (null != ht
&& tagOwner != null) {
if (ht.isHit(x, y, this.context)
&& tagOwner.tag is BPSwimlane) {
swimLaneNode = swimLane;
break endLoops;
}
}
}
}
return swimLaneNode;
}
/**
* Adds a new pool node to the graph and returns its corresponding BPPool
object. A swim lane node
* is added to the new pool as no empty pool may exist.
* @param name The name of the pool.
* @param position A value between 0 and the number of existing pools. If the given position is outside this
* range, no node is created and null
is returned. Otherwise all pools with a position greater than or
* equal to the given one are moved downwards and the new pool is inserted at this position.
* @return The BPPool
object corresponding to the new pool or null
if no pool has been added.
*/
public function addPool(name:String, position:uint):BPPool {
if (position > this.pools.length()) return null;
var pool:INode = createPoolNode(position);
var bpPool:BPPool = new BPPool();
bpPool.name = name;
this._poolMapper.mapValue(bpPool, pool);
updatePoolPositionMapper(position, 1);
graph.mapperRegistry.getMapper(BusinessProcessDiagramComponent.POOL_POSITION_KEY).mapValue(pool, position);
setPoolsDirty(); // as the position mapper has been modified, the property pools is no longer in sync with it.
poolBoundsUpdater.updatePoolsVertical();
var tagOwner:ITagOwner = pool.lookup(ITagOwner) as ITagOwner;
if (tagOwner != null) {
tagOwner.tag = bpPool;
}
addSwimLane(bpPool, "Swim Lane 0", 0); // there may be no empty pool so add a new swim lane
return bpPool;
}
/*
* Updates the mapper holding the positions of the pools. All pools with a position greater than or equal to
* fromPosition are increased by diff.
*/
private function updatePoolPositionMapper(fromPosition:uint, diff:int):void {
var poolPositions:DictionaryMapper = graph.mapperRegistry.getMapper(POOL_POSITION_KEY) as DictionaryMapper;
var poolsIt:Iterator = poolPositions.keys();
while (poolsIt.hasNext()) {
var pool:Object = poolsIt.next();
var poolPositionObject:Object = poolPositions.lookupValue(pool);
if (poolPositionObject != null) {
var poolPos:int = int(poolPositionObject);
if (poolPos >= fromPosition) {
poolPositions.mapValue(pool, poolPos + diff);
}
}
}
}
/*
* Called by addPool to create a node to visualize a new pool.
*/
private function createPoolNode(position:uint):INode {
var gg:IGroupedGraph = graph.lookup(IGroupedGraph) as IGroupedGraph;
var node:INode;
if (gg != null) {
var x:Number;
var y:Number;
var width:Number;
var height:Number = minimalSwimLaneHeight;
if (this.pools.length() == 0) {
// this should only occur on initialization as after that there always has to be at least one pool
x = 0;
y = 0;
width = minimalSwimLaneWidth;
} else {
var pool0:INode = this.pools.getItemAt(0) as INode;
x = pool0.layout.x;
width = pool0.layout.width;
if (this.pools.length() > position) {
y = (this.pools.getItemAt(position) as INode).layout.y;
} else {
var poolOnTop:INode = this.pools.getItemAt(position - 1) as INode;
y = poolOnTop.layout.y + poolOnTop.layout.height + poolToPoolDistance;
}
}
var poolNodeStyle:TemplateNodeStyle = new TemplateNodeStyle(PoolTemplate);
node = gg.createGroupNode(null, new ImmutableRectangle(x, y, width, height), poolNodeStyle);
}
return node;
}
/**
* Adds a new swim lane node to the graph and returns it's corresponding BPSwimlane
object. The node
* corresponding to the given BPPool
is set as parent node of the new swim lane node. If no
* INode
can be found that has the BPPool
as its tag, no swim lane is created and null is
* returned.
* @param pool The BPPool which this swim lane should be assigned to.
* @param name The name of new swim lane.
* @param position A value between 0 and the number of existing swim lanes in the given pool. If the given position
* is outside this range, no node is created and null
is returned. Otherwise all swim lanes with a
* position greater than or equal to the given one are moved downwards and the new swim lane is inserted at this position.
* @return The BPSwimlane object corresponding to the new swim lane or null
if no swim lane has been added.
*/
public function addSwimLane(pool:BPPool, name:String, position:uint):BPSwimlane {
var poolNode:INode = getPoolNode(pool);
var gg:IGroupedGraph = graph.lookup(IGroupedGraph) as IGroupedGraph;
if (poolNode == null || gg == null) return null;
var inip:INodeInsetsProvider = poolNode.lookup(INodeInsetsProvider) as INodeInsetsProvider;
var poolInsets:IRectangle;
if (inip == null) {
poolInsets = new YRectangle(0, 0, 0, 0);
} else {
poolInsets = inip.getInsets(poolNode) as IRectangle;
}
if (gg.hierarchy.getChildCount(poolNode) < position) {
return null;
}
var y:Number;
var swimLanes:Array = getSwimLanesOfPool(pool);
if (swimLanes == null) {
return null;
} else {
if (position >= swimLanes.length) {
// take bottom of last swim lane
var relSwimLane:INode = _swimLaneMapper.lookupValue(swimLanes[swimLanes.length - 1]) as INode;
if (relSwimLane != null) {
y = relSwimLane.layout.y + relSwimLane.layout.height;
} else {
y = poolNode.layout.y + poolInsets.y;
}
} else {
relSwimLane = _swimLaneMapper.lookupValue(swimLanes[position]) as INode;
if (relSwimLane != null) {
y = relSwimLane.layout.y;
}
}
}
var width:Number = poolNode.layout.width - poolInsets.x - poolInsets.width;
var bounds:ImmutableRectangle = new ImmutableRectangle(poolNode.layout.x + poolInsets.x,
y,
width, minimalSwimLaneHeight);
var swimLane:INode = createSwimLane(bounds, poolNode);
// update swim lane position properties
var absoluteSwimLanePosition:uint = getAbsoluteSwimLanePosition(poolNode, position);
updateSwimLanePositionMapper(absoluteSwimLanePosition, 1);
graph.mapperRegistry.getMapper(BusinessProcessDiagramComponent.SWIM_LANE_POSITION_KEY).mapValue(swimLane, absoluteSwimLanePosition);
poolBoundsUpdater.updatePoolsVertical();
var bpSwimlane:BPSwimlane = new BPSwimlane();
bpSwimlane.name = name;
var swimLaneTagOwner:ITagOwner = swimLane.lookup(ITagOwner) as ITagOwner;
if (swimLaneTagOwner != null) {
swimLaneTagOwner.tag = bpSwimlane;
}
this._swimLaneMapper.mapValue(bpSwimlane, swimLane);
return bpSwimlane;
}
/*
* Converts the relative position of a swim lane in a pool to the absolute position of this swim lane by adding
* the number of swim lanes in pools on top of the given one.
*/
private function getAbsoluteSwimLanePosition(poolNode:INode, position:uint):uint {
var gg:IGroupedGraph = graph.lookup(IGroupedGraph) as IGroupedGraph;
var poolPositionMapper:DictionaryMapper = graph.mapperRegistry.getMapper(BusinessProcessDiagramComponent.POOL_POSITION_KEY) as DictionaryMapper;
var absPosition:uint = 0;
var poolPosition:int = int(poolPositionMapper.lookupValue(poolNode));
var poolIt:Iterator = pools.iterator();
while (poolIt.hasNext()) {
var pool:INode = poolIt.next() as INode;
if (int(poolPositionMapper.lookupValue(pool)) < poolPosition) {
absPosition += gg.hierarchy.getChildCount(pool);
}
}
return absPosition += position;
}
public function getSwimLanesOfPool(pool:BPPool):Array {
var poolNode:INode = this._poolMapper.lookupValue(pool) as INode;
var gg:IGroupedGraph = graph.lookup(IGroupedGraph) as IGroupedGraph;
var spm:DictionaryMapper = graph.mapperRegistry.getMapper(BusinessProcessDiagramComponent.SWIM_LANE_POSITION_KEY) as DictionaryMapper;
if (poolNode == null || gg == null) return null;
var swimLaneNodes:Array = new Array();
var it:Iterator = gg.hierarchy.getChildren(poolNode).iterator();
while (it.hasNext()) {
swimLaneNodes.push(it.next());
}
swimLaneNodes.sort(function sortOnPosition(a:INode, b:INode):Number {
var posA:Number = int(spm.lookupValue(a));
var posB:Number = int(spm.lookupValue(b));
return (posA - posB);
});
var result:Array = new Array();
for (var i:int = 0; i < swimLaneNodes.length; i++) {
var swimLaneNode:INode = swimLaneNodes[i] as INode;
var tagOwner:ITagOwner = swimLaneNode.lookup(ITagOwner) as ITagOwner;
if (tagOwner != null && tagOwner.tag is BPSwimlane) {
result.push(tagOwner.tag);
}
}
if (swimLaneNodes.length == result.length) {
return result;
} else {
return null;
}
}
/*
* Updates the mapper holding the positions of the swim lanes. All swim lanes with a position greater than or equal to
* fromPosition are increased by diff.
*/
private function updateSwimLanePositionMapper(fromPosition:uint, diff:int):void {
var swimLanePositions:DictionaryMapper = graph.mapperRegistry.getMapper(SWIM_LANE_POSITION_KEY) as DictionaryMapper;
var swimLaneIt:Iterator = swimLanePositions.keys();
while (swimLaneIt.hasNext()) {
var swimLane:Object = swimLaneIt.next();
var swimLanePosObject:Object = swimLanePositions.lookupValue(swimLane);
if (swimLanePosObject != null) {
var swimLanePos:int = int(swimLanePosObject);
if (swimLanePos >= fromPosition) {
swimLanePositions.mapValue(swimLane, swimLanePos + diff);
}
}
}
}
/*
* Creates a new swim lane. Its parent pool and its new position are determined by the given SwimLaneEvent.
*
*/
private function newSwimLaneButtonClicked(evt:SwimLaneEvent):void {
var swimLane:INode = evt.swimLaneNode;
if (swimLane != null) {
var spMapper:DictionaryMapper = graph.mapperRegistry.getMapper(SWIM_LANE_POSITION_KEY) as DictionaryMapper;
var gg:IGroupedGraph = graph.lookup(IGroupedGraph) as IGroupedGraph;
if (gg != null
&& spMapper != null) {
var posObject:Object = spMapper.lookupValue(swimLane);
// get pool node of the swim lane of the given event.
var pool:INode = gg.getParent(swimLane);
if (pool != null) {
var poolTagOwner:ITagOwner = pool.lookup(ITagOwner) as ITagOwner;
if (poolTagOwner == null
|| !(poolTagOwner.tag is BPPool)) {
pool = null;
}
}
if (pool != null
&& posObject != null) {
var absSwimLanePosition:uint = uint(posObject);
var relPosition:int = absSwimLanePosition - getAbsoluteSwimLanePosition(pool, 0);
if (relPosition < 0) return; // absolute swim lane position was not in this pool
if (!evt.above) relPosition++;
var numSwimLanes:uint = gg.hierarchy.getChildCount(pool);
var poolOwner:ITagOwner = pool.lookup(ITagOwner) as ITagOwner;
if (poolOwner != null) {
var bpPool:BPPool = poolOwner.tag as BPPool;
if (bpPool != null) {
addSwimLane(bpPool, "Swim Lane " + numSwimLanes, relPosition);
}
}
}
}
}
}
/*
* Adds a new pool. The position of the new pool is determined by the given PoolEvent.
*/
private function newPoolButtonClicked(evt:PoolEvent):void {
var pool:INode = evt.poolNode;
if (pool != null) {
var position:int = this.pools.indexOf(pool);
if (!evt.above) position++;
addPool("Pool " + this.pools.length(), position);
}
}
/*
* Calls removeSwimLane for the swim lane in the SwimLaneEvent.
*/
private function removeSwimLaneButtonClicked(evt:SwimLaneEvent):void {
var swimLane:INode = evt.swimLaneNode;
removeSwimLane(swimLane);
}
/**
* Removes the given swim lane if it is empty and if it's not the only swim lane left in the diagram. If it is the
* last swim lane in its pool but there are other pools left, its pool is removed as well.
* @param swimLane The swim lane to remove.
* @return Whether the given swim lane has been removed or not.
*/
public function removeSwimLane(swimLane:INode):Boolean {
if (graph.nodes.length() <= 2) return false; // only the swim lane and its containing pool are left
if (swimLane != null) {
var gg:IGroupedGraph = graph.lookup(IGroupedGraph) as IGroupedGraph;
if (gg != null) {
if (gg.hierarchy.getChildCount(swimLane) == 0) {
// swim lane is empty
var pool:INode = gg.getParent(swimLane);
// update position of bends that are located in this swim lane to the upper bound of this swim lane
var edgesIt:Iterator = graph.edges.iterator();
while (edgesIt.hasNext()) {
var edge:IEdge = edgesIt.next() as IEdge;
var bends:IBendList = edge.bends;
var bendIt:Iterator = bends.iterator();
while (bendIt.hasNext()) {
var bend:IBend = bendIt.next() as IBend;
if (bend.y >= swimLane.layout.y
&& bend.y <= swimLane.layout.y + swimLane.layout.height) {
graph.setBendLocation(bend, bend.x, swimLane.layout.y);
}
}
}
deleteSwimLaneNode(swimLane);
if (gg.hierarchy.getChildCount(pool) == 0) {
// swim lane was the only one in this pool, so delete the pool as well
deletePoolNode(pool);
}
poolBoundsUpdater.updatePoolsVertical();
return true;
}
}
}
return false;
}
/*
* Calls removePool for the pool in the given PoolEvent.
*/
private function removePoolButtonClicked(evt:PoolEvent):void {
var pool:INode = evt.poolNode;
removePool(pool);
}
/**
* Removes the given pool and its swim lanes if all swim lanes are empty and if it isn't the last pool left.
* @param pool The pool node to remove.
* @return Whether the pool has been removed or not.
*/
public function removePool(pool:INode):Boolean {
if (this._pools.length() <= 1) return false; // it's the only pool left.
var gg:IGroupedGraph = graph.lookup(IGroupedGraph) as IGroupedGraph;
if (gg != null) {
// check if any swim lane in this pool is not empty.
var canBeDeleted:Boolean = true;
var it:Iterator = gg.hierarchy.getChildren(pool).iterator();
while (it.hasNext()) {
var swimLane:INode = it.next() as INode;
if (gg.hierarchy.getChildCount(swimLane) > 0) {
canBeDeleted = false;
break;
}
}
if (canBeDeleted) {
// remove swim lanes
while (gg.hierarchy.getChildCount(pool) > 0) {
swimLane = gg.hierarchy.getChildren(pool).iterator().next() as INode;
deleteSwimLaneNode(swimLane);
}
// update position of bends that are located in this pool to the upper bound of this pool minus the
// minimal pool distance
var edgesIt:Iterator = graph.edges.iterator();
while (edgesIt.hasNext()) {
var edge:IEdge = edgesIt.next() as IEdge;
var bends:IBendList = edge.bends;
var bendIt:Iterator = bends.iterator();
while (bendIt.hasNext()) {
var bend:IBend = bendIt.next() as IBend;
if (bend.y >= pool.layout.y
&& bend.y <= pool.layout.y + pool.layout.height) {
graph.setBendLocation(bend, bend.x, pool.layout.y - poolToPoolDistance);
}
}
}
// remove pool
deletePoolNode(pool);
poolBoundsUpdater.updatePoolsVertical();
return true;
}
}
return false;
}
/*
* Deletes the given swim lane node from the graph and updates the positions of the other swim lanes.
*/
private function deleteSwimLaneNode(swimLane:INode):void {
var tagOwner:ITagOwner = swimLane.lookup(ITagOwner) as ITagOwner;
if (tagOwner.tag != null
&& tagOwner.tag is BPSwimlane) {
this._swimLaneMapper.unMapValue(tagOwner.tag);
}
var swimLanePositionMapper:DictionaryMapper = graph.mapperRegistry.getMapper(BusinessProcessDiagramComponent.SWIM_LANE_POSITION_KEY) as DictionaryMapper;
var position:uint = uint(swimLanePositionMapper.lookupValue(swimLane));
swimLanePositionMapper.unMapValue(swimLane);
updateSwimLanePositionMapper(position, -1);
graph.removeNode(swimLane);
}
/*
* Deletes the given pool node from the graph and updates the positions of the other swim lanes.
*/
private function deletePoolNode(pool:INode):void {
var tagOwner:ITagOwner = pool.lookup(ITagOwner) as ITagOwner;
if (tagOwner != null
&& tagOwner.tag is BPPool) {
this._poolMapper.unMapValue(tagOwner.tag);
}
var poolPositionMapper:DictionaryMapper = graph.mapperRegistry.getMapper(BusinessProcessDiagramComponent.POOL_POSITION_KEY) as DictionaryMapper;
var position:uint = uint(poolPositionMapper.lookupValue(pool));
poolPositionMapper.unMapValue(pool);
updatePoolPositionMapper(position, -1);
graph.removeNode(pool);
setPoolsDirty();
}
/*
* Creates a swim lane node by the grouped graph with the given bounds and sets the given parent node.
*/
private function createSwimLane(bounds:IRectangle, parent:INode):INode {
var gg:IGroupedGraph = graph.lookup(IGroupedGraph) as IGroupedGraph;
var node:INode;
if (gg != null) {
node = gg.createGroupNode(parent, bounds, new TemplateNodeStyle(SwimLaneTemplate));
}
return node;
}
/**
* Returns the INode
corresponding to the given BPPool
.
* @param pool The corresponding BPPool
* @return The INode for this BPPool
*/
public function getPoolNode(pool:BPPool):INode {
return this._poolMapper.lookupValue(pool) as INode;
}
/**
* Returns the BPPool
corresponding to the pool node at the given position in the
* list of the property pools.
* @param position
* @return The BPPool
at the specified position.
*/
public function getPoolAtPosition(position:uint):BPPool {
if (position >= this.pools.length()) return null;
var poolNode:INode = this.pools.getItemAt(position) as INode;
if (poolNode != null) {
var tagOwner:ITagOwner = poolNode.lookup(ITagOwner) as ITagOwner;
if (tagOwner != null) {
return tagOwner.tag as BPPool;
}
}
return null;
}
/* **************************** GETTER AND SETTER *****************************/
/**
* The vertical distance between two adjacent pools.
*/
public function get poolToPoolDistance():uint {
var mapper:DictionaryMapper = graph.mapperRegistry.getMapper(BusinessProcessDiagramComponent.VERTICAL_POOL_DISTANCE_KEY) as DictionaryMapper;
return (mapper == null) ? 0 : uint(mapper.lookupValue(graph));
}
public function set poolToPoolDistance(val:uint):void {
var mapper:DictionaryMapper = graph.mapperRegistry.getMapper(BusinessProcessDiagramComponent.VERTICAL_POOL_DISTANCE_KEY) as DictionaryMapper;
if (mapper != null) {
mapper.mapValue(graph, val);
}
}
/**
* The minimal height a pool (or swim lane) node may have.
*/
public function get minimalSwimLaneHeight():uint {
var mapper:DictionaryMapper = graph.mapperRegistry.getMapper(BusinessProcessDiagramComponent.MIN_POOL_HEIGHT_KEY) as DictionaryMapper;
return (mapper == null) ? 0 : uint(mapper.lookupValue(graph));
}
public function set minimalSwimLaneHeight(val:uint):void {
var mapper:DictionaryMapper = graph.mapperRegistry.getMapper(BusinessProcessDiagramComponent.MIN_POOL_HEIGHT_KEY) as DictionaryMapper;
if (mapper != null) {
mapper.mapValue(graph, val);
}
}
/**
* The minimal width a pool node may have.
*/
public function get minimalSwimLaneWidth():uint {
var mapper:DictionaryMapper = graph.mapperRegistry.getMapper(BusinessProcessDiagramComponent.MIN_POOL_WIDTH_KEY) as DictionaryMapper;
return (mapper == null) ? 0 : uint(mapper.lookupValue(graph));
}
public function set minimalSwimLaneWidth(val:uint):void {
var mapper:DictionaryMapper = graph.mapperRegistry.getMapper(BusinessProcessDiagramComponent.MIN_POOL_WIDTH_KEY) as DictionaryMapper;
if (mapper != null) {
mapper.mapValue(graph, val);
}
}
/**
* The PoolBoundsUpdater
used for this BusinessProcessDiagramComponent
.
*/
public function get poolBoundsUpdater():PoolBoundsUpdater {
return _poolBoundsUpdater;
}
public function set poolBoundsUpdater(val:PoolBoundsUpdater):void {
_poolBoundsUpdater = val;
}
[Bindable]
/**
* An ArrayCollection
containing the file names of graphs stored on
* the server.
*/
public function get loadableGraphs():ArrayCollection {
return this._loadableGraphList;
}
public function set loadableGraphs(ac:ArrayCollection):void {
this._loadableGraphList = ac;
}
/**
* An ArrayList
of the pool nodes sorted by their position from top to bottom.
*/
public function get pools():ArrayList {
if (this._poolsDirty) {
var poolPosMapper:DictionaryMapper =
graph.mapperRegistry.getMapper(BusinessProcessDiagramComponent.POOL_POSITION_KEY) as DictionaryMapper;
if (poolPosMapper != null) {
this._pools.clear();
var it:Iterator = poolPosMapper.keys();
while (it.hasNext()) {
var pool:Object = it.next();
if (poolPosMapper.lookupValue(pool) != null) {
this._pools.addItem(pool);
}
}
this._pools.sort(function(pool1:Object, pool2:Object):int {
return int(poolPosMapper.lookupValue(pool1)) - int(poolPosMapper.lookupValue(pool2));
});
}
this._poolsDirty = false;
}
return this._pools;
}
/**
* Triggers the pools property to be updated according to the pool position mapper on its next access.
*/
public function setPoolsDirty():void {
this._poolsDirty = true;
}
}
}
import com.yworks.canvas.ICanvasContext;
import com.yworks.canvas.drawing.IHitTestable;
import mx.core.mx_internal
import demo.businessProcessDiagram.bpd.BusinessProcessDiagramComponent;
use namespace mx_internal;
/**
* This hit testable is used when dragging elements from the palette onto the canvas to decide if the current mouse
* location is a valid drop location.
* This is the case if the mouse hovers over a swim lane.
*/
class ValidNodeDropHitTestable implements IHitTestable {
private var canvas:BusinessProcessDiagramComponent;
public function ValidNodeDropHitTestable(canvas:BusinessProcessDiagramComponent) {
this.canvas = canvas;
}
public function isHit(x:Number, y:Number, ctx:ICanvasContext):Boolean {
return canvas.getSwimLaneNodeAtCursor(x, y) != null || !(mx.core.Singleton.getInstance("mx.managers::IDragManager") is mx.managers.DragManagerImpl);
}
}