public override void RolePlayerChanged(RolePlayerChangedEventArgs e) { if (e == null) { throw new ArgumentNullException("e"); } if (e.NewRolePlayer != null) { foreach (PresentationElement pe in e.ElementLink.PresentationRolePlayers) { // Fix up any binary link shapes attached to the element link. BinaryLinkShape linkShape = pe as BinaryLinkShape; if (linkShape != null) { if (linkShape.ToShape != null && linkShape.ToShape.ModelElement == e.OldRolePlayer) { // The role player on the "to" side of the link changed, point at new role player PresentationElementMoveableCollection newToShapes = e.NewRolePlayer.PresentationRolePlayers; linkShape.ToShape = newToShapes.Count > 0 ? newToShapes[0] as NodeShape : null; } else if (linkShape.FromShape != null && linkShape.FromShape.ModelElement == e.OldRolePlayer) { // The role player on the "from" side of the link changed, point at new role player PresentationElementMoveableCollection newFromShapes = e.NewRolePlayer.PresentationRolePlayers; linkShape.FromShape = newFromShapes.Count > 0 ? newFromShapes[0] as NodeShape : null; } } } } }
/// <summary> /// ChangeRule: typeof(ORMBaseBinaryLinkShape), FireTime=TopLevelCommit, Priority=DiagramFixupConstants.AutoLayoutShapesRulePriority; /// Keep relative child elements a fixed distance away from the fact /// when the shape changes. /// </summary> private static void LinkChangeRule(ElementPropertyChangedEventArgs e) { Guid attributeId = e.DomainProperty.Id; if (attributeId == ORMBaseBinaryLinkShape.EdgePointsDomainPropertyId) { ORMBaseBinaryLinkShape parentShape = e.ModelElement as ORMBaseBinaryLinkShape; LinkedElementCollection <ShapeElement> childShapes = parentShape.RelativeChildShapes; int childCount = childShapes.Count; for (int i = 0; i < childCount; ++i) { LinkConnectorShape linkConnector = childShapes[i] as LinkConnectorShape; if (linkConnector != null) { RectangleD bounds = parentShape.AbsoluteBoundingBox; linkConnector.Location = new PointD(bounds.Width / 2, bounds.Height / 2); ReadOnlyCollection <LinkConnectsToNode> links = DomainRoleInfo.GetElementLinks <LinkConnectsToNode>(linkConnector, LinkConnectsToNode.NodesDomainRoleId); int linksCount = links.Count; for (int j = 0; j < linksCount; ++j) { LinkConnectsToNode link = links[j]; BinaryLinkShape linkShape = link.Link as BinaryLinkShape; if (linkShape != null) { // Changing the location is not reliably reconnecting all shapes, especially // during load. Force the link to reconnect with a RecalculateRoute call linkShape.RecalculateRoute(); } } break; } } } }
/// <summary> /// ChangeRule: typeof(RingConstraint), FireTime=TopLevelCommit, Priority=DiagramFixupConstants.AddConnectionRulePriority; /// </summary> private static void RingConstraintPropertyChangeRule(ElementPropertyChangedEventArgs e) { if (e.DomainProperty.Id == RingConstraint.RingTypeDomainPropertyId) { RingConstraint ringConstraint = (RingConstraint)e.ModelElement; if (!ringConstraint.IsDeleted) { foreach (PresentationElement pel in PresentationViewsSubject.GetPresentation(ringConstraint)) { RingConstraintShape ringConstraintShape = pel as RingConstraintShape; if (ringConstraintShape != null) { foreach (LinkConnectsToNode connection in DomainRoleInfo.GetElementLinks <LinkConnectsToNode>(ringConstraintShape, LinkConnectsToNode.NodesDomainRoleId)) { BinaryLinkShape binaryLink = connection.Link as BinaryLinkShape; if (binaryLink != null) { binaryLink.RecalculateRoute(); } } SizeD oldSize = ringConstraintShape.Size; ringConstraintShape.AutoResize(); if (oldSize == ringConstraintShape.Size) { ringConstraintShape.InvalidateRequired(true); } } } } } }
/// <summary> /// This method is called when the rule is fired, that is when a new connection is added to the model. /// </summary> /// <param name="e">the ElementAddedEventArgs</param> public override void ElementAdded(ElementAddedEventArgs e) { BinaryLinkShape c = e.ModelElement as BinaryLinkShape; if (c == null) { return; } CompartmentMappingUtil.RerouteCompartmentMappings(c); }
/// <summary> /// Reroutes a compartment connection link shape. /// </summary> /// <remarks> /// The start and endpoints of the link will recalculated. /// </remarks> /// <param name="link">The BinaryLinkShape</param> public static void RerouteCompartmentMappings(BinaryLinkShape link) { // fill the cache FindAllCompartmentMappingRouter(link.GetType().Assembly); using (Transaction t = link.Store.TransactionManager.BeginTransaction("reroute compartment links")) { DoRerouteCompartmentMappings(link); t.Commit(); } }
/// <summary> /// Pulled directly from Reflector disassembly /// </summary> private static AnchorPoint TestHitAnchor(BinaryLinkShape linkShape, LineSegment segment, SizeD tolerance, PointD hitPoint) { RectangleD ed1 = new RectangleD(0, 0, tolerance.Width * 2, tolerance.Height * 2); NodeShape shape1 = null; bool flag1 = false; if (linkShape != null) { PointD td1; if (segment.IsStartSegment && segment.IsEndSegment) { if (ClosestEnd(segment, hitPoint)) { td1 = segment.StartPoint; shape1 = linkShape.FromShape; flag1 = true; } else { td1 = segment.EndPoint; shape1 = linkShape.ToShape; } } else if (segment.IsStartSegment) { td1 = segment.StartPoint; shape1 = linkShape.FromShape; flag1 = true; } else if (segment.IsEndSegment) { td1 = segment.EndPoint; shape1 = linkShape.ToShape; } else { return(null); } if ((shape1 != null) && !shape1.IsPort) { ed1.Offset(td1.X - tolerance.Width, td1.Y - tolerance.Height); if (ed1.Contains(hitPoint)) { return(new AnchorPoint(linkShape, segment, shape1, tolerance, flag1)); } } } return(null); }
/// <summary> /// Reroutes a compartment connection link shape. /// </summary> /// <remarks> /// To call this method you have to fill the cache and start a transaction! /// </remarks> /// <param name="link">The BinaryLinkShape</param> private static void DoRerouteCompartmentMappings(BinaryLinkShape link) { if (link == null || link.ModelElement == null) { return; } Type connectionType = link.ModelElement.GetType(); //do we have a ICompartmentMappingRouter instance for this type of connection? if (allCompartmentMappingRouter.ContainsKey(connectionType)) { allCompartmentMappingRouter[connectionType].CorrectBinaryLinkShapeEndPoints(link); } }
/// <summary> /// Repaints the connector shapes for given links. /// </summary> private static void RepaintRelationshipConnectors <T>(ModelElement element) where T : ElementLink { var link = element as T; if (link != null) { foreach (var shape in PresentationViewsSubject.GetPresentation(link)) { BinaryLinkShape connector = shape as BinaryLinkShape; if (connector != null) { connector.Invalidate(); } } } }
private static void AutoRouteConnector(BinaryLinkShape connector) { if (connector != null) { connector.ManuallyRouted = !connector.ManuallyRouted; connector.FixedFrom = VGFixedCode.NotFixed; connector.FixedTo = VGFixedCode.NotFixed; foreach (ShapeElement element in connector.RelativeChildShapes) { if (element is LineLabelShape lineLabelShape) { lineLabelShape.ManuallySized = false; lineLabelShape.ManuallyPlaced = false; } } connector.RecalculateRoute(); connector.ManuallyRouted = !connector.ManuallyRouted; } }
// ReSharper disable once UnusedParameter.Local private static void DoGraphvizLayout(List <DotNode> vertices, List <DotEdge> edges, EFModelDiagram diagram) { // set up to be a bidirectional graph with the edges we found BidirectionalGraph <DotNode, DotEdge> graph = edges.ToBidirectionalGraph <DotNode, DotEdge>(); // add all the vertices that aren't connected by edges graph.AddVertexRange(vertices.Except(edges.Select(e => e.Source).Union(edges.Select(e => e.Target)))); // we'll process as Graphviz GraphvizAlgorithm <DotNode, DotEdge> graphviz = new GraphvizAlgorithm <DotNode, DotEdge>(graph); graphviz.GraphFormat.NodeSeparation = 1.0; graphviz.GraphFormat.Splines = GraphvizSplineType.Ortho; graphviz.CommonVertexFormat.Shape = GraphvizVertexShape.Record; // labels will be the Id of the underlying Shape graphviz.FormatVertex += (sender, args) => { args.VertexFormat.Label = args.Vertex.Shape.ModelElement is ModelClass modelClass ? modelClass.Name : args.Vertex.Shape.ModelElement is ModelEnum modelEnum ? modelEnum.Name : args.Vertex.Shape.ModelElement.Id.ToString(); args.VertexFormat.FixedSize = true; args.VertexFormat.Size = new GraphvizSizeF((float)args.Vertex.Shape.Size.Width, (float)args.Vertex.Shape.Size.Height); args.VertexFormat.Label = args.Vertex.Shape.Id.ToString(); }; graphviz.FormatEdge += (sender, args) => { args.EdgeFormat.Label.Value = args.Edge.Shape.Id.ToString(); }; // generate the commands string dotCommands = graphviz.Generate(new DotEngine(), Path.Combine(Path.GetTempPath(), Path.GetTempFileName())); Debug.WriteLine(dotCommands); ProcessStartInfo dotStartInfo = new ProcessStartInfo(EFModelPackage.Options.DotExePath, "-T plain") { RedirectStandardInput = true, RedirectStandardOutput = true, UseShellExecute = false, CreateNoWindow = true }; string graphOutput; using (Process dotProcess = Process.Start(dotStartInfo)) { // stdin is redirected to our stream, so pump the commands in through that dotProcess.StandardInput.WriteLine(dotCommands); // closing the stream starts the process dotProcess.StandardInput.Close(); // stdout is redirected too, so capture the output graphOutput = dotProcess.StandardOutput.ReadToEnd(); dotProcess.WaitForExit(); } Debug.WriteLine(graphOutput); // break it up into lines of text for processing string[] outputLines = graphOutput.Split(new [] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); SizeD graphSize = SizeD.Empty; // ReSharper disable once LoopCanBePartlyConvertedToQuery foreach (string outputLine in outputLines) { // spaces aren't valid in any of the data, so we can treat them as delimiters string[] parts = outputLine.Split(' '); string id; double x, y; // graphviz coordinates have 0,0 at the bottom left, positive means moving up // our coordinates have 0,0 at the top left, positive means moving down // so we need to transform them switch (parts[0]) { case "graph": // 0 1 2 3 // graph 1 109.38 92.681 graphSize = new SizeD(double.Parse(parts[2]), double.Parse(parts[3])); break; case "node": // 0 1 2 3 4 5 6 7 // node 71 78.514 93.639 1.5 3.3056 "0f651fe7-da0f-453f-a08a-ec1d31ec0e71" solid record black lightgrey id = parts[6].Trim('"'); DotNode dotNode = vertices.Single(v => v.Shape.Id.ToString() == id); // label x = double.Parse(parts[2]); y = graphSize.Height - double.Parse(parts[3]); dotNode.Shape.Bounds = new RectangleD(x, y, dotNode.Shape.Size.Width, dotNode.Shape.Size.Height); break; case "edge": // 0 1 2 3 4 5 6 7 8 9 10 11 12 // edge 6 18 4 34.926 77.518 34.926 77.518 34.926 75.88 34.926 75.88 "567b5db7-7591-4aa7-845c-76635bf56f28" 36.083 77.16 solid black id = parts[4 + int.Parse(parts[3]) * 2].Trim('"'); DotEdge edge = edges.Single(e => e.Shape.Id.ToString() == id); // need to mark the connector as dirty. this is the easiest way to do this BinaryLinkShape linkShape = edge.Shape; linkShape.ManuallyRouted = !linkShape.ManuallyRouted; linkShape.FixedFrom = VGFixedCode.NotFixed; linkShape.FixedTo = VGFixedCode.NotFixed; // make the labels follow the lines foreach (LineLabelShape lineLabelShape in linkShape.RelativeChildShapes.OfType <LineLabelShape>()) { lineLabelShape.ManuallySized = false; lineLabelShape.ManuallyPlaced = false; } linkShape.EdgePoints.Clear(); int pointCount = int.Parse(parts[3]); for (int index = 4; index < 4 + pointCount * 2; index += 2) { x = double.Parse(parts[index]); y = graphSize.Height - double.Parse(parts[index + 1]); linkShape.EdgePoints.Add(new EdgePoint(x, y, VGPointType.Normal)); } // since we're not changing the nodes this edge connects, this really doesn't do much. // what it DOES do, however, is call ConnectEdgeToNodes, which is an internal method we'd otherwise // be unable to access linkShape.Connect(linkShape.FromShape, linkShape.ToShape); linkShape.ManuallyRouted = false; break; } } }
/// <summary> /// Correct the end points of a compartment connection to correspond to the entries of the compartmetn shapes. /// </summary> /// <param name="linkShape">the BinaryLinkShape</param> public void CorrectBinaryLinkShapeEndPoints(BinaryLinkShape linkShape) { CONNECTION connection = (CONNECTION)linkShape.ModelElement; if (connection == null) { return; } // source CompartmentShape fromShape = linkShape.FromShape as CompartmentShape; RectangleD fromRec = linkShape.FromShape.AbsoluteBoundingBox; PointD leftFrom; PointD rightFrom; if (fromShape != null) { // vertical offset on source Double fromOffset = GetVerticalOffset <SOURCE_COMPARTMENT_ENTRY>(fromShape, connection, GetBuilderInstance().IsEntryConnectionSource); leftFrom = new PointD(fromRec.Left, fromRec.Top + fromOffset); rightFrom = new PointD(fromRec.Right, fromRec.Top + fromOffset); } else { leftFrom = rightFrom = fromRec.Center; } // taget CompartmentShape toShape = linkShape.ToShape as CompartmentShape; RectangleD toRec = linkShape.ToShape.AbsoluteBoundingBox; PointD leftTarget; PointD rightTarget; if (toShape != null) { // vertical offset on source Double toOffset = GetVerticalOffset <TARGET_COMPARTMENT_ENTRY>(toShape, connection, GetBuilderInstance().IsEntryConnectionTarget); leftTarget = new PointD(toRec.Left, toRec.Top + toOffset); rightTarget = new PointD(toRec.Right, toRec.Top + toOffset); } else { leftTarget = rightTarget = toRec.Center; } // determinate the connection points ConnectionPoints points = GetConnectionPoints(leftFrom, rightFrom, leftTarget, rightTarget); // set point for source if (fromShape != null) { linkShape.FromEndPoint = points.Source; linkShape.FixedFrom = VGFixedCode.Caller; } // set point for taget if (toShape != null) { linkShape.ToEndPoint = points.Target; linkShape.FixedTo = VGFixedCode.Caller; } }
/// <summary> /// Pulled directly from Reflector disassembly /// </summary> private static AnchorPoint TestHitAnchor(BinaryLinkShape linkShape, LineSegment segment, SizeD tolerance, PointD hitPoint) { RectangleD ed1 = new RectangleD(0, 0, tolerance.Width * 2, tolerance.Height * 2); NodeShape shape1 = null; bool flag1 = false; if (linkShape != null) { PointD td1; if (segment.IsStartSegment && segment.IsEndSegment) { if (ClosestEnd(segment, hitPoint)) { td1 = segment.StartPoint; shape1 = linkShape.FromShape; flag1 = true; } else { td1 = segment.EndPoint; shape1 = linkShape.ToShape; } } else if (segment.IsStartSegment) { td1 = segment.StartPoint; shape1 = linkShape.FromShape; flag1 = true; } else if (segment.IsEndSegment) { td1 = segment.EndPoint; shape1 = linkShape.ToShape; } else { return null; } if ((shape1 != null) && !shape1.IsPort) { ed1.Offset(td1.X - tolerance.Width, td1.Y - tolerance.Height); if (ed1.Contains(hitPoint)) { return new AnchorPoint(linkShape, segment, shape1, tolerance, flag1); } } } return null; }
/// <summary> /// Reroutes a compartment connection link shape. /// </summary> /// <remarks> /// To call this method you have to fill the cache and start a transaction! /// </remarks> /// <param name="link">The BinaryLinkShape</param> private static void DoRerouteCompartmentMappings(BinaryLinkShape link) { if (link == null || link.ModelElement == null) return; Type connectionType = link.ModelElement.GetType(); //do we have a ICompartmentMappingRouter instance for this type of connection? if (allCompartmentMappingRouter.ContainsKey(connectionType)) allCompartmentMappingRouter[connectionType].CorrectBinaryLinkShapeEndPoints(link); }
private static void UpdateConnectors(GeometryGraph graph) { foreach (Edge edge in graph.Edges) { BinaryLinkShape linkShape = (BinaryLinkShape)edge.UserData; // need to mark the connector as dirty. this is the easiest way to do this linkShape.ManuallyRouted = !linkShape.ManuallyRouted; linkShape.FixedFrom = VGFixedCode.NotFixed; linkShape.FixedTo = VGFixedCode.NotFixed; // make the labels follow the lines foreach (LineLabelShape lineLabelShape in linkShape.RelativeChildShapes.OfType <LineLabelShape>()) { lineLabelShape.ManuallySized = false; lineLabelShape.ManuallyPlaced = false; } linkShape.EdgePoints.Clear(); // MSAGL deals in line segments; DSL deals in points // with the segments, tne end of one == the beginning of the next, so we can use just the beginning point // of each segment. // But we have to hang on to the end point so that, when we hit the last segment, we can finish off the // set of points if (edge.Curve is LineSegment lineSegment) { // When curve is a single line segment. linkShape.EdgePoints.Add(new EdgePoint(lineSegment.Start.X, lineSegment.Start.Y, VGPointType.Normal)); linkShape.EdgePoints.Add(new EdgePoint(lineSegment.End.X, lineSegment.End.Y, VGPointType.Normal)); } else if (edge.Curve is Curve curve) { //// When curve is a complex segment. EdgePoint lastPoint = null; foreach (ICurve segment in curve.Segments) { switch (segment.GetType().Name) { case "LineSegment": LineSegment line = segment as LineSegment; linkShape.EdgePoints.Add(new EdgePoint(line.Start.X, line.Start.Y, VGPointType.Normal)); lastPoint = new EdgePoint(line.End.X, line.End.Y, VGPointType.Normal); break; case "CubicBezierSegment": CubicBezierSegment bezier = segment as CubicBezierSegment; // there are 4 segments. Store all but the last one linkShape.EdgePoints.Add(new EdgePoint(bezier.B(0).X, bezier.B(0).Y, VGPointType.Normal)); linkShape.EdgePoints.Add(new EdgePoint(bezier.B(1).X, bezier.B(1).Y, VGPointType.Normal)); linkShape.EdgePoints.Add(new EdgePoint(bezier.B(2).X, bezier.B(2).Y, VGPointType.Normal)); lastPoint = new EdgePoint(bezier.B(3).X, bezier.B(3).Y, VGPointType.Normal); break; case "Ellipse": // rather than draw a curved line, we'll bust the curve into 5 parts and draw those as straight lines Ellipse ellipse = segment as Ellipse; double interval = (ellipse.ParEnd - ellipse.ParStart) / 5.0; lastPoint = null; for (double i = ellipse.ParStart; i <= ellipse.ParEnd; i += interval) { Point p = ellipse.Center + (Math.Cos(i) * ellipse.AxisA) + (Math.Sin(i) * ellipse.AxisB); // we'll remember the one we just calculated, but store away the one we calculated last time around // (if there _was_ a last time around). That way, when we're done, we'll have stored all of them except // for the last one if (lastPoint != null) { linkShape.EdgePoints.Add(lastPoint); } lastPoint = new EdgePoint(p.X, p.Y, VGPointType.Normal); } break; } } // finally tuck away the last one. Now we don't have duplicate points in our list if (lastPoint != null) { linkShape.EdgePoints.Add(lastPoint); } } // since we're not changing the nodes this edge connects, this really doesn't do much. // what it DOES do, however, is call ConnectEdgeToNodes, which is an internal method we'd otherwise // be unable to access linkShape.Connect(linkShape.FromShape, linkShape.ToShape); linkShape.ManuallyRouted = false; } }
private void AddElementsToActiveDiagram(List <ModelElement> newElements) { // TODO: Needs sped up int elementCount = newElements.Count; List <ShapeElement> newShapes = new List <ShapeElement>(); using (Transaction t = Store.TransactionManager.BeginTransaction("adding diagram elements")) { for (int index = 0; index < elementCount; index++) { ModelElement newElement = newElements[index]; StatusDisplay.Show($"Adding element {index + 1} of {elementCount}"); ForceAddShape = true; FixUpAllDiagrams.FixUp(this, newElement); newShapes.Add(newElement.GetFirstShapeElement()); ForceAddShape = false; } t.Commit(); } using (Transaction t = Store.TransactionManager.BeginTransaction("adding diagram links")) { for (int index = 0; index < elementCount; index++) { ModelElement newElement = newElements[index]; StatusDisplay.Show($"Linking {index + 1} of {elementCount}"); // find all element links that are attached to our element where the ends are in the diagram but the link isn't already in the diagram List <ElementLink> elementLinks = Store.GetAll <ElementLink>() .Where(link => link.LinkedElements.Contains(newElement) && link.LinkedElements.All(linkedElement => DisplayedElements.Contains(linkedElement)) && !DisplayedElements.Contains(link)) .ToList(); foreach (ElementLink elementLink in elementLinks) { BinaryLinkShape linkShape = CreateChildShape(elementLink) as BinaryLinkShape; newShapes.Add(linkShape); NestedChildShapes.Add(linkShape); switch (elementLink) { case Association a: linkShape.FromShape = a.Source.GetFirstShapeElement() as NodeShape; linkShape.ToShape = a.Target.GetFirstShapeElement() as NodeShape; break; case Generalization g: linkShape.FromShape = g.Subclass.GetFirstShapeElement() as NodeShape; linkShape.ToShape = g.Superclass.GetFirstShapeElement() as NodeShape; break; } } } AutoLayoutShapeElements(newShapes); t.Commit(); } }