[reflection] Coop handles icalls in System.Reflection and System.RuntimeTypeHandle...
[mono.git] / mcs / class / referencesource / System.Activities.Presentation / System.Activities.Presentation / System / Activities / Presentation / FreeFormEditing / AutoConnectHelper.cs
1 //----------------------------------------------------------------
2 // <copyright company="Microsoft Corporation">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //----------------------------------------------------------------
6
7 namespace System.Activities.Presentation.FreeFormEditing
8 {
9     using System.Activities.Presentation.Model;
10     using System.Collections.Generic;
11     using System.Runtime;
12     using System.Windows;
13     using System.Windows.Documents;
14     using System.Windows.Media;
15
16     internal sealed class AutoConnectHelper
17     {
18         private const double HitRegionOffset = 30;
19         private const double DropTargetWidth = 20;
20         private const double DropPointOffset = 50;
21
22         private FreeFormPanel panel = null;
23         private UIElement currentTarget = null;
24
25         public AutoConnectHelper(FreeFormPanel panel)
26         {
27             this.panel = panel;
28         }
29
30         internal UIElement CurrentTarget
31         {
32             get
33             {
34                 UIElement target = this.currentTarget;
35                 
36                 // It is possible that currentTarget has been removed from the FreeFormPanel
37                 if (target != null && VisualTreeHelper.GetParent(target) == null)
38                 {
39                     this.currentTarget = null;
40                     return null;
41                 }
42
43                 return target;
44             }
45
46             set
47             {
48                 this.currentTarget = value;
49             }
50         }
51
52         internal static Rect GetAutoConnectHitRect(DependencyObject target)
53         {
54             Size size = FreeFormPanel.GetChildSize(target);
55             Point location = FreeFormPanel.GetLocation(target);
56             Rect rect = new Rect(new Point(location.X - HitRegionOffset, location.Y - HitRegionOffset), new Size(size.Width + (HitRegionOffset * 2), size.Height + (HitRegionOffset * 2)));
57             return rect;
58         }
59
60         // *                                                                   
61         // * Diagram: Hit test rect for auto connect (best view with Courier New)                           
62         // *                                                                   
63         // *                        ├         W         ┤                      
64         // *                                 ┤a├                               
65         // *               ├   O    ┤                   ├    O   ┤             
66         // *                                 ┌─┐                    ┬          
67         // *                                 │ │                               
68         // *                                 │ │                    O          
69         // *                                 │ │                               
70         // *                        ┌────────┴─┴────────┐           ┴       ┬  
71         // *                        │                   │                      
72         // *               ┌────────┤                   ├────────┐     ┴ a  H  
73         // *               └────────┤                   ├────────┘     ┬       
74         // *                        │                   │                      
75         // *                        └────────┬─┬────────┘           ┬       ┴  
76         // *                                 │ │                               
77         // *                                 │ │                    O          
78         // *                                 │ │                               
79         // *                                 └─┘                    ┴          
80         // *                                                                   
81         // * W: Width                                                          
82         // * H: Height                                                         
83         // * O: HitRegionOffset                                                
84         // * a: DropTargetWidth                                                
85         // *                                                                   
86         internal static List<Rect> CreateHitTestRects(Point targetLocation, Size targetSize)
87         {
88             List<Rect> rects = new List<Rect>();
89
90             // See the diagram above for these rects
91             rects.Add(new Rect(new Point(targetLocation.X - HitRegionOffset, targetLocation.Y + ((targetSize.Height - DropTargetWidth) / 2)), new Size(HitRegionOffset, DropTargetWidth)));
92             rects.Add(new Rect(new Point(targetLocation.X + targetSize.Width, targetLocation.Y + ((targetSize.Height - DropTargetWidth) / 2)), new Size(HitRegionOffset, DropTargetWidth)));
93             rects.Add(new Rect(new Point(targetLocation.X + ((targetSize.Width - DropTargetWidth) / 2), targetLocation.Y - HitRegionOffset), new Size(DropTargetWidth, HitRegionOffset)));
94             rects.Add(new Rect(new Point(targetLocation.X + ((targetSize.Width - DropTargetWidth) / 2), targetLocation.Y + targetSize.Height), new Size(DropTargetWidth, HitRegionOffset)));
95             return rects;
96         }
97
98         internal static AutoConnectDirections GetAutoConnectDirection(int index)
99         {
100             switch (index)
101             {
102                 case 0:
103                     return AutoConnectDirections.Left;
104                 case 1:
105                     return AutoConnectDirections.Right;
106                 case 2:
107                     return AutoConnectDirections.Top;
108                 case 3:
109                     return AutoConnectDirections.Bottom;
110                 default:
111                     return AutoConnectDirections.None;
112             }
113         }
114
115         internal static Point CalculateDropLocation(Size droppedSize, DependencyObject autoConnectTarget, AutoConnectDirections direction, HashSet<Point> shapeLocations)
116         {
117             Point dropPoint = new Point(-1, -1);
118             if (autoConnectTarget != null)
119             {
120                 Point location = FreeFormPanel.GetLocation(autoConnectTarget);
121                 Size size = FreeFormPanel.GetChildSize(autoConnectTarget);
122                 switch (direction)
123                 {
124                     case AutoConnectDirections.Left:
125                         dropPoint = new Point(location.X - DropPointOffset - droppedSize.Width, location.Y + ((size.Height - droppedSize.Height) / 2));
126                         break;
127                     case AutoConnectDirections.Right:
128                         dropPoint = new Point(location.X + size.Width + DropPointOffset, location.Y + ((size.Height - droppedSize.Height) / 2));
129                         break;
130                     case AutoConnectDirections.Top:
131                         dropPoint = new Point(location.X + ((size.Width - droppedSize.Width) / 2), location.Y - DropPointOffset - droppedSize.Height);
132                         break;
133                     case AutoConnectDirections.Bottom:
134                         dropPoint = new Point(location.X + ((size.Width - droppedSize.Width) / 2), location.Y + DropPointOffset + size.Height);
135                         break;
136                     default:
137                         Fx.Assert(false, "Should not be here");
138                         break;
139                 }
140
141                 dropPoint = new Point(dropPoint.X < 0 ? 0 : dropPoint.X, dropPoint.Y < 0 ? 0 : dropPoint.Y);
142                 if (shapeLocations != null)
143                 {
144                     while (shapeLocations.Contains(dropPoint))
145                     {
146                         dropPoint.Offset(FreeFormPanel.GridSize, FreeFormPanel.GridSize);
147                     }
148                 }
149             }
150
151             return dropPoint;
152         }
153
154         internal static EdgeLocation AutoConnectDirection2EdgeLocation(AutoConnectDirections direction)
155         {
156             EdgeLocation edgeLocation = EdgeLocation.Right;
157             switch (direction)
158             {
159                 case AutoConnectDirections.Left:
160                     edgeLocation = EdgeLocation.Left;
161                     break;
162                 case AutoConnectDirections.Right:
163                     edgeLocation = EdgeLocation.Right;
164                     break;
165                 case AutoConnectDirections.Top:
166                     edgeLocation = EdgeLocation.Top;
167                     break;
168                 case AutoConnectDirections.Bottom:
169                     edgeLocation = EdgeLocation.Bottom;
170                     break;
171             }
172
173             return edgeLocation;
174         }
175         
176         internal static DependencyObject GetShapeContainingPoint(Point point, List<DependencyObject> shapes)
177         {
178             DependencyObject result = null;
179
180             foreach (DependencyObject shape in shapes)
181             {
182                 Rect rect = GetAutoConnectHitRect(shape);
183                 if (rect.Contains(point))
184                 {
185                     // The design is that if the point is inside of multiple hit test regions, we do not 
186                     // show any drop targets to avoid confusion.
187                     if (result != null)
188                     {
189                         return null;
190                     }
191
192                     result = shape;
193                 }
194             }
195
196             return result;
197         }
198
199         internal DependencyObject FindTarget(Point point, DependencyObject dragged, out AutoConnectDirections directions)
200         {
201             directions = AutoConnectDirections.None;
202             List<DependencyObject> childShapes = this.panel.GetChildShapes(dragged);
203             DependencyObject target = GetShapeContainingPoint(point, childShapes);
204
205             if (target != null)
206             {
207                 directions = this.GetAutoConnectDirections(directions, childShapes, target);
208             }
209
210             return target;
211         }
212
213         internal void OnPreviewDragOverPanel(DragEventArgs e)
214         {
215             // Do not do auto-connect if we are currently auto-splitting.
216             if (this.panel.CurrentAutoSplitTarget != null)
217             {
218                 return;
219             }
220
221             DependencyObject currentTarget = this.CurrentTarget;
222             if (currentTarget != null)
223             {
224                 Rect rect = GetAutoConnectHitRect(currentTarget);
225                 if (rect.Contains(e.GetPosition(this.panel)))
226                 {
227                     // Do not update the adorner if the cursor is still in the hit region of the current target
228                     return;
229                 }
230             }
231
232             ModelItem draggedModelItem = DragDropHelper.GetDraggedModelItemInternal(e);
233             DependencyObject draggedView = draggedModelItem != null ? draggedModelItem.View : null;
234             AutoConnectDirections direction;
235             UIElement target = this.FindTarget(e.GetPosition(this.panel), draggedView, out direction) as UIElement;
236             this.RemoveDropTargets();
237             if (target != null && (direction & this.panel.AutoConnectContainer.GetDirectionsAllowed(e, target)) != AutoConnectDirections.None)
238             {
239                 this.AddDropTargets(e, target, direction);
240             }  
241         }
242
243         internal void RemoveDropTargets()
244         {
245             UIElement adornedElement = this.CurrentTarget;
246             if (adornedElement != null)
247             {
248                 AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(adornedElement);
249                 Fx.Assert(adornerLayer != null, "AdornerLayer should not be null.");
250                 Adorner[] adorners = adornerLayer.GetAdorners(adornedElement);
251                 foreach (Adorner adorner in adorners)
252                 {
253                     if (adorner is AutoConnectAdorner)
254                     {
255                         adornerLayer.Remove(adorner);
256                         this.CurrentTarget = null;
257                         return;
258                     }
259                 }
260             }
261         }
262
263         // Check if hit test rects collide with any children of the FreeFormPanel, and remove that direction in case a collision is found.
264         private static void RemoveDirectionsInCollision(List<DependencyObject> childShapes, DependencyObject target, List<Rect> hitTestRects, ref AutoConnectDirections directions)
265         {
266             foreach (DependencyObject shape in childShapes)
267             {
268                 if (directions == AutoConnectDirections.None)
269                 {
270                     break;
271                 }
272
273                 if (object.Equals(shape, target))
274                 {
275                     continue;
276                 }
277
278                 Point shapeLocation = FreeFormPanel.GetLocation(shape);
279                 Size shapeSize = FreeFormPanel.GetChildSize(shape);
280                 Rect shapeRect = new Rect(shapeLocation, shapeSize);
281                 for (int i = 0; i < hitTestRects.Count; i++)
282                 {
283                     if (hitTestRects[i].IntersectsWith(shapeRect))
284                     {
285                         directions &= ~AutoConnectHelper.GetAutoConnectDirection(i);
286                     }
287                 }
288             }
289         }
290
291         // Check if hit test rects are completely within the FreeFormPanel rect, and remove that direction in case it's not.
292         private void RemoveDirectionsOutsideOfPanel(List<Rect> hitTestRects, ref AutoConnectDirections directions)
293         {
294             Rect panelRect = new Rect(0, 0, this.panel.Width, this.panel.Height);
295             for (int i = 0; i < hitTestRects.Count; i++)
296             {
297                 if (!panelRect.Contains(hitTestRects[i]))
298                 {
299                     directions &= ~AutoConnectHelper.GetAutoConnectDirection(i);
300                 }
301             }
302         }
303
304         private AutoConnectDirections GetAutoConnectDirections(AutoConnectDirections directions, List<DependencyObject> childShapes, DependencyObject target)
305         {
306             directions = AutoConnectDirections.Top | AutoConnectDirections.Bottom | AutoConnectDirections.Left | AutoConnectDirections.Right;
307             List<Rect> hitTestRects = CreateHitTestRects(FreeFormPanel.GetLocation(target), FreeFormPanel.GetChildSize(target));
308             this.RemoveDirectionsOutsideOfPanel(hitTestRects, ref directions);
309             RemoveDirectionsInCollision(childShapes, target, hitTestRects, ref directions);
310             return directions;
311         }
312
313         private void AddDropTargets(DragEventArgs e, UIElement adornedElement, AutoConnectDirections directions)
314         {
315             AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(adornedElement);
316             Fx.Assert(adornerLayer != null, "AdornerLayer should not be null.");
317             adornerLayer.Add(new AutoConnectAdorner(adornedElement, this.panel, (directions & this.panel.AutoConnectContainer.GetDirectionsAllowed(e, adornedElement))));
318             this.CurrentTarget = adornedElement;
319         }
320     }
321 }