1 | using System;
|
---|
2 | using System.Diagnostics;
|
---|
3 | using System.Collections;
|
---|
4 | using System.Drawing;
|
---|
5 | using System.Windows.Forms;
|
---|
6 |
|
---|
7 | namespace Netron.Diagramming.Core
|
---|
8 | {
|
---|
9 | /// <summary>
|
---|
10 | /// This tool implement the action of moving shapes on the canvas.
|
---|
11 | /// <para>Note that this tool is slightly different than other tools since it activates itself unless it has been suspended by another tool. </para>
|
---|
12 | /// </summary>
|
---|
13 | public class MoveTool : AbstractTool, IMouseListener
|
---|
14 | {
|
---|
15 |
|
---|
16 | #region Fields
|
---|
17 | /// <summary>
|
---|
18 | /// the location of the mouse when the motion starts
|
---|
19 | /// </summary>
|
---|
20 | private Point initialPoint;
|
---|
21 | /// <summary>
|
---|
22 | /// the intermediate location of the mouse during the motion
|
---|
23 | /// </summary>
|
---|
24 | private Point lastPoint;
|
---|
25 |
|
---|
26 | private IConnector hoveredConnector;
|
---|
27 | /// <summary>
|
---|
28 | /// the AsConnectorMover field
|
---|
29 | /// </summary>
|
---|
30 | private bool connectorMove;
|
---|
31 | /// <summary>
|
---|
32 | /// Gets or sets the AsConnectorMover
|
---|
33 | /// </summary>
|
---|
34 | public bool AsConnectorMover
|
---|
35 | {
|
---|
36 | get
|
---|
37 | {
|
---|
38 | return connectorMove;
|
---|
39 | }
|
---|
40 | set
|
---|
41 | {
|
---|
42 | connectorMove = value;
|
---|
43 | }
|
---|
44 | }
|
---|
45 |
|
---|
46 | public static bool Blocked = false;
|
---|
47 | #endregion
|
---|
48 |
|
---|
49 | #region Constructor
|
---|
50 | /// <summary>
|
---|
51 | /// Initializes a new instance of the <see cref="T:MoveTool"/> class.
|
---|
52 | /// </summary>
|
---|
53 | /// <param name="name">The name of the tool.</param>
|
---|
54 | public MoveTool(string name)
|
---|
55 | : base(name)
|
---|
56 | {
|
---|
57 | }
|
---|
58 | #endregion
|
---|
59 |
|
---|
60 | #region Methods
|
---|
61 |
|
---|
62 | /// <summary>
|
---|
63 | /// Called when the tool is activated.
|
---|
64 | /// </summary>
|
---|
65 | protected override void OnActivateTool()
|
---|
66 | {
|
---|
67 | Controller.View.CurrentCursor = CursorPalette.Move;
|
---|
68 |
|
---|
69 | }
|
---|
70 |
|
---|
71 | /// <summary>
|
---|
72 | /// Handles the mouse down event
|
---|
73 | /// </summary>
|
---|
74 | /// <param name="e">The <see cref="T:System.Windows.Forms.MouseEventArgs"/> instance containing the event data.</param>
|
---|
75 | public bool MouseDown(MouseEventArgs e)
|
---|
76 | {
|
---|
77 | if(e == null)
|
---|
78 | throw new ArgumentNullException("The argument object is 'null'");
|
---|
79 | if(e.Button == MouseButtons.Left && Enabled && !IsSuspended && !Blocked)
|
---|
80 | {
|
---|
81 | if(Selection.SelectedItems.Count > 0)
|
---|
82 | {
|
---|
83 | initialPoint = e.Location;
|
---|
84 | lastPoint = initialPoint;
|
---|
85 | connectorMove = false;
|
---|
86 | //while moving the shapes we'll clear the tracker
|
---|
87 | this.Controller.View.ResetTracker();
|
---|
88 | //now, go for it
|
---|
89 | this.ActivateTool();
|
---|
90 |
|
---|
91 | // Even if an entity is hit, I don't think the mouse down
|
---|
92 | // event should be handled here. That is, return false so
|
---|
93 | // other mouse listeners can respond. This tool really
|
---|
94 | // does its thing when the mouse is moved.
|
---|
95 | //return true;
|
---|
96 | }
|
---|
97 | else if(Selection.Connector != null && Selection.Connector.Parent is IConnection)
|
---|
98 | {
|
---|
99 | if(!typeof(IShape).IsInstanceOfType(Selection.Connector.Parent)) //note that there is a separate tool to move shape-connectors!
|
---|
100 | {
|
---|
101 | initialPoint = e.Location;
|
---|
102 | lastPoint = initialPoint;
|
---|
103 | connectorMove = true; //keep this for the final packaging into the undo manager
|
---|
104 | this.ActivateTool();
|
---|
105 | //return true;
|
---|
106 | }
|
---|
107 | }
|
---|
108 |
|
---|
109 | }
|
---|
110 | return false;
|
---|
111 | }
|
---|
112 |
|
---|
113 | /// <summary>
|
---|
114 | /// Handles the mouse move event
|
---|
115 | /// </summary>
|
---|
116 | /// <param name="e">The <see cref="T:System.Windows.Forms.MouseEventArgs"/> instance containing the event data.</param>
|
---|
117 | public void MouseMove(MouseEventArgs e)
|
---|
118 | {
|
---|
119 | if(e == null)
|
---|
120 | throw new ArgumentNullException("The argument object is 'null'");
|
---|
121 | Point point = e.Location;
|
---|
122 | if(IsActive)
|
---|
123 | {
|
---|
124 | //can be a connector
|
---|
125 | if(Selection.Connector != null)
|
---|
126 | {
|
---|
127 | Selection.Connector.MoveBy(new Point(point.X - lastPoint.X, point.Y - lastPoint.Y));
|
---|
128 | #region Do we hit something meaningful?
|
---|
129 |
|
---|
130 | if (hoveredConnector != null)
|
---|
131 | hoveredConnector.Hovered = false;
|
---|
132 |
|
---|
133 | hoveredConnector = Selection.FindConnectorAt(e.Location);
|
---|
134 | if(hoveredConnector!=null)
|
---|
135 | hoveredConnector.Hovered = true;
|
---|
136 | if(hoveredConnector != null && hoveredConnector!=Selection.Connector)
|
---|
137 | {
|
---|
138 | Controller.View.CurrentCursor = CursorPalette.Grip;
|
---|
139 |
|
---|
140 | }
|
---|
141 | else
|
---|
142 | Controller.View.CurrentCursor = CursorPalette.Move;
|
---|
143 | #endregion
|
---|
144 | }
|
---|
145 | else //can be a selection
|
---|
146 | {
|
---|
147 | foreach(IDiagramEntity entity in Selection.SelectedItems)
|
---|
148 | {
|
---|
149 | if (entity.AllowMove)
|
---|
150 | {
|
---|
151 | entity.MoveBy(new Point(point.X - lastPoint.X, point.Y - lastPoint.Y));
|
---|
152 | }
|
---|
153 | }
|
---|
154 | }
|
---|
155 | lastPoint = point;
|
---|
156 | }
|
---|
157 | }
|
---|
158 | /// <summary>
|
---|
159 | /// When an entity is moved there are various possibilities:
|
---|
160 | /// <list type="bullet">
|
---|
161 | /// <item>
|
---|
162 | /// <term>a connector is moved</term>
|
---|
163 | /// <description>in this case the moved connector can only be part of a onnection because moving shape-connectors is not allowed unless by means of the <see cref="ConnectorMoverTool"/>.
|
---|
164 | /// If a connector attached to a connection is moved we have the following fork:
|
---|
165 | /// <list type="bullet">
|
---|
166 | /// <item>the connector was attached</item>
|
---|
167 | /// <description>the connector has a parent and needs to be detached before being moved and eventually attached to another connector</description>
|
---|
168 | /// <item>the connector is moved</item>
|
---|
169 | /// <description>this is a standard motion and is similar for any <see cref="IDiagramEntity"/>
|
---|
170 | /// </description>
|
---|
171 | /// <item>the connector ends up somewhere near another connector and will become attached to it</item>
|
---|
172 | /// <description>the connector in the proximity of the moved connector will become the parent of it. Note that we previously detached any binding and that a connector can have only one parent.</description>
|
---|
173 | /// </list>
|
---|
174 | /// </description>
|
---|
175 | /// </item>
|
---|
176 | /// <item>an entity is moved</item>
|
---|
177 | /// <description>the normal <see cref="MoveCommand"/> can be used</description>
|
---|
178 | /// <item>a set of entities is moved</item>
|
---|
179 | /// <description>we need to create a bundle to package the entities and then use the <see cref="MoveCommand"/></description>
|
---|
180 | /// </list>
|
---|
181 | /// Several important remarks are in order here:
|
---|
182 | /// <list type="bullet">
|
---|
183 | /// <item>a connector can have only one parent</item>
|
---|
184 | /// <item>we need to package a move action in a command but this command needs NOT to be performed (i.e. call the Redo() method) because the motion already occured through the MouseMove handler. Because of this situation we cannot perform a Redo() on the full package since it would move the entities twice. Hence, commands are execute just after their creation (except for the move).
|
---|
185 | /// <item>when the stack of actions are undone the stack has to be reverted</item>
|
---|
186 | /// </item>
|
---|
187 | /// <item>whatever the situation is, the most economical way to code the different cases is by means of a <see cref="CompoundCommand"/> object</item>
|
---|
188 | /// </list>
|
---|
189 | /// </summary>
|
---|
190 | /// <param name="e">The <see cref="T:System.Windows.Forms.MouseEventArgs"/> instance containing the event data.</param>
|
---|
191 | public void MouseUp(MouseEventArgs e)
|
---|
192 | {
|
---|
193 | if(IsActive)
|
---|
194 | {
|
---|
195 | DeactivateTool();
|
---|
196 | //creation of the total undoredo package
|
---|
197 | CompoundCommand package = new CompoundCommand(this.Controller);
|
---|
198 | string message = string.Empty;
|
---|
199 | //notice that the connector can only be a connection connector because of the MouseDown check above
|
---|
200 | if(connectorMove)
|
---|
201 | {
|
---|
202 | #region We are moving a connection-connector
|
---|
203 |
|
---|
204 | #region Is the connector attached?
|
---|
205 | //detach only if there is a parent different than a connection; the join of a chained connection is allowed to move
|
---|
206 | if( Selection.Connector.AttachedTo != null && !typeof(IConnection).IsInstanceOfType(Selection.Connector.AttachedTo))
|
---|
207 | {
|
---|
208 | DetachConnectorCommand detach = new DetachConnectorCommand(this.Controller, Selection.Connector.AttachedTo, Selection.Connector);
|
---|
209 | detach.Redo();
|
---|
210 | package.Commands.Add(detach);
|
---|
211 |
|
---|
212 | }
|
---|
213 | #endregion
|
---|
214 |
|
---|
215 | #region The moving part
|
---|
216 | //a bundle might look like overkill here but it makes the coding model uniform
|
---|
217 | Bundle bundle = new Bundle(Controller.Model);
|
---|
218 | bundle.Entities.Add(Selection.Connector);
|
---|
219 | MoveCommand move = new MoveCommand(this.Controller, bundle, new Point(lastPoint.X - initialPoint.X, lastPoint.Y - initialPoint.Y));
|
---|
220 | //no Redo() necessary here!
|
---|
221 | package.Commands.Add(move);
|
---|
222 | #endregion
|
---|
223 |
|
---|
224 | #region The re-attachment near another connector
|
---|
225 | //let's see if the connection endpoints hit other connectors (different than the selected one!)
|
---|
226 | //define a predicate delegate to filter things out otherwise the hit will return the moved connector which would results
|
---|
227 | //in a stack overflow later on
|
---|
228 | Predicate<IConnector> predicate =
|
---|
229 | delegate(IConnector conn)
|
---|
230 | {
|
---|
231 | //whatever, except itself and any children of the moved connector
|
---|
232 | //since this would entail a child becoming a parent!
|
---|
233 | if(conn.Hit(e.Location) && conn != Selection.Connector && !Selection.Connector.AttachedConnectors.Contains(conn))
|
---|
234 | return true;
|
---|
235 | return false;
|
---|
236 | };
|
---|
237 | //find it!
|
---|
238 | IConnector parentConnector = Selection.FindConnector(predicate);
|
---|
239 |
|
---|
240 | if(parentConnector != null) //aha, there's an attachment
|
---|
241 | {
|
---|
242 | BindConnectorsCommand binder = new BindConnectorsCommand(this.Controller, parentConnector, Selection.Connector);
|
---|
243 | package.Commands.Add(binder);
|
---|
244 | binder.Redo(); //this one is necessary since the redo cannot be performed on the whole compound command
|
---|
245 | }
|
---|
246 | #endregion
|
---|
247 |
|
---|
248 | message = "Connector move";
|
---|
249 | #endregion
|
---|
250 | }
|
---|
251 | else
|
---|
252 | {
|
---|
253 | #region We are moving entities other than a connector
|
---|
254 | Bundle bundle = new Bundle(Controller.Model);
|
---|
255 | bundle.Entities.AddRange(Selection.SelectedItems);
|
---|
256 | MoveCommand cmd = new MoveCommand(this.Controller, bundle, new Point(lastPoint.X - initialPoint.X, lastPoint.Y - initialPoint.Y));
|
---|
257 | package.Commands.Add(cmd);
|
---|
258 | //not necessary to perform the Redo action of the command since the mouse-move already moved the bundle!
|
---|
259 | #endregion
|
---|
260 |
|
---|
261 | message = "Entities move";
|
---|
262 | }
|
---|
263 | //reset the hovered connector, if any
|
---|
264 | if (hoveredConnector != null)
|
---|
265 | hoveredConnector.Hovered = false;
|
---|
266 | package.Text = message;
|
---|
267 | //whatever the content of the package we add it to the undo history
|
---|
268 | this.Controller.UndoManager.AddUndoCommand(package);
|
---|
269 |
|
---|
270 | //show the tracker again
|
---|
271 | this.Controller.View.ShowTracker();
|
---|
272 |
|
---|
273 | }
|
---|
274 | }
|
---|
275 | #endregion
|
---|
276 | }
|
---|
277 |
|
---|
278 | }
|
---|