]> Shamusworld >> Repos - architektonas/blobdiff - src/arc.cpp
Fixed Arc to give continuous feedback like Line and Circle do.
[architektonas] / src / arc.cpp
index adccd21507f23c816110b031c45aa3450a0d9c2c..ad08a572979dc651c6a13592a8126a3001b06d9b 100644 (file)
 // (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 <QtGui>
 #include "mathconstants.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(QPainter * painter)
+
+/*virtual*/ void Arc::Draw(Painter * painter)
 {
-       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;
+       QPen pen;
+       painter->SetPen(QPen(Qt::red, 2.0, Qt::DotLine));
+
+       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 ((state == OSSelected) || ((state == OSInactive) && hitRotate))
+               painter->DrawHandle(handle2);
+
+       if ((state == OSSelected) || ((state == OSInactive) && hitSpan))
+               painter->DrawHandle(handle3);
+
+       if ((state == OSSelected) || ((state == OSInactive) && hitCenter))
+               painter->DrawHandle(position);
+
+       if ((state == OSInactive) && !hitArc)
+               painter->SetPen(QPen(Qt::black, 1.0, Qt::SolidLine));
+
+       painter->DrawArc(position, radius, startAngle, angleSpan);
 
-               if ((hitHandle2 || hitHandle3) && objectWasDragged)
+       if (draggingRotate || draggingSpan)
+       {
+               if (draggingRotate)
                {
-                       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;
-                               painter->setPen(QPen(QColor(0x80, 0x80, 0x80), 1.0, Qt::DashLine));
-                               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.
-                       painter->setPen(QPen(QColor(0x00, 0xC0, 0x80), 1.0, Qt::DashLine));
-                       painter->drawLine((int)position.x, (int)position.y, (int)oldPoint.x, (int)oldPoint.y);
+                       // 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);
                }
 
-               // Draw the center point of the arc
-               painter->setPen(QPen(Qt::red, 2.0, Qt::DotLine));
-               painter->drawEllipse(QPointF(position.x, position.y), 4.0, 4.0);
+               // 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;
 
-               // Draw the rotation & span setting handles
-               painter->drawEllipse(QPointF(handle2.x, handle2.y), 4.0, 4.0);
-               painter->drawEllipse(QPointF(handle3.x, handle3.y), 4.0, 4.0);
+               QString text;
 
-               // 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)
+               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)
                {
-                       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);
-                       }
-
-                       painter->setPen(QPen(QColor(0x00, 0xFF, 0x00), 1.0, Qt::SolidLine));
-                       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);
-
-                       textRect.setLeft(textRect.left() + 14);
-                       painter->setFont(*Object::font);
-                       painter->setPen(QPen(QColor(0xDF, 0x5F, 0x00), 1.0, Qt::SolidLine));
-                       painter->drawText(textRect, Qt::AlignVCenter, text);
-                       painter->restore();
+                       text = QObject::tr("Radius: %1\nScale: %2%");
+                       text = text.arg(radius, 0, 'd', 4).arg(radius / oldRadius * 100.0, 0, 'd', 0);
                }
 
-//             painter->setPen(QPen(Qt::red, 2.0, Qt::DotLine));
+               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->setPen(QPen(Qt::black, 1.0, Qt::SolidLine));
-
-       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);
 }
 
+
 /*virtual*/ Vector Arc::Center(void)
 {
        return position;
 }
 
+
 /*
  We need at least *four* handles for this object:
  - one for moving
@@ -143,8 +147,15 @@ Also: should put the snap logic into the Object base class (as a static method).
 /*virtual*/ bool Arc::Collided(Vector point)
 {
        objectWasDragged = false;
-       Vector v1 = point - position;                   // Head minus tail (vector points at "point")
+//     Vector v1 = point - position;                   // Head minus tail (vector points at "point")
 
+#if 1
+       bool hitSomething = HitTest(point);
+       draggingCenter = hitCenter;
+       draggingEdge   = hitArc;
+       draggingRotate = hitRotate;
+       draggingSpan   = hitSpan;
+#else
        // Check for collision with various things...
        hitHandle1 = false;     // Moving
        hitHandle2 = false;     // Rotation
@@ -159,6 +170,7 @@ 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;
@@ -180,6 +192,7 @@ radius (to get the length correct) and add it to the center point (to get the co
                && AngleInArcSpan(pointerAngle))
                hitHandle4 = true;
 #endif
+#endif
 
 /*
 State Management:
@@ -213,7 +226,8 @@ Selected|  |  |  |
 
 so let's do like this:
 */
-       if (hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4)
+//     if (hitCenter || hitArc || hitRotate || hitSpan)
+       if (hitSomething)
        {
                oldState = state;
                state = OSSelected;
@@ -221,7 +235,6 @@ so let's do like this:
                oldPoint = position;
                oldAngle = startAngle;
                oldRadius = radius;
-
                return true;
        }
 
@@ -229,29 +242,50 @@ so let's do like this:
        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();
 
@@ -260,42 +294,130 @@ so let's do like this:
 
                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
@@ -316,3 +438,47 @@ bool Arc::AngleInArcSpan(double angle)
 
        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);
+}
+