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;
611 if (t->measured == false)
613 t->extents = painter->MeasureTextObject(t->s.c_str(), scaledThickness);
617 painter->DrawTextObject(t->p[0], t->s.c_str(), scaledThickness, t->angle[0]);
630 // Containers require recursive rendering...
631 Container * c = (Container *)obj;
632 RenderObjects(painter, (*c).objects, layer);
634 //printf("Container extents: <%lf, %lf>, <%lf, %lf>\nsize: %i\n", r.l, r.t, r.r, r.b, c->objects.size());
635 // Containers also have special indicators showing they are selected
636 if (c->selected || c->hitObject)
638 Rect r = GetObjectExtents(obj);
639 painter->DrawRectCorners(r);
651 void DrawingView::AddHoveredToSelection(void)
653 std::vector<void *>::iterator i;
655 for(i=document.objects.begin(); i!=document.objects.end(); i++)
657 if (((Object *)(*i))->hovered)
658 ((Object *)(*i))->selected = true;
663 void DrawingView::GetSelection(std::vector<void *> & v)
666 std::vector<void *>::iterator i;
668 for(i=document.objects.begin(); i!=document.objects.end(); i++)
670 if (((Object *)(*i))->selected)
676 void DrawingView::GetHovered(std::vector<void *> & v)
679 std::vector<void *>::iterator i;
681 for(i=document.objects.begin(); i!=document.objects.end(); i++)
683 if (((Object *)(*i))->hovered)
685 //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"));
692 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
694 Global::screenSize = Vector(size().width(), size().height());
695 UpdateGridBackground();
699 void DrawingView::ToolHandler(int mode, Point p)
701 // Drop angle snap until it's needed
704 if (Global::tool == TTLine)
705 LineHandler(mode, p);
706 else if (Global::tool == TTCircle)
707 CircleHandler(mode, p);
708 else if (Global::tool == TTArc)
710 else if (Global::tool == TTRotate)
711 RotateHandler(mode, p);
712 else if (Global::tool == TTMirror)
713 MirrorHandler(mode, p);
717 void DrawingView::ToolDraw(Painter * painter)
719 if (Global::tool == TTLine)
721 if (Global::toolState == TSNone)
723 painter->DrawHandle(toolPoint[0]);
725 else if ((Global::toolState == TSPoint2) && shiftDown)
727 painter->DrawHandle(toolPoint[1]);
731 painter->DrawLine(toolPoint[0], toolPoint[1]);
732 painter->DrawHandle(toolPoint[1]);
734 Vector v(toolPoint[0], toolPoint[1]);
735 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
736 double absLength = v.Magnitude();
737 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
738 informativeText = text.arg(absLength).arg(absAngle);
741 else if (Global::tool == TTCircle)
743 if (Global::toolState == TSNone)
745 painter->DrawHandle(toolPoint[0]);
747 else if ((Global::toolState == TSPoint2) && shiftDown)
749 painter->DrawHandle(toolPoint[1]);
753 painter->DrawCross(toolPoint[0]);
754 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
755 painter->SetBrush(QBrush(Qt::NoBrush));
756 painter->DrawEllipse(toolPoint[0], length, length);
757 QString text = tr("Radius: %1 in.");
758 informativeText = text.arg(length);
761 else if (Global::tool == TTArc)
763 if (Global::toolState == TSNone)
765 painter->DrawHandle(toolPoint[0]);
767 else if (Global::toolState == TSPoint2)
769 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
770 painter->SetBrush(QBrush(Qt::NoBrush));
771 painter->DrawEllipse(toolPoint[0], length, length);
772 painter->DrawLine(toolPoint[0], toolPoint[1]);
773 painter->DrawHandle(toolPoint[1]);
774 QString text = tr("Radius: %1 in.");
775 informativeText = text.arg(length);
777 else if (Global::toolState == TSPoint3)
779 double angle = Vector::Angle(toolPoint[0], toolPoint[2]);
780 painter->DrawLine(toolPoint[0], toolPoint[2]);
781 painter->SetBrush(QBrush(Qt::NoBrush));
782 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
783 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
784 QString text = tr("Angle start: %1") + QChar(0x00B0);
785 informativeText = text.arg(RADIANS_TO_DEGREES * angle);
789 double angle = Vector::Angle(toolPoint[0], toolPoint[3]);
790 double span = angle - toolPoint[2].x;
795 painter->DrawLine(toolPoint[0], toolPoint[3]);
796 painter->SetBrush(QBrush(Qt::NoBrush));
797 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
798 painter->SetPen(0xFF00FF, 2.0, LSSolid);
799 painter->DrawArc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
800 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
801 QString text = tr("Arc span: %1") + QChar(0x00B0);
802 informativeText = text.arg(RADIANS_TO_DEGREES * span);
805 else if (Global::tool == TTRotate)
807 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
808 painter->DrawHandle(toolPoint[0]);
809 else if ((Global::toolState == TSPoint2) && shiftDown)
810 painter->DrawHandle(toolPoint[1]);
813 if (toolPoint[0] == toolPoint[1])
816 painter->DrawLine(toolPoint[0], toolPoint[1]);
818 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
819 QString text = QChar(0x2221) + QObject::tr(": %1");
820 informativeText = text.arg(absAngle);
823 informativeText += " (Copy)";
826 else if (Global::tool == TTMirror)
828 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
829 painter->DrawHandle(toolPoint[0]);
830 else if ((Global::toolState == TSPoint2) && shiftDown)
831 painter->DrawHandle(toolPoint[1]);
834 if (toolPoint[0] == toolPoint[1])
837 Point mirrorPoint = toolPoint[0] + Vector(toolPoint[1], toolPoint[0]);
838 painter->DrawLine(mirrorPoint, toolPoint[1]);
840 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
842 if (absAngle > 180.0)
845 QString text = QChar(0x2221) + QObject::tr(": %1");
846 informativeText = text.arg(absAngle);
849 informativeText += " (Copy)";
855 void DrawingView::LineHandler(int mode, Point p)
860 if (Global::toolState == TSNone)
867 if (Global::toolState == TSNone)
874 if (Global::toolState == TSNone)
876 Global::toolState = TSPoint2;
877 // Prevent spurious line from drawing...
878 toolPoint[1] = toolPoint[0];
880 else if ((Global::toolState == TSPoint2) && shiftDown)
882 // Key override is telling us to make a new line, not continue the
884 toolPoint[0] = toolPoint[1];
888 Line * l = new Line(toolPoint[0], toolPoint[1]);
889 l->layer = Global::activeLayer;
890 document.objects.push_back(l);
891 toolPoint[0] = toolPoint[1];
897 void DrawingView::CircleHandler(int mode, Point p)
902 if (Global::toolState == TSNone)
909 if (Global::toolState == TSNone)
916 if (Global::toolState == TSNone)
918 Global::toolState = TSPoint2;
919 // Prevent spurious line from drawing...
920 toolPoint[1] = toolPoint[0];
922 else if ((Global::toolState == TSPoint2) && shiftDown)
924 // Key override is telling us to make a new line, not continue the
926 toolPoint[0] = toolPoint[1];
930 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
931 Circle * c = new Circle(toolPoint[0], length);
932 c->layer = Global::activeLayer;
933 document.objects.push_back(c);
934 toolPoint[0] = toolPoint[1];
935 Global::toolState = TSNone;
941 void DrawingView::ArcHandler(int mode, Point p)
946 if (Global::toolState == TSNone)
948 else if (Global::toolState == TSPoint2)
950 else if (Global::toolState == TSPoint3)
957 if (Global::toolState == TSNone)
959 else if (Global::toolState == TSPoint2)
961 else if (Global::toolState == TSPoint3)
974 if (Global::toolState == TSNone)
976 // Prevent spurious line from drawing...
977 toolPoint[1] = toolPoint[0];
978 Global::toolState = TSPoint2;
980 else if (Global::toolState == TSPoint2)
984 // Key override is telling us to start arc at new center, not
985 // continue the current one.
986 toolPoint[0] = toolPoint[1];
990 // Set the radius in toolPoint[1].x
991 toolPoint[1].x = Vector::Magnitude(toolPoint[0], toolPoint[1]);
992 Global::toolState = TSPoint3;
994 else if (Global::toolState == TSPoint3)
996 // Set the angle in toolPoint[2].x
997 toolPoint[2].x = Vector::Angle(toolPoint[0], toolPoint[2]);
998 Global::toolState = TSPoint4;
1002 double endAngle = Vector::Angle(toolPoint[0], toolPoint[3]);
1003 double span = endAngle - toolPoint[2].x;
1008 Arc * arc = new Arc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
1009 arc->layer = Global::activeLayer;
1010 document.objects.push_back(arc);
1011 Global::toolState = TSNone;
1017 void DrawingView::RotateHandler(int mode, Point p)
1022 if (Global::toolState == TSNone)
1025 SavePointsFrom(select, toolScratch);
1026 Global::toolState = TSPoint1;
1028 else if (Global::toolState == TSPoint1)
1035 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1037 else if (Global::toolState == TSPoint2)
1045 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1046 std::vector<void *>::iterator j = select.begin();
1047 std::vector<Object>::iterator i = toolScratch.begin();
1049 for(; i!=toolScratch.end(); i++, j++)
1052 Point p1 = Geometry::RotatePointAroundPoint(obj.p[0], toolPoint[0], angle);
1053 Point p2 = Geometry::RotatePointAroundPoint(obj.p[1], toolPoint[0], angle);
1054 Object * obj2 = (Object *)(*j);
1058 if (obj.type == OTArc)
1060 obj2->angle[0] = obj.angle[0] + angle;
1062 if (obj2->angle[0] > TAU)
1063 obj2->angle[0] -= TAU;
1070 if (Global::toolState == TSPoint1)
1072 Global::toolState = TSPoint2;
1073 // Prevent spurious line from drawing...
1074 toolPoint[1] = toolPoint[0];
1076 else if ((Global::toolState == TSPoint2) && shiftDown)
1078 // Key override is telling us to make a new line, not continue the
1080 toolPoint[0] = toolPoint[1];
1084 // Either we're finished with our rotate, or we're stamping a copy.
1087 // Stamp a copy of the selection at the current rotation & bail
1088 std::vector<void *> temp;
1089 CopyObjects(select, temp);
1090 ClearSelected(temp);
1091 AddObjectsTo(document.objects, temp);
1092 RestorePointsTo(select, toolScratch);
1097 Global::toolState = TSPoint1;
1098 SavePointsFrom(select, toolScratch);
1103 // Reset the selection if shift held down...
1105 RestorePointsTo(select, toolScratch);
1109 // Reset selection when key is let up
1111 RotateHandler(ToolMouseMove, toolPoint[1]);
1115 RestorePointsTo(select, toolScratch);
1120 void DrawingView::MirrorHandler(int mode, Point p)
1125 if (Global::toolState == TSNone)
1128 SavePointsFrom(select, toolScratch);
1129 Global::toolState = TSPoint1;
1131 else if (Global::toolState == TSPoint1)
1138 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1140 else if (Global::toolState == TSPoint2)
1148 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1149 std::vector<void *>::iterator j = select.begin();
1150 std::vector<Object>::iterator i = toolScratch.begin();
1152 for(; i!=toolScratch.end(); i++, j++)
1155 Point p1 = Geometry::MirrorPointAroundLine(obj.p[0], toolPoint[0], toolPoint[1]);
1156 Point p2 = Geometry::MirrorPointAroundLine(obj.p[1], toolPoint[0], toolPoint[1]);
1157 Object * obj2 = (Object *)(*j);
1161 if (obj.type == OTArc)
1163 // This is 2*mirror angle - obj angle - obj span
1164 obj2->angle[0] = (2.0 * angle) - obj.angle[0] - obj.angle[1];
1166 if (obj2->angle[0] > TAU)
1167 obj2->angle[0] -= TAU;
1174 if (Global::toolState == TSPoint1)
1176 Global::toolState = TSPoint2;
1177 // Prevent spurious line from drawing...
1178 toolPoint[1] = toolPoint[0];
1180 else if ((Global::toolState == TSPoint2) && shiftDown)
1182 // Key override is telling us to make a new line, not continue the
1184 toolPoint[0] = toolPoint[1];
1188 // Either we're finished with our rotate, or we're stamping a copy.
1191 // Stamp a copy of the selection at the current rotation & bail
1192 std::vector<void *> temp;
1193 CopyObjects(select, temp);
1194 ClearSelected(temp);
1195 AddObjectsTo(document.objects, temp);
1196 RestorePointsTo(select, toolScratch);
1201 Global::toolState = TSPoint1;
1202 SavePointsFrom(select, toolScratch);
1207 // Reset the selection if shift held down...
1209 RestorePointsTo(select, toolScratch);
1213 // Reset selection when key is let up
1215 MirrorHandler(ToolMouseMove, toolPoint[1]);
1219 RestorePointsTo(select, toolScratch);
1224 void DrawingView::mousePressEvent(QMouseEvent * event)
1226 if (event->button() == Qt::LeftButton)
1228 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1230 // Handle tool processing, if any
1233 if (hoveringIntersection)
1234 point = intersectionPoint;
1235 else if (Global::snapToGrid)
1236 point = SnapPointToGrid(point);
1238 //Also, may want to figure out if hovering over a snap point on an
1239 //object, snap to grid if not.
1240 // Snap to object point if valid...
1241 // if (Global::snapPointIsValid)
1242 // point = Global::snapPoint;
1244 ToolHandler(ToolMouseDown, point);
1248 // Clear the selection only if CTRL isn't being held on click
1250 ClearSelected(document.objects);
1251 // ClearSelection();
1253 // If any objects are being hovered on click, add them to the selection
1257 AddHoveredToSelection();
1258 update(); // needed??
1259 GetHovered(hover); // prolly needed
1260 dragged = (Object *)hover[0];
1261 draggingObject = true;
1263 // See if anything is using just a straight click on a handle
1264 if (HandleObjectClicked())
1266 draggingObject = false;
1271 // Needed for grab & moving objects
1272 // We do it *after*... why? (doesn't seem to confer any advantage...)
1273 if (hoveringIntersection)
1274 oldPoint = intersectionPoint;
1275 else if (Global::snapToGrid)
1276 oldPoint = SnapPointToGrid(point);
1281 // Didn't hit any object and not using a tool, so do a selection rectangle
1282 Global::selectionInProgress = true;
1283 Global::selection.setTopLeft(QPointF(point.x, point.y));
1284 Global::selection.setBottomRight(QPointF(point.x, point.y));
1286 else if (event->button() == Qt::MiddleButton)
1289 oldPoint = Vector(event->x(), event->y());
1290 // Should also change the mouse pointer as well...
1291 setCursor(Qt::SizeAllCursor);
1296 void DrawingView::mouseMoveEvent(QMouseEvent * event)
1298 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1299 Global::selection.setBottomRight(QPointF(point.x, point.y));
1300 // Only needs to be done here, as mouse down is always preceded by movement
1301 Global::snapPointIsValid = false;
1302 hoveringIntersection = false;
1305 if (event->buttons() & Qt::MiddleButton)
1307 point = Vector(event->x(), event->y());
1308 // Since we're using Qt coords for scrolling, we have to adjust them
1309 // here to conform to Cartesian coords, since the origin is using
1311 Vector delta(oldPoint, point);
1312 delta /= Global::zoom;
1314 Global::origin -= delta;
1316 UpdateGridBackground();
1322 // If we're doing a selection rect, see if any objects are engulfed by it
1323 // (implies left mouse button held down)
1324 if (Global::selectionInProgress)
1326 CheckObjectBounds();
1331 // Do object hit testing...
1332 bool needUpdate = HitTestObjects(point);
1335 // Check for multi-hover...
1338 //need to check for case where hover is over 2 circles and a 3rd's center...
1339 Object * obj1 = (Object *)hover[0], * obj2 = (Object *)hover[1];
1341 Geometry::Intersects(obj1, obj2);
1342 int numIntersecting = Global::numIntersectParams;
1343 double t = Global::intersectParam[0];
1344 double u = Global::intersectParam[1];
1346 if (numIntersecting > 0)
1348 Vector v1 = Geometry::GetPointForParameter(obj1, t);
1349 Vector v2 = Geometry::GetPointForParameter(obj2, u);
1350 QString text = tr("Intersection t=%1 (%3, %4), u=%2 (%5, %6)");
1351 informativeText = text.arg(t).arg(u).arg(v1.x).arg(v1.y).arg(v2.x).arg(v2.y);
1353 hoveringIntersection = true;
1354 intersectionPoint = v1;
1357 numIntersecting = Global::numIntersectPoints;
1359 if (numIntersecting > 0)
1361 Vector v1 = Global::intersectPoint[0];
1363 if (numIntersecting == 2)
1365 Vector v2 = Global::intersectPoint[1];
1367 if (Vector::Magnitude(v2, point) < Vector::Magnitude(v1, point))
1371 QString text = tr("Intersection <%1, %2>");
1372 informativeText = text.arg(v1.x).arg(v1.y);
1373 hoveringIntersection = true;
1374 intersectionPoint = v1;
1378 // Handle object movement (left button down & over an object)
1379 if ((event->buttons() & Qt::LeftButton) && draggingObject && !Global::tool)
1381 if (hoveringIntersection)
1382 point = intersectionPoint;
1383 else if (hoverPointValid)
1385 else if (Global::snapToGrid)
1386 point = SnapPointToGrid(point);
1388 HandleObjectMovement(point);
1394 // Do tool handling, if any are active...
1397 if (hoveringIntersection)
1398 point = intersectionPoint;
1399 else if (hoverPointValid)
1401 else if (Global::snapToGrid)
1404 point = SnapPointToAngle(point);
1406 point = SnapPointToGrid(point);
1409 ToolHandler(ToolMouseMove, point);
1412 // This is used to draw the tool crosshair...
1415 if (needUpdate || Global::tool)
1420 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
1422 if (event->button() == Qt::LeftButton)
1424 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
1425 //could set it up to use the document's update function (assumes that all object
1426 //updates are being reported correctly:
1427 // if (document.NeedsUpdate())
1428 // Do an update if collided with at least *one* object in the document
1434 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1435 ToolHandler(ToolMouseUp, point);
1439 if (Global::selectionInProgress)
1440 Global::selectionInProgress = false;
1442 informativeText.clear();
1443 // Should we be doing this automagically? Hmm...
1444 // Clear our vectors
1449 std::vector<void *>::iterator i;
1451 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1453 if (((Object *)(*i))->selected)
1454 select.push_back(*i);
1457 draggingObject = false;
1459 else if (event->button() == Qt::MiddleButton)
1462 setCursor(Qt::ArrowCursor);
1467 void DrawingView::wheelEvent(QWheelEvent * event)
1469 double zoomFactor = 1.25;
1470 QSize sizeWin = size();
1471 Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
1472 center = Painter::QtToCartesianCoords(center);
1474 // This is not centering for some reason. Need to figure out why. :-/
1475 if (event->delta() > 0)
1477 Vector newOrigin = center - ((center - Global::origin) / zoomFactor);
1478 Global::origin = newOrigin;
1479 Global::zoom *= zoomFactor;
1483 Vector newOrigin = center + ((-center + Global::origin) * zoomFactor);
1484 Global::origin = newOrigin;
1485 Global::zoom /= zoomFactor;
1488 // Global::gridSpacing = gridPixels / Painter::zoom;
1489 // UpdateGridBackground();
1490 SetGridSize(Global::gridSpacing * Global::zoom);
1492 // zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Global::gridSpacing));
1496 void DrawingView::keyPressEvent(QKeyEvent * event)
1498 bool oldShift = shiftDown;
1499 bool oldCtrl = ctrlDown;
1501 if (event->key() == Qt::Key_Shift)
1503 else if (event->key() == Qt::Key_Control)
1506 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1509 ToolHandler(ToolKeyDown, Point(0, 0));
1514 if (select.size() > 0)
1516 if (event->key() == Qt::Key_Up)
1518 TranslateObjects(select, Point(0, +1.0));
1521 else if (event->key() == Qt::Key_Down)
1523 TranslateObjects(select, Point(0, -1.0));
1526 else if (event->key() == Qt::Key_Right)
1528 TranslateObjects(select, Point(+1.0, 0));
1531 else if (event->key() == Qt::Key_Left)
1533 TranslateObjects(select, Point(-1.0, 0));
1540 void DrawingView::keyReleaseEvent(QKeyEvent * event)
1542 bool oldShift = shiftDown;
1543 bool oldCtrl = ctrlDown;
1545 if (event->key() == Qt::Key_Shift)
1547 else if (event->key() == Qt::Key_Control)
1550 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1553 ToolHandler(ToolKeyUp, Point(0, 0));
1561 // This looks strange, but it's really quite simple: We want a point that's
1562 // more than half-way to the next grid point to snap there while conversely we
1563 // want a point that's less than half-way to to the next grid point then snap
1564 // to the one before it. So we add half of the grid spacing to the point, then
1565 // divide by it so that we can remove the fractional part, then multiply it
1566 // back to get back to the correct answer.
1568 Point DrawingView::SnapPointToGrid(Point point)
1570 point += Global::gridSpacing / 2.0; // *This* adds to Z!!!
1571 point /= Global::gridSpacing;
1572 point.x = floor(point.x);//need to fix this for negative numbers...
1573 point.y = floor(point.y);
1574 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
1575 point *= Global::gridSpacing;
1580 Point DrawingView::SnapPointToAngle(Point point)
1582 // Snap to a single digit angle (using toolpoint #1 as the center)
1583 double angle = Vector::Angle(toolPoint[0], point);
1584 double length = Vector::Magnitude(toolPoint[0], point);
1586 // Convert from radians to degrees
1587 double degAngle = angle * RADIANS_TO_DEGREES;
1588 double snapAngle = (double)((int)(degAngle + 0.5));
1591 v.SetAngleAndLength(snapAngle * DEGREES_TO_RADIANS, length);
1592 point = toolPoint[0] + v;
1598 Rect DrawingView::GetObjectExtents(Object * obj)
1600 // Default to empty rect, if object checks below fail for some reason
1608 rect = Rect(obj->p[0], obj->p[1]);
1614 rect = Rect(obj->p[0], obj->p[0]);
1615 rect.Expand(obj->radius[0]);
1621 Arc * a = (Arc *)obj;
1623 double start = a->angle[0];
1624 double end = start + a->angle[1];
1625 rect = Rect(Point(cos(start), sin(start)), Point(cos(end), sin(end)));
1627 // If the end of the arc is before the beginning, add 360 degrees to it
1631 // Adjust the bounds depending on which axes are crossed
1632 if ((start < QTR_TAU) && (end > QTR_TAU))
1635 if ((start < HALF_TAU) && (end > HALF_TAU))
1638 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
1641 if ((start < TAU) && (end > TAU))
1644 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
1647 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
1650 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
1653 rect *= a->radius[0];
1654 rect.Translate(a->p[0]);
1660 Text * t = (Text *)obj;
1661 rect = Rect(t->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
1667 Container * c = (Container *)obj;
1668 std::vector<void *>::iterator i = c->objects.begin();
1669 rect = GetObjectExtents((Object *)*i);
1672 for(; i!=c->objects.end(); i++)
1673 rect |= GetObjectExtents((Object *)*i);
1684 void DrawingView::CheckObjectBounds(void)
1686 std::vector<void *>::iterator i;
1688 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1690 Object * obj = (Object *)(*i);
1691 obj->selected = false;
1698 Line * l = (Line *)obj;
1700 if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
1708 Circle * c = (Circle *)obj;
1710 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]))
1718 Arc * a = (Arc *)obj;
1720 double start = a->angle[0];
1721 double end = start + a->angle[1];
1722 QPointF p1(cos(start), sin(start));
1723 QPointF p2(cos(end), sin(end));
1724 QRectF bounds(p1, p2);
1727 // Swap X/Y coordinates if they're backwards...
1728 if (bounds.left() > bounds.right())
1730 double temp = bounds.left();
1731 bounds.setLeft(bounds.right());
1732 bounds.setRight(temp);
1735 if (bounds.bottom() > bounds.top())
1737 double temp = bounds.bottom();
1738 bounds.setBottom(bounds.top());
1739 bounds.setTop(temp);
1742 // Doesn't work as advertised! For shame!
1743 bounds = bounds.normalized();
1746 // If the end of the arc is before the beginning, add 360 degrees to it
1750 // Adjust the bounds depending on which axes are crossed
1751 if ((start < QTR_TAU) && (end > QTR_TAU))
1754 if ((start < HALF_TAU) && (end > HALF_TAU))
1755 bounds.setLeft(-1.0);
1757 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
1758 bounds.setBottom(-1.0);
1760 if ((start < TAU) && (end > TAU))
1761 bounds.setRight(1.0);
1763 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
1766 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
1767 bounds.setLeft(-1.0);
1769 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
1770 bounds.setBottom(-1.0);
1772 bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
1773 bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
1774 bounds.translate(a->p[0].x, a->p[0].y);
1776 if (Global::selection.contains(bounds))
1784 Text * t = (Text *)obj;
1785 Rect r(obj->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
1787 if (Global::selection.contains(r.l, r.t) && Global::selection.contains(r.r, r.b))
1800 bool DrawingView::HitTestObjects(Point point)
1802 std::vector<void *>::iterator i;
1804 bool needUpdate = false;
1805 hoverPointValid = false;
1807 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1809 Object * obj = (Object *)(*i);
1811 // If we're seeing the object we're dragging, skip it
1812 if (draggingObject && (obj == dragged))
1815 if (HitTest(obj, point))
1821 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
1822 emit(ObjectHovered(obj));
1830 bool DrawingView::HitTest(Object * obj, Point point)
1832 bool needUpdate = false;
1838 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
1839 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
1840 Vector lineSegment = obj->p[1] - obj->p[0];
1841 Vector v1 = point - obj->p[0];
1842 Vector v2 = point - obj->p[1];
1843 double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
1847 distance = v1.Magnitude();
1849 distance = v2.Magnitude();
1851 // distance = ?Det?(ls, v1) / |ls|
1852 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
1853 / lineSegment.Magnitude());
1855 if ((v1.Magnitude() * Global::zoom) < 8.0)
1857 obj->hitPoint[0] = true;
1858 hoverPoint = obj->p[0];
1859 hoverPointValid = true;
1861 else if ((v2.Magnitude() * Global::zoom) < 8.0)
1863 obj->hitPoint[1] = true;
1864 hoverPoint = obj->p[1];
1865 hoverPointValid = true;
1867 else if ((distance * Global::zoom) < 5.0)
1868 obj->hitObject = true;
1870 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
1872 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
1880 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
1881 obj->hitPoint[0] = obj->hitObject = false;
1882 double length = Vector::Magnitude(obj->p[0], point);
1884 if ((length * Global::zoom) < 8.0)
1886 obj->hitPoint[0] = true;
1887 hoverPoint = obj->p[0];
1888 hoverPointValid = true;
1890 else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
1891 obj->hitObject = true;
1893 obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
1895 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
1903 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
1904 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
1905 double length = Vector::Magnitude(obj->p[0], point);
1906 double angle = Vector::Angle(obj->p[0], point);
1908 // Make sure we get the angle in the correct spot
1909 if (angle < obj->angle[0])
1912 // Get the span that we're pointing at...
1913 double span = angle - obj->angle[0];
1915 // N.B.: Still need to hit test the arc start & arc span handles...
1916 double spanAngle = obj->angle[0] + obj->angle[1];
1917 Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
1918 Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
1919 double length2 = Vector::Magnitude(point, handle1);
1920 double length3 = Vector::Magnitude(point, handle2);
1922 if ((length * Global::zoom) < 8.0)
1924 obj->hitPoint[0] = true;
1925 hoverPoint = obj->p[0];
1926 hoverPointValid = true;
1928 else if ((length2 * Global::zoom) < 8.0)
1930 obj->hitPoint[1] = true;
1931 hoverPoint = handle1;
1932 hoverPointValid = true;
1934 else if ((length3 * Global::zoom) < 8.0)
1936 obj->hitPoint[2] = true;
1937 hoverPoint = handle2;
1938 hoverPointValid = true;
1940 else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
1941 obj->hitObject = true;
1943 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
1945 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
1953 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHP3 = obj->hitPoint[3], oldHP4 = obj->hitPoint[4], oldHO = obj->hitObject;
1954 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitPoint[3] = obj->hitPoint[4] = obj->hitObject = false;
1956 Dimension * d = (Dimension *)obj;
1958 Vector orthogonal = Vector::Normal(d->lp[0], d->lp[1]);
1959 // Get our line parallel to our points
1960 float scaledThickness = Global::scale * obj->thickness;
1961 Point p1 = d->lp[0] + (orthogonal * 10.0 * scaledThickness);
1962 Point p2 = d->lp[1] + (orthogonal * 10.0 * scaledThickness);
1963 Point p3(p1, point);
1965 Vector v1(d->p[0], point);
1966 Vector v2(d->p[1], point);
1967 Vector lineSegment(p1, p2);
1968 double t = Geometry::ParameterOfLineAndPoint(p1, p2, point);
1970 Point midpoint = (p1 + p2) / 2.0;
1971 Point hFSPoint = Point(midpoint, point);
1972 Point hCS1Point = Point((p1 + midpoint) / 2.0, point);
1973 Point hCS2Point = Point((midpoint + p2) / 2.0, point);
1976 distance = v1.Magnitude();
1978 distance = v2.Magnitude();
1980 // distance = ?Det?(ls, v1) / |ls|
1981 distance = fabs((lineSegment.x * p3.y - p3.x * lineSegment.y)
1982 / lineSegment.Magnitude());
1984 if ((v1.Magnitude() * Global::zoom) < 8.0)
1985 obj->hitPoint[0] = true;
1986 else if ((v2.Magnitude() * Global::zoom) < 8.0)
1987 obj->hitPoint[1] = true;
1988 else if ((distance * Global::zoom) < 5.0)
1989 obj->hitObject = true;
1991 if ((hFSPoint.Magnitude() * Global::zoom) < 8.0)
1992 obj->hitPoint[2] = true;
1993 else if ((hCS1Point.Magnitude() * Global::zoom) < 8.0)
1994 obj->hitPoint[3] = true;
1995 else if ((hCS2Point.Magnitude() * Global::zoom) < 8.0)
1996 obj->hitPoint[4] = true;
1998 // return (hitPoint1 || hitPoint2 || hitLine || hitFlipSwitch || hitChangeSwitch1 || hitChangeSwitch2 ? true : false);
1999 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitPoint[3] || obj->hitPoint[4] || obj->hitObject ? true : false);
2001 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHP3 != obj->hitPoint[3]) || (oldHP4 != obj->hitPoint[4]) || (oldHO != obj->hitObject))
2009 Text * t = (Text *)obj;
2010 bool oldHO = obj->hitObject;
2011 obj->hitObject = false;
2013 Rect r(obj->p[0], Point(obj->p[0].x + t->extents.Width(), obj->p[0].y - t->extents.Height()));
2014 //printf("Text: p=<%lf, %lf>, w/h=%lf, %lf [lrtb=%lf, %lf, %lf, %lf]\n", obj->p[0].x, obj->p[0].y, t->extents.Width(), t->extents.Height(), t->extents.l, t->extents.r, t->extents.t, t->extents.b);
2016 if (r.Contains(point))
2017 obj->hitObject = true;
2019 obj->hovered = (obj->hitObject ? true : false);
2021 if (oldHO != obj->hitObject)
2029 // Containers must be recursively tested...
2030 Container * c = (Container *)obj;
2031 c->hitObject = false;
2033 std::vector<void *>::iterator i;
2035 for(i=c->objects.begin(); i!=c->objects.end(); i++)
2037 Object * cObj = (Object *)*i;
2039 if (HitTest(cObj, point))
2042 if (cObj->hitObject == true)
2043 c->hitObject = true;
2045 if (cObj->hovered == true)
2060 bool DrawingView::HandleObjectClicked(void)
2062 if (dragged->type == OTDimension)
2064 Dimension * d = (Dimension *)dragged;
2068 // Hit the "flip sides" switch, so flip 'em
2069 Point temp = d->p[0];
2074 else if (d->hitPoint[3])
2076 // There are three cases here: aligned, horizontal, & vertical.
2077 // Aligned and horizontal do the same thing, vertical goes back to
2079 if (d->subtype == DTLinearVert)
2080 d->subtype = DTLinear;
2082 d->subtype = DTLinearVert;
2086 else if (d->hitPoint[4])
2088 // There are three cases here: aligned, horizontal, & vertical.
2089 // Aligned and vertical do the same thing, horizontal goes back to
2091 if (d->subtype == DTLinearHorz)
2092 d->subtype = DTLinear;
2094 d->subtype = DTLinearHorz;
2104 void DrawingView::HandleObjectMovement(Point point)
2106 Point delta = point - oldPoint;
2107 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
2108 // Object * obj = (Object *)hover[0];
2109 Object * obj = dragged;
2110 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
2111 //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"));
2116 if (obj->hitPoint[0])
2118 else if (obj->hitPoint[1])
2120 else if (obj->hitObject)
2129 if (obj->hitPoint[0])
2131 else if (obj->hitObject)
2133 //this doesn't work. we need to save this on mouse down for this to work correctly!
2134 // double oldRadius = obj->radius[0];
2135 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
2137 QString text = QObject::tr("Radius: %1");//\nScale: %2%");
2138 informativeText = text.arg(obj->radius[0], 0, 'd', 4);//.arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0);
2144 if (obj->hitPoint[0])
2146 else if (obj->hitPoint[1])
2148 // Change the Arc's span (handle #1)
2151 double angle = Vector::Angle(obj->p[0], point);
2152 double delta = angle - obj->angle[0];
2157 obj->angle[1] -= delta;
2158 obj->angle[0] = angle;
2160 if (obj->angle[1] < 0)
2161 obj->angle[1] += TAU;
2163 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
2164 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);
2168 double angle = Vector::Angle(obj->p[0], point);
2169 obj->angle[0] = angle;
2170 QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
2171 informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 4);
2173 else if (obj->hitPoint[2])
2175 // Change the Arc's span (handle #2)
2178 double angle = Vector::Angle(obj->p[0], point);
2179 obj->angle[1] = angle - obj->angle[0];
2181 if (obj->angle[1] < 0)
2182 obj->angle[1] += TAU;
2184 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
2185 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);
2189 double angle = Vector::Angle(obj->p[0], point);
2190 obj->angle[0] = angle - obj->angle[1];
2192 if (obj->angle[0] < 0)
2193 obj->angle[0] += TAU;
2195 QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
2196 informativeText = text.arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 4);
2198 else if (obj->hitObject)
2205 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
2206 QString text = QObject::tr("Radius: %1");
2207 informativeText = text.arg(obj->radius[0], 0, 'd', 4);
2213 if (obj->hitPoint[0])
2215 else if (obj->hitPoint[1])
2217 else if (obj->hitObject)
2232 // This is shitty, but works for now until I can code up something
2234 TranslateObject(obj, delta);