File: System\Activities\Core\Presentation\FlowchartDesigner.Helpers.cs
Project: ndp\cdf\src\NetFx40\Tools\System.Activities.Core.Presentation\System.Activities.Core.Presentation.csproj (System.Activities.Core.Presentation)
    //------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------
 
namespace System.Activities.Core.Presentation
{
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Windows;
    using System.Windows.Documents;
    using System.Windows.Media;
    using System.Windows.Shapes;
    using System.Activities.Statements;
    using System.Activities.Presentation.Model;
    using System.Linq;
    using System.Runtime;
    using System.Globalization;
    using System.Activities.Presentation;
    using System.Activities.Presentation.FreeFormEditing;
    using System.Activities.Presentation.Internal.PropertyEditing;
 
    partial class FlowchartDesigner
    {
        //Returns true if visual is on the visual tree for point p relative to the panel.
        bool IsVisualHit(UIElement visual, UIElement reference, Point point)
        {
            bool visualIsHit = false;
            HitTestResult result = VisualTreeHelper.HitTest(reference, point);
            if (result != null)
            {
                DependencyObject obj = result.VisualHit;
                while (obj != null)
                {
                    if (visual.Equals(obj))
                    {
                        visualIsHit = true;
                        break;
                    }
                    obj = VisualTreeHelper.GetParent(obj);
                }
            }
            return visualIsHit;
        }
 
        /// <summary>
        /// Priority order of finding the connection point:
        /// 1. Unoccupied (free) connection point closest to the object
        /// 2. Existing incoming/outgoing connection point, depending on the 
        /// Fallback: Connection point closest to the object)
        /// </summary>
        /// <param name="srcConnPoints"></param>
        /// <param name="destConnPoints"></param>
        /// <param name="srcConnPoint"></param>
        /// <param name="destConnPoint"></param>
        internal void FindBestMatchConnectionPointPair(
            List<ConnectionPoint> srcConnPoints, 
            List<ConnectionPoint> destConnPoints, 
            out ConnectionPoint srcConnPoint, 
            out ConnectionPoint destConnPoint)
        {
            double minDist = double.PositiveInfinity;
            double dist;
            ConnectionPoint tempConnPoint;
            srcConnPoint = null;
            destConnPoint = null;
 
            List<ConnectionPoint> candidateSrcConnPoints = FindCandidatePointsForLink(srcConnPoints, ConnectionPointKind.Incoming);
            List<ConnectionPoint> candidateDestConnPoints = FindCandidatePointsForLink(destConnPoints, ConnectionPointKind.Outgoing);
 
            foreach (ConnectionPoint connPoint in candidateSrcConnPoints)
            {
                tempConnPoint = FindClosestConnectionPoint(connPoint, candidateDestConnPoints, out dist);
                if (dist < minDist)
                {
                    minDist = dist;
                    srcConnPoint = connPoint;
                    destConnPoint = tempConnPoint;
                }
            }
 
            Fx.Assert(srcConnPoint != null, "No ConnectionPoint found");
            Fx.Assert(destConnPoint != null, "No ConnectionPoint found");
        }
 
        private static List<ConnectionPoint> FindCandidatePointsForLink(List<ConnectionPoint> destConnPoints, ConnectionPointKind excludePointType)
        {
            List<ConnectionPoint> candidateDestConnPoints;
            IEnumerable<ConnectionPoint> freeDestConnPoints = destConnPoints.Where(p =>
                    p.PointType != excludePointType &&
                    !p.AttachedConnectors.Any());
 
            if (freeDestConnPoints.Any())
            {
                candidateDestConnPoints = freeDestConnPoints.ToList();
            }
            else
            {
                IEnumerable<ConnectionPoint> availablePoints =
                    destConnPoints.Where(
                        p => p.PointType != excludePointType &&
                        p.AttachedConnectors.Any(connector => FreeFormPanel.GetDestinationConnectionPoint(connector).Equals(p)));
 
                candidateDestConnPoints = availablePoints.Any() ? availablePoints.ToList() : destConnPoints;
            }
 
            return candidateDestConnPoints;
        }
 
        /// <summary>
        /// for connection:
        /// 1. return all free connection points are available on the object
        /// 2. return any existing points that are already connected on the object, excluding the unmatched type.
        ///    Fallback: return all connection points of the given object
        /// </summary>
        /// <param name="sourceConnectionPoint"></param>
        /// <param name="dest"></param>
        /// <param name="errorMessage"></param>
        /// <returns></returns>
        ConnectionPoint FindBestMatchDestConnectionPoint(ConnectionPoint sourceConnectionPoint, UIElement dest, out string errorMessage)
        {
            List<ConnectionPoint> destConnPoints = FlowchartDesigner.GetConnectionPoints(dest);
            Fx.Assert(null != destConnPoints && destConnPoints.Any(), "A flownode designer object should have one connection point.");
 
            errorMessage = string.Empty;
 
            if (sourceConnectionPoint.PointType == ConnectionPointKind.Incoming)
            {
                errorMessage = SR.FCInvalidLink;
                return null;
            }
            
            ConnectionPoint destConnectionPoint;
            double minDist;
            List<ConnectionPoint> candidateDestConnPoints = FindCandidatePointsForLink(destConnPoints, ConnectionPointKind.Outgoing);
            destConnectionPoint = FindClosestConnectionPoint(sourceConnectionPoint, candidateDestConnPoints, out minDist);
 
            return destConnectionPoint;
        }
 
        internal ConnectionPoint FindClosestConnectionPoint(ConnectionPoint srcConnPoint, List<ConnectionPoint> destConnPoints, out double minDist)
        {
            return FindClosestConnectionPoint(srcConnPoint.Location, destConnPoints, out minDist);
        }
 
        internal ConnectionPoint FindClosestConnectionPoint(Point srcConnPointLocation, List<ConnectionPoint> destConnPoints, out double minDist)
        {
            return ConnectionPoint.GetClosestConnectionPoint(destConnPoints, srcConnPointLocation, out minDist);
        }
 
        ConnectionPoint FindClosestConnectionPointNotOfType(ConnectionPoint srcConnectionPoint, List<ConnectionPoint> targetConnectionPoints, ConnectionPointKind illegalConnectionPointKind)
        {
            double minDist;
            List<ConnectionPoint> filteredConnectionPoints = new List<ConnectionPoint>();
            foreach (ConnectionPoint connPoint in targetConnectionPoints)
            {
                if (connPoint != null && connPoint.PointType != illegalConnectionPointKind && !connPoint.Equals(srcConnectionPoint))
                {
                    filteredConnectionPoints.Add(connPoint);
                }
            }
            return FindClosestConnectionPoint(srcConnectionPoint, filteredConnectionPoints, out minDist);
        }
 
        void RemoveAdorner(UIElement adornedElement, Type adornerType)
        {
            Fx.Assert(adornedElement != null, "Invalid argument");
            Fx.Assert(typeof(Adorner).IsAssignableFrom(adornerType), "Invalid argument");
            AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(adornedElement);
            if (adornerLayer != null)
            {
                Adorner[] adorners = adornerLayer.GetAdorners(adornedElement);
                if (adorners != null)
                {
                    foreach (Adorner adorner in adorners)
                    {
                        if (adornerType.IsAssignableFrom(adorner.GetType()))
                        {
                            adornerLayer.Remove(adorner);
                        }
                    }
                }
            }
        }
 
        //Returns true if child is a member of the tree rooted at the parent;
        bool IsParentOf(ModelItem parent, ModelItem child)
        {
            Fx.Assert(parent != null, "Invalid argument");
            bool isParentOf = false;
            while (child != null)
            {
                if (parent.Equals(child))
                {
                    isParentOf = true;
                    break;
                }
                child = child.Parent;
            }
            return isParentOf;
        }
 
        ConnectionPoint ConnectionPointHitTest(UIElement element, Point hitPoint)
        {
            List<ConnectionPoint> connectionPoints = new List<ConnectionPoint>();
            List<ConnectionPoint> defaultConnectionPoints = FlowchartDesigner.GetConnectionPoints(element);
            connectionPoints.InsertRange(0, defaultConnectionPoints);
            connectionPoints.Add(FlowchartDesigner.GetTrueConnectionPoint(element));
            connectionPoints.Add(FlowchartDesigner.GetFalseConnectionPoint(element));
            return FreeFormPanel.ConnectionPointHitTest(hitPoint, connectionPoints, this.panel);
        }
 
        internal int NumberOfIncomingLinks(UIElement designer)
        {
            return GetInComingConnectors(designer).Count;
        }
 
        List<Connector> GetAttachedConnectors(UIElement shape)
        {
            HashSet<Connector> attachedConnectors = new HashSet<Connector>();
            List<ConnectionPoint> allConnectionPoints = GetAllConnectionPoints(shape);
            foreach (ConnectionPoint connPoint in allConnectionPoints)
            {
                if (connPoint != null)
                {
                    foreach (Connector connector in connPoint.AttachedConnectors)
                    {
                        attachedConnectors.Add(connector);
                    }
                }
            }
            return attachedConnectors.ToList<Connector>();
        }
 
        List<Connector> GetOutGoingConnectors(UIElement shape)
        {
            List<Connector> outGoingConnectors = new List<Connector>();
            List<ConnectionPoint> allConnectionPoints = GetAllConnectionPoints(shape);
            foreach (ConnectionPoint connPoint in allConnectionPoints)
            {
                if (connPoint != null)
                {
                    outGoingConnectors.AddRange(connPoint.AttachedConnectors.Where(p => FreeFormPanel.GetSourceConnectionPoint(p).Equals(connPoint)));
                }
            }
            return outGoingConnectors;
        }
 
        List<Connector> GetInComingConnectors(UIElement shape)
        {
            List<Connector> inComingConnectors = new List<Connector>();
            List<ConnectionPoint> allConnectionPoints = GetAllConnectionPoints(shape);
            foreach (ConnectionPoint connPoint in allConnectionPoints)
            {
                if (connPoint != null)
                {
                    inComingConnectors.AddRange(connPoint.AttachedConnectors.Where(p => FreeFormPanel.GetDestinationConnectionPoint(p).Equals(connPoint)));
                }
            }
            return inComingConnectors;
        }
 
        static List<ConnectionPoint> GetAllConnectionPoints(UIElement shape)
        {
            List<ConnectionPoint> allConnectionPoints = new List<ConnectionPoint>(6);
            allConnectionPoints.AddRange(FlowchartDesigner.GetConnectionPoints(shape));
            allConnectionPoints.Add(FlowchartDesigner.GetTrueConnectionPoint(shape));
            allConnectionPoints.Add(FlowchartDesigner.GetFalseConnectionPoint(shape));
            return allConnectionPoints;
        }
 
        Point SnapPointToGrid(Point pt)
        {
            pt.X -= pt.X % FreeFormPanel.GridSize;
            pt.Y -= pt.Y % FreeFormPanel.GridSize;
            pt.X = pt.X < 0 ? 0 : pt.X;
            pt.Y = pt.Y < 0 ? 0 : pt.Y;
            return pt;
        }
 
        //This snaps the center of the element to grid.
        //This is called only when dropping an item 
        //Whereever, shapeAnchorPoint is valid, it is made co-incident with the drop location.
        Point SnapVisualToGrid(UIElement element, Point location, Point shapeAnchorPoint, bool isAnchorPointValid)
        {
            Fx.Assert(element != null, "Input UIElement is null");
            element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
            Point oldCenter = location;
            if (!isAnchorPointValid)
            {
                //shapeAnchorPoint is set to (-1, -1) in case where it does not make sense (Eg. toolbox drop).
                //In that scenario align the center of the shape to the drop point.
                location.X -= element.DesiredSize.Width / 2;
                location.Y -= element.DesiredSize.Height / 2;
            }
            else
            {
                //The else part also takes care of the ActivityDesigner case, 
                //where the drag handle is outside the shape.
                location.X -= shapeAnchorPoint.X;
                location.Y -= shapeAnchorPoint.Y;
                oldCenter = new Point(location.X + element.DesiredSize.Width / 2, location.Y + element.DesiredSize.Height / 2);
            }
 
            Point newCenter = SnapPointToGrid(oldCenter);
 
            location.Offset(newCenter.X - oldCenter.X, newCenter.Y - oldCenter.Y);
 
            if (location.X < 0)
            {
                double correction = FreeFormPanel.GridSize - ((location.X * (-1)) % FreeFormPanel.GridSize);
                location.X = (correction == FreeFormPanel.GridSize) ? 0 : correction;
            }
            if (location.Y < 0)
            {
                double correction = FreeFormPanel.GridSize - ((location.Y * (-1)) % FreeFormPanel.GridSize);
                location.Y = (correction == FreeFormPanel.GridSize) ? 0 : correction;
            }
            return location;
        }
 
 
        // This creates a link from modelItems[i] to modelItems[i+1] - foreach i between 0 and modelItems.Count-2;
        void CreateLinks(List<ModelItem> modelItems)
        {
            Fx.Assert(modelItems.Count > 1, "Link creation requires more than one ModelItem");
            modelItems.ForEach(p => { Fx.Assert(this.modelElement.ContainsKey(p), "View should be in the flowchart"); });
            ModelItem[] modelItemsArray = modelItems.ToArray();
            string errorMessage = string.Empty;
            for (int i = 0; i < modelItemsArray.Length - 1; i++)
            {
                string error = string.Empty;
                CreateLinkGesture(this.modelElement[modelItemsArray[i]], this.modelElement[modelItemsArray[i + 1]], out error, null);
                if (!string.Empty.Equals(error))
                {
                    errorMessage += string.Format(CultureInfo.CurrentUICulture, "Link{0}:{1}\n", i + 1, error);
                }
            }
            if (!string.Empty.Equals(errorMessage))
            {
                ErrorReporting.ShowErrorMessage(errorMessage);
            }
        }
 
        // This is a utility function to pack all the elements in an array that match a particular predicate
        // to the end of the array, while maintaining the rest of the system unchanged.
        public static bool Pack<T>(T[] toPack, Func<T, bool> isPacked) where T : class
        {
            if (toPack == null)
            {
                throw FxTrace.Exception.ArgumentNull("toPack");
            }
            if (isPacked == null)
            {
                throw FxTrace.Exception.ArgumentNull("isPacked");
            }
            int count = toPack.Length;
            bool needRearrange = false;
            bool found = false;
            T[] arranged = new T[count];
            for (int i = 0; i < count; i++)
            {
                if (isPacked(toPack[i]))
                {
                    arranged[i] = toPack[i];
                    toPack[i] = null;
                    found = true;
                }
                else
                {
                    if (found)
                    {
                        needRearrange = true;
                    }
                    arranged[i] = null;
                }
            }
            if (needRearrange)
            {
                int j = 0;
                for (int i = 0; i < count; i++)
                {
                    if (toPack[i] != null)
                    {
                        toPack[j++] = toPack[i];
                    }
                }
                j = count;
                for (int i = 0; i < count; i++)
                {
                    int k = count - i - 1;
                    if (arranged[k] != null)
                    {
                        toPack[--j] = arranged[k];
                    }
                }
            }
            else
            {
                for (int i = 0; i < count; i++)
                {
                    if (arranged[i] != null)
                    {
                        toPack[i] = arranged[i];
                    }
                }
            }
            return needRearrange;
        }
 
    }
}