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