// (C) 2011 Underground Software
// See the README and GPLv3 files for licensing and warranty information
//
-// JLH = James L. Hammons <jlhamm@acm.org>
+// JLH = James Hammons <jlhamm@acm.org>
//
// WHO WHEN WHAT
// --- ---------- ------------------------------------------------------------
// 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 "painter.h"
-Arc::Arc(Vector p1, double r, double a1, double a2, Object * p/*= NULL*/): Object(p1, p),
- radius(r), startAngle(a1), angleSpan(a2)
+Arc::Arc(Vector p1, double r, double a1, double a2, Object * p/*= NULL*/):
+ 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;
}
+
Arc::~Arc()
{
}
+
/*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);
- // Draw the center point of the arc
- painter->SetPen(QPen(Qt::red, 2.0, Qt::DotLine));
- painter->DrawEllipse(position, 4.0, 4.0);
+ if ((state == OSSelected) || ((state == OSInactive) && hitSpan))
+ painter->DrawHandle(handle3);
- // Draw the rotation & span setting handles
- painter->DrawEllipse(handle2, 4.0, 4.0);
- painter->DrawEllipse(handle3, 4.0, 4.0);
+ if ((state == OSSelected) || ((state == OSInactive) && hitCenter))
+ painter->DrawHandle(position);
- // 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)
- {
- double absAngle = (Vector(oldPoint - position).Angle()) * RADIANS_TO_DEGREES;
- double relAngle = (startAngle >= oldAngle ? startAngle - oldAngle :
- startAngle - oldAngle + (2.0 * PI)) * RADIANS_TO_DEGREES;
-
-// painter->save();
-//close, but no cigar. we need to "invert" our transformation to make this work properly
-// return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
-// painter->translate(0, viewportHeight);
-// painter->scale(1.0, -1.0);
-// Give up for now; just paint the info panel in the upper left corner of the screen
-// painter->resetTransform();
- 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, 220.0, 60.0); // x, y, w, h
- painter->DrawRoundedRect(textRect, 7.0, 7.0);
+ if ((state == OSInactive) && !hitArc)
+ painter->SetPen(QPen(Qt::black, 1.0, Qt::SolidLine));
- textRect.setLeft(textRect.left() + 14);
- painter->SetFont(*Object::font);
- pen = QPen(QColor(0xDF, 0x5F, 0x00), 1.0, Qt::SolidLine);
+ painter->DrawArc(position, radius, startAngle, angleSpan);
+
+ if (draggingRotate || draggingSpan)
+ {
+ if (draggingRotate)
+ {
+ // 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->Restore();
+// painter->DrawLine((int)position.x, (int)position.y, (int)oldLine.x, (int)oldLine.y);
+ painter->DrawLine(position, oldLine);
}
-// painter->setPen(QPen(Qt::red, 2.0, Qt::DotLine));
- }
- else
- {
- pen = QPen(Qt::black, 1.0, Qt::SolidLine);
+ // 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 we're rotating or setting the span, draw an information panel
+ // showing both absolute and relative angles being set.
+ if (draggingRotate || draggingSpan || draggingEdge)
+ {
+ 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
- QRectF rectangle(QPointF(position.x - radius, position.y - radius),
- QPointF(position.x + radius, position.y + radius));
- int angle1 = (int)(startAngle * RADIANS_TO_DEGREES * 16.0);
- int angle2 = (int)(angleSpan * RADIANS_TO_DEGREES * 16.0);
- painter->DrawArc(rectangle, -angle1, -angle2);
+ 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(0x00, 0x5F, 0xDF));
+ painter->SetPen(pen);
+ painter->DrawText(textRect, Qt::AlignVCenter, text);
#else
- painter->DrawArc(position, radius, startAngle, angleSpan);
+ painter->DrawInformativeText(text);
#endif
+ }
}
+
/*virtual*/ Vector Arc::Center(void)
{
return position;
}
+
/*
We need at least *four* handles for this object:
- one for moving
/*virtual*/ bool Arc::Collided(Vector point)
{
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();
-
-#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
+ // Now that we've done our hit testing on the non-snapped point, snap it if
+ // necessary...
+ if (snapToGrid)
+ point = SnapPointToGrid(point);
/*
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;
}
return false;
}
+
/*virtual*/ void 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.contains(Extents()))
+ state = OSSelected;
+ else
+ state = OSInactive;
+
+ return;
+ }
+
// 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();
+ HitTest(point);
+ needUpdate = HitStateChanged();
+ objectWasDragged = (draggingCenter | draggingEdge | draggingRotate | draggingSpan);
- if (!(hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4))
- return;
+ if (objectWasDragged)
+ needUpdate = true;
+// if (!(hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4))
+// return;
- Vector delta = point - oldPoint;
+// Vector delta = point - oldPoint;
- if (hitHandle1) // Move arc
- {
- position += delta;
- }
- else if (hitHandle2) // Rotate arc
+ 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;
}
+
/*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;
-
-but this is actually more compact and cleaner.
-*/
+ // 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;
+
+ // 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")
+ 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
-/*virtual*/ bool Arc::NeedsUpdate(void)
+ 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;
+ 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;
+ else if ((Vector::Magnitude(handle3, point) * Painter::zoom) < 8.0)
+ hitSpan = true;
+#endif
+
+ return (hitCenter || hitArc || hitRotate || hitSpan ? true : false);
+}
+
+
+/*virtual*/ QRectF Arc::Extents(void)
{
- return needUpdate;
+ double start = startAngle;
+ double end = start + angleSpan;
+ QPointF p1(cos(start), sin(start));
+ QPointF p2(cos(end), sin(end));
+ QRectF bounds(p1, p2);
+
+ // Swap X/Y coordinates if they're backwards...
+ if (bounds.left() > bounds.right())
+ {
+ double temp = bounds.left();
+ bounds.setLeft(bounds.right());
+ bounds.setRight(temp);
+ }
+
+ if (bounds.bottom() > bounds.top())
+ {
+ double temp = bounds.bottom();
+ bounds.setBottom(bounds.top());
+ bounds.setTop(temp);
+ }
+
+ // If the end of the arc is before the beginning, add 360 degrees to it
+ if (end < start)
+ end += 2.0 * PI;
+
+ // Adjust the bounds depending on which axes are crossed
+ if ((start < PI_OVER_2) && (end > PI_OVER_2))
+ bounds.setTop(1.0);
+
+ if ((start < PI) && (end > PI))
+ bounds.setLeft(-1.0);
+
+ if ((start < (PI + PI_OVER_2)) && (end > (PI + PI_OVER_2)))
+ bounds.setBottom(-1.0);
+
+ if ((start < (2.0 * PI)) && (end > (2.0 * PI)))
+ bounds.setRight(1.0);
+
+ if ((start < ((2.0 * PI) + PI_OVER_2)) && (end > ((2.0 * PI) + PI_OVER_2)))
+ bounds.setTop(1.0);
+
+ if ((start < (3.0 * PI)) && (end > (3.0 * PI)))
+ bounds.setLeft(-1.0);
+
+ if ((start < ((3.0 * PI) + PI_OVER_2)) && (end > ((3.0 * PI) + PI_OVER_2)))
+ bounds.setBottom(-1.0);
+
+ bounds.setTopLeft(QPointF(bounds.left() * radius, bounds.top() * radius));
+ bounds.setBottomRight(QPointF(bounds.right() * radius, bounds.bottom() * radius));
+ bounds.translate(position.x, position.y);
+ return bounds;
}
-#endif
+
/*
start = 350, span = 20, end = 10, angle = 5
return (passedInSpan <= angleSpan ? true : false);
}
+
+
+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);
+}
+
+
+/*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);
+}
+