// --- ---------- ------------------------------------------------------------
// JLH 03/30/2011 Created this file
// JLH 04/03/2011 Added information panel (angles) rendering
+// JLH 08/16/2013 Added continuous user feedack like for Line and Circle
//
#include "arc.h"
#include <QtGui>
+#include "geometry.h"
#include "mathconstants.h"
#include "painter.h"
Arc::Arc(Vector p1, double r, double a1, double a2, Object * p/*= NULL*/):
- Object(p1, p), /*type(OTArc),*/ radius(r), startAngle(a1), angleSpan(a2)
+ Object(p1, p), /*type(OTArc),*/ radius(r), startAngle(a1), angleSpan(a2),
+ draggingCenter(false), draggingEdge(false), draggingRotate(false),
+ draggingSpan(false),
+ hitCenter(false), hitArc(false), hitRotate(false), hitSpan(false)
{
// This is in the base class, why can't we use the contructor to fill it???
type = OTArc;
+ state = OSInactive;
}
/*virtual*/ void Arc::Draw(Painter * painter)
{
QPen pen;
+ painter->SetPen(QPen(Qt::red, 2.0, Qt::DotLine));
- if (state == OSSelected)
- {
- Point p1(cos(startAngle), sin(startAngle));
- Point p2(cos(startAngle + angleSpan), sin(startAngle + angleSpan));
- Vector handle2 = (p1 * radius) + position;
- Vector handle3 = (p2 * radius) + position;
+ Point p1(cos(startAngle), sin(startAngle));
+ Point p2(cos(startAngle + angleSpan), sin(startAngle + angleSpan));
+ Vector handle2 = (p1 * radius) + position;
+ Vector handle3 = (p2 * radius) + position;
- if ((hitHandle2 || hitHandle3) && objectWasDragged)
- {
- if (hitHandle2)
- {
- // If we rotating, we draw a guideline showing the angle we're
- // moving it from.
- Point p3(cos(oldAngle), sin(oldAngle));
- Vector oldLine = (p3 * (radius * 1.25)) + position;
- pen = QPen(QColor(0x80, 0x80, 0x80), 1.0, Qt::DashLine);
- painter->SetPen(pen);
- painter->DrawLine((int)position.x, (int)position.y, (int)oldLine.x, (int)oldLine.y);
- }
-
- // In rotating and setting the span, we draw a line showing where
- // we angle/span is that we're setting.
- pen = QPen(QColor(0x00, 0xC0, 0x80), 1.0, Qt::DashLine);
- painter->SetPen(pen);
- painter->DrawLine((int)position.x, (int)position.y, (int)oldPoint.x, (int)oldPoint.y);
- }
+ if ((state == OSSelected) || ((state == OSInactive) && hitRotate))
+ painter->DrawHandle(handle2);
+
+ if ((state == OSSelected) || ((state == OSInactive) && hitSpan))
+ painter->DrawHandle(handle3);
- // Draw the center point of the arc
- painter->SetPen(QPen(Qt::red, 2.0, Qt::DotLine));
+ if ((state == OSSelected) || ((state == OSInactive) && hitCenter))
painter->DrawHandle(position);
- // Draw the rotation & span setting handles
- painter->DrawHandle(handle2);
- painter->DrawHandle(handle3);
+ if ((state == OSInactive) && !hitArc)
+ painter->SetPen(QPen(Qt::black, 1.0, Qt::SolidLine));
- // If we're rotating or setting the span, draw an information panel
- // showing both absolute and relative angles being set.
- if ((hitHandle2 || hitHandle3 || hitHandle4) && objectWasDragged)
+ painter->DrawArc(position, radius, startAngle, angleSpan);
+
+ if (draggingRotate || draggingSpan)
+ {
+ if (draggingRotate)
{
- double absAngle = (Vector(oldPoint - position).Angle()) * RADIANS_TO_DEGREES;
- double relAngle = (startAngle >= oldAngle ? startAngle - oldAngle :
- startAngle - oldAngle + (2.0 * PI)) * RADIANS_TO_DEGREES;
-
- QString text;
-
- if (hitHandle2)
- {
- text = QObject::tr("Abs ") + QChar(0x2221) + ": %1" + QChar(0x00B0)
- + QObject::tr("\nRel ") + QChar(0x2221) + ": %2" + QChar(0x00B0);
- text = text.arg(absAngle, 0, 'd', 4).arg(relAngle, 0, 'd', 4);
- }
- else if (hitHandle3)
- {
- text = QObject::tr("Abs ") + QChar(0x2221) + ": %1" + QChar(0x00B0)
- + QObject::tr("\nSpan: %2") + QChar(0x00B0);
- text = text.arg(absAngle, 0, 'd', 4).arg(angleSpan * RADIANS_TO_DEGREES, 0, 'd', 4);
- }
- else if (hitHandle4)
- {
- text = QObject::tr("Radius: %1\nScale: %2%");
- text = text.arg(radius, 0, 'd', 4).arg(radius / oldRadius * 100.0, 0, 'd', 0);
- }
-
- pen = QPen(QColor(0x00, 0xFF, 0x00), 1.0, Qt::SolidLine);
- painter->SetPen(pen);
- painter->SetBrush(QBrush(QColor(0x40, 0xFF, 0x40, 0x9F)));
- QRectF textRect(10.0, 10.0, 270.0, 70.0); // x, y, w, h
- painter->DrawRoundedRect(textRect, 7.0, 7.0);
-
- textRect.setLeft(textRect.left() + 14);
- painter->SetFont(*Object::font);
-// pen = QPen(QColor(0xDF, 0x5F, 0x00), 1.0, Qt::SolidLine);
- pen = QPen(QColor(0x00, 0x5F, 0xDF));
+ // If we rotating, we draw a guideline showing the angle we're
+ // moving it from.
+ Point p3(cos(oldAngle), sin(oldAngle));
+ Vector oldLine = (p3 * (radius * 1.25)) + position;
+ pen = QPen(QColor(0x80, 0x80, 0x80), 1.0, Qt::DashLine);
painter->SetPen(pen);
- painter->DrawText(textRect, Qt::AlignVCenter, text);
- painter->SetPen(QPen(QColor(0xDF, 0x5F, 0x00)));
+// painter->DrawLine((int)position.x, (int)position.y, (int)oldLine.x, (int)oldLine.y);
+ painter->DrawLine(position, oldLine);
}
+
+ // In rotating and setting the span, we draw a line showing where
+ // we angle/span is that we're setting.
+ pen = QPen(QColor(0x00, 0xC0, 0x80), 1.0, Qt::DashLine);
+ painter->SetPen(pen);
+ painter->DrawLine((int)position.x, (int)position.y, (int)oldPoint.x, (int)oldPoint.y);
}
- else
+
+ // If we're rotating or setting the span, draw an information panel
+ // showing both absolute and relative angles being set.
+ if (draggingRotate || draggingSpan || draggingEdge)
{
- pen = QPen(Qt::black, 1.0, Qt::SolidLine);
+ double absAngle = (Vector(oldPoint - position).Angle()) * RADIANS_TO_DEGREES;
+ double relAngle = (startAngle >= oldAngle ? startAngle - oldAngle :
+ startAngle - oldAngle + (2.0 * PI)) * RADIANS_TO_DEGREES;
+
+ QString text;
+
+ if (draggingRotate)
+ {
+ text = QObject::tr("Abs ") + QChar(0x2221) + ": %1" + QChar(0x00B0)
+ + QObject::tr("\nRel ") + QChar(0x2221) + ": %2" + QChar(0x00B0);
+ text = text.arg(absAngle, 0, 'd', 4).arg(relAngle, 0, 'd', 4);
+ }
+ else if (draggingSpan)
+ {
+ text = QObject::tr("Abs ") + QChar(0x2221) + ": %1" + QChar(0x00B0)
+ + QObject::tr("\nSpan: %2") + QChar(0x00B0);
+ text = text.arg(absAngle, 0, 'd', 4).arg(angleSpan * RADIANS_TO_DEGREES, 0, 'd', 4);
+ }
+ else if (draggingEdge)
+ {
+ text = QObject::tr("Radius: %1\nScale: %2%");
+ text = text.arg(radius, 0, 'd', 4).arg(radius / oldRadius * 100.0, 0, 'd', 0);
+ }
+
+#if 0
+ pen = QPen(QColor(0x00, 0xFF, 0x00), 1.0, Qt::SolidLine);
painter->SetPen(pen);
- }
+ painter->SetBrush(QBrush(QColor(0x40, 0xFF, 0x40, 0x9F)));
+ QRectF textRect(10.0, 10.0, 270.0, 70.0); // x, y, w, h
+ painter->DrawRoundedRect(textRect, 7.0, 7.0);
- painter->DrawArc(position, radius, startAngle, angleSpan);
-// painter->DrawRect(Extents());
+ textRect.setLeft(textRect.left() + 14);
+ painter->SetFont(*Object::font);
+ pen = QPen(QColor(0x00, 0x5F, 0xDF));
+ painter->SetPen(pen);
+ painter->DrawText(textRect, Qt::AlignVCenter, text);
+#else
+ painter->DrawInformativeText(text);
+#endif
+ }
}
/*virtual*/ bool Arc::Collided(Vector point)
{
+ // Someone told us to fuck off, so we'll fuck off. :-)
+ if (ignoreClicks)
+ return false;
+
objectWasDragged = false;
- Vector v1 = point - position; // Head minus tail (vector points at "point")
+ bool hitSomething = HitTest(point);
+ draggingCenter = hitCenter;
+ draggingEdge = hitArc;
+ draggingRotate = hitRotate;
+ draggingSpan = hitSpan;
- // Check for collision with various things...
- hitHandle1 = false; // Moving
- hitHandle2 = false; // Rotation
- hitHandle3 = false; // Setting span of the arc
- hitHandle4 = false; // Resizing
-/*
-What we have:
-the center of the arc
-the starting angle
-the span of the arc
-The point on a unit circle given an angle a is x = cos(a), y = sin(a)
-This vector is already unitized, so all we need to do to get our point is to multiply it by
-radius (to get the length correct) and add it to the center point (to get the correct position).
-*/
- Point p1(cos(startAngle), sin(startAngle));
- Point p2(cos(startAngle + angleSpan), sin(startAngle + angleSpan));
- Vector handle2 = (p1 * radius) + position;
- Vector handle3 = (p2 * radius) + position;
- double pointerAngle = v1.Angle();
+ // Now that we've done our hit testing on the non-snapped point, snap it if
+ // necessary...
+ if (snapToGrid)
+ point = SnapPointToGrid(point);
-#if 1
- // Center handle
- if (v1.Magnitude() < 10.0)
- hitHandle1 = true;
- // Span handle
- else if (Vector(handle3 - point).Magnitude() < 10.0)
- hitHandle3 = true;
- // Rotate handle
- else if (Vector(handle2 - point).Magnitude() < 10.0)
- hitHandle2 = true;
- // Resize handle (the arc itself)
- else if ((v1.Magnitude() < radius + 3.0) && (v1.Magnitude() > radius - 3.0)
- && AngleInArcSpan(pointerAngle))
- hitHandle4 = true;
-#endif
+ if (snapPointIsValid)
+ point = snapPoint;
/*
State Management:
so let's do like this:
*/
- if (hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4)
+ if (hitSomething)
{
oldState = state;
state = OSSelected;
oldPoint = position;
oldAngle = startAngle;
oldRadius = radius;
-
return true;
}
}
-/*virtual*/ void Arc::PointerMoved(Vector point)
+/*virtual*/ bool Arc::PointerMoved(Vector point)
{
+// one other thing to check here for is if a modifier key is being held as well,
+// to allow for multi-selection
if (selectionInProgress)
{
// Check for whether or not the rect contains this circle
- if (selection.normalized().contains(Extents()))
+// if (selection.normalized().contains(Extents()))
+ if (selection.contains(Extents()))
state = OSSelected;
else
state = OSInactive;
- return;
+ return false;
}
// The TLC will send these messages if the object is selected but not clicked on.
// So we have to be careful with our assumptions here.
// This is actually untrue in that case, we need to come up with something better
// here...
- objectWasDragged = true;
- needUpdate = false;
+// objectWasDragged = true;
+// needUpdate = false;
+ SaveHitState();
+ bool hovered = HitTest(point);
+ needUpdate = HitStateChanged();
- if (!(hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4))
- return;
+ if (snapToGrid)
+ point = SnapPointToGrid(point);
- Vector delta = point - oldPoint;
+ if (snapPointIsValid)
+ point = snapPoint;
- if (hitHandle1) // Move arc
- {
- position += delta;
- }
- else if (hitHandle2) // Rotate arc
+ objectWasDragged = (draggingCenter | draggingEdge | draggingRotate | draggingSpan);
+
+ if (objectWasDragged)
+ needUpdate = true;
+// if (!(hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4))
+// return;
+
+// Vector delta = point - oldPoint;
+
+ if (draggingCenter)
+ position = point;
+ else if (draggingEdge)
+ radius = Vector::Magnitude(point, position);
+ else if (draggingRotate)
{
startAngle = Vector(point - position).Angle();
}
- else if (hitHandle3) // Set arc span
+ else if (draggingSpan)
{
double angle = Vector(point - position).Angle();
angleSpan = angle - startAngle;
}
- else if (hitHandle4) // Resize the radius of the arc
- {
- radius = Vector(point - position).Magnitude();
- }
+ // Why save this? For rendering code?
oldPoint = point;
- needUpdate = true;
+// needUpdate = true;
+ return hovered;
}
/*virtual*/ void Arc::PointerReleased(void)
{
- hitHandle1 = hitHandle2 = hitHandle3 = hitHandle4 = false;
- // Here we check for just a click: If object was clicked and dragged, then
- // revert to the old state (OSInactive). Otherwise, keep the new state that
- // we set.
-/*
-Maybe it would be better to just check for "object was dragged" state and not
-have to worry about keeping track of old states...
-Well, we can't know if it was dragged from Inactive or not, that's the problem.
-We could make a variable called "needToRevertToInactive" instead
-
-I mean, we could write like:
- if (objectWasDragged && oldState == OSInactive)
- state = OSInactive;
+ // Mouse went up, so our dragging is done (if any *was* done, that is)
+// hitHandle1 = hitHandle2 = hitHandle3 = hitHandle4 = false;
+ draggingCenter = draggingEdge = draggingRotate = draggingSpan = false;
+ hitCenter = hitArc = hitRotate = hitSpan = false;
-but this is actually more compact and cleaner.
-*/
+ // If the object was dragged, then revert to the old state.
+ // Otherwise, we were probably just clicked, and want to stay in the selected state.
if (objectWasDragged)
state = oldState;
}
+/*virtual*/ bool Arc::HitTest(Point point)
+{
+ hitCenter = hitArc = hitRotate = hitSpan = false;
+
+/*
+What we have:
+the center of the arc
+the starting angle
+the span of the arc
+The point on a unit circle given an angle a is x = cos(a), y = sin(a)
+This vector is already unitized, so all we need to do to get our point is to
+multiply it by radius (to get the length correct) and add it to the center
+point (to get the correct position).
+*/
+// Vector v1(point, position); // Head minus tail (vector points at "point")
+ Vector v1(position, point); // Head minus tail (vector points at "point")
+ Point p1(cos(startAngle), sin(startAngle));
+ Point p2(cos(startAngle + angleSpan), sin(startAngle + angleSpan));
+ Vector handle2 = (p1 * radius) + position;
+ Vector handle3 = (p2 * radius) + position;
+ double pointerAngle = v1.Angle();
+ double length = v1.Magnitude();
+
+#if 0
+ if (v1.Magnitude() < 10.0)
+ hitCenter = true;
+ else if (Vector(handle3 - point).Magnitude() < 10.0)
+ hitSpan = true;
+ else if (Vector(handle2 - point).Magnitude() < 10.0)
+ hitRotate = true;
+ else if ((v1.Magnitude() < radius + 3.0) && (v1.Magnitude() > radius - 3.0)
+ && AngleInArcSpan(pointerAngle))
+ hitArc = true;
+#else
+ if ((length * Painter::zoom) < 8.0)
+ {
+ hitCenter = true;
+ snapPoint = position;
+ snapPointIsValid = true;
+ }
+ else if (((fabs(length - radius) * Painter::zoom) < 2.0)
+ && AngleInArcSpan(pointerAngle))
+ hitArc = true;
+ else if ((Vector::Magnitude(handle2, point) * Painter::zoom) < 8.0)
+ {
+ hitRotate = true;
+ snapPoint = handle2;
+ snapPointIsValid = true;
+ }
+ else if ((Vector::Magnitude(handle3, point) * Painter::zoom) < 8.0)
+ {
+ hitSpan = true;
+ snapPoint = handle3;
+ snapPointIsValid = true;
+ }
+#endif
+
+ return (hitCenter || hitArc || hitRotate || hitSpan ? true : false);
+}
+
+
/*virtual*/ QRectF Arc::Extents(void)
{
double start = startAngle;
QPointF p2(cos(end), sin(end));
QRectF bounds(p1, p2);
+#if 0
// Swap X/Y coordinates if they're backwards...
if (bounds.left() > bounds.right())
{
bounds.setBottom(bounds.top());
bounds.setTop(temp);
}
+#else
+ bounds = bounds.normalized();
+#endif
// If the end of the arc is before the beginning, add 360 degrees to it
if (end < start)
}
-#if 0
-/*virtual*/ bool Arc::NeedsUpdate(void)
-{
- return needUpdate;
-}
-#endif
-
-
/*
start = 350, span = 20, end = 10, angle = 5
angle < start, so angle = 365
}
+void Arc::SaveHitState(void)
+{
+ oldHitCenter = hitCenter;
+ oldHitArc = hitArc;
+ oldHitRotate = hitRotate;
+ oldHitSpan = hitSpan;
+}
+
+
+bool Arc::HitStateChanged(void)
+{
+ if ((hitCenter != oldHitCenter)
+ || (hitArc != oldHitArc)
+ || (hitRotate != oldHitRotate)
+ || (hitSpan != oldHitSpan))
+ return true;
+
+ return false;
+}
+
+
/*virtual*/ void Arc::Enumerate(FILE * file)
{
- fprintf(file, "ARC (%lf,%lf) %lf, %lf, %lf\n", position.x, position.y, radius, startAngle, angleSpan);
+ fprintf(file, "ARC %i (%lf,%lf) %lf, %lf, %lf\n", layer, position.x, position.y, radius, startAngle, angleSpan);
+}
+
+
+/*virtual*/ Object * Arc::Copy(void)
+{
+#warning "!!! This doesn't take care of attached Dimensions !!!"
+/*
+This is a real problem. While having a pointer in the Dimension to this line's points
+is fast & easy, it creates a huge problem when trying to replicate an object like this.
+
+Maybe a way to fix that then, is to have reference numbers instead of pointers. That
+way, if you copy them, ... you might still have problems. Because you can't be sure if
+a copy will be persistant or not, you then *definitely* do not want them to have the
+same reference number.
+*/
+ return new Arc(position, radius, startAngle, angleSpan, parent);
+}
+
+
+/*virtual*/ void Arc::Rotate(Point point, double angle)
+{
+ Point c1 = Geometry::RotatePointAroundPoint(position, point, angle);
+ Point ap1(cos(startAngle), sin(startAngle));
+ Point angleStartPoint = (ap1 * radius) + position;
+ Point c2 = Geometry::RotatePointAroundPoint(angleStartPoint, point, angle);
+
+ position = c1;
+ startAngle = Vector(c1, c2).Angle();
+}
+
+
+/*virtual*/ void Arc::Mirror(Point p1, Point p2)
+{
+ Point c1 = Geometry::MirrorPointAroundLine(position, p1, p2);
+ Point ap1(cos(startAngle + angleSpan), sin(startAngle + angleSpan));
+ Point angleEndPoint = (ap1 * radius) + position;
+ Point c2 = Geometry::MirrorPointAroundLine(angleEndPoint, p1, p2);
+
+ position = c1;
+ startAngle = Vector(c2, c1).Angle();
+}
+
+
+/*virtual*/ void Arc::Save(void)
+{
+ Object::Save();
+ oldRadius2 = radius;
+ oldStartAngle = startAngle;
+ oldAngleSpan = angleSpan;
+}
+
+
+/*virtual*/ void Arc::Restore(void)
+{
+ Object::Restore();
+ radius = oldRadius2;
+ startAngle = oldStartAngle;
+ angleSpan = oldAngleSpan;
}