using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace Netron.Diagramming.Core {
// ----------------------------------------------------------------------
///
/// Abstract base class for shapes.
///
// ----------------------------------------------------------------------
public abstract partial class ShapeBase : DiagramEntityBase, IShape {
#region Fields
// ------------------------------------------------------------------
///
/// Implementation of IVersion - the current version of
/// ShapeBase.
///
// ------------------------------------------------------------------
protected const double shapeBaseVersion = 1.0;
// ------------------------------------------------------------------
///
/// Specifies if connectors are drawn.
///
// ------------------------------------------------------------------
protected bool mShowConnectors = true;
// ------------------------------------------------------------------
///
/// The collection of Connectors onto which you can attach a
/// connection.
///
// ------------------------------------------------------------------
protected CollectionBase mConnectors;
protected bool mIsFixed = false;
#endregion
#region Properties
// ------------------------------------------------------------------
///
/// Gets the current version.
///
// ------------------------------------------------------------------
public override double Version {
get {
return shapeBaseVersion;
}
}
// ------------------------------------------------------------------
///
/// Gets the image for this shape when displaying in a library (i.e.
/// toolbox).
///
// ------------------------------------------------------------------
public virtual Image LibraryImage {
get {
return null;
}
}
// ------------------------------------------------------------------
///
/// Gets or sets if the connectors are drawn.
///
// ------------------------------------------------------------------
public bool ShowConnectors {
get {
return this.mShowConnectors;
}
set {
bool oldValue = mShowConnectors;
this.mShowConnectors = value;
if (oldValue != value) {
Invalidate();
}
RaiseOnChange(this, new EntityEventArgs(this));
}
}
// ------------------------------------------------------------------
///
/// Gets or sets the canvas to which the entity belongs
///
///
// ------------------------------------------------------------------
public override IModel Model {
get {
return base.Model;
}
set {
base.Model = value;
if (Connectors == null)
throw new InconsistencyException("The connectors collection is 'null'.");
foreach (IConnector con in Connectors) {
con.Model = value;
}
}
}
// ------------------------------------------------------------------
///
/// Gets or sets the Connectors attached to this shape.
///
// ------------------------------------------------------------------
public CollectionBase Connectors {
get { return mConnectors; }
set { mConnectors = value; }
}
// ------------------------------------------------------------------
///
/// Gets or sets the width of the bundle
///
// ------------------------------------------------------------------
[Browsable(true),
Description("The width of the shape"),
Category("Layout")]
public int Width {
get {
return this.mRectangle.Width;
}
set {
Transform(
this.Rectangle.X,
this.Rectangle.Y,
value,
this.Height);
}
}
// ------------------------------------------------------------------
///
/// Gets or sets the height of the bundle
///
// ------------------------------------------------------------------
[Browsable(true),
Description("The height of the shape"),
Category("Layout")]
public int Height {
get {
return this.mRectangle.Height;
}
set {
Transform(
this.Rectangle.X,
this.Rectangle.Y,
this.Width,
value);
}
}
// ------------------------------------------------------------------
///
/// the x-coordinate of the upper-left corner
///
// ------------------------------------------------------------------
[Browsable(true),
Description("The x-coordinate of the upper-left corner"),
Category("Layout")]
public int X {
get {
return mRectangle.X;
}
set {
Point p = new Point(value - mRectangle.X, mRectangle.Y);
this.MoveBy(p);
//if(Model!=null)
// Model.RaiseOnInvalidate(); //note that 'this.Invalidate()' will not be enough
}
}
// ------------------------------------------------------------------
///
/// the y-coordinate of the upper-left corner
///
// ------------------------------------------------------------------
[Browsable(true), Description("The y-coordinate of the upper-left corner"), Category("Layout")]
public int Y {
get {
return mRectangle.Y;
}
set {
Point p = new Point(mRectangle.X, value - mRectangle.Y);
this.MoveBy(p);
//Model.RaiseOnInvalidate();
}
}
// ------------------------------------------------------------------
///
/// Gets or sets the location of the bundle;
///
// ------------------------------------------------------------------
[Browsable(false)]
public Point Location {
get {
return new Point(this.mRectangle.X, this.mRectangle.Y);
}
set {
//we use the move method but it requires the delta value, not
// an absolute position!
Point p = new Point(
value.X - mRectangle.X,
value.Y - mRectangle.Y);
// If you'd use this it would indeed move the bundle but not
// the connectors of the bundle
// this.mRectangle.X = value.X; this.mRectangle.Y = value.Y; Invalidate();
this.MoveBy(p);
}
}
// ------------------------------------------------------------------
///
/// Gets the bounds of the paintable entity
///
///
// ------------------------------------------------------------------
public override Rectangle Rectangle {
get {
return mRectangle;
}
//set{mRectangle = value;
//this.Invalidate(); }
}
#endregion
#region Constructor
///
/// Constructor with the site of the bundle
///
///
protected ShapeBase(IModel model)
: base(model) {
}
///
/// Initializes a new instance of the class.
///
protected ShapeBase()
: base() {
}
///
/// Inits this instance.
///
protected override void Initialize() {
base.Initialize();
mConnectors = new CollectionBase();
mConnectors.OnItemAdded += new EventHandler>(mConnectors_OnItemAdded);
mRectangle = new Rectangle(0, 0, 100, 70);
}
#endregion
#region Methods
// ------------------------------------------------------------------
///
/// Overrides the base 'OnBeforeDelete' to tell all attached
/// connectors they're being deleted.
///
///
// ------------------------------------------------------------------
public override void OnBeforeDelete(DeleteCommand deleteCommand) {
base.OnBeforeDelete(deleteCommand);
foreach (IConnector con in this.mConnectors) {
con.OnBeforeDelete(deleteCommand);
}
}
// ------------------------------------------------------------------
///
/// Overrides the base 'OnAfterDelete' to tell all attached
/// connectors they have been deleted.
///
///
// ------------------------------------------------------------------
public override void OnAfterDelete(DeleteCommand deleteCommand) {
base.OnAfterDelete(deleteCommand);
foreach (IConnector con in this.mConnectors) {
con.OnAfterDelete(deleteCommand);
}
}
// ------------------------------------------------------------------
///
/// Generates a new Uid for this entity.
///
/// bool: If the Uid has to be changed
/// recursively down to the sub-entities, set to true, otherwise
/// false.
// ------------------------------------------------------------------
public override void NewUid(bool recursive) {
if (recursive) {
foreach (IConnector connector in Connectors) {
connector.NewUid(recursive);
}
base.NewUid(recursive);
} else
base.NewUid(recursive);
}
// ------------------------------------------------------------------
///
/// Part of the initialization, this method connects newly added
/// connectors to the parenting shape.
///
/// object
/// CollectionEventArgs
// ------------------------------------------------------------------
void mConnectors_OnItemAdded(
object sender,
CollectionEventArgs e) {
e.Item.Parent = this;
}
// ------------------------------------------------------------------
///
/// The custom menu to be added to the base menu of this entity.
/// 'null' is returned here.
///
/// ToolStripItem[]
// ------------------------------------------------------------------
public override ToolStripItem[] Menu() {
return null;
}
// ------------------------------------------------------------------
///
/// Returns the connector hit by the mouse, if any.
///
/// Point: The mouse coordinates
/// IConnector: The connector hit by the mouse
// ------------------------------------------------------------------
public IConnector HitConnector(Point p) {
for (int k = 0; k < mConnectors.Count; k++) {
if (mConnectors[k].Hit(p)) {
mConnectors[k].Hovered = true;
mConnectors[k].Invalidate();
return mConnectors[k];
} else {
mConnectors[k].Hovered = false;
mConnectors[k].Invalidate();
}
}
return null;
}
// ------------------------------------------------------------------
///
/// Overrides the abstract paint method. Nothing performed here,
/// let the sub-shapes do the painting.
///
/// a graphics object onto which to paint
// ------------------------------------------------------------------
public override void Paint(Graphics g) {
return;
}
// ------------------------------------------------------------------
///
/// Override the abstract Hit method. Here a simple hit test is
/// performed by checking if our "Rectangle" contains the point
/// specified. Override this to perform more complex hit testing.
///
/// Point
/// bool
// ------------------------------------------------------------------
public override bool Hit(Point p) {
Rectangle r = new Rectangle(p, new Size(5, 5));
return Rectangle.Contains(r);
}
// ------------------------------------------------------------------
///
/// Overrides the abstract Invalidate method. This shape's rectangle
/// is inflated (not permanently) by a size of (40, 40) to ensure
/// the whole area is refreshed.
///
// ------------------------------------------------------------------
public override void Invalidate() {
Rectangle r = Rectangle;
r.Offset(-10, -10);
r.Inflate(40, 40);
if (Model != null) {
Model.RaiseOnInvalidateRectangle(r);
}
}
// ------------------------------------------------------------------
///
/// Moves the entity with the given shift (offset).
///
/// Represents a shift-vector, not the absolute
/// position!
// ------------------------------------------------------------------
public override void MoveBy(Point p) {
Rectangle recBefore = mRectangle;
recBefore.Inflate(20, 20);
this.mRectangle.X += p.X;
this.mRectangle.Y += p.Y;
UpdatePaintingMaterial();
for (int k = 0; k < this.mConnectors.Count; k++) {
mConnectors[k].MoveBy(p);
}
RaiseOnChange(this, new EntityEventArgs(this));
//refresh things
this.Invalidate(recBefore);//position before the move
this.Invalidate();//current position
}
// ------------------------------------------------------------------
///
/// Scales the entity with the given factor at the given origin.
/// More an historical milestone than used code.
///
/// The origin.
/// The scale X.
/// The scale Y.
// ------------------------------------------------------------------
void Scale(
Point origin,
double scaleX,
double scaleY) {
#region Variables
//temporary variables to assign the new location of the mConnectors
double a, b;
//the new location of the connector
Point p;
//calculated/scaled/biased corners of the new rectangle
double ltx = 0, lty = 0, rbx = 0, rby = 0;
Rectangle currentRectangle = Rectangle;
//we only need to transform the LT and RB corners since the rest of the rectangle can be deduced from that
/*
PointF[] corners = new PointF[]{new PointF(currentRectangle.X, currentRectangle.Y),
new PointF(currentRectangle.Right, currentRectangle.Bottom),
};
*/
Rectangle newRectangle;
#endregion
#region Transformation matrix
ltx = Math.Round((currentRectangle.X - origin.X) * scaleX, 1) + origin.X;
lty = Math.Round((currentRectangle.Y - origin.Y) * scaleY, 1) + origin.Y;
rbx = Math.Round((currentRectangle.Right - origin.X) * scaleX, 1) + origin.X;
rby = Math.Round((currentRectangle.Bottom - origin.Y) * scaleY, 1) + origin.Y;
//corners[0] = new PointF
//Matrix m = new Matrix();
// m.Translate(-origin.X, -origin.Y,MatrixOrder.Append);
// m.Scale(scaleX, scaleY, MatrixOrder.Append);
// m.Translate(origin.X, origin.Y, MatrixOrder.Append);
#endregion
//transfor the LTRB points of the current rectangle
//m.TransformPoints(corners);
#region Bias
/*
if(currentRectangle.Y <= origin.Y + ViewBase.TrackerOffset && origin.Y - ViewBase.TrackerOffset <= currentRectangle.Y)
{
//do not scale in the Y-direction
lty = currentRectangle.Y;
}
if(currentRectangle.X <= origin.X+ ViewBase.TrackerOffset && origin.X - ViewBase.TrackerOffset <= currentRectangle.X)
{
//do not scale in the X-direction
ltx = currentRectangle.X;
}
if(currentRectangle.Right <= origin.X + ViewBase.TrackerOffset && origin.X - ViewBase.TrackerOffset <= currentRectangle.Right)
{
//do not scale in the X-direction
rbx = currentRectangle.Right;
}
if(currentRectangle.Bottom <= origin.Y + ViewBase.TrackerOffset && origin.Y - ViewBase.TrackerOffset <= currentRectangle.Bottom)
{
//do not scale in the Y-direction
rby = currentRectangle.Bottom;
}
*/
#endregion
/*
ltx = Math.Round(ltx);
lty = Math.Round(lty);
rbx = Math.Round(rbx);
rby = Math.Round(rby);
* */
//now we can re-create the rectangle of this shape
//newRectangle = RectangleF.FromLTRB(ltx, lty, rbx, rby);
newRectangle = Rectangle.FromLTRB(Convert.ToInt32(ltx), Convert.ToInt32(lty), Convert.ToInt32(rbx), Convert.ToInt32(rby));
//if ((newRectangle.Width <= 50 && scaleX < 1) || (newRectangle.Height <= 50 && scaleY < 1))
// return;
#region Scaling of the mConnectors
//Note that this mechanism is way easier than the calculations in the old Netron library
//and it also allows dynamic mConnectors.
foreach (IConnector cn in this.mConnectors) {
//De profundis: ge wilt het gewoon nie weten hoeveel bloed, zweet en tranen ik in de onderstaande berekeningen heb gestoken...
//met al de afrondingen en meetkundinge schaalafwijkingen..tis een wonder dat ik eruit ben geraakt.
//Scaling preserves proportions, so we calculate the proportions before the rectangle was resized and
//re-assign the same proportion after the rectangle is resized.
//I have tried many, many different possibilities but the accumulation of double-to-int conversions is a real pain.
//The only working solution I found was to cut things off after the first decimal.
a = Math.Round(((double)cn.Point.X - (double)mRectangle.X) / (double)mRectangle.Width, 1) * newRectangle.Width + ltx;
b = Math.Round(((double)cn.Point.Y - (double)mRectangle.Y) / (double)mRectangle.Height, 1) * newRectangle.Height + lty;
p = new Point(Convert.ToInt32(a), Convert.ToInt32(b));
cn.Point = p;
}
#endregion
//assign the new calculated rectangle to this shape
this.mRectangle = newRectangle;
RaiseOnChange(this, new EntityEventArgs(this));
//invalidate the space before the resize; very important if the scaling is a contraction!
this.Invalidate(currentRectangle);
//invalidate the current situation
this.Invalidate();
}
// ------------------------------------------------------------------
///
/// Transforms the entity to the given new rectangle.
///
/// The x-coordinate of the new rectangle.
/// The y-coordinate of the new rectangle.
/// The width.
/// The height.
// ------------------------------------------------------------------
public virtual void Transform(int x, int y, int width, int height) {
// Make sure the new size is valid.
if ((width < myMinSize.Width) ||
(height < myMinSize.Height) ||
(width > myMaxSize.Width) ||
(height > myMaxSize.Height)) {
return;
}
double a, b;
Point p;
Rectangle before = mRectangle;
before.Inflate(20, 20);
foreach (IConnector cn in this.mConnectors) {
a = Math.Round(
((double)cn.Point.X - (double)mRectangle.X) /
(double)mRectangle.Width, 1) * width + x - cn.Point.X;
b = Math.Round(
((double)cn.Point.Y - (double)mRectangle.Y) /
(double)mRectangle.Height, 1) * height + y - cn.Point.Y;
p = new Point(Convert.ToInt32(a), Convert.ToInt32(b));
cn.MoveBy(p);
}
mRectangle = new Rectangle(x, y, width, height);
RaiseOnChange(this, new EntityEventArgs(this));
// Update the material; the gradient depends on the rectangle
UpdatePaintingMaterial();
Invalidate(before);
// If we're attached to a group, make sure to let him know
// we've moved/resized.
if (mGroup != null) {
mGroup.CalculateRectangle();
mGroup.Invalidate();
// The group will invalidate us when it's invalidated so just
// stop now.
//return;
}
Invalidate(mRectangle);
}
// ------------------------------------------------------------------
///
/// Transforms the entity to the given new rectangle.
///
/// The new bounds.
// ------------------------------------------------------------------
public virtual void Transform(Rectangle rectangle) {
Transform(
rectangle.X,
rectangle.Y,
rectangle.Width,
rectangle.Height);
}
#endregion
}
}