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)
1955 dragged->length = Vector::Magnitude(dragged->p[0], dragged->p[1]);
1959 if (dragged->type == OTCircle)
1961 // Save for informative text, uh, er, informing
1962 dragged->length = dragged->radius[0];
1968 // Didn't hit any object and not using a tool, so do a selection
1970 Global::selectionInProgress = true;
1971 Global::selection.setTopLeft(QPointF(point.x, point.y));
1972 Global::selection.setBottomRight(QPointF(point.x, point.y));
1973 select = GetSelection();
1975 else if (event->button() == Qt::MiddleButton)
1978 oldPoint = Vector(event->x(), event->y());
1979 // Should also change the mouse pointer as well...
1980 setCursor(Qt::SizeAllCursor);
1984 void DrawingView::mouseMoveEvent(QMouseEvent * event)
1986 // It seems that wheelEvent() triggers this for some reason...
1987 if (scrollWheelSeen)
1989 scrollWheelSeen = false;
1993 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1994 Global::selection.setBottomRight(QPointF(point.x, point.y));
1995 // Only needs to be done here, as mouse down is always preceded by movement
1996 Global::snapPointIsValid = false;
1997 hoveringIntersection = false;
1998 oldScrollPoint = Vector(event->x(), event->y());
2001 if ((event->buttons() & Qt::MiddleButton) || scrollDrag)
2003 point = Vector(event->x(), event->y());
2004 // Since we're using Qt coords for scrolling, we have to adjust them
2005 // here to conform to Cartesian coords, since the origin is using
2007 Vector delta(oldPoint, point);
2008 delta /= Global::zoom;
2010 Global::origin -= delta;
2012 // UpdateGridBackground();
2018 // If we're doing a selection rect, see if any objects are engulfed by it
2019 // (implies left mouse button held down)
2020 if (Global::selectionInProgress)
2022 CheckObjectBounds();
2024 // Make sure previously selected objects stay selected (CTRL held)
2025 for(VPVectorIter i=select.begin(); i!=select.end(); i++)
2027 // Make sure *not* to select items on hidden layers
2028 if (Global::layerHidden[((Object *)(*i))->layer] == false)
2029 ((Object *)(*i))->selected = true;
2036 // Do object hit testing...
2037 bool needUpdate = HitTestObjects(point);
2038 VPVector hover2 = GetHovered(true); // Exclude dimension objects and circle centers (probably need to add arc centers too) from hover (also dragged objects...)
2043 printf("mouseMoveEvent:: numHovered=%li, hover2.size()=%li\n", numHovered, hover2.size());
2045 if (hover2.size() > 0)
2046 printf(" (hover2[0]=$%llX, type=%s)\n", hover2[0], objName[((Object *)hover2[0])->type]);
2051 // Check for multi-hover...
2052 if (hover2.size() > 1)
2054 //need to check for case where hover is over 2 circles and a 3rd's center (no longer a problem, I think)...
2055 Object * obj1 = (Object *)hover2[0], * obj2 = (Object *)hover2[1];
2057 Geometry::Intersects(obj1, obj2);
2058 int numIntersecting = Global::numIntersectParams;
2059 double t = Global::intersectParam[0];
2060 double u = Global::intersectParam[1];
2062 if (numIntersecting > 0)
2064 Vector v1 = Geometry::GetPointForParameter(obj1, t);
2065 Vector v2 = Geometry::GetPointForParameter(obj2, u);
2066 QString text = tr("Intersection t=%1 (%3, %4), u=%2 (%5, %6)");
2067 informativeText = text.arg(t).arg(u).arg(v1.x).arg(v1.y).arg(v2.x).arg(v2.y);
2069 hoveringIntersection = true;
2070 intersectionPoint = v1;
2073 numIntersecting = Global::numIntersectPoints;
2075 if (numIntersecting > 0)
2077 Vector v1 = Global::intersectPoint[0];
2079 if (numIntersecting == 2)
2081 Vector v2 = Global::intersectPoint[1];
2083 if (Vector::Magnitude(v2, point) < Vector::Magnitude(v1, point))
2087 QString text = tr("Intersection <%1, %2>");
2088 informativeText = text.arg(v1.x).arg(v1.y);
2089 hoveringIntersection = true;
2090 intersectionPoint = v1;
2093 else if (hover2.size() == 1)
2095 Object * obj = (Object *)hover2[0];
2097 if (obj->type == OTLine)
2100 Not sure that this is the best way to handle this, but it works(TM)...
2102 Point midpoint = Geometry::Midpoint((Line *)obj);
2103 Vector v1 = Vector::Magnitude(midpoint, point);
2105 if ((v1.Magnitude() * Global::zoom) < 8.0)
2107 QString text = tr("Midpoint <%1, %2>");
2108 informativeText = text.arg(midpoint.x).arg(midpoint.y);
2109 hoverPointValid = true;
2110 hoverPoint = midpoint;
2114 else if (obj->type == OTCircle)
2116 if ((draggingObject && (dragged->type == OTLine)) && (dragged->hitPoint[0] || dragged->hitPoint[1]))
2118 Point p = (dragged->hitPoint[0] ? dragged->p[1] : dragged->p[0]);
2119 Geometry::FindTangents(obj, p);
2121 if (Global::numIntersectPoints > 0)
2123 hoveringIntersection = true;
2124 intersectionPoint = Geometry::NearestTo(point, Global::intersectPoint[0], Global::intersectPoint[1]);
2127 else if ((Global::tool == TTLine) && (Global::toolState == TSPoint2))
2129 Geometry::FindTangents(obj, toolPoint[0]);
2131 if (Global::numIntersectPoints > 0)
2133 hoveringIntersection = true;
2134 intersectionPoint = Geometry::NearestTo(point, Global::intersectPoint[0], Global::intersectPoint[1]);
2140 // Handle object movement (left button down & over an object)
2141 if ((event->buttons() & Qt::LeftButton) && draggingObject && !Global::tool)
2143 if (hoveringIntersection)
2144 point = intersectionPoint;
2145 else if (hoverPointValid)
2147 else if (Global::snapToGrid)
2148 point = SnapPointToGrid(point);
2150 HandleObjectMovement(point);
2156 // Do tool handling, if any are active...
2159 if (hoveringIntersection)
2160 point = intersectionPoint;
2161 else if (hoverPointValid)
2163 else if (Global::snapToGrid)
2166 point = SnapPointToAngle(point);
2168 point = SnapPointToGrid(point);
2171 ToolHandler(ToolMouseMove, point);
2174 // This is used to draw the tool crosshair...
2177 if (needUpdate || Global::tool)
2181 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
2183 if (event->button() == Qt::LeftButton)
2185 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
2186 //could set it up to use the document's update function (assumes that all object
2187 //updates are being reported correctly:
2188 // if (document.NeedsUpdate())
2189 // Do an update if collided with at least *one* object in the document
2195 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
2196 ToolHandler(ToolMouseUp, point);
2200 Global::selectionInProgress = false;
2201 informativeText.clear();
2203 // Should we be doing this automagically? Hmm...
2204 // Clear our vectors
2208 // 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???])
2209 select = GetSelection();
2211 draggingObject = false;
2213 else if (event->button() == Qt::MiddleButton)
2217 if (Global::penStamp)
2218 setCursor(curMarker);
2219 else if (Global::penDropper)
2220 setCursor(curDropper);
2222 setCursor(Qt::ArrowCursor);
2224 // Need to convert this, since it's in Qt coordinates (for wheelEvent)
2225 oldPoint = Painter::QtToCartesianCoords(oldPoint);
2229 void DrawingView::wheelEvent(QWheelEvent * event)
2231 double zoomFactor = 1.20;
2232 scrollWheelSeen = true;
2234 if (event->angleDelta().y() < 0)
2236 if (Global::zoom > 400.0)
2239 Global::zoom *= zoomFactor;
2243 if (Global::zoom < 0.125)
2246 Global::zoom /= zoomFactor;
2249 Point np = Painter::QtToCartesianCoords(oldScrollPoint);
2250 Global::origin += (oldPoint - np);
2252 emit(NeedZoomUpdate());
2255 void DrawingView::keyPressEvent(QKeyEvent * event)
2257 bool oldShift = shiftDown;
2258 bool oldCtrl = ctrlDown;
2259 bool oldAlt = altDown;
2261 if (event->key() == Qt::Key_Shift)
2263 else if (event->key() == Qt::Key_Control)
2265 else if (event->key() == Qt::Key_Alt)
2268 // If there's a change in any of the modifier key states, pass it on to
2269 // the current tool's handler
2270 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
2273 ToolHandler(ToolKeyDown, Point(0, 0));
2278 if (oldAlt != altDown)
2281 setCursor(Qt::SizeAllCursor);
2282 oldPoint = oldScrollPoint;
2285 if (select.size() > 0)
2287 if (event->key() == Qt::Key_Up)
2289 TranslateObjects(select, Point(0, +1.0));
2292 else if (event->key() == Qt::Key_Down)
2294 TranslateObjects(select, Point(0, -1.0));
2297 else if (event->key() == Qt::Key_Right)
2299 TranslateObjects(select, Point(+1.0, 0));
2302 else if (event->key() == Qt::Key_Left)
2304 TranslateObjects(select, Point(-1.0, 0));
2310 void DrawingView::keyReleaseEvent(QKeyEvent * event)
2312 bool oldShift = shiftDown;
2313 bool oldCtrl = ctrlDown;
2314 bool oldAlt = altDown;
2316 if (event->key() == Qt::Key_Shift)
2318 else if (event->key() == Qt::Key_Control)
2320 else if (event->key() == Qt::Key_Alt)
2323 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
2326 ToolHandler(ToolKeyUp, Point(0, 0));
2331 if (oldAlt != altDown)
2335 if (Global::penStamp)
2336 setCursor(curMarker);
2337 else if (Global::penDropper)
2338 setCursor(curDropper);
2340 setCursor(Qt::ArrowCursor);
2345 // This looks strange, but it's really quite simple: We want a point that's
2346 // more than half-way to the next grid point to snap there while conversely we
2347 // want a point that's less than half-way to to the next grid point then snap
2348 // to the one before it. So we add half of the grid spacing to the point, then
2349 // divide by it so that we can remove the fractional part, then multiply it
2350 // back to get back to the correct answer.
2352 Point DrawingView::SnapPointToGrid(Point point)
2354 point += Global::gridSpacing / 2.0; // *This* adds to Z!!!
2355 point /= Global::gridSpacing;
2356 point.x = floor(point.x);//need to fix this for negative numbers...
2357 point.y = floor(point.y);
2358 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
2359 point *= Global::gridSpacing;
2363 Point DrawingView::SnapPointToAngle(Point point)
2365 // Snap to a single digit angle (using toolpoint #1 as the center)
2366 double angle = Vector::Angle(toolPoint[0], point);
2367 double length = Vector::Magnitude(toolPoint[0], point);
2369 // Convert from radians to degrees
2370 double degAngle = angle * RADIANS_TO_DEGREES;
2371 double snapAngle = (double)((int)(degAngle + 0.5));
2374 v.SetAngleAndLength(snapAngle * DEGREES_TO_RADIANS, length);
2375 point = toolPoint[0] + v;
2380 Rect DrawingView::GetObjectExtents(Object * obj)
2382 // Default to empty rect, if object checks below fail for some reason
2390 rect = Rect(obj->p[0], obj->p[1]);
2396 rect = Rect(obj->p[0], obj->p[0]);
2397 rect.Expand(obj->radius[0]);
2403 Arc * a = (Arc *)obj;
2405 double start = a->angle[0];
2406 double end = start + a->angle[1];
2407 rect = Rect(Point(cos(start), sin(start)), Point(cos(end), sin(end)));
2409 // If the end of the arc is before the beginning, add 360 degrees to it
2413 // Adjust the bounds depending on which axes are crossed
2414 if ((start < QTR_TAU) && (end > QTR_TAU))
2417 if ((start < HALF_TAU) && (end > HALF_TAU))
2420 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
2423 if ((start < TAU) && (end > TAU))
2426 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
2429 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
2432 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
2435 rect *= a->radius[0];
2436 rect.Translate(a->p[0]);
2442 Text * t = (Text *)obj;
2443 rect = Rect(t->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
2449 Container * c = (Container *)obj;
2450 VPVectorIter i = c->objects.begin();
2451 rect = GetObjectExtents((Object *)*i);
2454 for(; i!=c->objects.end(); i++)
2455 rect |= GetObjectExtents((Object *)*i);
2465 void DrawingView::CheckObjectBounds(void)
2469 for(i=document.objects.begin(); i!=document.objects.end(); i++)
2471 Object * obj = (Object *)(*i);
2472 obj->selected = false;
2477 case OTDimension: // N.B.: We don't check this properly...
2479 Line * l = (Line *)obj;
2481 if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
2489 Circle * c = (Circle *)obj;
2491 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]))
2499 Arc * a = (Arc *)obj;
2501 double start = a->angle[0];
2502 double end = start + a->angle[1];
2503 QPointF p1(cos(start), sin(start));
2504 QPointF p2(cos(end), sin(end));
2505 QRectF bounds(p1, p2);
2508 // Swap X/Y coordinates if they're backwards...
2509 if (bounds.left() > bounds.right())
2511 double temp = bounds.left();
2512 bounds.setLeft(bounds.right());
2513 bounds.setRight(temp);
2516 if (bounds.bottom() > bounds.top())
2518 double temp = bounds.bottom();
2519 bounds.setBottom(bounds.top());
2520 bounds.setTop(temp);
2523 // Doesn't work as advertised! For shame!
2524 bounds = bounds.normalized();
2527 // If the end of the arc is before the beginning, add 360 degrees
2532 // Adjust the bounds depending on which axes are crossed
2533 if ((start < QTR_TAU) && (end > QTR_TAU))
2536 if ((start < HALF_TAU) && (end > HALF_TAU))
2537 bounds.setLeft(-1.0);
2539 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
2540 bounds.setBottom(-1.0);
2542 if ((start < TAU) && (end > TAU))
2543 bounds.setRight(1.0);
2545 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
2548 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
2549 bounds.setLeft(-1.0);
2551 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
2552 bounds.setBottom(-1.0);
2554 bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
2555 bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
2556 bounds.translate(a->p[0].x, a->p[0].y);
2558 if (Global::selection.contains(bounds))
2566 Text * t = (Text *)obj;
2567 Rect r(obj->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
2569 if (Global::selection.contains(r.l, r.t) && Global::selection.contains(r.r, r.b))
2577 Container * c = (Container *)obj;
2579 if (Global::selection.contains(c->p[0].x, c->p[0].y) && Global::selection.contains(c->p[1].x, c->p[1].y))
2591 bool DrawingView::HitTestObjects(Point point)
2595 bool needUpdate = false;
2596 hoverPointValid = false;
2598 for(i=document.objects.begin(); i!=document.objects.end(); i++)
2600 Object * obj = (Object *)(*i);
2602 // If we're seeing the object we're dragging, skip it
2603 if (draggingObject && (obj == dragged))
2606 if (HitTest(obj, point) == true)
2612 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
2613 emit ObjectHovered(obj);
2620 bool DrawingView::HitTest(Object * obj, Point point)
2622 bool needUpdate = false;
2628 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
2629 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
2630 Vector lineSegment = obj->p[1] - obj->p[0];
2631 Vector v1 = point - obj->p[0];
2632 Vector v2 = point - obj->p[1];
2633 double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
2637 distance = v1.Magnitude();
2639 distance = v2.Magnitude();
2641 // distance = ?Det?(ls, v1) / |ls|
2642 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
2643 / lineSegment.Magnitude());
2645 if ((v1.Magnitude() * Global::zoom) < 8.0)
2647 obj->hitPoint[0] = true;
2648 hoverPoint = obj->p[0];
2649 hoverPointValid = true;
2651 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2653 obj->hitPoint[1] = true;
2654 hoverPoint = obj->p[1];
2655 hoverPointValid = true;
2657 else if ((distance * Global::zoom) < 5.0)
2658 obj->hitObject = true;
2660 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
2662 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
2670 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
2671 obj->hitPoint[0] = obj->hitObject = false;
2672 double length = Vector::Magnitude(obj->p[0], point);
2674 if ((length * Global::zoom) < 8.0)
2676 obj->hitPoint[0] = true;
2677 hoverPoint = obj->p[0];
2678 hoverPointValid = true;
2680 else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
2681 obj->hitObject = true;
2683 obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
2685 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
2693 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
2694 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
2695 double length = Vector::Magnitude(obj->p[0], point);
2696 double angle = Vector::Angle(obj->p[0], point);
2698 // Make sure we get the angle in the correct spot
2699 if (angle < obj->angle[0])
2702 // Get the span that we're pointing at...
2703 double span = angle - obj->angle[0];
2705 // N.B.: Still need to hit test the arc start & arc span handles...
2706 double spanAngle = obj->angle[0] + obj->angle[1];
2707 Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
2708 Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
2709 double length2 = Vector::Magnitude(point, handle1);
2710 double length3 = Vector::Magnitude(point, handle2);
2712 if ((length * Global::zoom) < 8.0)
2714 obj->hitPoint[0] = true;
2715 hoverPoint = obj->p[0];
2716 hoverPointValid = true;
2718 else if ((length2 * Global::zoom) < 8.0)
2720 obj->hitPoint[1] = true;
2721 hoverPoint = handle1;
2722 hoverPointValid = true;
2724 else if ((length3 * Global::zoom) < 8.0)
2726 obj->hitPoint[2] = true;
2727 hoverPoint = handle2;
2728 hoverPointValid = true;
2730 else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
2731 obj->hitObject = true;
2733 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
2735 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
2743 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHP3 = obj->hitPoint[3], oldHP4 = obj->hitPoint[4], oldHO = obj->hitObject;
2744 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitPoint[3] = obj->hitPoint[4] = obj->hitObject = false;
2746 Dimension * d = (Dimension *)obj;
2748 Vector orthogonal = Vector::Normal(d->lp[0], d->lp[1]);
2749 // Get our line parallel to our points
2750 float scaledThickness = Global::scale * obj->thickness;
2752 Point p1 = d->lp[0] + (orthogonal * (d->offset + (10.0 * scaledThickness)));
2753 Point p2 = d->lp[1] + (orthogonal * (d->offset + (10.0 * scaledThickness)));
2755 Point p1 = d->lp[0] + (orthogonal * (10.0 + d->offset) * scaledThickness);
2756 Point p2 = d->lp[1] + (orthogonal * (10.0 + d->offset) * scaledThickness);
2758 Point p3(p1, point);
2760 Vector v1(d->p[0], point);
2761 Vector v2(d->p[1], point);
2762 Vector lineSegment(p1, p2);
2763 double t = Geometry::ParameterOfLineAndPoint(p1, p2, point);
2765 Point midpoint = (p1 + p2) / 2.0;
2766 Point hFSPoint = Point(midpoint, point);
2767 Point hCS1Point = Point((p1 + midpoint) / 2.0, point);
2768 Point hCS2Point = Point((midpoint + p2) / 2.0, point);
2771 distance = v1.Magnitude();
2773 distance = v2.Magnitude();
2775 // distance = ?Det?(ls, v1) / |ls|
2776 distance = fabs((lineSegment.x * p3.y - p3.x * lineSegment.y)
2777 / lineSegment.Magnitude());
2779 if ((v1.Magnitude() * Global::zoom) < 8.0)
2780 obj->hitPoint[0] = true;
2781 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2782 obj->hitPoint[1] = true;
2783 else if ((distance * Global::zoom) < 5.0)
2784 obj->hitObject = true;
2786 if ((hFSPoint.Magnitude() * Global::zoom) < 8.0)
2787 obj->hitPoint[2] = true;
2788 else if ((hCS1Point.Magnitude() * Global::zoom) < 8.0)
2789 obj->hitPoint[3] = true;
2790 else if ((hCS2Point.Magnitude() * Global::zoom) < 8.0)
2791 obj->hitPoint[4] = true;
2793 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitPoint[3] || obj->hitPoint[4] || obj->hitObject ? true : false);
2795 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHP3 != obj->hitPoint[3]) || (oldHP4 != obj->hitPoint[4]) || (oldHO != obj->hitObject))
2803 Text * t = (Text *)obj;
2804 bool oldHO = obj->hitObject;
2805 obj->hitObject = false;
2807 Rect r(obj->p[0], Point(obj->p[0].x + t->extents.Width(), obj->p[0].y - t->extents.Height()));
2808 //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);
2810 if (r.Contains(point))
2811 obj->hitObject = true;
2813 obj->hovered = (obj->hitObject ? true : false);
2815 if (oldHO != obj->hitObject)
2823 // Containers must be recursively tested... Or do they???
2825 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.
2827 // bool oldHitObj = c->hitObject, oldHovered = c->hovered;
2828 // Object * oldClicked = c->clicked;
2830 still need to compare old state to new state, and set things up based upon that...
2831 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);
2832 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.
2834 Container * c = (Container *)obj;
2835 c->hitObject = false;
2839 VPVector flat = Flatten(c);
2841 //printf("HitTest::OTContainer (size=%li)\n", flat.size());
2842 for(VPVectorIter i=flat.begin(); i!=flat.end(); i++)
2844 Object * cObj = (Object *)(*i);
2846 // Skip the flattened containers (if any)...
2847 if (cObj->type == OTContainer)
2850 // We do it this way instead of needUpdate = HitTest() because we
2851 // are checking more than one object, and that way of doing will
2852 // not return consistent results.
2853 if (HitTest(cObj, point) == true)
2855 //printf("HitTest::OTContainer, subobj ($%llX) hit!\n", cObj);
2857 // c->hitObject = true;
2858 // c->clicked = cObj;
2859 // c->hovered = true;
2862 // Same reasons for doing it this way here apply.
2863 if (cObj->hitObject == true)
2865 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2866 c->hitObject = true;
2870 if (cObj->hitPoint[0] == true)
2872 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2873 c->hitPoint[0] = true;
2877 if (cObj->hitPoint[1] == true)
2879 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2880 c->hitPoint[1] = true;
2884 if (cObj->hitPoint[2] == true)
2886 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2887 c->hitPoint[2] = true;
2891 if (cObj->hovered == true)
2892 c->hovered = true;//*/
2905 bool DrawingView::HandleObjectClicked(void)
2907 if (dragged->type == OTDimension)
2909 Dimension * d = (Dimension *)dragged;
2913 // Hit the "flip sides" switch, so flip 'em
2914 Point temp = d->p[0];
2919 else if (d->hitPoint[3])
2921 // There are three cases here: aligned, horizontal, & vertical.
2922 // Aligned and horizontal do the same thing, vertical goes back to
2924 if (d->subtype == DTLinearVert)
2925 d->subtype = DTLinear;
2927 d->subtype = DTLinearVert;
2931 else if (d->hitPoint[4])
2933 // There are three cases here: aligned, horizontal, & vertical.
2934 // Aligned and vertical do the same thing, horizontal goes back to
2936 if (d->subtype == DTLinearHorz)
2937 d->subtype = DTLinear;
2939 d->subtype = DTLinearHorz;
2948 void DrawingView::HandleObjectMovement(Point point)
2950 Point delta = point - oldPoint;
2951 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
2952 // Object * obj = (Object *)hover[0];
2953 Object * obj = dragged;
2954 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
2955 //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"));
2960 if (obj->hitPoint[0])
2962 if (Global::fixedLength)
2964 Vector line = point - obj->p[1];
2965 Vector unit = line.Unit();
2966 point = obj->p[1] + (unit * obj->length);
2971 else if (obj->hitPoint[1])
2973 if (Global::fixedLength)
2975 Vector line = point - obj->p[0];
2976 Vector unit = line.Unit();
2977 point = obj->p[0] + (unit * obj->length);
2982 else if (obj->hitObject)
2991 if (obj->hitPoint[0])
2993 else if (obj->hitObject)
2995 double oldRadius = obj->length;
2996 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
2998 QString text = QObject::tr("Radius: %1\nScale: %2%");
2999 informativeText = text.arg(obj->radius[0], 0, 'd', 4).arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0);
3005 if (obj->hitPoint[0])
3007 else if (obj->hitPoint[1])
3009 // Change the Arc's span (handle #1)
3012 double angle = Vector::Angle(obj->p[0], point);
3013 double delta = angle - obj->angle[0];
3018 obj->angle[1] -= delta;
3019 obj->angle[0] = angle;
3021 if (obj->angle[1] < 0)
3022 obj->angle[1] += TAU;
3024 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
3025 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);
3029 double angle = Vector::Angle(obj->p[0], point);
3030 obj->angle[0] = angle;
3031 QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
3032 informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 4);
3034 else if (obj->hitPoint[2])
3036 // Change the Arc's span (handle #2)
3039 double angle = Vector::Angle(obj->p[0], point);
3040 obj->angle[1] = angle - obj->angle[0];
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 - obj->angle[1];
3053 if (obj->angle[0] < 0)
3054 obj->angle[0] += TAU;
3056 QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
3057 informativeText = text.arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 4);
3059 else if (obj->hitObject)
3066 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
3067 QString text = QObject::tr("Radius: %1");
3068 informativeText = text.arg(obj->radius[0], 0, 'd', 4);
3074 if (obj->hitPoint[0])
3076 else if (obj->hitPoint[1])
3078 else if (obj->hitObject)
3080 // Move measurement lines in/out
3083 Dimension * d = (Dimension *)obj;
3084 double dist = Geometry::DistanceToLineFromPoint(d->lp[0], d->lp[1], point);
3085 float scaledThickness = Global::scale * obj->thickness;
3086 // Looks like offset is 0 to +MAX, but line is at 10.0. So
3087 // anything less than 10.0 should set the offset to 0.
3090 if (dist > (10.0 * scaledThickness))
3091 d->offset = dist - (10.0 * scaledThickness);
3109 // This is shitty, but works for now until I can code up something
3112 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.
3114 // TranslateObject(obj, delta);
3115 TranslateContainer((Container *)obj, point, delta);
3123 void DrawingView::AddDimensionTo(void * o)
3125 Object * obj = (Object *)o;
3130 document.Add(new Dimension(obj->p[0], obj->p[1]));