X-Git-Url: http://shamusworld.gotdns.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Farc.cpp;h=f5078694d9a27a425b93ab98aca044997cf4248d;hb=7f3a6b11585376eecd80979ec3da2346c5314d88;hp=a4efa39de9c03b2f4922907d6737188dda986f04;hpb=eb0057e8a8145032152e4c417fcd102ef5a21484;p=architektonas diff --git a/src/arc.cpp b/src/arc.cpp index a4efa39..f507869 100644 --- a/src/arc.cpp +++ b/src/arc.cpp @@ -10,18 +10,26 @@ // --- ---------- ------------------------------------------------------------ // 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 +#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), - 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; } @@ -33,90 +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); - // Draw the center point of the arc - painter->SetPen(QPen(Qt::red, 2.0, Qt::DotLine)); + if ((state == OSSelected) || ((state == OSInactive) && hitSpan)) + painter->DrawHandle(handle3); + + 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) - { - 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, 260.0, 60.0); // x, y, w, h - painter->DrawRoundedRect(textRect, 7.0, 7.0); + painter->DrawArc(position, radius, startAngle, angleSpan); - textRect.setLeft(textRect.left() + 14); - painter->SetFont(*Object::font); - pen = QPen(QColor(0xDF, 0x5F, 0x00), 1.0, Qt::SolidLine); + 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->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); + 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 + } } @@ -142,44 +152,24 @@ 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(); + // 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: @@ -213,7 +203,7 @@ Selected| | | | so let's do like this: */ - if (hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4) + if (hitSomething) { oldState = state; state = OSSelected; @@ -221,7 +211,6 @@ so let's do like this: oldPoint = position; oldAngle = startAngle; oldRadius = radius; - return true; } @@ -230,29 +219,56 @@ 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.contains(Extents())) + state = OSSelected; + else + state = OSInactive; + + 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(); @@ -261,43 +277,143 @@ 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 - -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") + 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 -/*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; + 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) { - 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 /* @@ -321,8 +437,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(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; }