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 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);
753 void DrawingView::ToolDraw(Painter * painter)
755 if (Global::tool == TTLine)
757 if (Global::toolState == TSNone)
759 painter->DrawHandle(toolPoint[0]);
761 else if ((Global::toolState == TSPoint2) && shiftDown)
763 painter->DrawHandle(toolPoint[1]);
767 painter->DrawLine(toolPoint[0], toolPoint[1]);
768 painter->DrawHandle(toolPoint[1]);
770 Vector v(toolPoint[0], toolPoint[1]);
771 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
772 double absLength = v.Magnitude();
773 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
774 informativeText = text.arg(absLength).arg(absAngle);
777 else if (Global::tool == TTCircle)
779 if (Global::toolState == TSNone)
781 painter->DrawHandle(toolPoint[0]);
783 else if ((Global::toolState == TSPoint2) && shiftDown)
785 painter->DrawHandle(toolPoint[1]);
789 painter->DrawCross(toolPoint[0]);
790 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
791 painter->SetBrush(QBrush(Qt::NoBrush));
792 painter->DrawEllipse(toolPoint[0], length, length);
793 QString text = tr("Radius: %1 in.");
794 informativeText = text.arg(length);
797 else if (Global::tool == TTArc)
799 if (Global::toolState == TSNone)
801 painter->DrawHandle(toolPoint[0]);
803 else if (Global::toolState == TSPoint2)
805 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
806 painter->SetBrush(QBrush(Qt::NoBrush));
807 painter->DrawEllipse(toolPoint[0], length, length);
808 painter->DrawLine(toolPoint[0], toolPoint[1]);
809 painter->DrawHandle(toolPoint[1]);
810 QString text = tr("Radius: %1 in.");
811 informativeText = text.arg(length);
813 else if (Global::toolState == TSPoint3)
815 double angle = Vector::Angle(toolPoint[0], toolPoint[2]);
816 painter->DrawLine(toolPoint[0], toolPoint[2]);
817 painter->SetBrush(QBrush(Qt::NoBrush));
818 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
819 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
820 QString text = tr("Angle start: %1") + QChar(0x00B0);
821 informativeText = text.arg(RADIANS_TO_DEGREES * angle);
825 double angle = Vector::Angle(toolPoint[0], toolPoint[3]);
826 double span = angle - toolPoint[2].x;
831 painter->DrawLine(toolPoint[0], toolPoint[3]);
832 painter->SetBrush(QBrush(Qt::NoBrush));
833 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
834 painter->SetPen(0xFF00FF, 2.0, LSSolid);
835 painter->DrawArc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
836 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
837 QString text = tr("Arc span: %1") + QChar(0x00B0);
838 informativeText = text.arg(RADIANS_TO_DEGREES * span);
841 else if (Global::tool == TTRotate)
843 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
844 painter->DrawHandle(toolPoint[0]);
845 else if ((Global::toolState == TSPoint2) && shiftDown)
846 painter->DrawHandle(toolPoint[1]);
849 if (toolPoint[0] == toolPoint[1])
852 painter->DrawLine(toolPoint[0], toolPoint[1]);
854 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
855 QString text = QChar(0x2221) + QObject::tr(": %1");
856 informativeText = text.arg(absAngle);
859 informativeText += " (Copy)";
862 else if (Global::tool == TTMirror)
864 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
865 painter->DrawHandle(toolPoint[0]);
866 else if ((Global::toolState == TSPoint2) && shiftDown)
867 painter->DrawHandle(toolPoint[1]);
870 if (toolPoint[0] == toolPoint[1])
873 Point mirrorPoint = toolPoint[0] + Vector(toolPoint[1], toolPoint[0]);
874 painter->DrawLine(mirrorPoint, toolPoint[1]);
876 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
878 if (absAngle > 180.0)
881 QString text = QChar(0x2221) + QObject::tr(": %1");
882 informativeText = text.arg(absAngle);
885 informativeText += " (Copy)";
891 void DrawingView::LineHandler(int mode, Point p)
896 if (Global::toolState == TSNone)
903 if (Global::toolState == TSNone)
910 if (Global::toolState == TSNone)
912 Global::toolState = TSPoint2;
913 // Prevent spurious line from drawing...
914 toolPoint[1] = toolPoint[0];
916 else if ((Global::toolState == TSPoint2) && shiftDown)
918 // Key override is telling us to make a new line, not continue the
920 toolPoint[0] = toolPoint[1];
924 Line * l = new Line(toolPoint[0], toolPoint[1]);
925 l->layer = Global::activeLayer;
926 document.objects.push_back(l);
927 toolPoint[0] = toolPoint[1];
933 void DrawingView::CircleHandler(int mode, Point p)
938 if (Global::toolState == TSNone)
945 if (Global::toolState == TSNone)
952 if (Global::toolState == TSNone)
954 Global::toolState = TSPoint2;
955 // Prevent spurious line from drawing...
956 toolPoint[1] = toolPoint[0];
958 else if ((Global::toolState == TSPoint2) && shiftDown)
960 // Key override is telling us to make a new line, not continue the
962 toolPoint[0] = toolPoint[1];
966 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
967 Circle * c = new Circle(toolPoint[0], length);
968 c->layer = Global::activeLayer;
969 document.objects.push_back(c);
970 toolPoint[0] = toolPoint[1];
971 Global::toolState = TSNone;
977 void DrawingView::ArcHandler(int mode, Point p)
982 if (Global::toolState == TSNone)
984 else if (Global::toolState == TSPoint2)
986 else if (Global::toolState == TSPoint3)
993 if (Global::toolState == TSNone)
995 else if (Global::toolState == TSPoint2)
997 else if (Global::toolState == TSPoint3)
1010 if (Global::toolState == TSNone)
1012 // Prevent spurious line from drawing...
1013 toolPoint[1] = toolPoint[0];
1014 Global::toolState = TSPoint2;
1016 else if (Global::toolState == TSPoint2)
1020 // Key override is telling us to start arc at new center, not
1021 // continue the current one.
1022 toolPoint[0] = toolPoint[1];
1026 // Set the radius in toolPoint[1].x
1027 toolPoint[1].x = Vector::Magnitude(toolPoint[0], toolPoint[1]);
1028 Global::toolState = TSPoint3;
1030 else if (Global::toolState == TSPoint3)
1032 // Set the angle in toolPoint[2].x
1033 toolPoint[2].x = Vector::Angle(toolPoint[0], toolPoint[2]);
1034 Global::toolState = TSPoint4;
1038 double endAngle = Vector::Angle(toolPoint[0], toolPoint[3]);
1039 double span = endAngle - toolPoint[2].x;
1044 Arc * arc = new Arc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
1045 arc->layer = Global::activeLayer;
1046 document.objects.push_back(arc);
1047 Global::toolState = TSNone;
1053 void DrawingView::RotateHandler(int mode, Point p)
1058 if (Global::toolState == TSNone)
1061 SavePointsFrom(select, toolScratch);
1062 Global::toolState = TSPoint1;
1064 else if (Global::toolState == TSPoint1)
1071 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1073 else if (Global::toolState == TSPoint2)
1081 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1082 std::vector<void *>::iterator j = select.begin();
1083 std::vector<Object>::iterator i = toolScratch.begin();
1085 for(; i!=toolScratch.end(); i++, j++)
1088 Point p1 = Geometry::RotatePointAroundPoint(obj.p[0], toolPoint[0], angle);
1089 Point p2 = Geometry::RotatePointAroundPoint(obj.p[1], toolPoint[0], angle);
1090 Object * obj2 = (Object *)(*j);
1094 if (obj.type == OTArc)
1096 obj2->angle[0] = obj.angle[0] + angle;
1098 if (obj2->angle[0] > TAU)
1099 obj2->angle[0] -= TAU;
1106 if (Global::toolState == TSPoint1)
1108 Global::toolState = TSPoint2;
1109 // Prevent spurious line from drawing...
1110 toolPoint[1] = toolPoint[0];
1112 else if ((Global::toolState == TSPoint2) && shiftDown)
1114 // Key override is telling us to make a new line, not continue the
1116 toolPoint[0] = toolPoint[1];
1120 // Either we're finished with our rotate, or we're stamping a copy.
1123 // Stamp a copy of the selection at the current rotation & bail
1124 std::vector<void *> temp;
1125 CopyObjects(select, temp);
1126 ClearSelected(temp);
1127 AddObjectsTo(document.objects, temp);
1128 RestorePointsTo(select, toolScratch);
1133 Global::toolState = TSPoint1;
1134 SavePointsFrom(select, toolScratch);
1139 // Reset the selection if shift held down...
1141 RestorePointsTo(select, toolScratch);
1145 // Reset selection when key is let up
1147 RotateHandler(ToolMouseMove, toolPoint[1]);
1151 RestorePointsTo(select, toolScratch);
1156 void DrawingView::MirrorHandler(int mode, Point p)
1161 if (Global::toolState == TSNone)
1164 SavePointsFrom(select, toolScratch);
1165 Global::toolState = TSPoint1;
1167 else if (Global::toolState == TSPoint1)
1174 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1176 else if (Global::toolState == TSPoint2)
1184 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1185 std::vector<void *>::iterator j = select.begin();
1186 std::vector<Object>::iterator i = toolScratch.begin();
1188 for(; i!=toolScratch.end(); i++, j++)
1191 Point p1 = Geometry::MirrorPointAroundLine(obj.p[0], toolPoint[0], toolPoint[1]);
1192 Point p2 = Geometry::MirrorPointAroundLine(obj.p[1], toolPoint[0], toolPoint[1]);
1193 Object * obj2 = (Object *)(*j);
1197 if (obj.type == OTArc)
1199 // This is 2*mirror angle - obj angle - obj span
1200 obj2->angle[0] = (2.0 * angle) - obj.angle[0] - obj.angle[1];
1202 if (obj2->angle[0] > TAU)
1203 obj2->angle[0] -= TAU;
1210 if (Global::toolState == TSPoint1)
1212 Global::toolState = TSPoint2;
1213 // Prevent spurious line from drawing...
1214 toolPoint[1] = toolPoint[0];
1216 else if ((Global::toolState == TSPoint2) && shiftDown)
1218 // Key override is telling us to make a new line, not continue the
1220 toolPoint[0] = toolPoint[1];
1224 // Either we're finished with our rotate, or we're stamping a copy.
1227 // Stamp a copy of the selection at the current rotation & bail
1228 std::vector<void *> temp;
1229 CopyObjects(select, temp);
1230 ClearSelected(temp);
1231 AddObjectsTo(document.objects, temp);
1232 RestorePointsTo(select, toolScratch);
1237 Global::toolState = TSPoint1;
1238 SavePointsFrom(select, toolScratch);
1243 // Reset the selection if shift held down...
1245 RestorePointsTo(select, toolScratch);
1249 // Reset selection when key is let up
1251 MirrorHandler(ToolMouseMove, toolPoint[1]);
1255 RestorePointsTo(select, toolScratch);
1260 void DrawingView::mousePressEvent(QMouseEvent * event)
1262 if (event->button() == Qt::LeftButton)
1264 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1266 // Handle tool processing, if any
1269 if (hoveringIntersection)
1270 point = intersectionPoint;
1271 else if (Global::snapToGrid)
1272 point = SnapPointToGrid(point);
1274 //Also, may want to figure out if hovering over a snap point on an
1275 //object, snap to grid if not.
1276 // Snap to object point if valid...
1277 // if (Global::snapPointIsValid)
1278 // point = Global::snapPoint;
1280 ToolHandler(ToolMouseDown, point);
1284 // Clear the selection only if CTRL isn't being held on click
1286 ClearSelected(document.objects);
1287 // ClearSelection();
1289 // If any objects are being hovered on click, add them to the selection
1293 AddHoveredToSelection();
1294 update(); // needed??
1295 GetHovered(hover); // prolly needed
1296 dragged = (Object *)hover[0];
1297 draggingObject = true;
1299 // Alert the pen widget
1300 emit(ObjectSelected(dragged));
1302 // See if anything is using just a straight click on a handle
1303 if (HandleObjectClicked())
1305 draggingObject = false;
1310 // Needed for grab & moving objects
1311 // We do it *after*... why? (doesn't seem to confer any advantage...)
1312 if (hoveringIntersection)
1313 oldPoint = intersectionPoint;
1314 else if (Global::snapToGrid)
1315 oldPoint = SnapPointToGrid(point);
1320 // Didn't hit any object and not using a tool, so do a selection rectangle
1321 Global::selectionInProgress = true;
1322 Global::selection.setTopLeft(QPointF(point.x, point.y));
1323 Global::selection.setBottomRight(QPointF(point.x, point.y));
1325 else if (event->button() == Qt::MiddleButton)
1328 oldPoint = Vector(event->x(), event->y());
1329 // Should also change the mouse pointer as well...
1330 setCursor(Qt::SizeAllCursor);
1335 void DrawingView::mouseMoveEvent(QMouseEvent * event)
1337 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1338 Global::selection.setBottomRight(QPointF(point.x, point.y));
1339 // Only needs to be done here, as mouse down is always preceded by movement
1340 Global::snapPointIsValid = false;
1341 hoveringIntersection = false;
1344 if (event->buttons() & Qt::MiddleButton)
1346 point = Vector(event->x(), event->y());
1347 // Since we're using Qt coords for scrolling, we have to adjust them
1348 // here to conform to Cartesian coords, since the origin is using
1350 Vector delta(oldPoint, point);
1351 delta /= Global::zoom;
1353 Global::origin -= delta;
1355 UpdateGridBackground();
1361 // If we're doing a selection rect, see if any objects are engulfed by it
1362 // (implies left mouse button held down)
1363 if (Global::selectionInProgress)
1365 CheckObjectBounds();
1370 // Do object hit testing...
1371 bool needUpdate = HitTestObjects(point);
1374 // Check for multi-hover...
1377 //need to check for case where hover is over 2 circles and a 3rd's center...
1378 Object * obj1 = (Object *)hover[0], * obj2 = (Object *)hover[1];
1380 Geometry::Intersects(obj1, obj2);
1381 int numIntersecting = Global::numIntersectParams;
1382 double t = Global::intersectParam[0];
1383 double u = Global::intersectParam[1];
1385 if (numIntersecting > 0)
1387 Vector v1 = Geometry::GetPointForParameter(obj1, t);
1388 Vector v2 = Geometry::GetPointForParameter(obj2, u);
1389 QString text = tr("Intersection t=%1 (%3, %4), u=%2 (%5, %6)");
1390 informativeText = text.arg(t).arg(u).arg(v1.x).arg(v1.y).arg(v2.x).arg(v2.y);
1392 hoveringIntersection = true;
1393 intersectionPoint = v1;
1396 numIntersecting = Global::numIntersectPoints;
1398 if (numIntersecting > 0)
1400 Vector v1 = Global::intersectPoint[0];
1402 if (numIntersecting == 2)
1404 Vector v2 = Global::intersectPoint[1];
1406 if (Vector::Magnitude(v2, point) < Vector::Magnitude(v1, point))
1410 QString text = tr("Intersection <%1, %2>");
1411 informativeText = text.arg(v1.x).arg(v1.y);
1412 hoveringIntersection = true;
1413 intersectionPoint = v1;
1417 // Handle object movement (left button down & over an object)
1418 if ((event->buttons() & Qt::LeftButton) && draggingObject && !Global::tool)
1420 if (hoveringIntersection)
1421 point = intersectionPoint;
1422 else if (hoverPointValid)
1424 else if (Global::snapToGrid)
1425 point = SnapPointToGrid(point);
1427 HandleObjectMovement(point);
1433 // Do tool handling, if any are active...
1436 if (hoveringIntersection)
1437 point = intersectionPoint;
1438 else if (hoverPointValid)
1440 else if (Global::snapToGrid)
1443 point = SnapPointToAngle(point);
1445 point = SnapPointToGrid(point);
1448 ToolHandler(ToolMouseMove, point);
1451 // This is used to draw the tool crosshair...
1454 if (needUpdate || Global::tool)
1459 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
1461 if (event->button() == Qt::LeftButton)
1463 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
1464 //could set it up to use the document's update function (assumes that all object
1465 //updates are being reported correctly:
1466 // if (document.NeedsUpdate())
1467 // Do an update if collided with at least *one* object in the document
1473 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1474 ToolHandler(ToolMouseUp, point);
1478 if (Global::selectionInProgress)
1479 Global::selectionInProgress = false;
1481 informativeText.clear();
1482 // Should we be doing this automagically? Hmm...
1483 // Clear our vectors
1488 std::vector<void *>::iterator i;
1490 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1492 if (((Object *)(*i))->selected)
1493 select.push_back(*i);
1496 draggingObject = false;
1498 else if (event->button() == Qt::MiddleButton)
1501 setCursor(Qt::ArrowCursor);
1506 void DrawingView::wheelEvent(QWheelEvent * event)
1508 double zoomFactor = 1.25;
1509 QSize sizeWin = size();
1510 Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
1511 center = Painter::QtToCartesianCoords(center);
1513 // This is not centering for some reason. Need to figure out why. :-/
1514 if (event->delta() > 0)
1516 Vector newOrigin = center - ((center - Global::origin) / zoomFactor);
1517 Global::origin = newOrigin;
1518 Global::zoom *= zoomFactor;
1522 Vector newOrigin = center + ((-center + Global::origin) * zoomFactor);
1523 Global::origin = newOrigin;
1524 Global::zoom /= zoomFactor;
1527 // Global::gridSpacing = gridPixels / Painter::zoom;
1528 // UpdateGridBackground();
1529 SetGridSize(Global::gridSpacing * Global::zoom);
1531 // zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Global::gridSpacing));
1535 void DrawingView::keyPressEvent(QKeyEvent * event)
1537 bool oldShift = shiftDown;
1538 bool oldCtrl = ctrlDown;
1540 if (event->key() == Qt::Key_Shift)
1542 else if (event->key() == Qt::Key_Control)
1545 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1548 ToolHandler(ToolKeyDown, Point(0, 0));
1553 if (select.size() > 0)
1555 if (event->key() == Qt::Key_Up)
1557 TranslateObjects(select, Point(0, +1.0));
1560 else if (event->key() == Qt::Key_Down)
1562 TranslateObjects(select, Point(0, -1.0));
1565 else if (event->key() == Qt::Key_Right)
1567 TranslateObjects(select, Point(+1.0, 0));
1570 else if (event->key() == Qt::Key_Left)
1572 TranslateObjects(select, Point(-1.0, 0));
1579 void DrawingView::keyReleaseEvent(QKeyEvent * event)
1581 bool oldShift = shiftDown;
1582 bool oldCtrl = ctrlDown;
1584 if (event->key() == Qt::Key_Shift)
1586 else if (event->key() == Qt::Key_Control)
1589 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1592 ToolHandler(ToolKeyUp, Point(0, 0));
1600 // This looks strange, but it's really quite simple: We want a point that's
1601 // more than half-way to the next grid point to snap there while conversely we
1602 // want a point that's less than half-way to to the next grid point then snap
1603 // to the one before it. So we add half of the grid spacing to the point, then
1604 // divide by it so that we can remove the fractional part, then multiply it
1605 // back to get back to the correct answer.
1607 Point DrawingView::SnapPointToGrid(Point point)
1609 point += Global::gridSpacing / 2.0; // *This* adds to Z!!!
1610 point /= Global::gridSpacing;
1611 point.x = floor(point.x);//need to fix this for negative numbers...
1612 point.y = floor(point.y);
1613 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
1614 point *= Global::gridSpacing;
1619 Point DrawingView::SnapPointToAngle(Point point)
1621 // Snap to a single digit angle (using toolpoint #1 as the center)
1622 double angle = Vector::Angle(toolPoint[0], point);
1623 double length = Vector::Magnitude(toolPoint[0], point);
1625 // Convert from radians to degrees
1626 double degAngle = angle * RADIANS_TO_DEGREES;
1627 double snapAngle = (double)((int)(degAngle + 0.5));
1630 v.SetAngleAndLength(snapAngle * DEGREES_TO_RADIANS, length);
1631 point = toolPoint[0] + v;
1637 Rect DrawingView::GetObjectExtents(Object * obj)
1639 // Default to empty rect, if object checks below fail for some reason
1647 rect = Rect(obj->p[0], obj->p[1]);
1653 rect = Rect(obj->p[0], obj->p[0]);
1654 rect.Expand(obj->radius[0]);
1660 Arc * a = (Arc *)obj;
1662 double start = a->angle[0];
1663 double end = start + a->angle[1];
1664 rect = Rect(Point(cos(start), sin(start)), Point(cos(end), sin(end)));
1666 // If the end of the arc is before the beginning, add 360 degrees to it
1670 // Adjust the bounds depending on which axes are crossed
1671 if ((start < QTR_TAU) && (end > QTR_TAU))
1674 if ((start < HALF_TAU) && (end > HALF_TAU))
1677 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
1680 if ((start < TAU) && (end > TAU))
1683 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
1686 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
1689 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
1692 rect *= a->radius[0];
1693 rect.Translate(a->p[0]);
1699 Text * t = (Text *)obj;
1700 rect = Rect(t->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
1706 Container * c = (Container *)obj;
1707 std::vector<void *>::iterator i = c->objects.begin();
1708 rect = GetObjectExtents((Object *)*i);
1711 for(; i!=c->objects.end(); i++)
1712 rect |= GetObjectExtents((Object *)*i);
1723 void DrawingView::CheckObjectBounds(void)
1725 std::vector<void *>::iterator i;
1727 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1729 Object * obj = (Object *)(*i);
1730 obj->selected = false;
1737 Line * l = (Line *)obj;
1739 if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
1747 Circle * c = (Circle *)obj;
1749 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]))
1757 Arc * a = (Arc *)obj;
1759 double start = a->angle[0];
1760 double end = start + a->angle[1];
1761 QPointF p1(cos(start), sin(start));
1762 QPointF p2(cos(end), sin(end));
1763 QRectF bounds(p1, p2);
1766 // Swap X/Y coordinates if they're backwards...
1767 if (bounds.left() > bounds.right())
1769 double temp = bounds.left();
1770 bounds.setLeft(bounds.right());
1771 bounds.setRight(temp);
1774 if (bounds.bottom() > bounds.top())
1776 double temp = bounds.bottom();
1777 bounds.setBottom(bounds.top());
1778 bounds.setTop(temp);
1781 // Doesn't work as advertised! For shame!
1782 bounds = bounds.normalized();
1785 // If the end of the arc is before the beginning, add 360 degrees to it
1789 // Adjust the bounds depending on which axes are crossed
1790 if ((start < QTR_TAU) && (end > QTR_TAU))
1793 if ((start < HALF_TAU) && (end > HALF_TAU))
1794 bounds.setLeft(-1.0);
1796 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
1797 bounds.setBottom(-1.0);
1799 if ((start < TAU) && (end > TAU))
1800 bounds.setRight(1.0);
1802 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
1805 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
1806 bounds.setLeft(-1.0);
1808 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
1809 bounds.setBottom(-1.0);
1811 bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
1812 bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
1813 bounds.translate(a->p[0].x, a->p[0].y);
1815 if (Global::selection.contains(bounds))
1823 Text * t = (Text *)obj;
1824 Rect r(obj->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
1826 if (Global::selection.contains(r.l, r.t) && Global::selection.contains(r.r, r.b))
1839 bool DrawingView::HitTestObjects(Point point)
1841 std::vector<void *>::iterator i;
1843 bool needUpdate = false;
1844 hoverPointValid = false;
1846 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1848 Object * obj = (Object *)(*i);
1850 // If we're seeing the object we're dragging, skip it
1851 if (draggingObject && (obj == dragged))
1854 if (HitTest(obj, point))
1860 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
1861 emit(ObjectHovered(obj));
1869 bool DrawingView::HitTest(Object * obj, Point point)
1871 bool needUpdate = false;
1877 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
1878 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
1879 Vector lineSegment = obj->p[1] - obj->p[0];
1880 Vector v1 = point - obj->p[0];
1881 Vector v2 = point - obj->p[1];
1882 double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
1886 distance = v1.Magnitude();
1888 distance = v2.Magnitude();
1890 // distance = ?Det?(ls, v1) / |ls|
1891 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
1892 / lineSegment.Magnitude());
1894 if ((v1.Magnitude() * Global::zoom) < 8.0)
1896 obj->hitPoint[0] = true;
1897 hoverPoint = obj->p[0];
1898 hoverPointValid = true;
1900 else if ((v2.Magnitude() * Global::zoom) < 8.0)
1902 obj->hitPoint[1] = true;
1903 hoverPoint = obj->p[1];
1904 hoverPointValid = true;
1906 else if ((distance * Global::zoom) < 5.0)
1907 obj->hitObject = true;
1909 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
1911 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
1919 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
1920 obj->hitPoint[0] = obj->hitObject = false;
1921 double length = Vector::Magnitude(obj->p[0], point);
1923 if ((length * Global::zoom) < 8.0)
1925 obj->hitPoint[0] = true;
1926 hoverPoint = obj->p[0];
1927 hoverPointValid = true;
1929 else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
1930 obj->hitObject = true;
1932 obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
1934 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
1942 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
1943 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
1944 double length = Vector::Magnitude(obj->p[0], point);
1945 double angle = Vector::Angle(obj->p[0], point);
1947 // Make sure we get the angle in the correct spot
1948 if (angle < obj->angle[0])
1951 // Get the span that we're pointing at...
1952 double span = angle - obj->angle[0];
1954 // N.B.: Still need to hit test the arc start & arc span handles...
1955 double spanAngle = obj->angle[0] + obj->angle[1];
1956 Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
1957 Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
1958 double length2 = Vector::Magnitude(point, handle1);
1959 double length3 = Vector::Magnitude(point, handle2);
1961 if ((length * Global::zoom) < 8.0)
1963 obj->hitPoint[0] = true;
1964 hoverPoint = obj->p[0];
1965 hoverPointValid = true;
1967 else if ((length2 * Global::zoom) < 8.0)
1969 obj->hitPoint[1] = true;
1970 hoverPoint = handle1;
1971 hoverPointValid = true;
1973 else if ((length3 * Global::zoom) < 8.0)
1975 obj->hitPoint[2] = true;
1976 hoverPoint = handle2;
1977 hoverPointValid = true;
1979 else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
1980 obj->hitObject = true;
1982 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
1984 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
1992 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHP3 = obj->hitPoint[3], oldHP4 = obj->hitPoint[4], oldHO = obj->hitObject;
1993 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitPoint[3] = obj->hitPoint[4] = obj->hitObject = false;
1995 Dimension * d = (Dimension *)obj;
1997 Vector orthogonal = Vector::Normal(d->lp[0], d->lp[1]);
1998 // Get our line parallel to our points
1999 float scaledThickness = Global::scale * obj->thickness;
2000 Point p1 = d->lp[0] + (orthogonal * 10.0 * scaledThickness);
2001 Point p2 = d->lp[1] + (orthogonal * 10.0 * scaledThickness);
2002 Point p3(p1, point);
2004 Vector v1(d->p[0], point);
2005 Vector v2(d->p[1], point);
2006 Vector lineSegment(p1, p2);
2007 double t = Geometry::ParameterOfLineAndPoint(p1, p2, point);
2009 Point midpoint = (p1 + p2) / 2.0;
2010 Point hFSPoint = Point(midpoint, point);
2011 Point hCS1Point = Point((p1 + midpoint) / 2.0, point);
2012 Point hCS2Point = Point((midpoint + p2) / 2.0, point);
2015 distance = v1.Magnitude();
2017 distance = v2.Magnitude();
2019 // distance = ?Det?(ls, v1) / |ls|
2020 distance = fabs((lineSegment.x * p3.y - p3.x * lineSegment.y)
2021 / lineSegment.Magnitude());
2023 if ((v1.Magnitude() * Global::zoom) < 8.0)
2024 obj->hitPoint[0] = true;
2025 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2026 obj->hitPoint[1] = true;
2027 else if ((distance * Global::zoom) < 5.0)
2028 obj->hitObject = true;
2030 if ((hFSPoint.Magnitude() * Global::zoom) < 8.0)
2031 obj->hitPoint[2] = true;
2032 else if ((hCS1Point.Magnitude() * Global::zoom) < 8.0)
2033 obj->hitPoint[3] = true;
2034 else if ((hCS2Point.Magnitude() * Global::zoom) < 8.0)
2035 obj->hitPoint[4] = true;
2037 // return (hitPoint1 || hitPoint2 || hitLine || hitFlipSwitch || hitChangeSwitch1 || hitChangeSwitch2 ? true : false);
2038 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitPoint[3] || obj->hitPoint[4] || obj->hitObject ? true : false);
2040 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHP3 != obj->hitPoint[3]) || (oldHP4 != obj->hitPoint[4]) || (oldHO != obj->hitObject))
2048 Text * t = (Text *)obj;
2049 bool oldHO = obj->hitObject;
2050 obj->hitObject = false;
2052 Rect r(obj->p[0], Point(obj->p[0].x + t->extents.Width(), obj->p[0].y - t->extents.Height()));
2053 //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);
2055 if (r.Contains(point))
2056 obj->hitObject = true;
2058 obj->hovered = (obj->hitObject ? true : false);
2060 if (oldHO != obj->hitObject)
2068 // Containers must be recursively tested...
2069 Container * c = (Container *)obj;
2070 c->hitObject = false;
2072 std::vector<void *>::iterator i;
2074 for(i=c->objects.begin(); i!=c->objects.end(); i++)
2076 Object * cObj = (Object *)*i;
2078 if (HitTest(cObj, point))
2081 if (cObj->hitObject == true)
2082 c->hitObject = true;
2084 if (cObj->hovered == true)
2099 bool DrawingView::HandleObjectClicked(void)
2101 if (dragged->type == OTDimension)
2103 Dimension * d = (Dimension *)dragged;
2107 // Hit the "flip sides" switch, so flip 'em
2108 Point temp = d->p[0];
2113 else if (d->hitPoint[3])
2115 // There are three cases here: aligned, horizontal, & vertical.
2116 // Aligned and horizontal do the same thing, vertical goes back to
2118 if (d->subtype == DTLinearVert)
2119 d->subtype = DTLinear;
2121 d->subtype = DTLinearVert;
2125 else if (d->hitPoint[4])
2127 // There are three cases here: aligned, horizontal, & vertical.
2128 // Aligned and vertical do the same thing, horizontal goes back to
2130 if (d->subtype == DTLinearHorz)
2131 d->subtype = DTLinear;
2133 d->subtype = DTLinearHorz;
2143 void DrawingView::HandleObjectMovement(Point point)
2145 Point delta = point - oldPoint;
2146 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
2147 // Object * obj = (Object *)hover[0];
2148 Object * obj = dragged;
2149 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
2150 //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"));
2155 if (obj->hitPoint[0])
2157 else if (obj->hitPoint[1])
2159 else if (obj->hitObject)
2168 if (obj->hitPoint[0])
2170 else if (obj->hitObject)
2172 //this doesn't work. we need to save this on mouse down for this to work correctly!
2173 // double oldRadius = obj->radius[0];
2174 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
2176 QString text = QObject::tr("Radius: %1");//\nScale: %2%");
2177 informativeText = text.arg(obj->radius[0], 0, 'd', 4);//.arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0);
2183 if (obj->hitPoint[0])
2185 else if (obj->hitPoint[1])
2187 // Change the Arc's span (handle #1)
2190 double angle = Vector::Angle(obj->p[0], point);
2191 double delta = angle - obj->angle[0];
2196 obj->angle[1] -= delta;
2197 obj->angle[0] = angle;
2199 if (obj->angle[1] < 0)
2200 obj->angle[1] += TAU;
2202 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
2203 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);
2207 double angle = Vector::Angle(obj->p[0], point);
2208 obj->angle[0] = angle;
2209 QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
2210 informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 4);
2212 else if (obj->hitPoint[2])
2214 // Change the Arc's span (handle #2)
2217 double angle = Vector::Angle(obj->p[0], point);
2218 obj->angle[1] = angle - obj->angle[0];
2220 if (obj->angle[1] < 0)
2221 obj->angle[1] += TAU;
2223 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
2224 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);
2228 double angle = Vector::Angle(obj->p[0], point);
2229 obj->angle[0] = angle - obj->angle[1];
2231 if (obj->angle[0] < 0)
2232 obj->angle[0] += TAU;
2234 QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
2235 informativeText = text.arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 4);
2237 else if (obj->hitObject)
2244 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
2245 QString text = QObject::tr("Radius: %1");
2246 informativeText = text.arg(obj->radius[0], 0, 'd', 4);
2252 if (obj->hitPoint[0])
2254 else if (obj->hitPoint[1])
2256 else if (obj->hitObject)
2271 // This is shitty, but works for now until I can code up something
2273 TranslateObject(obj, delta);