using System;
using System.Drawing;
using System.Windows.Forms;
namespace Netron.Diagramming.Core {
///
/// This tool implement the action of moving shapes on the canvas.
/// Note that this tool is slightly different than other tools since it activates itself unless it has been suspended by another tool.
///
public class MoveTool : AbstractTool, IMouseListener {
#region Fields
///
/// the location of the mouse when the motion starts
///
private Point initialPoint;
///
/// the intermediate location of the mouse during the motion
///
private Point lastPoint;
private IConnector hoveredConnector;
///
/// the AsConnectorMover field
///
private bool connectorMove;
///
/// Gets or sets the AsConnectorMover
///
public bool AsConnectorMover {
get {
return connectorMove;
}
set {
connectorMove = value;
}
}
public static bool Blocked = false;
#endregion
#region Constructor
///
/// Initializes a new instance of the class.
///
/// The name of the tool.
public MoveTool(string name)
: base(name) {
}
#endregion
#region Methods
///
/// Called when the tool is activated.
///
protected override void OnActivateTool() {
Controller.View.CurrentCursor = CursorPalette.Move;
}
///
/// Handles the mouse down event
///
/// The instance containing the event data.
public bool MouseDown(MouseEventArgs e) {
if (e == null)
throw new ArgumentNullException("The argument object is 'null'");
if (e.Button == MouseButtons.Left && Enabled && !IsSuspended && !Blocked) {
if (this.Controller.Model.Selection.SelectedItems.Count > 0) {
initialPoint = e.Location;
lastPoint = initialPoint;
connectorMove = false;
//while moving the shapes we'll clear the tracker
this.Controller.View.ResetTracker();
//now, go for it
this.ActivateTool();
// Even if an entity is hit, I don't think the mouse down
// event should be handled here. That is, return false so
// other mouse listeners can respond. This tool really
// does its thing when the mouse is moved.
//return true;
} else if (this.Controller.Model.Selection.Connector != null && this.Controller.Model.Selection.Connector.Parent is IConnection) {
if (!typeof(IShape).IsInstanceOfType(this.Controller.Model.Selection.Connector.Parent)) //note that there is a separate tool to move shape-connectors!
{
if (this.Controller.Model.Selection.Connector.AllowMove) {
initialPoint = e.Location;
lastPoint = initialPoint;
connectorMove = true; //keep this for the final packaging into the undo manager
this.ActivateTool();
//return true;
}
}
}
}
return false;
}
///
/// Handles the mouse move event
///
/// The instance containing the event data.
public void MouseMove(MouseEventArgs e) {
if (e == null)
throw new ArgumentNullException("The argument object is 'null'");
Point point = e.Location;
if (IsActive) {
//can be a connector
if (this.Controller.Model.Selection.Connector != null) {
this.Controller.Model.Selection.Connector.MoveBy(new Point(point.X - lastPoint.X, point.Y - lastPoint.Y));
#region Do we hit something meaningful?
if (hoveredConnector != null)
hoveredConnector.Hovered = false;
hoveredConnector = this.Controller.Model.Selection.FindConnectorAt(e.Location);
if (hoveredConnector != null)
hoveredConnector.Hovered = true;
if (hoveredConnector != null && hoveredConnector != this.Controller.Model.Selection.Connector) {
Controller.View.CurrentCursor = CursorPalette.Grip;
} else
Controller.View.CurrentCursor = CursorPalette.Move;
#endregion
} else //can be a selection
{
foreach (IDiagramEntity entity in this.Controller.Model.Selection.SelectedItems) {
if (entity.AllowMove) {
entity.MoveBy(new Point(point.X - lastPoint.X, point.Y - lastPoint.Y));
}
}
}
lastPoint = point;
}
}
///
/// When an entity is moved there are various possibilities:
///
/// -
/// a connector is moved
/// 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 .
/// If a connector attached to a connection is moved we have the following fork:
///
/// - the connector was attached
/// the connector has a parent and needs to be detached before being moved and eventually attached to another connector
/// - the connector is moved
/// this is a standard motion and is similar for any
///
/// - the connector ends up somewhere near another connector and will become attached to it
/// 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.
///
///
///
/// - an entity is moved
/// the normal can be used
/// - a set of entities is moved
/// we need to create a bundle to package the entities and then use the
///
/// Several important remarks are in order here:
///
/// - a connector can have only one parent
/// - 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).
///
- when the stack of actions are undone the stack has to be reverted
///
/// - whatever the situation is, the most economical way to code the different cases is by means of a object
///
///
/// The instance containing the event data.
public void MouseUp(MouseEventArgs e) {
if (IsActive) {
DeactivateTool();
//creation of the total undoredo package
CompoundCommand package = new CompoundCommand(this.Controller);
string message = string.Empty;
//notice that the connector can only be a connection connector because of the MouseDown check above
if (connectorMove) {
#region We are moving a connection-connector
#region Is the connector attached?
//detach only if there is a parent different than a connection; the join of a chained connection is allowed to move
if (this.Controller.Model.Selection.Connector.AttachedTo != null && !typeof(IConnection).IsInstanceOfType(this.Controller.Model.Selection.Connector.AttachedTo)) {
DetachConnectorCommand detach = new DetachConnectorCommand(this.Controller, this.Controller.Model.Selection.Connector.AttachedTo, this.Controller.Model.Selection.Connector);
detach.Redo();
package.Commands.Add(detach);
}
#endregion
#region The moving part
//a bundle might look like overkill here but it makes the coding model uniform
Bundle bundle = new Bundle(Controller.Model);
bundle.Entities.Add(this.Controller.Model.Selection.Connector);
MoveCommand move = new MoveCommand(this.Controller, bundle, new Point(lastPoint.X - initialPoint.X, lastPoint.Y - initialPoint.Y));
//no Redo() necessary here!
package.Commands.Add(move);
#endregion
#region The re-attachment near another connector
//let's see if the connection endpoints hit other connectors (different than the selected one!)
//define a predicate delegate to filter things out otherwise the hit will return the moved connector which would results
//in a stack overflow later on
Predicate predicate =
delegate(IConnector conn) {
//whatever, except itself and any children of the moved connector
//since this would entail a child becoming a parent!
if (conn.Hit(e.Location) && conn != this.Controller.Model.Selection.Connector && !this.Controller.Model.Selection.Connector.AttachedConnectors.Contains(conn))
return true;
return false;
};
//find it!
IConnector parentConnector = this.Controller.Model.Selection.FindConnector(predicate);
if (parentConnector != null) //aha, there's an attachment
{
BindConnectorsCommand binder = new BindConnectorsCommand(this.Controller, parentConnector, this.Controller.Model.Selection.Connector);
package.Commands.Add(binder);
binder.Redo(); //this one is necessary since the redo cannot be performed on the whole compound command
}
#endregion
message = "Connector move";
#endregion
} else {
#region We are moving entities other than a connector
Bundle bundle = new Bundle(Controller.Model);
bundle.Entities.AddRange(this.Controller.Model.Selection.SelectedItems);
MoveCommand cmd = new MoveCommand(this.Controller, bundle, new Point(lastPoint.X - initialPoint.X, lastPoint.Y - initialPoint.Y));
package.Commands.Add(cmd);
//not necessary to perform the Redo action of the command since the mouse-move already moved the bundle!
#endregion
message = "Entities move";
}
//reset the hovered connector, if any
if (hoveredConnector != null)
hoveredConnector.Hovered = false;
package.Text = message;
//whatever the content of the package we add it to the undo history
this.Controller.UndoManager.AddUndoCommand(package);
//show the tracker again
this.Controller.View.ShowTracker();
}
}
#endregion
}
}