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 //Container DrawingView::document(Vector(0, 0));
48 DrawingView::DrawingView(QWidget * parent/*= NULL*/): QWidget(parent),
49 // The value in the settings file will override this.
50 useAntialiasing(true), numSelected(0), numHovered(0), shiftDown(false),
52 gridBackground(BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE),
53 scale(1.0), offsetX(-10), offsetY(-10),
54 gridPixels(0), collided(false), hoveringIntersection(false)
56 // document.isTopLevelContainer = true;
57 //wtf? doesn't work except in c++11??? document = { 0 };
58 setBackgroundRole(QPalette::Base);
59 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
61 Global::gridSpacing = 12.0; // In base units (inch is default)
64 Line * line = new Line(Vector(5, 5), Vector(50, 40), &document);
66 document.Add(new Line(Vector(50, 40), Vector(10, 83), &document));
67 document.Add(new Line(Vector(10, 83), Vector(17, 2), &document));
68 document.Add(new Circle(Vector(100, 100), 36, &document));
69 document.Add(new Circle(Vector(50, 150), 49, &document));
70 document.Add(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3, &document)),
71 document.Add(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5, &document));
73 Dimension * dimension = new Dimension(Vector(0, 0), Vector(0, 0), DTLinear, &document);
74 line->SetDimensionOnLine(dimension);
75 document.Add(dimension);
77 // Alternate way to do the above...
78 line->SetDimensionOnLine();
81 Line * line = new Line;//(Vector(5, 5), Vector(50, 40), &document);
82 line->p[0] = Vector(5, 5);
83 line->p[1] = Vector(50, 40);
85 line->thickness = 2.0;
87 line->color = 0xFF7F00;
89 document.objects.push_back(line);
90 document.objects.push_back(new Line(Vector(50, 40), Vector(10, 83)));
91 document.objects.push_back(new Line(Vector(10, 83), Vector(17, 2)));
92 document.objects.push_back(new Circle(Vector(100, 100), 36));
93 document.objects.push_back(new Circle(Vector(50, 150), 49));
94 document.objects.push_back(new Arc(Vector(300, 300), 32, TAU / 8.0, TAU * 0.65)),
95 document.objects.push_back(new Arc(Vector(200, 200), 60, TAU / 4.0, TAU * 0.75));
96 document.objects.push_back(new Dimension(Vector(50, 40), Vector(5, 5)));
97 document.objects.push_back(new Text(Vector(10, 83), "Here is some awesome text!"));
101 Here we set the grid size in pixels--12 in this case. Initially, we have our
102 zoom set to make this represent 12 inches at a zoom factor of 25%. (This is
103 arbitrary.) So, to be able to decouple the grid size from the zoom, we need
104 to be able to set the size of the background grid (which we do here at an
105 arbitrary 12 pixels) to anything we want (within reason, of course :-).
107 The drawing enforces the grid spacing through the drawing->gridSpacing variable.
109 drawing->gridSpacing = 12.0 / Global::zoom;
111 Global::zoom is the zoom factor for the drawing, and all mouse clicks are
112 translated to Cartesian coordinates through this. (Initially, Global::zoom is
113 set to 1.0. SCREEN_ZOOM is set to 1.0/4.0.)
115 Really, the 100% zoom level can be set at *any* zoom level, it's more of a
116 convenience function than any measure of absolutes. Doing things that way we
117 could rid ourselves of the whole SCREEN_ZOOM parameter and all the attendant
118 shittiness that comes with it.
120 However, it seems that SCREEN_ZOOM is used to make text and arrow sizes show up
121 a certain way, which means we should probably create something else in those
122 objects to take its place--like some kind of scale factor. This would seem to
123 imply that certain point sizes actually *do* tie things like fonts to absolute
124 sizes on the screen, but not necessarily because you could have an inch scale
125 with text that is quite small relative to other objects on the screen, which
126 currently you have to zoom in to see (and which blows up the text). Point sizes
127 in an application like this are a bit meaningless; even though an inch is an
128 inch regardless of the zoom level a piece of text can be larger or smaller than
129 this. Maybe this is the case for having a base unit and basing point sizes off
132 Here's what's been figured out. Global::zoom is simply the ratio of pixels to
133 base units. What that means is that if you have a 12px grid with a 6" grid size
134 (& base unit of "inches"), Global::zoom becomes 12px / 6" = 2.0 px/in.
136 Dimensions now have a "size" parameter to set their absolute size in relation
137 to the base unit. ATM, the arrows are drawn in pixels, but also scaled by
138 Global::zoom *and* size. Same with the dimension text; it's drawn at 10pt and
139 scaled the same way as the arrowheads.
141 Need a way to scale line widths as well. :-/ Shouldn't be too difficult, just
142 need a thickness parameter similar to the "size" param for dimensions. (And now
146 SetGridSize(12); // This is in pixels
150 void DrawingView::SetGridSize(uint32_t size)
153 if (size == gridPixels)
156 // Recreate the background bitmap
158 QPainter pmp(&gridBackground);
159 pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
160 pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
162 for(int i=0; i<(BACKGROUND_MAX_SIZE-1); i+=gridPixels)
164 pmp.drawLine(i, 0, i, BACKGROUND_MAX_SIZE - 1);
165 pmp.drawLine(0, i, BACKGROUND_MAX_SIZE - 1, i);
170 // Set up new BG brush & zoom level (pixels per base unit)
171 Global::zoom = gridPixels / Global::gridSpacing;
172 UpdateGridBackground();
176 void DrawingView::UpdateGridBackground(void)
178 // Transform the origin to Qt coordinates
179 Vector pixmapOrigin = Painter::CartesianToQtCoords(Vector());
180 int x = (int)pixmapOrigin.x;
181 int y = (int)pixmapOrigin.y;
182 // Use mod arithmetic to grab the correct swatch of background
184 Negative numbers still screw it up... Need to think about what we're
185 trying to do here. The fact that it worked with 72 seems to have been pure luck.
186 It seems the problem is negative numbers: We can't let that happen.
187 When taking away the zero, it pops over 1 px at zero, then goes about 1/2 a
190 The bitmap looks like this:
200 @ x = 1, we want it to look like:
202 -+---+---+---+---+---
205 -+---+---+---+---+---
210 Which means we need to grab the sample from x = 3. @ x = -1:
220 Which means we need to grab the sample from x = 1. Which means we have to take
221 the mirror of the modulus of gridPixels.
223 Doing a mod of a negative number is problematic: 1st, the compiler converts the
224 negative number to an unsigned int, then it does the mod. Gets you wrong answers
225 most of the time, unless you use a power of 2. :-P So what we do here is just
226 take the modulus of the negation, which means we don't have to worry about
229 The positive case looks gruesome (and it is) but it boils down to this: We take
230 the modulus of the X coordinate, then mirror it by subtraction from the
231 maximum (in this case, gridPixels). This gives us a number in the range of 1 to
232 gridPixels. But we need the case where the result equalling gridPixels to be
233 zero; so we do another modulus operation on the result to achieve this.
238 x = (gridPixels - (x % gridPixels)) % gridPixels;
243 y = (gridPixels - (y % gridPixels)) % gridPixels;
245 // Here we grab a section of the bigger pixmap, so that the background
246 // *looks* like it's scrolling...
247 QPixmap pm = gridBackground.copy(x, y, gridPixels, gridPixels);
248 QPalette pal = palette();
249 pal.setBrush(backgroundRole(), QBrush(pm));
250 setAutoFillBackground(true);
256 // Basically, we just make a single pass through the Container. If the layer #
257 // is less than the layer # being deleted, then do nothing. If the layer # is
258 // equal to the layer # being deleted, then delete the object. If the layer #
259 // is greater than the layer # being deleted, then set the layer # to its layer
262 void DrawingView::DeleteCurrentLayer(int layer)
264 //printf("DrawingView::DeleteCurrentLayer(): currentLayer = %i\n", layer);
265 std::vector<void *>::iterator i = document.objects.begin();
267 while (i != document.objects.end())
269 Object * obj = (Object *)(*i);
271 if (obj->layer < layer)
273 else if (obj->layer == layer)
275 document.objects.erase(i);
285 // We've just done a destructive action, so update the screen!
290 void DrawingView::HandleLayerToggle(void)
292 // A layer's visibility was toggled, so update the screen...
298 // A layer was moved up or down in the layer list, so we have to swap the
299 // document's object's layer numbers in the layers that were swapped.
301 void DrawingView::HandleLayerSwap(int layer1, int layer2)
303 //printf("DrawingView: Swapping layers %i and %i.\n", layer1, layer2);
304 std::vector<void *>::iterator i;
306 for(i=document.objects.begin(); i!=document.objects.end(); i++)
308 Object * obj = (Object *)(*i);
310 if (obj->layer == layer1)
312 else if (obj->layer == layer2)
318 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
320 // This is undoing the transform, e.g. going from client coords to local
321 // coords. In essence, the height - y is height + (y * -1), the (y * -1)
322 // term doing the conversion of the y-axis from increasing bottom to top.
323 return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
327 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
329 // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
330 // No voodoo here, it's just grouped wrong to see it. It should be:
331 // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive [why? we use -offsetX after all]
332 return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
336 void DrawingView::paintEvent(QPaintEvent * /*event*/)
338 QPainter qtPainter(this);
339 Painter painter(&qtPainter);
342 qtPainter.setRenderHint(QPainter::Antialiasing);
344 Global::viewportHeight = size().height();
346 // Draw coordinate axes
347 painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
348 painter.DrawLine(0, -16384, 0, 16384);
349 painter.DrawLine(-16384, 0, 16384, 0);
351 // Do object rendering...
352 for(int i=0; i<Global::numLayers; i++)
354 if (Global::layerHidden[i] == false)
355 RenderObjects(&painter, document.objects, i);
358 // Do tool rendering, if any...
361 painter.SetPen(QPen(QColor(200, 100, 0, 255), 1.0, Qt::DashLine));
362 painter.DrawCrosshair(oldPoint);
366 // Do selection rectangle rendering, if any
367 if (Global::selectionInProgress)
369 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
370 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
371 painter.DrawRect(Global::selection);
374 if (hoveringIntersection)
375 painter.DrawHandle(intersectionPoint);
377 if (!informativeText.isEmpty())
378 painter.DrawInformativeText(informativeText);
383 // Renders objects in the passed in vector
385 void DrawingView::RenderObjects(Painter * painter, std::vector<void *> & v, int layer)
387 std::vector<void *>::iterator i;
389 for(i=v.begin(); i!=v.end(); i++)
391 Object * obj = (Object *)(*i);
392 float scaledThickness = Global::scale * obj->thickness;
394 // If the object isn't on the current layer being drawn, skip it
395 if (obj->layer != layer)
398 if ((Global::tool == TTRotate) && ctrlDown && obj->selected)
400 painter->SetPen(0x00FF00, 2.0, LSSolid);
404 painter->SetPen(obj->color, Global::zoom * scaledThickness, obj->style);
405 painter->SetBrush(obj->color);
407 if (obj->selected || obj->hitObject)
408 painter->SetPen(0xFF0000, Global::zoom * scaledThickness, LSDash);
414 painter->DrawLine(obj->p[0], obj->p[1]);
416 if (obj->hitPoint[0])
417 painter->DrawHandle(obj->p[0]);
419 if (obj->hitPoint[1])
420 painter->DrawHandle(obj->p[1]);
424 painter->SetBrush(QBrush(Qt::NoBrush));
425 painter->DrawEllipse(obj->p[0], obj->radius[0], obj->radius[0]);
427 if (obj->hitPoint[0])
428 painter->DrawHandle(obj->p[0]);
432 painter->DrawArc(obj->p[0], obj->radius[0], obj->angle[0], obj->angle[1]);
434 if (obj->hitPoint[0])
435 painter->DrawHandle(obj->p[0]);
437 if (obj->hitPoint[1])
438 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]));
440 if (obj->hitPoint[2])
441 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0] + obj->angle[1]), sin(obj->angle[0] + obj->angle[1])) * obj->radius[0]));
446 Dimension * d = (Dimension *)obj;
448 Vector v(d->p[0], d->p[1]);
449 double angle = v.Angle();
450 Vector unit = v.Unit();
451 Vector linePt1 = d->p[0], linePt2 = d->p[1];
453 double x1, y1, length;
455 if (d->subtype == DTLinearVert)
457 if ((angle < 0) || (angle > HALF_TAU))
459 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
460 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
461 ortho = Vector(1.0, 0);
462 angle = THREE_QTR_TAU;
466 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
467 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
468 ortho = Vector(-1.0, 0);
472 linePt1.x = linePt2.x = x1;
473 length = fabs(d->p[0].y - d->p[1].y);
475 else if (d->subtype == DTLinearHorz)
477 if ((angle < QTR_TAU) || (angle > THREE_QTR_TAU))
479 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
480 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
481 ortho = Vector(0, 1.0);
486 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
487 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
488 ortho = Vector(0, -1.0);
492 linePt1.y = linePt2.y = y1;
493 length = fabs(d->p[0].x - d->p[1].x);
495 else if (d->subtype == DTLinear)
497 angle = Vector(linePt1, linePt2).Angle();
498 ortho = Vector::Normal(linePt1, linePt2);
499 length = v.Magnitude();
502 unit = Vector(linePt1, linePt2).Unit();
504 Point p1 = linePt1 + (ortho * 10.0 * scaledThickness);
505 Point p2 = linePt2 + (ortho * 10.0 * scaledThickness);
506 Point p3 = linePt1 + (ortho * 16.0 * scaledThickness);
507 Point p4 = linePt2 + (ortho * 16.0 * scaledThickness);
508 Point p5 = d->p[0] + (ortho * 4.0 * scaledThickness);
509 Point p6 = d->p[1] + (ortho * 4.0 * scaledThickness);
512 The numbers hardcoded into here, what are they?
513 I believe they are pixels.
515 // Draw extension lines (if certain type)
516 painter->DrawLine(p3, p5);
517 painter->DrawLine(p4, p6);
519 // Calculate whether or not the arrowheads are too crowded to put
520 // inside the extension lines. 9.0 is the length of the arrowhead.
521 double t = Geometry::ParameterOfLineAndPoint(linePt1, linePt2, linePt2 - (unit * 9.0 * scaledThickness));
523 // On the screen, it's acting like this is actually 58%...
524 // This is correct, we want it to happen at > 50%
527 // Draw main dimension line + arrowheads
528 painter->DrawLine(p1, p2);
529 painter->DrawArrowhead(p1, p2, scaledThickness);
530 painter->DrawArrowhead(p2, p1, scaledThickness);
534 // Draw outside arrowheads
535 Point p7 = p1 - (unit * 9.0 * scaledThickness);
536 Point p8 = p2 + (unit * 9.0 * scaledThickness);
537 painter->DrawArrowhead(p1, p7, scaledThickness);
538 painter->DrawArrowhead(p2, p8, scaledThickness);
539 painter->DrawLine(p1, p1 - (unit * 14.0 * scaledThickness));
540 painter->DrawLine(p2, p2 + (unit * 14.0 * scaledThickness));
543 // Draw length of dimension line...
544 painter->SetFont(QFont("Arial", 8.0 * Global::zoom * scaledThickness));
545 Point ctr = p2 + (Vector(p2, p1) / 2.0);
550 dimText = QString("%1\"").arg(length);
553 double feet = (double)((int)length / 12);
554 double inches = length - (feet * 12.0);
557 dimText = QString("%1'").arg(feet);
559 dimText = QString("%1' %2\"").arg(feet).arg(inches);
562 painter->DrawAngledText(ctr, angle, dimText, scaledThickness);
568 Text * t = (Text *)obj;
569 painter->DrawTextObject(t->p[0], t->s.c_str(), scaledThickness);
579 void DrawingView::AddHoveredToSelection(void)
581 std::vector<void *>::iterator i;
583 for(i=document.objects.begin(); i!=document.objects.end(); i++)
585 if (((Object *)(*i))->hovered)
586 ((Object *)(*i))->selected = true;
591 void DrawingView::GetSelection(std::vector<void *> & v)
594 std::vector<void *>::iterator i;
596 for(i=document.objects.begin(); i!=document.objects.end(); i++)
598 if (((Object *)(*i))->selected)
604 void DrawingView::GetHovered(std::vector<void *> & v)
607 std::vector<void *>::iterator i;
609 for(i=document.objects.begin(); i!=document.objects.end(); i++)
611 if (((Object *)(*i))->hovered)
613 //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"));
620 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
622 Global::screenSize = Vector(size().width(), size().height());
623 UpdateGridBackground();
627 void DrawingView::ToolHandler(int mode, Point p)
629 if (Global::tool == TTLine)
630 LineHandler(mode, p);
631 else if (Global::tool == TTCircle)
632 CircleHandler(mode, p);
633 else if (Global::tool == TTArc)
635 else if (Global::tool == TTRotate)
636 RotateHandler(mode, p);
637 else if (Global::tool == TTMirror)
638 MirrorHandler(mode, p);
642 void DrawingView::ToolDraw(Painter * painter)
644 if (Global::tool == TTLine)
646 if (Global::toolState == TSNone)
648 painter->DrawHandle(toolPoint[0]);
650 else if ((Global::toolState == TSPoint2) && shiftDown)
652 painter->DrawHandle(toolPoint[1]);
656 painter->DrawLine(toolPoint[0], toolPoint[1]);
657 painter->DrawHandle(toolPoint[1]);
659 Vector v(toolPoint[0], toolPoint[1]);
660 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
661 double absLength = v.Magnitude();
662 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
663 informativeText = text.arg(absLength).arg(absAngle);
666 else if (Global::tool == TTCircle)
668 if (Global::toolState == TSNone)
670 painter->DrawHandle(toolPoint[0]);
672 else if ((Global::toolState == TSPoint2) && shiftDown)
674 painter->DrawHandle(toolPoint[1]);
678 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
679 // painter->DrawLine(toolPoint[0], toolPoint[1]);
680 // painter->DrawHandle(toolPoint[1]);
681 painter->SetBrush(QBrush(Qt::NoBrush));
682 painter->DrawEllipse(toolPoint[0], length, length);
683 QString text = tr("Radius: %1 in.");//\n") + QChar(0x2221) + tr(": %2");
684 informativeText = text.arg(length);//.arg(absAngle);
687 else if (Global::tool == TTArc)
689 if (Global::toolState == TSNone)
691 painter->DrawHandle(toolPoint[0]);
693 else if (Global::toolState == TSPoint2)
695 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
696 painter->SetBrush(QBrush(Qt::NoBrush));
697 painter->DrawEllipse(toolPoint[0], length, length);
698 painter->DrawLine(toolPoint[0], toolPoint[1]);
699 painter->DrawHandle(toolPoint[1]);
700 QString text = tr("Radius: %1 in.");
701 informativeText = text.arg(length);
703 else if (Global::toolState == TSPoint3)
705 double angle = Vector::Angle(toolPoint[0], toolPoint[2]);
706 painter->DrawLine(toolPoint[0], toolPoint[2]);
707 painter->SetBrush(QBrush(Qt::NoBrush));
708 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
709 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
710 QString text = tr("Angle start: %1") + QChar(0x00B0);
711 informativeText = text.arg(RADIANS_TO_DEGREES * angle);
715 double angle = Vector::Angle(toolPoint[0], toolPoint[3]);
716 double span = angle - toolPoint[2].x;
721 painter->DrawLine(toolPoint[0], toolPoint[3]);
722 painter->SetBrush(QBrush(Qt::NoBrush));
723 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
724 painter->SetPen(0xFF00FF, 2.0, LSSolid);
725 painter->DrawArc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
726 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
727 QString text = tr("Arc span: %1") + QChar(0x00B0);
728 informativeText = text.arg(RADIANS_TO_DEGREES * span);
731 else if (Global::tool == TTRotate)
733 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
734 painter->DrawHandle(toolPoint[0]);
735 else if ((Global::toolState == TSPoint2) && shiftDown)
736 painter->DrawHandle(toolPoint[1]);
739 if (toolPoint[0] == toolPoint[1])
742 painter->DrawLine(toolPoint[0], toolPoint[1]);
743 // Likely we need a tool container for this... (now we do!)
747 painter->SetPen(0x00FF00, 2.0, LSSolid);
748 overrideColor = true;
751 RenderObjects(painter, toolObjects);
752 overrideColor = false;
755 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
756 QString text = QChar(0x2221) + QObject::tr(": %1");
757 informativeText = text.arg(absAngle);
760 informativeText += " (Copy)";
763 else if (Global::tool == TTMirror)
765 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
766 painter->DrawHandle(toolPoint[0]);
767 else if ((Global::toolState == TSPoint2) && shiftDown)
768 painter->DrawHandle(toolPoint[1]);
771 if (toolPoint[0] == toolPoint[1])
774 Point mirrorPoint = toolPoint[0] + Vector(toolPoint[1], toolPoint[0]);
775 // painter->DrawLine(toolPoint[0], toolPoint[1]);
776 painter->DrawLine(mirrorPoint, toolPoint[1]);
777 // Likely we need a tool container for this... (now we do!)
781 painter->SetPen(0x00FF00, 2.0, LSSolid);
782 overrideColor = true;
785 RenderObjects(painter, toolObjects);
786 overrideColor = false;
789 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
791 if (absAngle > 180.0)
794 QString text = QChar(0x2221) + QObject::tr(": %1");
795 informativeText = text.arg(absAngle);
798 informativeText += " (Copy)";
804 void DrawingView::LineHandler(int mode, Point p)
809 if (Global::toolState == TSNone)
816 if (Global::toolState == TSNone)
823 if (Global::toolState == TSNone)
825 Global::toolState = TSPoint2;
826 // Prevent spurious line from drawing...
827 toolPoint[1] = toolPoint[0];
829 else if ((Global::toolState == TSPoint2) && shiftDown)
831 // Key override is telling us to make a new line, not continue the
833 toolPoint[0] = toolPoint[1];
837 Line * l = new Line(toolPoint[0], toolPoint[1]);
838 l->layer = Global::activeLayer;
839 document.objects.push_back(l);
840 toolPoint[0] = toolPoint[1];
846 void DrawingView::CircleHandler(int mode, Point p)
851 if (Global::toolState == TSNone)
858 if (Global::toolState == TSNone)
865 if (Global::toolState == TSNone)
867 Global::toolState = TSPoint2;
868 // Prevent spurious line from drawing...
869 toolPoint[1] = toolPoint[0];
871 else if ((Global::toolState == TSPoint2) && shiftDown)
873 // Key override is telling us to make a new line, not continue the
875 toolPoint[0] = toolPoint[1];
879 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
880 Circle * c = new Circle(toolPoint[0], length);
881 c->layer = Global::activeLayer;
882 document.objects.push_back(c);
883 toolPoint[0] = toolPoint[1];
884 Global::toolState = TSNone;
890 void DrawingView::ArcHandler(int mode, Point p)
895 if (Global::toolState == TSNone)
897 else if (Global::toolState == TSPoint2)
899 else if (Global::toolState == TSPoint3)
906 if (Global::toolState == TSNone)
908 else if (Global::toolState == TSPoint2)
910 else if (Global::toolState == TSPoint3)
917 if (Global::toolState == TSNone)
919 // Prevent spurious line from drawing...
920 toolPoint[1] = toolPoint[0];
921 Global::toolState = TSPoint2;
923 else if (Global::toolState == TSPoint2)
927 // Key override is telling us to start circle at new center, not
928 // continue the current one.
929 toolPoint[0] = toolPoint[1];
933 // Set the radius in toolPoint[1].x
934 toolPoint[1].x = Vector::Magnitude(toolPoint[0], toolPoint[1]);
935 Global::toolState = TSPoint3;
937 else if (Global::toolState == TSPoint3)
939 // Set the angle in toolPoint[2].x
940 toolPoint[2].x = Vector::Angle(toolPoint[0], toolPoint[2]);
941 Global::toolState = TSPoint4;
945 double endAngle = Vector::Angle(toolPoint[0], toolPoint[3]);
946 double span = endAngle - toolPoint[2].x;
951 Arc * arc = new Arc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
952 arc->layer = Global::activeLayer;
953 document.objects.push_back(arc);
954 Global::toolState = TSNone;
960 void DrawingView::RotateHandler(int mode, Point p)
965 if (Global::toolState == TSNone)
968 SavePointsFrom(select, toolScratch);
969 Global::toolState = TSPoint1;
971 else if (Global::toolState == TSPoint1)
978 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
980 else if (Global::toolState == TSPoint2)
987 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
988 std::vector<void *>::iterator j = select.begin();
989 std::vector<Object>::iterator i = toolScratch.begin();
991 for(; i!=toolScratch.end(); i++, j++)
994 Point p1 = Geometry::RotatePointAroundPoint(obj.p[0], toolPoint[0], angle);
995 Point p2 = Geometry::RotatePointAroundPoint(obj.p[1], toolPoint[0], angle);
996 Object * obj2 = (Object *)(*j);
1000 if (obj.type == OTArc)
1002 obj2->angle[0] = obj.angle[0] + angle;
1004 if (obj2->angle[0] > TAU)
1005 obj2->angle[0] -= TAU;
1012 if (Global::toolState == TSPoint1)
1014 Global::toolState = TSPoint2;
1015 // Prevent spurious line from drawing...
1016 toolPoint[1] = toolPoint[0];
1018 else if ((Global::toolState == TSPoint2) && shiftDown)
1020 // Key override is telling us to make a new line, not continue the
1022 toolPoint[0] = toolPoint[1];
1026 // Either we're finished with our rotate, or we're stamping a copy.
1029 // Stamp a copy of the selection at the current rotation & bail
1030 std::vector<void *> temp;
1031 CopyObjects(select, temp);
1032 ClearSelected(temp);
1033 AddObjectsTo(document.objects, temp);
1034 RestorePointsTo(select, toolScratch);
1039 Global::toolState = TSPoint1;
1040 SavePointsFrom(select, toolScratch);
1045 // Reset the selection if shift held down...
1047 RestorePointsTo(select, toolScratch);
1051 // Reset selection when key is let up
1053 RotateHandler(ToolMouseMove, toolPoint[1]);
1057 RestorePointsTo(select, toolScratch);
1062 void DrawingView::MirrorHandler(int mode, Point p)
1067 if (Global::toolState == TSNone)
1070 SavePointsFrom(select, toolScratch);
1071 Global::toolState = TSPoint1;
1073 else if (Global::toolState == TSPoint1)
1080 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1082 else if (Global::toolState == TSPoint2)
1089 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1090 std::vector<void *>::iterator j = select.begin();
1091 std::vector<Object>::iterator i = toolScratch.begin();
1093 for(; i!=toolScratch.end(); i++, j++)
1096 Point p1 = Geometry::MirrorPointAroundLine(obj.p[0], toolPoint[0], toolPoint[1]);
1097 Point p2 = Geometry::MirrorPointAroundLine(obj.p[1], toolPoint[0], toolPoint[1]);
1098 Object * obj2 = (Object *)(*j);
1102 if (obj.type == OTArc)
1104 // This is 2*mirror angle - obj angle - obj span
1105 obj2->angle[0] = (2.0 * angle) - obj.angle[0] - obj.angle[1];
1107 if (obj2->angle[0] > TAU)
1108 obj2->angle[0] -= TAU;
1115 if (Global::toolState == TSPoint1)
1117 Global::toolState = TSPoint2;
1118 // Prevent spurious line from drawing...
1119 toolPoint[1] = toolPoint[0];
1121 else if ((Global::toolState == TSPoint2) && shiftDown)
1123 // Key override is telling us to make a new line, not continue the
1125 toolPoint[0] = toolPoint[1];
1129 // Either we're finished with our rotate, or we're stamping a copy.
1132 // Stamp a copy of the selection at the current rotation & bail
1133 std::vector<void *> temp;
1134 CopyObjects(select, temp);
1135 ClearSelected(temp);
1136 AddObjectsTo(document.objects, temp);
1137 RestorePointsTo(select, toolScratch);
1142 Global::toolState = TSPoint1;
1143 SavePointsFrom(select, toolScratch);
1148 // Reset the selection if shift held down...
1150 RestorePointsTo(select, toolScratch);
1154 // Reset selection when key is let up
1156 MirrorHandler(ToolMouseMove, toolPoint[1]);
1160 RestorePointsTo(select, toolScratch);
1165 void DrawingView::mousePressEvent(QMouseEvent * event)
1167 if (event->button() == Qt::LeftButton)
1169 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1171 // Handle tool processing, if any
1174 if (Global::snapToGrid)
1175 point = SnapPointToGrid(point);
1177 //Also, may want to figure out if hovering over a snap point on an object,
1178 //snap to grid if not.
1179 // Snap to object point if valid...
1180 // if (Global::snapPointIsValid)
1181 // point = Global::snapPoint;
1183 ToolHandler(ToolMouseDown, point);
1187 // Clear the selection only if CTRL isn't being held on click
1189 ClearSelected(document.objects);
1190 // ClearSelection();
1192 // If any objects are being hovered on click, add them to the selection
1196 AddHoveredToSelection();
1197 update(); // needed??
1198 GetHovered(hover); // prolly needed
1200 // Needed for grab & moving objects
1201 // We do it *after*... why? (doesn't seem to confer any advantage...)
1202 if (Global::snapToGrid)
1203 oldPoint = SnapPointToGrid(point);
1208 // Didn't hit any object and not using a tool, so do a selection rectangle
1209 Global::selectionInProgress = true;
1210 Global::selection.setTopLeft(QPointF(point.x, point.y));
1211 Global::selection.setBottomRight(QPointF(point.x, point.y));
1213 else if (event->button() == Qt::MiddleButton)
1216 oldPoint = Vector(event->x(), event->y());
1217 // Should also change the mouse pointer as well...
1218 setCursor(Qt::SizeAllCursor);
1223 void DrawingView::mouseMoveEvent(QMouseEvent * event)
1225 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1226 Global::selection.setBottomRight(QPointF(point.x, point.y));
1227 // Only needs to be done here, as mouse down is always preceded by movement
1228 Global::snapPointIsValid = false;
1229 hoveringIntersection = false;
1232 if (event->buttons() & Qt::MiddleButton)
1234 point = Vector(event->x(), event->y());
1235 // Since we're using Qt coords for scrolling, we have to adjust them here to
1236 // conform to Cartesian coords, since the origin is using Cartesian. :-)
1237 Vector delta(oldPoint, point);
1238 delta /= Global::zoom;
1240 Global::origin -= delta;
1242 UpdateGridBackground();
1248 // If we're doing a selection rect, see if any objects are engulfed by it
1249 // (implies left mouse button held down)
1250 if (Global::selectionInProgress)
1252 CheckObjectBounds();
1257 // Handle object movement (left button down & over an object)
1258 if ((event->buttons() & Qt::LeftButton) && numHovered && !Global::tool)
1260 if (Global::snapToGrid)
1261 point = SnapPointToGrid(point);
1263 HandleObjectMovement(point);
1269 // Do object hit testing...
1270 bool needUpdate = HitTestObjects(point);
1272 // Check for multi-hover...
1278 // int numIntersecting = Geometry::Intersects((Object *)hover[0], (Object *)hover[1], &t, &u);
1279 Geometry::Intersects((Object *)hover[0], (Object *)hover[1]);
1280 int numIntersecting = Global::numIntersectParams;
1281 double t = Global::intersectParam[0];
1282 double u = Global::intersectParam[1];
1284 if (numIntersecting > 0)
1286 Vector v1 = Geometry::GetPointForParameter((Object *)hover[0], t);
1287 Vector v2 = Geometry::GetPointForParameter((Object *)hover[1], u);
1288 QString text = tr("Intersection t=%1 (%3, %4), u=%2 (%5, %6)");
1289 informativeText = text.arg(t).arg(u).arg(v1.x).arg(v1.y).arg(v2.x).arg(v2.y);
1291 hoveringIntersection = true;
1292 intersectionPoint = v1;
1295 numIntersecting = Global::numIntersectPoints;
1297 if (numIntersecting > 0)
1299 Vector v1 = Global::intersectPoint[0];
1301 if (numIntersecting == 2)
1303 Vector v2 = Global::intersectPoint[1];
1305 if (Vector::Magnitude(v2, point) < Vector::Magnitude(v1, point))
1309 QString text = tr("Intersection <%1, %2>");
1310 informativeText = text.arg(v1.x).arg(v1.y);
1311 hoveringIntersection = true;
1312 intersectionPoint = v1;
1316 // Do tool handling, if any are active...
1319 if (Global::snapToGrid)
1320 point = SnapPointToGrid(point);
1322 ToolHandler(ToolMouseMove, point);
1325 // This is used to draw the tool crosshair...
1328 if (needUpdate || Global::tool)
1333 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
1335 if (event->button() == Qt::LeftButton)
1337 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
1338 //could set it up to use the document's update function (assumes that all object updates
1339 //are being reported correctly:
1340 // if (document.NeedsUpdate())
1341 // Do an update if collided with at least *one* object in the document
1347 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1348 ToolHandler(ToolMouseUp, point);
1352 if (Global::selectionInProgress)
1353 Global::selectionInProgress = false;
1355 informativeText.clear();
1356 // Should we be doing this automagically? Hmm...
1357 // Clear our vectors
1362 std::vector<void *>::iterator i;
1364 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1366 if (((Object *)(*i))->selected)
1367 select.push_back(*i);
1369 //hmm, this is no good, too late to do any good :-P
1370 // if ((*i)->hovered)
1371 // hover.push_back(*i);
1374 else if (event->button() == Qt::MiddleButton)
1377 setCursor(Qt::ArrowCursor);
1382 void DrawingView::wheelEvent(QWheelEvent * event)
1384 double zoomFactor = 1.25;
1385 QSize sizeWin = size();
1386 Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
1387 center = Painter::QtToCartesianCoords(center);
1389 // This is not centering for some reason. Need to figure out why. :-/
1390 if (event->delta() > 0)
1392 Vector newOrigin = center - ((center - Global::origin) / zoomFactor);
1393 Global::origin = newOrigin;
1394 Global::zoom *= zoomFactor;
1398 Vector newOrigin = center + ((-center + Global::origin) * zoomFactor);
1399 Global::origin = newOrigin;
1400 Global::zoom /= zoomFactor;
1403 // Global::gridSpacing = gridPixels / Painter::zoom;
1404 // UpdateGridBackground();
1405 SetGridSize(Global::gridSpacing * Global::zoom);
1407 // zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Global::gridSpacing));
1411 void DrawingView::keyPressEvent(QKeyEvent * event)
1413 bool oldShift = shiftDown;
1414 bool oldCtrl = ctrlDown;
1416 if (event->key() == Qt::Key_Shift)
1418 else if (event->key() == Qt::Key_Control)
1421 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1424 ToolHandler(ToolKeyDown, Point(0, 0));
1431 void DrawingView::keyReleaseEvent(QKeyEvent * event)
1433 bool oldShift = shiftDown;
1434 bool oldCtrl = ctrlDown;
1436 if (event->key() == Qt::Key_Shift)
1438 else if (event->key() == Qt::Key_Control)
1441 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1444 ToolHandler(ToolKeyUp, Point(0, 0));
1452 // This looks strange, but it's really quite simple: We want a point that's
1453 // more than half-way to the next grid point to snap there while conversely we
1454 // want a point that's less than half-way to to the next grid point then snap
1455 // to the one before it. So we add half of the grid spacing to the point, then
1456 // divide by it so that we can remove the fractional part, then multiply it
1457 // back to get back to the correct answer.
1459 Point DrawingView::SnapPointToGrid(Point point)
1461 point += Global::gridSpacing / 2.0; // *This* adds to Z!!!
1462 point /= Global::gridSpacing;
1463 point.x = floor(point.x);//need to fix this for negative numbers...
1464 point.y = floor(point.y);
1465 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
1466 point *= Global::gridSpacing;
1471 void DrawingView::CheckObjectBounds(void)
1473 std::vector<void *>::iterator i;
1476 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1478 Object * obj = (Object *)(*i);
1479 obj->selected = false;
1485 Line * l = (Line *)obj;
1487 if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
1494 Circle * c = (Circle *)obj;
1496 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]))
1503 Arc * a = (Arc *)obj;
1505 double start = a->angle[0];
1506 double end = start + a->angle[1];
1507 QPointF p1(cos(start), sin(start));
1508 QPointF p2(cos(end), sin(end));
1509 QRectF bounds(p1, p2);
1512 // Swap X/Y coordinates if they're backwards...
1513 if (bounds.left() > bounds.right())
1515 double temp = bounds.left();
1516 bounds.setLeft(bounds.right());
1517 bounds.setRight(temp);
1520 if (bounds.bottom() > bounds.top())
1522 double temp = bounds.bottom();
1523 bounds.setBottom(bounds.top());
1524 bounds.setTop(temp);
1527 // Doesn't work as advertised! For shame!
1528 bounds = bounds.normalized();
1531 // If the end of the arc is before the beginning, add 360 degrees to it
1535 // Adjust the bounds depending on which axes are crossed
1536 if ((start < QTR_TAU) && (end > QTR_TAU))
1539 if ((start < HALF_TAU) && (end > HALF_TAU))
1540 bounds.setLeft(-1.0);
1542 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
1543 bounds.setBottom(-1.0);
1545 if ((start < TAU) && (end > TAU))
1546 bounds.setRight(1.0);
1548 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
1551 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
1552 bounds.setLeft(-1.0);
1554 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
1555 bounds.setBottom(-1.0);
1557 bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
1558 bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
1559 bounds.translate(a->p[0].x, a->p[0].y);
1561 if (Global::selection.contains(bounds))
1576 bool DrawingView::HitTestObjects(Point point)
1578 std::vector<void *>::iterator i;
1580 bool needUpdate = false;
1582 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1584 Object * obj = (Object *)(*i);
1590 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
1591 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
1592 Vector lineSegment = obj->p[1] - obj->p[0];
1593 Vector v1 = point - obj->p[0];
1594 Vector v2 = point - obj->p[1];
1595 double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
1599 distance = v1.Magnitude();
1601 distance = v2.Magnitude();
1603 // distance = ?Det?(ls, v1) / |ls|
1604 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
1605 / lineSegment.Magnitude());
1607 if ((v1.Magnitude() * Global::zoom) < 8.0)
1608 obj->hitPoint[0] = true;
1609 else if ((v2.Magnitude() * Global::zoom) < 8.0)
1610 obj->hitPoint[1] = true;
1611 else if ((distance * Global::zoom) < 5.0)
1612 obj->hitObject = true;
1614 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
1616 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
1623 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
1624 obj->hitPoint[0] = obj->hitObject = false;
1625 double length = Vector::Magnitude(obj->p[0], point);
1627 if ((length * Global::zoom) < 8.0)
1628 obj->hitPoint[0] = true;
1629 else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
1630 obj->hitObject = true;
1632 obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
1634 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
1641 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
1642 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
1643 double length = Vector::Magnitude(obj->p[0], point);
1644 double angle = Vector::Angle(obj->p[0], point);
1646 // Make sure we get the angle in the correct spot
1647 if (angle < obj->angle[0])
1650 // Get the span that we're pointing at...
1651 double span = angle - obj->angle[0];
1653 // N.B.: Still need to hit test the arc start & arc span handles...
1654 double spanAngle = obj->angle[0] + obj->angle[1];
1655 Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
1656 Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
1657 double length2 = Vector::Magnitude(point, handle1);
1658 double length3 = Vector::Magnitude(point, handle2);
1660 if ((length * Global::zoom) < 8.0)
1661 obj->hitPoint[0] = true;
1662 else if ((length2 * Global::zoom) < 8.0)
1663 obj->hitPoint[1] = true;
1664 else if ((length3 * Global::zoom) < 8.0)
1665 obj->hitPoint[2] = true;
1666 else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
1667 obj->hitObject = true;
1669 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
1671 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
1683 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
1691 void DrawingView::HandleObjectMovement(Point point)
1693 Point delta = point - oldPoint;
1694 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
1695 Object * obj = (Object *)hover[0];
1696 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
1697 //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"));
1702 if (obj->hitPoint[0])
1704 else if (obj->hitPoint[1])
1706 else if (obj->hitObject)
1714 if (obj->hitPoint[0])
1716 else if (obj->hitObject)
1718 //this doesn't work. we need to save this on mouse down for this to work correctly!
1719 // double oldRadius = obj->radius[0];
1720 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
1722 QString text = QObject::tr("Radius: %1");//\nScale: %2%");
1723 informativeText = text.arg(obj->radius[0], 0, 'd', 4);//.arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0);
1728 if (obj->hitPoint[0])
1730 else if (obj->hitPoint[1])
1732 // Change the Arc's span (handle #1)
1735 double angle = Vector::Angle(obj->p[0], point);
1736 double delta = angle - obj->angle[0];
1741 obj->angle[1] -= delta;
1742 obj->angle[0] = angle;
1744 if (obj->angle[1] < 0)
1745 obj->angle[1] += TAU;
1747 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
1748 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);
1752 double angle = Vector::Angle(obj->p[0], point);
1753 obj->angle[0] = angle;
1754 QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
1755 informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 4);
1757 else if (obj->hitPoint[2])
1759 // Change the Arc's span (handle #2)
1762 double angle = Vector::Angle(obj->p[0], point);
1763 obj->angle[1] = angle - obj->angle[0];
1765 if (obj->angle[1] < 0)
1766 obj->angle[1] += TAU;
1768 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
1769 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);
1773 double angle = Vector::Angle(obj->p[0], point);
1774 obj->angle[0] = angle - obj->angle[1];
1776 if (obj->angle[0] < 0)
1777 obj->angle[0] += TAU;
1779 QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
1780 informativeText = text.arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 4);
1782 else if (obj->hitObject)
1789 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
1790 QString text = QObject::tr("Radius: %1");
1791 informativeText = text.arg(obj->radius[0], 0, 'd', 4);
1803 // This returns true if we've moved over an object...
1804 if (document.PointerMoved(point)) // <-- This
1805 // This is where the object would do automagic dragging & shit. Since we don't
1806 // do that anymore, we need a strategy to handle it.
1810 Now objects handle mouse move snapping as well. The code below mainly works only
1811 for tools; we need to fix it so that objects work as well...
1813 There's a problem with the object point snapping in that it's dependent on the
1814 order of the objects in the document. Most likely this is because it counts the
1815 selected object last and thus fucks up the algorithm. Need to fix this...
1819 // Do object snapping here. Grid snapping on mouse down is done in the
1820 // objects themselves, only because we have to hit test the raw point,
1821 // not the snapped point. There has to be a better way...!
1822 if (document.penultimateObjectHovered)
1824 // Two objects are hovered, see if we have an intersection point
1825 if ((document.lastObjectHovered->type == OTLine) && (document.penultimateObjectHovered->type == OTLine))
1828 int n = Geometry::Intersects((Line *)document.lastObjectHovered, (Line *)document.penultimateObjectHovered, &t);
1832 Global::snapPoint = document.lastObjectHovered->GetPointAtParameter(t);
1833 Global::snapPointIsValid = true;
1836 else if ((document.lastObjectHovered->type == OTCircle) && (document.penultimateObjectHovered->type == OTCircle))
1839 int n = Geometry::Intersects((Circle *)document.lastObjectHovered, (Circle *)document.penultimateObjectHovered, 0, 0, 0, 0, &p1, &p2);
1843 Global::snapPoint = p1;
1844 Global::snapPointIsValid = true;
1848 double d1 = Vector(point, p1).Magnitude();
1849 double d2 = Vector(point, p2).Magnitude();
1852 Global::snapPoint = p1;
1854 Global::snapPoint = p2;
1856 Global::snapPointIsValid = true;
1862 // Otherwise, it was a single object hovered...
1868 if (Global::snapToGrid)
1869 point = Global::SnapPointToGrid(point);
1871 // We always snap to object points, and they take precendence over
1873 if (Global::snapPointIsValid)
1874 point = Global::snapPoint;
1876 toolAction->MouseMoved(point);