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 switch (Global::tool)
815 if (Global::toolState == TSNone)
817 painter->DrawHandle(toolPoint[0]);
819 else if ((Global::toolState == TSPoint2) && shiftDown)
821 painter->DrawHandle(toolPoint[1]);
825 painter->DrawLine(toolPoint[0], toolPoint[1]);
826 painter->DrawHandle(toolPoint[1]);
828 Vector v(toolPoint[0], toolPoint[1]);
829 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
830 double absLength = v.Magnitude();
831 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
832 informativeText = text.arg(absLength).arg(absAngle);
838 if (Global::toolState == TSNone)
840 painter->DrawHandle(toolPoint[0]);
842 else if ((Global::toolState == TSPoint2) && shiftDown)
844 painter->DrawHandle(toolPoint[1]);
848 painter->DrawCross(toolPoint[0]);
849 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
850 painter->SetBrush(QBrush(Qt::NoBrush));
851 painter->DrawEllipse(toolPoint[0], length, length);
852 QString text = tr("Radius: %1 in.");
853 informativeText = text.arg(length);
859 if (Global::toolState == TSNone)
861 painter->DrawHandle(toolPoint[0]);
863 else if (Global::toolState == TSPoint2)
865 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
866 painter->SetBrush(QBrush(Qt::NoBrush));
867 painter->DrawEllipse(toolPoint[0], length, length);
868 painter->DrawLine(toolPoint[0], toolPoint[1]);
869 painter->DrawHandle(toolPoint[1]);
870 QString text = tr("Radius: %1 in.");
871 informativeText = text.arg(length);
873 else if (Global::toolState == TSPoint3)
875 double angle = Vector::Angle(toolPoint[0], toolPoint[2]);
876 painter->DrawLine(toolPoint[0], toolPoint[2]);
877 painter->SetBrush(QBrush(Qt::NoBrush));
878 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
879 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
880 QString text = tr("Angle start: %1") + QChar(0x00B0);
881 informativeText = text.arg(RADIANS_TO_DEGREES * angle);
885 double angle = Vector::Angle(toolPoint[0], toolPoint[3]);
886 double span = angle - toolPoint[2].x;
891 painter->DrawLine(toolPoint[0], toolPoint[3]);
892 painter->SetBrush(QBrush(Qt::NoBrush));
893 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
894 painter->SetPen(0xFF00FF, 2.0, LSSolid);
895 painter->DrawArc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
896 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
897 QString text = tr("Arc span: %1") + QChar(0x00B0);
898 informativeText = text.arg(RADIANS_TO_DEGREES * span);
904 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
905 painter->DrawHandle(toolPoint[0]);
906 else if ((Global::toolState == TSPoint2) && shiftDown)
907 painter->DrawHandle(toolPoint[1]);
910 if (toolPoint[0] == toolPoint[1])
913 painter->DrawLine(toolPoint[0], toolPoint[1]);
915 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
916 QString text = QChar(0x2221) + QObject::tr(": %1");
917 informativeText = text.arg(absAngle);
920 informativeText += " (Copy)";
926 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
927 painter->DrawHandle(toolPoint[0]);
928 else if ((Global::toolState == TSPoint2) && shiftDown)
929 painter->DrawHandle(toolPoint[1]);
932 if (toolPoint[0] == toolPoint[1])
935 Point mirrorPoint = toolPoint[0] + Vector(toolPoint[1], toolPoint[0]);
936 painter->DrawLine(mirrorPoint, toolPoint[1]);
938 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
940 if (absAngle > 180.0)
943 QString text = QChar(0x2221) + QObject::tr(": %1");
944 informativeText = text.arg(absAngle);
947 informativeText += " (Copy)";
953 if (Global::toolState == TSNone)
955 painter->DrawHandle(toolPoint[0]);
957 else if ((Global::toolState == TSPoint2) && shiftDown)
959 painter->DrawHandle(toolPoint[1]);
963 painter->DrawLine(toolPoint[0], toolPoint[1]);
964 painter->DrawHandle(toolPoint[1]);
966 Vector v(toolPoint[0], toolPoint[1]);
967 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
968 double absLength = v.Magnitude();
969 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
970 informativeText = text.arg(absLength).arg(absAngle);
976 if (toolObj[0] != NULL)
978 // We're assuming ATM it's just a line...
979 painter->SetPen(0xAF0000, 3.0, LSSolid);
980 painter->DrawLine(toolPoint[0], toolPoint[1]);
981 // QString text = tr("Arc span: %1") + QChar(0x00B0);
982 // informativeText = text.arg(RADIANS_TO_DEGREES * span);
988 if (Global::toolState == TSPoint1)
990 painter->SetPen(0xFF00FF, 2.0, LSSolid);
991 painter->SetBrush(QBrush(Qt::NoBrush));
993 double length = Vector::Magnitude(toolObj[0]->p[0], toolPoint[0]);
994 bool inside = (length >= toolObj[0]->radius[0] ? false : true);
996 for(int i=1; i<=Global::parallelNum; i++)
998 if (toolObj[0]->type == OTLine)
1000 painter->DrawLine(toolObj[0]->p[0] + (toolPoint[0] * Global::parallelDist * (double)i), toolObj[0]->p[1] + (toolPoint[0] * Global::parallelDist * (double)i));
1002 else if ((toolObj[0]->type == OTCircle) || (toolObj[0]->type == OTArc))
1004 double radius = toolObj[0]->radius[0] + ((double)i * Global::parallelDist * (inside ? -1.0 : 1.0));
1008 if (toolObj[0]->type == OTCircle)
1009 painter->DrawEllipse(toolObj[0]->p[0], radius, radius);
1011 painter->DrawArc(toolObj[0]->p[0], radius, toolObj[0]->angle[0], toolObj[0]->angle[1]);
1024 void DrawingView::LineHandler(int mode, Point p)
1029 /* toolObj[0] = NULL;
1031 // Check to see if we can do a circle tangent snap
1032 if (numHovered == 1)
1034 VPVector hover = GetHovered();
1035 Object * obj = (Object *)hover[0];
1037 // 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! :-)
1038 if ((obj->type == OTCircle) && (obj->hitPoint[0] == false))
1042 if (Global::toolState == TSNone)
1050 if (Global::toolState == TSNone)
1055 /* bool isCircle = false;
1057 if (numHovered == 1)
1059 VPVector hover = GetHovered();
1060 Object * obj = (Object *)hover[0];
1062 if ((obj->type == OTCircle) && (obj->hitPoint[0] == false))
1069 // Adjust initial point if it's on a circle (tangent point)
1070 if (toolObj[0] != NULL)
1074 Geometry::FindTangents(toolObj[0], toolObj[1]);
1076 if (Global::numIntersectPoints > 0)
1078 toolPoint[0] = Global::intersectPoint[0];
1079 toolPoint[1] = Global::intersectPoint[1];
1084 Geometry::FindTangents(toolObj[0], p);
1086 if (Global::numIntersectPoints > 0)
1087 toolPoint[0] = Global::intersectPoint[0];
1094 Geometry::FindTangents(toolObj[1], toolPoint[0]);
1096 if (Global::numIntersectPoints > 0)
1097 toolPoint[1] = Global::intersectPoint[0];
1105 if (Global::toolState == TSNone)
1107 Global::toolState = TSPoint2;
1108 // Prevent spurious line from drawing...
1109 toolPoint[1] = toolPoint[0];
1111 else if ((Global::toolState == TSPoint2) && shiftDown)
1113 // Key override is telling us to make a new line, not continue the
1115 toolPoint[0] = toolPoint[1];
1119 Line * l = new Line(toolPoint[0], toolPoint[1], Global::penWidth, Global::penColor, Global::penStyle);
1120 l->layer = Global::activeLayer;
1121 document.objects.push_back(l);
1122 toolPoint[0] = toolPoint[1];
1127 void DrawingView::CircleHandler(int mode, Point p)
1132 if (Global::toolState == TSNone)
1140 if (Global::toolState == TSNone)
1148 if (Global::toolState == TSNone)
1150 Global::toolState = TSPoint2;
1151 // Prevent spurious line from drawing...
1152 toolPoint[1] = toolPoint[0];
1154 else if ((Global::toolState == TSPoint2) && shiftDown)
1156 // Key override is telling us to make a new line, not continue the
1158 toolPoint[0] = toolPoint[1];
1162 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
1163 Circle * c = new Circle(toolPoint[0], length, Global::penWidth, Global::penColor, Global::penStyle);
1164 c->layer = Global::activeLayer;
1165 document.objects.push_back(c);
1166 toolPoint[0] = toolPoint[1];
1167 Global::toolState = TSNone;
1172 void DrawingView::ArcHandler(int mode, Point p)
1177 if (Global::toolState == TSNone)
1179 else if (Global::toolState == TSPoint2)
1181 else if (Global::toolState == TSPoint3)
1189 if (Global::toolState == TSNone)
1191 else if (Global::toolState == TSPoint2)
1193 else if (Global::toolState == TSPoint3)
1207 if (Global::toolState == TSNone)
1209 // Prevent spurious line from drawing...
1210 toolPoint[1] = toolPoint[0];
1211 Global::toolState = TSPoint2;
1213 else if (Global::toolState == TSPoint2)
1217 // Key override is telling us to start arc at new center, not
1218 // continue the current one.
1219 toolPoint[0] = toolPoint[1];
1223 // Set the radius in toolPoint[1].x
1224 toolPoint[1].x = Vector::Magnitude(toolPoint[0], toolPoint[1]);
1225 Global::toolState = TSPoint3;
1227 else if (Global::toolState == TSPoint3)
1229 // Set the angle in toolPoint[2].x
1230 toolPoint[2].x = Vector::Angle(toolPoint[0], toolPoint[2]);
1231 Global::toolState = TSPoint4;
1235 double endAngle = Vector::Angle(toolPoint[0], toolPoint[3]);
1236 double span = endAngle - toolPoint[2].x;
1241 Arc * arc = new Arc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span, Global::penWidth, Global::penColor, Global::penStyle);
1242 arc->layer = Global::activeLayer;
1243 document.objects.push_back(arc);
1244 Global::toolState = TSNone;
1249 void DrawingView::RotateHandler(int mode, Point p)
1254 if (Global::toolState == TSNone)
1257 // SavePointsFrom(select, toolScratch);
1258 CopyObjects(select, toolScratch2);
1259 Global::toolState = TSPoint1;
1261 else if (Global::toolState == TSPoint1)
1269 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1271 else if (Global::toolState == TSPoint2)
1279 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1280 VPVectorIter j = select.begin();
1281 // std::vector<Object>::iterator i = toolScratch.begin();
1282 VPVectorIter i = toolScratch2.begin();
1284 // for(; i!=toolScratch.end(); i++, j++)
1285 for(; i!=toolScratch2.end(); i++, j++)
1287 // Object objT = *i;
1288 // Point p1 = Geometry::RotatePointAroundPoint(objT.p[0], toolPoint[0], angle);
1289 // Point p2 = Geometry::RotatePointAroundPoint(objT.p[1], toolPoint[0], angle);
1290 Object * objT = (Object *)(*i);
1291 Object * objS = (Object *)(*j);
1293 Point p1 = Geometry::RotatePointAroundPoint(objT->p[0], toolPoint[0], angle);
1294 Point p2 = Geometry::RotatePointAroundPoint(objT->p[1], toolPoint[0], angle);
1299 // if (objT.type == OTArc || objT.type == OTText)
1300 if (objT->type == OTArc || objT->type == OTText)
1302 // objS->angle[0] = objT.angle[0] + angle;
1303 objS->angle[0] = objT->angle[0] + angle;
1305 if (objS->angle[0] > TAU)
1306 objS->angle[0] -= TAU;
1308 // else if (objT.type == OTContainer)
1309 else if (objT->type == OTContainer)
1311 // 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...]
1312 // Container * c = (Container *)&objT;
1313 Container * c = (Container *)objT;
1314 Container * c2 = (Container *)objS;
1315 VPVectorIter l = c->objects.begin();
1316 // TODO: Rotate items in the container
1317 // TODO: Make this recursive
1318 for(VPVectorIter k=c2->objects.begin(); k!=c2->objects.end(); k++, l++)
1320 Object * obj3 = (Object *)(*k);
1321 Object * obj4 = (Object *)(*l);
1323 p1 = Geometry::RotatePointAroundPoint(obj4->p[0], toolPoint[0], angle);
1324 p2 = Geometry::RotatePointAroundPoint(obj4->p[1], toolPoint[0], angle);
1328 // obj3->angle[0] = objT.angle[0] + angle;
1329 obj3->angle[0] = obj4->angle[0] + angle;
1331 if (obj3->angle[0] > TAU)
1332 obj3->angle[0] -= TAU;
1335 Rect r = GetObjectExtents(objS);
1336 c2->p[0] = r.TopLeft();
1337 c2->p[1] = r.BottomRight();
1345 if (Global::toolState == TSPoint1)
1347 Global::toolState = TSPoint2;
1348 // Prevent spurious line from drawing...
1349 toolPoint[1] = toolPoint[0];
1351 else if ((Global::toolState == TSPoint2) && shiftDown)
1353 // Key override is telling us to make a new line, not continue the
1355 toolPoint[0] = toolPoint[1];
1359 // Either we're finished with our rotate, or we're stamping a copy.
1362 // Stamp a copy of the selection at the current rotation & bail
1364 CopyObjects(select, temp);
1365 ClearSelected(temp);
1366 AddObjectsTo(document.objects, temp);
1367 // RestorePointsTo(select, toolScratch);
1368 RestorePointsTo(select, toolScratch2);
1373 Global::toolState = TSPoint1;
1374 // SavePointsFrom(select, toolScratch);
1375 DeleteContents(toolScratch2);
1376 CopyObjects(select, toolScratch2);
1382 // Reset the selection if shift held down...
1384 // RestorePointsTo(select, toolScratch);
1385 RestorePointsTo(select, toolScratch2);
1390 // Reset selection when key is let up
1392 RotateHandler(ToolMouseMove, toolPoint[1]);
1397 // RestorePointsTo(select, toolScratch);
1398 RestorePointsTo(select, toolScratch2);
1399 DeleteContents(toolScratch2);
1403 void DrawingView::MirrorHandler(int mode, Point p)
1408 if (Global::toolState == TSNone)
1411 SavePointsFrom(select, toolScratch);
1412 Global::toolState = TSPoint1;
1414 else if (Global::toolState == TSPoint1)
1422 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1424 else if (Global::toolState == TSPoint2)
1432 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1433 VPVectorIter j = select.begin();
1434 std::vector<Object>::iterator i = toolScratch.begin();
1436 for(; i!=toolScratch.end(); i++, j++)
1439 Point p1 = Geometry::MirrorPointAroundLine(obj.p[0], toolPoint[0], toolPoint[1]);
1440 Point p2 = Geometry::MirrorPointAroundLine(obj.p[1], toolPoint[0], toolPoint[1]);
1441 Object * obj2 = (Object *)(*j);
1446 N.B.: When mirroring an arc thru a horizontal axis, this causes the arc to have
1447 a negative start angle which makes it impossible to interact with.
1450 if (obj.type == OTArc)
1452 // This is 2*mirror angle - obj angle - obj span
1453 obj2->angle[0] = (2.0 * angle) - obj.angle[0] - obj.angle[1];
1455 if (obj2->angle[0] > TAU)
1456 obj2->angle[0] -= TAU;
1464 if (Global::toolState == TSPoint1)
1466 Global::toolState = TSPoint2;
1467 // Prevent spurious line from drawing...
1468 toolPoint[1] = toolPoint[0];
1470 else if ((Global::toolState == TSPoint2) && shiftDown)
1472 // Key override is telling us to make a new line, not continue the
1474 toolPoint[0] = toolPoint[1];
1478 // Either we're finished with our rotate, or we're stamping a copy.
1481 // Stamp a copy of the selection at the current rotation & bail
1483 CopyObjects(select, temp);
1484 ClearSelected(temp);
1485 AddObjectsTo(document.objects, temp);
1486 RestorePointsTo(select, toolScratch);
1491 Global::toolState = TSPoint1;
1492 SavePointsFrom(select, toolScratch);
1498 // Reset the selection if shift held down...
1500 RestorePointsTo(select, toolScratch);
1505 // Reset selection when key is let up
1507 MirrorHandler(ToolMouseMove, toolPoint[1]);
1512 RestorePointsTo(select, toolScratch);
1516 void DrawingView::DimensionHandler(int mode, Point p)
1521 if (Global::toolState == TSNone)
1529 if (Global::toolState == TSNone)
1537 if (Global::toolState == TSNone)
1539 Global::toolState = TSPoint2;
1540 // Prevent spurious line from drawing...
1541 toolPoint[1] = toolPoint[0];
1543 else if ((Global::toolState == TSPoint2) && shiftDown)
1545 // Key override is telling us to make a new line, not continue the
1547 toolPoint[0] = toolPoint[1];
1551 Dimension * d = new Dimension(toolPoint[0], toolPoint[1], DTLinear, 0, Global::penWidth);
1552 d->layer = Global::activeLayer;
1553 document.objects.push_back(d);
1554 Global::toolState = TSNone;
1559 void DrawingView::DeleteHandler(int mode, Point /*p*/)
1565 VPVector hovered = GetHovered();
1567 RemoveHoveredObjects(document.objects);
1568 DeleteContents(hovered);
1589 void DrawingView::TriangulateHandler(int mode, Point /*p*/)
1595 // Skip if nothing hovered...
1596 if (numHovered != 1)
1599 VPVector hover = GetHovered();
1600 Object * obj = (Object *)hover[0];
1602 // Skip if it's not a line...
1603 if (obj->type != OTLine)
1606 if (Global::toolState == TSNone)
1608 else if (Global::toolState == TSPoint2)
1617 if (Global::toolState == TSNone)
1619 else if (Global::toolState == TSPoint2)
1621 else if (Global::toolState == TSPoint3)
1635 if (Global::toolState == TSNone)
1637 Global::toolState = TSPoint2;
1639 else if (Global::toolState == TSPoint2)
1643 // Key override is telling us to start arc at new center, not
1644 // continue the current one.
1645 toolPoint[0] = toolPoint[1];
1649 Global::toolState = TSPoint3;
1653 double len2 = Vector::Magnitude(toolObj[1]->p[0], toolObj[1]->p[1]);
1654 double len3 = Vector::Magnitude(toolObj[2]->p[0], toolObj[2]->p[1]);
1656 Circle c1(toolObj[0]->p[0], len2);
1657 Circle c2(toolObj[0]->p[1], len3);
1659 Geometry::CheckCircleToCircleIntersection((Object *)&c1, (Object *)&c2);
1661 // Only move lines if the triangle formed by them is not degenerate
1662 if (Global::numIntersectPoints > 0)
1664 toolObj[1]->p[0] = toolObj[0]->p[0];
1665 toolObj[1]->p[1] = Global::intersectPoint[0];
1667 toolObj[2]->p[0] = Global::intersectPoint[0];
1668 toolObj[2]->p[1] = toolObj[0]->p[1];
1671 Global::toolState = TSNone;
1676 void DrawingView::TrimHandler(int mode, Point p)
1687 // Bail out if nothing hovered...
1688 if (numHovered != 1)
1694 VPVector hover = GetHovered();
1695 Object * obj = (Object *)hover[0];
1697 // Skip if it's not a line...
1698 if (obj->type != OTLine)
1705 double hoveredParam = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], p);
1706 double t = 0, u = 1.0;
1708 // Currently only deal with line against line trimming, can expand to
1709 // others as well (line/circle, circle/circle, line/arc, etc)
1711 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1713 obj = (Object *)(*i);
1715 if (obj == toolObj[0])
1717 else if (obj->type != OTLine)
1720 Geometry::CheckLineToLineIntersection(toolObj[0], obj);
1722 if (Global::numIntersectParams > 0)
1724 // Skip endpoint-endpoint intersections
1725 if ((Global::numIntersectParams == 2)
1726 && (Global::intersectParam[0] == 0
1727 || Global::intersectParam[0] == 1.0)
1728 && (Global::intersectParam[1] == 0
1729 || Global::intersectParam[1] == 1.0))
1732 // Mark the line segment somehow (the side the mouse is on) so that it can be drawn & trimmed when we hit ToolMouseDown.
1733 if ((Global::intersectParam[0] > t) && (Global::intersectParam[0] < hoveredParam))
1734 t = Global::intersectParam[0];
1736 if ((Global::intersectParam[0] < u) && (Global::intersectParam[0] > hoveredParam))
1737 u = Global::intersectParam[0];
1743 toolPoint[0] = Geometry::GetPointForParameter(toolObj[0], t);
1744 toolPoint[1] = Geometry::GetPointForParameter(toolObj[0], u);
1750 // Bail out if there's no object to trim
1751 if (toolObj[0] == NULL)
1754 Vector v(toolObj[0]->p[0], toolObj[0]->p[1]);
1756 // Check to see which case we have.
1757 if ((toolParam[0] == 0) && (toolParam[1] == 1.0))
1759 // There was no intersection, so delete the object
1760 toolObj[0]->selected = true;
1761 DeleteSelectedObjects(document.objects);
1763 else if (toolParam[0] == 0)
1765 // We delete the end near point #1
1766 toolObj[0]->p[0] = toolObj[0]->p[0] + (v * toolParam[1]);
1768 else if (toolParam[1] == 1.0)
1770 // We delete the end near point #2
1771 toolObj[0]->p[1] = toolObj[0]->p[0] + (v * toolParam[0]);
1775 // We delete the segment in between, and create a new line in the process
1776 Point p1 = toolObj[0]->p[0] + (v * toolParam[0]);
1777 Point p2 = toolObj[0]->p[0] + (v * toolParam[1]);
1778 Point p3 = toolObj[0]->p[1];
1779 toolObj[0]->p[1] = p1;
1780 Line * l = new Line(p2, p3, toolObj[0]->thickness, toolObj[0]->color, toolObj[0]->style);
1781 document.objects.push_back(l);
1782 // Global::toolState = TSNone;
1785 toolObj[0]->hitObject = toolObj[0]->hitPoint[0] = toolObj[0]->hitPoint[1] = false;
1801 void DrawingView::ParallelHandler(int mode, Point p)
1806 if (numHovered == 1)
1808 // New selection made...
1809 VPVector hover = GetHovered();
1810 toolObj[0] = (Object *)hover[0];
1811 Global::toolState = TSNone;
1813 else if ((numHovered == 0) && (toolObj[0] != NULL))
1815 double length = Vector::Magnitude(toolObj[0]->p[0], toolPoint[0]);
1816 bool inside = (length >= toolObj[0]->radius[0] ? false : true);
1818 // Stamp out new parallel object(s)...
1819 for(int i=1; i<=Global::parallelNum; i++)
1821 if (toolObj[0]->type == OTLine)
1823 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);
1824 // Should probably have a user selection for this whether it goes into the selected objects layer or the global layer...
1825 l->layer = toolObj[0]->layer;
1826 document.objects.push_back(l);
1828 else if (toolObj[0]->type == OTCircle)
1830 double radius = toolObj[0]->radius[0] + ((double)i * Global::parallelDist * (inside ? -1.0 : 1.0));
1834 Circle * c = new Circle(toolObj[0]->p[0], radius, Global::penWidth, Global::penColor, Global::penStyle);
1835 c->layer = toolObj[0]->layer;
1836 document.objects.push_back(c);
1839 else if (toolObj[0]->type == OTArc)
1841 double radius = toolObj[0]->radius[0] + ((double)i * Global::parallelDist * (inside ? -1.0 : 1.0));
1845 Arc * a = new Arc(toolObj[0]->p[0], radius, toolObj[0]->angle[0], toolObj[0]->angle[1], Global::penWidth, Global::penColor, Global::penStyle);
1846 a->layer = toolObj[0]->layer;
1847 document.objects.push_back(a);
1852 // Then reset the state
1853 toolObj[0]->selected = false;
1855 Global::toolState = TSNone;
1861 if ((numHovered == 0) && toolObj[0] != NULL)
1862 Global::toolState = TSPoint1;
1864 Global::toolState = TSNone;
1866 if (Global::toolState == TSPoint1)
1868 // Figure out which side of the object we're on, and draw the preview on that side...
1869 if (toolObj[0]->type == OTLine)
1871 Vector normal = Geometry::GetNormalOfPointAndLine(p, (Line *)toolObj[0]);
1872 toolPoint[0] = normal;
1874 else if ((toolObj[0]->type == OTCircle) || (toolObj[0]->type == OTArc))
1896 void DrawingView::mousePressEvent(QMouseEvent * event)
1898 if (event->button() == Qt::LeftButton)
1900 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1902 // Handle tool processing, if any
1905 if (hoveringIntersection)
1906 point = intersectionPoint;
1907 else if (hoverPointValid)
1909 else if (Global::snapToGrid)
1910 point = SnapPointToGrid(point);
1912 ToolHandler(ToolMouseDown, point);
1916 // Clear the selection only if CTRL isn't being held on click
1918 ClearSelected(document.objects);
1920 // If any objects are being hovered on click, deal with 'em
1923 VPVector hover2 = GetHovered();
1924 dragged = (Object *)hover2[0];
1926 // Alert the pen widget
1927 if (Global::penDropper)
1929 Global::penColor = dragged->color;
1930 Global::penWidth = dragged->thickness;
1931 Global::penStyle = dragged->style;
1932 emit ObjectSelected(dragged);
1935 else if (Global::penStamp)
1937 dragged->color = Global::penColor;
1938 dragged->thickness = Global::penWidth;
1939 dragged->style = Global::penStyle;
1942 // See if anything is using just a straight click on a custom
1943 // object handle (like Dimension objects)
1944 else if (HandleObjectClicked())
1950 draggingObject = true;
1951 HandleSelectionClick(hover2);
1952 update(); // needed??
1954 // Needed for grab & moving objects
1955 // We do it *after*... why? (doesn't seem to confer any advantage...)
1956 if (hoveringIntersection)
1957 oldPoint = intersectionPoint;
1958 else if (hoverPointValid)
1959 oldPoint = hoverPoint;
1960 else if (Global::snapToGrid)
1961 oldPoint = SnapPointToGrid(point);
1963 // Needed for fixed length handling
1964 if (Global::fixedLength)
1966 if (dragged->type == OTLine)
1967 dragged->length = ((Line *)dragged)->Length();
1970 // Needed for fixed angle handling
1971 if (Global::fixedAngle)
1973 if (dragged->type == OTLine)
1974 dragged->p[2] = ((Line *)dragged)->Unit();
1977 if (dragged->type == OTCircle)
1979 // Save for informative text, uh, er, informing
1980 dragged->length = dragged->radius[0];
1986 // Didn't hit any object and not using a tool, so do a selection
1988 Global::selectionInProgress = true;
1989 Global::selection.setTopLeft(QPointF(point.x, point.y));
1990 Global::selection.setBottomRight(QPointF(point.x, point.y));
1991 select = GetSelection();
1993 else if (event->button() == Qt::MiddleButton)
1996 oldPoint = Vector(event->x(), event->y());
1997 // Should also change the mouse pointer as well...
1998 setCursor(Qt::SizeAllCursor);
2002 void DrawingView::mouseMoveEvent(QMouseEvent * event)
2004 // It seems that wheelEvent() triggers this for some reason...
2005 if (scrollWheelSeen)
2007 scrollWheelSeen = false;
2011 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
2012 Global::selection.setBottomRight(QPointF(point.x, point.y));
2013 // Only needs to be done here, as mouse down is always preceded by movement
2014 Global::snapPointIsValid = false;
2015 hoveringIntersection = false;
2016 oldScrollPoint = Vector(event->x(), event->y());
2019 if ((event->buttons() & Qt::MiddleButton) || scrollDrag)
2021 point = Vector(event->x(), event->y());
2022 // Since we're using Qt coords for scrolling, we have to adjust them
2023 // here to conform to Cartesian coords, since the origin is using
2025 Vector delta(oldPoint, point);
2026 delta /= Global::zoom;
2028 Global::origin -= delta;
2030 // UpdateGridBackground();
2036 // If we're doing a selection rect, see if any objects are engulfed by it
2037 // (implies left mouse button held down)
2038 if (Global::selectionInProgress)
2040 CheckObjectBounds();
2042 // Make sure previously selected objects stay selected (CTRL held)
2043 for(VPVectorIter i=select.begin(); i!=select.end(); i++)
2045 // Make sure *not* to select items on hidden layers
2046 if (Global::layerHidden[((Object *)(*i))->layer] == false)
2047 ((Object *)(*i))->selected = true;
2054 // Do object hit testing...
2055 bool needUpdate = HitTestObjects(point);
2056 VPVector hover2 = GetHovered(true); // Exclude dimension objects and circle centers (probably need to add arc centers too) from hover (also dragged objects...)
2061 printf("mouseMoveEvent:: numHovered=%li, hover2.size()=%li\n", numHovered, hover2.size());
2063 if (hover2.size() > 0)
2064 printf(" (hover2[0]=$%llX, type=%s)\n", hover2[0], objName[((Object *)hover2[0])->type]);
2069 // Check for multi-hover...
2070 if (hover2.size() > 1)
2072 //need to check for case where hover is over 2 circles and a 3rd's center (no longer a problem, I think)...
2073 Object * obj1 = (Object *)hover2[0], * obj2 = (Object *)hover2[1];
2075 Geometry::Intersects(obj1, obj2);
2076 int numIntersecting = Global::numIntersectParams;
2077 double t = Global::intersectParam[0];
2078 double u = Global::intersectParam[1];
2080 if (numIntersecting > 0)
2082 Vector v1 = Geometry::GetPointForParameter(obj1, t);
2083 Vector v2 = Geometry::GetPointForParameter(obj2, u);
2084 QString text = tr("Intersection t=%1 (%3, %4), u=%2 (%5, %6)");
2085 informativeText = text.arg(t).arg(u).arg(v1.x).arg(v1.y).arg(v2.x).arg(v2.y);
2087 hoveringIntersection = true;
2088 intersectionPoint = v1;
2091 numIntersecting = Global::numIntersectPoints;
2093 if (numIntersecting > 0)
2095 Vector v1 = Global::intersectPoint[0];
2097 if (numIntersecting == 2)
2099 Vector v2 = Global::intersectPoint[1];
2101 if (Vector::Magnitude(v2, point) < Vector::Magnitude(v1, point))
2105 QString text = tr("Intersection <%1, %2>");
2106 informativeText = text.arg(v1.x).arg(v1.y);
2107 hoveringIntersection = true;
2108 intersectionPoint = v1;
2111 else if (hover2.size() == 1)
2113 Object * obj = (Object *)hover2[0];
2115 if (obj->type == OTLine)
2118 Not sure that this is the best way to handle this, but it works(TM)...
2120 Point midpoint = Geometry::Midpoint((Line *)obj);
2121 Vector v1 = Vector::Magnitude(midpoint, point);
2123 if ((v1.Magnitude() * Global::zoom) < 8.0)
2125 QString text = tr("Midpoint <%1, %2>");
2126 informativeText = text.arg(midpoint.x).arg(midpoint.y);
2127 hoverPointValid = true;
2128 hoverPoint = midpoint;
2132 else if (obj->type == OTCircle)
2134 if ((draggingObject && (dragged->type == OTLine)) && (dragged->hitPoint[0] || dragged->hitPoint[1]))
2136 Point p = (dragged->hitPoint[0] ? dragged->p[1] : dragged->p[0]);
2137 Geometry::FindTangents(obj, p);
2139 if (Global::numIntersectPoints > 0)
2141 hoveringIntersection = true;
2142 intersectionPoint = Geometry::NearestTo(point, Global::intersectPoint[0], Global::intersectPoint[1]);
2145 else if ((Global::tool == TTLine) && (Global::toolState == TSPoint2))
2147 Geometry::FindTangents(obj, toolPoint[0]);
2149 if (Global::numIntersectPoints > 0)
2151 hoveringIntersection = true;
2152 intersectionPoint = Geometry::NearestTo(point, Global::intersectPoint[0], Global::intersectPoint[1]);
2158 // Handle object movement (left button down & over an object)
2159 if ((event->buttons() & Qt::LeftButton) && draggingObject && !Global::tool)
2161 if (hoveringIntersection)
2162 point = intersectionPoint;
2163 else if (hoverPointValid)
2165 else if (Global::snapToGrid)
2166 point = SnapPointToGrid(point);
2168 HandleObjectMovement(point);
2174 // Do tool handling, if any are active...
2177 if (hoveringIntersection)
2178 point = intersectionPoint;
2179 else if (hoverPointValid)
2181 else if (Global::snapToGrid)
2184 point = SnapPointToAngle(point);
2186 point = SnapPointToGrid(point);
2189 ToolHandler(ToolMouseMove, point);
2192 // This is used to draw the tool crosshair...
2195 if (needUpdate || Global::tool)
2199 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
2201 if (event->button() == Qt::LeftButton)
2203 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
2204 //could set it up to use the document's update function (assumes that all object
2205 //updates are being reported correctly:
2206 // if (document.NeedsUpdate())
2207 // Do an update if collided with at least *one* object in the document
2213 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
2214 ToolHandler(ToolMouseUp, point);
2218 Global::selectionInProgress = false;
2219 informativeText.clear();
2221 // Should we be doing this automagically? Hmm...
2222 // Clear our vectors
2226 // 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???])
2227 select = GetSelection();
2229 draggingObject = false;
2231 else if (event->button() == Qt::MiddleButton)
2235 if (Global::penStamp)
2236 setCursor(curMarker);
2237 else if (Global::penDropper)
2238 setCursor(curDropper);
2240 setCursor(Qt::ArrowCursor);
2242 // Need to convert this, since it's in Qt coordinates (for wheelEvent)
2243 oldPoint = Painter::QtToCartesianCoords(oldPoint);
2247 void DrawingView::wheelEvent(QWheelEvent * event)
2249 double zoomFactor = 1.20;
2250 scrollWheelSeen = true;
2252 if (event->angleDelta().y() < 0)
2254 if (Global::zoom > 400.0)
2257 Global::zoom *= zoomFactor;
2261 if (Global::zoom < 0.125)
2264 Global::zoom /= zoomFactor;
2267 Point np = Painter::QtToCartesianCoords(oldScrollPoint);
2268 Global::origin += (oldPoint - np);
2270 emit(NeedZoomUpdate());
2273 void DrawingView::keyPressEvent(QKeyEvent * event)
2275 bool oldShift = shiftDown;
2276 bool oldCtrl = ctrlDown;
2277 bool oldAlt = altDown;
2279 if (event->key() == Qt::Key_Shift)
2281 else if (event->key() == Qt::Key_Control)
2283 else if (event->key() == Qt::Key_Alt)
2286 // If there's a change in any of the modifier key states, pass it on to
2287 // the current tool's handler
2288 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
2291 ToolHandler(ToolKeyDown, Point(0, 0));
2296 if (oldAlt != altDown)
2299 setCursor(Qt::SizeAllCursor);
2300 oldPoint = oldScrollPoint;
2303 if (select.size() > 0)
2305 if (event->key() == Qt::Key_Up)
2307 TranslateObjects(select, Point(0, +1.0));
2310 else if (event->key() == Qt::Key_Down)
2312 TranslateObjects(select, Point(0, -1.0));
2315 else if (event->key() == Qt::Key_Right)
2317 TranslateObjects(select, Point(+1.0, 0));
2320 else if (event->key() == Qt::Key_Left)
2322 TranslateObjects(select, Point(-1.0, 0));
2328 void DrawingView::keyReleaseEvent(QKeyEvent * event)
2330 bool oldShift = shiftDown;
2331 bool oldCtrl = ctrlDown;
2332 bool oldAlt = altDown;
2334 if (event->key() == Qt::Key_Shift)
2336 else if (event->key() == Qt::Key_Control)
2338 else if (event->key() == Qt::Key_Alt)
2341 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
2344 ToolHandler(ToolKeyUp, Point(0, 0));
2349 if (oldAlt != altDown)
2353 if (Global::penStamp)
2354 setCursor(curMarker);
2355 else if (Global::penDropper)
2356 setCursor(curDropper);
2358 setCursor(Qt::ArrowCursor);
2363 // This looks strange, but it's really quite simple: We want a point that's
2364 // more than half-way to the next grid point to snap there while conversely we
2365 // want a point that's less than half-way to to the next grid point then snap
2366 // to the one before it. So we add half of the grid spacing to the point, then
2367 // divide by it so that we can remove the fractional part, then multiply it
2368 // back to get back to the correct answer.
2370 Point DrawingView::SnapPointToGrid(Point point)
2372 point += Global::gridSpacing / 2.0; // *This* adds to Z!!!
2373 point /= Global::gridSpacing;
2374 point.x = floor(point.x);//need to fix this for negative numbers...
2375 point.y = floor(point.y);
2376 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
2377 point *= Global::gridSpacing;
2381 Point DrawingView::SnapPointToAngle(Point point)
2383 // Snap to a single digit angle (using toolpoint #1 as the center)
2384 double angle = Vector::Angle(toolPoint[0], point);
2385 double length = Vector::Magnitude(toolPoint[0], point);
2387 // Convert from radians to degrees
2388 double degAngle = angle * RADIANS_TO_DEGREES;
2389 double snapAngle = (double)((int)(degAngle + 0.5));
2392 v.SetAngleAndLength(snapAngle * DEGREES_TO_RADIANS, length);
2393 point = toolPoint[0] + v;
2398 Rect DrawingView::GetObjectExtents(Object * obj)
2400 // Default to empty rect, if object checks below fail for some reason
2408 rect = Rect(obj->p[0], obj->p[1]);
2414 rect = Rect(obj->p[0], obj->p[0]);
2415 rect.Expand(obj->radius[0]);
2421 Arc * a = (Arc *)obj;
2423 double start = a->angle[0];
2424 double end = start + a->angle[1];
2425 rect = Rect(Point(cos(start), sin(start)), Point(cos(end), sin(end)));
2427 // If the end of the arc is before the beginning, add 360 degrees to it
2431 // Adjust the bounds depending on which axes are crossed
2432 if ((start < QTR_TAU) && (end > QTR_TAU))
2435 if ((start < HALF_TAU) && (end > HALF_TAU))
2438 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
2441 if ((start < TAU) && (end > TAU))
2444 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
2447 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
2450 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
2453 rect *= a->radius[0];
2454 rect.Translate(a->p[0]);
2460 Text * t = (Text *)obj;
2461 rect = Rect(t->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
2467 Container * c = (Container *)obj;
2468 VPVectorIter i = c->objects.begin();
2469 rect = GetObjectExtents((Object *)*i);
2472 for(; i!=c->objects.end(); i++)
2473 rect |= GetObjectExtents((Object *)*i);
2483 void DrawingView::CheckObjectBounds(void)
2487 for(i=document.objects.begin(); i!=document.objects.end(); i++)
2489 Object * obj = (Object *)(*i);
2490 obj->selected = false;
2495 case OTDimension: // N.B.: We don't check this properly...
2497 Line * l = (Line *)obj;
2499 if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
2507 Circle * c = (Circle *)obj;
2509 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]))
2517 Arc * a = (Arc *)obj;
2519 double start = a->angle[0];
2520 double end = start + a->angle[1];
2521 QPointF p1(cos(start), sin(start));
2522 QPointF p2(cos(end), sin(end));
2523 QRectF bounds(p1, p2);
2526 // Swap X/Y coordinates if they're backwards...
2527 if (bounds.left() > bounds.right())
2529 double temp = bounds.left();
2530 bounds.setLeft(bounds.right());
2531 bounds.setRight(temp);
2534 if (bounds.bottom() > bounds.top())
2536 double temp = bounds.bottom();
2537 bounds.setBottom(bounds.top());
2538 bounds.setTop(temp);
2541 // Doesn't work as advertised! For shame!
2542 bounds = bounds.normalized();
2545 // If the end of the arc is before the beginning, add 360 degrees
2550 // Adjust the bounds depending on which axes are crossed
2551 if ((start < QTR_TAU) && (end > QTR_TAU))
2554 if ((start < HALF_TAU) && (end > HALF_TAU))
2555 bounds.setLeft(-1.0);
2557 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
2558 bounds.setBottom(-1.0);
2560 if ((start < TAU) && (end > TAU))
2561 bounds.setRight(1.0);
2563 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
2566 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
2567 bounds.setLeft(-1.0);
2569 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
2570 bounds.setBottom(-1.0);
2572 bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
2573 bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
2574 bounds.translate(a->p[0].x, a->p[0].y);
2576 if (Global::selection.contains(bounds))
2584 Text * t = (Text *)obj;
2585 Rect r(obj->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
2587 if (Global::selection.contains(r.l, r.t) && Global::selection.contains(r.r, r.b))
2595 Container * c = (Container *)obj;
2597 if (Global::selection.contains(c->p[0].x, c->p[0].y) && Global::selection.contains(c->p[1].x, c->p[1].y))
2609 bool DrawingView::HitTestObjects(Point point)
2613 bool needUpdate = false;
2614 hoverPointValid = false;
2616 for(i=document.objects.begin(); i!=document.objects.end(); i++)
2618 Object * obj = (Object *)(*i);
2620 // If we're seeing the object we're dragging, skip it
2621 if (draggingObject && (obj == dragged))
2624 if (HitTest(obj, point) == true)
2630 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
2631 emit ObjectHovered(obj);
2638 bool DrawingView::HitTest(Object * obj, Point point)
2640 bool needUpdate = false;
2642 // Make sure we don't hit test stuff on an invisible layer...
2643 if (Global::layerHidden[obj->layer] == true)
2650 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
2651 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
2652 Vector lineSegment = obj->p[1] - obj->p[0];
2653 Vector v1 = point - obj->p[0];
2654 Vector v2 = point - obj->p[1];
2655 double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
2659 distance = v1.Magnitude();
2661 distance = v2.Magnitude();
2663 // distance = ?Det?(ls, v1) / |ls|
2664 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
2665 / lineSegment.Magnitude());
2667 if ((v1.Magnitude() * Global::zoom) < 8.0)
2669 obj->hitPoint[0] = true;
2670 hoverPoint = obj->p[0];
2671 hoverPointValid = true;
2673 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2675 obj->hitPoint[1] = true;
2676 hoverPoint = obj->p[1];
2677 hoverPointValid = true;
2679 else if ((distance * Global::zoom) < 5.0)
2680 obj->hitObject = true;
2682 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
2684 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
2692 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
2693 obj->hitPoint[0] = obj->hitObject = false;
2694 double length = Vector::Magnitude(obj->p[0], point);
2696 if ((length * Global::zoom) < 8.0)
2698 obj->hitPoint[0] = true;
2699 hoverPoint = obj->p[0];
2700 hoverPointValid = true;
2702 else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
2703 obj->hitObject = true;
2705 obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
2707 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
2715 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
2716 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
2717 double length = Vector::Magnitude(obj->p[0], point);
2718 double angle = Vector::Angle(obj->p[0], point);
2720 // Make sure we get the angle in the correct spot
2721 if (angle < obj->angle[0])
2724 // Get the span that we're pointing at...
2725 double span = angle - obj->angle[0];
2727 // N.B.: Still need to hit test the arc start & arc span handles...
2728 double spanAngle = obj->angle[0] + obj->angle[1];
2729 Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
2730 Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
2731 double length2 = Vector::Magnitude(point, handle1);
2732 double length3 = Vector::Magnitude(point, handle2);
2734 if ((length * Global::zoom) < 8.0)
2736 obj->hitPoint[0] = true;
2737 hoverPoint = obj->p[0];
2738 hoverPointValid = true;
2740 else if ((length2 * Global::zoom) < 8.0)
2742 obj->hitPoint[1] = true;
2743 hoverPoint = handle1;
2744 hoverPointValid = true;
2746 else if ((length3 * Global::zoom) < 8.0)
2748 obj->hitPoint[2] = true;
2749 hoverPoint = handle2;
2750 hoverPointValid = true;
2752 else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
2753 obj->hitObject = true;
2755 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
2757 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
2765 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHP3 = obj->hitPoint[3], oldHP4 = obj->hitPoint[4], oldHO = obj->hitObject;
2766 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitPoint[3] = obj->hitPoint[4] = obj->hitObject = false;
2768 Dimension * d = (Dimension *)obj;
2770 Vector orthogonal = Vector::Normal(d->lp[0], d->lp[1]);
2771 // Get our line parallel to our points
2772 float scaledThickness = Global::scale * obj->thickness;
2774 Point p1 = d->lp[0] + (orthogonal * (d->offset + (10.0 * scaledThickness)));
2775 Point p2 = d->lp[1] + (orthogonal * (d->offset + (10.0 * scaledThickness)));
2777 Point p1 = d->lp[0] + (orthogonal * (10.0 + d->offset) * scaledThickness);
2778 Point p2 = d->lp[1] + (orthogonal * (10.0 + d->offset) * scaledThickness);
2780 Point p3(p1, point);
2782 Vector v1(d->p[0], point);
2783 Vector v2(d->p[1], point);
2784 Vector lineSegment(p1, p2);
2785 double t = Geometry::ParameterOfLineAndPoint(p1, p2, point);
2787 Point midpoint = (p1 + p2) / 2.0;
2788 Point hFSPoint = Point(midpoint, point);
2789 Point hCS1Point = Point((p1 + midpoint) / 2.0, point);
2790 Point hCS2Point = Point((midpoint + p2) / 2.0, point);
2793 distance = v1.Magnitude();
2795 distance = v2.Magnitude();
2797 // distance = ?Det?(ls, v1) / |ls|
2798 distance = fabs((lineSegment.x * p3.y - p3.x * lineSegment.y)
2799 / lineSegment.Magnitude());
2801 if ((v1.Magnitude() * Global::zoom) < 8.0)
2802 obj->hitPoint[0] = true;
2803 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2804 obj->hitPoint[1] = true;
2805 else if ((distance * Global::zoom) < 5.0)
2806 obj->hitObject = true;
2808 if ((hFSPoint.Magnitude() * Global::zoom) < 8.0)
2809 obj->hitPoint[2] = true;
2810 else if ((hCS1Point.Magnitude() * Global::zoom) < 8.0)
2811 obj->hitPoint[3] = true;
2812 else if ((hCS2Point.Magnitude() * Global::zoom) < 8.0)
2813 obj->hitPoint[4] = true;
2815 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitPoint[3] || obj->hitPoint[4] || obj->hitObject ? true : false);
2817 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHP3 != obj->hitPoint[3]) || (oldHP4 != obj->hitPoint[4]) || (oldHO != obj->hitObject))
2825 Text * t = (Text *)obj;
2826 bool oldHO = obj->hitObject;
2827 obj->hitObject = false;
2829 Rect r(obj->p[0], Point(obj->p[0].x + t->extents.Width(), obj->p[0].y - t->extents.Height()));
2830 //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);
2832 if (r.Contains(point))
2833 obj->hitObject = true;
2835 obj->hovered = (obj->hitObject ? true : false);
2837 if (oldHO != obj->hitObject)
2845 // Containers must be recursively tested... Or do they???
2847 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.
2849 // bool oldHitObj = c->hitObject, oldHovered = c->hovered;
2850 // Object * oldClicked = c->clicked;
2852 still need to compare old state to new state, and set things up based upon that...
2853 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);
2854 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.
2856 Container * c = (Container *)obj;
2857 c->hitObject = false;
2861 VPVector flat = Flatten(c);
2863 //printf("HitTest::OTContainer (size=%li)\n", flat.size());
2864 for(VPVectorIter i=flat.begin(); i!=flat.end(); i++)
2866 Object * cObj = (Object *)(*i);
2868 // Skip the flattened containers (if any)...
2869 if (cObj->type == OTContainer)
2872 // We do it this way instead of needUpdate = HitTest() because we
2873 // are checking more than one object, and that way of doing will
2874 // not return consistent results.
2875 if (HitTest(cObj, point) == true)
2877 //printf("HitTest::OTContainer, subobj ($%llX) hit!\n", cObj);
2879 // c->hitObject = true;
2880 // c->clicked = cObj;
2881 // c->hovered = true;
2884 // Same reasons for doing it this way here apply.
2885 if (cObj->hitObject == true)
2887 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2888 c->hitObject = true;
2892 if (cObj->hitPoint[0] == true)
2894 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2895 c->hitPoint[0] = true;
2899 if (cObj->hitPoint[1] == true)
2901 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2902 c->hitPoint[1] = true;
2906 if (cObj->hitPoint[2] == true)
2908 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2909 c->hitPoint[2] = true;
2913 if (cObj->hovered == true)
2914 c->hovered = true;//*/
2927 bool DrawingView::HandleObjectClicked(void)
2929 if (dragged->type == OTDimension)
2931 Dimension * d = (Dimension *)dragged;
2935 // Hit the "flip sides" switch, so flip 'em
2936 Point temp = d->p[0];
2941 else if (d->hitPoint[3])
2943 // There are three cases here: aligned, horizontal, & vertical.
2944 // Aligned and horizontal do the same thing, vertical goes back to
2946 if (d->subtype == DTLinearVert)
2947 d->subtype = DTLinear;
2949 d->subtype = DTLinearVert;
2953 else if (d->hitPoint[4])
2955 // There are three cases here: aligned, horizontal, & vertical.
2956 // Aligned and vertical do the same thing, horizontal goes back to
2958 if (d->subtype == DTLinearHorz)
2959 d->subtype = DTLinear;
2961 d->subtype = DTLinearHorz;
2970 void DrawingView::HandleObjectMovement(Point point)
2972 Point delta = point - oldPoint;
2973 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
2974 // Object * obj = (Object *)hover[0];
2975 Object * obj = dragged;
2976 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
2977 //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"));
2982 if (obj->hitPoint[0])
2985 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
2987 if (Global::fixedLength)
2989 Vector unit = Vector::Unit(obj->p[1], point);
2990 point = obj->p[1] + (unit * obj->length);
2993 if (Global::fixedAngle)
2995 // Calculate the component of the current vector along the
2996 // fixed angle: A_compB = (A • Bu) * Bu (p[2] has the unit
2998 double magnitudeAlongB = Vector::Dot(Vector(point - obj->p[1]), obj->p[2]);
2999 point = obj->p[1] + (obj->p[2] * magnitudeAlongB);
3004 else if (obj->hitPoint[1])
3006 if (Global::fixedLength)
3008 Vector unit = Vector::Unit(obj->p[0], point);
3009 point = obj->p[0] + (unit * obj->length);
3012 if (Global::fixedAngle)
3014 double magnitudeAlongB = Vector::Dot(Vector(point - obj->p[0]), obj->p[2]);
3015 point = obj->p[0] + (obj->p[2] * magnitudeAlongB);
3020 else if (obj->hitObject)
3029 if (obj->hitPoint[0])
3031 else if (obj->hitObject)
3033 double oldRadius = obj->length;
3034 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
3036 QString text = QObject::tr("Radius: %1\nScale: %2%");
3037 informativeText = text.arg(obj->radius[0], 0, 'd', 4).arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0);
3043 if (obj->hitPoint[0])
3045 else if (obj->hitPoint[1])
3047 // Change the Arc's span (handle #1)
3050 double angle = Vector::Angle(obj->p[0], point);
3051 double delta = angle - obj->angle[0];
3056 obj->angle[1] -= delta;
3057 obj->angle[0] = angle;
3059 if (obj->angle[1] < 0)
3060 obj->angle[1] += TAU;
3062 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
3063 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);
3067 double angle = Vector::Angle(obj->p[0], point);
3068 obj->angle[0] = angle;
3069 QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
3070 informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 4);
3072 else if (obj->hitPoint[2])
3074 // Change the Arc's span (handle #2)
3077 double angle = Vector::Angle(obj->p[0], point);
3078 obj->angle[1] = angle - obj->angle[0];
3080 if (obj->angle[1] < 0)
3081 obj->angle[1] += TAU;
3083 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
3084 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);
3088 double angle = Vector::Angle(obj->p[0], point);
3089 obj->angle[0] = angle - obj->angle[1];
3091 if (obj->angle[0] < 0)
3092 obj->angle[0] += TAU;
3094 QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
3095 informativeText = text.arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 4);
3097 else if (obj->hitObject)
3104 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
3105 QString text = QObject::tr("Radius: %1");
3106 informativeText = text.arg(obj->radius[0], 0, 'd', 4);
3112 if (obj->hitPoint[0])
3114 else if (obj->hitPoint[1])
3116 else if (obj->hitObject)
3118 // Move measurement lines in/out
3121 Dimension * d = (Dimension *)obj;
3122 double dist = Geometry::DistanceToLineFromPoint(d->lp[0], d->lp[1], point);
3123 float scaledThickness = Global::scale * obj->thickness;
3124 // Looks like offset is 0 to +MAX, but line is at 10.0. So
3125 // anything less than 10.0 should set the offset to 0.
3128 if (dist > (10.0 * scaledThickness))
3129 d->offset = dist - (10.0 * scaledThickness);
3147 // This is shitty, but works for now until I can code up something
3150 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.
3152 // TranslateObject(obj, delta);
3153 TranslateContainer((Container *)obj, point, delta);
3161 void DrawingView::AddDimensionTo(void * o)
3163 Object * obj = (Object *)o;
3168 document.Add(new Dimension(obj->p[0], obj->p[1]));