+
+//
+// This looks strange, but it's really quite simple: We want a point that's
+// more than half-way to the next grid point to snap there while conversely we
+// want a point that's less than half-way to to the next grid point then snap
+// to the one before it. So we add half of the grid spacing to the point, then
+// divide by it so that we can remove the fractional part, then multiply it
+// back to get back to the correct answer.
+//
+Point DrawingView::SnapPointToGrid(Point point)
+{
+ point += Global::gridSpacing / 2.0; // *This* adds to Z!!!
+ point /= Global::gridSpacing;
+ point.x = floor(point.x);//need to fix this for negative numbers...
+ point.y = floor(point.y);
+ point.z = 0; // Make *sure* Z doesn't go anywhere!!!
+ point *= Global::gridSpacing;
+ return point;
+}
+
+
+void DrawingView::CheckObjectBounds(void)
+{
+ std::vector<void *>::iterator i;
+ numSelected = 0;
+
+ for(i=document.objects.begin(); i!=document.objects.end(); i++)
+ {
+ Object * obj = (Object *)(*i);
+ obj->selected = false;
+
+ switch (obj->type)
+ {
+ case OTLine:
+ {
+ Line * l = (Line *)obj;
+
+ if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
+ l->selected = true;
+
+ break;
+ }
+ case OTCircle:
+ {
+ Circle * c = (Circle *)obj;
+
+ if (Global::selection.contains(c->p[0].x - c->radius[0], c->p[0].y - c->radius[0]) && Global::selection.contains(c->p[0].x + c->radius[0], c->p[0].y + c->radius[0]))
+ c->selected = true;
+
+ break;
+ }
+ case OTArc:
+ {
+ Arc * a = (Arc *)obj;
+
+ double start = a->angle[0];
+ double end = start + a->angle[1];
+ QPointF p1(cos(start), sin(start));
+ QPointF p2(cos(end), sin(end));
+ QRectF bounds(p1, p2);
+
+#if 1
+ // 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);
+ }
+#else
+ // Doesn't work as advertised! For shame!
+ bounds = bounds.normalized();
+#endif
+
+ // 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() * a->radius[0], bounds.top() * a->radius[0]));
+ bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
+ bounds.translate(a->p[0].x, a->p[0].y);
+
+ if (Global::selection.contains(bounds))
+ a->selected = true;
+
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (obj->selected)
+ numSelected++;
+ }
+}
+
+
+bool DrawingView::HitTestObjects(Point point)
+{
+ std::vector<void *>::iterator i;
+ numHovered = 0;
+ bool needUpdate = false;
+
+ for(i=document.objects.begin(); i!=document.objects.end(); i++)
+ {
+ Object * obj = (Object *)(*i);
+
+ switch (obj->type)
+ {
+ case OTLine:
+ {
+ bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
+ obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
+ Vector lineSegment = obj->p[1] - obj->p[0];
+ Vector v1 = point - obj->p[0];
+ Vector v2 = point - obj->p[1];
+ double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
+ double distance;
+
+ if (t < 0.0)
+ distance = v1.Magnitude();
+ else if (t > 1.0)
+ distance = v2.Magnitude();
+ else
+ // distance = ?Det?(ls, v1) / |ls|
+ distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
+ / lineSegment.Magnitude());
+
+ if ((v1.Magnitude() * Global::zoom) < 8.0)
+ obj->hitPoint[0] = true;
+ else if ((v2.Magnitude() * Global::zoom) < 8.0)
+ obj->hitPoint[1] = true;
+ else if ((distance * Global::zoom) < 5.0)
+ obj->hitObject = true;
+
+ obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
+
+ if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
+ needUpdate = true;
+
+ break;
+ }
+ case OTCircle:
+ {
+ bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
+ obj->hitPoint[0] = obj->hitObject = false;
+ double length = Vector::Magnitude(obj->p[0], point);
+
+ if ((length * Global::zoom) < 8.0)
+ obj->hitPoint[0] = true;
+ else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
+ obj->hitObject = true;
+
+ obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
+
+ if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
+ needUpdate = true;
+
+ break;
+ }
+ case OTArc:
+ {
+ bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
+ obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
+ double length = Vector::Magnitude(obj->p[0], point);
+ double angle = Vector::Angle(obj->p[0], point);
+
+ // Make sure we get the angle in the correct spot
+ if (angle < obj->angle[0])
+ angle += PI_TIMES_2;
+
+ // Get the span that we're pointing at...
+ double span = angle - obj->angle[0];
+
+ // N.B.: Still need to hit test the arc start & arc span handles...
+ double spanAngle = obj->angle[0] + obj->angle[1];
+ Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
+ Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
+ double length2 = Vector::Magnitude(point, handle1);
+ double length3 = Vector::Magnitude(point, handle2);
+
+ if ((length * Global::zoom) < 8.0)
+ obj->hitPoint[0] = true;
+ else if ((length2 * Global::zoom) < 8.0)
+ obj->hitPoint[1] = true;
+ else if ((length3 * Global::zoom) < 8.0)
+ obj->hitPoint[2] = true;
+ else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
+ obj->hitObject = true;
+
+ obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
+
+ if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
+ needUpdate = true;
+
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (obj->hovered)
+// {
+ numHovered++;
+//printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
+// }
+ }
+
+ return needUpdate;
+}
+
+
+void DrawingView::HandleObjectMovement(Point point)
+{
+ Point delta = point - oldPoint;
+//printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
+ Object * obj = (Object *)hover[0];
+//printf("Object type = %i (size=%i), ", obj->type, hover.size());
+//printf("Object (%X) move: hp1=%s, hp2=%s, hl=%s\n", obj, (obj->hitPoint[0] ? "true" : "false"), (obj->hitPoint[1] ? "true" : "false"), (obj->hitObject ? "true" : "false"));
+
+ switch (obj->type)
+ {
+ case OTLine:
+ if (obj->hitPoint[0])
+ obj->p[0] = point;
+ else if (obj->hitPoint[1])
+ obj->p[1] = point;
+ else if (obj->hitObject)
+ {
+ obj->p[0] += delta;
+ obj->p[1] += delta;
+ }
+
+ break;
+ case OTCircle:
+ if (obj->hitPoint[0])
+ obj->p[0] = point;
+ else if (obj->hitObject)
+ {
+//this doesn't work. we need to save this on mouse down for this to work correctly!
+// double oldRadius = obj->radius[0];
+ obj->radius[0] = Vector::Magnitude(obj->p[0], point);
+
+ QString text = QObject::tr("Radius: %1");//\nScale: %2%");
+ informativeText = text.arg(obj->radius[0], 0, 'd', 4);//.arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0);
+ }
+
+ break;
+ case OTArc:
+ if (obj->hitPoint[0])
+ obj->p[0] = point;
+ else if (obj->hitPoint[1])
+ {
+ // Change the Arc's span (handle #1)
+ if (shiftDown)
+ {
+ double angle = Vector::Angle(obj->p[0], point);
+ double delta = angle - obj->angle[0];
+
+ if (delta < 0)
+ delta += PI_TIMES_2;
+
+ obj->angle[1] -= delta;
+ obj->angle[0] = angle;
+
+ if (obj->angle[1] < 0)
+ obj->angle[1] += PI_TIMES_2;
+
+ QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
+ informativeText = text.arg(obj->angle[1] * RADIANS_TO_DEGREES, 0, 'd', 4).arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 2).arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 2);
+ return;
+ }
+
+ double angle = Vector::Angle(obj->p[0], point);
+ obj->angle[0] = angle;
+ QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
+ informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 4);
+ }
+ else if (obj->hitPoint[2])
+ {
+ // Change the Arc's span (handle #2)
+ if (shiftDown)
+ {
+ double angle = Vector::Angle(obj->p[0], point);
+ obj->angle[1] = angle - obj->angle[0];
+
+ if (obj->angle[1] < 0)
+ obj->angle[1] += PI_TIMES_2;
+
+ QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
+ informativeText = text.arg(obj->angle[1] * RADIANS_TO_DEGREES, 0, 'd', 4).arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 2).arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 2);
+ return;
+ }
+
+ double angle = Vector::Angle(obj->p[0], point);
+ obj->angle[0] = angle - obj->angle[1];
+
+ if (obj->angle[0] < 0)
+ obj->angle[0] += PI_TIMES_2;
+
+ QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
+ informativeText = text.arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 4);
+ }
+ else if (obj->hitObject)
+ {
+ if (shiftDown)
+ {
+ return;
+ }
+
+ obj->radius[0] = Vector::Magnitude(obj->p[0], point);
+ QString text = QObject::tr("Radius: %1");
+ informativeText = text.arg(obj->radius[0], 0, 'd', 4);
+ }
+
+ break;
+ default:
+ break;
+ }
+}
+
+
+
+#if 0
+ // This returns true if we've moved over an object...
+ if (document.PointerMoved(point)) // <-- This
+ // This is where the object would do automagic dragging & shit. Since we don't
+ // do that anymore, we need a strategy to handle it.
+ {
+
+/*
+Now objects handle mouse move snapping as well. The code below mainly works only
+for tools; we need to fix it so that objects work as well...
+
+There's a problem with the object point snapping in that it's dependent on the
+order of the objects in the document. Most likely this is because it counts the
+selected object last and thus fucks up the algorithm. Need to fix this...
+
+
+*/
+ // Do object snapping here. Grid snapping on mouse down is done in the
+ // objects themselves, only because we have to hit test the raw point,
+ // not the snapped point. There has to be a better way...!
+ if (document.penultimateObjectHovered)
+ {
+ // Two objects are hovered, see if we have an intersection point
+ if ((document.lastObjectHovered->type == OTLine) && (document.penultimateObjectHovered->type == OTLine))
+ {
+ double t;
+ int n = Geometry::Intersects((Line *)document.lastObjectHovered, (Line *)document.penultimateObjectHovered, &t);
+
+ if (n == 1)
+ {
+ Global::snapPoint = document.lastObjectHovered->GetPointAtParameter(t);
+ Global::snapPointIsValid = true;
+ }
+ }
+ else if ((document.lastObjectHovered->type == OTCircle) && (document.penultimateObjectHovered->type == OTCircle))
+ {
+ Point p1, p2;
+ int n = Geometry::Intersects((Circle *)document.lastObjectHovered, (Circle *)document.penultimateObjectHovered, 0, 0, 0, 0, &p1, &p2);
+
+ if (n == 1)
+ {
+ Global::snapPoint = p1;
+ Global::snapPointIsValid = true;
+ }
+ else if (n == 2)
+ {
+ double d1 = Vector(point, p1).Magnitude();
+ double d2 = Vector(point, p2).Magnitude();
+
+ if (d1 < d2)
+ Global::snapPoint = p1;
+ else
+ Global::snapPoint = p2;
+
+ Global::snapPointIsValid = true;
+ }
+ }
+ }
+// else
+// {
+ // Otherwise, it was a single object hovered...
+// }
+ }
+
+ if (toolAction)
+ {
+ if (Global::snapToGrid)
+ point = Global::SnapPointToGrid(point);
+
+ // We always snap to object points, and they take precendence over
+ // grid points...
+ if (Global::snapPointIsValid)
+ point = Global::snapPoint;
+
+ toolAction->MouseMoved(point);
+ }
+#else
+#endif
+