/// <summary> /// Called once the move operation has been initialized /// </summary> /// <remarks> /// Calculates which components stay fixed and which nodes will be moved by the user. /// </remarks> private void OnMoveInitialized(object sender, EventArgs eventArgs) { if (layout != null) { CopiedLayoutGraph copy = this.copiedLayoutGraph; var componentNumber = copy.CreateNodeMap(); GraphConnectivity.ConnectedComponents(copy, componentNumber); System.Collections.Generic.HashSet <int> movedComponents = new System.Collections.Generic.HashSet <int>(); System.Collections.Generic.HashSet <Node> selectedNodes = new System.Collections.Generic.HashSet <Node>(); foreach (INode node in movedNodes) { Node copiedNode = copy.GetCopiedNode(node); if (copiedNode != null) { // remember that we nailed down this node selectedNodes.Add(copiedNode); // remember that we are moving this component movedComponents.Add(componentNumber.GetInt(copiedNode)); //Update the position of the node in the CLG to match the one in the IGraph layout.SetCenter(copiedNode, node.Layout.X + node.Layout.Width * 0.5, node.Layout.Y + node.Layout.Height * 0.5); //Actually, the node itself is fixed at the start of a drag gesture layout.SetInertia(copiedNode, 1.0); //Increasing has the effect that the layout will consider this node as not completely placed... // In this case, the node itself is fixed, but it's neighbors will wake up IncreaseHeat(copiedNode, layout, 0.5); } } // there are components that won't be moved - nail the nodes down so that they don't spread apart infinitely foreach (var copiedNode in copy.Nodes) { if (!movedComponents.Contains(componentNumber.GetInt(copiedNode))) { layout.SetInertia(copiedNode, 1); } else { if (!selectedNodes.Contains(copiedNode)) { // make it float freely layout.SetInertia(copiedNode, 0); } } } // dispose the map copy.DisposeNodeMap(componentNumber); //Notify the layout algorithm that there is new work to do... layout.WakeUp(); } }
private void OnDone(object sender, EventArgs eventArgs) { if (layoutGraph != null) { layoutGraph.RemoveDataProvider(AbortHandler.AbortHandlerDpKey); layoutGraph = null; } graph = null; if (abortDialog != null) { abortDialog.Close(); abortDialog = null; } }
/// <summary> /// If the provided <see cref="LayoutGraph"/> edge has a represented edge, /// returns the edge of the original input graph that corresponds to this edge. /// </summary> protected IEdge GetRepresentedEdge(Edge layoutEdge) { IEdgeInfo edgeInfo = result.GetEdgeInfo(layoutEdge); Edge representedEdge = edgeInfo.RepresentedEdge; if (null != representedEdge) { CopiedLayoutGraph copiedLayoutGraph = representedEdge.Graph as CopiedLayoutGraph; if (null != copiedLayoutGraph) { return((IEdge)copiedLayoutGraph.GetOriginalEdge(representedEdge)); } } return(null); }
/// <summary> /// If the provided <see cref="LayoutGraph"/> node has a represented node, /// returns the node of the original input graph that corresponds to this node. /// </summary> protected INode GetRepresentedNode(Node layoutNode) { INodeInfo nodeInfo = result.GetNodeInfo(layoutNode); // represented node is the Node in the // CopiedLayoutIGraph which is the LayoutGraph representation // of the model graph Node representedNode = nodeInfo.RepresentedNode; if (null != representedNode) { CopiedLayoutGraph copiedLayoutGraph = representedNode.Graph as CopiedLayoutGraph; if (null != copiedLayoutGraph) { // translate it into the corresponding INode from the model graph. return((INode)copiedLayoutGraph.GetOriginalNode(representedNode)); } } return(null); }
/// <summary> /// Notifies the layout of the new positions of the interactively moved nodes. /// </summary> private void OnMoving(object sender, InputModeEventArgs inputModeEventArgs) { if (layout != null) { CopiedLayoutGraph copy = this.copiedLayoutGraph; foreach (INode node in movedNodes) { Node copiedNode = copy.GetCopiedNode(node); if (copiedNode != null) { //Update the position of the node in the CLG to match the one in the IGraph layout.SetCenter(copiedNode, node.Layout.GetCenter().X, node.Layout.GetCenter().Y); //Increasing the heat has the effect that the layout will consider these nodes as not completely placed... IncreaseHeat(copiedNode, layout, 0.05); } } //Notify the layout algorithm that there is new work to do... layout.WakeUp(); } }
/// <summary> /// Called once the interactive move is finished. /// </summary> /// <remarks> /// Updates the state of the interactive layout. /// </remarks> private void OnMovedFinished(object sender, InputModeEventArgs inputModeEventArgs) { if (layout != null) { CopiedLayoutGraph copy = this.copiedLayoutGraph; foreach (INode node in movedNodes) { Node copiedNode = copy.GetCopiedNode(node); if (copiedNode != null) { //Update the position of the node in the CLG to match the one in the IGraph layout.SetCenter(copiedNode, node.Layout.GetCenter().X, node.Layout.GetCenter().Y); layout.SetStress(copiedNode, 0); } } foreach (var copiedNode in copy.Nodes) { //Reset the node's inertia to be fixed layout.SetInertia(copiedNode, 1.0); layout.SetStress(copiedNode, 0); } } }
protected override void Apply(LayoutGraphAdapter layoutGraphAdapter, ILayoutAlgorithm layout, CopiedLayoutGraph layoutGraph) { layoutGraphAdapter.AddDataProvider(SelectedNodes.DpKey, SelectedNodes.ProvideMapper(layoutGraphAdapter, layout)); }
/// <summary> /// Executes the module on the given graph using the provided context. /// </summary> /// <remarks> /// The layout will be calculated <see cref="RunInBackground">optionally</see> /// in a separate thread in method <see cref="RunModuleAsync"/>. /// </remarks> /// <param name="graph">The graph to execute on.</param> /// <param name="newContext">The context to use. This method will query a <c>ISelectionModel<IModelItem></c></param> /// for the selected nodes and edges and the <c>GraphControl</c> to morph the layout. protected virtual async Task StartWithIGraph(IGraph graph, ILookup newContext) { this.graph = graph; if (ShouldConfigureTableLayout()) { PrepareTableLayout(); } ISelectionModel <IModelItem> selectionModel = newContext.Lookup <ISelectionModel <IModelItem> >(); LayoutGraphAdapter adapter = new LayoutGraphAdapter(graph, selectionModel); this.layoutGraph = adapter.CreateCopiedLayoutGraph(); ILookup additionalLookup = Lookups.Single(layoutGraph, typeof(LayoutGraph)); ILookup wrappedLookup = Lookups.Wrapped(newContext, additionalLookup); try { ICompoundEdit compoundEdit = graph.BeginEdit("Layout", "Layout"); CheckReentrant(wrappedLookup); ConfigureModule(); if (RunInBackground) { // without the LayoutExecutor helper class on the layout graph side of things, we register the aborthandler // to the layout graph with the utility method provided by AbortHandler var abortHandler = AbortHandler.CreateForGraph(layoutGraph); // now create the dialog that controls the abort handler abortDialog = new AbortDialog { AbortHandler = abortHandler, Owner = Application.Current.MainWindow }; // start the layout in another thread. var layoutThread = new Thread(async() => await RunModuleAsync(wrappedLookup, graph, compoundEdit)); // now if we are not doing a quick layout - and if it takes more than a few seconds, we open the dialog to // enable the user to stop or cancel the execution var showDialogTimer = new DispatcherTimer(DispatcherPriority.Normal, abortDialog.Dispatcher) { Interval = TimeSpan.FromSeconds(2) }; showDialogTimer.Tick += delegate { // it could be that the layout is already done - so check whether we still // need to open the dialog var dialogInstance = abortDialog; if (dialogInstance != null) { // open the abort dialog dialogInstance.Show(); } // we only want to let it go off once - so stop the timer showDialogTimer.Stop(); }; // kick-off the timer and the layout showDialogTimer.Start(); layoutThread.Start(); } else { await RunModuleAsync(wrappedLookup, graph, compoundEdit); } } catch (Exception e) { FreeReentrant(); TableLayoutConfigurator.CleanUp(graph); OnDone(new LayoutEventArgs(e)); //optionally do something here... } }
protected override void Apply(LayoutGraphAdapter adapter, ILayoutAlgorithm layout, CopiedLayoutGraph layoutGraph) { var graph = adapter.AdaptedGraph; // check if only selected elements should be laid out var layoutOnlySelection = layout is BpmnLayout && ((BpmnLayout)layout).Scope == Scope.SelectedElements; // mark 'flow' edges, i.e. sequence flows, default flows and conditional flows adapter.AddDataProvider(BpmnLayout.SequenceFlowEdgesDpKey, Mappers.FromDelegate <IEdge, bool>(IsSequenceFlow)); // mark boundary interrupting edges for the BalancingPortOptimizer adapter.AddDataProvider(BpmnLayout.BoundaryInterruptingEdgesDpKey, Mappers.FromDelegate((IEdge edge) => edge.SourcePort.Style is EventPortStyle)); // mark conversations, events and gateways so their port locations are adjusted adapter.AddDataProvider(PortLocationAdjuster.AffectedNodesDpKey, Mappers.FromDelegate((INode node) => (node.Style is ConversationNodeStyle || node.Style is EventNodeStyle || node.Style is GatewayNodeStyle))); // add NodeHalos around nodes with event ports or specific exterior labels so the layout keeps space for the event ports and labels as well AddNodeHalos(adapter, graph, layoutOnlySelection); // add PreferredPlacementDescriptors for labels on sequence, default or conditional flows to place them at source side AddEdgeLabelPlacementDescriptors(adapter); // mark nodes, edges and labels as either fixed or affected by the layout and configure port constraints and incremental hints MarkFixedAndAffectedItems(adapter, layoutOnlySelection); // mark associations and message flows as undirected so they have less impact on layering EdgeDirectedness.Delegate = edge => (IsMessageFlow(edge) || IsAssociation(edge)) ? 0 : 1; // add layer constraints for start events, sub processes and message flows AddLayerConstraints(graph); // add EdgeLayoutDescriptor to specify minimum edge length for edges AddMinimumEdgeLength(MinimumEdgeLength); base.Apply(adapter, layout, layoutGraph); }
protected override void Apply(LayoutGraphAdapter layoutGraphAdapter, ILayoutAlgorithm layout, CopiedLayoutGraph layoutGraph) { layoutGraphAdapter.AddDataProvider(SelectedLabelsStage.SelectedLabelsAtItemKey, SelectedLabelsAtItem.ProvideMapper(layoutGraphAdapter, layout)); }