[code]/*
- Copyright © Northwoods Software Corporation, 1998-2010. All Rights
- Reserved.
-
- Restricted Rights: Use, duplication, or disclosure by the U.S.
- Government is subject to restrictions as set forth in subparagraph
- © (1) (ii) of DFARS 252.227-7013, or in FAR 52.227-19, or in FAR
- 52.227-14 Alt. III, as applicable.
*/
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using Northwoods.Go;
namespace Demo1 {
///
/// An object in the shape of an elliptical curve.
///
[Serializable]
public class Arc : GoShape {
///
/// The constructor produces an arc with a standard black
/// outline and no fill.
///
public Arc() {
this.ResizesRealtime = true;
}
/// <summary>
/// Draw a possibly shadowed arc.
/// </summary>
/// <param name="g"></param>
/// <param name="view"></param>
public override void Paint(Graphics g, GoView view) {
// ??? A SweepAngle of 0 draws nothing.
float sa = this.StartAngle;
float sweep = this.SweepAngle;
// sa is always: 0 <= sa < 360
// sweep is always: -360 <= sweep <= 360
RectangleF rect = this.Bounds;
if (this.Shadowed) {
SizeF offset = GetShadowOffset(view);
if (this.Pen != null) {
Pen pen = GetShadowPen(view, this.PenWidth);
g.DrawArc(pen, rect.X + offset.Width, rect.Y + offset.Height, rect.Width, rect.Height, sa, sweep);
}
}
if (this.Pen != null) {
g.DrawArc(this.Pen, rect.X, rect.Y, rect.Width, rect.Height, sa, sweep);
}
}
/// <summary>
/// Produce a <c>GraphicsPath</c> by adding an arc.
/// </summary>
/// <returns></returns>
public override GraphicsPath MakePath() {
GraphicsPath path = new GraphicsPath(FillMode.Winding);
RectangleF rect = this.Bounds;
if (rect.Width > 0 && rect.Height > 0)
path.AddArc(rect.X, rect.Y, rect.Width, rect.Height, this.StartAngle, this.SweepAngle);
return path;
}
/// <summary>
/// Gets or sets the initial angle of the section of the ellipse to be drawn.
/// </summary>
/// <value>
/// This value is in degrees, measured clockwise from zero along the positive X axis.
/// The value should range from zero to just below 360. Values outside this range
/// are adjusted to equivalent angles that fall in this range.
/// </value>
[Category("Appearance"), DefaultValue(0)]
[Description("The start angle for the side of the arc")]
public float StartAngle {
get { return myStartAngle; }
set {
float old = myStartAngle;
if (value < 0) {
value = 360 - (-value%360);
} else if (value >= 360) {
value = value%360;
}
if (old != value) {
myStartAngle = value;
ResetPath();
Changed(ChangedStartAngle, 0, null, MakeRect(old), 0, null, MakeRect(value));
}
}
}
/// <summary>
/// Gets or sets the angle of the width of the section of the ellipse to be drawn.
/// </summary>
/// <value>
/// This value is in degrees, measured clockwise from the <see cref="StartAngle"/>.
/// Absolute values equal to or greater than 360 degrees are adjusted to the equivalent
/// angles less than 360 degrees.
/// The default value is 300.
/// </value>
[Category("Appearance"), DefaultValue(300)]
[Description("The sweep angle for the body of the arc")]
public float SweepAngle {
get { return mySweepAngle; }
set {
float old = mySweepAngle;
if (value > 360 || value < -360)
value = value%360;
if (old != value) {
mySweepAngle = value;
ResetPath();
Changed(ChangedSweepAngle, 0, null, MakeRect(old), 0, null, MakeRect(value));
}
}
}
/// <summary>
/// Gets or sets whether to add the resizing handle controlling the start angle.
/// </summary>
/// <value>
/// This defaults to true.
/// </value>
[Category("Behavior"), DefaultValue(true)]
[Description("Whether users can resize the start angle of this resizable object.")]
public virtual bool ResizableStartAngle {
get { return myResizableStartAngle; }
set {
bool old = myResizableStartAngle;
if (old != value) {
myResizableStartAngle = value;
Changed(ChangedResizableStartAngle, 0, old, NullRect, 0, value, NullRect);
}
}
}
private bool myResizableStartAngle = true;
/// <summary>
/// Gets or sets whether to add the resizing handle controlling the end angle.
/// </summary>
/// <value>
/// This defaults to true.
/// </value>
[Category("Behavior"), DefaultValue(true)]
[Description("Whether users can resize the end angle of this resizable object.")]
public virtual bool ResizableEndAngle {
get { return myResizableEndAngle; }
set {
bool old = myResizableEndAngle;
if (old != value) {
myResizableEndAngle = value;
Changed(ChangedResizableEndAngle, 0, old, NullRect, 0, value, NullRect);
}
}
}
private bool myResizableEndAngle = true;
/// <summary>
/// Support allowing the user to move the angle control handles.
/// </summary>
/// <param name="view"></param>
/// <param name="origRect"></param>
/// <param name="newPoint"></param>
/// <param name="whichHandle"></param>
/// <param name="evttype"></param>
/// <param name="min"></param>
/// <param name="max"></param>
public override void DoResize(GoView view, RectangleF origRect, PointF newPoint,
int whichHandle, GoInputState evttype, SizeF min, SizeF max) {
if ((whichHandle == StartAngleHandleID || whichHandle == EndAngleHandleID) &&
(this.ResizesRealtime ||
evttype == GoInputState.Finish || evttype == GoInputState.Cancel)) {
if (whichHandle == StartAngleHandleID) {
RectangleF rect = this.Bounds;
float Rx = rect.Width/2;
float Ry = rect.Height/2;
float Cx = rect.X + Rx;
float Cy = rect.Y + Ry;
float ang = GoStroke.GetAngle(newPoint.X-Cx, newPoint.Y-Cy);
float sweep = this.SweepAngle - (ang-this.StartAngle);
if (this.SweepAngle >= 0) {
if (sweep < 0)
sweep += 360;
} else {
if (sweep >= 0)
sweep -= 360;
}
this.SweepAngle = sweep;
this.StartAngle = ang;
} else if (whichHandle == EndAngleHandleID) {
RectangleF rect = this.Bounds;
float Rx = rect.Width/2;
float Ry = rect.Height/2;
float Cx = rect.X + Rx;
float Cy = rect.Y + Ry;
float ang = GoStroke.GetAngle(newPoint.X-Cx, newPoint.Y-Cy);
float sweep = ang-this.StartAngle;
if (this.SweepAngle >= 0) {
if (sweep < 0)
sweep += 360;
} else {
if (sweep >= 0)
sweep -= 360;
}
this.SweepAngle = sweep;
}
} else {
base.DoResize(view, origRect, newPoint, whichHandle, evttype, min, max);
}
}
/// <summary>
/// If <see cref="GoObject.CanReshape"/> is true, supports angle control handles if
/// <see cref="ResizableStartAngle"/> and/or <see cref="ResizableEndAngle"/> are true.
/// </summary>
/// <param name="sel"></param>
/// <param name="selectedObj"></param>
public override void AddSelectionHandles(GoSelection sel, GoObject selectedObj) {
base.AddSelectionHandles(sel, selectedObj);
if (CanReshape()) {
if (this.ResizableStartAngle) {
PointF handlePoint = GetPointAtAngle(this.StartAngle);
IGoHandle handle = sel.CreateResizeHandle(this, selectedObj, handlePoint, StartAngleHandleID, true);
MakeDiamondResizeHandle(handle, Middle);
}
if (this.ResizableEndAngle) {
PointF handlePoint = GetPointAtAngle(this.StartAngle + this.SweepAngle);
IGoHandle handle = sel.CreateResizeHandle(this, selectedObj, handlePoint, EndAngleHandleID, true);
MakeDiamondResizeHandle(handle, Middle);
}
}
}
private void MakeDiamondResizeHandle(IGoHandle handle, int spot) {
GoHandle goh = handle.GoObject as GoHandle;
if (goh != null) {
goh.Style = GoHandleStyle.Diamond;
if (!(goh.SelectedObject is IGoLink))
goh.Brush = Brushes.Yellow;
RectangleF bounds = goh.Bounds;
bounds.Inflate(bounds.Width/6, bounds.Height/6);
goh.Bounds = bounds;
goh.CursorName = "move";
}
}
internal PointF GetPointAtAngle(float ang) {
RectangleF rect = this.Bounds;
float Rx = rect.Width/2;
float Ry = rect.Height/2;
float Cx = rect.X + Rx;
float Cy = rect.Y + Ry;
if (Rx == 0) return new PointF(Cx, Cy);
float cos1 = (float)Math.Cos(ang/180*Math.PI);
float e2 = (float)(1 - ((Ry*Ry)/(Rx*Rx)));
float r1 = (float)(Rx*Math.Sqrt((1-e2)/(1-e2*cos1*cos1)));
float q1x = r1*cos1;
PointF q1 = new PointF(Cx + q1x, (float)(Cy + Math.Tan(ang/180*Math.PI)*q1x));
return q1;
}
/// <summary>
/// A point is in this object only if it really is inside the section of the ellipse.
/// </summary>
/// <param name="p"></param>
/// <returns></returns>
public override bool ContainsPoint(PointF p) {
if (!base.ContainsPoint(p))
return false;
// normalize
RectangleF rect = this.Bounds;
float pw2 = this.PenWidth / 2;
float a = rect.Width/2;
float b = rect.Height/2;
float cx = rect.X + a;
float cy = rect.Y + b;
const float MARGIN = 2;
a += pw2 + MARGIN;
b += pw2 + MARGIN;
if (a == 0 || b == 0)
return false;
float x = p.X - cx;
float y = p.Y - cy;
// outside ellipse
if ((x*x)/(a*a) + (y*y)/(b*b) > 1)
return false;
// inside ellipse
a = rect.Width/2 - pw2 - MARGIN;
b = rect.Height/2 - pw2 - MARGIN;
if (a > 0 && b > 0 && (x*x)/(a*a) + (y*y)/(b*b) < 1)
return false;
float pAngle = GoStroke.GetAngle(p.X - cx, p.Y - cy);
float sa;
float sw;
if (this.SweepAngle < 0) {
sa = StartAngle + SweepAngle;
sw = -SweepAngle;
} else {
sa = StartAngle;
sw = SweepAngle;
}
if (sw > 360)
return true;
if (sa + sw > 360) {
return ((pAngle >= sa) ||
(pAngle <= (sa + sw - 360)));
}
return (pAngle >= sa &&
pAngle <= (sa + sw));
}
/// <summary>
/// Find the intersection points of an arc and the infinite line p1-p2
/// that is closest to point p1.
/// </summary>
/// <param name="p1"></param>
/// <param name="p2"></param>
/// <param name="result"></param>
/// <returns></returns>
public override bool GetNearestIntersectionPoint(PointF p1, PointF p2, out PointF result) {
RectangleF rect = this.Bounds;
float pw2 = this.PenWidth / 2;
rect.Inflate(pw2, pw2);
return GoEllipse.NearestIntersectionOnArc(rect, p1, p2, out result, this.StartAngle, this.SweepAngle);
}
/// <summary>
/// Handle this class's property changes for undo and redo
/// </summary>
/// <param name="e"></param>
/// <param name="undo"></param>
public override void ChangeValue(GoChangedEventArgs e, bool undo) {
switch (e.SubHint) {
case ChangedStartAngle:
this.StartAngle = e.GetFloat(undo);
return;
case ChangedSweepAngle:
this.SweepAngle = e.GetFloat(undo);
return;
case ChangedResizableStartAngle:
this.ResizableStartAngle = (bool)e.GetValue(undo);
return;
case ChangedResizableEndAngle:
this.ResizableEndAngle = (bool)e.GetValue(undo);
return;
default:
base.ChangeValue(e, undo);
return;
}
}
/// <summary>
/// This is a <see cref="GoObject.Changed"/> subhint identifying changes to the value of the <see cref="StartAngle"/> property.
/// </summary>
public const int ChangedStartAngle = 1471;
/// <summary>
/// This is a <see cref="GoObject.Changed"/> subhint identifying changes to the value of the <see cref="SweepAngle"/> property.
/// </summary>
public const int ChangedSweepAngle = 1472;
/// <summary>
/// This is a <see cref="GoObject.Changed"/> subhint identifying changes to the value of the <see cref="ResizableStartAngle"/> property.
/// </summary>
public const int ChangedResizableStartAngle = 1473;
/// <summary>
/// This is a <see cref="GoObject.Changed"/> subhint identifying changes to the value of the <see cref="ResizableEndAngle"/> property.
/// </summary>
public const int ChangedResizableEndAngle = 1474;
/// <summary>
/// A special handle ID for a handle which controls the start angle of the arc.
/// </summary>
public const int StartAngleHandleID = 1044;
/// <summary>
/// A special handle ID for a handle which controls the end angle of the arc.
/// </summary>
public const int EndAngleHandleID = 1045;
// Arc state
private float myStartAngle;
private float mySweepAngle = 300;
}
}[/code]