private static IBend CreateBends([NotNull] IGraph graph, [NotNull] IEdge edge, int segmentIndex, double ratio, [NotNull] IListEnumerable <IPoint> pathPoints) { //Create 3 bends and adjust the neighbors //The first bend we need to touch is at startIndex var startIndex = segmentIndex * 3; //This holds the new coordinates left and right of the split point //We don't actually need all of them, but this keeps the algorithm more straightforward. var left = new PointD[4]; var right = new PointD[4]; //Determine the new control points to cleanly split the curve GetCubicSplitPoints(ratio, new[] { pathPoints[startIndex].ToPointD(), pathPoints[startIndex + 1].ToPointD(), pathPoints[startIndex + 2].ToPointD(), pathPoints[startIndex + 3].ToPointD() }, left, right); //Previous control point - does always exist as a bend, given our precondition var previousBend = edge.Bends[startIndex]; //Next control point - also always exists given the precondition for bend counts (i.e. there have to be at least two) var nextBend = edge.Bends[startIndex + 1]; //We create the three new bends between previous bend and next bend and adjust these two. //We don't have to adjust more bends, since we just have a cubic curve. IBend bendToMove; var engine = graph.GetUndoEngine(); //Wrap everything into a single compound edit, so that everything can be undone in a single unit using (var edit = graph.BeginEdit("Create Bezier Bend", "Create Bezier Bend")) { try { //Adjust the previous bend - given the split algorithm, its coordinate is in left[1] //(left[0] is actually kept unchanged from the initial value) var oldPrevLocation = previousBend.Location.ToPointD(); var newPrevLocation = left[1]; graph.SetBendLocation(previousBend, newPrevLocation); // Add unit to engine graph.AddUndoUnit("Set bend location", "Set bend location", () => graph.SetBendLocation(previousBend, oldPrevLocation), () => graph.SetBendLocation(previousBend, newPrevLocation)); //Insert the new triple, using the values from left and right in order graph.AddBend(edge, left[2], startIndex + 1); bendToMove = graph.AddBend(edge, left[3], startIndex + 2); //right[0] == left[3], so right[1] is the next new control point graph.AddBend(edge, right[1], startIndex + 3); //Adjust the next bend var oldNextLocation = nextBend.Location.ToPointD(); var newNextLocation = right[2]; graph.SetBendLocation(nextBend, newNextLocation); // Add unit to engine graph.AddUndoUnit("Set bend location", "Set bend location", () => graph.SetBendLocation(nextBend, oldNextLocation), () => graph.SetBendLocation(nextBend, newNextLocation)); } catch { //Cancel the edit in case anything goes wrong. edit.Cancel(); throw; } } return(bendToMove); }
/// <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... } }