3 // Part of the Architektonas Project
4 // (C) 2011 Underground Software
5 // See the README and GPLv3 files for licensing and warranty information
7 // JLH = James Hammons <jlhamm@acm.org>
10 // --- ---------- ------------------------------------------------------------
11 // JLH 03/22/2011 Created this file
12 // JLH 09/29/2011 Added middle mouse button panning
17 // - Redo rendering code to *not* use Qt's transform functions, as they are tied
18 // to a left-handed system and we need a right-handed one. [DONE]
23 // - Layer locking (hiding works)
26 // Uncomment this for debugging...
28 //#define DEBUGFOO // Various tool debugging...
29 //#define DEBUGTP // Toolpalette debugging...
31 #include "drawingview.h"
36 #include "mathconstants.h"
42 #define BACKGROUND_MAX_SIZE 512
45 DrawingView::DrawingView(QWidget * parent/*= NULL*/): QWidget(parent),
46 // The value in the settings file will override this.
47 useAntialiasing(true), /*numSelected(0),*/ numHovered(0), shiftDown(false),
49 gridBackground(BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE),
50 scale(1.0), offsetX(-10), offsetY(-10), document(true),
51 gridPixels(0), collided(false), hoveringIntersection(false)
53 //wtf? doesn't work except in c++11??? document = { 0 };
54 setBackgroundRole(QPalette::Base);
55 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
57 Global::gridSpacing = 12.0; // In base units (inch is default)
60 Line * line = new Line(Vector(5, 5), Vector(50, 40), &document);
62 document.Add(new Line(Vector(50, 40), Vector(10, 83), &document));
63 document.Add(new Line(Vector(10, 83), Vector(17, 2), &document));
64 document.Add(new Circle(Vector(100, 100), 36, &document));
65 document.Add(new Circle(Vector(50, 150), 49, &document));
66 document.Add(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3, &document)),
67 document.Add(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5, &document));
69 Dimension * dimension = new Dimension(Vector(0, 0), Vector(0, 0), DTLinear, &document);
70 line->SetDimensionOnLine(dimension);
71 document.Add(dimension);
73 // Alternate way to do the above...
74 line->SetDimensionOnLine();
77 Line * line = new Line;//(Vector(5, 5), Vector(50, 40), &document);
78 line->p[0] = Vector(5, 5);
79 line->p[1] = Vector(50, 40);
81 line->thickness = 2.0;
83 line->color = 0xFF7F00;
85 document.objects.push_back(line);
86 document.objects.push_back(new Line(Vector(50, 40), Vector(10, 83)));
87 document.objects.push_back(new Line(Vector(10, 83), Vector(17, 2)));
88 document.objects.push_back(new Circle(Vector(100, 100), 36));
89 document.objects.push_back(new Circle(Vector(50, 150), 49));
90 document.objects.push_back(new Arc(Vector(300, 300), 32, TAU / 8.0, TAU * 0.65)),
91 document.objects.push_back(new Arc(Vector(200, 200), 60, TAU / 4.0, TAU * 0.75));
92 document.objects.push_back(new Dimension(Vector(50, 40), Vector(5, 5)));
93 document.objects.push_back(new Text(Vector(10, 83), "Here is some awesome text!"));
97 Here we set the grid size in pixels--12 in this case. Initially, we have our
98 zoom set to make this represent 12 inches at a zoom factor of 25%. (This is
99 arbitrary.) So, to be able to decouple the grid size from the zoom, we need
100 to be able to set the size of the background grid (which we do here at an
101 arbitrary 12 pixels) to anything we want (within reason, of course :-).
103 The drawing enforces the grid spacing through the drawing->gridSpacing variable.
105 drawing->gridSpacing = 12.0 / Global::zoom;
107 Global::zoom is the zoom factor for the drawing, and all mouse clicks are
108 translated to Cartesian coordinates through this. (Initially, Global::zoom is
109 set to 1.0. SCREEN_ZOOM is set to 1.0/4.0.)
111 Really, the 100% zoom level can be set at *any* zoom level, it's more of a
112 convenience function than any measure of absolutes. Doing things that way we
113 could rid ourselves of the whole SCREEN_ZOOM parameter and all the attendant
114 shittiness that comes with it.
116 However, it seems that SCREEN_ZOOM is used to make text and arrow sizes show up
117 a certain way, which means we should probably create something else in those
118 objects to take its place--like some kind of scale factor. This would seem to
119 imply that certain point sizes actually *do* tie things like fonts to absolute
120 sizes on the screen, but not necessarily because you could have an inch scale
121 with text that is quite small relative to other objects on the screen, which
122 currently you have to zoom in to see (and which blows up the text). Point sizes
123 in an application like this are a bit meaningless; even though an inch is an
124 inch regardless of the zoom level a piece of text can be larger or smaller than
125 this. Maybe this is the case for having a base unit and basing point sizes off
128 Here's what's been figured out. Global::zoom is simply the ratio of pixels to
129 base units. What that means is that if you have a 12px grid with a 6" grid size
130 (& base unit of "inches"), Global::zoom becomes 12px / 6" = 2.0 px/in.
132 Dimensions now have a "size" parameter to set their absolute size in relation
133 to the base unit. ATM, the arrows are drawn in pixels, but also scaled by
134 Global::zoom *and* size. Same with the dimension text; it's drawn at 10pt and
135 scaled the same way as the arrowheads.
137 Need a way to scale line widths as well. :-/ Shouldn't be too difficult, just
138 need a thickness parameter similar to the "size" param for dimensions. (And now
142 SetGridSize(12); // This is in pixels
146 void DrawingView::SetGridSize(uint32_t size)
149 if (size == gridPixels)
152 // Recreate the background bitmap
154 QPainter pmp(&gridBackground);
155 pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
156 pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
158 for(int i=0; i<(BACKGROUND_MAX_SIZE-1); i+=gridPixels)
160 pmp.drawLine(i, 0, i, BACKGROUND_MAX_SIZE - 1);
161 pmp.drawLine(0, i, BACKGROUND_MAX_SIZE - 1, i);
166 // Set up new BG brush & zoom level (pixels per base unit)
167 Global::zoom = gridPixels / Global::gridSpacing;
168 UpdateGridBackground();
172 void DrawingView::UpdateGridBackground(void)
174 // Transform the origin to Qt coordinates
175 Vector pixmapOrigin = Painter::CartesianToQtCoords(Vector());
176 int x = (int)pixmapOrigin.x;
177 int y = (int)pixmapOrigin.y;
178 // Use mod arithmetic to grab the correct swatch of background
180 Negative numbers still screw it up... Need to think about what we're
181 trying to do here. The fact that it worked with 72 seems to have been pure luck.
182 It seems the problem is negative numbers: We can't let that happen.
183 When taking away the zero, it pops over 1 px at zero, then goes about 1/2 a
186 The bitmap looks like this:
196 @ x = 1, we want it to look like:
198 -+---+---+---+---+---
201 -+---+---+---+---+---
206 Which means we need to grab the sample from x = 3. @ x = -1:
216 Which means we need to grab the sample from x = 1. Which means we have to take
217 the mirror of the modulus of gridPixels.
219 Doing a mod of a negative number is problematic: 1st, the compiler converts the
220 negative number to an unsigned int, then it does the mod. Gets you wrong answers
221 most of the time, unless you use a power of 2. :-P So what we do here is just
222 take the modulus of the negation, which means we don't have to worry about
225 The positive case looks gruesome (and it is) but it boils down to this: We take
226 the modulus of the X coordinate, then mirror it by subtraction from the
227 maximum (in this case, gridPixels). This gives us a number in the range of 1 to
228 gridPixels. But we need the case where the result equalling gridPixels to be
229 zero; so we do another modulus operation on the result to achieve this.
234 x = (gridPixels - (x % gridPixels)) % gridPixels;
239 y = (gridPixels - (y % gridPixels)) % gridPixels;
241 // Here we grab a section of the bigger pixmap, so that the background
242 // *looks* like it's scrolling...
243 QPixmap pm = gridBackground.copy(x, y, gridPixels, gridPixels);
244 QPalette pal = palette();
245 pal.setBrush(backgroundRole(), QBrush(pm));
246 setAutoFillBackground(true);
252 // Basically, we just make a single pass through the Container. If the layer #
253 // is less than the layer # being deleted, then do nothing. If the layer # is
254 // equal to the layer # being deleted, then delete the object. If the layer #
255 // is greater than the layer # being deleted, then set the layer # to its layer
258 void DrawingView::DeleteCurrentLayer(int layer)
260 //printf("DrawingView::DeleteCurrentLayer(): currentLayer = %i\n", layer);
261 std::vector<void *>::iterator i = document.objects.begin();
263 while (i != document.objects.end())
265 Object * obj = (Object *)(*i);
267 if (obj->layer < layer)
269 else if (obj->layer == layer)
271 document.objects.erase(i);
281 // We've just done a destructive action, so update the screen!
286 void DrawingView::HandleLayerToggle(void)
288 // A layer's visibility was toggled, so update the screen...
294 // A layer was moved up or down in the layer list, so we have to swap the
295 // document's object's layer numbers in the layers that were swapped.
297 void DrawingView::HandleLayerSwap(int layer1, int layer2)
299 //printf("DrawingView: Swapping layers %i and %i.\n", layer1, layer2);
300 std::vector<void *>::iterator i;
302 for(i=document.objects.begin(); i!=document.objects.end(); i++)
304 Object * obj = (Object *)(*i);
306 if (obj->layer == layer1)
308 else if (obj->layer == layer2)
314 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
316 // This is undoing the transform, e.g. going from client coords to local
317 // coords. In essence, the height - y is height + (y * -1), the (y * -1)
318 // term doing the conversion of the y-axis from increasing bottom to top.
319 return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
323 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
325 // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
326 // No voodoo here, it's just grouped wrong to see it. It should be:
327 // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive [why? we use -offsetX after all]
328 return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
332 void DrawingView::paintEvent(QPaintEvent * /*event*/)
334 QPainter qtPainter(this);
335 Painter painter(&qtPainter);
338 qtPainter.setRenderHint(QPainter::Antialiasing);
340 Global::viewportHeight = size().height();
342 // Draw coordinate axes
343 painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
344 painter.DrawLine(0, -16384, 0, 16384);
345 painter.DrawLine(-16384, 0, 16384, 0);
347 // Do object rendering...
348 for(int i=0; i<Global::numLayers; i++)
350 if (Global::layerHidden[i] == false)
351 RenderObjects(&painter, document.objects, i);
354 // Do tool rendering, if any...
357 painter.SetPen(QPen(QColor(200, 100, 0, 255), 1.0, Qt::DashLine));
358 painter.DrawCrosshair(oldPoint);
362 // Do selection rectangle rendering, if any
363 if (Global::selectionInProgress)
365 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
366 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
367 painter.DrawRect(Global::selection);
370 if (hoveringIntersection)
371 painter.DrawHandle(intersectionPoint);
373 if (!informativeText.isEmpty())
374 painter.DrawInformativeText(informativeText);
379 // Renders objects in the passed in vector
381 void DrawingView::RenderObjects(Painter * painter, std::vector<void *> & v, int layer, bool ignoreLayer/*= false*/)
383 std::vector<void *>::iterator i;
385 for(i=v.begin(); i!=v.end(); i++)
387 Object * obj = (Object *)(*i);
388 float scaledThickness = Global::scale * obj->thickness;
390 // If the object isn't on the current layer being drawn, skip it
391 if (!ignoreLayer && (obj->layer != layer))
394 if ((Global::tool == TTRotate) && ctrlDown && obj->selected)
396 painter->SetPen(0x00FF00, 2.0, LSSolid);
400 painter->SetPen(obj->color, Global::zoom * scaledThickness, obj->style);
401 painter->SetBrush(obj->color);
403 if (obj->selected || obj->hitObject)
404 painter->SetPen(0xFF0000, Global::zoom * scaledThickness, LSDash);
410 painter->DrawLine(obj->p[0], obj->p[1]);
412 if (obj->hitPoint[0])
413 painter->DrawHandle(obj->p[0]);
415 if (obj->hitPoint[1])
416 painter->DrawHandle(obj->p[1]);
420 painter->SetBrush(QBrush(Qt::NoBrush));
421 painter->DrawEllipse(obj->p[0], obj->radius[0], obj->radius[0]);
423 if (obj->hitPoint[0])
424 painter->DrawHandle(obj->p[0]);
428 painter->DrawArc(obj->p[0], obj->radius[0], obj->angle[0], obj->angle[1]);
430 if (obj->hitPoint[0])
431 painter->DrawHandle(obj->p[0]);
433 if (obj->hitPoint[1])
434 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]));
436 if (obj->hitPoint[2])
437 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0] + obj->angle[1]), sin(obj->angle[0] + obj->angle[1])) * obj->radius[0]));
442 Dimension * d = (Dimension *)obj;
444 Vector v(d->p[0], d->p[1]);
445 double angle = v.Angle();
446 Vector unit = v.Unit();
447 Vector linePt1 = d->p[0], linePt2 = d->p[1];
449 double x1, y1, length;
451 if (d->subtype == DTLinearVert)
453 if ((angle < 0) || (angle > HALF_TAU))
455 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
456 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
457 ortho = Vector(1.0, 0);
458 angle = THREE_QTR_TAU;
462 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
463 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
464 ortho = Vector(-1.0, 0);
468 linePt1.x = linePt2.x = x1;
469 length = fabs(d->p[0].y - d->p[1].y);
471 else if (d->subtype == DTLinearHorz)
473 if ((angle < QTR_TAU) || (angle > THREE_QTR_TAU))
475 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
476 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
477 ortho = Vector(0, 1.0);
482 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
483 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
484 ortho = Vector(0, -1.0);
488 linePt1.y = linePt2.y = y1;
489 length = fabs(d->p[0].x - d->p[1].x);
491 else if (d->subtype == DTLinear)
493 angle = Vector(linePt1, linePt2).Angle();
494 ortho = Vector::Normal(linePt1, linePt2);
495 length = v.Magnitude();
498 unit = Vector(linePt1, linePt2).Unit();
500 Point p1 = linePt1 + (ortho * 10.0 * scaledThickness);
501 Point p2 = linePt2 + (ortho * 10.0 * scaledThickness);
502 Point p3 = linePt1 + (ortho * 16.0 * scaledThickness);
503 Point p4 = linePt2 + (ortho * 16.0 * scaledThickness);
504 Point p5 = d->p[0] + (ortho * 4.0 * scaledThickness);
505 Point p6 = d->p[1] + (ortho * 4.0 * scaledThickness);
508 The numbers hardcoded into here, what are they?
509 I believe they are pixels.
511 // Draw extension lines (if certain type)
512 painter->DrawLine(p3, p5);
513 painter->DrawLine(p4, p6);
515 // Calculate whether or not the arrowheads are too crowded to put
516 // inside the extension lines. 9.0 is the length of the arrowhead.
517 double t = Geometry::ParameterOfLineAndPoint(linePt1, linePt2, linePt2 - (unit * 9.0 * scaledThickness));
519 // On the screen, it's acting like this is actually 58%...
520 // This is correct, we want it to happen at > 50%
523 // Draw main dimension line + arrowheads
524 painter->DrawLine(p1, p2);
525 painter->DrawArrowhead(p1, p2, scaledThickness);
526 painter->DrawArrowhead(p2, p1, scaledThickness);
530 // Draw outside arrowheads
531 Point p7 = p1 - (unit * 9.0 * scaledThickness);
532 Point p8 = p2 + (unit * 9.0 * scaledThickness);
533 painter->DrawArrowhead(p1, p7, scaledThickness);
534 painter->DrawArrowhead(p2, p8, scaledThickness);
535 painter->DrawLine(p1, p1 - (unit * 14.0 * scaledThickness));
536 painter->DrawLine(p2, p2 + (unit * 14.0 * scaledThickness));
539 // Draw length of dimension line...
540 painter->SetFont(QFont("Arial", 8.0 * Global::zoom * scaledThickness));
541 Point ctr = p2 + (Vector(p2, p1) / 2.0);
546 dimText = QString("%1\"").arg(length);
549 double feet = (double)((int)length / 12);
550 double inches = length - (feet * 12.0);
553 dimText = QString("%1'").arg(feet);
555 dimText = QString("%1' %2\"").arg(feet).arg(inches);
558 painter->DrawAngledText(ctr, angle, dimText, scaledThickness);
564 Text * t = (Text *)obj;
565 painter->DrawTextObject(t->p[0], t->s.c_str(), scaledThickness);
578 // Containers require recursive rendering...
579 Container * c = (Container *)obj;
580 RenderObjects(painter, (*c).objects, layer);
582 //printf("Container extents: <%lf, %lf>, <%lf, %lf>\nsize: %i\n", r.l, r.t, r.r, r.b, c->objects.size());
583 // Containers also have special indicators showing they are selected
584 if (c->selected || c->hitObject)
586 Rect r = GetObjectExtents(obj);
587 painter->DrawRectCorners(r);
599 void DrawingView::AddHoveredToSelection(void)
601 std::vector<void *>::iterator i;
603 for(i=document.objects.begin(); i!=document.objects.end(); i++)
605 if (((Object *)(*i))->hovered)
606 ((Object *)(*i))->selected = true;
611 void DrawingView::GetSelection(std::vector<void *> & v)
614 std::vector<void *>::iterator i;
616 for(i=document.objects.begin(); i!=document.objects.end(); i++)
618 if (((Object *)(*i))->selected)
624 void DrawingView::GetHovered(std::vector<void *> & v)
627 std::vector<void *>::iterator i;
629 for(i=document.objects.begin(); i!=document.objects.end(); i++)
631 if (((Object *)(*i))->hovered)
633 //printf("GetHovered: adding object (%X) to hover... hp1=%s, hp2=%s, hl=%s\n", (*i), (((Line *)(*i))->hitPoint[0] ? "true" : "false"), (((Line *)(*i))->hitPoint[1] ? "true" : "false"), (((Line *)(*i))->hitObject ? "true" : "false"));
640 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
642 Global::screenSize = Vector(size().width(), size().height());
643 UpdateGridBackground();
647 void DrawingView::ToolHandler(int mode, Point p)
649 if (Global::tool == TTLine)
650 LineHandler(mode, p);
651 else if (Global::tool == TTCircle)
652 CircleHandler(mode, p);
653 else if (Global::tool == TTArc)
655 else if (Global::tool == TTRotate)
656 RotateHandler(mode, p);
657 else if (Global::tool == TTMirror)
658 MirrorHandler(mode, p);
662 void DrawingView::ToolDraw(Painter * painter)
664 if (Global::tool == TTLine)
666 if (Global::toolState == TSNone)
668 painter->DrawHandle(toolPoint[0]);
670 else if ((Global::toolState == TSPoint2) && shiftDown)
672 painter->DrawHandle(toolPoint[1]);
676 painter->DrawLine(toolPoint[0], toolPoint[1]);
677 painter->DrawHandle(toolPoint[1]);
679 Vector v(toolPoint[0], toolPoint[1]);
680 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
681 double absLength = v.Magnitude();
682 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
683 informativeText = text.arg(absLength).arg(absAngle);
686 else if (Global::tool == TTCircle)
688 if (Global::toolState == TSNone)
690 painter->DrawHandle(toolPoint[0]);
692 else if ((Global::toolState == TSPoint2) && shiftDown)
694 painter->DrawHandle(toolPoint[1]);
698 painter->DrawCross(toolPoint[0]);
699 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
700 painter->SetBrush(QBrush(Qt::NoBrush));
701 painter->DrawEllipse(toolPoint[0], length, length);
702 QString text = tr("Radius: %1 in.");
703 informativeText = text.arg(length);
706 else if (Global::tool == TTArc)
708 if (Global::toolState == TSNone)
710 painter->DrawHandle(toolPoint[0]);
712 else if (Global::toolState == TSPoint2)
714 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
715 painter->SetBrush(QBrush(Qt::NoBrush));
716 painter->DrawEllipse(toolPoint[0], length, length);
717 painter->DrawLine(toolPoint[0], toolPoint[1]);
718 painter->DrawHandle(toolPoint[1]);
719 QString text = tr("Radius: %1 in.");
720 informativeText = text.arg(length);
722 else if (Global::toolState == TSPoint3)
724 double angle = Vector::Angle(toolPoint[0], toolPoint[2]);
725 painter->DrawLine(toolPoint[0], toolPoint[2]);
726 painter->SetBrush(QBrush(Qt::NoBrush));
727 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
728 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
729 QString text = tr("Angle start: %1") + QChar(0x00B0);
730 informativeText = text.arg(RADIANS_TO_DEGREES * angle);
734 double angle = Vector::Angle(toolPoint[0], toolPoint[3]);
735 double span = angle - toolPoint[2].x;
740 painter->DrawLine(toolPoint[0], toolPoint[3]);
741 painter->SetBrush(QBrush(Qt::NoBrush));
742 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
743 painter->SetPen(0xFF00FF, 2.0, LSSolid);
744 painter->DrawArc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
745 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
746 QString text = tr("Arc span: %1") + QChar(0x00B0);
747 informativeText = text.arg(RADIANS_TO_DEGREES * span);
750 else if (Global::tool == TTRotate)
752 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
753 painter->DrawHandle(toolPoint[0]);
754 else if ((Global::toolState == TSPoint2) && shiftDown)
755 painter->DrawHandle(toolPoint[1]);
758 if (toolPoint[0] == toolPoint[1])
761 painter->DrawLine(toolPoint[0], toolPoint[1]);
763 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
764 QString text = QChar(0x2221) + QObject::tr(": %1");
765 informativeText = text.arg(absAngle);
768 informativeText += " (Copy)";
771 else if (Global::tool == TTMirror)
773 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
774 painter->DrawHandle(toolPoint[0]);
775 else if ((Global::toolState == TSPoint2) && shiftDown)
776 painter->DrawHandle(toolPoint[1]);
779 if (toolPoint[0] == toolPoint[1])
782 Point mirrorPoint = toolPoint[0] + Vector(toolPoint[1], toolPoint[0]);
783 painter->DrawLine(mirrorPoint, toolPoint[1]);
785 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
787 if (absAngle > 180.0)
790 QString text = QChar(0x2221) + QObject::tr(": %1");
791 informativeText = text.arg(absAngle);
794 informativeText += " (Copy)";
800 void DrawingView::LineHandler(int mode, Point p)
805 if (Global::toolState == TSNone)
812 if (Global::toolState == TSNone)
819 if (Global::toolState == TSNone)
821 Global::toolState = TSPoint2;
822 // Prevent spurious line from drawing...
823 toolPoint[1] = toolPoint[0];
825 else if ((Global::toolState == TSPoint2) && shiftDown)
827 // Key override is telling us to make a new line, not continue the
829 toolPoint[0] = toolPoint[1];
833 Line * l = new Line(toolPoint[0], toolPoint[1]);
834 l->layer = Global::activeLayer;
835 document.objects.push_back(l);
836 toolPoint[0] = toolPoint[1];
842 void DrawingView::CircleHandler(int mode, Point p)
847 if (Global::toolState == TSNone)
854 if (Global::toolState == TSNone)
861 if (Global::toolState == TSNone)
863 Global::toolState = TSPoint2;
864 // Prevent spurious line from drawing...
865 toolPoint[1] = toolPoint[0];
867 else if ((Global::toolState == TSPoint2) && shiftDown)
869 // Key override is telling us to make a new line, not continue the
871 toolPoint[0] = toolPoint[1];
875 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
876 Circle * c = new Circle(toolPoint[0], length);
877 c->layer = Global::activeLayer;
878 document.objects.push_back(c);
879 toolPoint[0] = toolPoint[1];
880 Global::toolState = TSNone;
886 void DrawingView::ArcHandler(int mode, Point p)
891 if (Global::toolState == TSNone)
893 else if (Global::toolState == TSPoint2)
895 else if (Global::toolState == TSPoint3)
902 if (Global::toolState == TSNone)
904 else if (Global::toolState == TSPoint2)
906 else if (Global::toolState == TSPoint3)
913 if (Global::toolState == TSNone)
915 // Prevent spurious line from drawing...
916 toolPoint[1] = toolPoint[0];
917 Global::toolState = TSPoint2;
919 else if (Global::toolState == TSPoint2)
923 // Key override is telling us to start circle at new center, not
924 // continue the current one.
925 toolPoint[0] = toolPoint[1];
929 // Set the radius in toolPoint[1].x
930 toolPoint[1].x = Vector::Magnitude(toolPoint[0], toolPoint[1]);
931 Global::toolState = TSPoint3;
933 else if (Global::toolState == TSPoint3)
935 // Set the angle in toolPoint[2].x
936 toolPoint[2].x = Vector::Angle(toolPoint[0], toolPoint[2]);
937 Global::toolState = TSPoint4;
941 double endAngle = Vector::Angle(toolPoint[0], toolPoint[3]);
942 double span = endAngle - toolPoint[2].x;
947 Arc * arc = new Arc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
948 arc->layer = Global::activeLayer;
949 document.objects.push_back(arc);
950 Global::toolState = TSNone;
956 void DrawingView::RotateHandler(int mode, Point p)
961 if (Global::toolState == TSNone)
964 SavePointsFrom(select, toolScratch);
965 Global::toolState = TSPoint1;
967 else if (Global::toolState == TSPoint1)
974 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
976 else if (Global::toolState == TSPoint2)
983 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
984 std::vector<void *>::iterator j = select.begin();
985 std::vector<Object>::iterator i = toolScratch.begin();
987 for(; i!=toolScratch.end(); i++, j++)
990 Point p1 = Geometry::RotatePointAroundPoint(obj.p[0], toolPoint[0], angle);
991 Point p2 = Geometry::RotatePointAroundPoint(obj.p[1], toolPoint[0], angle);
992 Object * obj2 = (Object *)(*j);
996 if (obj.type == OTArc)
998 obj2->angle[0] = obj.angle[0] + angle;
1000 if (obj2->angle[0] > TAU)
1001 obj2->angle[0] -= TAU;
1008 if (Global::toolState == TSPoint1)
1010 Global::toolState = TSPoint2;
1011 // Prevent spurious line from drawing...
1012 toolPoint[1] = toolPoint[0];
1014 else if ((Global::toolState == TSPoint2) && shiftDown)
1016 // Key override is telling us to make a new line, not continue the
1018 toolPoint[0] = toolPoint[1];
1022 // Either we're finished with our rotate, or we're stamping a copy.
1025 // Stamp a copy of the selection at the current rotation & bail
1026 std::vector<void *> temp;
1027 CopyObjects(select, temp);
1028 ClearSelected(temp);
1029 AddObjectsTo(document.objects, temp);
1030 RestorePointsTo(select, toolScratch);
1035 Global::toolState = TSPoint1;
1036 SavePointsFrom(select, toolScratch);
1041 // Reset the selection if shift held down...
1043 RestorePointsTo(select, toolScratch);
1047 // Reset selection when key is let up
1049 RotateHandler(ToolMouseMove, toolPoint[1]);
1053 RestorePointsTo(select, toolScratch);
1058 void DrawingView::MirrorHandler(int mode, Point p)
1063 if (Global::toolState == TSNone)
1066 SavePointsFrom(select, toolScratch);
1067 Global::toolState = TSPoint1;
1069 else if (Global::toolState == TSPoint1)
1076 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1078 else if (Global::toolState == TSPoint2)
1085 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1086 std::vector<void *>::iterator j = select.begin();
1087 std::vector<Object>::iterator i = toolScratch.begin();
1089 for(; i!=toolScratch.end(); i++, j++)
1092 Point p1 = Geometry::MirrorPointAroundLine(obj.p[0], toolPoint[0], toolPoint[1]);
1093 Point p2 = Geometry::MirrorPointAroundLine(obj.p[1], toolPoint[0], toolPoint[1]);
1094 Object * obj2 = (Object *)(*j);
1098 if (obj.type == OTArc)
1100 // This is 2*mirror angle - obj angle - obj span
1101 obj2->angle[0] = (2.0 * angle) - obj.angle[0] - obj.angle[1];
1103 if (obj2->angle[0] > TAU)
1104 obj2->angle[0] -= TAU;
1111 if (Global::toolState == TSPoint1)
1113 Global::toolState = TSPoint2;
1114 // Prevent spurious line from drawing...
1115 toolPoint[1] = toolPoint[0];
1117 else if ((Global::toolState == TSPoint2) && shiftDown)
1119 // Key override is telling us to make a new line, not continue the
1121 toolPoint[0] = toolPoint[1];
1125 // Either we're finished with our rotate, or we're stamping a copy.
1128 // Stamp a copy of the selection at the current rotation & bail
1129 std::vector<void *> temp;
1130 CopyObjects(select, temp);
1131 ClearSelected(temp);
1132 AddObjectsTo(document.objects, temp);
1133 RestorePointsTo(select, toolScratch);
1138 Global::toolState = TSPoint1;
1139 SavePointsFrom(select, toolScratch);
1144 // Reset the selection if shift held down...
1146 RestorePointsTo(select, toolScratch);
1150 // Reset selection when key is let up
1152 MirrorHandler(ToolMouseMove, toolPoint[1]);
1156 RestorePointsTo(select, toolScratch);
1161 void DrawingView::mousePressEvent(QMouseEvent * event)
1163 if (event->button() == Qt::LeftButton)
1165 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1167 // Handle tool processing, if any
1170 if (hoveringIntersection)
1171 point = intersectionPoint;
1172 else if (Global::snapToGrid)
1173 point = SnapPointToGrid(point);
1175 //Also, may want to figure out if hovering over a snap point on an object,
1176 //snap to grid if not.
1177 // Snap to object point if valid...
1178 // if (Global::snapPointIsValid)
1179 // point = Global::snapPoint;
1181 ToolHandler(ToolMouseDown, point);
1185 // Clear the selection only if CTRL isn't being held on click
1187 ClearSelected(document.objects);
1188 // ClearSelection();
1190 // If any objects are being hovered on click, add them to the selection
1194 AddHoveredToSelection();
1195 update(); // needed??
1196 GetHovered(hover); // prolly needed
1198 // Needed for grab & moving objects
1199 // We do it *after*... why? (doesn't seem to confer any advantage...)
1200 if (hoveringIntersection)
1201 oldPoint = intersectionPoint;
1202 else if (Global::snapToGrid)
1203 oldPoint = SnapPointToGrid(point);
1208 // Didn't hit any object and not using a tool, so do a selection rectangle
1209 Global::selectionInProgress = true;
1210 Global::selection.setTopLeft(QPointF(point.x, point.y));
1211 Global::selection.setBottomRight(QPointF(point.x, point.y));
1213 else if (event->button() == Qt::MiddleButton)
1216 oldPoint = Vector(event->x(), event->y());
1217 // Should also change the mouse pointer as well...
1218 setCursor(Qt::SizeAllCursor);
1223 void DrawingView::mouseMoveEvent(QMouseEvent * event)
1225 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1226 Global::selection.setBottomRight(QPointF(point.x, point.y));
1227 // Only needs to be done here, as mouse down is always preceded by movement
1228 Global::snapPointIsValid = false;
1229 hoveringIntersection = false;
1232 if (event->buttons() & Qt::MiddleButton)
1234 point = Vector(event->x(), event->y());
1235 // Since we're using Qt coords for scrolling, we have to adjust them
1236 // here to conform to Cartesian coords, since the origin is using
1238 Vector delta(oldPoint, point);
1239 delta /= Global::zoom;
1241 Global::origin -= delta;
1243 UpdateGridBackground();
1249 // If we're doing a selection rect, see if any objects are engulfed by it
1250 // (implies left mouse button held down)
1251 if (Global::selectionInProgress)
1253 CheckObjectBounds();
1258 // Handle object movement (left button down & over an object)
1259 if ((event->buttons() & Qt::LeftButton) && numHovered && !Global::tool)
1261 if (hoveringIntersection)
1262 point = intersectionPoint;
1263 else if (Global::snapToGrid)
1264 point = SnapPointToGrid(point);
1266 HandleObjectMovement(point);
1272 // Do object hit testing...
1273 bool needUpdate = HitTestObjects(point);
1275 // Check for multi-hover...
1279 Geometry::Intersects((Object *)hover[0], (Object *)hover[1]);
1280 int numIntersecting = Global::numIntersectParams;
1281 double t = Global::intersectParam[0];
1282 double u = Global::intersectParam[1];
1284 if (numIntersecting > 0)
1286 Vector v1 = Geometry::GetPointForParameter((Object *)hover[0], t);
1287 Vector v2 = Geometry::GetPointForParameter((Object *)hover[1], u);
1288 QString text = tr("Intersection t=%1 (%3, %4), u=%2 (%5, %6)");
1289 informativeText = text.arg(t).arg(u).arg(v1.x).arg(v1.y).arg(v2.x).arg(v2.y);
1291 hoveringIntersection = true;
1292 intersectionPoint = v1;
1295 numIntersecting = Global::numIntersectPoints;
1297 if (numIntersecting > 0)
1299 Vector v1 = Global::intersectPoint[0];
1301 if (numIntersecting == 2)
1303 Vector v2 = Global::intersectPoint[1];
1305 if (Vector::Magnitude(v2, point) < Vector::Magnitude(v1, point))
1309 QString text = tr("Intersection <%1, %2>");
1310 informativeText = text.arg(v1.x).arg(v1.y);
1311 hoveringIntersection = true;
1312 intersectionPoint = v1;
1316 //this doesn't work down here for some reason... :-P
1317 //could be because the object being moved is part of the intersection, and this is screwing things up. In which case, we need to exclude the moving object somehow from the hit test function...
1319 // Handle object movement (left button down & over an object)
1320 if ((event->buttons() & Qt::LeftButton) && numHovered && !Global::tool)
1322 if (hoveringIntersection)
1323 point = intersectionPoint;
1324 else if (Global::snapToGrid)
1325 point = SnapPointToGrid(point);
1327 HandleObjectMovement(point);
1334 // Do tool handling, if any are active...
1337 if (hoveringIntersection)
1338 point = intersectionPoint;
1339 else if (Global::snapToGrid)
1340 point = SnapPointToGrid(point);
1342 ToolHandler(ToolMouseMove, point);
1345 // This is used to draw the tool crosshair...
1348 if (needUpdate || Global::tool)
1353 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
1355 if (event->button() == Qt::LeftButton)
1357 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
1358 //could set it up to use the document's update function (assumes that all object
1359 //updates are being reported correctly:
1360 // if (document.NeedsUpdate())
1361 // Do an update if collided with at least *one* object in the document
1367 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1368 ToolHandler(ToolMouseUp, point);
1372 if (Global::selectionInProgress)
1373 Global::selectionInProgress = false;
1375 informativeText.clear();
1376 // Should we be doing this automagically? Hmm...
1377 // Clear our vectors
1382 std::vector<void *>::iterator i;
1384 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1386 if (((Object *)(*i))->selected)
1387 select.push_back(*i);
1389 //hmm, this is no good, too late to do any good :-P
1390 // if ((*i)->hovered)
1391 // hover.push_back(*i);
1394 else if (event->button() == Qt::MiddleButton)
1397 setCursor(Qt::ArrowCursor);
1402 void DrawingView::wheelEvent(QWheelEvent * event)
1404 double zoomFactor = 1.25;
1405 QSize sizeWin = size();
1406 Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
1407 center = Painter::QtToCartesianCoords(center);
1409 // This is not centering for some reason. Need to figure out why. :-/
1410 if (event->delta() > 0)
1412 Vector newOrigin = center - ((center - Global::origin) / zoomFactor);
1413 Global::origin = newOrigin;
1414 Global::zoom *= zoomFactor;
1418 Vector newOrigin = center + ((-center + Global::origin) * zoomFactor);
1419 Global::origin = newOrigin;
1420 Global::zoom /= zoomFactor;
1423 // Global::gridSpacing = gridPixels / Painter::zoom;
1424 // UpdateGridBackground();
1425 SetGridSize(Global::gridSpacing * Global::zoom);
1427 // zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Global::gridSpacing));
1431 void DrawingView::keyPressEvent(QKeyEvent * event)
1433 bool oldShift = shiftDown;
1434 bool oldCtrl = ctrlDown;
1436 if (event->key() == Qt::Key_Shift)
1438 else if (event->key() == Qt::Key_Control)
1441 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1444 ToolHandler(ToolKeyDown, Point(0, 0));
1451 void DrawingView::keyReleaseEvent(QKeyEvent * event)
1453 bool oldShift = shiftDown;
1454 bool oldCtrl = ctrlDown;
1456 if (event->key() == Qt::Key_Shift)
1458 else if (event->key() == Qt::Key_Control)
1461 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1464 ToolHandler(ToolKeyUp, Point(0, 0));
1472 // This looks strange, but it's really quite simple: We want a point that's
1473 // more than half-way to the next grid point to snap there while conversely we
1474 // want a point that's less than half-way to to the next grid point then snap
1475 // to the one before it. So we add half of the grid spacing to the point, then
1476 // divide by it so that we can remove the fractional part, then multiply it
1477 // back to get back to the correct answer.
1479 Point DrawingView::SnapPointToGrid(Point point)
1481 point += Global::gridSpacing / 2.0; // *This* adds to Z!!!
1482 point /= Global::gridSpacing;
1483 point.x = floor(point.x);//need to fix this for negative numbers...
1484 point.y = floor(point.y);
1485 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
1486 point *= Global::gridSpacing;
1491 Rect DrawingView::GetObjectExtents(Object * obj)
1493 // Default to empty rect, if object checks below fail for some reason
1500 rect = Rect(obj->p[0], obj->p[1]);
1505 rect = Rect(obj->p[0], obj->p[0]);
1506 rect.Expand(obj->radius[0]);
1511 Arc * a = (Arc *)obj;
1513 double start = a->angle[0];
1514 double end = start + a->angle[1];
1515 rect = Rect(Point(cos(start), sin(start)), Point(cos(end), sin(end)));
1517 // If the end of the arc is before the beginning, add 360 degrees to it
1521 // Adjust the bounds depending on which axes are crossed
1522 if ((start < QTR_TAU) && (end > QTR_TAU))
1525 if ((start < HALF_TAU) && (end > HALF_TAU))
1528 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
1531 if ((start < TAU) && (end > TAU))
1534 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
1537 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
1540 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
1543 rect *= a->radius[0];
1544 rect.Translate(a->p[0]);
1550 Container * c = (Container *)obj;
1551 std::vector<void *>::iterator i = c->objects.begin();
1552 rect = GetObjectExtents((Object *)*i);
1555 for(; i!=c->objects.end(); i++)
1556 rect |= GetObjectExtents((Object *)*i);
1566 void DrawingView::CheckObjectBounds(void)
1568 std::vector<void *>::iterator i;
1570 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1572 Object * obj = (Object *)(*i);
1573 obj->selected = false;
1579 Line * l = (Line *)obj;
1581 if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
1588 Circle * c = (Circle *)obj;
1590 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]))
1597 Arc * a = (Arc *)obj;
1599 double start = a->angle[0];
1600 double end = start + a->angle[1];
1601 QPointF p1(cos(start), sin(start));
1602 QPointF p2(cos(end), sin(end));
1603 QRectF bounds(p1, p2);
1606 // Swap X/Y coordinates if they're backwards...
1607 if (bounds.left() > bounds.right())
1609 double temp = bounds.left();
1610 bounds.setLeft(bounds.right());
1611 bounds.setRight(temp);
1614 if (bounds.bottom() > bounds.top())
1616 double temp = bounds.bottom();
1617 bounds.setBottom(bounds.top());
1618 bounds.setTop(temp);
1621 // Doesn't work as advertised! For shame!
1622 bounds = bounds.normalized();
1625 // If the end of the arc is before the beginning, add 360 degrees to it
1629 // Adjust the bounds depending on which axes are crossed
1630 if ((start < QTR_TAU) && (end > QTR_TAU))
1633 if ((start < HALF_TAU) && (end > HALF_TAU))
1634 bounds.setLeft(-1.0);
1636 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
1637 bounds.setBottom(-1.0);
1639 if ((start < TAU) && (end > TAU))
1640 bounds.setRight(1.0);
1642 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
1645 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
1646 bounds.setLeft(-1.0);
1648 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
1649 bounds.setBottom(-1.0);
1651 bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
1652 bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
1653 bounds.translate(a->p[0].x, a->p[0].y);
1655 if (Global::selection.contains(bounds))
1667 bool DrawingView::HitTestObjects(Point point)
1669 std::vector<void *>::iterator i;
1671 bool needUpdate = false;
1673 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1675 Object * obj = (Object *)(*i);
1677 if (HitTest(obj, point))
1684 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
1685 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
1686 Vector lineSegment = obj->p[1] - obj->p[0];
1687 Vector v1 = point - obj->p[0];
1688 Vector v2 = point - obj->p[1];
1689 double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
1693 distance = v1.Magnitude();
1695 distance = v2.Magnitude();
1697 // distance = ?Det?(ls, v1) / |ls|
1698 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
1699 / lineSegment.Magnitude());
1701 if ((v1.Magnitude() * Global::zoom) < 8.0)
1702 obj->hitPoint[0] = true;
1703 else if ((v2.Magnitude() * Global::zoom) < 8.0)
1704 obj->hitPoint[1] = true;
1705 else if ((distance * Global::zoom) < 5.0)
1706 obj->hitObject = true;
1708 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
1710 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
1717 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
1718 obj->hitPoint[0] = obj->hitObject = false;
1719 double length = Vector::Magnitude(obj->p[0], point);
1721 if ((length * Global::zoom) < 8.0)
1722 obj->hitPoint[0] = true;
1723 else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
1724 obj->hitObject = true;
1726 obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
1728 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
1735 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
1736 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
1737 double length = Vector::Magnitude(obj->p[0], point);
1738 double angle = Vector::Angle(obj->p[0], point);
1740 // Make sure we get the angle in the correct spot
1741 if (angle < obj->angle[0])
1744 // Get the span that we're pointing at...
1745 double span = angle - obj->angle[0];
1747 // N.B.: Still need to hit test the arc start & arc span handles...
1748 double spanAngle = obj->angle[0] + obj->angle[1];
1749 Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
1750 Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
1751 double length2 = Vector::Magnitude(point, handle1);
1752 double length3 = Vector::Magnitude(point, handle2);
1754 if ((length * Global::zoom) < 8.0)
1755 obj->hitPoint[0] = true;
1756 else if ((length2 * Global::zoom) < 8.0)
1757 obj->hitPoint[1] = true;
1758 else if ((length3 * Global::zoom) < 8.0)
1759 obj->hitPoint[2] = true;
1760 else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
1761 obj->hitObject = true;
1763 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
1765 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
1772 // Containers must be recursively tested...
1773 Container * c = (Container *)obj;
1774 std::vector<void *>::iterator i;
1776 for(i=c->objects.begin(); i!=c->objects.end(); i++)
1789 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
1790 emit(ObjectHovered(obj));
1798 bool DrawingView::HitTest(Object * obj, Point point)
1800 bool needUpdate = false;
1806 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
1807 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
1808 Vector lineSegment = obj->p[1] - obj->p[0];
1809 Vector v1 = point - obj->p[0];
1810 Vector v2 = point - obj->p[1];
1811 double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
1815 distance = v1.Magnitude();
1817 distance = v2.Magnitude();
1819 // distance = ?Det?(ls, v1) / |ls|
1820 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
1821 / lineSegment.Magnitude());
1823 if ((v1.Magnitude() * Global::zoom) < 8.0)
1824 obj->hitPoint[0] = true;
1825 else if ((v2.Magnitude() * Global::zoom) < 8.0)
1826 obj->hitPoint[1] = true;
1827 else if ((distance * Global::zoom) < 5.0)
1828 obj->hitObject = true;
1830 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
1832 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
1839 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
1840 obj->hitPoint[0] = obj->hitObject = false;
1841 double length = Vector::Magnitude(obj->p[0], point);
1843 if ((length * Global::zoom) < 8.0)
1844 obj->hitPoint[0] = true;
1845 else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
1846 obj->hitObject = true;
1848 obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
1850 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
1857 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
1858 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
1859 double length = Vector::Magnitude(obj->p[0], point);
1860 double angle = Vector::Angle(obj->p[0], point);
1862 // Make sure we get the angle in the correct spot
1863 if (angle < obj->angle[0])
1866 // Get the span that we're pointing at...
1867 double span = angle - obj->angle[0];
1869 // N.B.: Still need to hit test the arc start & arc span handles...
1870 double spanAngle = obj->angle[0] + obj->angle[1];
1871 Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
1872 Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
1873 double length2 = Vector::Magnitude(point, handle1);
1874 double length3 = Vector::Magnitude(point, handle2);
1876 if ((length * Global::zoom) < 8.0)
1877 obj->hitPoint[0] = true;
1878 else if ((length2 * Global::zoom) < 8.0)
1879 obj->hitPoint[1] = true;
1880 else if ((length3 * Global::zoom) < 8.0)
1881 obj->hitPoint[2] = true;
1882 else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
1883 obj->hitObject = true;
1885 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
1887 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
1894 // Containers must be recursively tested...
1895 Container * c = (Container *)obj;
1896 c->hitObject = false;
1898 std::vector<void *>::iterator i;
1900 for(i=c->objects.begin(); i!=c->objects.end(); i++)
1902 Object * cObj = (Object *)*i;
1904 if (HitTest(cObj, point))
1907 if (cObj->hitObject == true)
1908 c->hitObject = true;
1910 if (cObj->hovered == true)
1924 void DrawingView::HandleObjectMovement(Point point)
1926 Point delta = point - oldPoint;
1927 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
1928 Object * obj = (Object *)hover[0];
1929 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
1930 //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"));
1935 if (obj->hitPoint[0])
1937 else if (obj->hitPoint[1])
1939 else if (obj->hitObject)
1947 if (obj->hitPoint[0])
1949 else if (obj->hitObject)
1951 //this doesn't work. we need to save this on mouse down for this to work correctly!
1952 // double oldRadius = obj->radius[0];
1953 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
1955 QString text = QObject::tr("Radius: %1");//\nScale: %2%");
1956 informativeText = text.arg(obj->radius[0], 0, 'd', 4);//.arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0);
1961 if (obj->hitPoint[0])
1963 else if (obj->hitPoint[1])
1965 // Change the Arc's span (handle #1)
1968 double angle = Vector::Angle(obj->p[0], point);
1969 double delta = angle - obj->angle[0];
1974 obj->angle[1] -= delta;
1975 obj->angle[0] = angle;
1977 if (obj->angle[1] < 0)
1978 obj->angle[1] += TAU;
1980 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
1981 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);
1985 double angle = Vector::Angle(obj->p[0], point);
1986 obj->angle[0] = angle;
1987 QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
1988 informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 4);
1990 else if (obj->hitPoint[2])
1992 // Change the Arc's span (handle #2)
1995 double angle = Vector::Angle(obj->p[0], point);
1996 obj->angle[1] = angle - obj->angle[0];
1998 if (obj->angle[1] < 0)
1999 obj->angle[1] += TAU;
2001 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
2002 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);
2006 double angle = Vector::Angle(obj->p[0], point);
2007 obj->angle[0] = angle - obj->angle[1];
2009 if (obj->angle[0] < 0)
2010 obj->angle[0] += TAU;
2012 QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
2013 informativeText = text.arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 4);
2015 else if (obj->hitObject)
2022 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
2023 QString text = QObject::tr("Radius: %1");
2024 informativeText = text.arg(obj->radius[0], 0, 'd', 4);
2029 // This is shitty, but works for now until I can code up something
2031 TranslateObject(obj, delta);