]> Shamusworld >> Repos - architektonas/blobdiff - src/arc.cpp
Trim tool now works for Lines, but inaccurate.
[architektonas] / src / arc.cpp
index 355d7a76f2b5b425ddc5b882f7e21e86a22d8dc4..6aefc1c52bc663d1070e8b8e252f175962266737 100644 (file)
 // ---  ----------  ------------------------------------------------------------
 // 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;
 }
 
 
@@ -35,93 +41,92 @@ 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);
+
+       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));
+
+       painter->DrawArc(position, radius, startAngle, angleSpan);
 
-               // 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 || 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);
+                       // 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->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));
-                       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
+       }
 }
 
 
@@ -147,44 +152,21 @@ Also: should put the snap logic into the Object base class (as a static method).
 
 /*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();
-
-#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:
@@ -218,7 +200,7 @@ Selected|  |  |  |
 
 so let's do like this:
 */
-       if (hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4)
+       if (hitSomething)
        {
                oldState = state;
                state = OSSelected;
@@ -226,7 +208,6 @@ so let's do like this:
                oldPoint = position;
                oldAngle = startAngle;
                oldRadius = radius;
-
                return true;
        }
 
@@ -235,40 +216,49 @@ so let's do like this:
 }
 
 
-/*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();
+       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();
 
@@ -277,39 +267,77 @@ 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;
+       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
+       // 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;
 
-I mean, we could write like:
-       if (objectWasDragged && oldState == OSInactive)
-               state = OSInactive;
-
-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;
+       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)
 {
        double start = startAngle;
@@ -366,14 +394,6 @@ but this is actually more compact and cleaner.
 }
 
 
-#if 0
-/*virtual*/ bool Arc::NeedsUpdate(void)
-{
-       return needUpdate;
-}
-#endif
-
-
 /*
 start = 350, span = 20, end = 10, angle = 5
 angle < start, so angle = 365
@@ -395,8 +415,87 @@ bool Arc::AngleInArcSpan(double angle)
 }
 
 
+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(c2, c1).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;
 }