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),
52 dragged(NULL), draggingObject(false)
54 //wtf? doesn't work except in c++11??? document = { 0 };
55 setBackgroundRole(QPalette::Base);
56 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
58 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
1176 //object, 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
1197 dragged = (Object *)hover[0];
1198 draggingObject = true;
1200 // Needed for grab & moving objects
1201 // We do it *after*... why? (doesn't seem to confer any advantage...)
1202 if (hoveringIntersection)
1203 oldPoint = intersectionPoint;
1204 else if (Global::snapToGrid)
1205 oldPoint = SnapPointToGrid(point);
1210 // Didn't hit any object and not using a tool, so do a selection rectangle
1211 Global::selectionInProgress = true;
1212 Global::selection.setTopLeft(QPointF(point.x, point.y));
1213 Global::selection.setBottomRight(QPointF(point.x, point.y));
1215 else if (event->button() == Qt::MiddleButton)
1218 oldPoint = Vector(event->x(), event->y());
1219 // Should also change the mouse pointer as well...
1220 setCursor(Qt::SizeAllCursor);
1225 void DrawingView::mouseMoveEvent(QMouseEvent * event)
1227 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1228 Global::selection.setBottomRight(QPointF(point.x, point.y));
1229 // Only needs to be done here, as mouse down is always preceded by movement
1230 Global::snapPointIsValid = false;
1231 hoveringIntersection = false;
1234 if (event->buttons() & Qt::MiddleButton)
1236 point = Vector(event->x(), event->y());
1237 // Since we're using Qt coords for scrolling, we have to adjust them
1238 // here to conform to Cartesian coords, since the origin is using
1240 Vector delta(oldPoint, point);
1241 delta /= Global::zoom;
1243 Global::origin -= delta;
1245 UpdateGridBackground();
1251 // If we're doing a selection rect, see if any objects are engulfed by it
1252 // (implies left mouse button held down)
1253 if (Global::selectionInProgress)
1255 CheckObjectBounds();
1260 // Do object hit testing...
1261 bool needUpdate = HitTestObjects(point);
1264 // Check for multi-hover...
1267 //need to check for case where hover is over 2 circles and a 3rd's center...
1268 Object * obj1 = (Object *)hover[0], * obj2 = (Object *)hover[1];
1270 Geometry::Intersects(obj1, obj2);
1271 int numIntersecting = Global::numIntersectParams;
1272 double t = Global::intersectParam[0];
1273 double u = Global::intersectParam[1];
1275 if (numIntersecting > 0)
1277 Vector v1 = Geometry::GetPointForParameter(obj1, t);
1278 Vector v2 = Geometry::GetPointForParameter(obj2, u);
1279 QString text = tr("Intersection t=%1 (%3, %4), u=%2 (%5, %6)");
1280 informativeText = text.arg(t).arg(u).arg(v1.x).arg(v1.y).arg(v2.x).arg(v2.y);
1282 hoveringIntersection = true;
1283 intersectionPoint = v1;
1286 numIntersecting = Global::numIntersectPoints;
1288 if (numIntersecting > 0)
1290 Vector v1 = Global::intersectPoint[0];
1292 if (numIntersecting == 2)
1294 Vector v2 = Global::intersectPoint[1];
1296 if (Vector::Magnitude(v2, point) < Vector::Magnitude(v1, point))
1300 QString text = tr("Intersection <%1, %2>");
1301 informativeText = text.arg(v1.x).arg(v1.y);
1302 hoveringIntersection = true;
1303 intersectionPoint = v1;
1307 // Handle object movement (left button down & over an object)
1308 if ((event->buttons() & Qt::LeftButton) && draggingObject && !Global::tool)
1310 if (hoveringIntersection)
1311 point = intersectionPoint;
1312 else if (hoverPointValid)
1314 else if (Global::snapToGrid)
1315 point = SnapPointToGrid(point);
1317 HandleObjectMovement(point);
1323 // Do tool handling, if any are active...
1326 if (hoveringIntersection)
1327 point = intersectionPoint;
1328 else if (hoverPointValid)
1330 else if (Global::snapToGrid)
1331 point = SnapPointToGrid(point);
1333 ToolHandler(ToolMouseMove, point);
1336 // This is used to draw the tool crosshair...
1339 if (needUpdate || Global::tool)
1344 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
1346 if (event->button() == Qt::LeftButton)
1348 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
1349 //could set it up to use the document's update function (assumes that all object
1350 //updates are being reported correctly:
1351 // if (document.NeedsUpdate())
1352 // Do an update if collided with at least *one* object in the document
1358 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1359 ToolHandler(ToolMouseUp, point);
1363 if (Global::selectionInProgress)
1364 Global::selectionInProgress = false;
1366 informativeText.clear();
1367 // Should we be doing this automagically? Hmm...
1368 // Clear our vectors
1373 std::vector<void *>::iterator i;
1375 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1377 if (((Object *)(*i))->selected)
1378 select.push_back(*i);
1381 draggingObject = false;
1383 else if (event->button() == Qt::MiddleButton)
1386 setCursor(Qt::ArrowCursor);
1391 void DrawingView::wheelEvent(QWheelEvent * event)
1393 double zoomFactor = 1.25;
1394 QSize sizeWin = size();
1395 Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
1396 center = Painter::QtToCartesianCoords(center);
1398 // This is not centering for some reason. Need to figure out why. :-/
1399 if (event->delta() > 0)
1401 Vector newOrigin = center - ((center - Global::origin) / zoomFactor);
1402 Global::origin = newOrigin;
1403 Global::zoom *= zoomFactor;
1407 Vector newOrigin = center + ((-center + Global::origin) * zoomFactor);
1408 Global::origin = newOrigin;
1409 Global::zoom /= zoomFactor;
1412 // Global::gridSpacing = gridPixels / Painter::zoom;
1413 // UpdateGridBackground();
1414 SetGridSize(Global::gridSpacing * Global::zoom);
1416 // zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Global::gridSpacing));
1420 void DrawingView::keyPressEvent(QKeyEvent * event)
1422 bool oldShift = shiftDown;
1423 bool oldCtrl = ctrlDown;
1425 if (event->key() == Qt::Key_Shift)
1427 else if (event->key() == Qt::Key_Control)
1430 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1433 ToolHandler(ToolKeyDown, Point(0, 0));
1438 if (select.size() > 0)
1440 if (event->key() == Qt::Key_Up)
1442 TranslateObjects(select, Point(0, +1.0));
1445 else if (event->key() == Qt::Key_Down)
1447 TranslateObjects(select, Point(0, -1.0));
1450 else if (event->key() == Qt::Key_Right)
1452 TranslateObjects(select, Point(+1.0, 0));
1455 else if (event->key() == Qt::Key_Left)
1457 TranslateObjects(select, Point(-1.0, 0));
1464 void DrawingView::keyReleaseEvent(QKeyEvent * event)
1466 bool oldShift = shiftDown;
1467 bool oldCtrl = ctrlDown;
1469 if (event->key() == Qt::Key_Shift)
1471 else if (event->key() == Qt::Key_Control)
1474 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1477 ToolHandler(ToolKeyUp, Point(0, 0));
1485 // This looks strange, but it's really quite simple: We want a point that's
1486 // more than half-way to the next grid point to snap there while conversely we
1487 // want a point that's less than half-way to to the next grid point then snap
1488 // to the one before it. So we add half of the grid spacing to the point, then
1489 // divide by it so that we can remove the fractional part, then multiply it
1490 // back to get back to the correct answer.
1492 Point DrawingView::SnapPointToGrid(Point point)
1494 point += Global::gridSpacing / 2.0; // *This* adds to Z!!!
1495 point /= Global::gridSpacing;
1496 point.x = floor(point.x);//need to fix this for negative numbers...
1497 point.y = floor(point.y);
1498 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
1499 point *= Global::gridSpacing;
1504 Rect DrawingView::GetObjectExtents(Object * obj)
1506 // Default to empty rect, if object checks below fail for some reason
1513 rect = Rect(obj->p[0], obj->p[1]);
1518 rect = Rect(obj->p[0], obj->p[0]);
1519 rect.Expand(obj->radius[0]);
1524 Arc * a = (Arc *)obj;
1526 double start = a->angle[0];
1527 double end = start + a->angle[1];
1528 rect = Rect(Point(cos(start), sin(start)), Point(cos(end), sin(end)));
1530 // If the end of the arc is before the beginning, add 360 degrees to it
1534 // Adjust the bounds depending on which axes are crossed
1535 if ((start < QTR_TAU) && (end > QTR_TAU))
1538 if ((start < HALF_TAU) && (end > HALF_TAU))
1541 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
1544 if ((start < TAU) && (end > TAU))
1547 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
1550 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
1553 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
1556 rect *= a->radius[0];
1557 rect.Translate(a->p[0]);
1563 Container * c = (Container *)obj;
1564 std::vector<void *>::iterator i = c->objects.begin();
1565 rect = GetObjectExtents((Object *)*i);
1568 for(; i!=c->objects.end(); i++)
1569 rect |= GetObjectExtents((Object *)*i);
1579 void DrawingView::CheckObjectBounds(void)
1581 std::vector<void *>::iterator i;
1583 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1585 Object * obj = (Object *)(*i);
1586 obj->selected = false;
1592 Line * l = (Line *)obj;
1594 if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
1601 Circle * c = (Circle *)obj;
1603 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]))
1610 Arc * a = (Arc *)obj;
1612 double start = a->angle[0];
1613 double end = start + a->angle[1];
1614 QPointF p1(cos(start), sin(start));
1615 QPointF p2(cos(end), sin(end));
1616 QRectF bounds(p1, p2);
1619 // Swap X/Y coordinates if they're backwards...
1620 if (bounds.left() > bounds.right())
1622 double temp = bounds.left();
1623 bounds.setLeft(bounds.right());
1624 bounds.setRight(temp);
1627 if (bounds.bottom() > bounds.top())
1629 double temp = bounds.bottom();
1630 bounds.setBottom(bounds.top());
1631 bounds.setTop(temp);
1634 // Doesn't work as advertised! For shame!
1635 bounds = bounds.normalized();
1638 // If the end of the arc is before the beginning, add 360 degrees to it
1642 // Adjust the bounds depending on which axes are crossed
1643 if ((start < QTR_TAU) && (end > QTR_TAU))
1646 if ((start < HALF_TAU) && (end > HALF_TAU))
1647 bounds.setLeft(-1.0);
1649 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
1650 bounds.setBottom(-1.0);
1652 if ((start < TAU) && (end > TAU))
1653 bounds.setRight(1.0);
1655 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
1658 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
1659 bounds.setLeft(-1.0);
1661 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
1662 bounds.setBottom(-1.0);
1664 bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
1665 bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
1666 bounds.translate(a->p[0].x, a->p[0].y);
1668 if (Global::selection.contains(bounds))
1680 bool DrawingView::HitTestObjects(Point point)
1682 std::vector<void *>::iterator i;
1684 bool needUpdate = false;
1685 hoverPointValid = false;
1687 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1689 Object * obj = (Object *)(*i);
1691 // If we're seeing the object we're dragging, skip it
1692 if (draggingObject && (obj == dragged))
1695 if (HitTest(obj, point))
1701 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
1702 emit(ObjectHovered(obj));
1710 bool DrawingView::HitTest(Object * obj, Point point)
1712 bool needUpdate = false;
1718 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
1719 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
1720 Vector lineSegment = obj->p[1] - obj->p[0];
1721 Vector v1 = point - obj->p[0];
1722 Vector v2 = point - obj->p[1];
1723 double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
1727 distance = v1.Magnitude();
1729 distance = v2.Magnitude();
1731 // distance = ?Det?(ls, v1) / |ls|
1732 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
1733 / lineSegment.Magnitude());
1735 if ((v1.Magnitude() * Global::zoom) < 8.0)
1737 obj->hitPoint[0] = true;
1738 hoverPoint = obj->p[0];
1739 hoverPointValid = true;
1741 else if ((v2.Magnitude() * Global::zoom) < 8.0)
1743 obj->hitPoint[1] = true;
1744 hoverPoint = obj->p[1];
1745 hoverPointValid = true;
1747 else if ((distance * Global::zoom) < 5.0)
1748 obj->hitObject = true;
1750 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
1752 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
1759 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
1760 obj->hitPoint[0] = obj->hitObject = false;
1761 double length = Vector::Magnitude(obj->p[0], point);
1763 if ((length * Global::zoom) < 8.0)
1764 obj->hitPoint[0] = true;
1765 else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
1766 obj->hitObject = true;
1768 obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
1770 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
1777 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
1778 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
1779 double length = Vector::Magnitude(obj->p[0], point);
1780 double angle = Vector::Angle(obj->p[0], point);
1782 // Make sure we get the angle in the correct spot
1783 if (angle < obj->angle[0])
1786 // Get the span that we're pointing at...
1787 double span = angle - obj->angle[0];
1789 // N.B.: Still need to hit test the arc start & arc span handles...
1790 double spanAngle = obj->angle[0] + obj->angle[1];
1791 Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
1792 Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
1793 double length2 = Vector::Magnitude(point, handle1);
1794 double length3 = Vector::Magnitude(point, handle2);
1796 if ((length * Global::zoom) < 8.0)
1797 obj->hitPoint[0] = true;
1798 else if ((length2 * Global::zoom) < 8.0)
1799 obj->hitPoint[1] = true;
1800 else if ((length3 * Global::zoom) < 8.0)
1801 obj->hitPoint[2] = true;
1802 else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
1803 obj->hitObject = true;
1805 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
1807 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
1814 // Containers must be recursively tested...
1815 Container * c = (Container *)obj;
1816 c->hitObject = false;
1818 std::vector<void *>::iterator i;
1820 for(i=c->objects.begin(); i!=c->objects.end(); i++)
1822 Object * cObj = (Object *)*i;
1824 if (HitTest(cObj, point))
1827 if (cObj->hitObject == true)
1828 c->hitObject = true;
1830 if (cObj->hovered == true)
1844 void DrawingView::HandleObjectMovement(Point point)
1846 Point delta = point - oldPoint;
1847 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
1848 // Object * obj = (Object *)hover[0];
1849 Object * obj = dragged;
1850 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
1851 //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"));
1856 if (obj->hitPoint[0])
1858 else if (obj->hitPoint[1])
1860 else if (obj->hitObject)
1868 if (obj->hitPoint[0])
1870 else if (obj->hitObject)
1872 //this doesn't work. we need to save this on mouse down for this to work correctly!
1873 // double oldRadius = obj->radius[0];
1874 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
1876 QString text = QObject::tr("Radius: %1");//\nScale: %2%");
1877 informativeText = text.arg(obj->radius[0], 0, 'd', 4);//.arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0);
1882 if (obj->hitPoint[0])
1884 else if (obj->hitPoint[1])
1886 // Change the Arc's span (handle #1)
1889 double angle = Vector::Angle(obj->p[0], point);
1890 double delta = angle - obj->angle[0];
1895 obj->angle[1] -= delta;
1896 obj->angle[0] = angle;
1898 if (obj->angle[1] < 0)
1899 obj->angle[1] += TAU;
1901 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
1902 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);
1906 double angle = Vector::Angle(obj->p[0], point);
1907 obj->angle[0] = angle;
1908 QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
1909 informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 4);
1911 else if (obj->hitPoint[2])
1913 // Change the Arc's span (handle #2)
1916 double angle = Vector::Angle(obj->p[0], point);
1917 obj->angle[1] = angle - obj->angle[0];
1919 if (obj->angle[1] < 0)
1920 obj->angle[1] += TAU;
1922 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
1923 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);
1927 double angle = Vector::Angle(obj->p[0], point);
1928 obj->angle[0] = angle - obj->angle[1];
1930 if (obj->angle[0] < 0)
1931 obj->angle[0] += TAU;
1933 QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
1934 informativeText = text.arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 4);
1936 else if (obj->hitObject)
1943 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
1944 QString text = QObject::tr("Radius: %1");
1945 informativeText = text.arg(obj->radius[0], 0, 'd', 4);
1950 // This is shitty, but works for now until I can code up something
1952 TranslateObject(obj, delta);