4 // Part of the Architektonas Project
5 // (C) 2011-2020 Underground Software
6 // See the README and GPLv3 files for licensing and warranty information
8 // JLH = James Hammons <jlhamm@acm.org>
11 // --- ---------- ------------------------------------------------------------
12 // JLH 03/22/2011 Created this file
13 // JLH 09/29/2011 Added middle mouse button panning
18 // - Redo rendering code to *not* use Qt's transform functions, as they are tied
19 // to a left-handed system and we need a right-handed one. [DONE]
20 // - Fixed length tool doesn't work on lines [DONE]
25 // - Layer locking (hiding works)
26 // - Fixed angle tool doesn't work on lines
27 // - Make it so "dirty" flag reflects drawing state
30 // Uncomment this for debugging...
32 //#define DEBUGFOO // Various tool debugging...
33 //#define DEBUGTP // Toolpalette debugging...
35 #include "drawingview.h"
40 #include "mathconstants.h"
42 #include "penwidget.h"
46 #define BACKGROUND_MAX_SIZE 512
48 DrawingView::DrawingView(QWidget * parent/*= NULL*/): QWidget(parent),
49 // The value in the settings file will override this.
50 useAntialiasing(true), numHovered(0), shiftDown(false),
51 ctrlDown(false), altDown(false),
52 gridBackground(BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE),
53 scale(1.0), offsetX(-10), offsetY(-10), supressSelected(false),
55 gridPixels(0), collided(false), scrollDrag(false), hoverPointValid(false),
56 hoveringIntersection(false), dragged(NULL), draggingObject(false),
57 angleSnap(false), dirty(false)
59 //wtf? doesn't work except in c++11??? document = { 0 };
60 setBackgroundRole(QPalette::Base);
61 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
63 curMarker = QCursor(QPixmap(":/res/cursor-marker.png"), 1, 18);
64 curDropper = QCursor(QPixmap(":/res/cursor-dropper.png"), 1, 20);
66 Global::gridSpacing = 12.0; // In base units (inch is default)
68 Line * line = new Line(Vector(5, 5), Vector(50, 40), 2.0, 0xFF7F00, LSDash);
70 document.Add(new Line(Vector(50, 40), Vector(10, 83)));
71 document.Add(new Line(Vector(10, 83), Vector(17, 2)));
72 document.Add(new Circle(Vector(100, 100), 36));
73 document.Add(new Circle(Vector(50, 150), 49));
74 document.Add(new Arc(Vector(300, 300), 32, TAU / 8.0, TAU * 0.65)),
75 document.Add(new Arc(Vector(200, 200), 60, TAU / 4.0, TAU * 0.75));
76 document.Add(new Text(Vector(10, 83), "Here is some awesome text!"));
81 Here we set the grid size in pixels--12 in this case. Initially, we have our
82 zoom set to make this represent 12 inches at a zoom factor of 25%. (This is
83 arbitrary.) So, to be able to decouple the grid size from the zoom, we need
84 to be able to set the size of the background grid (which we do here at an
85 arbitrary 12 pixels) to anything we want (within reason, of course :-).
87 The drawing enforces the grid spacing through the drawing->gridSpacing variable.
89 drawing->gridSpacing = 12.0 / Global::zoom;
91 Global::zoom is the zoom factor for the drawing, and all mouse clicks are
92 translated to Cartesian coordinates through this. (Initially, Global::zoom is
93 set to 1.0. SCREEN_ZOOM is set to 1.0/4.0.)
95 Really, the 100% zoom level can be set at *any* zoom level, it's more of a
96 convenience function than any measure of absolutes. Doing things that way we
97 could rid ourselves of the whole SCREEN_ZOOM parameter and all the attendant
98 shittiness that comes with it.
100 However, it seems that SCREEN_ZOOM is used to make text and arrow sizes show up
101 a certain way, which means we should probably create something else in those
102 objects to take its place--like some kind of scale factor. This would seem to
103 imply that certain point sizes actually *do* tie things like fonts to absolute
104 sizes on the screen, but not necessarily because you could have an inch scale
105 with text that is quite small relative to other objects on the screen, which
106 currently you have to zoom in to see (and which blows up the text). Point sizes
107 in an application like this are a bit meaningless; even though an inch is an
108 inch regardless of the zoom level a piece of text can be larger or smaller than
109 this. Maybe this is the case for having a base unit and basing point sizes off
112 Here's what's been figured out. Global::zoom is simply the ratio of pixels to
113 base units. What that means is that if you have a 12px grid with a 6" grid size
114 (& base unit of "inches"), Global::zoom becomes 12px / 6" = 2.0 px/in.
116 Dimensions now have a "size" parameter to set their absolute size in relation
117 to the base unit. ATM, the arrows are drawn in pixels, but also scaled by
118 Global::zoom *and* size. Same with the dimension text; it's drawn at 10pt and
119 scaled the same way as the arrowheads.
121 Need a way to scale line widths as well. :-/ Shouldn't be too difficult, just
122 need a thickness parameter similar to the "size" param for dimensions. (And now
127 void DrawingView::DrawBackground(Painter * painter)
129 Point ul = Painter::QtToCartesianCoords(Vector(0, 0));
130 Point br = Painter::QtToCartesianCoords(Vector(Global::screenSize.x, Global::screenSize.y));
132 painter->SetBrush(0xF0F0F0);
133 painter->SetPen(0xF0F0F0, 1, 1);
134 painter->DrawRect(QRectF(QPointF(ul.x, ul.y), QPointF(br.x, br.y)));
136 double spacing = Global::gridSpacing;
141 double leftx = floor(ul.x / spacing) * spacing;
142 double bottomy = floor(br.y / spacing) * spacing;
144 double w = (br.x - ul.x) + Global::gridSpacing + 1.0;
145 double h = (ul.y - br.y) + Global::gridSpacing + 1.0;
147 Vector start(leftx, bottomy), size(w, h);
149 if (Global::gridSpacing <= 0.015625)
150 DrawSubGrid(painter, 0xFFD2D2, 0.015625, start, size);
152 if (Global::gridSpacing <= 0.03125)
153 DrawSubGrid(painter, 0xFFD2D2, 0.03125, start, size);
155 if (Global::gridSpacing <= 0.0625)
156 DrawSubGrid(painter, 0xB8ECFF, 0.0625, start, size);
158 if (Global::gridSpacing <= 0.125)
159 DrawSubGrid(painter, 0xB8ECFF, 0.125, start, size);
161 if (Global::gridSpacing <= 0.25)
162 DrawSubGrid(painter, 0xE6E6FF, 0.25, start, size);
164 if (Global::gridSpacing <= 0.5)
165 DrawSubGrid(painter, 0xE6E6FF, 0.5, start, size);
167 painter->SetPen(QPen(QColor(0xE0, 0xE0, 0xFF), 2.0, Qt::SolidLine));
169 for(double i=0; i<=w; i+=spacing)
170 painter->DrawVLine(leftx + i);
172 for(double i=0; i<=h; i+=spacing)
173 painter->DrawHLine(bottomy + i);
176 void DrawingView::DrawSubGrid(Painter * painter, uint32_t color, double step, Vector start, Vector size)
178 painter->SetPen(color, 1, 1);
180 for(double i=-step; i<=size.x; i+=step*2.0)
181 painter->DrawVLine(start.x + i);
183 for(double i=-step; i<=size.y; i+=step*2.0)
184 painter->DrawHLine(start.y + i);
188 // Basically, we just make a single pass through the Container. If the layer #
189 // is less than the layer # being deleted, then do nothing. If the layer # is
190 // equal to the layer # being deleted, then delete the object. If the layer #
191 // is greater than the layer # being deleted, then set the layer # to its layer
194 void DrawingView::DeleteCurrentLayer(int layer)
196 VPVectorIter i = document.objects.begin();
198 while (i != document.objects.end())
200 Object * obj = (Object *)(*i);
202 if (obj->layer < layer)
204 else if (obj->layer == layer)
206 document.objects.erase(i);
216 // We've just done a destructive action, so update the screen!
220 void DrawingView::HandleLayerToggle(void)
222 // A layer's visibility was toggled, so update the screen...
227 // A layer was moved up or down in the layer list, so we have to swap the
228 // document's object's layer numbers in the layers that were swapped.
230 void DrawingView::HandleLayerSwap(int layer1, int layer2)
232 HandleLayerSwap(layer1, layer2, document.objects);
236 We can roll this into the main one above, by having the LayerWidget's emit() call sending NULL for the VPVector, which we can test for and set to document.objects to grab the top layer. Or, keep it a top level call and a recursive call. Which is worse? :-P
238 void DrawingView::HandleLayerSwap(int layer1, int layer2, VPVector & v)
240 for(VPVectorIter i=v.begin(); i!=v.end(); i++)
242 Object * obj = (Object *)(*i);
244 if (obj->layer == layer1)
246 else if (obj->layer == layer2)
249 if (obj->type == OTContainer)
250 HandleLayerSwap(layer1, layer2, ((Container *)obj)->objects);
254 void DrawingView::HandlePenStamp(QAction * action)
256 PenWidget * pw = (PenWidget *)action->parentWidget();
257 pw->dropperAction->setChecked(false);
258 Global::penDropper = false;
259 Global::penStamp = action->isChecked();
261 if (Global::penStamp)
262 setCursor(curMarker);
264 setCursor(Qt::ArrowCursor);
266 if (Global::penStamp == false)
267 ClearSelected(document.objects);
272 void DrawingView::HandlePenDropper(QAction * action)
274 PenWidget * pw = (PenWidget *)action->parentWidget();
275 pw->stampAction->setChecked(false);
276 Global::penStamp = false;
277 Global::penDropper = action->isChecked();
279 if (Global::penDropper)
280 setCursor(curDropper);
282 setCursor(Qt::ArrowCursor);
287 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
289 // This is undoing the transform, e.g. going from client coords to local
290 // coords. In essence, the height - y is height + (y * -1), the (y * -1)
291 // term doing the conversion of the y-axis from increasing bottom to top.
292 return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
295 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
297 // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
298 // No voodoo here, it's just grouped wrong to see it. It should be:
299 // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive [why? we use -offsetX after all]
300 return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
303 void DrawingView::focusOutEvent(QFocusEvent * /*event*/)
305 // Make sure all modkeys being held are marked as released when the app
306 // loses focus (N.B.: This only works because the app sets the focus policy
307 // of this object to something other than Qt::NoFocus)
308 shiftDown = ctrlDown = altDown = false;
310 setCursor(Qt::ArrowCursor);
313 void DrawingView::focusInEvent(QFocusEvent * /*event*/)
315 if (Global::penStamp)
316 setCursor(curMarker);
317 else if (Global::penDropper)
318 setCursor(curDropper);
321 void DrawingView::paintEvent(QPaintEvent * /*event*/)
323 QPainter qtPainter(this);
324 Painter painter(&qtPainter);
327 qtPainter.setRenderHint(QPainter::Antialiasing);
329 Global::viewportHeight = size().height();
331 DrawBackground(&painter);
333 // Draw coordinate axes
334 painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
335 painter.DrawLine(0, -16384, 0, 16384);
336 painter.DrawLine(-16384, 0, 16384, 0);
338 // Do object rendering...
339 for(int i=0; i<Global::numLayers; i++)
341 if (Global::layerHidden[i] == false)
342 RenderObjects(&painter, document.objects, i);
345 // Do tool rendering, if any...
348 if (Global::toolSuppressCrosshair == false)
350 painter.SetPen(QPen(QColor(200, 100, 0, 255), 1.0, Qt::DashLine));
351 painter.DrawCrosshair(oldPoint);
357 // Do selection rectangle rendering, if any
358 if (Global::selectionInProgress)
360 painter.SetPen(QPen(QColor(0xFF, 0x7F, 0x00, 0xFF)));
361 painter.SetBrush(QBrush(QColor(0xFF, 0x7F, 0x00, 0x64)));
362 painter.DrawRect(Global::selection);
365 if (hoveringIntersection)
366 painter.DrawHandle(intersectionPoint);
369 painter.DrawHandle(hoverPoint);
371 if (!informativeText.isEmpty())
372 painter.DrawInformativeText(informativeText);
376 // Renders objects in the passed in vector
379 N.B.: Since we have "hoverPointValid" drawing regular object handles above,
380 we can probably do away with a lot of them that are being done down below.
382 [Well, it seems to work OK *except* when you move one of the points, then you get to see nothing. Is it worth fixing there to get rid of problems here? Have to investigate...]
384 void DrawingView::RenderObjects(Painter * painter, VPVector & v, int layer, bool ignoreLayer/*= false*/)
386 for(VPVectorIter i=v.begin(); i!=v.end(); i++)
388 Object * obj = (Object *)(*i);
389 float scaledThickness = Global::scale * obj->thickness;
391 // If the object isn't on the current layer being drawn, skip it
392 if (!ignoreLayer && (obj->layer != layer))
395 if ((Global::tool == TTRotate) && ctrlDown && obj->selected)
397 painter->SetPen(0x00FF00, 2.0, LSSolid);
401 painter->SetPen(obj->color, Global::zoom * scaledThickness, obj->style);
402 painter->SetBrush(obj->color);
404 // penStamp supresses object highlighting, so that changes can be seen.
405 if (supressSelected || Global::penStamp)
409 painter->SetPen(Global::penColor, Global::zoom * Global::scale * Global::penWidth, Global::penStyle);
410 painter->SetBrush(Global::penColor);
413 else if (obj->selected || obj->hitObject)
414 painter->SetPen(0xFF0000, Global::zoom * scaledThickness, LSDash);
420 painter->DrawLine(obj->p[0], obj->p[1]);
422 if (obj->hitPoint[0])
423 painter->DrawHandle(obj->p[0]);
425 if (obj->hitPoint[1])
426 painter->DrawHandle(obj->p[1]);
429 painter->DrawSmallHandle(Geometry::Midpoint((Line *)obj));
434 painter->SetBrush(QBrush(Qt::NoBrush));
435 painter->DrawEllipse(obj->p[0], obj->radius[0], obj->radius[0]);
437 if (obj->hitPoint[0])
438 painter->DrawHandle(obj->p[0]);
443 painter->DrawArc(obj->p[0], obj->radius[0], obj->angle[0], obj->angle[1]);
445 if (obj->hitPoint[0])
446 painter->DrawHandle(obj->p[0]);
448 if (obj->hitPoint[1])
449 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]));
451 if (obj->hitPoint[2])
452 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0] + obj->angle[1]), sin(obj->angle[0] + obj->angle[1])) * obj->radius[0]));
458 Dimension * d = (Dimension *)obj;
460 Vector v(d->p[0], d->p[1]);
461 double angle = v.Angle();
462 Vector unit = v.Unit();
463 d->lp[0] = d->p[0], d->lp[1] = d->p[1];
465 double x1, y1, length;
467 if (d->subtype == DTLinearVert)
469 if ((angle < 0) || (angle > HALF_TAU))
471 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
472 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
473 ortho = Vector(1.0, 0);
474 angle = THREE_QTR_TAU;
478 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
479 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
480 ortho = Vector(-1.0, 0);
484 d->lp[0].x = d->lp[1].x = x1;
485 length = fabs(d->p[0].y - d->p[1].y);
487 else if (d->subtype == DTLinearHorz)
489 if ((angle < QTR_TAU) || (angle > THREE_QTR_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(0, 1.0);
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(0, -1.0);
504 d->lp[0].y = d->lp[1].y = y1;
505 length = fabs(d->p[0].x - d->p[1].x);
507 else if (d->subtype == DTLinear)
509 angle = Vector(d->lp[0], d->lp[1]).Angle();
510 ortho = Vector::Normal(d->lp[0], d->lp[1]);
511 length = v.Magnitude();
514 unit = Vector(d->lp[0], d->lp[1]).Unit();
516 Point p1 = d->lp[0] + (ortho * (d->offset + (10.0 * scaledThickness)));
517 Point p2 = d->lp[1] + (ortho * (d->offset + (10.0 * scaledThickness)));
518 Point p3 = d->lp[0] + (ortho * (d->offset + (16.0 * scaledThickness)));
519 Point p4 = d->lp[1] + (ortho * (d->offset + (16.0 * scaledThickness)));
520 Point p5 = d->p[0] + (ortho * 4.0 * scaledThickness);
521 Point p6 = d->p[1] + (ortho * 4.0 * scaledThickness);
524 The numbers hardcoded into here, what are they?
525 I believe they are pixels.
527 // Draw extension lines (if certain type)
528 painter->DrawLine(p3, p5);
529 painter->DrawLine(p4, p6);
531 // Calculate whether or not the arrowheads are too crowded to put
532 // inside the extension lines. 9.0 is the length of the arrowhead.
533 double t = Geometry::ParameterOfLineAndPoint(d->lp[0], d->lp[1], d->lp[1] - (unit * 9.0 * scaledThickness));
535 // On the screen, it's acting like this is actually 58%...
536 // This is correct, we want it to happen at > 50%
539 // Draw main dimension line + arrowheads
540 painter->DrawLine(p1, p2);
541 painter->DrawArrowhead(p1, p2, scaledThickness);
542 painter->DrawArrowhead(p2, p1, scaledThickness);
546 // Draw outside arrowheads
547 Point p7 = p1 - (unit * 9.0 * scaledThickness);
548 Point p8 = p2 + (unit * 9.0 * scaledThickness);
549 painter->DrawArrowhead(p1, p7, scaledThickness);
550 painter->DrawArrowhead(p2, p8, scaledThickness);
551 painter->DrawLine(p1, p1 - (unit * 14.0 * scaledThickness));
552 painter->DrawLine(p2, p2 + (unit * 14.0 * scaledThickness));
555 // Draw length of dimension line...
556 painter->SetFont(QFont("Arial", 8.0 * Global::zoom * scaledThickness));
557 Point ctr = p2 + (Vector(p2, p1) / 2.0);
562 dimText = QString("%1\"").arg(length);
565 double feet = (double)((int)length / 12);
566 double inches = length - (feet * 12.0);
569 dimText = QString("%1'").arg(feet);
571 dimText = QString("%1' %2\"").arg(feet).arg(inches);
575 Where is the text offset? It looks like it's drawing in the center, but obviously it isn't. It isn't here, it's in Painter::DrawAngledText().
577 painter->DrawAngledText(ctr, angle, dimText, scaledThickness);
581 Point hp1 = (p1 + p2) / 2.0;
582 Point hp2 = (p1 + hp1) / 2.0;
583 Point hp3 = (hp1 + p2) / 2.0;
587 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
588 painter->SetBrush(QBrush(QColor(Qt::magenta)));
589 painter->DrawArrowHandle(hp1, ortho.Angle() + HALF_TAU);
590 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
593 painter->DrawHandle(hp1);
594 painter->SetPen(QPen(Qt::blue, 1.0 * Global::zoom * scaledThickness, Qt::SolidLine));
598 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
599 painter->SetBrush(QBrush(QColor(Qt::magenta)));
600 painter->DrawArrowToLineHandle(hp2, (d->subtype == DTLinearVert ? v.Angle() - QTR_TAU : (v.Angle() < HALF_TAU ? HALF_TAU : 0)));
601 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
604 painter->DrawHandle(hp2);
605 painter->SetPen(QPen(Qt::blue, 1.0 * Global::zoom * scaledThickness, Qt::SolidLine));
609 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
610 painter->SetBrush(QBrush(QColor(Qt::magenta)));
611 painter->DrawArrowToLineHandle(hp3, (d->subtype == DTLinearHorz ? v.Angle() - QTR_TAU : (v.Angle() > HALF_TAU && v.Angle() < THREE_QTR_TAU ? THREE_QTR_TAU : QTR_TAU)));
612 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
615 painter->DrawHandle(hp3);
618 if (obj->hitPoint[0])
619 painter->DrawHandle(obj->p[0]);
621 if (obj->hitPoint[1])
622 painter->DrawHandle(obj->p[1]);
629 Text * t = (Text *)obj;
631 if (t->measured == false)
633 t->extents = painter->MeasureTextObject(t->s.c_str(), scaledThickness);
637 painter->DrawTextObject(t->p[0], t->s.c_str(), scaledThickness, t->angle[0]);
658 // Containers require recursive rendering...
659 Container * c = (Container *)obj;
660 //printf("About to render container: # objs=%i, layer=%i\n", (*c).objects.size(), layer);
661 RenderObjects(painter, (*c).objects, layer, ignoreLayer);
663 //printf("Container extents: <%lf, %lf>, <%lf, %lf>\nsize: %i\n", r.l, r.t, r.r, r.b, c->objects.size());
664 // Containers also have special indicators showing they are selected
665 if (c->selected || c->hitObject)
667 // Rect r = GetObjectExtents(obj);
668 // painter->DrawRectCorners(r);
669 painter->DrawRectCorners(Rect(c->p[0], c->p[1]));
680 supressSelected = false;
684 // This toggles the selection being hovered (typically, only 1 object). We
685 // toggle because the CTRL key might be held, in which case, we want to
686 // deselect a selected object.
688 void DrawingView::HandleSelectionClick(VPVector & v)
692 for(VPVectorIter i=v.begin(); i!=v.end(); i++)
694 Object * obj = (Object *)(*i);
697 obj->selected = !obj->selected;
703 for(VPVectorIter i=v.begin(); i!=v.end(); i++)
704 ((Object *)(*i))->selected = false;
706 // Check if the hover changed, and if so, reset the selection stack
707 if (oldHover.size() != v.size())
714 // Select next object in the stack under the cursor
717 if (currentSelect >= v.size())
721 dragged = (Object *)v[currentSelect];
722 dragged->selected = true;
725 VPVector DrawingView::GetSelection(void)
729 for(VPVectorIter i=document.objects.begin(); i!=document.objects.end(); i++)
731 if (((Object *)(*i))->selected)
739 // When testing for hovered intersections, we need to be able to exclude some
740 // objects which have funky characteristics or handles; so we allow for that
743 VPVector DrawingView::GetHovered(bool exclude/*= false*/)
747 for(VPVectorIter i=document.objects.begin(); i!=document.objects.end(); i++)
749 Object * obj = (Object *)(*i);
754 && ((obj->type == OTDimension)
755 || ((obj->type == OTCircle) && (obj->hitPoint[0] == true))
756 || ((obj->type == OTArc) && (obj->hitPoint[0] == true))
757 || (draggingObject && (obj == dragged))))
767 void DrawingView::MoveSelectedToLayer(int layer)
769 for(VPVectorIter i=document.objects.begin(); i!=document.objects.end(); i++)
771 Object * obj = (Object *)(*i);
773 if (obj->selected || obj->hovered)
778 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
780 Global::screenSize = Vector(size().width(), size().height());
783 void DrawingView::ToolHandler(int mode, Point p)
785 // Drop angle snap until it's needed
788 if (Global::tool == TTLine)
789 LineHandler(mode, p);
790 else if (Global::tool == TTCircle)
791 CircleHandler(mode, p);
792 else if (Global::tool == TTArc)
794 else if (Global::tool == TTRotate)
795 RotateHandler(mode, p);
796 else if (Global::tool == TTMirror)
797 MirrorHandler(mode, p);
798 else if (Global::tool == TTDimension)
799 DimensionHandler(mode, p);
800 else if (Global::tool == TTDelete)
801 DeleteHandler(mode, p);
802 else if (Global::tool == TTTriangulate)
803 TriangulateHandler(mode, p);
804 else if (Global::tool == TTTrim)
805 TrimHandler(mode, p);
806 else if (Global::tool == TTParallel)
807 ParallelHandler(mode, p);
810 void DrawingView::ToolDraw(Painter * painter)
812 if (Global::tool == TTLine)
814 if (Global::toolState == TSNone)
816 painter->DrawHandle(toolPoint[0]);
818 else if ((Global::toolState == TSPoint2) && shiftDown)
820 painter->DrawHandle(toolPoint[1]);
824 painter->DrawLine(toolPoint[0], toolPoint[1]);
825 painter->DrawHandle(toolPoint[1]);
827 Vector v(toolPoint[0], toolPoint[1]);
828 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
829 double absLength = v.Magnitude();
830 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
831 informativeText = text.arg(absLength).arg(absAngle);
834 else if (Global::tool == TTCircle)
836 if (Global::toolState == TSNone)
838 painter->DrawHandle(toolPoint[0]);
840 else if ((Global::toolState == TSPoint2) && shiftDown)
842 painter->DrawHandle(toolPoint[1]);
846 painter->DrawCross(toolPoint[0]);
847 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
848 painter->SetBrush(QBrush(Qt::NoBrush));
849 painter->DrawEllipse(toolPoint[0], length, length);
850 QString text = tr("Radius: %1 in.");
851 informativeText = text.arg(length);
854 else if (Global::tool == TTArc)
856 if (Global::toolState == TSNone)
858 painter->DrawHandle(toolPoint[0]);
860 else if (Global::toolState == TSPoint2)
862 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
863 painter->SetBrush(QBrush(Qt::NoBrush));
864 painter->DrawEllipse(toolPoint[0], length, length);
865 painter->DrawLine(toolPoint[0], toolPoint[1]);
866 painter->DrawHandle(toolPoint[1]);
867 QString text = tr("Radius: %1 in.");
868 informativeText = text.arg(length);
870 else if (Global::toolState == TSPoint3)
872 double angle = Vector::Angle(toolPoint[0], toolPoint[2]);
873 painter->DrawLine(toolPoint[0], toolPoint[2]);
874 painter->SetBrush(QBrush(Qt::NoBrush));
875 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
876 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
877 QString text = tr("Angle start: %1") + QChar(0x00B0);
878 informativeText = text.arg(RADIANS_TO_DEGREES * angle);
882 double angle = Vector::Angle(toolPoint[0], toolPoint[3]);
883 double span = angle - toolPoint[2].x;
888 painter->DrawLine(toolPoint[0], toolPoint[3]);
889 painter->SetBrush(QBrush(Qt::NoBrush));
890 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
891 painter->SetPen(0xFF00FF, 2.0, LSSolid);
892 painter->DrawArc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
893 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
894 QString text = tr("Arc span: %1") + QChar(0x00B0);
895 informativeText = text.arg(RADIANS_TO_DEGREES * span);
898 else if (Global::tool == TTRotate)
900 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
901 painter->DrawHandle(toolPoint[0]);
902 else if ((Global::toolState == TSPoint2) && shiftDown)
903 painter->DrawHandle(toolPoint[1]);
906 if (toolPoint[0] == toolPoint[1])
909 painter->DrawLine(toolPoint[0], toolPoint[1]);
911 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
912 QString text = QChar(0x2221) + QObject::tr(": %1");
913 informativeText = text.arg(absAngle);
916 informativeText += " (Copy)";
919 else if (Global::tool == TTMirror)
921 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
922 painter->DrawHandle(toolPoint[0]);
923 else if ((Global::toolState == TSPoint2) && shiftDown)
924 painter->DrawHandle(toolPoint[1]);
927 if (toolPoint[0] == toolPoint[1])
930 Point mirrorPoint = toolPoint[0] + Vector(toolPoint[1], toolPoint[0]);
931 painter->DrawLine(mirrorPoint, toolPoint[1]);
933 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
935 if (absAngle > 180.0)
938 QString text = QChar(0x2221) + QObject::tr(": %1");
939 informativeText = text.arg(absAngle);
942 informativeText += " (Copy)";
945 else if (Global::tool == TTDimension)
947 if (Global::toolState == TSNone)
949 painter->DrawHandle(toolPoint[0]);
951 else if ((Global::toolState == TSPoint2) && shiftDown)
953 painter->DrawHandle(toolPoint[1]);
957 painter->DrawLine(toolPoint[0], toolPoint[1]);
958 painter->DrawHandle(toolPoint[1]);
960 Vector v(toolPoint[0], toolPoint[1]);
961 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
962 double absLength = v.Magnitude();
963 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
964 informativeText = text.arg(absLength).arg(absAngle);
967 else if (Global::tool == TTTrim)
969 if (toolObj[0] != NULL)
971 // We're assuming ATM it's just a line...
972 painter->SetPen(0xAF0000, 3.0, LSSolid);
973 painter->DrawLine(toolPoint[0], toolPoint[1]);
974 // QString text = tr("Arc span: %1") + QChar(0x00B0);
975 // informativeText = text.arg(RADIANS_TO_DEGREES * span);
978 else if (Global::tool == TTParallel)
980 if (Global::toolState == TSPoint1)
982 painter->SetPen(0xFF00FF, 2.0, LSSolid);
983 painter->SetBrush(QBrush(Qt::NoBrush));
985 double length = Vector::Magnitude(toolObj[0]->p[0], toolPoint[0]);
986 bool inside = (length >= toolObj[0]->radius[0] ? false : true);
988 for(int i=1; i<=Global::parallelNum; i++)
990 if (toolObj[0]->type == OTLine)
992 painter->DrawLine(toolObj[0]->p[0] + (toolPoint[0] * Global::parallelDist * (double)i), toolObj[0]->p[1] + (toolPoint[0] * Global::parallelDist * (double)i));
994 else if ((toolObj[0]->type == OTCircle) || (toolObj[0]->type == OTArc))
996 double radius = toolObj[0]->radius[0] + ((double)i * Global::parallelDist * (inside ? -1.0 : 1.0));
1000 if (toolObj[0]->type == OTCircle)
1001 painter->DrawEllipse(toolObj[0]->p[0], radius, radius);
1003 painter->DrawArc(toolObj[0]->p[0], radius, toolObj[0]->angle[0], toolObj[0]->angle[1]);
1011 void DrawingView::LineHandler(int mode, Point p)
1016 /* toolObj[0] = NULL;
1018 // Check to see if we can do a circle tangent snap
1019 if (numHovered == 1)
1021 VPVector hover = GetHovered();
1022 Object * obj = (Object *)hover[0];
1024 // Save for later if the object clicked was a circle (need to check that it wasn't the center clicked on, because that will fuck up connecting centers of circles with lines... and now we do! :-)
1025 if ((obj->type == OTCircle) && (obj->hitPoint[0] == false))
1029 if (Global::toolState == TSNone)
1037 if (Global::toolState == TSNone)
1042 /* bool isCircle = false;
1044 if (numHovered == 1)
1046 VPVector hover = GetHovered();
1047 Object * obj = (Object *)hover[0];
1049 if ((obj->type == OTCircle) && (obj->hitPoint[0] == false))
1056 // Adjust initial point if it's on a circle (tangent point)
1057 if (toolObj[0] != NULL)
1061 Geometry::FindTangents(toolObj[0], toolObj[1]);
1063 if (Global::numIntersectPoints > 0)
1065 toolPoint[0] = Global::intersectPoint[0];
1066 toolPoint[1] = Global::intersectPoint[1];
1071 Geometry::FindTangents(toolObj[0], p);
1073 if (Global::numIntersectPoints > 0)
1074 toolPoint[0] = Global::intersectPoint[0];
1081 Geometry::FindTangents(toolObj[1], toolPoint[0]);
1083 if (Global::numIntersectPoints > 0)
1084 toolPoint[1] = Global::intersectPoint[0];
1092 if (Global::toolState == TSNone)
1094 Global::toolState = TSPoint2;
1095 // Prevent spurious line from drawing...
1096 toolPoint[1] = toolPoint[0];
1098 else if ((Global::toolState == TSPoint2) && shiftDown)
1100 // Key override is telling us to make a new line, not continue the
1102 toolPoint[0] = toolPoint[1];
1106 Line * l = new Line(toolPoint[0], toolPoint[1], Global::penWidth, Global::penColor, Global::penStyle);
1107 l->layer = Global::activeLayer;
1108 document.objects.push_back(l);
1109 toolPoint[0] = toolPoint[1];
1114 void DrawingView::CircleHandler(int mode, Point p)
1119 if (Global::toolState == TSNone)
1127 if (Global::toolState == TSNone)
1135 if (Global::toolState == TSNone)
1137 Global::toolState = TSPoint2;
1138 // Prevent spurious line from drawing...
1139 toolPoint[1] = toolPoint[0];
1141 else if ((Global::toolState == TSPoint2) && shiftDown)
1143 // Key override is telling us to make a new line, not continue the
1145 toolPoint[0] = toolPoint[1];
1149 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
1150 Circle * c = new Circle(toolPoint[0], length, Global::penWidth, Global::penColor, Global::penStyle);
1151 c->layer = Global::activeLayer;
1152 document.objects.push_back(c);
1153 toolPoint[0] = toolPoint[1];
1154 Global::toolState = TSNone;
1159 void DrawingView::ArcHandler(int mode, Point p)
1164 if (Global::toolState == TSNone)
1166 else if (Global::toolState == TSPoint2)
1168 else if (Global::toolState == TSPoint3)
1176 if (Global::toolState == TSNone)
1178 else if (Global::toolState == TSPoint2)
1180 else if (Global::toolState == TSPoint3)
1194 if (Global::toolState == TSNone)
1196 // Prevent spurious line from drawing...
1197 toolPoint[1] = toolPoint[0];
1198 Global::toolState = TSPoint2;
1200 else if (Global::toolState == TSPoint2)
1204 // Key override is telling us to start arc at new center, not
1205 // continue the current one.
1206 toolPoint[0] = toolPoint[1];
1210 // Set the radius in toolPoint[1].x
1211 toolPoint[1].x = Vector::Magnitude(toolPoint[0], toolPoint[1]);
1212 Global::toolState = TSPoint3;
1214 else if (Global::toolState == TSPoint3)
1216 // Set the angle in toolPoint[2].x
1217 toolPoint[2].x = Vector::Angle(toolPoint[0], toolPoint[2]);
1218 Global::toolState = TSPoint4;
1222 double endAngle = Vector::Angle(toolPoint[0], toolPoint[3]);
1223 double span = endAngle - toolPoint[2].x;
1228 Arc * arc = new Arc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span, Global::penWidth, Global::penColor, Global::penStyle);
1229 arc->layer = Global::activeLayer;
1230 document.objects.push_back(arc);
1231 Global::toolState = TSNone;
1236 void DrawingView::RotateHandler(int mode, Point p)
1241 if (Global::toolState == TSNone)
1244 // SavePointsFrom(select, toolScratch);
1245 CopyObjects(select, toolScratch2);
1246 Global::toolState = TSPoint1;
1248 else if (Global::toolState == TSPoint1)
1256 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1258 else if (Global::toolState == TSPoint2)
1266 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1267 VPVectorIter j = select.begin();
1268 // std::vector<Object>::iterator i = toolScratch.begin();
1269 VPVectorIter i = toolScratch2.begin();
1271 // for(; i!=toolScratch.end(); i++, j++)
1272 for(; i!=toolScratch2.end(); i++, j++)
1274 // Object objT = *i;
1275 // Point p1 = Geometry::RotatePointAroundPoint(objT.p[0], toolPoint[0], angle);
1276 // Point p2 = Geometry::RotatePointAroundPoint(objT.p[1], toolPoint[0], angle);
1277 Object * objT = (Object *)(*i);
1278 Object * objS = (Object *)(*j);
1280 Point p1 = Geometry::RotatePointAroundPoint(objT->p[0], toolPoint[0], angle);
1281 Point p2 = Geometry::RotatePointAroundPoint(objT->p[1], toolPoint[0], angle);
1286 // if (objT.type == OTArc || objT.type == OTText)
1287 if (objT->type == OTArc || objT->type == OTText)
1289 // objS->angle[0] = objT.angle[0] + angle;
1290 objS->angle[0] = objT->angle[0] + angle;
1292 if (objS->angle[0] > TAU)
1293 objS->angle[0] -= TAU;
1295 // else if (objT.type == OTContainer)
1296 else if (objT->type == OTContainer)
1298 // OK, this doesn't work because toolScratch only has points and nothing in the containers... [ACTUALLY... toolScratch is is a vector of type Object... which DOESN'T have an objects vector in it...]
1299 // Container * c = (Container *)&objT;
1300 Container * c = (Container *)objT;
1301 Container * c2 = (Container *)objS;
1302 VPVectorIter l = c->objects.begin();
1303 // TODO: Rotate items in the container
1304 // TODO: Make this recursive
1305 for(VPVectorIter k=c2->objects.begin(); k!=c2->objects.end(); k++, l++)
1307 Object * obj3 = (Object *)(*k);
1308 Object * obj4 = (Object *)(*l);
1310 p1 = Geometry::RotatePointAroundPoint(obj4->p[0], toolPoint[0], angle);
1311 p2 = Geometry::RotatePointAroundPoint(obj4->p[1], toolPoint[0], angle);
1315 // obj3->angle[0] = objT.angle[0] + angle;
1316 obj3->angle[0] = obj4->angle[0] + angle;
1318 if (obj3->angle[0] > TAU)
1319 obj3->angle[0] -= TAU;
1322 Rect r = GetObjectExtents(objS);
1323 c2->p[0] = r.TopLeft();
1324 c2->p[1] = r.BottomRight();
1332 if (Global::toolState == TSPoint1)
1334 Global::toolState = TSPoint2;
1335 // Prevent spurious line from drawing...
1336 toolPoint[1] = toolPoint[0];
1338 else if ((Global::toolState == TSPoint2) && shiftDown)
1340 // Key override is telling us to make a new line, not continue the
1342 toolPoint[0] = toolPoint[1];
1346 // Either we're finished with our rotate, or we're stamping a copy.
1349 // Stamp a copy of the selection at the current rotation & bail
1351 CopyObjects(select, temp);
1352 ClearSelected(temp);
1353 AddObjectsTo(document.objects, temp);
1354 // RestorePointsTo(select, toolScratch);
1355 RestorePointsTo(select, toolScratch2);
1360 Global::toolState = TSPoint1;
1361 // SavePointsFrom(select, toolScratch);
1362 DeleteContents(toolScratch2);
1363 CopyObjects(select, toolScratch2);
1369 // Reset the selection if shift held down...
1371 // RestorePointsTo(select, toolScratch);
1372 RestorePointsTo(select, toolScratch2);
1377 // Reset selection when key is let up
1379 RotateHandler(ToolMouseMove, toolPoint[1]);
1384 // RestorePointsTo(select, toolScratch);
1385 RestorePointsTo(select, toolScratch2);
1386 DeleteContents(toolScratch2);
1390 void DrawingView::MirrorHandler(int mode, Point p)
1395 if (Global::toolState == TSNone)
1398 SavePointsFrom(select, toolScratch);
1399 Global::toolState = TSPoint1;
1401 else if (Global::toolState == TSPoint1)
1409 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1411 else if (Global::toolState == TSPoint2)
1419 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1420 VPVectorIter j = select.begin();
1421 std::vector<Object>::iterator i = toolScratch.begin();
1423 for(; i!=toolScratch.end(); i++, j++)
1426 Point p1 = Geometry::MirrorPointAroundLine(obj.p[0], toolPoint[0], toolPoint[1]);
1427 Point p2 = Geometry::MirrorPointAroundLine(obj.p[1], toolPoint[0], toolPoint[1]);
1428 Object * obj2 = (Object *)(*j);
1433 N.B.: When mirroring an arc thru a horizontal axis, this causes the arc to have
1434 a negative start angle which makes it impossible to interact with.
1437 if (obj.type == OTArc)
1439 // This is 2*mirror angle - obj angle - obj span
1440 obj2->angle[0] = (2.0 * angle) - obj.angle[0] - obj.angle[1];
1442 if (obj2->angle[0] > TAU)
1443 obj2->angle[0] -= TAU;
1451 if (Global::toolState == TSPoint1)
1453 Global::toolState = TSPoint2;
1454 // Prevent spurious line from drawing...
1455 toolPoint[1] = toolPoint[0];
1457 else if ((Global::toolState == TSPoint2) && shiftDown)
1459 // Key override is telling us to make a new line, not continue the
1461 toolPoint[0] = toolPoint[1];
1465 // Either we're finished with our rotate, or we're stamping a copy.
1468 // Stamp a copy of the selection at the current rotation & bail
1470 CopyObjects(select, temp);
1471 ClearSelected(temp);
1472 AddObjectsTo(document.objects, temp);
1473 RestorePointsTo(select, toolScratch);
1478 Global::toolState = TSPoint1;
1479 SavePointsFrom(select, toolScratch);
1485 // Reset the selection if shift held down...
1487 RestorePointsTo(select, toolScratch);
1492 // Reset selection when key is let up
1494 MirrorHandler(ToolMouseMove, toolPoint[1]);
1499 RestorePointsTo(select, toolScratch);
1503 void DrawingView::DimensionHandler(int mode, Point p)
1508 if (Global::toolState == TSNone)
1516 if (Global::toolState == TSNone)
1524 if (Global::toolState == TSNone)
1526 Global::toolState = TSPoint2;
1527 // Prevent spurious line from drawing...
1528 toolPoint[1] = toolPoint[0];
1530 else if ((Global::toolState == TSPoint2) && shiftDown)
1532 // Key override is telling us to make a new line, not continue the
1534 toolPoint[0] = toolPoint[1];
1538 Dimension * d = new Dimension(toolPoint[0], toolPoint[1], DTLinear, 0, Global::penWidth);
1539 d->layer = Global::activeLayer;
1540 document.objects.push_back(d);
1541 Global::toolState = TSNone;
1546 void DrawingView::DeleteHandler(int mode, Point /*p*/)
1552 VPVector hovered = GetHovered();
1554 RemoveHoveredObjects(document.objects);
1555 DeleteContents(hovered);
1576 void DrawingView::TriangulateHandler(int mode, Point /*p*/)
1582 // Skip if nothing hovered...
1583 if (numHovered != 1)
1586 VPVector hover = GetHovered();
1587 Object * obj = (Object *)hover[0];
1589 // Skip if it's not a line...
1590 if (obj->type != OTLine)
1593 if (Global::toolState == TSNone)
1595 else if (Global::toolState == TSPoint2)
1604 if (Global::toolState == TSNone)
1606 else if (Global::toolState == TSPoint2)
1608 else if (Global::toolState == TSPoint3)
1622 if (Global::toolState == TSNone)
1624 Global::toolState = TSPoint2;
1626 else if (Global::toolState == TSPoint2)
1630 // Key override is telling us to start arc at new center, not
1631 // continue the current one.
1632 toolPoint[0] = toolPoint[1];
1636 Global::toolState = TSPoint3;
1640 double len2 = Vector::Magnitude(toolObj[1]->p[0], toolObj[1]->p[1]);
1641 double len3 = Vector::Magnitude(toolObj[2]->p[0], toolObj[2]->p[1]);
1643 Circle c1(toolObj[0]->p[0], len2);
1644 Circle c2(toolObj[0]->p[1], len3);
1646 Geometry::CheckCircleToCircleIntersection((Object *)&c1, (Object *)&c2);
1648 // Only move lines if the triangle formed by them is not degenerate
1649 if (Global::numIntersectPoints > 0)
1651 toolObj[1]->p[0] = toolObj[0]->p[0];
1652 toolObj[1]->p[1] = Global::intersectPoint[0];
1654 toolObj[2]->p[0] = Global::intersectPoint[0];
1655 toolObj[2]->p[1] = toolObj[0]->p[1];
1658 Global::toolState = TSNone;
1663 void DrawingView::TrimHandler(int mode, Point p)
1674 // Bail out if nothing hovered...
1675 if (numHovered != 1)
1681 VPVector hover = GetHovered();
1682 Object * obj = (Object *)hover[0];
1684 // Skip if it's not a line...
1685 if (obj->type != OTLine)
1692 double hoveredParam = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], p);
1693 double t = 0, u = 1.0;
1695 // Currently only deal with line against line trimming, can expand to
1696 // others as well (line/circle, circle/circle, line/arc, etc)
1698 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1700 obj = (Object *)(*i);
1702 if (obj == toolObj[0])
1704 else if (obj->type != OTLine)
1707 Geometry::CheckLineToLineIntersection(toolObj[0], obj);
1709 if (Global::numIntersectParams > 0)
1711 // Skip endpoint-endpoint intersections
1712 if ((Global::numIntersectParams == 2)
1713 && (Global::intersectParam[0] == 0
1714 || Global::intersectParam[0] == 1.0)
1715 && (Global::intersectParam[1] == 0
1716 || Global::intersectParam[1] == 1.0))
1719 // Mark the line segment somehow (the side the mouse is on) so that it can be drawn & trimmed when we hit ToolMouseDown.
1720 if ((Global::intersectParam[0] > t) && (Global::intersectParam[0] < hoveredParam))
1721 t = Global::intersectParam[0];
1723 if ((Global::intersectParam[0] < u) && (Global::intersectParam[0] > hoveredParam))
1724 u = Global::intersectParam[0];
1730 toolPoint[0] = Geometry::GetPointForParameter(toolObj[0], t);
1731 toolPoint[1] = Geometry::GetPointForParameter(toolObj[0], u);
1737 // Bail out if there's no object to trim
1738 if (toolObj[0] == NULL)
1741 Vector v(toolObj[0]->p[0], toolObj[0]->p[1]);
1743 // Check to see which case we have.
1744 if ((toolParam[0] == 0) && (toolParam[1] == 1.0))
1746 // There was no intersection, so delete the object
1747 toolObj[0]->selected = true;
1748 DeleteSelectedObjects(document.objects);
1750 else if (toolParam[0] == 0)
1752 // We delete the end near point #1
1753 toolObj[0]->p[0] = toolObj[0]->p[0] + (v * toolParam[1]);
1755 else if (toolParam[1] == 1.0)
1757 // We delete the end near point #2
1758 toolObj[0]->p[1] = toolObj[0]->p[0] + (v * toolParam[0]);
1762 // We delete the segment in between, and create a new line in the process
1763 Point p1 = toolObj[0]->p[0] + (v * toolParam[0]);
1764 Point p2 = toolObj[0]->p[0] + (v * toolParam[1]);
1765 Point p3 = toolObj[0]->p[1];
1766 toolObj[0]->p[1] = p1;
1767 Line * l = new Line(p2, p3, toolObj[0]->thickness, toolObj[0]->color, toolObj[0]->style);
1768 document.objects.push_back(l);
1769 // Global::toolState = TSNone;
1772 toolObj[0]->hitObject = toolObj[0]->hitPoint[0] = toolObj[0]->hitPoint[1] = false;
1788 void DrawingView::ParallelHandler(int mode, Point p)
1793 if (numHovered == 1)
1795 // New selection made...
1796 VPVector hover = GetHovered();
1797 toolObj[0] = (Object *)hover[0];
1798 Global::toolState = TSNone;
1800 else if ((numHovered == 0) && (toolObj[0] != NULL))
1802 double length = Vector::Magnitude(toolObj[0]->p[0], toolPoint[0]);
1803 bool inside = (length >= toolObj[0]->radius[0] ? false : true);
1805 // Stamp out new parallel object(s)...
1806 for(int i=1; i<=Global::parallelNum; i++)
1808 if (toolObj[0]->type == OTLine)
1810 Line * l = new Line(toolObj[0]->p[0] + (toolPoint[0] * Global::parallelDist * (double)i), toolObj[0]->p[1] + (toolPoint[0] * Global::parallelDist * (double)i), Global::penWidth, Global::penColor, Global::penStyle);
1811 // Should probably have a user selection for this whether it goes into the selected objects layer or the global layer...
1812 l->layer = toolObj[0]->layer;
1813 document.objects.push_back(l);
1815 else if (toolObj[0]->type == OTCircle)
1817 double radius = toolObj[0]->radius[0] + ((double)i * Global::parallelDist * (inside ? -1.0 : 1.0));
1821 Circle * c = new Circle(toolObj[0]->p[0], radius, Global::penWidth, Global::penColor, Global::penStyle);
1822 c->layer = toolObj[0]->layer;
1823 document.objects.push_back(c);
1826 else if (toolObj[0]->type == OTArc)
1828 double radius = toolObj[0]->radius[0] + ((double)i * Global::parallelDist * (inside ? -1.0 : 1.0));
1832 Arc * a = new Arc(toolObj[0]->p[0], radius, toolObj[0]->angle[0], toolObj[0]->angle[1], Global::penWidth, Global::penColor, Global::penStyle);
1833 a->layer = toolObj[0]->layer;
1834 document.objects.push_back(a);
1839 // Then reset the state
1840 toolObj[0]->selected = false;
1842 Global::toolState = TSNone;
1848 if ((numHovered == 0) && toolObj[0] != NULL)
1849 Global::toolState = TSPoint1;
1851 Global::toolState = TSNone;
1853 if (Global::toolState == TSPoint1)
1855 // Figure out which side of the object we're on, and draw the preview on that side...
1856 if (toolObj[0]->type == OTLine)
1858 Vector normal = Geometry::GetNormalOfPointAndLine(p, (Line *)toolObj[0]);
1859 toolPoint[0] = normal;
1861 else if ((toolObj[0]->type == OTCircle) || (toolObj[0]->type == OTArc))
1883 void DrawingView::mousePressEvent(QMouseEvent * event)
1885 if (event->button() == Qt::LeftButton)
1887 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1889 // Handle tool processing, if any
1892 if (hoveringIntersection)
1893 point = intersectionPoint;
1894 else if (hoverPointValid)
1896 else if (Global::snapToGrid)
1897 point = SnapPointToGrid(point);
1899 ToolHandler(ToolMouseDown, point);
1903 // Clear the selection only if CTRL isn't being held on click
1905 ClearSelected(document.objects);
1907 // If any objects are being hovered on click, deal with 'em
1910 VPVector hover2 = GetHovered();
1911 dragged = (Object *)hover2[0];
1913 // Alert the pen widget
1914 if (Global::penDropper)
1916 Global::penColor = dragged->color;
1917 Global::penWidth = dragged->thickness;
1918 Global::penStyle = dragged->style;
1919 emit ObjectSelected(dragged);
1922 else if (Global::penStamp)
1924 dragged->color = Global::penColor;
1925 dragged->thickness = Global::penWidth;
1926 dragged->style = Global::penStyle;
1929 // See if anything is using just a straight click on a custom
1930 // object handle (like Dimension objects)
1931 else if (HandleObjectClicked())
1937 draggingObject = true;
1938 HandleSelectionClick(hover2);
1939 update(); // needed??
1941 // Needed for grab & moving objects
1942 // We do it *after*... why? (doesn't seem to confer any advantage...)
1943 if (hoveringIntersection)
1944 oldPoint = intersectionPoint;
1945 else if (hoverPointValid)
1946 oldPoint = hoverPoint;
1947 else if (Global::snapToGrid)
1948 oldPoint = SnapPointToGrid(point);
1950 // Needed for fixed length handling
1951 if (Global::fixedLength)
1953 if (dragged->type == OTLine)
1954 dragged->length = ((Line *)dragged)->Length();
1957 // Needed for fixed angle handling
1958 if (Global::fixedAngle)
1960 if (dragged->type == OTLine)
1961 dragged->p[2] = ((Line *)dragged)->Unit();
1964 if (dragged->type == OTCircle)
1966 // Save for informative text, uh, er, informing
1967 dragged->length = dragged->radius[0];
1973 // Didn't hit any object and not using a tool, so do a selection
1975 Global::selectionInProgress = true;
1976 Global::selection.setTopLeft(QPointF(point.x, point.y));
1977 Global::selection.setBottomRight(QPointF(point.x, point.y));
1978 select = GetSelection();
1980 else if (event->button() == Qt::MiddleButton)
1983 oldPoint = Vector(event->x(), event->y());
1984 // Should also change the mouse pointer as well...
1985 setCursor(Qt::SizeAllCursor);
1989 void DrawingView::mouseMoveEvent(QMouseEvent * event)
1991 // It seems that wheelEvent() triggers this for some reason...
1992 if (scrollWheelSeen)
1994 scrollWheelSeen = false;
1998 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1999 Global::selection.setBottomRight(QPointF(point.x, point.y));
2000 // Only needs to be done here, as mouse down is always preceded by movement
2001 Global::snapPointIsValid = false;
2002 hoveringIntersection = false;
2003 oldScrollPoint = Vector(event->x(), event->y());
2006 if ((event->buttons() & Qt::MiddleButton) || scrollDrag)
2008 point = Vector(event->x(), event->y());
2009 // Since we're using Qt coords for scrolling, we have to adjust them
2010 // here to conform to Cartesian coords, since the origin is using
2012 Vector delta(oldPoint, point);
2013 delta /= Global::zoom;
2015 Global::origin -= delta;
2017 // UpdateGridBackground();
2023 // If we're doing a selection rect, see if any objects are engulfed by it
2024 // (implies left mouse button held down)
2025 if (Global::selectionInProgress)
2027 CheckObjectBounds();
2029 // Make sure previously selected objects stay selected (CTRL held)
2030 for(VPVectorIter i=select.begin(); i!=select.end(); i++)
2032 // Make sure *not* to select items on hidden layers
2033 if (Global::layerHidden[((Object *)(*i))->layer] == false)
2034 ((Object *)(*i))->selected = true;
2041 // Do object hit testing...
2042 bool needUpdate = HitTestObjects(point);
2043 VPVector hover2 = GetHovered(true); // Exclude dimension objects and circle centers (probably need to add arc centers too) from hover (also dragged objects...)
2048 printf("mouseMoveEvent:: numHovered=%li, hover2.size()=%li\n", numHovered, hover2.size());
2050 if (hover2.size() > 0)
2051 printf(" (hover2[0]=$%llX, type=%s)\n", hover2[0], objName[((Object *)hover2[0])->type]);
2056 // Check for multi-hover...
2057 if (hover2.size() > 1)
2059 //need to check for case where hover is over 2 circles and a 3rd's center (no longer a problem, I think)...
2060 Object * obj1 = (Object *)hover2[0], * obj2 = (Object *)hover2[1];
2062 Geometry::Intersects(obj1, obj2);
2063 int numIntersecting = Global::numIntersectParams;
2064 double t = Global::intersectParam[0];
2065 double u = Global::intersectParam[1];
2067 if (numIntersecting > 0)
2069 Vector v1 = Geometry::GetPointForParameter(obj1, t);
2070 Vector v2 = Geometry::GetPointForParameter(obj2, u);
2071 QString text = tr("Intersection t=%1 (%3, %4), u=%2 (%5, %6)");
2072 informativeText = text.arg(t).arg(u).arg(v1.x).arg(v1.y).arg(v2.x).arg(v2.y);
2074 hoveringIntersection = true;
2075 intersectionPoint = v1;
2078 numIntersecting = Global::numIntersectPoints;
2080 if (numIntersecting > 0)
2082 Vector v1 = Global::intersectPoint[0];
2084 if (numIntersecting == 2)
2086 Vector v2 = Global::intersectPoint[1];
2088 if (Vector::Magnitude(v2, point) < Vector::Magnitude(v1, point))
2092 QString text = tr("Intersection <%1, %2>");
2093 informativeText = text.arg(v1.x).arg(v1.y);
2094 hoveringIntersection = true;
2095 intersectionPoint = v1;
2098 else if (hover2.size() == 1)
2100 Object * obj = (Object *)hover2[0];
2102 if (obj->type == OTLine)
2105 Not sure that this is the best way to handle this, but it works(TM)...
2107 Point midpoint = Geometry::Midpoint((Line *)obj);
2108 Vector v1 = Vector::Magnitude(midpoint, point);
2110 if ((v1.Magnitude() * Global::zoom) < 8.0)
2112 QString text = tr("Midpoint <%1, %2>");
2113 informativeText = text.arg(midpoint.x).arg(midpoint.y);
2114 hoverPointValid = true;
2115 hoverPoint = midpoint;
2119 else if (obj->type == OTCircle)
2121 if ((draggingObject && (dragged->type == OTLine)) && (dragged->hitPoint[0] || dragged->hitPoint[1]))
2123 Point p = (dragged->hitPoint[0] ? dragged->p[1] : dragged->p[0]);
2124 Geometry::FindTangents(obj, p);
2126 if (Global::numIntersectPoints > 0)
2128 hoveringIntersection = true;
2129 intersectionPoint = Geometry::NearestTo(point, Global::intersectPoint[0], Global::intersectPoint[1]);
2132 else if ((Global::tool == TTLine) && (Global::toolState == TSPoint2))
2134 Geometry::FindTangents(obj, toolPoint[0]);
2136 if (Global::numIntersectPoints > 0)
2138 hoveringIntersection = true;
2139 intersectionPoint = Geometry::NearestTo(point, Global::intersectPoint[0], Global::intersectPoint[1]);
2145 // Handle object movement (left button down & over an object)
2146 if ((event->buttons() & Qt::LeftButton) && draggingObject && !Global::tool)
2148 if (hoveringIntersection)
2149 point = intersectionPoint;
2150 else if (hoverPointValid)
2152 else if (Global::snapToGrid)
2153 point = SnapPointToGrid(point);
2155 HandleObjectMovement(point);
2161 // Do tool handling, if any are active...
2164 if (hoveringIntersection)
2165 point = intersectionPoint;
2166 else if (hoverPointValid)
2168 else if (Global::snapToGrid)
2171 point = SnapPointToAngle(point);
2173 point = SnapPointToGrid(point);
2176 ToolHandler(ToolMouseMove, point);
2179 // This is used to draw the tool crosshair...
2182 if (needUpdate || Global::tool)
2186 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
2188 if (event->button() == Qt::LeftButton)
2190 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
2191 //could set it up to use the document's update function (assumes that all object
2192 //updates are being reported correctly:
2193 // if (document.NeedsUpdate())
2194 // Do an update if collided with at least *one* object in the document
2200 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
2201 ToolHandler(ToolMouseUp, point);
2205 Global::selectionInProgress = false;
2206 informativeText.clear();
2208 // Should we be doing this automagically? Hmm...
2209 // Clear our vectors
2213 // Scoop 'em up (do we need to??? Seems we do, because keyboard movement uses it. Also, tools use it too. But we can move it out of here [where to???])
2214 select = GetSelection();
2216 draggingObject = false;
2218 else if (event->button() == Qt::MiddleButton)
2222 if (Global::penStamp)
2223 setCursor(curMarker);
2224 else if (Global::penDropper)
2225 setCursor(curDropper);
2227 setCursor(Qt::ArrowCursor);
2229 // Need to convert this, since it's in Qt coordinates (for wheelEvent)
2230 oldPoint = Painter::QtToCartesianCoords(oldPoint);
2234 void DrawingView::wheelEvent(QWheelEvent * event)
2236 double zoomFactor = 1.20;
2237 scrollWheelSeen = true;
2239 if (event->angleDelta().y() < 0)
2241 if (Global::zoom > 400.0)
2244 Global::zoom *= zoomFactor;
2248 if (Global::zoom < 0.125)
2251 Global::zoom /= zoomFactor;
2254 Point np = Painter::QtToCartesianCoords(oldScrollPoint);
2255 Global::origin += (oldPoint - np);
2257 emit(NeedZoomUpdate());
2260 void DrawingView::keyPressEvent(QKeyEvent * event)
2262 bool oldShift = shiftDown;
2263 bool oldCtrl = ctrlDown;
2264 bool oldAlt = altDown;
2266 if (event->key() == Qt::Key_Shift)
2268 else if (event->key() == Qt::Key_Control)
2270 else if (event->key() == Qt::Key_Alt)
2273 // If there's a change in any of the modifier key states, pass it on to
2274 // the current tool's handler
2275 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
2278 ToolHandler(ToolKeyDown, Point(0, 0));
2283 if (oldAlt != altDown)
2286 setCursor(Qt::SizeAllCursor);
2287 oldPoint = oldScrollPoint;
2290 if (select.size() > 0)
2292 if (event->key() == Qt::Key_Up)
2294 TranslateObjects(select, Point(0, +1.0));
2297 else if (event->key() == Qt::Key_Down)
2299 TranslateObjects(select, Point(0, -1.0));
2302 else if (event->key() == Qt::Key_Right)
2304 TranslateObjects(select, Point(+1.0, 0));
2307 else if (event->key() == Qt::Key_Left)
2309 TranslateObjects(select, Point(-1.0, 0));
2315 void DrawingView::keyReleaseEvent(QKeyEvent * event)
2317 bool oldShift = shiftDown;
2318 bool oldCtrl = ctrlDown;
2319 bool oldAlt = altDown;
2321 if (event->key() == Qt::Key_Shift)
2323 else if (event->key() == Qt::Key_Control)
2325 else if (event->key() == Qt::Key_Alt)
2328 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
2331 ToolHandler(ToolKeyUp, Point(0, 0));
2336 if (oldAlt != altDown)
2340 if (Global::penStamp)
2341 setCursor(curMarker);
2342 else if (Global::penDropper)
2343 setCursor(curDropper);
2345 setCursor(Qt::ArrowCursor);
2350 // This looks strange, but it's really quite simple: We want a point that's
2351 // more than half-way to the next grid point to snap there while conversely we
2352 // want a point that's less than half-way to to the next grid point then snap
2353 // to the one before it. So we add half of the grid spacing to the point, then
2354 // divide by it so that we can remove the fractional part, then multiply it
2355 // back to get back to the correct answer.
2357 Point DrawingView::SnapPointToGrid(Point point)
2359 point += Global::gridSpacing / 2.0; // *This* adds to Z!!!
2360 point /= Global::gridSpacing;
2361 point.x = floor(point.x);//need to fix this for negative numbers...
2362 point.y = floor(point.y);
2363 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
2364 point *= Global::gridSpacing;
2368 Point DrawingView::SnapPointToAngle(Point point)
2370 // Snap to a single digit angle (using toolpoint #1 as the center)
2371 double angle = Vector::Angle(toolPoint[0], point);
2372 double length = Vector::Magnitude(toolPoint[0], point);
2374 // Convert from radians to degrees
2375 double degAngle = angle * RADIANS_TO_DEGREES;
2376 double snapAngle = (double)((int)(degAngle + 0.5));
2379 v.SetAngleAndLength(snapAngle * DEGREES_TO_RADIANS, length);
2380 point = toolPoint[0] + v;
2385 Rect DrawingView::GetObjectExtents(Object * obj)
2387 // Default to empty rect, if object checks below fail for some reason
2395 rect = Rect(obj->p[0], obj->p[1]);
2401 rect = Rect(obj->p[0], obj->p[0]);
2402 rect.Expand(obj->radius[0]);
2408 Arc * a = (Arc *)obj;
2410 double start = a->angle[0];
2411 double end = start + a->angle[1];
2412 rect = Rect(Point(cos(start), sin(start)), Point(cos(end), sin(end)));
2414 // If the end of the arc is before the beginning, add 360 degrees to it
2418 // Adjust the bounds depending on which axes are crossed
2419 if ((start < QTR_TAU) && (end > QTR_TAU))
2422 if ((start < HALF_TAU) && (end > HALF_TAU))
2425 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
2428 if ((start < TAU) && (end > TAU))
2431 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
2434 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
2437 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
2440 rect *= a->radius[0];
2441 rect.Translate(a->p[0]);
2447 Text * t = (Text *)obj;
2448 rect = Rect(t->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
2454 Container * c = (Container *)obj;
2455 VPVectorIter i = c->objects.begin();
2456 rect = GetObjectExtents((Object *)*i);
2459 for(; i!=c->objects.end(); i++)
2460 rect |= GetObjectExtents((Object *)*i);
2470 void DrawingView::CheckObjectBounds(void)
2474 for(i=document.objects.begin(); i!=document.objects.end(); i++)
2476 Object * obj = (Object *)(*i);
2477 obj->selected = false;
2482 case OTDimension: // N.B.: We don't check this properly...
2484 Line * l = (Line *)obj;
2486 if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
2494 Circle * c = (Circle *)obj;
2496 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]))
2504 Arc * a = (Arc *)obj;
2506 double start = a->angle[0];
2507 double end = start + a->angle[1];
2508 QPointF p1(cos(start), sin(start));
2509 QPointF p2(cos(end), sin(end));
2510 QRectF bounds(p1, p2);
2513 // Swap X/Y coordinates if they're backwards...
2514 if (bounds.left() > bounds.right())
2516 double temp = bounds.left();
2517 bounds.setLeft(bounds.right());
2518 bounds.setRight(temp);
2521 if (bounds.bottom() > bounds.top())
2523 double temp = bounds.bottom();
2524 bounds.setBottom(bounds.top());
2525 bounds.setTop(temp);
2528 // Doesn't work as advertised! For shame!
2529 bounds = bounds.normalized();
2532 // If the end of the arc is before the beginning, add 360 degrees
2537 // Adjust the bounds depending on which axes are crossed
2538 if ((start < QTR_TAU) && (end > QTR_TAU))
2541 if ((start < HALF_TAU) && (end > HALF_TAU))
2542 bounds.setLeft(-1.0);
2544 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
2545 bounds.setBottom(-1.0);
2547 if ((start < TAU) && (end > TAU))
2548 bounds.setRight(1.0);
2550 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
2553 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
2554 bounds.setLeft(-1.0);
2556 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
2557 bounds.setBottom(-1.0);
2559 bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
2560 bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
2561 bounds.translate(a->p[0].x, a->p[0].y);
2563 if (Global::selection.contains(bounds))
2571 Text * t = (Text *)obj;
2572 Rect r(obj->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
2574 if (Global::selection.contains(r.l, r.t) && Global::selection.contains(r.r, r.b))
2582 Container * c = (Container *)obj;
2584 if (Global::selection.contains(c->p[0].x, c->p[0].y) && Global::selection.contains(c->p[1].x, c->p[1].y))
2596 bool DrawingView::HitTestObjects(Point point)
2600 bool needUpdate = false;
2601 hoverPointValid = false;
2603 for(i=document.objects.begin(); i!=document.objects.end(); i++)
2605 Object * obj = (Object *)(*i);
2607 // If we're seeing the object we're dragging, skip it
2608 if (draggingObject && (obj == dragged))
2611 if (HitTest(obj, point) == true)
2617 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
2618 emit ObjectHovered(obj);
2625 bool DrawingView::HitTest(Object * obj, Point point)
2627 bool needUpdate = false;
2633 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
2634 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
2635 Vector lineSegment = obj->p[1] - obj->p[0];
2636 Vector v1 = point - obj->p[0];
2637 Vector v2 = point - obj->p[1];
2638 double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
2642 distance = v1.Magnitude();
2644 distance = v2.Magnitude();
2646 // distance = ?Det?(ls, v1) / |ls|
2647 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
2648 / lineSegment.Magnitude());
2650 if ((v1.Magnitude() * Global::zoom) < 8.0)
2652 obj->hitPoint[0] = true;
2653 hoverPoint = obj->p[0];
2654 hoverPointValid = true;
2656 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2658 obj->hitPoint[1] = true;
2659 hoverPoint = obj->p[1];
2660 hoverPointValid = true;
2662 else if ((distance * Global::zoom) < 5.0)
2663 obj->hitObject = true;
2665 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
2667 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
2675 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
2676 obj->hitPoint[0] = obj->hitObject = false;
2677 double length = Vector::Magnitude(obj->p[0], point);
2679 if ((length * Global::zoom) < 8.0)
2681 obj->hitPoint[0] = true;
2682 hoverPoint = obj->p[0];
2683 hoverPointValid = true;
2685 else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
2686 obj->hitObject = true;
2688 obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
2690 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
2698 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
2699 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
2700 double length = Vector::Magnitude(obj->p[0], point);
2701 double angle = Vector::Angle(obj->p[0], point);
2703 // Make sure we get the angle in the correct spot
2704 if (angle < obj->angle[0])
2707 // Get the span that we're pointing at...
2708 double span = angle - obj->angle[0];
2710 // N.B.: Still need to hit test the arc start & arc span handles...
2711 double spanAngle = obj->angle[0] + obj->angle[1];
2712 Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
2713 Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
2714 double length2 = Vector::Magnitude(point, handle1);
2715 double length3 = Vector::Magnitude(point, handle2);
2717 if ((length * Global::zoom) < 8.0)
2719 obj->hitPoint[0] = true;
2720 hoverPoint = obj->p[0];
2721 hoverPointValid = true;
2723 else if ((length2 * Global::zoom) < 8.0)
2725 obj->hitPoint[1] = true;
2726 hoverPoint = handle1;
2727 hoverPointValid = true;
2729 else if ((length3 * Global::zoom) < 8.0)
2731 obj->hitPoint[2] = true;
2732 hoverPoint = handle2;
2733 hoverPointValid = true;
2735 else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
2736 obj->hitObject = true;
2738 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
2740 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
2748 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHP3 = obj->hitPoint[3], oldHP4 = obj->hitPoint[4], oldHO = obj->hitObject;
2749 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitPoint[3] = obj->hitPoint[4] = obj->hitObject = false;
2751 Dimension * d = (Dimension *)obj;
2753 Vector orthogonal = Vector::Normal(d->lp[0], d->lp[1]);
2754 // Get our line parallel to our points
2755 float scaledThickness = Global::scale * obj->thickness;
2757 Point p1 = d->lp[0] + (orthogonal * (d->offset + (10.0 * scaledThickness)));
2758 Point p2 = d->lp[1] + (orthogonal * (d->offset + (10.0 * scaledThickness)));
2760 Point p1 = d->lp[0] + (orthogonal * (10.0 + d->offset) * scaledThickness);
2761 Point p2 = d->lp[1] + (orthogonal * (10.0 + d->offset) * scaledThickness);
2763 Point p3(p1, point);
2765 Vector v1(d->p[0], point);
2766 Vector v2(d->p[1], point);
2767 Vector lineSegment(p1, p2);
2768 double t = Geometry::ParameterOfLineAndPoint(p1, p2, point);
2770 Point midpoint = (p1 + p2) / 2.0;
2771 Point hFSPoint = Point(midpoint, point);
2772 Point hCS1Point = Point((p1 + midpoint) / 2.0, point);
2773 Point hCS2Point = Point((midpoint + p2) / 2.0, point);
2776 distance = v1.Magnitude();
2778 distance = v2.Magnitude();
2780 // distance = ?Det?(ls, v1) / |ls|
2781 distance = fabs((lineSegment.x * p3.y - p3.x * lineSegment.y)
2782 / lineSegment.Magnitude());
2784 if ((v1.Magnitude() * Global::zoom) < 8.0)
2785 obj->hitPoint[0] = true;
2786 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2787 obj->hitPoint[1] = true;
2788 else if ((distance * Global::zoom) < 5.0)
2789 obj->hitObject = true;
2791 if ((hFSPoint.Magnitude() * Global::zoom) < 8.0)
2792 obj->hitPoint[2] = true;
2793 else if ((hCS1Point.Magnitude() * Global::zoom) < 8.0)
2794 obj->hitPoint[3] = true;
2795 else if ((hCS2Point.Magnitude() * Global::zoom) < 8.0)
2796 obj->hitPoint[4] = true;
2798 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitPoint[3] || obj->hitPoint[4] || obj->hitObject ? true : false);
2800 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHP3 != obj->hitPoint[3]) || (oldHP4 != obj->hitPoint[4]) || (oldHO != obj->hitObject))
2808 Text * t = (Text *)obj;
2809 bool oldHO = obj->hitObject;
2810 obj->hitObject = false;
2812 Rect r(obj->p[0], Point(obj->p[0].x + t->extents.Width(), obj->p[0].y - t->extents.Height()));
2813 //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);
2815 if (r.Contains(point))
2816 obj->hitObject = true;
2818 obj->hovered = (obj->hitObject ? true : false);
2820 if (oldHO != obj->hitObject)
2828 // Containers must be recursively tested... Or do they???
2830 So the idea here is to flatten the structure, *then* test the objects within. Flattening includes the Containers as well; we can do this because it's pointers all the way down.
2832 // bool oldHitObj = c->hitObject, oldHovered = c->hovered;
2833 // Object * oldClicked = c->clicked;
2835 still need to compare old state to new state, and set things up based upon that...
2836 likely we can just rely on the object itself and steal its state like we have in the commented out portion below; can prolly rewrite the HitTest() portion to be one line: needUpdate = HitTest(cObj, point);
2837 Well, you could if there was only one object in the Container. But since there isn't, we have to keep the if HitTest() == true then needUpdate = true bit. Because otherwise, a false result anywhere will kill the needed update elsewhere.
2839 Container * c = (Container *)obj;
2840 c->hitObject = false;
2844 VPVector flat = Flatten(c);
2846 //printf("HitTest::OTContainer (size=%li)\n", flat.size());
2847 for(VPVectorIter i=flat.begin(); i!=flat.end(); i++)
2849 Object * cObj = (Object *)(*i);
2851 // Skip the flattened containers (if any)...
2852 if (cObj->type == OTContainer)
2855 // We do it this way instead of needUpdate = HitTest() because we
2856 // are checking more than one object, and that way of doing will
2857 // not return consistent results.
2858 if (HitTest(cObj, point) == true)
2860 //printf("HitTest::OTContainer, subobj ($%llX) hit!\n", cObj);
2862 // c->hitObject = true;
2863 // c->clicked = cObj;
2864 // c->hovered = true;
2867 // Same reasons for doing it this way here apply.
2868 if (cObj->hitObject == true)
2870 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2871 c->hitObject = true;
2875 if (cObj->hitPoint[0] == true)
2877 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2878 c->hitPoint[0] = true;
2882 if (cObj->hitPoint[1] == true)
2884 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2885 c->hitPoint[1] = true;
2889 if (cObj->hitPoint[2] == true)
2891 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2892 c->hitPoint[2] = true;
2896 if (cObj->hovered == true)
2897 c->hovered = true;//*/
2910 bool DrawingView::HandleObjectClicked(void)
2912 if (dragged->type == OTDimension)
2914 Dimension * d = (Dimension *)dragged;
2918 // Hit the "flip sides" switch, so flip 'em
2919 Point temp = d->p[0];
2924 else if (d->hitPoint[3])
2926 // There are three cases here: aligned, horizontal, & vertical.
2927 // Aligned and horizontal do the same thing, vertical goes back to
2929 if (d->subtype == DTLinearVert)
2930 d->subtype = DTLinear;
2932 d->subtype = DTLinearVert;
2936 else if (d->hitPoint[4])
2938 // There are three cases here: aligned, horizontal, & vertical.
2939 // Aligned and vertical do the same thing, horizontal goes back to
2941 if (d->subtype == DTLinearHorz)
2942 d->subtype = DTLinear;
2944 d->subtype = DTLinearHorz;
2953 void DrawingView::HandleObjectMovement(Point point)
2955 Point delta = point - oldPoint;
2956 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
2957 // Object * obj = (Object *)hover[0];
2958 Object * obj = dragged;
2959 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
2960 //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"));
2965 if (obj->hitPoint[0])
2968 N.B.: Mixing fixed length with fixed angle (and in this order) is probably *not* going to work out in any meaningful way, and we should probably make the GUI force these to be mutually exclusive. Besides, this combined effect already works by dragging the line segment by clicking on it. :-P
2970 if (Global::fixedLength)
2972 Vector unit = Vector::Unit(obj->p[1], point);
2973 point = obj->p[1] + (unit * obj->length);
2976 if (Global::fixedAngle)
2978 // Calculate the component of the current vector along the
2979 // fixed angle: A_compB = (A • Bu) * Bu (p[2] has the unit
2981 double magnitudeAlongB = Vector::Dot(Vector(point - obj->p[1]), obj->p[2]);
2982 point = obj->p[1] + (obj->p[2] * magnitudeAlongB);
2987 else if (obj->hitPoint[1])
2989 if (Global::fixedLength)
2991 Vector unit = Vector::Unit(obj->p[0], point);
2992 point = obj->p[0] + (unit * obj->length);
2995 if (Global::fixedAngle)
2997 double magnitudeAlongB = Vector::Dot(Vector(point - obj->p[0]), obj->p[2]);
2998 point = obj->p[0] + (obj->p[2] * magnitudeAlongB);
3003 else if (obj->hitObject)
3012 if (obj->hitPoint[0])
3014 else if (obj->hitObject)
3016 double oldRadius = obj->length;
3017 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
3019 QString text = QObject::tr("Radius: %1\nScale: %2%");
3020 informativeText = text.arg(obj->radius[0], 0, 'd', 4).arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0);
3026 if (obj->hitPoint[0])
3028 else if (obj->hitPoint[1])
3030 // Change the Arc's span (handle #1)
3033 double angle = Vector::Angle(obj->p[0], point);
3034 double delta = angle - obj->angle[0];
3039 obj->angle[1] -= delta;
3040 obj->angle[0] = angle;
3042 if (obj->angle[1] < 0)
3043 obj->angle[1] += TAU;
3045 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
3046 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);
3050 double angle = Vector::Angle(obj->p[0], point);
3051 obj->angle[0] = angle;
3052 QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
3053 informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 4);
3055 else if (obj->hitPoint[2])
3057 // Change the Arc's span (handle #2)
3060 double angle = Vector::Angle(obj->p[0], point);
3061 obj->angle[1] = angle - obj->angle[0];
3063 if (obj->angle[1] < 0)
3064 obj->angle[1] += TAU;
3066 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
3067 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);
3071 double angle = Vector::Angle(obj->p[0], point);
3072 obj->angle[0] = angle - obj->angle[1];
3074 if (obj->angle[0] < 0)
3075 obj->angle[0] += TAU;
3077 QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
3078 informativeText = text.arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 4);
3080 else if (obj->hitObject)
3087 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
3088 QString text = QObject::tr("Radius: %1");
3089 informativeText = text.arg(obj->radius[0], 0, 'd', 4);
3095 if (obj->hitPoint[0])
3097 else if (obj->hitPoint[1])
3099 else if (obj->hitObject)
3101 // Move measurement lines in/out
3104 Dimension * d = (Dimension *)obj;
3105 double dist = Geometry::DistanceToLineFromPoint(d->lp[0], d->lp[1], point);
3106 float scaledThickness = Global::scale * obj->thickness;
3107 // Looks like offset is 0 to +MAX, but line is at 10.0. So
3108 // anything less than 10.0 should set the offset to 0.
3111 if (dist > (10.0 * scaledThickness))
3112 d->offset = dist - (10.0 * scaledThickness);
3130 // This is shitty, but works for now until I can code up something
3133 The idea is to make it so whichever point on the object in question is being dragged becomes the snap point for the container; shouldn't be too difficult to figure out how to do this.
3135 // TranslateObject(obj, delta);
3136 TranslateContainer((Container *)obj, point, delta);
3144 void DrawingView::AddDimensionTo(void * o)
3146 Object * obj = (Object *)o;
3151 document.Add(new Dimension(obj->p[0], obj->p[1]));