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),
48 ctrlDown(false), altDown(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 void DrawingView::HandlePenWidth(float width)
316 std::vector<void *>::iterator i = select.begin();
318 for(; i!=select.end(); i++)
320 Object * obj = (Object *)(*i);
321 obj->thickness = width;
326 void DrawingView::HandlePenStyle(int style)
328 std::vector<void *>::iterator i = select.begin();
330 for(; i!=select.end(); i++)
332 Object * obj = (Object *)(*i);
338 void DrawingView::HandlePenColor(uint32_t color)
340 std::vector<void *>::iterator i = select.begin();
342 for(; i!=select.end(); i++)
344 Object * obj = (Object *)(*i);
350 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
352 // This is undoing the transform, e.g. going from client coords to local
353 // coords. In essence, the height - y is height + (y * -1), the (y * -1)
354 // term doing the conversion of the y-axis from increasing bottom to top.
355 return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
359 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
361 // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
362 // No voodoo here, it's just grouped wrong to see it. It should be:
363 // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive [why? we use -offsetX after all]
364 return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
368 void DrawingView::paintEvent(QPaintEvent * /*event*/)
370 QPainter qtPainter(this);
371 Painter painter(&qtPainter);
374 qtPainter.setRenderHint(QPainter::Antialiasing);
376 Global::viewportHeight = size().height();
378 // Draw coordinate axes
379 painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
380 painter.DrawLine(0, -16384, 0, 16384);
381 painter.DrawLine(-16384, 0, 16384, 0);
383 // Do object rendering...
384 for(int i=0; i<Global::numLayers; i++)
386 if (Global::layerHidden[i] == false)
387 RenderObjects(&painter, document.objects, i);
390 // Do tool rendering, if any...
393 painter.SetPen(QPen(QColor(200, 100, 0, 255), 1.0, Qt::DashLine));
394 painter.DrawCrosshair(oldPoint);
398 // Do selection rectangle rendering, if any
399 if (Global::selectionInProgress)
401 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
402 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
403 painter.DrawRect(Global::selection);
406 if (hoveringIntersection)
407 painter.DrawHandle(intersectionPoint);
409 if (!informativeText.isEmpty())
410 painter.DrawInformativeText(informativeText);
415 // Renders objects in the passed in vector
417 void DrawingView::RenderObjects(Painter * painter, std::vector<void *> & v, int layer, bool ignoreLayer/*= false*/)
419 std::vector<void *>::iterator i;
421 for(i=v.begin(); i!=v.end(); i++)
423 Object * obj = (Object *)(*i);
424 float scaledThickness = Global::scale * obj->thickness;
426 // If the object isn't on the current layer being drawn, skip it
427 if (!ignoreLayer && (obj->layer != layer))
430 if ((Global::tool == TTRotate) && ctrlDown && obj->selected)
432 painter->SetPen(0x00FF00, 2.0, LSSolid);
436 painter->SetPen(obj->color, Global::zoom * scaledThickness, obj->style);
437 painter->SetBrush(obj->color);
439 if (obj->selected || obj->hitObject)
440 painter->SetPen(0xFF0000, Global::zoom * scaledThickness, LSDash);
446 painter->DrawLine(obj->p[0], obj->p[1]);
448 if (obj->hitPoint[0])
449 painter->DrawHandle(obj->p[0]);
451 if (obj->hitPoint[1])
452 painter->DrawHandle(obj->p[1]);
456 painter->SetBrush(QBrush(Qt::NoBrush));
457 painter->DrawEllipse(obj->p[0], obj->radius[0], obj->radius[0]);
459 if (obj->hitPoint[0])
460 painter->DrawHandle(obj->p[0]);
464 painter->DrawArc(obj->p[0], obj->radius[0], obj->angle[0], obj->angle[1]);
466 if (obj->hitPoint[0])
467 painter->DrawHandle(obj->p[0]);
469 if (obj->hitPoint[1])
470 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]));
472 if (obj->hitPoint[2])
473 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0] + obj->angle[1]), sin(obj->angle[0] + obj->angle[1])) * obj->radius[0]));
478 Dimension * d = (Dimension *)obj;
480 Vector v(d->p[0], d->p[1]);
481 double angle = v.Angle();
482 Vector unit = v.Unit();
483 d->lp[0] = d->p[0], d->lp[1] = d->p[1];
485 double x1, y1, length;
487 if (d->subtype == DTLinearVert)
489 if ((angle < 0) || (angle > HALF_TAU))
491 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
492 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
493 ortho = Vector(1.0, 0);
494 angle = THREE_QTR_TAU;
498 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
499 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
500 ortho = Vector(-1.0, 0);
504 d->lp[0].x = d->lp[1].x = x1;
505 length = fabs(d->p[0].y - d->p[1].y);
507 else if (d->subtype == DTLinearHorz)
509 if ((angle < QTR_TAU) || (angle > THREE_QTR_TAU))
511 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
512 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
513 ortho = Vector(0, 1.0);
518 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
519 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
520 ortho = Vector(0, -1.0);
524 d->lp[0].y = d->lp[1].y = y1;
525 length = fabs(d->p[0].x - d->p[1].x);
527 else if (d->subtype == DTLinear)
529 angle = Vector(d->lp[0], d->lp[1]).Angle();
530 ortho = Vector::Normal(d->lp[0], d->lp[1]);
531 length = v.Magnitude();
534 unit = Vector(d->lp[0], d->lp[1]).Unit();
536 Point p1 = d->lp[0] + (ortho * 10.0 * scaledThickness);
537 Point p2 = d->lp[1] + (ortho * 10.0 * scaledThickness);
538 Point p3 = d->lp[0] + (ortho * 16.0 * scaledThickness);
539 Point p4 = d->lp[1] + (ortho * 16.0 * scaledThickness);
540 Point p5 = d->p[0] + (ortho * 4.0 * scaledThickness);
541 Point p6 = d->p[1] + (ortho * 4.0 * scaledThickness);
544 The numbers hardcoded into here, what are they?
545 I believe they are pixels.
547 // Draw extension lines (if certain type)
548 painter->DrawLine(p3, p5);
549 painter->DrawLine(p4, p6);
551 // Calculate whether or not the arrowheads are too crowded to put
552 // inside the extension lines. 9.0 is the length of the arrowhead.
553 double t = Geometry::ParameterOfLineAndPoint(d->lp[0], d->lp[1], d->lp[1] - (unit * 9.0 * scaledThickness));
555 // On the screen, it's acting like this is actually 58%...
556 // This is correct, we want it to happen at > 50%
559 // Draw main dimension line + arrowheads
560 painter->DrawLine(p1, p2);
561 painter->DrawArrowhead(p1, p2, scaledThickness);
562 painter->DrawArrowhead(p2, p1, scaledThickness);
566 // Draw outside arrowheads
567 Point p7 = p1 - (unit * 9.0 * scaledThickness);
568 Point p8 = p2 + (unit * 9.0 * scaledThickness);
569 painter->DrawArrowhead(p1, p7, scaledThickness);
570 painter->DrawArrowhead(p2, p8, scaledThickness);
571 painter->DrawLine(p1, p1 - (unit * 14.0 * scaledThickness));
572 painter->DrawLine(p2, p2 + (unit * 14.0 * scaledThickness));
575 // Draw length of dimension line...
576 painter->SetFont(QFont("Arial", 8.0 * Global::zoom * scaledThickness));
577 Point ctr = p2 + (Vector(p2, p1) / 2.0);
582 dimText = QString("%1\"").arg(length);
585 double feet = (double)((int)length / 12);
586 double inches = length - (feet * 12.0);
589 dimText = QString("%1'").arg(feet);
591 dimText = QString("%1' %2\"").arg(feet).arg(inches);
594 painter->DrawAngledText(ctr, angle, dimText, scaledThickness);
598 Point hp1 = (p1 + p2) / 2.0;
599 Point hp2 = (p1 + hp1) / 2.0;
600 Point hp3 = (hp1 + p2) / 2.0;
604 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
605 painter->SetBrush(QBrush(QColor(Qt::magenta)));
606 painter->DrawArrowHandle(hp1, ortho.Angle() + HALF_TAU);
607 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
610 painter->DrawHandle(hp1);
611 painter->SetPen(QPen(Qt::blue, 1.0 * Global::zoom * scaledThickness, Qt::SolidLine));
615 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
616 painter->SetBrush(QBrush(QColor(Qt::magenta)));
617 painter->DrawArrowToLineHandle(hp2, (d->subtype == DTLinearVert ? v.Angle() - QTR_TAU : (v.Angle() < HALF_TAU ? HALF_TAU : 0)));
618 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
621 painter->DrawHandle(hp2);
622 painter->SetPen(QPen(Qt::blue, 1.0 * Global::zoom * scaledThickness, Qt::SolidLine));
626 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
627 painter->SetBrush(QBrush(QColor(Qt::magenta)));
628 painter->DrawArrowToLineHandle(hp3, (d->subtype == DTLinearHorz ? v.Angle() - QTR_TAU : (v.Angle() > HALF_TAU && v.Angle() < THREE_QTR_TAU ? THREE_QTR_TAU : QTR_TAU)));
629 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
632 painter->DrawHandle(hp3);
635 if (obj->hitPoint[0])
636 painter->DrawHandle(obj->p[0]);
638 if (obj->hitPoint[1])
639 painter->DrawHandle(obj->p[1]);
645 Text * t = (Text *)obj;
647 if (t->measured == false)
649 t->extents = painter->MeasureTextObject(t->s.c_str(), scaledThickness);
653 painter->DrawTextObject(t->p[0], t->s.c_str(), scaledThickness, t->angle[0]);
666 // Containers require recursive rendering...
667 Container * c = (Container *)obj;
668 RenderObjects(painter, (*c).objects, layer);
670 //printf("Container extents: <%lf, %lf>, <%lf, %lf>\nsize: %i\n", r.l, r.t, r.r, r.b, c->objects.size());
671 // Containers also have special indicators showing they are selected
672 if (c->selected || c->hitObject)
674 Rect r = GetObjectExtents(obj);
675 painter->DrawRectCorners(r);
687 void DrawingView::AddHoveredToSelection(void)
689 std::vector<void *>::iterator i;
691 for(i=document.objects.begin(); i!=document.objects.end(); i++)
693 if (((Object *)(*i))->hovered)
694 ((Object *)(*i))->selected = true;
699 void DrawingView::GetSelection(std::vector<void *> & v)
702 std::vector<void *>::iterator i;
704 for(i=document.objects.begin(); i!=document.objects.end(); i++)
706 if (((Object *)(*i))->selected)
712 void DrawingView::GetHovered(std::vector<void *> & v)
715 std::vector<void *>::iterator i;
717 for(i=document.objects.begin(); i!=document.objects.end(); i++)
719 if (((Object *)(*i))->hovered)
721 //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"));
728 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
730 Global::screenSize = Vector(size().width(), size().height());
731 UpdateGridBackground();
735 void DrawingView::ToolHandler(int mode, Point p)
737 // Drop angle snap until it's needed
740 if (Global::tool == TTLine)
741 LineHandler(mode, p);
742 else if (Global::tool == TTCircle)
743 CircleHandler(mode, p);
744 else if (Global::tool == TTArc)
746 else if (Global::tool == TTRotate)
747 RotateHandler(mode, p);
748 else if (Global::tool == TTMirror)
749 MirrorHandler(mode, p);
750 else if (Global::tool == TTDimension)
751 DimensionHandler(mode, p);
755 void DrawingView::ToolDraw(Painter * painter)
757 if (Global::tool == TTLine)
759 if (Global::toolState == TSNone)
761 painter->DrawHandle(toolPoint[0]);
763 else if ((Global::toolState == TSPoint2) && shiftDown)
765 painter->DrawHandle(toolPoint[1]);
769 painter->DrawLine(toolPoint[0], toolPoint[1]);
770 painter->DrawHandle(toolPoint[1]);
772 Vector v(toolPoint[0], toolPoint[1]);
773 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
774 double absLength = v.Magnitude();
775 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
776 informativeText = text.arg(absLength).arg(absAngle);
779 else if (Global::tool == TTCircle)
781 if (Global::toolState == TSNone)
783 painter->DrawHandle(toolPoint[0]);
785 else if ((Global::toolState == TSPoint2) && shiftDown)
787 painter->DrawHandle(toolPoint[1]);
791 painter->DrawCross(toolPoint[0]);
792 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
793 painter->SetBrush(QBrush(Qt::NoBrush));
794 painter->DrawEllipse(toolPoint[0], length, length);
795 QString text = tr("Radius: %1 in.");
796 informativeText = text.arg(length);
799 else if (Global::tool == TTArc)
801 if (Global::toolState == TSNone)
803 painter->DrawHandle(toolPoint[0]);
805 else if (Global::toolState == TSPoint2)
807 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
808 painter->SetBrush(QBrush(Qt::NoBrush));
809 painter->DrawEllipse(toolPoint[0], length, length);
810 painter->DrawLine(toolPoint[0], toolPoint[1]);
811 painter->DrawHandle(toolPoint[1]);
812 QString text = tr("Radius: %1 in.");
813 informativeText = text.arg(length);
815 else if (Global::toolState == TSPoint3)
817 double angle = Vector::Angle(toolPoint[0], toolPoint[2]);
818 painter->DrawLine(toolPoint[0], toolPoint[2]);
819 painter->SetBrush(QBrush(Qt::NoBrush));
820 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
821 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
822 QString text = tr("Angle start: %1") + QChar(0x00B0);
823 informativeText = text.arg(RADIANS_TO_DEGREES * angle);
827 double angle = Vector::Angle(toolPoint[0], toolPoint[3]);
828 double span = angle - toolPoint[2].x;
833 painter->DrawLine(toolPoint[0], toolPoint[3]);
834 painter->SetBrush(QBrush(Qt::NoBrush));
835 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
836 painter->SetPen(0xFF00FF, 2.0, LSSolid);
837 painter->DrawArc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
838 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
839 QString text = tr("Arc span: %1") + QChar(0x00B0);
840 informativeText = text.arg(RADIANS_TO_DEGREES * span);
843 else if (Global::tool == TTRotate)
845 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
846 painter->DrawHandle(toolPoint[0]);
847 else if ((Global::toolState == TSPoint2) && shiftDown)
848 painter->DrawHandle(toolPoint[1]);
851 if (toolPoint[0] == toolPoint[1])
854 painter->DrawLine(toolPoint[0], toolPoint[1]);
856 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
857 QString text = QChar(0x2221) + QObject::tr(": %1");
858 informativeText = text.arg(absAngle);
861 informativeText += " (Copy)";
864 else if (Global::tool == TTMirror)
866 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
867 painter->DrawHandle(toolPoint[0]);
868 else if ((Global::toolState == TSPoint2) && shiftDown)
869 painter->DrawHandle(toolPoint[1]);
872 if (toolPoint[0] == toolPoint[1])
875 Point mirrorPoint = toolPoint[0] + Vector(toolPoint[1], toolPoint[0]);
876 painter->DrawLine(mirrorPoint, toolPoint[1]);
878 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
880 if (absAngle > 180.0)
883 QString text = QChar(0x2221) + QObject::tr(": %1");
884 informativeText = text.arg(absAngle);
887 informativeText += " (Copy)";
890 if (Global::tool == TTDimension)
892 if (Global::toolState == TSNone)
894 painter->DrawHandle(toolPoint[0]);
896 else if ((Global::toolState == TSPoint2) && shiftDown)
898 painter->DrawHandle(toolPoint[1]);
902 painter->DrawLine(toolPoint[0], toolPoint[1]);
903 painter->DrawHandle(toolPoint[1]);
905 Vector v(toolPoint[0], toolPoint[1]);
906 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
907 double absLength = v.Magnitude();
908 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
909 informativeText = text.arg(absLength).arg(absAngle);
915 void DrawingView::LineHandler(int mode, Point p)
920 if (Global::toolState == TSNone)
927 if (Global::toolState == TSNone)
934 if (Global::toolState == TSNone)
936 Global::toolState = TSPoint2;
937 // Prevent spurious line from drawing...
938 toolPoint[1] = toolPoint[0];
940 else if ((Global::toolState == TSPoint2) && shiftDown)
942 // Key override is telling us to make a new line, not continue the
944 toolPoint[0] = toolPoint[1];
948 Line * l = new Line(toolPoint[0], toolPoint[1], Global::penWidth, Global::penColor, Global::penStyle);
949 l->layer = Global::activeLayer;
950 document.objects.push_back(l);
951 toolPoint[0] = toolPoint[1];
957 void DrawingView::CircleHandler(int mode, Point p)
962 if (Global::toolState == TSNone)
969 if (Global::toolState == TSNone)
976 if (Global::toolState == TSNone)
978 Global::toolState = TSPoint2;
979 // Prevent spurious line from drawing...
980 toolPoint[1] = toolPoint[0];
982 else if ((Global::toolState == TSPoint2) && shiftDown)
984 // Key override is telling us to make a new line, not continue the
986 toolPoint[0] = toolPoint[1];
990 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
991 Circle * c = new Circle(toolPoint[0], length, Global::penWidth, Global::penColor, Global::penStyle);
992 c->layer = Global::activeLayer;
993 document.objects.push_back(c);
994 toolPoint[0] = toolPoint[1];
995 Global::toolState = TSNone;
1001 void DrawingView::ArcHandler(int mode, Point p)
1006 if (Global::toolState == TSNone)
1008 else if (Global::toolState == TSPoint2)
1010 else if (Global::toolState == TSPoint3)
1017 if (Global::toolState == TSNone)
1019 else if (Global::toolState == TSPoint2)
1021 else if (Global::toolState == TSPoint3)
1034 if (Global::toolState == TSNone)
1036 // Prevent spurious line from drawing...
1037 toolPoint[1] = toolPoint[0];
1038 Global::toolState = TSPoint2;
1040 else if (Global::toolState == TSPoint2)
1044 // Key override is telling us to start arc at new center, not
1045 // continue the current one.
1046 toolPoint[0] = toolPoint[1];
1050 // Set the radius in toolPoint[1].x
1051 toolPoint[1].x = Vector::Magnitude(toolPoint[0], toolPoint[1]);
1052 Global::toolState = TSPoint3;
1054 else if (Global::toolState == TSPoint3)
1056 // Set the angle in toolPoint[2].x
1057 toolPoint[2].x = Vector::Angle(toolPoint[0], toolPoint[2]);
1058 Global::toolState = TSPoint4;
1062 double endAngle = Vector::Angle(toolPoint[0], toolPoint[3]);
1063 double span = endAngle - toolPoint[2].x;
1068 Arc * arc = new Arc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span, Global::penWidth, Global::penColor, Global::penStyle);
1069 arc->layer = Global::activeLayer;
1070 document.objects.push_back(arc);
1071 Global::toolState = TSNone;
1077 void DrawingView::RotateHandler(int mode, Point p)
1082 if (Global::toolState == TSNone)
1085 SavePointsFrom(select, toolScratch);
1086 Global::toolState = TSPoint1;
1088 else if (Global::toolState == TSPoint1)
1095 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1097 else if (Global::toolState == TSPoint2)
1105 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1106 std::vector<void *>::iterator j = select.begin();
1107 std::vector<Object>::iterator i = toolScratch.begin();
1109 for(; i!=toolScratch.end(); i++, j++)
1112 Point p1 = Geometry::RotatePointAroundPoint(obj.p[0], toolPoint[0], angle);
1113 Point p2 = Geometry::RotatePointAroundPoint(obj.p[1], toolPoint[0], angle);
1114 Object * obj2 = (Object *)(*j);
1118 if (obj.type == OTArc)
1120 obj2->angle[0] = obj.angle[0] + angle;
1122 if (obj2->angle[0] > TAU)
1123 obj2->angle[0] -= TAU;
1130 if (Global::toolState == TSPoint1)
1132 Global::toolState = TSPoint2;
1133 // Prevent spurious line from drawing...
1134 toolPoint[1] = toolPoint[0];
1136 else if ((Global::toolState == TSPoint2) && shiftDown)
1138 // Key override is telling us to make a new line, not continue the
1140 toolPoint[0] = toolPoint[1];
1144 // Either we're finished with our rotate, or we're stamping a copy.
1147 // Stamp a copy of the selection at the current rotation & bail
1148 std::vector<void *> temp;
1149 CopyObjects(select, temp);
1150 ClearSelected(temp);
1151 AddObjectsTo(document.objects, temp);
1152 RestorePointsTo(select, toolScratch);
1157 Global::toolState = TSPoint1;
1158 SavePointsFrom(select, toolScratch);
1163 // Reset the selection if shift held down...
1165 RestorePointsTo(select, toolScratch);
1169 // Reset selection when key is let up
1171 RotateHandler(ToolMouseMove, toolPoint[1]);
1175 RestorePointsTo(select, toolScratch);
1180 void DrawingView::MirrorHandler(int mode, Point p)
1185 if (Global::toolState == TSNone)
1188 SavePointsFrom(select, toolScratch);
1189 Global::toolState = TSPoint1;
1191 else if (Global::toolState == TSPoint1)
1198 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1200 else if (Global::toolState == TSPoint2)
1208 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1209 std::vector<void *>::iterator j = select.begin();
1210 std::vector<Object>::iterator i = toolScratch.begin();
1212 for(; i!=toolScratch.end(); i++, j++)
1215 Point p1 = Geometry::MirrorPointAroundLine(obj.p[0], toolPoint[0], toolPoint[1]);
1216 Point p2 = Geometry::MirrorPointAroundLine(obj.p[1], toolPoint[0], toolPoint[1]);
1217 Object * obj2 = (Object *)(*j);
1221 if (obj.type == OTArc)
1223 // This is 2*mirror angle - obj angle - obj span
1224 obj2->angle[0] = (2.0 * angle) - obj.angle[0] - obj.angle[1];
1226 if (obj2->angle[0] > TAU)
1227 obj2->angle[0] -= TAU;
1234 if (Global::toolState == TSPoint1)
1236 Global::toolState = TSPoint2;
1237 // Prevent spurious line from drawing...
1238 toolPoint[1] = toolPoint[0];
1240 else if ((Global::toolState == TSPoint2) && shiftDown)
1242 // Key override is telling us to make a new line, not continue the
1244 toolPoint[0] = toolPoint[1];
1248 // Either we're finished with our rotate, or we're stamping a copy.
1251 // Stamp a copy of the selection at the current rotation & bail
1252 std::vector<void *> temp;
1253 CopyObjects(select, temp);
1254 ClearSelected(temp);
1255 AddObjectsTo(document.objects, temp);
1256 RestorePointsTo(select, toolScratch);
1261 Global::toolState = TSPoint1;
1262 SavePointsFrom(select, toolScratch);
1267 // Reset the selection if shift held down...
1269 RestorePointsTo(select, toolScratch);
1273 // Reset selection when key is let up
1275 MirrorHandler(ToolMouseMove, toolPoint[1]);
1279 RestorePointsTo(select, toolScratch);
1284 void DrawingView::DimensionHandler(int mode, Point p)
1289 if (Global::toolState == TSNone)
1296 if (Global::toolState == TSNone)
1303 if (Global::toolState == TSNone)
1305 Global::toolState = TSPoint2;
1306 // Prevent spurious line from drawing...
1307 toolPoint[1] = toolPoint[0];
1309 else if ((Global::toolState == TSPoint2) && shiftDown)
1311 // Key override is telling us to make a new line, not continue the
1313 toolPoint[0] = toolPoint[1];
1317 Dimension * d = new Dimension(toolPoint[0], toolPoint[1], DTLinear);
1318 d->layer = Global::activeLayer;
1319 document.objects.push_back(d);
1320 Global::toolState = TSNone;
1326 void DrawingView::mousePressEvent(QMouseEvent * event)
1328 if (event->button() == Qt::LeftButton)
1330 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1332 // Handle tool processing, if any
1335 if (hoveringIntersection)
1336 point = intersectionPoint;
1337 else if (Global::snapToGrid)
1338 point = SnapPointToGrid(point);
1340 //Also, may want to figure out if hovering over a snap point on an
1341 //object, snap to grid if not.
1342 // Snap to object point if valid...
1343 // if (Global::snapPointIsValid)
1344 // point = Global::snapPoint;
1346 ToolHandler(ToolMouseDown, point);
1350 // Clear the selection only if CTRL isn't being held on click
1352 ClearSelected(document.objects);
1353 // ClearSelection();
1355 // If any objects are being hovered on click, add them to the selection
1359 AddHoveredToSelection();
1360 update(); // needed??
1361 GetHovered(hover); // prolly needed
1362 dragged = (Object *)hover[0];
1363 draggingObject = true;
1365 // Alert the pen widget
1366 emit(ObjectSelected(dragged));
1368 // See if anything is using just a straight click on a handle
1369 if (HandleObjectClicked())
1371 draggingObject = false;
1376 // Needed for grab & moving objects
1377 // We do it *after*... why? (doesn't seem to confer any advantage...)
1378 if (hoveringIntersection)
1379 oldPoint = intersectionPoint;
1380 else if (Global::snapToGrid)
1381 oldPoint = SnapPointToGrid(point);
1386 // Didn't hit any object and not using a tool, so do a selection rectangle
1387 Global::selectionInProgress = true;
1388 Global::selection.setTopLeft(QPointF(point.x, point.y));
1389 Global::selection.setBottomRight(QPointF(point.x, point.y));
1391 else if (event->button() == Qt::MiddleButton)
1394 oldPoint = Vector(event->x(), event->y());
1395 // Should also change the mouse pointer as well...
1396 setCursor(Qt::SizeAllCursor);
1401 void DrawingView::mouseMoveEvent(QMouseEvent * event)
1403 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1404 Global::selection.setBottomRight(QPointF(point.x, point.y));
1405 // Only needs to be done here, as mouse down is always preceded by movement
1406 Global::snapPointIsValid = false;
1407 hoveringIntersection = false;
1408 oldScrollPoint = Vector(event->x(), event->y());
1411 if ((event->buttons() & Qt::MiddleButton) || scrollDrag)
1413 point = Vector(event->x(), event->y());
1414 // Since we're using Qt coords for scrolling, we have to adjust them
1415 // here to conform to Cartesian coords, since the origin is using
1417 Vector delta(oldPoint, point);
1418 delta /= Global::zoom;
1420 Global::origin -= delta;
1422 UpdateGridBackground();
1428 // If we're doing a selection rect, see if any objects are engulfed by it
1429 // (implies left mouse button held down)
1430 if (Global::selectionInProgress)
1432 CheckObjectBounds();
1437 // Do object hit testing...
1438 bool needUpdate = HitTestObjects(point);
1441 // Check for multi-hover...
1444 //need to check for case where hover is over 2 circles and a 3rd's center...
1445 Object * obj1 = (Object *)hover[0], * obj2 = (Object *)hover[1];
1447 Geometry::Intersects(obj1, obj2);
1448 int numIntersecting = Global::numIntersectParams;
1449 double t = Global::intersectParam[0];
1450 double u = Global::intersectParam[1];
1452 if (numIntersecting > 0)
1454 Vector v1 = Geometry::GetPointForParameter(obj1, t);
1455 Vector v2 = Geometry::GetPointForParameter(obj2, u);
1456 QString text = tr("Intersection t=%1 (%3, %4), u=%2 (%5, %6)");
1457 informativeText = text.arg(t).arg(u).arg(v1.x).arg(v1.y).arg(v2.x).arg(v2.y);
1459 hoveringIntersection = true;
1460 intersectionPoint = v1;
1463 numIntersecting = Global::numIntersectPoints;
1465 if (numIntersecting > 0)
1467 Vector v1 = Global::intersectPoint[0];
1469 if (numIntersecting == 2)
1471 Vector v2 = Global::intersectPoint[1];
1473 if (Vector::Magnitude(v2, point) < Vector::Magnitude(v1, point))
1477 QString text = tr("Intersection <%1, %2>");
1478 informativeText = text.arg(v1.x).arg(v1.y);
1479 hoveringIntersection = true;
1480 intersectionPoint = v1;
1484 // Handle object movement (left button down & over an object)
1485 if ((event->buttons() & Qt::LeftButton) && draggingObject && !Global::tool)
1487 if (hoveringIntersection)
1488 point = intersectionPoint;
1489 else if (hoverPointValid)
1491 else if (Global::snapToGrid)
1492 point = SnapPointToGrid(point);
1494 HandleObjectMovement(point);
1500 // Do tool handling, if any are active...
1503 if (hoveringIntersection)
1504 point = intersectionPoint;
1505 else if (hoverPointValid)
1507 else if (Global::snapToGrid)
1510 point = SnapPointToAngle(point);
1512 point = SnapPointToGrid(point);
1515 ToolHandler(ToolMouseMove, point);
1518 // This is used to draw the tool crosshair...
1521 if (needUpdate || Global::tool)
1526 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
1528 if (event->button() == Qt::LeftButton)
1530 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
1531 //could set it up to use the document's update function (assumes that all object
1532 //updates are being reported correctly:
1533 // if (document.NeedsUpdate())
1534 // Do an update if collided with at least *one* object in the document
1540 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1541 ToolHandler(ToolMouseUp, point);
1545 if (Global::selectionInProgress)
1546 Global::selectionInProgress = false;
1548 informativeText.clear();
1549 // Should we be doing this automagically? Hmm...
1550 // Clear our vectors
1555 std::vector<void *>::iterator i;
1557 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1559 if (((Object *)(*i))->selected)
1560 select.push_back(*i);
1563 draggingObject = false;
1565 else if (event->button() == Qt::MiddleButton)
1568 setCursor(Qt::ArrowCursor);
1573 void DrawingView::wheelEvent(QWheelEvent * event)
1575 double zoomFactor = 1.25;
1576 QSize sizeWin = size();
1577 Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
1578 center = Painter::QtToCartesianCoords(center);
1580 // This is not centering for some reason. Need to figure out why. :-/
1581 if (event->delta() > 0)
1583 Vector newOrigin = center - ((center - Global::origin) / zoomFactor);
1584 Global::origin = newOrigin;
1585 Global::zoom *= zoomFactor;
1589 Vector newOrigin = center + ((-center + Global::origin) * zoomFactor);
1590 Global::origin = newOrigin;
1591 Global::zoom /= zoomFactor;
1594 // Global::gridSpacing = gridPixels / Painter::zoom;
1595 // UpdateGridBackground();
1596 SetGridSize(Global::gridSpacing * Global::zoom);
1598 // zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Global::gridSpacing));
1602 void DrawingView::keyPressEvent(QKeyEvent * event)
1604 bool oldShift = shiftDown;
1605 bool oldCtrl = ctrlDown;
1606 bool oldAlt = altDown;
1608 if (event->key() == Qt::Key_Shift)
1610 else if (event->key() == Qt::Key_Control)
1612 else if (event->key() == Qt::Key_Alt)
1615 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1618 ToolHandler(ToolKeyDown, Point(0, 0));
1623 if (oldAlt != altDown)
1626 setCursor(Qt::SizeAllCursor);
1627 // oldPoint = Vector();
1628 oldPoint = oldScrollPoint;
1631 if (select.size() > 0)
1633 if (event->key() == Qt::Key_Up)
1635 TranslateObjects(select, Point(0, +1.0));
1638 else if (event->key() == Qt::Key_Down)
1640 TranslateObjects(select, Point(0, -1.0));
1643 else if (event->key() == Qt::Key_Right)
1645 TranslateObjects(select, Point(+1.0, 0));
1648 else if (event->key() == Qt::Key_Left)
1650 TranslateObjects(select, Point(-1.0, 0));
1657 void DrawingView::keyReleaseEvent(QKeyEvent * event)
1659 bool oldShift = shiftDown;
1660 bool oldCtrl = ctrlDown;
1661 bool oldAlt = altDown;
1663 if (event->key() == Qt::Key_Shift)
1665 else if (event->key() == Qt::Key_Control)
1667 else if (event->key() == Qt::Key_Alt)
1670 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1673 ToolHandler(ToolKeyUp, Point(0, 0));
1678 if (oldAlt != altDown)
1681 setCursor(Qt::ArrowCursor);
1687 // This looks strange, but it's really quite simple: We want a point that's
1688 // more than half-way to the next grid point to snap there while conversely we
1689 // want a point that's less than half-way to to the next grid point then snap
1690 // to the one before it. So we add half of the grid spacing to the point, then
1691 // divide by it so that we can remove the fractional part, then multiply it
1692 // back to get back to the correct answer.
1694 Point DrawingView::SnapPointToGrid(Point point)
1696 point += Global::gridSpacing / 2.0; // *This* adds to Z!!!
1697 point /= Global::gridSpacing;
1698 point.x = floor(point.x);//need to fix this for negative numbers...
1699 point.y = floor(point.y);
1700 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
1701 point *= Global::gridSpacing;
1706 Point DrawingView::SnapPointToAngle(Point point)
1708 // Snap to a single digit angle (using toolpoint #1 as the center)
1709 double angle = Vector::Angle(toolPoint[0], point);
1710 double length = Vector::Magnitude(toolPoint[0], point);
1712 // Convert from radians to degrees
1713 double degAngle = angle * RADIANS_TO_DEGREES;
1714 double snapAngle = (double)((int)(degAngle + 0.5));
1717 v.SetAngleAndLength(snapAngle * DEGREES_TO_RADIANS, length);
1718 point = toolPoint[0] + v;
1724 Rect DrawingView::GetObjectExtents(Object * obj)
1726 // Default to empty rect, if object checks below fail for some reason
1734 rect = Rect(obj->p[0], obj->p[1]);
1740 rect = Rect(obj->p[0], obj->p[0]);
1741 rect.Expand(obj->radius[0]);
1747 Arc * a = (Arc *)obj;
1749 double start = a->angle[0];
1750 double end = start + a->angle[1];
1751 rect = Rect(Point(cos(start), sin(start)), Point(cos(end), sin(end)));
1753 // If the end of the arc is before the beginning, add 360 degrees to it
1757 // Adjust the bounds depending on which axes are crossed
1758 if ((start < QTR_TAU) && (end > QTR_TAU))
1761 if ((start < HALF_TAU) && (end > HALF_TAU))
1764 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
1767 if ((start < TAU) && (end > TAU))
1770 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
1773 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
1776 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
1779 rect *= a->radius[0];
1780 rect.Translate(a->p[0]);
1786 Text * t = (Text *)obj;
1787 rect = Rect(t->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
1793 Container * c = (Container *)obj;
1794 std::vector<void *>::iterator i = c->objects.begin();
1795 rect = GetObjectExtents((Object *)*i);
1798 for(; i!=c->objects.end(); i++)
1799 rect |= GetObjectExtents((Object *)*i);
1810 void DrawingView::CheckObjectBounds(void)
1812 std::vector<void *>::iterator i;
1814 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1816 Object * obj = (Object *)(*i);
1817 obj->selected = false;
1824 Line * l = (Line *)obj;
1826 if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
1834 Circle * c = (Circle *)obj;
1836 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]))
1844 Arc * a = (Arc *)obj;
1846 double start = a->angle[0];
1847 double end = start + a->angle[1];
1848 QPointF p1(cos(start), sin(start));
1849 QPointF p2(cos(end), sin(end));
1850 QRectF bounds(p1, p2);
1853 // Swap X/Y coordinates if they're backwards...
1854 if (bounds.left() > bounds.right())
1856 double temp = bounds.left();
1857 bounds.setLeft(bounds.right());
1858 bounds.setRight(temp);
1861 if (bounds.bottom() > bounds.top())
1863 double temp = bounds.bottom();
1864 bounds.setBottom(bounds.top());
1865 bounds.setTop(temp);
1868 // Doesn't work as advertised! For shame!
1869 bounds = bounds.normalized();
1872 // If the end of the arc is before the beginning, add 360 degrees to it
1876 // Adjust the bounds depending on which axes are crossed
1877 if ((start < QTR_TAU) && (end > QTR_TAU))
1880 if ((start < HALF_TAU) && (end > HALF_TAU))
1881 bounds.setLeft(-1.0);
1883 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
1884 bounds.setBottom(-1.0);
1886 if ((start < TAU) && (end > TAU))
1887 bounds.setRight(1.0);
1889 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
1892 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
1893 bounds.setLeft(-1.0);
1895 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
1896 bounds.setBottom(-1.0);
1898 bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
1899 bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
1900 bounds.translate(a->p[0].x, a->p[0].y);
1902 if (Global::selection.contains(bounds))
1910 Text * t = (Text *)obj;
1911 Rect r(obj->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
1913 if (Global::selection.contains(r.l, r.t) && Global::selection.contains(r.r, r.b))
1926 bool DrawingView::HitTestObjects(Point point)
1928 std::vector<void *>::iterator i;
1930 bool needUpdate = false;
1931 hoverPointValid = false;
1933 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1935 Object * obj = (Object *)(*i);
1937 // If we're seeing the object we're dragging, skip it
1938 if (draggingObject && (obj == dragged))
1941 if (HitTest(obj, point))
1947 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
1948 emit(ObjectHovered(obj));
1956 bool DrawingView::HitTest(Object * obj, Point point)
1958 bool needUpdate = false;
1964 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
1965 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
1966 Vector lineSegment = obj->p[1] - obj->p[0];
1967 Vector v1 = point - obj->p[0];
1968 Vector v2 = point - obj->p[1];
1969 double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
1973 distance = v1.Magnitude();
1975 distance = v2.Magnitude();
1977 // distance = ?Det?(ls, v1) / |ls|
1978 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
1979 / lineSegment.Magnitude());
1981 if ((v1.Magnitude() * Global::zoom) < 8.0)
1983 obj->hitPoint[0] = true;
1984 hoverPoint = obj->p[0];
1985 hoverPointValid = true;
1987 else if ((v2.Magnitude() * Global::zoom) < 8.0)
1989 obj->hitPoint[1] = true;
1990 hoverPoint = obj->p[1];
1991 hoverPointValid = true;
1993 else if ((distance * Global::zoom) < 5.0)
1994 obj->hitObject = true;
1996 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
1998 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
2006 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
2007 obj->hitPoint[0] = obj->hitObject = false;
2008 double length = Vector::Magnitude(obj->p[0], point);
2010 if ((length * Global::zoom) < 8.0)
2012 obj->hitPoint[0] = true;
2013 hoverPoint = obj->p[0];
2014 hoverPointValid = true;
2016 else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
2017 obj->hitObject = true;
2019 obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
2021 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
2029 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
2030 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
2031 double length = Vector::Magnitude(obj->p[0], point);
2032 double angle = Vector::Angle(obj->p[0], point);
2034 // Make sure we get the angle in the correct spot
2035 if (angle < obj->angle[0])
2038 // Get the span that we're pointing at...
2039 double span = angle - obj->angle[0];
2041 // N.B.: Still need to hit test the arc start & arc span handles...
2042 double spanAngle = obj->angle[0] + obj->angle[1];
2043 Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
2044 Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
2045 double length2 = Vector::Magnitude(point, handle1);
2046 double length3 = Vector::Magnitude(point, handle2);
2048 if ((length * Global::zoom) < 8.0)
2050 obj->hitPoint[0] = true;
2051 hoverPoint = obj->p[0];
2052 hoverPointValid = true;
2054 else if ((length2 * Global::zoom) < 8.0)
2056 obj->hitPoint[1] = true;
2057 hoverPoint = handle1;
2058 hoverPointValid = true;
2060 else if ((length3 * Global::zoom) < 8.0)
2062 obj->hitPoint[2] = true;
2063 hoverPoint = handle2;
2064 hoverPointValid = true;
2066 else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
2067 obj->hitObject = true;
2069 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
2071 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
2079 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHP3 = obj->hitPoint[3], oldHP4 = obj->hitPoint[4], oldHO = obj->hitObject;
2080 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitPoint[3] = obj->hitPoint[4] = obj->hitObject = false;
2082 Dimension * d = (Dimension *)obj;
2084 Vector orthogonal = Vector::Normal(d->lp[0], d->lp[1]);
2085 // Get our line parallel to our points
2086 float scaledThickness = Global::scale * obj->thickness;
2087 Point p1 = d->lp[0] + (orthogonal * 10.0 * scaledThickness);
2088 Point p2 = d->lp[1] + (orthogonal * 10.0 * scaledThickness);
2089 Point p3(p1, point);
2091 Vector v1(d->p[0], point);
2092 Vector v2(d->p[1], point);
2093 Vector lineSegment(p1, p2);
2094 double t = Geometry::ParameterOfLineAndPoint(p1, p2, point);
2096 Point midpoint = (p1 + p2) / 2.0;
2097 Point hFSPoint = Point(midpoint, point);
2098 Point hCS1Point = Point((p1 + midpoint) / 2.0, point);
2099 Point hCS2Point = Point((midpoint + p2) / 2.0, point);
2102 distance = v1.Magnitude();
2104 distance = v2.Magnitude();
2106 // distance = ?Det?(ls, v1) / |ls|
2107 distance = fabs((lineSegment.x * p3.y - p3.x * lineSegment.y)
2108 / lineSegment.Magnitude());
2110 if ((v1.Magnitude() * Global::zoom) < 8.0)
2111 obj->hitPoint[0] = true;
2112 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2113 obj->hitPoint[1] = true;
2114 else if ((distance * Global::zoom) < 5.0)
2115 obj->hitObject = true;
2117 if ((hFSPoint.Magnitude() * Global::zoom) < 8.0)
2118 obj->hitPoint[2] = true;
2119 else if ((hCS1Point.Magnitude() * Global::zoom) < 8.0)
2120 obj->hitPoint[3] = true;
2121 else if ((hCS2Point.Magnitude() * Global::zoom) < 8.0)
2122 obj->hitPoint[4] = true;
2124 // return (hitPoint1 || hitPoint2 || hitLine || hitFlipSwitch || hitChangeSwitch1 || hitChangeSwitch2 ? true : false);
2125 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitPoint[3] || obj->hitPoint[4] || obj->hitObject ? true : false);
2127 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHP3 != obj->hitPoint[3]) || (oldHP4 != obj->hitPoint[4]) || (oldHO != obj->hitObject))
2135 Text * t = (Text *)obj;
2136 bool oldHO = obj->hitObject;
2137 obj->hitObject = false;
2139 Rect r(obj->p[0], Point(obj->p[0].x + t->extents.Width(), obj->p[0].y - t->extents.Height()));
2140 //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);
2142 if (r.Contains(point))
2143 obj->hitObject = true;
2145 obj->hovered = (obj->hitObject ? true : false);
2147 if (oldHO != obj->hitObject)
2155 // Containers must be recursively tested...
2156 Container * c = (Container *)obj;
2157 c->hitObject = false;
2159 std::vector<void *>::iterator i;
2161 for(i=c->objects.begin(); i!=c->objects.end(); i++)
2163 Object * cObj = (Object *)*i;
2165 if (HitTest(cObj, point))
2168 if (cObj->hitObject == true)
2169 c->hitObject = true;
2171 if (cObj->hovered == true)
2186 bool DrawingView::HandleObjectClicked(void)
2188 if (dragged->type == OTDimension)
2190 Dimension * d = (Dimension *)dragged;
2194 // Hit the "flip sides" switch, so flip 'em
2195 Point temp = d->p[0];
2200 else if (d->hitPoint[3])
2202 // There are three cases here: aligned, horizontal, & vertical.
2203 // Aligned and horizontal do the same thing, vertical goes back to
2205 if (d->subtype == DTLinearVert)
2206 d->subtype = DTLinear;
2208 d->subtype = DTLinearVert;
2212 else if (d->hitPoint[4])
2214 // There are three cases here: aligned, horizontal, & vertical.
2215 // Aligned and vertical do the same thing, horizontal goes back to
2217 if (d->subtype == DTLinearHorz)
2218 d->subtype = DTLinear;
2220 d->subtype = DTLinearHorz;
2230 void DrawingView::HandleObjectMovement(Point point)
2232 Point delta = point - oldPoint;
2233 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
2234 // Object * obj = (Object *)hover[0];
2235 Object * obj = dragged;
2236 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
2237 //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"));
2242 if (obj->hitPoint[0])
2244 else if (obj->hitPoint[1])
2246 else if (obj->hitObject)
2255 if (obj->hitPoint[0])
2257 else if (obj->hitObject)
2259 //this doesn't work. we need to save this on mouse down for this to work correctly!
2260 // double oldRadius = obj->radius[0];
2261 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
2263 QString text = QObject::tr("Radius: %1");//\nScale: %2%");
2264 informativeText = text.arg(obj->radius[0], 0, 'd', 4);//.arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0);
2270 if (obj->hitPoint[0])
2272 else if (obj->hitPoint[1])
2274 // Change the Arc's span (handle #1)
2277 double angle = Vector::Angle(obj->p[0], point);
2278 double delta = angle - obj->angle[0];
2283 obj->angle[1] -= delta;
2284 obj->angle[0] = angle;
2286 if (obj->angle[1] < 0)
2287 obj->angle[1] += TAU;
2289 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
2290 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);
2294 double angle = Vector::Angle(obj->p[0], point);
2295 obj->angle[0] = angle;
2296 QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
2297 informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 4);
2299 else if (obj->hitPoint[2])
2301 // Change the Arc's span (handle #2)
2304 double angle = Vector::Angle(obj->p[0], point);
2305 obj->angle[1] = angle - obj->angle[0];
2307 if (obj->angle[1] < 0)
2308 obj->angle[1] += TAU;
2310 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
2311 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);
2315 double angle = Vector::Angle(obj->p[0], point);
2316 obj->angle[0] = angle - obj->angle[1];
2318 if (obj->angle[0] < 0)
2319 obj->angle[0] += TAU;
2321 QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
2322 informativeText = text.arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 4);
2324 else if (obj->hitObject)
2331 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
2332 QString text = QObject::tr("Radius: %1");
2333 informativeText = text.arg(obj->radius[0], 0, 'd', 4);
2339 if (obj->hitPoint[0])
2341 else if (obj->hitPoint[1])
2343 else if (obj->hitObject)
2358 // This is shitty, but works for now until I can code up something
2360 TranslateObject(obj, delta);