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)
61 Line * line = new Line(Vector(5, 5), Vector(50, 40), &document);
63 document.Add(new Line(Vector(50, 40), Vector(10, 83), &document));
64 document.Add(new Line(Vector(10, 83), Vector(17, 2), &document));
65 document.Add(new Circle(Vector(100, 100), 36, &document));
66 document.Add(new Circle(Vector(50, 150), 49, &document));
67 document.Add(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3, &document)),
68 document.Add(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5, &document));
70 Dimension * dimension = new Dimension(Vector(0, 0), Vector(0, 0), DTLinear, &document);
71 line->SetDimensionOnLine(dimension);
72 document.Add(dimension);
74 // Alternate way to do the above...
75 line->SetDimensionOnLine();
78 Line * line = new Line;//(Vector(5, 5), Vector(50, 40), &document);
79 line->p[0] = Vector(5, 5);
80 line->p[1] = Vector(50, 40);
82 line->thickness = 2.0;
84 line->color = 0xFF7F00;
86 document.objects.push_back(line);
87 document.objects.push_back(new Line(Vector(50, 40), Vector(10, 83)));
88 document.objects.push_back(new Line(Vector(10, 83), Vector(17, 2)));
89 document.objects.push_back(new Circle(Vector(100, 100), 36));
90 document.objects.push_back(new Circle(Vector(50, 150), 49));
91 document.objects.push_back(new Arc(Vector(300, 300), 32, TAU / 8.0, TAU * 0.65)),
92 document.objects.push_back(new Arc(Vector(200, 200), 60, TAU / 4.0, TAU * 0.75));
93 document.objects.push_back(new Dimension(Vector(50, 40), Vector(5, 5)));
94 document.objects.push_back(new Text(Vector(10, 83), "Here is some awesome text!"));
98 Here we set the grid size in pixels--12 in this case. Initially, we have our
99 zoom set to make this represent 12 inches at a zoom factor of 25%. (This is
100 arbitrary.) So, to be able to decouple the grid size from the zoom, we need
101 to be able to set the size of the background grid (which we do here at an
102 arbitrary 12 pixels) to anything we want (within reason, of course :-).
104 The drawing enforces the grid spacing through the drawing->gridSpacing variable.
106 drawing->gridSpacing = 12.0 / Global::zoom;
108 Global::zoom is the zoom factor for the drawing, and all mouse clicks are
109 translated to Cartesian coordinates through this. (Initially, Global::zoom is
110 set to 1.0. SCREEN_ZOOM is set to 1.0/4.0.)
112 Really, the 100% zoom level can be set at *any* zoom level, it's more of a
113 convenience function than any measure of absolutes. Doing things that way we
114 could rid ourselves of the whole SCREEN_ZOOM parameter and all the attendant
115 shittiness that comes with it.
117 However, it seems that SCREEN_ZOOM is used to make text and arrow sizes show up
118 a certain way, which means we should probably create something else in those
119 objects to take its place--like some kind of scale factor. This would seem to
120 imply that certain point sizes actually *do* tie things like fonts to absolute
121 sizes on the screen, but not necessarily because you could have an inch scale
122 with text that is quite small relative to other objects on the screen, which
123 currently you have to zoom in to see (and which blows up the text). Point sizes
124 in an application like this are a bit meaningless; even though an inch is an
125 inch regardless of the zoom level a piece of text can be larger or smaller than
126 this. Maybe this is the case for having a base unit and basing point sizes off
129 Here's what's been figured out. Global::zoom is simply the ratio of pixels to
130 base units. What that means is that if you have a 12px grid with a 6" grid size
131 (& base unit of "inches"), Global::zoom becomes 12px / 6" = 2.0 px/in.
133 Dimensions now have a "size" parameter to set their absolute size in relation
134 to the base unit. ATM, the arrows are drawn in pixels, but also scaled by
135 Global::zoom *and* size. Same with the dimension text; it's drawn at 10pt and
136 scaled the same way as the arrowheads.
138 Need a way to scale line widths as well. :-/ Shouldn't be too difficult, just
139 need a thickness parameter similar to the "size" param for dimensions. (And now
143 SetGridSize(12); // This is in pixels
147 void DrawingView::SetGridSize(uint32_t size)
150 if (size == gridPixels)
153 // Recreate the background bitmap
155 QPainter pmp(&gridBackground);
156 pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
157 pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
159 for(int i=0; i<(BACKGROUND_MAX_SIZE-1); i+=gridPixels)
161 pmp.drawLine(i, 0, i, BACKGROUND_MAX_SIZE - 1);
162 pmp.drawLine(0, i, BACKGROUND_MAX_SIZE - 1, i);
167 // Set up new BG brush & zoom level (pixels per base unit)
168 Global::zoom = gridPixels / Global::gridSpacing;
169 UpdateGridBackground();
173 void DrawingView::UpdateGridBackground(void)
175 // Transform the origin to Qt coordinates
176 Vector pixmapOrigin = Painter::CartesianToQtCoords(Vector());
177 int x = (int)pixmapOrigin.x;
178 int y = (int)pixmapOrigin.y;
179 // Use mod arithmetic to grab the correct swatch of background
181 Negative numbers still screw it up... Need to think about what we're
182 trying to do here. The fact that it worked with 72 seems to have been pure luck.
183 It seems the problem is negative numbers: We can't let that happen.
184 When taking away the zero, it pops over 1 px at zero, then goes about 1/2 a
187 The bitmap looks like this:
197 @ x = 1, we want it to look like:
199 -+---+---+---+---+---
202 -+---+---+---+---+---
207 Which means we need to grab the sample from x = 3. @ x = -1:
217 Which means we need to grab the sample from x = 1. Which means we have to take
218 the mirror of the modulus of gridPixels.
220 Doing a mod of a negative number is problematic: 1st, the compiler converts the
221 negative number to an unsigned int, then it does the mod. Gets you wrong answers
222 most of the time, unless you use a power of 2. :-P So what we do here is just
223 take the modulus of the negation, which means we don't have to worry about
226 The positive case looks gruesome (and it is) but it boils down to this: We take
227 the modulus of the X coordinate, then mirror it by subtraction from the
228 maximum (in this case, gridPixels). This gives us a number in the range of 1 to
229 gridPixels. But we need the case where the result equalling gridPixels to be
230 zero; so we do another modulus operation on the result to achieve this.
235 x = (gridPixels - (x % gridPixels)) % gridPixels;
240 y = (gridPixels - (y % gridPixels)) % gridPixels;
242 // Here we grab a section of the bigger pixmap, so that the background
243 // *looks* like it's scrolling...
244 QPixmap pm = gridBackground.copy(x, y, gridPixels, gridPixels);
245 QPalette pal = palette();
246 pal.setBrush(backgroundRole(), QBrush(pm));
247 setAutoFillBackground(true);
253 // Basically, we just make a single pass through the Container. If the layer #
254 // is less than the layer # being deleted, then do nothing. If the layer # is
255 // equal to the layer # being deleted, then delete the object. If the layer #
256 // is greater than the layer # being deleted, then set the layer # to its layer
259 void DrawingView::DeleteCurrentLayer(int layer)
261 //printf("DrawingView::DeleteCurrentLayer(): currentLayer = %i\n", layer);
262 std::vector<void *>::iterator i = document.objects.begin();
264 while (i != document.objects.end())
266 Object * obj = (Object *)(*i);
268 if (obj->layer < layer)
270 else if (obj->layer == layer)
272 document.objects.erase(i);
282 // We've just done a destructive action, so update the screen!
287 void DrawingView::HandleLayerToggle(void)
289 // A layer's visibility was toggled, so update the screen...
295 // A layer was moved up or down in the layer list, so we have to swap the
296 // document's object's layer numbers in the layers that were swapped.
298 void DrawingView::HandleLayerSwap(int layer1, int layer2)
300 //printf("DrawingView: Swapping layers %i and %i.\n", layer1, layer2);
301 std::vector<void *>::iterator i;
303 for(i=document.objects.begin(); i!=document.objects.end(); i++)
305 Object * obj = (Object *)(*i);
307 if (obj->layer == layer1)
309 else if (obj->layer == layer2)
315 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
317 // This is undoing the transform, e.g. going from client coords to local
318 // coords. In essence, the height - y is height + (y * -1), the (y * -1)
319 // term doing the conversion of the y-axis from increasing bottom to top.
320 return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
324 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
326 // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
327 // No voodoo here, it's just grouped wrong to see it. It should be:
328 // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive [why? we use -offsetX after all]
329 return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
333 void DrawingView::paintEvent(QPaintEvent * /*event*/)
335 QPainter qtPainter(this);
336 Painter painter(&qtPainter);
339 qtPainter.setRenderHint(QPainter::Antialiasing);
341 Global::viewportHeight = size().height();
343 // Draw coordinate axes
344 painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
345 painter.DrawLine(0, -16384, 0, 16384);
346 painter.DrawLine(-16384, 0, 16384, 0);
348 // Do object rendering...
349 for(int i=0; i<Global::numLayers; i++)
351 if (Global::layerHidden[i] == false)
352 RenderObjects(&painter, document.objects, i);
355 // Do tool rendering, if any...
358 painter.SetPen(QPen(QColor(200, 100, 0, 255), 1.0, Qt::DashLine));
359 painter.DrawCrosshair(oldPoint);
363 // Do selection rectangle rendering, if any
364 if (Global::selectionInProgress)
366 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
367 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
368 painter.DrawRect(Global::selection);
371 if (hoveringIntersection)
372 painter.DrawHandle(intersectionPoint);
374 if (!informativeText.isEmpty())
375 painter.DrawInformativeText(informativeText);
380 // Renders objects in the passed in vector
382 void DrawingView::RenderObjects(Painter * painter, std::vector<void *> & v, int layer, bool ignoreLayer/*= false*/)
384 std::vector<void *>::iterator i;
386 for(i=v.begin(); i!=v.end(); i++)
388 Object * obj = (Object *)(*i);
389 float scaledThickness = Global::scale * obj->thickness;
391 // If the object isn't on the current layer being drawn, skip it
392 if (!ignoreLayer && (obj->layer != layer))
395 if ((Global::tool == TTRotate) && ctrlDown && obj->selected)
397 painter->SetPen(0x00FF00, 2.0, LSSolid);
401 painter->SetPen(obj->color, Global::zoom * scaledThickness, obj->style);
402 painter->SetBrush(obj->color);
404 if (obj->selected || obj->hitObject)
405 painter->SetPen(0xFF0000, Global::zoom * scaledThickness, LSDash);
411 painter->DrawLine(obj->p[0], obj->p[1]);
413 if (obj->hitPoint[0])
414 painter->DrawHandle(obj->p[0]);
416 if (obj->hitPoint[1])
417 painter->DrawHandle(obj->p[1]);
421 painter->SetBrush(QBrush(Qt::NoBrush));
422 painter->DrawEllipse(obj->p[0], obj->radius[0], obj->radius[0]);
424 if (obj->hitPoint[0])
425 painter->DrawHandle(obj->p[0]);
429 painter->DrawArc(obj->p[0], obj->radius[0], obj->angle[0], obj->angle[1]);
431 if (obj->hitPoint[0])
432 painter->DrawHandle(obj->p[0]);
434 if (obj->hitPoint[1])
435 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]));
437 if (obj->hitPoint[2])
438 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0] + obj->angle[1]), sin(obj->angle[0] + obj->angle[1])) * obj->radius[0]));
443 Dimension * d = (Dimension *)obj;
445 Vector v(d->p[0], d->p[1]);
446 double angle = v.Angle();
447 Vector unit = v.Unit();
448 Vector linePt1 = d->p[0], linePt2 = d->p[1];
450 double x1, y1, length;
452 if (d->subtype == DTLinearVert)
454 if ((angle < 0) || (angle > HALF_TAU))
456 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
457 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
458 ortho = Vector(1.0, 0);
459 angle = THREE_QTR_TAU;
463 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
464 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
465 ortho = Vector(-1.0, 0);
469 linePt1.x = linePt2.x = x1;
470 length = fabs(d->p[0].y - d->p[1].y);
472 else if (d->subtype == DTLinearHorz)
474 if ((angle < QTR_TAU) || (angle > THREE_QTR_TAU))
476 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
477 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
478 ortho = Vector(0, 1.0);
483 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
484 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
485 ortho = Vector(0, -1.0);
489 linePt1.y = linePt2.y = y1;
490 length = fabs(d->p[0].x - d->p[1].x);
492 else if (d->subtype == DTLinear)
494 angle = Vector(linePt1, linePt2).Angle();
495 ortho = Vector::Normal(linePt1, linePt2);
496 length = v.Magnitude();
499 unit = Vector(linePt1, linePt2).Unit();
501 Point p1 = linePt1 + (ortho * 10.0 * scaledThickness);
502 Point p2 = linePt2 + (ortho * 10.0 * scaledThickness);
503 Point p3 = linePt1 + (ortho * 16.0 * scaledThickness);
504 Point p4 = linePt2 + (ortho * 16.0 * scaledThickness);
505 Point p5 = d->p[0] + (ortho * 4.0 * scaledThickness);
506 Point p6 = d->p[1] + (ortho * 4.0 * scaledThickness);
509 The numbers hardcoded into here, what are they?
510 I believe they are pixels.
512 // Draw extension lines (if certain type)
513 painter->DrawLine(p3, p5);
514 painter->DrawLine(p4, p6);
516 // Calculate whether or not the arrowheads are too crowded to put
517 // inside the extension lines. 9.0 is the length of the arrowhead.
518 double t = Geometry::ParameterOfLineAndPoint(linePt1, linePt2, linePt2 - (unit * 9.0 * scaledThickness));
520 // On the screen, it's acting like this is actually 58%...
521 // This is correct, we want it to happen at > 50%
524 // Draw main dimension line + arrowheads
525 painter->DrawLine(p1, p2);
526 painter->DrawArrowhead(p1, p2, scaledThickness);
527 painter->DrawArrowhead(p2, p1, scaledThickness);
531 // Draw outside arrowheads
532 Point p7 = p1 - (unit * 9.0 * scaledThickness);
533 Point p8 = p2 + (unit * 9.0 * scaledThickness);
534 painter->DrawArrowhead(p1, p7, scaledThickness);
535 painter->DrawArrowhead(p2, p8, scaledThickness);
536 painter->DrawLine(p1, p1 - (unit * 14.0 * scaledThickness));
537 painter->DrawLine(p2, p2 + (unit * 14.0 * scaledThickness));
540 // Draw length of dimension line...
541 painter->SetFont(QFont("Arial", 8.0 * Global::zoom * scaledThickness));
542 Point ctr = p2 + (Vector(p2, p1) / 2.0);
547 dimText = QString("%1\"").arg(length);
550 double feet = (double)((int)length / 12);
551 double inches = length - (feet * 12.0);
554 dimText = QString("%1'").arg(feet);
556 dimText = QString("%1' %2\"").arg(feet).arg(inches);
559 painter->DrawAngledText(ctr, angle, dimText, scaledThickness);
565 Text * t = (Text *)obj;
566 painter->DrawTextObject(t->p[0], t->s.c_str(), scaledThickness);
579 // Containers require recursive rendering...
580 Container * c = (Container *)obj;
581 RenderObjects(painter, (*c).objects, layer);
583 //printf("Container extents: <%lf, %lf>, <%lf, %lf>\nsize: %i\n", r.l, r.t, r.r, r.b, c->objects.size());
584 // Containers also have special indicators showing they are selected
585 if (c->selected || c->hitObject)
587 Rect r = GetObjectExtents(obj);
588 painter->DrawRectCorners(r);
600 void DrawingView::AddHoveredToSelection(void)
602 std::vector<void *>::iterator i;
604 for(i=document.objects.begin(); i!=document.objects.end(); i++)
606 if (((Object *)(*i))->hovered)
607 ((Object *)(*i))->selected = true;
612 void DrawingView::GetSelection(std::vector<void *> & v)
615 std::vector<void *>::iterator i;
617 for(i=document.objects.begin(); i!=document.objects.end(); i++)
619 if (((Object *)(*i))->selected)
625 void DrawingView::GetHovered(std::vector<void *> & v)
628 std::vector<void *>::iterator i;
630 for(i=document.objects.begin(); i!=document.objects.end(); i++)
632 if (((Object *)(*i))->hovered)
634 //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"));
641 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
643 Global::screenSize = Vector(size().width(), size().height());
644 UpdateGridBackground();
648 void DrawingView::ToolHandler(int mode, Point p)
650 if (Global::tool == TTLine)
651 LineHandler(mode, p);
652 else if (Global::tool == TTCircle)
653 CircleHandler(mode, p);
654 else if (Global::tool == TTArc)
656 else if (Global::tool == TTRotate)
657 RotateHandler(mode, p);
658 else if (Global::tool == TTMirror)
659 MirrorHandler(mode, p);
663 void DrawingView::ToolDraw(Painter * painter)
665 if (Global::tool == TTLine)
667 if (Global::toolState == TSNone)
669 painter->DrawHandle(toolPoint[0]);
671 else if ((Global::toolState == TSPoint2) && shiftDown)
673 painter->DrawHandle(toolPoint[1]);
677 painter->DrawLine(toolPoint[0], toolPoint[1]);
678 painter->DrawHandle(toolPoint[1]);
680 Vector v(toolPoint[0], toolPoint[1]);
681 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
682 double absLength = v.Magnitude();
683 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
684 informativeText = text.arg(absLength).arg(absAngle);
687 else if (Global::tool == TTCircle)
689 if (Global::toolState == TSNone)
691 painter->DrawHandle(toolPoint[0]);
693 else if ((Global::toolState == TSPoint2) && shiftDown)
695 painter->DrawHandle(toolPoint[1]);
699 painter->DrawCross(toolPoint[0]);
700 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
701 painter->SetBrush(QBrush(Qt::NoBrush));
702 painter->DrawEllipse(toolPoint[0], length, length);
703 QString text = tr("Radius: %1 in.");
704 informativeText = text.arg(length);
707 else if (Global::tool == TTArc)
709 if (Global::toolState == TSNone)
711 painter->DrawHandle(toolPoint[0]);
713 else if (Global::toolState == TSPoint2)
715 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
716 painter->SetBrush(QBrush(Qt::NoBrush));
717 painter->DrawEllipse(toolPoint[0], length, length);
718 painter->DrawLine(toolPoint[0], toolPoint[1]);
719 painter->DrawHandle(toolPoint[1]);
720 QString text = tr("Radius: %1 in.");
721 informativeText = text.arg(length);
723 else if (Global::toolState == TSPoint3)
725 double angle = Vector::Angle(toolPoint[0], toolPoint[2]);
726 painter->DrawLine(toolPoint[0], toolPoint[2]);
727 painter->SetBrush(QBrush(Qt::NoBrush));
728 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
729 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
730 QString text = tr("Angle start: %1") + QChar(0x00B0);
731 informativeText = text.arg(RADIANS_TO_DEGREES * angle);
735 double angle = Vector::Angle(toolPoint[0], toolPoint[3]);
736 double span = angle - toolPoint[2].x;
741 painter->DrawLine(toolPoint[0], toolPoint[3]);
742 painter->SetBrush(QBrush(Qt::NoBrush));
743 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
744 painter->SetPen(0xFF00FF, 2.0, LSSolid);
745 painter->DrawArc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
746 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
747 QString text = tr("Arc span: %1") + QChar(0x00B0);
748 informativeText = text.arg(RADIANS_TO_DEGREES * span);
751 else if (Global::tool == TTRotate)
753 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
754 painter->DrawHandle(toolPoint[0]);
755 else if ((Global::toolState == TSPoint2) && shiftDown)
756 painter->DrawHandle(toolPoint[1]);
759 if (toolPoint[0] == toolPoint[1])
762 painter->DrawLine(toolPoint[0], toolPoint[1]);
764 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
765 QString text = QChar(0x2221) + QObject::tr(": %1");
766 informativeText = text.arg(absAngle);
769 informativeText += " (Copy)";
772 else if (Global::tool == TTMirror)
774 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
775 painter->DrawHandle(toolPoint[0]);
776 else if ((Global::toolState == TSPoint2) && shiftDown)
777 painter->DrawHandle(toolPoint[1]);
780 if (toolPoint[0] == toolPoint[1])
783 Point mirrorPoint = toolPoint[0] + Vector(toolPoint[1], toolPoint[0]);
784 painter->DrawLine(mirrorPoint, toolPoint[1]);
786 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
788 if (absAngle > 180.0)
791 QString text = QChar(0x2221) + QObject::tr(": %1");
792 informativeText = text.arg(absAngle);
795 informativeText += " (Copy)";
801 void DrawingView::LineHandler(int mode, Point p)
806 if (Global::toolState == TSNone)
813 if (Global::toolState == TSNone)
820 if (Global::toolState == TSNone)
822 Global::toolState = TSPoint2;
823 // Prevent spurious line from drawing...
824 toolPoint[1] = toolPoint[0];
826 else if ((Global::toolState == TSPoint2) && shiftDown)
828 // Key override is telling us to make a new line, not continue the
830 toolPoint[0] = toolPoint[1];
834 Line * l = new Line(toolPoint[0], toolPoint[1]);
835 l->layer = Global::activeLayer;
836 document.objects.push_back(l);
837 toolPoint[0] = toolPoint[1];
843 void DrawingView::CircleHandler(int mode, Point p)
848 if (Global::toolState == TSNone)
855 if (Global::toolState == TSNone)
862 if (Global::toolState == TSNone)
864 Global::toolState = TSPoint2;
865 // Prevent spurious line from drawing...
866 toolPoint[1] = toolPoint[0];
868 else if ((Global::toolState == TSPoint2) && shiftDown)
870 // Key override is telling us to make a new line, not continue the
872 toolPoint[0] = toolPoint[1];
876 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
877 Circle * c = new Circle(toolPoint[0], length);
878 c->layer = Global::activeLayer;
879 document.objects.push_back(c);
880 toolPoint[0] = toolPoint[1];
881 Global::toolState = TSNone;
887 void DrawingView::ArcHandler(int mode, Point p)
892 if (Global::toolState == TSNone)
894 else if (Global::toolState == TSPoint2)
896 else if (Global::toolState == TSPoint3)
903 if (Global::toolState == TSNone)
905 else if (Global::toolState == TSPoint2)
907 else if (Global::toolState == TSPoint3)
914 if (Global::toolState == TSNone)
916 // Prevent spurious line from drawing...
917 toolPoint[1] = toolPoint[0];
918 Global::toolState = TSPoint2;
920 else if (Global::toolState == TSPoint2)
924 // Key override is telling us to start circle at new center, not
925 // continue the current one.
926 toolPoint[0] = toolPoint[1];
930 // Set the radius in toolPoint[1].x
931 toolPoint[1].x = Vector::Magnitude(toolPoint[0], toolPoint[1]);
932 Global::toolState = TSPoint3;
934 else if (Global::toolState == TSPoint3)
936 // Set the angle in toolPoint[2].x
937 toolPoint[2].x = Vector::Angle(toolPoint[0], toolPoint[2]);
938 Global::toolState = TSPoint4;
942 double endAngle = Vector::Angle(toolPoint[0], toolPoint[3]);
943 double span = endAngle - toolPoint[2].x;
948 Arc * arc = new Arc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
949 arc->layer = Global::activeLayer;
950 document.objects.push_back(arc);
951 Global::toolState = TSNone;
957 void DrawingView::RotateHandler(int mode, Point p)
962 if (Global::toolState == TSNone)
965 SavePointsFrom(select, toolScratch);
966 Global::toolState = TSPoint1;
968 else if (Global::toolState == TSPoint1)
975 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
977 else if (Global::toolState == TSPoint2)
984 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
985 std::vector<void *>::iterator j = select.begin();
986 std::vector<Object>::iterator i = toolScratch.begin();
988 for(; i!=toolScratch.end(); i++, j++)
991 Point p1 = Geometry::RotatePointAroundPoint(obj.p[0], toolPoint[0], angle);
992 Point p2 = Geometry::RotatePointAroundPoint(obj.p[1], toolPoint[0], angle);
993 Object * obj2 = (Object *)(*j);
997 if (obj.type == OTArc)
999 obj2->angle[0] = obj.angle[0] + angle;
1001 if (obj2->angle[0] > TAU)
1002 obj2->angle[0] -= TAU;
1009 if (Global::toolState == TSPoint1)
1011 Global::toolState = TSPoint2;
1012 // Prevent spurious line from drawing...
1013 toolPoint[1] = toolPoint[0];
1015 else if ((Global::toolState == TSPoint2) && shiftDown)
1017 // Key override is telling us to make a new line, not continue the
1019 toolPoint[0] = toolPoint[1];
1023 // Either we're finished with our rotate, or we're stamping a copy.
1026 // Stamp a copy of the selection at the current rotation & bail
1027 std::vector<void *> temp;
1028 CopyObjects(select, temp);
1029 ClearSelected(temp);
1030 AddObjectsTo(document.objects, temp);
1031 RestorePointsTo(select, toolScratch);
1036 Global::toolState = TSPoint1;
1037 SavePointsFrom(select, toolScratch);
1042 // Reset the selection if shift held down...
1044 RestorePointsTo(select, toolScratch);
1048 // Reset selection when key is let up
1050 RotateHandler(ToolMouseMove, toolPoint[1]);
1054 RestorePointsTo(select, toolScratch);
1059 void DrawingView::MirrorHandler(int mode, Point p)
1064 if (Global::toolState == TSNone)
1067 SavePointsFrom(select, toolScratch);
1068 Global::toolState = TSPoint1;
1070 else if (Global::toolState == TSPoint1)
1077 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1079 else if (Global::toolState == TSPoint2)
1086 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1087 std::vector<void *>::iterator j = select.begin();
1088 std::vector<Object>::iterator i = toolScratch.begin();
1090 for(; i!=toolScratch.end(); i++, j++)
1093 Point p1 = Geometry::MirrorPointAroundLine(obj.p[0], toolPoint[0], toolPoint[1]);
1094 Point p2 = Geometry::MirrorPointAroundLine(obj.p[1], toolPoint[0], toolPoint[1]);
1095 Object * obj2 = (Object *)(*j);
1099 if (obj.type == OTArc)
1101 // This is 2*mirror angle - obj angle - obj span
1102 obj2->angle[0] = (2.0 * angle) - obj.angle[0] - obj.angle[1];
1104 if (obj2->angle[0] > TAU)
1105 obj2->angle[0] -= TAU;
1112 if (Global::toolState == TSPoint1)
1114 Global::toolState = TSPoint2;
1115 // Prevent spurious line from drawing...
1116 toolPoint[1] = toolPoint[0];
1118 else if ((Global::toolState == TSPoint2) && shiftDown)
1120 // Key override is telling us to make a new line, not continue the
1122 toolPoint[0] = toolPoint[1];
1126 // Either we're finished with our rotate, or we're stamping a copy.
1129 // Stamp a copy of the selection at the current rotation & bail
1130 std::vector<void *> temp;
1131 CopyObjects(select, temp);
1132 ClearSelected(temp);
1133 AddObjectsTo(document.objects, temp);
1134 RestorePointsTo(select, toolScratch);
1139 Global::toolState = TSPoint1;
1140 SavePointsFrom(select, toolScratch);
1145 // Reset the selection if shift held down...
1147 RestorePointsTo(select, toolScratch);
1151 // Reset selection when key is let up
1153 MirrorHandler(ToolMouseMove, toolPoint[1]);
1157 RestorePointsTo(select, toolScratch);
1162 void DrawingView::mousePressEvent(QMouseEvent * event)
1164 if (event->button() == Qt::LeftButton)
1166 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1168 // Handle tool processing, if any
1171 if (hoveringIntersection)
1172 point = intersectionPoint;
1173 else if (Global::snapToGrid)
1174 point = SnapPointToGrid(point);
1176 //Also, may want to figure out if hovering over a snap point on an
1177 //object, snap to grid if not.
1178 // Snap to object point if valid...
1179 // if (Global::snapPointIsValid)
1180 // point = Global::snapPoint;
1182 ToolHandler(ToolMouseDown, point);
1186 // Clear the selection only if CTRL isn't being held on click
1188 ClearSelected(document.objects);
1189 // ClearSelection();
1191 // If any objects are being hovered on click, add them to the selection
1195 AddHoveredToSelection();
1196 update(); // needed??
1197 GetHovered(hover); // prolly needed
1198 dragged = (Object *)hover[0];
1199 draggingObject = true;
1201 // Needed for grab & moving objects
1202 // We do it *after*... why? (doesn't seem to confer any advantage...)
1203 if (hoveringIntersection)
1204 oldPoint = intersectionPoint;
1205 else if (Global::snapToGrid)
1206 oldPoint = SnapPointToGrid(point);
1211 // Didn't hit any object and not using a tool, so do a selection rectangle
1212 Global::selectionInProgress = true;
1213 Global::selection.setTopLeft(QPointF(point.x, point.y));
1214 Global::selection.setBottomRight(QPointF(point.x, point.y));
1216 else if (event->button() == Qt::MiddleButton)
1219 oldPoint = Vector(event->x(), event->y());
1220 // Should also change the mouse pointer as well...
1221 setCursor(Qt::SizeAllCursor);
1226 void DrawingView::mouseMoveEvent(QMouseEvent * event)
1228 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1229 Global::selection.setBottomRight(QPointF(point.x, point.y));
1230 // Only needs to be done here, as mouse down is always preceded by movement
1231 Global::snapPointIsValid = false;
1232 hoveringIntersection = false;
1235 if (event->buttons() & Qt::MiddleButton)
1237 point = Vector(event->x(), event->y());
1238 // Since we're using Qt coords for scrolling, we have to adjust them
1239 // here to conform to Cartesian coords, since the origin is using
1241 Vector delta(oldPoint, point);
1242 delta /= Global::zoom;
1244 Global::origin -= delta;
1246 UpdateGridBackground();
1252 // If we're doing a selection rect, see if any objects are engulfed by it
1253 // (implies left mouse button held down)
1254 if (Global::selectionInProgress)
1256 CheckObjectBounds();
1261 // Do object hit testing...
1262 bool needUpdate = HitTestObjects(point);
1264 // Check for multi-hover...
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 (Global::snapToGrid)
1313 point = SnapPointToGrid(point);
1315 HandleObjectMovement(point);
1321 // Do tool handling, if any are active...
1324 if (hoveringIntersection)
1325 point = intersectionPoint;
1326 else if (Global::snapToGrid)
1327 point = SnapPointToGrid(point);
1329 ToolHandler(ToolMouseMove, point);
1332 // This is used to draw the tool crosshair...
1335 if (needUpdate || Global::tool)
1340 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
1342 if (event->button() == Qt::LeftButton)
1344 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
1345 //could set it up to use the document's update function (assumes that all object
1346 //updates are being reported correctly:
1347 // if (document.NeedsUpdate())
1348 // Do an update if collided with at least *one* object in the document
1354 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1355 ToolHandler(ToolMouseUp, point);
1359 if (Global::selectionInProgress)
1360 Global::selectionInProgress = false;
1362 informativeText.clear();
1363 // Should we be doing this automagically? Hmm...
1364 // Clear our vectors
1369 std::vector<void *>::iterator i;
1371 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1373 if (((Object *)(*i))->selected)
1374 select.push_back(*i);
1377 draggingObject = false;
1379 else if (event->button() == Qt::MiddleButton)
1382 setCursor(Qt::ArrowCursor);
1387 void DrawingView::wheelEvent(QWheelEvent * event)
1389 double zoomFactor = 1.25;
1390 QSize sizeWin = size();
1391 Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
1392 center = Painter::QtToCartesianCoords(center);
1394 // This is not centering for some reason. Need to figure out why. :-/
1395 if (event->delta() > 0)
1397 Vector newOrigin = center - ((center - Global::origin) / zoomFactor);
1398 Global::origin = newOrigin;
1399 Global::zoom *= zoomFactor;
1403 Vector newOrigin = center + ((-center + Global::origin) * zoomFactor);
1404 Global::origin = newOrigin;
1405 Global::zoom /= zoomFactor;
1408 // Global::gridSpacing = gridPixels / Painter::zoom;
1409 // UpdateGridBackground();
1410 SetGridSize(Global::gridSpacing * Global::zoom);
1412 // zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Global::gridSpacing));
1416 void DrawingView::keyPressEvent(QKeyEvent * event)
1418 bool oldShift = shiftDown;
1419 bool oldCtrl = ctrlDown;
1421 if (event->key() == Qt::Key_Shift)
1423 else if (event->key() == Qt::Key_Control)
1426 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1429 ToolHandler(ToolKeyDown, Point(0, 0));
1434 if (select.size() > 0)
1436 if (event->key() == Qt::Key_Up)
1438 TranslateObjects(select, Point(0, +1.0));
1441 else if (event->key() == Qt::Key_Down)
1443 TranslateObjects(select, Point(0, -1.0));
1446 else if (event->key() == Qt::Key_Right)
1448 TranslateObjects(select, Point(+1.0, 0));
1451 else if (event->key() == Qt::Key_Left)
1453 TranslateObjects(select, Point(-1.0, 0));
1460 void DrawingView::keyReleaseEvent(QKeyEvent * event)
1462 bool oldShift = shiftDown;
1463 bool oldCtrl = ctrlDown;
1465 if (event->key() == Qt::Key_Shift)
1467 else if (event->key() == Qt::Key_Control)
1470 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1473 ToolHandler(ToolKeyUp, Point(0, 0));
1481 // This looks strange, but it's really quite simple: We want a point that's
1482 // more than half-way to the next grid point to snap there while conversely we
1483 // want a point that's less than half-way to to the next grid point then snap
1484 // to the one before it. So we add half of the grid spacing to the point, then
1485 // divide by it so that we can remove the fractional part, then multiply it
1486 // back to get back to the correct answer.
1488 Point DrawingView::SnapPointToGrid(Point point)
1490 point += Global::gridSpacing / 2.0; // *This* adds to Z!!!
1491 point /= Global::gridSpacing;
1492 point.x = floor(point.x);//need to fix this for negative numbers...
1493 point.y = floor(point.y);
1494 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
1495 point *= Global::gridSpacing;
1500 Rect DrawingView::GetObjectExtents(Object * obj)
1502 // Default to empty rect, if object checks below fail for some reason
1509 rect = Rect(obj->p[0], obj->p[1]);
1514 rect = Rect(obj->p[0], obj->p[0]);
1515 rect.Expand(obj->radius[0]);
1520 Arc * a = (Arc *)obj;
1522 double start = a->angle[0];
1523 double end = start + a->angle[1];
1524 rect = Rect(Point(cos(start), sin(start)), Point(cos(end), sin(end)));
1526 // If the end of the arc is before the beginning, add 360 degrees to it
1530 // Adjust the bounds depending on which axes are crossed
1531 if ((start < QTR_TAU) && (end > QTR_TAU))
1534 if ((start < HALF_TAU) && (end > HALF_TAU))
1537 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
1540 if ((start < TAU) && (end > TAU))
1543 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
1546 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
1549 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
1552 rect *= a->radius[0];
1553 rect.Translate(a->p[0]);
1559 Container * c = (Container *)obj;
1560 std::vector<void *>::iterator i = c->objects.begin();
1561 rect = GetObjectExtents((Object *)*i);
1564 for(; i!=c->objects.end(); i++)
1565 rect |= GetObjectExtents((Object *)*i);
1575 void DrawingView::CheckObjectBounds(void)
1577 std::vector<void *>::iterator i;
1579 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1581 Object * obj = (Object *)(*i);
1582 obj->selected = false;
1588 Line * l = (Line *)obj;
1590 if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
1597 Circle * c = (Circle *)obj;
1599 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]))
1606 Arc * a = (Arc *)obj;
1608 double start = a->angle[0];
1609 double end = start + a->angle[1];
1610 QPointF p1(cos(start), sin(start));
1611 QPointF p2(cos(end), sin(end));
1612 QRectF bounds(p1, p2);
1615 // Swap X/Y coordinates if they're backwards...
1616 if (bounds.left() > bounds.right())
1618 double temp = bounds.left();
1619 bounds.setLeft(bounds.right());
1620 bounds.setRight(temp);
1623 if (bounds.bottom() > bounds.top())
1625 double temp = bounds.bottom();
1626 bounds.setBottom(bounds.top());
1627 bounds.setTop(temp);
1630 // Doesn't work as advertised! For shame!
1631 bounds = bounds.normalized();
1634 // If the end of the arc is before the beginning, add 360 degrees to it
1638 // Adjust the bounds depending on which axes are crossed
1639 if ((start < QTR_TAU) && (end > QTR_TAU))
1642 if ((start < HALF_TAU) && (end > HALF_TAU))
1643 bounds.setLeft(-1.0);
1645 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
1646 bounds.setBottom(-1.0);
1648 if ((start < TAU) && (end > TAU))
1649 bounds.setRight(1.0);
1651 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
1654 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
1655 bounds.setLeft(-1.0);
1657 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
1658 bounds.setBottom(-1.0);
1660 bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
1661 bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
1662 bounds.translate(a->p[0].x, a->p[0].y);
1664 if (Global::selection.contains(bounds))
1676 bool DrawingView::HitTestObjects(Point point)
1678 std::vector<void *>::iterator i;
1680 bool needUpdate = false;
1682 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1684 Object * obj = (Object *)(*i);
1686 // If we're seeing the object we're dragging, skip it
1687 if (draggingObject && (obj == dragged))
1690 if (HitTest(obj, point))
1696 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
1697 emit(ObjectHovered(obj));
1705 bool DrawingView::HitTest(Object * obj, Point point)
1707 bool needUpdate = false;
1713 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
1714 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
1715 Vector lineSegment = obj->p[1] - obj->p[0];
1716 Vector v1 = point - obj->p[0];
1717 Vector v2 = point - obj->p[1];
1718 double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
1722 distance = v1.Magnitude();
1724 distance = v2.Magnitude();
1726 // distance = ?Det?(ls, v1) / |ls|
1727 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
1728 / lineSegment.Magnitude());
1730 if ((v1.Magnitude() * Global::zoom) < 8.0)
1731 obj->hitPoint[0] = true;
1732 else if ((v2.Magnitude() * Global::zoom) < 8.0)
1733 obj->hitPoint[1] = true;
1734 else if ((distance * Global::zoom) < 5.0)
1735 obj->hitObject = true;
1737 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
1739 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
1746 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
1747 obj->hitPoint[0] = obj->hitObject = false;
1748 double length = Vector::Magnitude(obj->p[0], point);
1750 if ((length * Global::zoom) < 8.0)
1751 obj->hitPoint[0] = true;
1752 else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
1753 obj->hitObject = true;
1755 obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
1757 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
1764 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
1765 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
1766 double length = Vector::Magnitude(obj->p[0], point);
1767 double angle = Vector::Angle(obj->p[0], point);
1769 // Make sure we get the angle in the correct spot
1770 if (angle < obj->angle[0])
1773 // Get the span that we're pointing at...
1774 double span = angle - obj->angle[0];
1776 // N.B.: Still need to hit test the arc start & arc span handles...
1777 double spanAngle = obj->angle[0] + obj->angle[1];
1778 Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
1779 Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
1780 double length2 = Vector::Magnitude(point, handle1);
1781 double length3 = Vector::Magnitude(point, handle2);
1783 if ((length * Global::zoom) < 8.0)
1784 obj->hitPoint[0] = true;
1785 else if ((length2 * Global::zoom) < 8.0)
1786 obj->hitPoint[1] = true;
1787 else if ((length3 * Global::zoom) < 8.0)
1788 obj->hitPoint[2] = true;
1789 else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
1790 obj->hitObject = true;
1792 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
1794 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
1801 // Containers must be recursively tested...
1802 Container * c = (Container *)obj;
1803 c->hitObject = false;
1805 std::vector<void *>::iterator i;
1807 for(i=c->objects.begin(); i!=c->objects.end(); i++)
1809 Object * cObj = (Object *)*i;
1811 if (HitTest(cObj, point))
1814 if (cObj->hitObject == true)
1815 c->hitObject = true;
1817 if (cObj->hovered == true)
1831 void DrawingView::HandleObjectMovement(Point point)
1833 Point delta = point - oldPoint;
1834 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
1835 // Object * obj = (Object *)hover[0];
1836 Object * obj = dragged;
1837 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
1838 //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"));
1843 if (obj->hitPoint[0])
1845 else if (obj->hitPoint[1])
1847 else if (obj->hitObject)
1855 if (obj->hitPoint[0])
1857 else if (obj->hitObject)
1859 //this doesn't work. we need to save this on mouse down for this to work correctly!
1860 // double oldRadius = obj->radius[0];
1861 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
1863 QString text = QObject::tr("Radius: %1");//\nScale: %2%");
1864 informativeText = text.arg(obj->radius[0], 0, 'd', 4);//.arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0);
1869 if (obj->hitPoint[0])
1871 else if (obj->hitPoint[1])
1873 // Change the Arc's span (handle #1)
1876 double angle = Vector::Angle(obj->p[0], point);
1877 double delta = angle - obj->angle[0];
1882 obj->angle[1] -= delta;
1883 obj->angle[0] = angle;
1885 if (obj->angle[1] < 0)
1886 obj->angle[1] += TAU;
1888 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
1889 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);
1893 double angle = Vector::Angle(obj->p[0], point);
1894 obj->angle[0] = angle;
1895 QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
1896 informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 4);
1898 else if (obj->hitPoint[2])
1900 // Change the Arc's span (handle #2)
1903 double angle = Vector::Angle(obj->p[0], point);
1904 obj->angle[1] = angle - obj->angle[0];
1906 if (obj->angle[1] < 0)
1907 obj->angle[1] += TAU;
1909 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
1910 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);
1914 double angle = Vector::Angle(obj->p[0], point);
1915 obj->angle[0] = angle - obj->angle[1];
1917 if (obj->angle[0] < 0)
1918 obj->angle[0] += TAU;
1920 QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
1921 informativeText = text.arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 4);
1923 else if (obj->hitObject)
1930 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
1931 QString text = QObject::tr("Radius: %1");
1932 informativeText = text.arg(obj->radius[0], 0, 'd', 4);
1937 // This is shitty, but works for now until I can code up something
1939 TranslateObject(obj, delta);