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), angleSnap(false)
54 //wtf? doesn't work except in c++11??? document = { 0 };
55 setBackgroundRole(QPalette::Base);
56 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
58 Global::gridSpacing = 12.0; // In base units (inch is default)
60 Line * line = new Line(Vector(5, 5), Vector(50, 40), &document);
62 document.Add(new Line(Vector(50, 40), Vector(10, 83), &document));
63 document.Add(new Line(Vector(10, 83), Vector(17, 2), &document));
64 document.Add(new Circle(Vector(100, 100), 36, &document));
65 document.Add(new Circle(Vector(50, 150), 49, &document));
66 document.Add(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3, &document)),
67 document.Add(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5, &document));
69 Dimension * dimension = new Dimension(Vector(0, 0), Vector(0, 0), DTLinear, &document);
70 line->SetDimensionOnLine(dimension);
71 document.Add(dimension);
73 // Alternate way to do the above...
74 line->SetDimensionOnLine();
77 Line * line = new Line;//(Vector(5, 5), Vector(50, 40), &document);
78 line->p[0] = Vector(5, 5);
79 line->p[1] = Vector(50, 40);
81 line->thickness = 2.0;
83 line->color = 0xFF7F00;
85 document.objects.push_back(line);
86 document.objects.push_back(new Line(Vector(50, 40), Vector(10, 83)));
87 document.objects.push_back(new Line(Vector(10, 83), Vector(17, 2)));
88 document.objects.push_back(new Circle(Vector(100, 100), 36));
89 document.objects.push_back(new Circle(Vector(50, 150), 49));
90 document.objects.push_back(new Arc(Vector(300, 300), 32, TAU / 8.0, TAU * 0.65)),
91 document.objects.push_back(new Arc(Vector(200, 200), 60, TAU / 4.0, TAU * 0.75));
92 document.objects.push_back(new Dimension(Vector(50, 40), Vector(5, 5)));
93 document.objects.push_back(new Text(Vector(10, 83), "Here is some awesome text!"));
97 Here we set the grid size in pixels--12 in this case. Initially, we have our
98 zoom set to make this represent 12 inches at a zoom factor of 25%. (This is
99 arbitrary.) So, to be able to decouple the grid size from the zoom, we need
100 to be able to set the size of the background grid (which we do here at an
101 arbitrary 12 pixels) to anything we want (within reason, of course :-).
103 The drawing enforces the grid spacing through the drawing->gridSpacing variable.
105 drawing->gridSpacing = 12.0 / Global::zoom;
107 Global::zoom is the zoom factor for the drawing, and all mouse clicks are
108 translated to Cartesian coordinates through this. (Initially, Global::zoom is
109 set to 1.0. SCREEN_ZOOM is set to 1.0/4.0.)
111 Really, the 100% zoom level can be set at *any* zoom level, it's more of a
112 convenience function than any measure of absolutes. Doing things that way we
113 could rid ourselves of the whole SCREEN_ZOOM parameter and all the attendant
114 shittiness that comes with it.
116 However, it seems that SCREEN_ZOOM is used to make text and arrow sizes show up
117 a certain way, which means we should probably create something else in those
118 objects to take its place--like some kind of scale factor. This would seem to
119 imply that certain point sizes actually *do* tie things like fonts to absolute
120 sizes on the screen, but not necessarily because you could have an inch scale
121 with text that is quite small relative to other objects on the screen, which
122 currently you have to zoom in to see (and which blows up the text). Point sizes
123 in an application like this are a bit meaningless; even though an inch is an
124 inch regardless of the zoom level a piece of text can be larger or smaller than
125 this. Maybe this is the case for having a base unit and basing point sizes off
128 Here's what's been figured out. Global::zoom is simply the ratio of pixels to
129 base units. What that means is that if you have a 12px grid with a 6" grid size
130 (& base unit of "inches"), Global::zoom becomes 12px / 6" = 2.0 px/in.
132 Dimensions now have a "size" parameter to set their absolute size in relation
133 to the base unit. ATM, the arrows are drawn in pixels, but also scaled by
134 Global::zoom *and* size. Same with the dimension text; it's drawn at 10pt and
135 scaled the same way as the arrowheads.
137 Need a way to scale line widths as well. :-/ Shouldn't be too difficult, just
138 need a thickness parameter similar to the "size" param for dimensions. (And now
142 SetGridSize(12); // This is in pixels
146 void DrawingView::SetGridSize(uint32_t size)
149 if (size == gridPixels)
152 // Recreate the background bitmap
154 QPainter pmp(&gridBackground);
155 pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
156 pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
158 for(int i=0; i<(BACKGROUND_MAX_SIZE-1); i+=gridPixels)
160 pmp.drawLine(i, 0, i, BACKGROUND_MAX_SIZE - 1);
161 pmp.drawLine(0, i, BACKGROUND_MAX_SIZE - 1, i);
166 // Set up new BG brush & zoom level (pixels per base unit)
167 Global::zoom = gridPixels / Global::gridSpacing;
168 UpdateGridBackground();
172 void DrawingView::UpdateGridBackground(void)
174 // Transform the origin to Qt coordinates
175 Vector pixmapOrigin = Painter::CartesianToQtCoords(Vector());
176 int x = (int)pixmapOrigin.x;
177 int y = (int)pixmapOrigin.y;
178 // Use mod arithmetic to grab the correct swatch of background
180 Negative numbers still screw it up... Need to think about what we're
181 trying to do here. The fact that it worked with 72 seems to have been pure luck.
182 It seems the problem is negative numbers: We can't let that happen.
183 When taking away the zero, it pops over 1 px at zero, then goes about 1/2 a
186 The bitmap looks like this:
196 @ x = 1, we want it to look like:
198 -+---+---+---+---+---
201 -+---+---+---+---+---
206 Which means we need to grab the sample from x = 3. @ x = -1:
216 Which means we need to grab the sample from x = 1. Which means we have to take
217 the mirror of the modulus of gridPixels.
219 Doing a mod of a negative number is problematic: 1st, the compiler converts the
220 negative number to an unsigned int, then it does the mod. Gets you wrong answers
221 most of the time, unless you use a power of 2. :-P So what we do here is just
222 take the modulus of the negation, which means we don't have to worry about
225 The positive case looks gruesome (and it is) but it boils down to this: We take
226 the modulus of the X coordinate, then mirror it by subtraction from the
227 maximum (in this case, gridPixels). This gives us a number in the range of 1 to
228 gridPixels. But we need the case where the result equalling gridPixels to be
229 zero; so we do another modulus operation on the result to achieve this.
234 x = (gridPixels - (x % gridPixels)) % gridPixels;
239 y = (gridPixels - (y % gridPixels)) % gridPixels;
241 // Here we grab a section of the bigger pixmap, so that the background
242 // *looks* like it's scrolling...
243 QPixmap pm = gridBackground.copy(x, y, gridPixels, gridPixels);
244 QPalette pal = palette();
245 pal.setBrush(backgroundRole(), QBrush(pm));
246 setAutoFillBackground(true);
252 // Basically, we just make a single pass through the Container. If the layer #
253 // is less than the layer # being deleted, then do nothing. If the layer # is
254 // equal to the layer # being deleted, then delete the object. If the layer #
255 // is greater than the layer # being deleted, then set the layer # to its layer
258 void DrawingView::DeleteCurrentLayer(int layer)
260 //printf("DrawingView::DeleteCurrentLayer(): currentLayer = %i\n", layer);
261 std::vector<void *>::iterator i = document.objects.begin();
263 while (i != document.objects.end())
265 Object * obj = (Object *)(*i);
267 if (obj->layer < layer)
269 else if (obj->layer == layer)
271 document.objects.erase(i);
281 // We've just done a destructive action, so update the screen!
286 void DrawingView::HandleLayerToggle(void)
288 // A layer's visibility was toggled, so update the screen...
294 // A layer was moved up or down in the layer list, so we have to swap the
295 // document's object's layer numbers in the layers that were swapped.
297 void DrawingView::HandleLayerSwap(int layer1, int layer2)
299 //printf("DrawingView: Swapping layers %i and %i.\n", layer1, layer2);
300 std::vector<void *>::iterator i;
302 for(i=document.objects.begin(); i!=document.objects.end(); i++)
304 Object * obj = (Object *)(*i);
306 if (obj->layer == layer1)
308 else if (obj->layer == layer2)
314 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
316 // This is undoing the transform, e.g. going from client coords to local
317 // coords. In essence, the height - y is height + (y * -1), the (y * -1)
318 // term doing the conversion of the y-axis from increasing bottom to top.
319 return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
323 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
325 // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
326 // No voodoo here, it's just grouped wrong to see it. It should be:
327 // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive [why? we use -offsetX after all]
328 return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
332 void DrawingView::paintEvent(QPaintEvent * /*event*/)
334 QPainter qtPainter(this);
335 Painter painter(&qtPainter);
338 qtPainter.setRenderHint(QPainter::Antialiasing);
340 Global::viewportHeight = size().height();
342 // Draw coordinate axes
343 painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
344 painter.DrawLine(0, -16384, 0, 16384);
345 painter.DrawLine(-16384, 0, 16384, 0);
347 // Do object rendering...
348 for(int i=0; i<Global::numLayers; i++)
350 if (Global::layerHidden[i] == false)
351 RenderObjects(&painter, document.objects, i);
354 // Do tool rendering, if any...
357 painter.SetPen(QPen(QColor(200, 100, 0, 255), 1.0, Qt::DashLine));
358 painter.DrawCrosshair(oldPoint);
362 // Do selection rectangle rendering, if any
363 if (Global::selectionInProgress)
365 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
366 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
367 painter.DrawRect(Global::selection);
370 if (hoveringIntersection)
371 painter.DrawHandle(intersectionPoint);
373 if (!informativeText.isEmpty())
374 painter.DrawInformativeText(informativeText);
379 // Renders objects in the passed in vector
381 void DrawingView::RenderObjects(Painter * painter, std::vector<void *> & v, int layer, bool ignoreLayer/*= false*/)
383 std::vector<void *>::iterator i;
385 for(i=v.begin(); i!=v.end(); i++)
387 Object * obj = (Object *)(*i);
388 float scaledThickness = Global::scale * obj->thickness;
390 // If the object isn't on the current layer being drawn, skip it
391 if (!ignoreLayer && (obj->layer != layer))
394 if ((Global::tool == TTRotate) && ctrlDown && obj->selected)
396 painter->SetPen(0x00FF00, 2.0, LSSolid);
400 painter->SetPen(obj->color, Global::zoom * scaledThickness, obj->style);
401 painter->SetBrush(obj->color);
403 if (obj->selected || obj->hitObject)
404 painter->SetPen(0xFF0000, Global::zoom * scaledThickness, LSDash);
410 painter->DrawLine(obj->p[0], obj->p[1]);
412 if (obj->hitPoint[0])
413 painter->DrawHandle(obj->p[0]);
415 if (obj->hitPoint[1])
416 painter->DrawHandle(obj->p[1]);
420 painter->SetBrush(QBrush(Qt::NoBrush));
421 painter->DrawEllipse(obj->p[0], obj->radius[0], obj->radius[0]);
423 if (obj->hitPoint[0])
424 painter->DrawHandle(obj->p[0]);
428 painter->DrawArc(obj->p[0], obj->radius[0], obj->angle[0], obj->angle[1]);
430 if (obj->hitPoint[0])
431 painter->DrawHandle(obj->p[0]);
433 if (obj->hitPoint[1])
434 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]));
436 if (obj->hitPoint[2])
437 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0] + obj->angle[1]), sin(obj->angle[0] + obj->angle[1])) * obj->radius[0]));
442 Dimension * d = (Dimension *)obj;
444 Vector v(d->p[0], d->p[1]);
445 double angle = v.Angle();
446 Vector unit = v.Unit();
447 d->lp[0] = d->p[0], d->lp[1] = d->p[1];
449 double x1, y1, length;
451 if (d->subtype == DTLinearVert)
453 if ((angle < 0) || (angle > HALF_TAU))
455 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
456 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
457 ortho = Vector(1.0, 0);
458 angle = THREE_QTR_TAU;
462 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
463 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
464 ortho = Vector(-1.0, 0);
468 d->lp[0].x = d->lp[1].x = x1;
469 length = fabs(d->p[0].y - d->p[1].y);
471 else if (d->subtype == DTLinearHorz)
473 if ((angle < QTR_TAU) || (angle > THREE_QTR_TAU))
475 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
476 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
477 ortho = Vector(0, 1.0);
482 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
483 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
484 ortho = Vector(0, -1.0);
488 d->lp[0].y = d->lp[1].y = y1;
489 length = fabs(d->p[0].x - d->p[1].x);
491 else if (d->subtype == DTLinear)
493 angle = Vector(d->lp[0], d->lp[1]).Angle();
494 ortho = Vector::Normal(d->lp[0], d->lp[1]);
495 length = v.Magnitude();
498 unit = Vector(d->lp[0], d->lp[1]).Unit();
500 Point p1 = d->lp[0] + (ortho * 10.0 * scaledThickness);
501 Point p2 = d->lp[1] + (ortho * 10.0 * scaledThickness);
502 Point p3 = d->lp[0] + (ortho * 16.0 * scaledThickness);
503 Point p4 = d->lp[1] + (ortho * 16.0 * scaledThickness);
504 Point p5 = d->p[0] + (ortho * 4.0 * scaledThickness);
505 Point p6 = d->p[1] + (ortho * 4.0 * scaledThickness);
508 The numbers hardcoded into here, what are they?
509 I believe they are pixels.
511 // Draw extension lines (if certain type)
512 painter->DrawLine(p3, p5);
513 painter->DrawLine(p4, p6);
515 // Calculate whether or not the arrowheads are too crowded to put
516 // inside the extension lines. 9.0 is the length of the arrowhead.
517 double t = Geometry::ParameterOfLineAndPoint(d->lp[0], d->lp[1], d->lp[1] - (unit * 9.0 * scaledThickness));
519 // On the screen, it's acting like this is actually 58%...
520 // This is correct, we want it to happen at > 50%
523 // Draw main dimension line + arrowheads
524 painter->DrawLine(p1, p2);
525 painter->DrawArrowhead(p1, p2, scaledThickness);
526 painter->DrawArrowhead(p2, p1, scaledThickness);
530 // Draw outside arrowheads
531 Point p7 = p1 - (unit * 9.0 * scaledThickness);
532 Point p8 = p2 + (unit * 9.0 * scaledThickness);
533 painter->DrawArrowhead(p1, p7, scaledThickness);
534 painter->DrawArrowhead(p2, p8, scaledThickness);
535 painter->DrawLine(p1, p1 - (unit * 14.0 * scaledThickness));
536 painter->DrawLine(p2, p2 + (unit * 14.0 * scaledThickness));
539 // Draw length of dimension line...
540 painter->SetFont(QFont("Arial", 8.0 * Global::zoom * scaledThickness));
541 Point ctr = p2 + (Vector(p2, p1) / 2.0);
546 dimText = QString("%1\"").arg(length);
549 double feet = (double)((int)length / 12);
550 double inches = length - (feet * 12.0);
553 dimText = QString("%1'").arg(feet);
555 dimText = QString("%1' %2\"").arg(feet).arg(inches);
558 painter->DrawAngledText(ctr, angle, dimText, scaledThickness);
562 Point hp1 = (p1 + p2) / 2.0;
563 Point hp2 = (p1 + hp1) / 2.0;
564 Point hp3 = (hp1 + p2) / 2.0;
568 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
569 painter->SetBrush(QBrush(QColor(Qt::magenta)));
570 painter->DrawArrowHandle(hp1, ortho.Angle() + HALF_TAU);
571 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
574 painter->DrawHandle(hp1);
575 painter->SetPen(QPen(Qt::blue, 1.0 * Global::zoom * scaledThickness, Qt::SolidLine));
579 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
580 painter->SetBrush(QBrush(QColor(Qt::magenta)));
581 painter->DrawArrowToLineHandle(hp2, (d->subtype == DTLinearVert ? v.Angle() - QTR_TAU : (v.Angle() < HALF_TAU ? HALF_TAU : 0)));
582 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
585 painter->DrawHandle(hp2);
586 painter->SetPen(QPen(Qt::blue, 1.0 * Global::zoom * scaledThickness, Qt::SolidLine));
590 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
591 painter->SetBrush(QBrush(QColor(Qt::magenta)));
592 painter->DrawArrowToLineHandle(hp3, (d->subtype == DTLinearHorz ? v.Angle() - QTR_TAU : (v.Angle() > HALF_TAU && v.Angle() < THREE_QTR_TAU ? THREE_QTR_TAU : QTR_TAU)));
593 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
596 painter->DrawHandle(hp3);
599 if (obj->hitPoint[0])
600 painter->DrawHandle(obj->p[0]);
602 if (obj->hitPoint[1])
603 painter->DrawHandle(obj->p[1]);
609 Text * t = (Text *)obj;
610 painter->DrawTextObject(t->p[0], t->s.c_str(), scaledThickness);
623 // Containers require recursive rendering...
624 Container * c = (Container *)obj;
625 RenderObjects(painter, (*c).objects, layer);
627 //printf("Container extents: <%lf, %lf>, <%lf, %lf>\nsize: %i\n", r.l, r.t, r.r, r.b, c->objects.size());
628 // Containers also have special indicators showing they are selected
629 if (c->selected || c->hitObject)
631 Rect r = GetObjectExtents(obj);
632 painter->DrawRectCorners(r);
644 void DrawingView::AddHoveredToSelection(void)
646 std::vector<void *>::iterator i;
648 for(i=document.objects.begin(); i!=document.objects.end(); i++)
650 if (((Object *)(*i))->hovered)
651 ((Object *)(*i))->selected = true;
656 void DrawingView::GetSelection(std::vector<void *> & v)
659 std::vector<void *>::iterator i;
661 for(i=document.objects.begin(); i!=document.objects.end(); i++)
663 if (((Object *)(*i))->selected)
669 void DrawingView::GetHovered(std::vector<void *> & v)
672 std::vector<void *>::iterator i;
674 for(i=document.objects.begin(); i!=document.objects.end(); i++)
676 if (((Object *)(*i))->hovered)
678 //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"));
685 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
687 Global::screenSize = Vector(size().width(), size().height());
688 UpdateGridBackground();
692 void DrawingView::ToolHandler(int mode, Point p)
694 // Drop angle snap until it's needed
697 if (Global::tool == TTLine)
698 LineHandler(mode, p);
699 else if (Global::tool == TTCircle)
700 CircleHandler(mode, p);
701 else if (Global::tool == TTArc)
703 else if (Global::tool == TTRotate)
704 RotateHandler(mode, p);
705 else if (Global::tool == TTMirror)
706 MirrorHandler(mode, p);
710 void DrawingView::ToolDraw(Painter * painter)
712 if (Global::tool == TTLine)
714 if (Global::toolState == TSNone)
716 painter->DrawHandle(toolPoint[0]);
718 else if ((Global::toolState == TSPoint2) && shiftDown)
720 painter->DrawHandle(toolPoint[1]);
724 painter->DrawLine(toolPoint[0], toolPoint[1]);
725 painter->DrawHandle(toolPoint[1]);
727 Vector v(toolPoint[0], toolPoint[1]);
728 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
729 double absLength = v.Magnitude();
730 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
731 informativeText = text.arg(absLength).arg(absAngle);
734 else if (Global::tool == TTCircle)
736 if (Global::toolState == TSNone)
738 painter->DrawHandle(toolPoint[0]);
740 else if ((Global::toolState == TSPoint2) && shiftDown)
742 painter->DrawHandle(toolPoint[1]);
746 painter->DrawCross(toolPoint[0]);
747 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
748 painter->SetBrush(QBrush(Qt::NoBrush));
749 painter->DrawEllipse(toolPoint[0], length, length);
750 QString text = tr("Radius: %1 in.");
751 informativeText = text.arg(length);
754 else if (Global::tool == TTArc)
756 if (Global::toolState == TSNone)
758 painter->DrawHandle(toolPoint[0]);
760 else if (Global::toolState == TSPoint2)
762 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
763 painter->SetBrush(QBrush(Qt::NoBrush));
764 painter->DrawEllipse(toolPoint[0], length, length);
765 painter->DrawLine(toolPoint[0], toolPoint[1]);
766 painter->DrawHandle(toolPoint[1]);
767 QString text = tr("Radius: %1 in.");
768 informativeText = text.arg(length);
770 else if (Global::toolState == TSPoint3)
772 double angle = Vector::Angle(toolPoint[0], toolPoint[2]);
773 painter->DrawLine(toolPoint[0], toolPoint[2]);
774 painter->SetBrush(QBrush(Qt::NoBrush));
775 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
776 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
777 QString text = tr("Angle start: %1") + QChar(0x00B0);
778 informativeText = text.arg(RADIANS_TO_DEGREES * angle);
782 double angle = Vector::Angle(toolPoint[0], toolPoint[3]);
783 double span = angle - toolPoint[2].x;
788 painter->DrawLine(toolPoint[0], toolPoint[3]);
789 painter->SetBrush(QBrush(Qt::NoBrush));
790 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
791 painter->SetPen(0xFF00FF, 2.0, LSSolid);
792 painter->DrawArc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
793 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
794 QString text = tr("Arc span: %1") + QChar(0x00B0);
795 informativeText = text.arg(RADIANS_TO_DEGREES * span);
798 else if (Global::tool == TTRotate)
800 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
801 painter->DrawHandle(toolPoint[0]);
802 else if ((Global::toolState == TSPoint2) && shiftDown)
803 painter->DrawHandle(toolPoint[1]);
806 if (toolPoint[0] == toolPoint[1])
809 painter->DrawLine(toolPoint[0], toolPoint[1]);
811 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
812 QString text = QChar(0x2221) + QObject::tr(": %1");
813 informativeText = text.arg(absAngle);
816 informativeText += " (Copy)";
819 else if (Global::tool == TTMirror)
821 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
822 painter->DrawHandle(toolPoint[0]);
823 else if ((Global::toolState == TSPoint2) && shiftDown)
824 painter->DrawHandle(toolPoint[1]);
827 if (toolPoint[0] == toolPoint[1])
830 Point mirrorPoint = toolPoint[0] + Vector(toolPoint[1], toolPoint[0]);
831 painter->DrawLine(mirrorPoint, toolPoint[1]);
833 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
835 if (absAngle > 180.0)
838 QString text = QChar(0x2221) + QObject::tr(": %1");
839 informativeText = text.arg(absAngle);
842 informativeText += " (Copy)";
848 void DrawingView::LineHandler(int mode, Point p)
853 if (Global::toolState == TSNone)
860 if (Global::toolState == TSNone)
867 if (Global::toolState == TSNone)
869 Global::toolState = TSPoint2;
870 // Prevent spurious line from drawing...
871 toolPoint[1] = toolPoint[0];
873 else if ((Global::toolState == TSPoint2) && shiftDown)
875 // Key override is telling us to make a new line, not continue the
877 toolPoint[0] = toolPoint[1];
881 Line * l = new Line(toolPoint[0], toolPoint[1]);
882 l->layer = Global::activeLayer;
883 document.objects.push_back(l);
884 toolPoint[0] = toolPoint[1];
890 void DrawingView::CircleHandler(int mode, Point p)
895 if (Global::toolState == TSNone)
902 if (Global::toolState == TSNone)
909 if (Global::toolState == TSNone)
911 Global::toolState = TSPoint2;
912 // Prevent spurious line from drawing...
913 toolPoint[1] = toolPoint[0];
915 else if ((Global::toolState == TSPoint2) && shiftDown)
917 // Key override is telling us to make a new line, not continue the
919 toolPoint[0] = toolPoint[1];
923 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
924 Circle * c = new Circle(toolPoint[0], length);
925 c->layer = Global::activeLayer;
926 document.objects.push_back(c);
927 toolPoint[0] = toolPoint[1];
928 Global::toolState = TSNone;
934 void DrawingView::ArcHandler(int mode, Point p)
939 if (Global::toolState == TSNone)
941 else if (Global::toolState == TSPoint2)
943 else if (Global::toolState == TSPoint3)
950 if (Global::toolState == TSNone)
952 else if (Global::toolState == TSPoint2)
954 else if (Global::toolState == TSPoint3)
967 if (Global::toolState == TSNone)
969 // Prevent spurious line from drawing...
970 toolPoint[1] = toolPoint[0];
971 Global::toolState = TSPoint2;
973 else if (Global::toolState == TSPoint2)
977 // Key override is telling us to start arc at new center, not
978 // continue the current one.
979 toolPoint[0] = toolPoint[1];
983 // Set the radius in toolPoint[1].x
984 toolPoint[1].x = Vector::Magnitude(toolPoint[0], toolPoint[1]);
985 Global::toolState = TSPoint3;
987 else if (Global::toolState == TSPoint3)
989 // Set the angle in toolPoint[2].x
990 toolPoint[2].x = Vector::Angle(toolPoint[0], toolPoint[2]);
991 Global::toolState = TSPoint4;
995 double endAngle = Vector::Angle(toolPoint[0], toolPoint[3]);
996 double span = endAngle - toolPoint[2].x;
1001 Arc * arc = new Arc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
1002 arc->layer = Global::activeLayer;
1003 document.objects.push_back(arc);
1004 Global::toolState = TSNone;
1010 void DrawingView::RotateHandler(int mode, Point p)
1015 if (Global::toolState == TSNone)
1018 SavePointsFrom(select, toolScratch);
1019 Global::toolState = TSPoint1;
1021 else if (Global::toolState == TSPoint1)
1028 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1030 else if (Global::toolState == TSPoint2)
1038 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1039 std::vector<void *>::iterator j = select.begin();
1040 std::vector<Object>::iterator i = toolScratch.begin();
1042 for(; i!=toolScratch.end(); i++, j++)
1045 Point p1 = Geometry::RotatePointAroundPoint(obj.p[0], toolPoint[0], angle);
1046 Point p2 = Geometry::RotatePointAroundPoint(obj.p[1], toolPoint[0], angle);
1047 Object * obj2 = (Object *)(*j);
1051 if (obj.type == OTArc)
1053 obj2->angle[0] = obj.angle[0] + angle;
1055 if (obj2->angle[0] > TAU)
1056 obj2->angle[0] -= TAU;
1063 if (Global::toolState == TSPoint1)
1065 Global::toolState = TSPoint2;
1066 // Prevent spurious line from drawing...
1067 toolPoint[1] = toolPoint[0];
1069 else if ((Global::toolState == TSPoint2) && shiftDown)
1071 // Key override is telling us to make a new line, not continue the
1073 toolPoint[0] = toolPoint[1];
1077 // Either we're finished with our rotate, or we're stamping a copy.
1080 // Stamp a copy of the selection at the current rotation & bail
1081 std::vector<void *> temp;
1082 CopyObjects(select, temp);
1083 ClearSelected(temp);
1084 AddObjectsTo(document.objects, temp);
1085 RestorePointsTo(select, toolScratch);
1090 Global::toolState = TSPoint1;
1091 SavePointsFrom(select, toolScratch);
1096 // Reset the selection if shift held down...
1098 RestorePointsTo(select, toolScratch);
1102 // Reset selection when key is let up
1104 RotateHandler(ToolMouseMove, toolPoint[1]);
1108 RestorePointsTo(select, toolScratch);
1113 void DrawingView::MirrorHandler(int mode, Point p)
1118 if (Global::toolState == TSNone)
1121 SavePointsFrom(select, toolScratch);
1122 Global::toolState = TSPoint1;
1124 else if (Global::toolState == TSPoint1)
1131 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1133 else if (Global::toolState == TSPoint2)
1141 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1142 std::vector<void *>::iterator j = select.begin();
1143 std::vector<Object>::iterator i = toolScratch.begin();
1145 for(; i!=toolScratch.end(); i++, j++)
1148 Point p1 = Geometry::MirrorPointAroundLine(obj.p[0], toolPoint[0], toolPoint[1]);
1149 Point p2 = Geometry::MirrorPointAroundLine(obj.p[1], toolPoint[0], toolPoint[1]);
1150 Object * obj2 = (Object *)(*j);
1154 if (obj.type == OTArc)
1156 // This is 2*mirror angle - obj angle - obj span
1157 obj2->angle[0] = (2.0 * angle) - obj.angle[0] - obj.angle[1];
1159 if (obj2->angle[0] > TAU)
1160 obj2->angle[0] -= TAU;
1167 if (Global::toolState == TSPoint1)
1169 Global::toolState = TSPoint2;
1170 // Prevent spurious line from drawing...
1171 toolPoint[1] = toolPoint[0];
1173 else if ((Global::toolState == TSPoint2) && shiftDown)
1175 // Key override is telling us to make a new line, not continue the
1177 toolPoint[0] = toolPoint[1];
1181 // Either we're finished with our rotate, or we're stamping a copy.
1184 // Stamp a copy of the selection at the current rotation & bail
1185 std::vector<void *> temp;
1186 CopyObjects(select, temp);
1187 ClearSelected(temp);
1188 AddObjectsTo(document.objects, temp);
1189 RestorePointsTo(select, toolScratch);
1194 Global::toolState = TSPoint1;
1195 SavePointsFrom(select, toolScratch);
1200 // Reset the selection if shift held down...
1202 RestorePointsTo(select, toolScratch);
1206 // Reset selection when key is let up
1208 MirrorHandler(ToolMouseMove, toolPoint[1]);
1212 RestorePointsTo(select, toolScratch);
1217 void DrawingView::mousePressEvent(QMouseEvent * event)
1219 if (event->button() == Qt::LeftButton)
1221 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1223 // Handle tool processing, if any
1226 if (hoveringIntersection)
1227 point = intersectionPoint;
1228 else if (Global::snapToGrid)
1229 point = SnapPointToGrid(point);
1231 //Also, may want to figure out if hovering over a snap point on an
1232 //object, snap to grid if not.
1233 // Snap to object point if valid...
1234 // if (Global::snapPointIsValid)
1235 // point = Global::snapPoint;
1237 ToolHandler(ToolMouseDown, point);
1241 // Clear the selection only if CTRL isn't being held on click
1243 ClearSelected(document.objects);
1244 // ClearSelection();
1246 // If any objects are being hovered on click, add them to the selection
1250 AddHoveredToSelection();
1251 update(); // needed??
1252 GetHovered(hover); // prolly needed
1253 dragged = (Object *)hover[0];
1254 draggingObject = true;
1256 // See if anything is using just a straight click on a handle
1257 if (HandleObjectClicked())
1259 draggingObject = false;
1264 // Needed for grab & moving objects
1265 // We do it *after*... why? (doesn't seem to confer any advantage...)
1266 if (hoveringIntersection)
1267 oldPoint = intersectionPoint;
1268 else if (Global::snapToGrid)
1269 oldPoint = SnapPointToGrid(point);
1274 // Didn't hit any object and not using a tool, so do a selection rectangle
1275 Global::selectionInProgress = true;
1276 Global::selection.setTopLeft(QPointF(point.x, point.y));
1277 Global::selection.setBottomRight(QPointF(point.x, point.y));
1279 else if (event->button() == Qt::MiddleButton)
1282 oldPoint = Vector(event->x(), event->y());
1283 // Should also change the mouse pointer as well...
1284 setCursor(Qt::SizeAllCursor);
1289 void DrawingView::mouseMoveEvent(QMouseEvent * event)
1291 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1292 Global::selection.setBottomRight(QPointF(point.x, point.y));
1293 // Only needs to be done here, as mouse down is always preceded by movement
1294 Global::snapPointIsValid = false;
1295 hoveringIntersection = false;
1298 if (event->buttons() & Qt::MiddleButton)
1300 point = Vector(event->x(), event->y());
1301 // Since we're using Qt coords for scrolling, we have to adjust them
1302 // here to conform to Cartesian coords, since the origin is using
1304 Vector delta(oldPoint, point);
1305 delta /= Global::zoom;
1307 Global::origin -= delta;
1309 UpdateGridBackground();
1315 // If we're doing a selection rect, see if any objects are engulfed by it
1316 // (implies left mouse button held down)
1317 if (Global::selectionInProgress)
1319 CheckObjectBounds();
1324 // Do object hit testing...
1325 bool needUpdate = HitTestObjects(point);
1328 // Check for multi-hover...
1331 //need to check for case where hover is over 2 circles and a 3rd's center...
1332 Object * obj1 = (Object *)hover[0], * obj2 = (Object *)hover[1];
1334 Geometry::Intersects(obj1, obj2);
1335 int numIntersecting = Global::numIntersectParams;
1336 double t = Global::intersectParam[0];
1337 double u = Global::intersectParam[1];
1339 if (numIntersecting > 0)
1341 Vector v1 = Geometry::GetPointForParameter(obj1, t);
1342 Vector v2 = Geometry::GetPointForParameter(obj2, u);
1343 QString text = tr("Intersection t=%1 (%3, %4), u=%2 (%5, %6)");
1344 informativeText = text.arg(t).arg(u).arg(v1.x).arg(v1.y).arg(v2.x).arg(v2.y);
1346 hoveringIntersection = true;
1347 intersectionPoint = v1;
1350 numIntersecting = Global::numIntersectPoints;
1352 if (numIntersecting > 0)
1354 Vector v1 = Global::intersectPoint[0];
1356 if (numIntersecting == 2)
1358 Vector v2 = Global::intersectPoint[1];
1360 if (Vector::Magnitude(v2, point) < Vector::Magnitude(v1, point))
1364 QString text = tr("Intersection <%1, %2>");
1365 informativeText = text.arg(v1.x).arg(v1.y);
1366 hoveringIntersection = true;
1367 intersectionPoint = v1;
1371 // Handle object movement (left button down & over an object)
1372 if ((event->buttons() & Qt::LeftButton) && draggingObject && !Global::tool)
1374 if (hoveringIntersection)
1375 point = intersectionPoint;
1376 else if (hoverPointValid)
1378 else if (Global::snapToGrid)
1379 point = SnapPointToGrid(point);
1381 HandleObjectMovement(point);
1387 // Do tool handling, if any are active...
1390 if (hoveringIntersection)
1391 point = intersectionPoint;
1392 else if (hoverPointValid)
1394 else if (Global::snapToGrid)
1397 point = SnapPointToAngle(point);
1399 point = SnapPointToGrid(point);
1402 ToolHandler(ToolMouseMove, point);
1405 // This is used to draw the tool crosshair...
1408 if (needUpdate || Global::tool)
1413 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
1415 if (event->button() == Qt::LeftButton)
1417 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
1418 //could set it up to use the document's update function (assumes that all object
1419 //updates are being reported correctly:
1420 // if (document.NeedsUpdate())
1421 // Do an update if collided with at least *one* object in the document
1427 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1428 ToolHandler(ToolMouseUp, point);
1432 if (Global::selectionInProgress)
1433 Global::selectionInProgress = false;
1435 informativeText.clear();
1436 // Should we be doing this automagically? Hmm...
1437 // Clear our vectors
1442 std::vector<void *>::iterator i;
1444 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1446 if (((Object *)(*i))->selected)
1447 select.push_back(*i);
1450 draggingObject = false;
1452 else if (event->button() == Qt::MiddleButton)
1455 setCursor(Qt::ArrowCursor);
1460 void DrawingView::wheelEvent(QWheelEvent * event)
1462 double zoomFactor = 1.25;
1463 QSize sizeWin = size();
1464 Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
1465 center = Painter::QtToCartesianCoords(center);
1467 // This is not centering for some reason. Need to figure out why. :-/
1468 if (event->delta() > 0)
1470 Vector newOrigin = center - ((center - Global::origin) / zoomFactor);
1471 Global::origin = newOrigin;
1472 Global::zoom *= zoomFactor;
1476 Vector newOrigin = center + ((-center + Global::origin) * zoomFactor);
1477 Global::origin = newOrigin;
1478 Global::zoom /= zoomFactor;
1481 // Global::gridSpacing = gridPixels / Painter::zoom;
1482 // UpdateGridBackground();
1483 SetGridSize(Global::gridSpacing * Global::zoom);
1485 // zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Global::gridSpacing));
1489 void DrawingView::keyPressEvent(QKeyEvent * event)
1491 bool oldShift = shiftDown;
1492 bool oldCtrl = ctrlDown;
1494 if (event->key() == Qt::Key_Shift)
1496 else if (event->key() == Qt::Key_Control)
1499 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1502 ToolHandler(ToolKeyDown, Point(0, 0));
1507 if (select.size() > 0)
1509 if (event->key() == Qt::Key_Up)
1511 TranslateObjects(select, Point(0, +1.0));
1514 else if (event->key() == Qt::Key_Down)
1516 TranslateObjects(select, Point(0, -1.0));
1519 else if (event->key() == Qt::Key_Right)
1521 TranslateObjects(select, Point(+1.0, 0));
1524 else if (event->key() == Qt::Key_Left)
1526 TranslateObjects(select, Point(-1.0, 0));
1533 void DrawingView::keyReleaseEvent(QKeyEvent * event)
1535 bool oldShift = shiftDown;
1536 bool oldCtrl = ctrlDown;
1538 if (event->key() == Qt::Key_Shift)
1540 else if (event->key() == Qt::Key_Control)
1543 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1546 ToolHandler(ToolKeyUp, Point(0, 0));
1554 // This looks strange, but it's really quite simple: We want a point that's
1555 // more than half-way to the next grid point to snap there while conversely we
1556 // want a point that's less than half-way to to the next grid point then snap
1557 // to the one before it. So we add half of the grid spacing to the point, then
1558 // divide by it so that we can remove the fractional part, then multiply it
1559 // back to get back to the correct answer.
1561 Point DrawingView::SnapPointToGrid(Point point)
1563 point += Global::gridSpacing / 2.0; // *This* adds to Z!!!
1564 point /= Global::gridSpacing;
1565 point.x = floor(point.x);//need to fix this for negative numbers...
1566 point.y = floor(point.y);
1567 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
1568 point *= Global::gridSpacing;
1573 Point DrawingView::SnapPointToAngle(Point point)
1575 // Snap to a single digit angle (using toolpoint #1 as the center)
1576 double angle = Vector::Angle(toolPoint[0], point);
1577 double length = Vector::Magnitude(toolPoint[0], point);
1579 // Convert from radians to degrees
1580 double degAngle = angle * RADIANS_TO_DEGREES;
1581 double snapAngle = (double)((int)(degAngle + 0.5));
1584 v.SetAngleAndLength(snapAngle * DEGREES_TO_RADIANS, length);
1585 point = toolPoint[0] + v;
1591 Rect DrawingView::GetObjectExtents(Object * obj)
1593 // Default to empty rect, if object checks below fail for some reason
1601 rect = Rect(obj->p[0], obj->p[1]);
1606 rect = Rect(obj->p[0], obj->p[0]);
1607 rect.Expand(obj->radius[0]);
1612 Arc * a = (Arc *)obj;
1614 double start = a->angle[0];
1615 double end = start + a->angle[1];
1616 rect = Rect(Point(cos(start), sin(start)), Point(cos(end), sin(end)));
1618 // If the end of the arc is before the beginning, add 360 degrees to it
1622 // Adjust the bounds depending on which axes are crossed
1623 if ((start < QTR_TAU) && (end > QTR_TAU))
1626 if ((start < HALF_TAU) && (end > HALF_TAU))
1629 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
1632 if ((start < TAU) && (end > TAU))
1635 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
1638 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
1641 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
1644 rect *= a->radius[0];
1645 rect.Translate(a->p[0]);
1651 Container * c = (Container *)obj;
1652 std::vector<void *>::iterator i = c->objects.begin();
1653 rect = GetObjectExtents((Object *)*i);
1656 for(; i!=c->objects.end(); i++)
1657 rect |= GetObjectExtents((Object *)*i);
1667 void DrawingView::CheckObjectBounds(void)
1669 std::vector<void *>::iterator i;
1671 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1673 Object * obj = (Object *)(*i);
1674 obj->selected = false;
1681 Line * l = (Line *)obj;
1683 if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
1690 Circle * c = (Circle *)obj;
1692 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]))
1699 Arc * a = (Arc *)obj;
1701 double start = a->angle[0];
1702 double end = start + a->angle[1];
1703 QPointF p1(cos(start), sin(start));
1704 QPointF p2(cos(end), sin(end));
1705 QRectF bounds(p1, p2);
1708 // Swap X/Y coordinates if they're backwards...
1709 if (bounds.left() > bounds.right())
1711 double temp = bounds.left();
1712 bounds.setLeft(bounds.right());
1713 bounds.setRight(temp);
1716 if (bounds.bottom() > bounds.top())
1718 double temp = bounds.bottom();
1719 bounds.setBottom(bounds.top());
1720 bounds.setTop(temp);
1723 // Doesn't work as advertised! For shame!
1724 bounds = bounds.normalized();
1727 // If the end of the arc is before the beginning, add 360 degrees to it
1731 // Adjust the bounds depending on which axes are crossed
1732 if ((start < QTR_TAU) && (end > QTR_TAU))
1735 if ((start < HALF_TAU) && (end > HALF_TAU))
1736 bounds.setLeft(-1.0);
1738 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
1739 bounds.setBottom(-1.0);
1741 if ((start < TAU) && (end > TAU))
1742 bounds.setRight(1.0);
1744 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
1747 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
1748 bounds.setLeft(-1.0);
1750 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
1751 bounds.setBottom(-1.0);
1753 bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
1754 bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
1755 bounds.translate(a->p[0].x, a->p[0].y);
1757 if (Global::selection.contains(bounds))
1769 bool DrawingView::HitTestObjects(Point point)
1771 std::vector<void *>::iterator i;
1773 bool needUpdate = false;
1774 hoverPointValid = false;
1776 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1778 Object * obj = (Object *)(*i);
1780 // If we're seeing the object we're dragging, skip it
1781 if (draggingObject && (obj == dragged))
1784 if (HitTest(obj, point))
1790 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
1791 emit(ObjectHovered(obj));
1799 bool DrawingView::HitTest(Object * obj, Point point)
1801 bool needUpdate = false;
1807 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
1808 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
1809 Vector lineSegment = obj->p[1] - obj->p[0];
1810 Vector v1 = point - obj->p[0];
1811 Vector v2 = point - obj->p[1];
1812 double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
1816 distance = v1.Magnitude();
1818 distance = v2.Magnitude();
1820 // distance = ?Det?(ls, v1) / |ls|
1821 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
1822 / lineSegment.Magnitude());
1824 if ((v1.Magnitude() * Global::zoom) < 8.0)
1826 obj->hitPoint[0] = true;
1827 hoverPoint = obj->p[0];
1828 hoverPointValid = true;
1830 else if ((v2.Magnitude() * Global::zoom) < 8.0)
1832 obj->hitPoint[1] = true;
1833 hoverPoint = obj->p[1];
1834 hoverPointValid = true;
1836 else if ((distance * Global::zoom) < 5.0)
1837 obj->hitObject = true;
1839 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
1841 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
1848 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
1849 obj->hitPoint[0] = obj->hitObject = false;
1850 double length = Vector::Magnitude(obj->p[0], point);
1852 if ((length * Global::zoom) < 8.0)
1853 obj->hitPoint[0] = true;
1854 else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
1855 obj->hitObject = true;
1857 obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
1859 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
1866 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
1867 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
1868 double length = Vector::Magnitude(obj->p[0], point);
1869 double angle = Vector::Angle(obj->p[0], point);
1871 // Make sure we get the angle in the correct spot
1872 if (angle < obj->angle[0])
1875 // Get the span that we're pointing at...
1876 double span = angle - obj->angle[0];
1878 // N.B.: Still need to hit test the arc start & arc span handles...
1879 double spanAngle = obj->angle[0] + obj->angle[1];
1880 Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
1881 Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
1882 double length2 = Vector::Magnitude(point, handle1);
1883 double length3 = Vector::Magnitude(point, handle2);
1885 if ((length * Global::zoom) < 8.0)
1886 obj->hitPoint[0] = true;
1887 else if ((length2 * Global::zoom) < 8.0)
1889 obj->hitPoint[1] = true;
1890 hoverPoint = handle1;
1891 hoverPointValid = true;
1893 else if ((length3 * Global::zoom) < 8.0)
1895 obj->hitPoint[2] = true;
1896 hoverPoint = handle2;
1897 hoverPointValid = true;
1899 else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
1900 obj->hitObject = true;
1902 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
1904 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
1911 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHP3 = obj->hitPoint[3], oldHP4 = obj->hitPoint[4], oldHO = obj->hitObject;
1912 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitPoint[3] = obj->hitPoint[4] = obj->hitObject = false;
1914 Dimension * d = (Dimension *)obj;
1916 Vector orthogonal = Vector::Normal(d->lp[0], d->lp[1]);
1917 // Get our line parallel to our points
1918 float scaledThickness = Global::scale * obj->thickness;
1919 Point p1 = d->lp[0] + (orthogonal * 10.0 * scaledThickness);
1920 Point p2 = d->lp[1] + (orthogonal * 10.0 * scaledThickness);
1921 Point p3(p1, point);
1923 Vector v1(d->p[0], point);
1924 Vector v2(d->p[1], point);
1925 Vector lineSegment(p1, p2);
1926 double t = Geometry::ParameterOfLineAndPoint(p1, p2, point);
1928 Point midpoint = (p1 + p2) / 2.0;
1929 Point hFSPoint = Point(midpoint, point);
1930 Point hCS1Point = Point((p1 + midpoint) / 2.0, point);
1931 Point hCS2Point = Point((midpoint + p2) / 2.0, point);
1934 distance = v1.Magnitude();
1936 distance = v2.Magnitude();
1938 // distance = ?Det?(ls, v1) / |ls|
1939 distance = fabs((lineSegment.x * p3.y - p3.x * lineSegment.y)
1940 / lineSegment.Magnitude());
1942 if ((v1.Magnitude() * Global::zoom) < 8.0)
1943 obj->hitPoint[0] = true;
1944 else if ((v2.Magnitude() * Global::zoom) < 8.0)
1945 obj->hitPoint[1] = true;
1946 else if ((distance * Global::zoom) < 5.0)
1947 obj->hitObject = true;
1949 if ((hFSPoint.Magnitude() * Global::zoom) < 8.0)
1950 obj->hitPoint[2] = true;
1951 else if ((hCS1Point.Magnitude() * Global::zoom) < 8.0)
1952 obj->hitPoint[3] = true;
1953 else if ((hCS2Point.Magnitude() * Global::zoom) < 8.0)
1954 obj->hitPoint[4] = true;
1956 // return (hitPoint1 || hitPoint2 || hitLine || hitFlipSwitch || hitChangeSwitch1 || hitChangeSwitch2 ? true : false);
1957 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitPoint[3] || obj->hitPoint[4] || obj->hitObject ? true : false);
1959 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHP3 != obj->hitPoint[3]) || (oldHP4 != obj->hitPoint[4]) || (oldHO != obj->hitObject))
1967 // Containers must be recursively tested...
1968 Container * c = (Container *)obj;
1969 c->hitObject = false;
1971 std::vector<void *>::iterator i;
1973 for(i=c->objects.begin(); i!=c->objects.end(); i++)
1975 Object * cObj = (Object *)*i;
1977 if (HitTest(cObj, point))
1980 if (cObj->hitObject == true)
1981 c->hitObject = true;
1983 if (cObj->hovered == true)
1997 bool DrawingView::HandleObjectClicked(void)
1999 if (dragged->type == OTDimension)
2001 Dimension * d = (Dimension *)dragged;
2005 // Hit the "flip sides" switch, so flip 'em
2006 Point temp = d->p[0];
2011 else if (d->hitPoint[3])
2013 // There are three cases here: aligned, horizontal, & vertical.
2014 // Aligned and horizontal do the same thing, vertical goes back to
2016 if (d->subtype == DTLinearVert)
2017 d->subtype = DTLinear;
2019 d->subtype = DTLinearVert;
2023 else if (d->hitPoint[4])
2025 // There are three cases here: aligned, horizontal, & vertical.
2026 // Aligned and vertical do the same thing, horizontal goes back to
2028 if (d->subtype == DTLinearHorz)
2029 d->subtype = DTLinear;
2031 d->subtype = DTLinearHorz;
2041 void DrawingView::HandleObjectMovement(Point point)
2043 Point delta = point - oldPoint;
2044 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
2045 // Object * obj = (Object *)hover[0];
2046 Object * obj = dragged;
2047 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
2048 //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"));
2053 if (obj->hitPoint[0])
2055 else if (obj->hitPoint[1])
2057 else if (obj->hitObject)
2066 if (obj->hitPoint[0])
2068 else if (obj->hitObject)
2070 //this doesn't work. we need to save this on mouse down for this to work correctly!
2071 // double oldRadius = obj->radius[0];
2072 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
2074 QString text = QObject::tr("Radius: %1");//\nScale: %2%");
2075 informativeText = text.arg(obj->radius[0], 0, 'd', 4);//.arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0);
2081 if (obj->hitPoint[0])
2083 else if (obj->hitPoint[1])
2085 // Change the Arc's span (handle #1)
2088 double angle = Vector::Angle(obj->p[0], point);
2089 double delta = angle - obj->angle[0];
2094 obj->angle[1] -= delta;
2095 obj->angle[0] = angle;
2097 if (obj->angle[1] < 0)
2098 obj->angle[1] += TAU;
2100 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
2101 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);
2105 double angle = Vector::Angle(obj->p[0], point);
2106 obj->angle[0] = angle;
2107 QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
2108 informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 4);
2110 else if (obj->hitPoint[2])
2112 // Change the Arc's span (handle #2)
2115 double angle = Vector::Angle(obj->p[0], point);
2116 obj->angle[1] = angle - obj->angle[0];
2118 if (obj->angle[1] < 0)
2119 obj->angle[1] += TAU;
2121 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
2122 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);
2126 double angle = Vector::Angle(obj->p[0], point);
2127 obj->angle[0] = angle - obj->angle[1];
2129 if (obj->angle[0] < 0)
2130 obj->angle[0] += TAU;
2132 QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
2133 informativeText = text.arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 4);
2135 else if (obj->hitObject)
2142 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
2143 QString text = QObject::tr("Radius: %1");
2144 informativeText = text.arg(obj->radius[0], 0, 'd', 4);
2150 if (obj->hitPoint[0])
2152 else if (obj->hitPoint[1])
2154 else if (obj->hitObject)
2163 // This is shitty, but works for now until I can code up something
2165 TranslateObject(obj, delta);