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"
47 #define BACKGROUND_MAX_SIZE 512
49 DrawingView::DrawingView(QWidget * parent/*= NULL*/): QWidget(parent),
50 // The value in the settings file will override this.
51 useAntialiasing(true), numHovered(0), shiftDown(false),
52 ctrlDown(false), altDown(false),
53 gridBackground(BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE),
54 scale(1.0), offsetX(-10), offsetY(-10), supressSelected(false),
56 gridPixels(0), collided(false), scrollDrag(false),
57 hoverPointValid(false), hoveringIntersection(false),
58 dragged(NULL), draggingObject(false),
59 angleSnap(false), dirty(false)
61 //wtf? doesn't work except in c++11??? document = { 0 };
62 setBackgroundRole(QPalette::Base);
63 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
65 curMarker = QCursor(QPixmap(":/res/cursor-marker.png"), 1, 18);
66 curDropper = QCursor(QPixmap(":/res/cursor-dropper.png"), 1, 20);
68 Global::gridSpacing = 12.0; // In base units (inch is default)
70 Line * line = new Line(Vector(5, 5), Vector(50, 40), 2.0, 0xFF7F00, LSDash);
72 document.Add(new Line(Vector(50, 40), Vector(10, 83)));
73 document.Add(new Line(Vector(10, 83), Vector(17, 2)));
74 document.Add(new Circle(Vector(100, 100), 36));
75 document.Add(new Circle(Vector(50, 150), 49));
76 document.Add(new Arc(Vector(300, 300), 32, TAU / 8.0, TAU * 0.65)),
77 document.Add(new Arc(Vector(200, 200), 60, TAU / 4.0, TAU * 0.75));
78 document.Add(new Text(Vector(10, 83), "Here is some awesome text!"));
83 Here we set the grid size in pixels--12 in this case. Initially, we have our
84 zoom set to make this represent 12 inches at a zoom factor of 25%. (This is
85 arbitrary.) So, to be able to decouple the grid size from the zoom, we need
86 to be able to set the size of the background grid (which we do here at an
87 arbitrary 12 pixels) to anything we want (within reason, of course :-).
89 The drawing enforces the grid spacing through the drawing->gridSpacing variable.
91 drawing->gridSpacing = 12.0 / Global::zoom;
93 Global::zoom is the zoom factor for the drawing, and all mouse clicks are
94 translated to Cartesian coordinates through this. (Initially, Global::zoom is
95 set to 1.0. SCREEN_ZOOM is set to 1.0/4.0.)
97 Really, the 100% zoom level can be set at *any* zoom level, it's more of a
98 convenience function than any measure of absolutes. Doing things that way we
99 could rid ourselves of the whole SCREEN_ZOOM parameter and all the attendant
100 shittiness that comes with it.
102 However, it seems that SCREEN_ZOOM is used to make text and arrow sizes show up
103 a certain way, which means we should probably create something else in those
104 objects to take its place--like some kind of scale factor. This would seem to
105 imply that certain point sizes actually *do* tie things like fonts to absolute
106 sizes on the screen, but not necessarily because you could have an inch scale
107 with text that is quite small relative to other objects on the screen, which
108 currently you have to zoom in to see (and which blows up the text). Point sizes
109 in an application like this are a bit meaningless; even though an inch is an
110 inch regardless of the zoom level a piece of text can be larger or smaller than
111 this. Maybe this is the case for having a base unit and basing point sizes off
114 Here's what's been figured out. Global::zoom is simply the ratio of pixels to
115 base units. What that means is that if you have a 12px grid with a 6" grid size
116 (& base unit of "inches"), Global::zoom becomes 12px / 6" = 2.0 px/in.
118 Dimensions now have a "size" parameter to set their absolute size in relation
119 to the base unit. ATM, the arrows are drawn in pixels, but also scaled by
120 Global::zoom *and* size. Same with the dimension text; it's drawn at 10pt and
121 scaled the same way as the arrowheads.
123 Need a way to scale line widths as well. :-/ Shouldn't be too difficult, just
124 need a thickness parameter similar to the "size" param for dimensions. (And now
129 void DrawingView::DrawBackground(Painter * painter)
131 Point ul = Painter::QtToCartesianCoords(Vector(0, 0));
132 Point br = Painter::QtToCartesianCoords(Vector(Global::screenSize.x, Global::screenSize.y));
134 painter->SetBrush(0xF0F0F0);
135 painter->SetPen(0xF0F0F0, 1, 1);
136 painter->DrawRect(Rect(ul, br));
138 double spacing = Global::gridSpacing;
143 double leftx = floor(ul.x / spacing) * spacing;
144 double bottomy = floor(br.y / spacing) * spacing;
146 double w = (br.x - ul.x) + Global::gridSpacing + 1.0;
147 double h = (ul.y - br.y) + Global::gridSpacing + 1.0;
149 Vector start(leftx, bottomy), size(w, h);
151 if (Global::gridSpacing <= 0.015625)
152 DrawSubGrid(painter, 0xFFD2D2, 0.015625, start, size);
154 if (Global::gridSpacing <= 0.03125)
155 DrawSubGrid(painter, 0xFFD2D2, 0.03125, start, size);
157 if (Global::gridSpacing <= 0.0625)
158 DrawSubGrid(painter, 0xB8ECFF, 0.0625, start, size);
160 if (Global::gridSpacing <= 0.125)
161 DrawSubGrid(painter, 0xB8ECFF, 0.125, start, size);
163 if (Global::gridSpacing <= 0.25)
164 DrawSubGrid(painter, 0xE6E6FF, 0.25, start, size);
166 if (Global::gridSpacing <= 0.5)
167 DrawSubGrid(painter, 0xE6E6FF, 0.5, start, size);
169 painter->SetPen(QPen(QColor(0xE0, 0xE0, 0xFF), 2.0, Qt::SolidLine));
171 for(double i=0; i<=w; i+=spacing)
172 painter->DrawVLine(leftx + i);
174 for(double i=0; i<=h; i+=spacing)
175 painter->DrawHLine(bottomy + i);
178 void DrawingView::DrawSubGrid(Painter * painter, uint32_t color, double step, Vector start, Vector size)
180 painter->SetPen(color, 1, 1);
182 for(double i=-step; i<=size.x; i+=step*2.0)
183 painter->DrawVLine(start.x + i);
185 for(double i=-step; i<=size.y; i+=step*2.0)
186 painter->DrawHLine(start.y + i);
190 // Basically, we just make a single pass through the Container. If the layer #
191 // is less than the layer # being deleted, then do nothing. If the layer # is
192 // equal to the layer # being deleted, then delete the object. If the layer #
193 // is greater than the layer # being deleted, then set the layer # to its layer
196 void DrawingView::DeleteCurrentLayer(int layer)
198 VPVectorIter i = document.objects.begin();
200 while (i != document.objects.end())
202 Object * obj = (Object *)(*i);
204 if (obj->layer < layer)
206 else if (obj->layer == layer)
208 document.objects.erase(i);
218 // We've just done a destructive action, so update the screen!
222 void DrawingView::HandleLayerToggle(void)
224 // A layer's visibility was toggled, so update the screen...
229 // A layer was moved up or down in the layer list, so we have to swap the
230 // document's object's layer numbers in the layers that were swapped.
232 void DrawingView::HandleLayerSwap(int layer1, int layer2)
234 HandleLayerSwap(layer1, layer2, document.objects);
238 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
240 void DrawingView::HandleLayerSwap(int layer1, int layer2, VPVector & v)
242 for(VPVectorIter i=v.begin(); i!=v.end(); i++)
244 Object * obj = (Object *)(*i);
246 if (obj->layer == layer1)
248 else if (obj->layer == layer2)
251 if (obj->type == OTContainer)
252 HandleLayerSwap(layer1, layer2, ((Container *)obj)->objects);
256 void DrawingView::HandlePenStamp(QAction * action)
258 PenWidget * pw = (PenWidget *)action->parentWidget();
259 pw->dropperAction->setChecked(false);
260 Global::penDropper = false;
261 Global::penStamp = action->isChecked();
263 if (Global::penStamp)
264 setCursor(curMarker);
266 setCursor(Qt::ArrowCursor);
268 if (Global::penStamp == false)
269 ClearSelected(document.objects);
274 void DrawingView::HandlePenDropper(QAction * action)
276 PenWidget * pw = (PenWidget *)action->parentWidget();
277 pw->stampAction->setChecked(false);
278 Global::penStamp = false;
279 Global::penDropper = action->isChecked();
281 if (Global::penDropper)
282 setCursor(curDropper);
284 setCursor(Qt::ArrowCursor);
289 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
291 // This is undoing the transform, e.g. going from client coords to local
292 // coords. In essence, the height - y is height + (y * -1), the (y * -1)
293 // term doing the conversion of the y-axis from increasing bottom to top.
294 return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
297 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
299 // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
300 // No voodoo here, it's just grouped wrong to see it. It should be:
301 // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive [why? we use -offsetX after all]
302 return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
305 void DrawingView::focusOutEvent(QFocusEvent * /*event*/)
307 // Make sure all modkeys being held are marked as released when the app
308 // loses focus (N.B.: This only works because the app sets the focus policy
309 // of this object to something other than Qt::NoFocus)
310 shiftDown = ctrlDown = altDown = false;
312 setCursor(Qt::ArrowCursor);
315 void DrawingView::focusInEvent(QFocusEvent * /*event*/)
317 if (Global::penStamp)
318 setCursor(curMarker);
319 else if (Global::penDropper)
320 setCursor(curDropper);
323 void DrawingView::paintEvent(QPaintEvent * /*event*/)
325 QPainter qtPainter(this);
326 Painter painter(&qtPainter);
329 qtPainter.setRenderHint(QPainter::Antialiasing);
331 Global::viewportHeight = size().height();
333 DrawBackground(&painter);
335 // Draw coordinate axes
336 painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
337 painter.DrawLine(0, -16384, 0, 16384);
338 painter.DrawLine(-16384, 0, 16384, 0);
340 // Do object rendering...
341 for(int i=0; i<Global::numLayers; i++)
343 if (Global::layerHidden[i] == false)
344 RenderObjects(&painter, document.objects, i);
347 // Do tool rendering, if any...
350 if (Global::toolSuppressCrosshair == false)
352 painter.SetPen(QPen(QColor(200, 100, 0, 255), 1.0, Qt::DashLine));
353 painter.DrawCrosshair(oldPoint);
359 // Do selection rectangle rendering, if any
360 if (Global::selectionInProgress)
362 painter.SetPen(QPen(QColor(0xFF, 0x7F, 0x00, 0xFF)));
363 painter.SetBrush(QBrush(QColor(0xFF, 0x7F, 0x00, 0x64)));
364 painter.DrawRect(Global::selection);
367 if (hoveringIntersection)
368 painter.DrawHandle(intersectionPoint);
371 painter.DrawHandle(hoverPoint);
373 if (!informativeText.isEmpty())
374 painter.DrawInformativeText(informativeText);
378 // Renders objects in the passed in vector
381 N.B.: Since we have "hoverPointValid" drawing regular object handles above,
382 we can probably do away with a lot of them that are being done down below.
384 [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...]
386 void DrawingView::RenderObjects(Painter * painter, VPVector & v, int layer, bool ignoreLayer/*= false*/)
388 for(VPVectorIter i=v.begin(); i!=v.end(); i++)
390 Object * obj = (Object *)(*i);
391 float scaledThickness = Global::scale * obj->thickness;
393 // If the object isn't on the current layer being drawn, skip it
394 if (!ignoreLayer && (obj->layer != layer))
397 if ((Global::tool == TTRotate) && ctrlDown && obj->selected)
399 painter->SetPen(0x00FF00, 2.0, LSSolid);
403 painter->SetPen(obj->color, Global::zoom * scaledThickness, obj->style);
404 painter->SetBrush(obj->color);
406 // penStamp supresses object highlighting, so that changes can be seen.
407 if (supressSelected || Global::penStamp)
411 painter->SetPen(Global::penColor, Global::zoom * Global::scale * Global::penWidth, Global::penStyle);
412 painter->SetBrush(Global::penColor);
415 else if (obj->selected || obj->hitObject)
416 painter->SetPen(0xFF0000, Global::zoom * scaledThickness, LSDash);
422 painter->DrawLine(obj->p[0], obj->p[1]);
424 if (obj->hitPoint[0])
425 painter->DrawHandle(obj->p[0]);
427 if (obj->hitPoint[1])
428 painter->DrawHandle(obj->p[1]);
431 painter->DrawSmallHandle(Geometry::Midpoint((Line *)obj));
436 painter->SetBrush(QBrush(Qt::NoBrush));
437 painter->DrawEllipse(obj->p[0], obj->radius[0], obj->radius[0]);
439 if (obj->hitPoint[0])
440 painter->DrawHandle(obj->p[0]);
445 painter->DrawArc(obj->p[0], obj->radius[0], obj->angle[0], obj->angle[1]);
447 if (obj->hitPoint[0])
448 painter->DrawHandle(obj->p[0]);
450 if (obj->hitPoint[1])
451 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]));
453 if (obj->hitPoint[2])
454 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0] + obj->angle[1]), sin(obj->angle[0] + obj->angle[1])) * obj->radius[0]));
460 painter->SetBrush(QBrush(Qt::NoBrush));
461 Polyline * pl = (Polyline *)obj;
463 for(long unsigned int i=0; i<(pl->points.size()-1); i++)
465 Point p1 = pl->points[i];
466 Point p2 = pl->points[i + 1];
469 painter->DrawLine(p1, p2);
472 Arc a = Geometry::Unpack(p1, p2, p1.b);
473 painter->DrawArc(a.p[0], a.radius[0], a.angle[0], a.angle[1]);
482 Dimension * d = (Dimension *)obj;
484 Vector v(d->p[0], d->p[1]);
485 double angle = v.Angle();
486 Vector unit = v.Unit();
487 d->lp[0] = d->p[0], d->lp[1] = d->p[1];
489 double x1, y1, length;
491 if (d->subtype == DTLinearVert)
493 if ((angle < 0) || (angle > HALF_TAU))
495 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
496 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
497 ortho = Vector(1.0, 0);
498 angle = THREE_QTR_TAU;
502 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
503 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
504 ortho = Vector(-1.0, 0);
508 d->lp[0].x = d->lp[1].x = x1;
509 length = fabs(d->p[0].y - d->p[1].y);
511 else if (d->subtype == DTLinearHorz)
513 if ((angle < QTR_TAU) || (angle > THREE_QTR_TAU))
515 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
516 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
517 ortho = Vector(0, 1.0);
522 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
523 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
524 ortho = Vector(0, -1.0);
528 d->lp[0].y = d->lp[1].y = y1;
529 length = fabs(d->p[0].x - d->p[1].x);
531 else if (d->subtype == DTLinear)
533 angle = Vector(d->lp[0], d->lp[1]).Angle();
534 ortho = Vector::Normal(d->lp[0], d->lp[1]);
535 length = v.Magnitude();
538 unit = Vector(d->lp[0], d->lp[1]).Unit();
540 Point p1 = d->lp[0] + (ortho * (d->offset + (10.0 * scaledThickness)));
541 Point p2 = d->lp[1] + (ortho * (d->offset + (10.0 * scaledThickness)));
542 Point p3 = d->lp[0] + (ortho * (d->offset + (16.0 * scaledThickness)));
543 Point p4 = d->lp[1] + (ortho * (d->offset + (16.0 * scaledThickness)));
544 Point p5 = d->p[0] + (ortho * 4.0 * scaledThickness);
545 Point p6 = d->p[1] + (ortho * 4.0 * scaledThickness);
548 The numbers hardcoded into here, what are they?
549 I believe they are pixels.
551 // Draw extension lines (if certain type)
552 painter->DrawLine(p3, p5);
553 painter->DrawLine(p4, p6);
555 // Calculate whether or not the arrowheads are too crowded to put
556 // inside the extension lines. 9.0 is the length of the arrowhead.
557 double t = Geometry::ParameterOfLineAndPoint(d->lp[0], d->lp[1], d->lp[1] - (unit * 9.0 * scaledThickness));
559 // On the screen, it's acting like this is actually 58%...
560 // This is correct, we want it to happen at > 50%
563 // Draw main dimension line + arrowheads
564 painter->DrawLine(p1, p2);
565 painter->DrawArrowhead(p1, p2, scaledThickness);
566 painter->DrawArrowhead(p2, p1, scaledThickness);
570 // Draw outside arrowheads
571 Point p7 = p1 - (unit * 9.0 * scaledThickness);
572 Point p8 = p2 + (unit * 9.0 * scaledThickness);
573 painter->DrawArrowhead(p1, p7, scaledThickness);
574 painter->DrawArrowhead(p2, p8, scaledThickness);
575 painter->DrawLine(p1, p1 - (unit * 14.0 * scaledThickness));
576 painter->DrawLine(p2, p2 + (unit * 14.0 * scaledThickness));
579 // Draw length of dimension line...
580 painter->SetFont(QFont("Arial", 8.0 * Global::zoom * scaledThickness));
581 Point ctr = p2 + (Vector(p2, p1) / 2.0);
583 QString dimText = GetDimensionText(&document, length);
586 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().
588 painter->DrawAngledText(ctr, angle, dimText, scaledThickness);
592 Point hp1 = (p1 + p2) / 2.0;
593 Point hp2 = (p1 + hp1) / 2.0;
594 Point hp3 = (hp1 + p2) / 2.0;
598 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
599 painter->SetBrush(QBrush(QColor(Qt::magenta)));
600 painter->DrawArrowHandle(hp1, ortho.Angle() + HALF_TAU);
601 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
604 painter->DrawHandle(hp1);
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(hp2, (d->subtype == DTLinearVert ? v.Angle() - QTR_TAU : (v.Angle() < HALF_TAU ? HALF_TAU : 0)));
612 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
615 painter->DrawHandle(hp2);
616 painter->SetPen(QPen(Qt::blue, 1.0 * Global::zoom * scaledThickness, Qt::SolidLine));
620 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
621 painter->SetBrush(QBrush(QColor(Qt::magenta)));
622 painter->DrawArrowToLineHandle(hp3, (d->subtype == DTLinearHorz ? v.Angle() - QTR_TAU : (v.Angle() > HALF_TAU && v.Angle() < THREE_QTR_TAU ? THREE_QTR_TAU : QTR_TAU)));
623 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
626 painter->DrawHandle(hp3);
629 if (obj->hitPoint[0])
630 painter->DrawHandle(obj->p[0]);
632 if (obj->hitPoint[1])
633 painter->DrawHandle(obj->p[1]);
640 Text * t = (Text *)obj;
642 if (t->measured == false)
644 t->extents = painter->MeasureTextObject(t->s.c_str(), scaledThickness);
648 painter->DrawTextObject(t->p[0], t->s.c_str(), scaledThickness, t->angle[0]);
666 // Containers require recursive rendering...
667 Container * c = (Container *)obj;
668 //printf("About to render container: # objs=%i, layer=%i\n", (*c).objects.size(), layer);
669 RenderObjects(painter, (*c).objects, layer, ignoreLayer);
671 //printf("Container extents: <%lf, %lf>, <%lf, %lf>\nsize: %i\n", r.l, r.t, r.r, r.b, c->objects.size());
672 // Containers also have special indicators showing they are selected
673 if (c->selected || c->hitObject)
675 // Rect r = GetObjectExtents(obj);
676 // painter->DrawRectCorners(r);
677 painter->DrawRectCorners(Rect(c->p[0], c->p[1]));
688 supressSelected = false;
692 // This toggles the selection being hovered (typically, only 1 object). We
693 // toggle because the CTRL key might be held, in which case, we want to
694 // deselect a selected object.
696 void DrawingView::HandleSelectionClick(VPVector & v)
700 for(VPVectorIter i=v.begin(); i!=v.end(); i++)
702 Object * obj = (Object *)(*i);
705 obj->selected = !obj->selected;
711 for(VPVectorIter i=v.begin(); i!=v.end(); i++)
712 ((Object *)(*i))->selected = false;
714 // Check if the hover changed, and if so, reset the selection stack
715 if (oldHover.size() != v.size())
722 // Select next object in the stack under the cursor
725 if (currentSelect >= v.size())
729 dragged = (Object *)v[currentSelect];
730 dragged->selected = true;
733 VPVector DrawingView::GetSelection(void)
737 for(VPVectorIter i=document.objects.begin(); i!=document.objects.end(); i++)
739 if (((Object *)(*i))->selected)
747 // When testing for hovered intersections, we need to be able to exclude some
748 // objects which have funky characteristics or handles; so we allow for that
751 VPVector DrawingView::GetHovered(bool exclude/*= false*/)
755 for(VPVectorIter i=document.objects.begin(); i!=document.objects.end(); i++)
757 Object * obj = (Object *)(*i);
762 && ((obj->type == OTDimension)
763 || ((obj->type == OTCircle) && (obj->hitPoint[0] == true))
764 || ((obj->type == OTArc) && (obj->hitPoint[0] == true))
765 || (draggingObject && (obj == dragged))))
775 void DrawingView::MoveSelectedToLayer(int layer)
777 for(VPVectorIter i=document.objects.begin(); i!=document.objects.end(); i++)
779 Object * obj = (Object *)(*i);
781 if (obj->selected || obj->hovered)
786 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
788 Global::screenSize = Vector(size().width(), size().height());
791 void DrawingView::ToolHandler(int mode, Point p)
793 // Drop angle snap until it's needed
796 if (Global::tool == TTLine)
797 LineHandler(mode, p);
798 else if (Global::tool == TTCircle)
799 CircleHandler(mode, p);
800 else if (Global::tool == TTArc)
802 else if (Global::tool == TTRotate)
803 RotateHandler(mode, p);
804 else if (Global::tool == TTMirror)
805 MirrorHandler(mode, p);
806 else if (Global::tool == TTDimension)
807 DimensionHandler(mode, p);
808 else if (Global::tool == TTDelete)
809 DeleteHandler(mode, p);
810 else if (Global::tool == TTTriangulate)
811 TriangulateHandler(mode, p);
812 else if (Global::tool == TTTrim)
813 TrimHandler(mode, p);
814 else if (Global::tool == TTParallel)
815 ParallelHandler(mode, p);
818 void DrawingView::ToolDraw(Painter * painter)
820 switch (Global::tool)
823 if (Global::toolState == TSNone)
825 painter->DrawHandle(toolPoint[0]);
827 else if ((Global::toolState == TSPoint2) && shiftDown)
829 painter->DrawHandle(toolPoint[1]);
833 painter->DrawLine(toolPoint[0], toolPoint[1]);
834 painter->DrawHandle(toolPoint[1]);
836 Vector v(toolPoint[0], toolPoint[1]);
837 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
838 double absLength = v.Magnitude();
839 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
840 informativeText = text.arg(absLength).arg(absAngle);
846 if (Global::toolState == TSNone)
848 painter->DrawHandle(toolPoint[0]);
850 else if ((Global::toolState == TSPoint2) && shiftDown)
852 painter->DrawHandle(toolPoint[1]);
856 painter->DrawCross(toolPoint[0]);
857 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
858 painter->SetBrush(QBrush(Qt::NoBrush));
859 painter->DrawEllipse(toolPoint[0], length, length);
860 QString text = tr("Radius: %1 in.");
861 informativeText = text.arg(length);
867 if (Global::toolState == TSNone)
869 painter->DrawHandle(toolPoint[0]);
871 else if (Global::toolState == TSPoint2)
873 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
874 painter->SetBrush(QBrush(Qt::NoBrush));
875 painter->DrawEllipse(toolPoint[0], length, length);
876 painter->DrawLine(toolPoint[0], toolPoint[1]);
877 painter->DrawHandle(toolPoint[1]);
878 QString text = tr("Radius: %1 in.");
879 informativeText = text.arg(length);
881 else if (Global::toolState == TSPoint3)
883 double angle = Vector::Angle(toolPoint[0], toolPoint[2]);
884 painter->DrawLine(toolPoint[0], toolPoint[2]);
885 painter->SetBrush(QBrush(Qt::NoBrush));
886 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
887 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
888 QString text = tr("Angle start: %1") + QChar(0x00B0);
889 informativeText = text.arg(RADIANS_TO_DEGREES * angle);
893 double angle = Vector::Angle(toolPoint[0], toolPoint[3]);
894 double span = angle - toolPoint[2].x;
899 painter->DrawLine(toolPoint[0], toolPoint[3]);
900 painter->SetBrush(QBrush(Qt::NoBrush));
901 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
902 painter->SetPen(0xFF00FF, 2.0, LSSolid);
903 painter->DrawArc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
904 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
905 QString text = tr("Arc span: %1") + QChar(0x00B0);
906 informativeText = text.arg(RADIANS_TO_DEGREES * span);
912 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
913 painter->DrawHandle(toolPoint[0]);
914 else if ((Global::toolState == TSPoint2) && shiftDown)
915 painter->DrawHandle(toolPoint[1]);
918 if (toolPoint[0] == toolPoint[1])
921 painter->DrawLine(toolPoint[0], toolPoint[1]);
923 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
924 QString text = QChar(0x2221) + QObject::tr(": %1");
925 informativeText = text.arg(absAngle);
928 informativeText += " (Copy)";
934 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
935 painter->DrawHandle(toolPoint[0]);
936 else if ((Global::toolState == TSPoint2) && shiftDown)
937 painter->DrawHandle(toolPoint[1]);
940 if (toolPoint[0] == toolPoint[1])
943 Point mirrorPoint = toolPoint[0] + Vector(toolPoint[1], toolPoint[0]);
944 painter->DrawLine(mirrorPoint, toolPoint[1]);
946 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
948 if (absAngle > 180.0)
951 QString text = QChar(0x2221) + QObject::tr(": %1");
952 informativeText = text.arg(absAngle);
955 informativeText += " (Copy)";
961 if (Global::toolState == TSNone)
963 painter->DrawHandle(toolPoint[0]);
965 else if ((Global::toolState == TSPoint2) && shiftDown)
967 painter->DrawHandle(toolPoint[1]);
971 painter->DrawLine(toolPoint[0], toolPoint[1]);
972 painter->DrawHandle(toolPoint[1]);
974 Vector v(toolPoint[0], toolPoint[1]);
975 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
976 double absLength = v.Magnitude();
977 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
978 informativeText = text.arg(absLength).arg(absAngle);
984 if (toolObj[0] != NULL)
986 // We're assuming ATM it's just a line...
987 painter->SetPen(0xAF0000, 3.0, LSSolid);
988 painter->DrawLine(toolPoint[0], toolPoint[1]);
989 // QString text = tr("Arc span: %1") + QChar(0x00B0);
990 // informativeText = text.arg(RADIANS_TO_DEGREES * span);
996 if (Global::toolState == TSPoint1)
998 painter->SetPen(0xFF00FF, 2.0, LSSolid);
999 painter->SetBrush(QBrush(Qt::NoBrush));
1001 double length = Vector::Magnitude(toolObj[0]->p[0], toolPoint[0]);
1002 bool inside = (length >= toolObj[0]->radius[0] ? false : true);
1004 for(int i=1; i<=Global::parallelNum; i++)
1006 if (toolObj[0]->type == OTLine)
1008 painter->DrawLine(toolObj[0]->p[0] + (toolPoint[0] * Global::parallelDist * (double)i), toolObj[0]->p[1] + (toolPoint[0] * Global::parallelDist * (double)i));
1010 else if ((toolObj[0]->type == OTCircle) || (toolObj[0]->type == OTArc))
1012 double radius = toolObj[0]->radius[0] + ((double)i * Global::parallelDist * (inside ? -1.0 : 1.0));
1016 if (toolObj[0]->type == OTCircle)
1017 painter->DrawEllipse(toolObj[0]->p[0], radius, radius);
1019 painter->DrawArc(toolObj[0]->p[0], radius, toolObj[0]->angle[0], toolObj[0]->angle[1]);
1032 void DrawingView::LineHandler(int mode, Point p)
1037 /* toolObj[0] = NULL;
1039 // Check to see if we can do a circle tangent snap
1040 if (numHovered == 1)
1042 VPVector hover = GetHovered();
1043 Object * obj = (Object *)hover[0];
1045 // 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! :-)
1046 if ((obj->type == OTCircle) && (obj->hitPoint[0] == false))
1050 if (Global::toolState == TSNone)
1058 if (Global::toolState == TSNone)
1063 /* bool isCircle = false;
1065 if (numHovered == 1)
1067 VPVector hover = GetHovered();
1068 Object * obj = (Object *)hover[0];
1070 if ((obj->type == OTCircle) && (obj->hitPoint[0] == false))
1077 // Adjust initial point if it's on a circle (tangent point)
1078 if (toolObj[0] != NULL)
1082 Geometry::FindTangents(toolObj[0], toolObj[1]);
1084 if (Global::numIntersectPoints > 0)
1086 toolPoint[0] = Global::intersectPoint[0];
1087 toolPoint[1] = Global::intersectPoint[1];
1092 Geometry::FindTangents(toolObj[0], p);
1094 if (Global::numIntersectPoints > 0)
1095 toolPoint[0] = Global::intersectPoint[0];
1102 Geometry::FindTangents(toolObj[1], toolPoint[0]);
1104 if (Global::numIntersectPoints > 0)
1105 toolPoint[1] = Global::intersectPoint[0];
1113 if (Global::toolState == TSNone)
1115 Global::toolState = TSPoint2;
1116 // Prevent spurious line from drawing...
1117 toolPoint[1] = toolPoint[0];
1119 else if ((Global::toolState == TSPoint2) && shiftDown)
1121 // Key override is telling us to make a new line, not continue the
1123 toolPoint[0] = toolPoint[1];
1127 Line * l = new Line(toolPoint[0], toolPoint[1], Global::penWidth, Global::penColor, Global::penStyle);
1128 l->layer = Global::activeLayer;
1129 document.objects.push_back(l);
1130 toolPoint[0] = toolPoint[1];
1135 void DrawingView::CircleHandler(int mode, Point p)
1140 if (Global::toolState == TSNone)
1148 if (Global::toolState == TSNone)
1156 if (Global::toolState == TSNone)
1158 Global::toolState = TSPoint2;
1159 // Prevent spurious line from drawing...
1160 toolPoint[1] = toolPoint[0];
1162 else if ((Global::toolState == TSPoint2) && shiftDown)
1164 // Key override is telling us to make a new line, not continue the
1166 toolPoint[0] = toolPoint[1];
1170 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
1171 Circle * c = new Circle(toolPoint[0], length, Global::penWidth, Global::penColor, Global::penStyle);
1172 c->layer = Global::activeLayer;
1173 document.objects.push_back(c);
1174 toolPoint[0] = toolPoint[1];
1175 Global::toolState = TSNone;
1180 void DrawingView::ArcHandler(int mode, Point p)
1185 if (Global::toolState == TSNone)
1187 else if (Global::toolState == TSPoint2)
1189 else if (Global::toolState == TSPoint3)
1197 if (Global::toolState == TSNone)
1199 else if (Global::toolState == TSPoint2)
1201 else if (Global::toolState == TSPoint3)
1215 if (Global::toolState == TSNone)
1217 // Prevent spurious line from drawing...
1218 toolPoint[1] = toolPoint[0];
1219 Global::toolState = TSPoint2;
1221 else if (Global::toolState == TSPoint2)
1225 // Key override is telling us to start arc at new center, not
1226 // continue the current one.
1227 toolPoint[0] = toolPoint[1];
1231 // Set the radius in toolPoint[1].x
1232 toolPoint[1].x = Vector::Magnitude(toolPoint[0], toolPoint[1]);
1233 Global::toolState = TSPoint3;
1235 else if (Global::toolState == TSPoint3)
1237 // Set the angle in toolPoint[2].x
1238 toolPoint[2].x = Vector::Angle(toolPoint[0], toolPoint[2]);
1239 Global::toolState = TSPoint4;
1243 double endAngle = Vector::Angle(toolPoint[0], toolPoint[3]);
1244 double span = endAngle - toolPoint[2].x;
1249 Arc * arc = new Arc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span, Global::penWidth, Global::penColor, Global::penStyle);
1250 arc->layer = Global::activeLayer;
1251 document.objects.push_back(arc);
1252 Global::toolState = TSNone;
1257 void DrawingView::RotateHandler(int mode, Point p)
1262 if (Global::toolState == TSNone)
1265 // SavePointsFrom(select, toolScratch);
1266 CopyObjects(select, toolScratch2);
1267 Global::toolState = TSPoint1;
1269 else if (Global::toolState == TSPoint1)
1277 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1279 else if (Global::toolState == TSPoint2)
1287 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1288 VPVectorIter j = select.begin();
1289 // std::vector<Object>::iterator i = toolScratch.begin();
1290 VPVectorIter i = toolScratch2.begin();
1292 // for(; i!=toolScratch.end(); i++, j++)
1293 for(; i!=toolScratch2.end(); i++, j++)
1295 // Object objT = *i;
1296 // Point p1 = Geometry::RotatePointAroundPoint(objT.p[0], toolPoint[0], angle);
1297 // Point p2 = Geometry::RotatePointAroundPoint(objT.p[1], toolPoint[0], angle);
1298 Object * objT = (Object *)(*i);
1299 Object * objS = (Object *)(*j);
1301 Point p1 = Geometry::RotatePointAroundPoint(objT->p[0], toolPoint[0], angle);
1302 Point p2 = Geometry::RotatePointAroundPoint(objT->p[1], toolPoint[0], angle);
1307 // if (objT.type == OTArc || objT.type == OTText)
1308 if (objT->type == OTArc || objT->type == OTText)
1310 // objS->angle[0] = objT.angle[0] + angle;
1311 objS->angle[0] = objT->angle[0] + angle;
1313 if (objS->angle[0] > TAU)
1314 objS->angle[0] -= TAU;
1316 // else if (objT.type == OTContainer)
1317 else if (objT->type == OTContainer)
1319 // 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...]
1320 // Container * c = (Container *)&objT;
1321 Container * c = (Container *)objT;
1322 Container * c2 = (Container *)objS;
1323 VPVectorIter l = c->objects.begin();
1324 // TODO: Rotate items in the container
1325 // TODO: Make this recursive
1326 for(VPVectorIter k=c2->objects.begin(); k!=c2->objects.end(); k++, l++)
1328 Object * obj3 = (Object *)(*k);
1329 Object * obj4 = (Object *)(*l);
1331 p1 = Geometry::RotatePointAroundPoint(obj4->p[0], toolPoint[0], angle);
1332 p2 = Geometry::RotatePointAroundPoint(obj4->p[1], toolPoint[0], angle);
1336 // obj3->angle[0] = objT.angle[0] + angle;
1337 obj3->angle[0] = obj4->angle[0] + angle;
1339 if (obj3->angle[0] > TAU)
1340 obj3->angle[0] -= TAU;
1343 Rect r = GetObjectExtents(objS);
1344 c2->p[0] = r.TopLeft();
1345 c2->p[1] = r.BottomRight();
1353 if (Global::toolState == TSPoint1)
1355 Global::toolState = TSPoint2;
1356 // Prevent spurious line from drawing...
1357 toolPoint[1] = toolPoint[0];
1359 else if ((Global::toolState == TSPoint2) && shiftDown)
1361 // Key override is telling us to make a new line, not continue the
1363 toolPoint[0] = toolPoint[1];
1367 // Either we're finished with our rotate, or we're stamping a copy.
1370 // Stamp a copy of the selection at the current rotation & bail
1372 CopyObjects(select, temp);
1373 ClearSelected(temp);
1374 AddObjectsTo(document.objects, temp);
1375 // RestorePointsTo(select, toolScratch);
1376 RestorePointsTo(select, toolScratch2);
1381 Global::toolState = TSPoint1;
1382 // SavePointsFrom(select, toolScratch);
1383 DeleteContents(toolScratch2);
1384 CopyObjects(select, toolScratch2);
1390 // Reset the selection if shift held down...
1392 // RestorePointsTo(select, toolScratch);
1393 RestorePointsTo(select, toolScratch2);
1398 // Reset selection when key is let up
1400 RotateHandler(ToolMouseMove, toolPoint[1]);
1405 // RestorePointsTo(select, toolScratch);
1406 RestorePointsTo(select, toolScratch2);
1407 DeleteContents(toolScratch2);
1411 void DrawingView::MirrorHandler(int mode, Point p)
1416 if (Global::toolState == TSNone)
1419 SavePointsFrom(select, toolScratch);
1420 Global::toolState = TSPoint1;
1422 else if (Global::toolState == TSPoint1)
1430 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1432 else if (Global::toolState == TSPoint2)
1440 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1441 VPVectorIter j = select.begin();
1442 std::vector<Object>::iterator i = toolScratch.begin();
1444 for(; i!=toolScratch.end(); i++, j++)
1447 Point p1 = Geometry::MirrorPointAroundLine(obj.p[0], toolPoint[0], toolPoint[1]);
1448 Point p2 = Geometry::MirrorPointAroundLine(obj.p[1], toolPoint[0], toolPoint[1]);
1449 Object * obj2 = (Object *)(*j);
1454 N.B.: When mirroring an arc thru a horizontal axis, this causes the arc to have
1455 a negative start angle which makes it impossible to interact with.
1458 if (obj.type == OTArc)
1460 // This is 2*mirror angle - obj angle - obj span
1461 obj2->angle[0] = (2.0 * angle) - obj.angle[0] - obj.angle[1];
1463 if (obj2->angle[0] > TAU)
1464 obj2->angle[0] -= TAU;
1472 if (Global::toolState == TSPoint1)
1474 Global::toolState = TSPoint2;
1475 // Prevent spurious line from drawing...
1476 toolPoint[1] = toolPoint[0];
1478 else if ((Global::toolState == TSPoint2) && shiftDown)
1480 // Key override is telling us to make a new line, not continue the
1482 toolPoint[0] = toolPoint[1];
1486 // Either we're finished with our rotate, or we're stamping a copy.
1489 // Stamp a copy of the selection at the current rotation & bail
1491 CopyObjects(select, temp);
1492 ClearSelected(temp);
1493 AddObjectsTo(document.objects, temp);
1494 RestorePointsTo(select, toolScratch);
1499 Global::toolState = TSPoint1;
1500 SavePointsFrom(select, toolScratch);
1506 // Reset the selection if shift held down...
1508 RestorePointsTo(select, toolScratch);
1513 // Reset selection when key is let up
1515 MirrorHandler(ToolMouseMove, toolPoint[1]);
1520 RestorePointsTo(select, toolScratch);
1524 void DrawingView::DimensionHandler(int mode, Point p)
1529 if (Global::toolState == TSNone)
1537 if (Global::toolState == TSNone)
1545 if (Global::toolState == TSNone)
1547 Global::toolState = TSPoint2;
1548 // Prevent spurious line from drawing...
1549 toolPoint[1] = toolPoint[0];
1551 else if ((Global::toolState == TSPoint2) && shiftDown)
1553 // Key override is telling us to make a new line, not continue the
1555 toolPoint[0] = toolPoint[1];
1559 Dimension * d = new Dimension(toolPoint[0], toolPoint[1], DTLinear, 0, Global::penWidth);
1560 d->layer = Global::activeLayer;
1561 document.objects.push_back(d);
1562 Global::toolState = TSNone;
1567 void DrawingView::DeleteHandler(int mode, Point /*p*/)
1573 VPVector hovered = GetHovered();
1575 RemoveHoveredObjects(document.objects);
1576 DeleteContents(hovered);
1597 void DrawingView::TriangulateHandler(int mode, Point /*p*/)
1603 // Skip if nothing hovered...
1604 if (numHovered != 1)
1607 VPVector hover = GetHovered();
1608 Object * obj = (Object *)hover[0];
1610 // Skip if it's not a line...
1611 if (obj->type != OTLine)
1614 if (Global::toolState == TSNone)
1616 else if (Global::toolState == TSPoint2)
1625 if (Global::toolState == TSNone)
1627 else if (Global::toolState == TSPoint2)
1629 else if (Global::toolState == TSPoint3)
1643 if (Global::toolState == TSNone)
1645 Global::toolState = TSPoint2;
1647 else if (Global::toolState == TSPoint2)
1651 // Key override is telling us to start arc at new center, not
1652 // continue the current one.
1653 toolPoint[0] = toolPoint[1];
1657 Global::toolState = TSPoint3;
1661 double len2 = Vector::Magnitude(toolObj[1]->p[0], toolObj[1]->p[1]);
1662 double len3 = Vector::Magnitude(toolObj[2]->p[0], toolObj[2]->p[1]);
1664 Circle c1(toolObj[0]->p[0], len2);
1665 Circle c2(toolObj[0]->p[1], len3);
1667 Geometry::CheckCircleToCircleIntersection((Object *)&c1, (Object *)&c2);
1669 // Only move lines if the triangle formed by them is not degenerate
1670 if (Global::numIntersectPoints > 0)
1672 toolObj[1]->p[0] = toolObj[0]->p[0];
1673 toolObj[1]->p[1] = Global::intersectPoint[0];
1675 toolObj[2]->p[0] = Global::intersectPoint[0];
1676 toolObj[2]->p[1] = toolObj[0]->p[1];
1679 Global::toolState = TSNone;
1684 void DrawingView::TrimHandler(int mode, Point p)
1695 // Bail out if nothing hovered...
1696 if (numHovered != 1)
1702 VPVector hover = GetHovered();
1703 Object * obj = (Object *)hover[0];
1705 // Skip if it's not a line...
1706 if (obj->type != OTLine)
1713 double hoveredParam = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], p);
1714 double t = 0, u = 1.0;
1716 // Currently only deal with line against line trimming, can expand to
1717 // others as well (line/circle, circle/circle, line/arc, etc)
1719 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1721 obj = (Object *)(*i);
1723 if (obj == toolObj[0])
1725 else if (obj->type != OTLine)
1728 Geometry::CheckLineToLineIntersection(toolObj[0], obj);
1730 if (Global::numIntersectParams > 0)
1732 // Skip endpoint-endpoint intersections
1733 if ((Global::numIntersectParams == 2)
1734 && (Global::intersectParam[0] == 0
1735 || Global::intersectParam[0] == 1.0)
1736 && (Global::intersectParam[1] == 0
1737 || Global::intersectParam[1] == 1.0))
1740 // Mark the line segment somehow (the side the mouse is on) so that it can be drawn & trimmed when we hit ToolMouseDown.
1741 if ((Global::intersectParam[0] > t) && (Global::intersectParam[0] < hoveredParam))
1742 t = Global::intersectParam[0];
1744 if ((Global::intersectParam[0] < u) && (Global::intersectParam[0] > hoveredParam))
1745 u = Global::intersectParam[0];
1751 toolPoint[0] = Geometry::GetPointForParameter(toolObj[0], t);
1752 toolPoint[1] = Geometry::GetPointForParameter(toolObj[0], u);
1758 // Bail out if there's no object to trim
1759 if (toolObj[0] == NULL)
1762 Vector v(toolObj[0]->p[0], toolObj[0]->p[1]);
1764 // Check to see which case we have.
1765 if ((toolParam[0] == 0) && (toolParam[1] == 1.0))
1767 // There was no intersection, so delete the object
1768 toolObj[0]->selected = true;
1769 DeleteSelectedObjects(document.objects);
1771 else if (toolParam[0] == 0)
1773 // We delete the end near point #1
1774 toolObj[0]->p[0] = toolObj[0]->p[0] + (v * toolParam[1]);
1776 else if (toolParam[1] == 1.0)
1778 // We delete the end near point #2
1779 toolObj[0]->p[1] = toolObj[0]->p[0] + (v * toolParam[0]);
1783 // We delete the segment in between, and create a new line in the process
1784 Point p1 = toolObj[0]->p[0] + (v * toolParam[0]);
1785 Point p2 = toolObj[0]->p[0] + (v * toolParam[1]);
1786 Point p3 = toolObj[0]->p[1];
1787 toolObj[0]->p[1] = p1;
1788 Line * l = new Line(p2, p3, toolObj[0]->thickness, toolObj[0]->color, toolObj[0]->style);
1789 document.objects.push_back(l);
1790 // Global::toolState = TSNone;
1793 toolObj[0]->hitObject = toolObj[0]->hitPoint[0] = toolObj[0]->hitPoint[1] = false;
1809 void DrawingView::ParallelHandler(int mode, Point p)
1814 if (numHovered == 1)
1816 // New selection made...
1817 VPVector hover = GetHovered();
1818 toolObj[0] = (Object *)hover[0];
1819 Global::toolState = TSNone;
1821 else if ((numHovered == 0) && (toolObj[0] != NULL))
1823 double length = Vector::Magnitude(toolObj[0]->p[0], toolPoint[0]);
1824 bool inside = (length >= toolObj[0]->radius[0] ? false : true);
1826 // Stamp out new parallel object(s)...
1827 for(int i=1; i<=Global::parallelNum; i++)
1829 if (toolObj[0]->type == OTLine)
1831 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);
1832 // Should probably have a user selection for this whether it goes into the selected objects layer or the global layer...
1833 l->layer = toolObj[0]->layer;
1834 document.objects.push_back(l);
1836 else if (toolObj[0]->type == OTCircle)
1838 double radius = toolObj[0]->radius[0] + ((double)i * Global::parallelDist * (inside ? -1.0 : 1.0));
1842 Circle * c = new Circle(toolObj[0]->p[0], radius, Global::penWidth, Global::penColor, Global::penStyle);
1843 c->layer = toolObj[0]->layer;
1844 document.objects.push_back(c);
1847 else if (toolObj[0]->type == OTArc)
1849 double radius = toolObj[0]->radius[0] + ((double)i * Global::parallelDist * (inside ? -1.0 : 1.0));
1853 Arc * a = new Arc(toolObj[0]->p[0], radius, toolObj[0]->angle[0], toolObj[0]->angle[1], Global::penWidth, Global::penColor, Global::penStyle);
1854 a->layer = toolObj[0]->layer;
1855 document.objects.push_back(a);
1860 // Then reset the state
1861 toolObj[0]->selected = false;
1863 Global::toolState = TSNone;
1869 if ((numHovered == 0) && toolObj[0] != NULL)
1870 Global::toolState = TSPoint1;
1872 Global::toolState = TSNone;
1874 if (Global::toolState == TSPoint1)
1876 // Figure out which side of the object we're on, and draw the preview on that side...
1877 if (toolObj[0]->type == OTLine)
1879 Vector normal = Geometry::GetNormalOfPointAndLine(p, (Line *)toolObj[0]);
1880 toolPoint[0] = normal;
1882 else if ((toolObj[0]->type == OTCircle) || (toolObj[0]->type == OTArc))
1904 void DrawingView::mousePressEvent(QMouseEvent * event)
1906 if (event->button() == Qt::LeftButton)
1908 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1910 // Handle tool processing, if any
1913 if (hoveringIntersection)
1914 point = intersectionPoint;
1915 else if (hoverPointValid)
1917 else if (Global::snapToGrid)
1918 point = SnapPointToGrid(point);
1920 ToolHandler(ToolMouseDown, point);
1924 // Clear the selection only if CTRL isn't being held on click
1926 ClearSelected(document.objects);
1928 // If any objects are being hovered on click, deal with 'em
1931 VPVector hover2 = GetHovered();
1932 dragged = (Object *)hover2[0];
1934 // Alert the pen widget
1935 if (Global::penDropper)
1937 Global::penColor = dragged->color;
1938 Global::penWidth = dragged->thickness;
1939 Global::penStyle = dragged->style;
1940 emit ObjectSelected(dragged);
1943 else if (Global::penStamp)
1945 dragged->color = Global::penColor;
1946 dragged->thickness = Global::penWidth;
1947 dragged->style = Global::penStyle;
1950 // See if anything is using just a straight click on a custom
1951 // object handle (like Dimension objects)
1952 else if (HandleObjectClicked())
1958 draggingObject = true;
1959 HandleSelectionClick(hover2);
1960 update(); // needed??
1962 // Needed for grab & moving objects
1963 // We do it *after*... why? (doesn't seem to confer any advantage...)
1964 if (hoveringIntersection)
1965 oldPoint = intersectionPoint;
1966 else if (hoverPointValid)
1967 oldPoint = hoverPoint;
1968 else if (Global::snapToGrid)
1969 oldPoint = SnapPointToGrid(point);
1971 // Needed for fixed length handling
1972 if (Global::fixedLength)
1974 if (dragged->type == OTLine)
1975 dragged->length = ((Line *)dragged)->Length();
1978 // Needed for fixed angle handling
1979 if (Global::fixedAngle)
1981 if (dragged->type == OTLine)
1982 dragged->p[2] = ((Line *)dragged)->Unit();
1985 if (dragged->type == OTCircle)
1987 // Save for informative text, uh, er, informing
1988 dragged->length = dragged->radius[0];
1994 // Didn't hit any object and not using a tool, so do a selection
1996 Global::selectionInProgress = true;
1997 Global::selection.l = Global::selection.r = point.x;
1998 Global::selection.t = Global::selection.b = point.y;
1999 select = GetSelection();
2001 else if (event->button() == Qt::MiddleButton)
2004 oldPoint = Vector(event->x(), event->y());
2005 // Should also change the mouse pointer as well...
2006 setCursor(Qt::SizeAllCursor);
2010 void DrawingView::mouseMoveEvent(QMouseEvent * event)
2012 // It seems that wheelEvent() triggers this for some reason...
2013 if (scrollWheelSeen)
2015 scrollWheelSeen = false;
2019 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
2020 Global::selection.r = point.x;
2021 Global::selection.b = point.y;
2022 // Only needs to be done here, as mouse down is always preceded by movement
2023 Global::snapPointIsValid = false;
2024 hoveringIntersection = false;
2025 oldScrollPoint = Vector(event->x(), event->y());
2028 if ((event->buttons() & Qt::MiddleButton) || scrollDrag)
2030 point = Vector(event->x(), event->y());
2031 // Since we're using Qt coords for scrolling, we have to adjust them
2032 // here to conform to Cartesian coords, since the origin is using
2034 Vector delta(oldPoint, point);
2035 delta /= Global::zoom;
2037 Global::origin -= delta;
2039 // UpdateGridBackground();
2045 // If we're doing a selection rect, see if any objects are engulfed by it
2046 // (implies left mouse button held down)
2047 if (Global::selectionInProgress)
2049 CheckObjectBounds();
2051 // Make sure previously selected objects stay selected (CTRL held)
2052 for(VPVectorIter i=select.begin(); i!=select.end(); i++)
2054 // Make sure *not* to select items on hidden layers
2055 if (Global::layerHidden[((Object *)(*i))->layer] == false)
2056 ((Object *)(*i))->selected = true;
2063 // Do object hit testing...
2064 bool needUpdate = HitTestObjects(point);
2065 VPVector hover2 = GetHovered(true); // Exclude dimension objects and circle centers (probably need to add arc centers too) from hover (also dragged objects...)
2070 printf("mouseMoveEvent:: numHovered=%li, hover2.size()=%li\n", numHovered, hover2.size());
2072 if (hover2.size() > 0)
2073 printf(" (hover2[0]=$%llX, type=%s)\n", hover2[0], objName[((Object *)hover2[0])->type]);
2078 // Check for multi-hover...
2079 if (hover2.size() > 1)
2081 //need to check for case where hover is over 2 circles and a 3rd's center (no longer a problem, I think)...
2082 Object * obj1 = (Object *)hover2[0], * obj2 = (Object *)hover2[1];
2084 Geometry::Intersects(obj1, obj2);
2085 int numIntersecting = Global::numIntersectParams;
2086 double t = Global::intersectParam[0];
2087 double u = Global::intersectParam[1];
2089 if (numIntersecting > 0)
2091 Vector v1 = Geometry::GetPointForParameter(obj1, t);
2092 Vector v2 = Geometry::GetPointForParameter(obj2, u);
2093 QString text = tr("Intersection t=%1 (%3, %4), u=%2 (%5, %6)");
2094 informativeText = text.arg(t).arg(u).arg(v1.x).arg(v1.y).arg(v2.x).arg(v2.y);
2096 hoveringIntersection = true;
2097 intersectionPoint = v1;
2100 numIntersecting = Global::numIntersectPoints;
2102 if (numIntersecting > 0)
2104 Vector v1 = Global::intersectPoint[0];
2106 if (numIntersecting == 2)
2108 Vector v2 = Global::intersectPoint[1];
2110 if (Vector::Magnitude(v2, point) < Vector::Magnitude(v1, point))
2114 QString text = tr("Intersection <%1, %2>");
2115 informativeText = text.arg(v1.x).arg(v1.y);
2116 hoveringIntersection = true;
2117 intersectionPoint = v1;
2120 else if (hover2.size() == 1)
2122 Object * obj = (Object *)hover2[0];
2124 if (obj->type == OTLine)
2127 Not sure that this is the best way to handle this, but it works(TM)...
2129 Point midpoint = Geometry::Midpoint((Line *)obj);
2130 Vector v1 = Vector::Magnitude(midpoint, point);
2132 if ((v1.Magnitude() * Global::zoom) < 8.0)
2134 QString text = tr("Midpoint <%1, %2>");
2135 informativeText = text.arg(midpoint.x).arg(midpoint.y);
2136 hoverPointValid = true;
2137 hoverPoint = midpoint;
2141 else if (obj->type == OTCircle)
2143 if ((draggingObject && (dragged->type == OTLine)) && (dragged->hitPoint[0] || dragged->hitPoint[1]))
2145 Point p = (dragged->hitPoint[0] ? dragged->p[1] : dragged->p[0]);
2146 Geometry::FindTangents(obj, p);
2148 if (Global::numIntersectPoints > 0)
2150 hoveringIntersection = true;
2151 intersectionPoint = Geometry::NearestTo(point, Global::intersectPoint[0], Global::intersectPoint[1]);
2154 else if ((Global::tool == TTLine) && (Global::toolState == TSPoint2))
2156 Geometry::FindTangents(obj, toolPoint[0]);
2158 if (Global::numIntersectPoints > 0)
2160 hoveringIntersection = true;
2161 intersectionPoint = Geometry::NearestTo(point, Global::intersectPoint[0], Global::intersectPoint[1]);
2167 // Handle object movement (left button down & over an object)
2168 if ((event->buttons() & Qt::LeftButton) && draggingObject && !Global::tool)
2170 if (hoveringIntersection)
2171 point = intersectionPoint;
2172 else if (hoverPointValid)
2174 else if (Global::snapToGrid)
2175 point = SnapPointToGrid(point);
2177 HandleObjectMovement(point);
2183 // Do tool handling, if any are active...
2186 if (hoveringIntersection)
2187 point = intersectionPoint;
2188 else if (hoverPointValid)
2190 else if (Global::snapToGrid)
2193 point = SnapPointToAngle(point);
2195 point = SnapPointToGrid(point);
2198 ToolHandler(ToolMouseMove, point);
2201 // This is used to draw the tool crosshair...
2204 if (needUpdate || Global::tool)
2208 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
2210 if (event->button() == Qt::LeftButton)
2212 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
2213 //could set it up to use the document's update function (assumes that all object
2214 //updates are being reported correctly:
2215 // if (document.NeedsUpdate())
2216 // Do an update if collided with at least *one* object in the document
2222 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
2223 ToolHandler(ToolMouseUp, point);
2227 Global::selectionInProgress = false;
2228 informativeText.clear();
2230 // Should we be doing this automagically? Hmm...
2231 // Clear our vectors
2235 // 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???])
2236 select = GetSelection();
2238 draggingObject = false;
2240 else if (event->button() == Qt::MiddleButton)
2244 if (Global::penStamp)
2245 setCursor(curMarker);
2246 else if (Global::penDropper)
2247 setCursor(curDropper);
2249 setCursor(Qt::ArrowCursor);
2251 // Need to convert this, since it's in Qt coordinates (for wheelEvent)
2252 oldPoint = Painter::QtToCartesianCoords(oldPoint);
2256 void DrawingView::wheelEvent(QWheelEvent * event)
2258 double zoomFactor = 1.20;
2259 scrollWheelSeen = true;
2261 if (event->angleDelta().y() < 0)
2263 if (Global::zoom > 400.0)
2266 Global::zoom *= zoomFactor;
2270 if (Global::zoom < 0.125)
2273 Global::zoom /= zoomFactor;
2276 Point np = Painter::QtToCartesianCoords(oldScrollPoint);
2277 Global::origin += (oldPoint - np);
2279 emit(NeedZoomUpdate());
2282 void DrawingView::keyPressEvent(QKeyEvent * event)
2284 bool oldShift = shiftDown;
2285 bool oldCtrl = ctrlDown;
2286 bool oldAlt = altDown;
2288 if (event->key() == Qt::Key_Shift)
2290 else if (event->key() == Qt::Key_Control)
2292 else if (event->key() == Qt::Key_Alt)
2295 // If there's a change in any of the modifier key states, pass it on to
2296 // the current tool's handler
2297 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
2300 ToolHandler(ToolKeyDown, Point(0, 0));
2305 if (oldAlt != altDown)
2308 setCursor(Qt::SizeAllCursor);
2309 oldPoint = oldScrollPoint;
2312 if (select.size() > 0)
2314 if (event->key() == Qt::Key_Up)
2316 TranslateObjects(select, Point(0, +1.0));
2319 else if (event->key() == Qt::Key_Down)
2321 TranslateObjects(select, Point(0, -1.0));
2324 else if (event->key() == Qt::Key_Right)
2326 TranslateObjects(select, Point(+1.0, 0));
2329 else if (event->key() == Qt::Key_Left)
2331 TranslateObjects(select, Point(-1.0, 0));
2337 void DrawingView::keyReleaseEvent(QKeyEvent * event)
2339 bool oldShift = shiftDown;
2340 bool oldCtrl = ctrlDown;
2341 bool oldAlt = altDown;
2343 if (event->key() == Qt::Key_Shift)
2345 else if (event->key() == Qt::Key_Control)
2347 else if (event->key() == Qt::Key_Alt)
2350 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
2353 ToolHandler(ToolKeyUp, Point(0, 0));
2358 if (oldAlt != altDown)
2362 if (Global::penStamp)
2363 setCursor(curMarker);
2364 else if (Global::penDropper)
2365 setCursor(curDropper);
2367 setCursor(Qt::ArrowCursor);
2372 // This looks strange, but it's really quite simple: We want a point that's
2373 // more than half-way to the next grid point to snap there while conversely we
2374 // want a point that's less than half-way to to the next grid point then snap
2375 // to the one before it. So we add half of the grid spacing to the point, then
2376 // divide by it so that we can remove the fractional part, then multiply it
2377 // back to get back to the correct answer.
2379 Point DrawingView::SnapPointToGrid(Point point)
2381 point += Global::gridSpacing / 2.0; // *This* adds to Z!!!
2382 point /= Global::gridSpacing;
2383 point.x = floor(point.x);//need to fix this for negative numbers...
2384 point.y = floor(point.y);
2385 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
2386 point *= Global::gridSpacing;
2391 Point DrawingView::SnapPointToAngle(Point point)
2393 // Snap to a single digit angle (using toolpoint #1 as the center)
2394 double angle = Vector::Angle(toolPoint[0], point);
2395 double length = Vector::Magnitude(toolPoint[0], point);
2397 // Convert from radians to degrees
2398 double degAngle = angle * RADIANS_TO_DEGREES;
2399 double snapAngle = (double)((int)(degAngle + 0.5));
2402 v.SetAngleAndLength(snapAngle * DEGREES_TO_RADIANS, length);
2403 point = toolPoint[0] + v;
2408 Rect DrawingView::GetObjectExtents(Object * obj)
2410 // Default to empty rect, if object checks below fail for some reason
2418 rect = Rect(obj->p[0], obj->p[1]);
2424 rect = Rect(obj->p[0], obj->p[0]);
2425 rect.Expand(obj->radius[0]);
2431 Arc * a = (Arc *)obj;
2438 Polyline * p = (Polyline *)obj;
2445 Text * t = (Text *)obj;
2446 rect = Rect(t->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
2452 Container * c = (Container *)obj;
2453 VPVectorIter i = c->objects.begin();
2454 rect = GetObjectExtents((Object *)*i);
2457 for(; i!=c->objects.end(); i++)
2458 rect |= GetObjectExtents((Object *)*i);
2468 void DrawingView::CheckObjectBounds(void)
2472 for(i=document.objects.begin(); i!=document.objects.end(); i++)
2474 Object * obj = (Object *)(*i);
2475 obj->selected = false;
2476 Rect selection = Global::selection;
2477 selection.Normalize();
2482 case OTDimension: // N.B.: We don't check this properly...
2484 Line * l = (Line *)obj;
2486 if (selection.Contains(l->p[0]) && selection.Contains(l->p[1]))
2494 Circle * c = (Circle *)obj;
2495 Vector radVec(c->radius[0], c->radius[0]);
2497 if (selection.Contains(c->p[0] - radVec) && selection.Contains(c->p[0] + radVec))
2505 Arc * a = (Arc *)obj;
2506 Rect bounds = a->Bounds();
2508 if (selection.Contains(bounds))
2516 Polyline * pl = (Polyline *)obj;
2517 Rect bounds = pl->Bounds();
2519 if (selection.Contains(bounds))
2520 pl->selected = true;
2527 Text * t = (Text *)obj;
2528 Rect r(obj->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
2530 if (selection.Contains(r))
2538 Container * c = (Container *)obj;
2540 if (selection.Contains(c->p[0]) && selection.Contains(c->p[1]))
2552 bool DrawingView::HitTestObjects(Point point)
2556 bool needUpdate = false;
2557 hoverPointValid = false;
2559 for(i=document.objects.begin(); i!=document.objects.end(); i++)
2561 Object * obj = (Object *)(*i);
2563 // If we're seeing the object we're dragging, skip it
2564 if (draggingObject && (obj == dragged))
2567 if (HitTest(obj, point) == true)
2573 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
2574 emit ObjectHovered(obj);
2581 bool DrawingView::HitTest(Object * obj, Point point)
2583 bool needUpdate = false;
2585 // Make sure we don't hit test stuff on an invisible layer...
2586 if (Global::layerHidden[obj->layer] == true)
2593 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
2594 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
2595 Vector lineSegment = obj->p[1] - obj->p[0];
2596 Vector v1 = point - obj->p[0];
2597 Vector v2 = point - obj->p[1];
2598 double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
2602 distance = v1.Magnitude();
2604 distance = v2.Magnitude();
2606 // distance = ?Det?(ls, v1) / |ls|
2607 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
2608 / lineSegment.Magnitude());
2610 if ((v1.Magnitude() * Global::zoom) < 8.0)
2612 obj->hitPoint[0] = true;
2613 hoverPoint = obj->p[0];
2614 hoverPointValid = true;
2616 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2618 obj->hitPoint[1] = true;
2619 hoverPoint = obj->p[1];
2620 hoverPointValid = true;
2622 else if ((distance * Global::zoom) < 5.0)
2623 obj->hitObject = true;
2625 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject);
2627 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
2635 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
2636 obj->hitPoint[0] = obj->hitObject = false;
2637 double length = Vector::Magnitude(obj->p[0], point);
2639 if ((length * Global::zoom) < 8.0)
2641 obj->hitPoint[0] = true;
2642 hoverPoint = obj->p[0];
2643 hoverPointValid = true;
2645 else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
2646 obj->hitObject = true;
2648 obj->hovered = (obj->hitPoint[0] || obj->hitObject);
2650 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
2658 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
2659 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
2660 double length = Vector::Magnitude(obj->p[0], point);
2661 double angle = Vector::Angle(obj->p[0], point);
2663 // Make sure we get the angle in the correct spot
2664 if (angle < obj->angle[0])
2667 // Get the span that we're pointing at...
2668 double span = angle - obj->angle[0];
2670 // N.B.: Still need to hit test the arc start & arc span handles... [looks like it's DONE?]
2671 double spanAngle = obj->angle[0] + obj->angle[1];
2672 Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
2673 Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
2674 double length2 = Vector::Magnitude(point, handle1);
2675 double length3 = Vector::Magnitude(point, handle2);
2677 if ((length * Global::zoom) < 8.0)
2679 obj->hitPoint[0] = true;
2680 hoverPoint = obj->p[0];
2681 hoverPointValid = true;
2683 else if ((length2 * Global::zoom) < 8.0)
2685 obj->hitPoint[1] = true;
2686 hoverPoint = handle1;
2687 hoverPointValid = true;
2689 else if ((length3 * Global::zoom) < 8.0)
2691 obj->hitPoint[2] = true;
2692 hoverPoint = handle2;
2693 hoverPointValid = true;
2695 else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
2696 obj->hitObject = true;
2698 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject);
2700 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
2708 Polyline * pl = (Polyline *)obj;
2709 bool oldHP0 = pl->hitPoint[0], oldHO = pl->hitObject;
2710 pl->hitPoint[0] = pl->hitObject = false;
2712 for(long unsigned int i=0; i<(pl->points.size()-1); i++)
2714 Point p1 = pl->points[i];
2715 Point p2 = pl->points[i + 1];
2717 double dist1 = Vector::Magnitude(p1, point) * Global::zoom;
2718 double dist2 = Vector::Magnitude(p2, point) * Global::zoom;
2720 // Check for endpoints of lines and/or arcs first
2723 pl->hitPoint[0] = true;
2725 hoverPointValid = true;
2728 else if (dist2 < 8.0)
2730 pl->hitPoint[0] = true;
2732 hoverPointValid = true;
2735 // Check for object (line/arc) last
2738 double t = Geometry::ParameterOfLineAndPoint(p1, p2, point);
2741 // No bump == check for line proximity
2749 Vector v1 = l.Vect();
2750 Vector v2(p1, point);
2751 objDist = fabs((v1.x * v2.y - v2.x * v1.y) / l.Length()) * Global::zoom;
2755 pl->hitObject = true;
2759 // We have a bump == check for arc proximity
2760 Arc a = Geometry::Unpack(p1, p2, p1.b);
2761 double length = Vector::Magnitude(a.p[0], point);
2762 double angle = Vector::Angle(a.p[0], point);
2763 double span = angle - a.angle[0];
2765 // Ensure point span is positive if we have a positive arc span
2766 if (span < 0 && a.angle[1] > 0)
2769 // Ensure point span is negative if we have a negative arc span
2770 if (span > 0 && a.angle[1] < 0)
2773 if (((fabs(length - a.radius[0]) * Global::zoom) < 2.5) && (fabs(span) < fabs(a.angle[1])))
2774 pl->hitObject = true;
2778 pl->hovered = (pl->hitPoint[0] || pl->hitObject);
2780 if ((oldHP0 != pl->hitPoint[0]) || (oldHO != pl->hitObject))
2788 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHP3 = obj->hitPoint[3], oldHP4 = obj->hitPoint[4], oldHO = obj->hitObject;
2789 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitPoint[3] = obj->hitPoint[4] = obj->hitObject = false;
2791 Dimension * d = (Dimension *)obj;
2793 Vector orthogonal = Vector::Normal(d->lp[0], d->lp[1]);
2794 // Get our line parallel to our points
2795 float scaledThickness = Global::scale * obj->thickness;
2797 Point p1 = d->lp[0] + (orthogonal * (d->offset + (10.0 * scaledThickness)));
2798 Point p2 = d->lp[1] + (orthogonal * (d->offset + (10.0 * scaledThickness)));
2800 Point p1 = d->lp[0] + (orthogonal * (10.0 + d->offset) * scaledThickness);
2801 Point p2 = d->lp[1] + (orthogonal * (10.0 + d->offset) * scaledThickness);
2803 Point p3(p1, point);
2805 Vector v1(d->p[0], point);
2806 Vector v2(d->p[1], point);
2807 Vector lineSegment(p1, p2);
2808 double t = Geometry::ParameterOfLineAndPoint(p1, p2, point);
2810 Point midpoint = (p1 + p2) / 2.0;
2811 Point hFSPoint = Point(midpoint, point);
2812 Point hCS1Point = Point((p1 + midpoint) / 2.0, point);
2813 Point hCS2Point = Point((midpoint + p2) / 2.0, point);
2816 distance = v1.Magnitude();
2818 distance = v2.Magnitude();
2820 // distance = ?Det?(ls, v1) / |ls|
2821 distance = fabs((lineSegment.x * p3.y - p3.x * lineSegment.y)
2822 / lineSegment.Magnitude());
2824 if ((v1.Magnitude() * Global::zoom) < 8.0)
2825 obj->hitPoint[0] = true;
2826 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2827 obj->hitPoint[1] = true;
2828 else if ((distance * Global::zoom) < 5.0)
2829 obj->hitObject = true;
2831 if ((hFSPoint.Magnitude() * Global::zoom) < 8.0)
2832 obj->hitPoint[2] = true;
2833 else if ((hCS1Point.Magnitude() * Global::zoom) < 8.0)
2834 obj->hitPoint[3] = true;
2835 else if ((hCS2Point.Magnitude() * Global::zoom) < 8.0)
2836 obj->hitPoint[4] = true;
2838 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitPoint[3] || obj->hitPoint[4] || obj->hitObject);
2840 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHP3 != obj->hitPoint[3]) || (oldHP4 != obj->hitPoint[4]) || (oldHO != obj->hitObject))
2848 Text * t = (Text *)obj;
2849 bool oldHO = obj->hitObject;
2850 obj->hitObject = false;
2852 Rect r(obj->p[0], Point(obj->p[0].x + t->extents.Width(), obj->p[0].y - t->extents.Height()));
2853 //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);
2855 if (r.Contains(point))
2856 obj->hitObject = true;
2858 obj->hovered = obj->hitObject;
2860 if (oldHO != obj->hitObject)
2868 // Containers must be recursively tested... Or do they???
2870 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.
2872 // bool oldHitObj = c->hitObject, oldHovered = c->hovered;
2873 // Object * oldClicked = c->clicked;
2875 still need to compare old state to new state, and set things up based upon that...
2876 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);
2877 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.
2879 Container * c = (Container *)obj;
2880 c->hitObject = false;
2884 VPVector flat = Flatten(c);
2886 //printf("HitTest::OTContainer (size=%li)\n", flat.size());
2887 for(VPVectorIter i=flat.begin(); i!=flat.end(); i++)
2889 Object * cObj = (Object *)(*i);
2891 // Skip the flattened containers (if any)...
2892 if (cObj->type == OTContainer)
2895 // We do it this way instead of needUpdate = HitTest() because we
2896 // are checking more than one object, and that way of doing will
2897 // not return consistent results.
2898 if (HitTest(cObj, point) == true)
2900 //printf("HitTest::OTContainer, subobj ($%llX) hit!\n", cObj);
2902 // c->hitObject = true;
2903 // c->clicked = cObj;
2904 // c->hovered = true;
2907 // Same reasons for doing it this way here apply.
2908 if (cObj->hitObject == true)
2910 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2911 c->hitObject = true;
2915 if (cObj->hitPoint[0] == true)
2917 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2918 c->hitPoint[0] = true;
2922 if (cObj->hitPoint[1] == true)
2924 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2925 c->hitPoint[1] = true;
2929 if (cObj->hitPoint[2] == true)
2931 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2932 c->hitPoint[2] = true;
2936 if (cObj->hovered == true)
2937 c->hovered = true;//*/
2950 bool DrawingView::HandleObjectClicked(void)
2952 if (dragged->type == OTDimension)
2954 Dimension * d = (Dimension *)dragged;
2958 // Hit the "flip sides" switch, so flip 'em
2959 Point temp = d->p[0];
2964 else if (d->hitPoint[3])
2966 // There are three cases here: aligned, horizontal, & vertical.
2967 // Aligned and horizontal do the same thing, vertical goes back to
2969 if (d->subtype == DTLinearVert)
2970 d->subtype = DTLinear;
2972 d->subtype = DTLinearVert;
2976 else if (d->hitPoint[4])
2978 // There are three cases here: aligned, horizontal, & vertical.
2979 // Aligned and vertical do the same thing, horizontal goes back to
2981 if (d->subtype == DTLinearHorz)
2982 d->subtype = DTLinear;
2984 d->subtype = DTLinearHorz;
2993 void DrawingView::HandleObjectMovement(Point point)
2995 Point delta = point - oldPoint;
2996 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
2997 // Object * obj = (Object *)hover[0];
2998 Object * obj = dragged;
2999 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
3000 //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"));
3005 if (obj->hitPoint[0])
3008 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
3010 if (Global::fixedLength)
3012 Vector unit = Vector::Unit(obj->p[1], point);
3013 point = obj->p[1] + (unit * obj->length);
3016 if (Global::fixedAngle)
3018 // Calculate the component of the current vector along the
3019 // fixed angle: A_compB = (A • Bu) * Bu (p[2] has the unit
3021 double magnitudeAlongB = Vector::Dot(Vector(point - obj->p[1]), obj->p[2]);
3022 point = obj->p[1] + (obj->p[2] * magnitudeAlongB);
3027 else if (obj->hitPoint[1])
3029 if (Global::fixedLength)
3031 Vector unit = Vector::Unit(obj->p[0], point);
3032 point = obj->p[0] + (unit * obj->length);
3035 if (Global::fixedAngle)
3037 double magnitudeAlongB = Vector::Dot(Vector(point - obj->p[0]), obj->p[2]);
3038 point = obj->p[0] + (obj->p[2] * magnitudeAlongB);
3043 else if (obj->hitObject)
3052 if (obj->hitPoint[0])
3054 else if (obj->hitObject)
3058 double oldRadius = obj->length;
3059 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
3061 QString text = QObject::tr("Radius: %1\nScale: %2%");
3062 informativeText = text.arg(obj->radius[0], 0, 'f', 4).arg(obj->radius[0] / oldRadius * 100.0, 0, 'f', 0);
3071 if (obj->hitPoint[0])
3073 else if (obj->hitPoint[1])
3075 // Change the Arc's span (handle #1)
3078 double angle = Vector::Angle(obj->p[0], point);
3079 double delta = angle - obj->angle[0];
3084 obj->angle[1] -= delta;
3085 obj->angle[0] = angle;
3087 if (obj->angle[1] < 0)
3088 obj->angle[1] += TAU;
3090 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
3091 informativeText = text.arg(obj->angle[1] * RADIANS_TO_DEGREES, 0, 'f', 4).arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'f', 2).arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'f', 2);
3095 double angle = Vector::Angle(obj->p[0], point);
3096 obj->angle[0] = angle;
3097 QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
3098 informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'f', 4);
3100 else if (obj->hitPoint[2])
3102 // Change the Arc's span (handle #2)
3105 double angle = Vector::Angle(obj->p[0], point);
3106 obj->angle[1] = angle - obj->angle[0];
3108 if (obj->angle[1] < 0)
3109 obj->angle[1] += TAU;
3111 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
3112 informativeText = text.arg(obj->angle[1] * RADIANS_TO_DEGREES, 0, 'f', 4).arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'f', 2).arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'f', 2);
3116 double angle = Vector::Angle(obj->p[0], point);
3117 obj->angle[0] = angle - obj->angle[1];
3119 if (obj->angle[0] < 0)
3120 obj->angle[0] += TAU;
3122 double endAngle = obj->angle[0] + obj->angle[1];
3127 QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
3128 informativeText = text.arg(endAngle * RADIANS_TO_DEGREES, 0, 'f', 4);
3130 else if (obj->hitObject)
3134 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
3135 QString text = QObject::tr("Radius: %1");
3136 informativeText = text.arg(obj->radius[0], 0, 'f', 4);
3147 // Do this for now...
3148 ((Polyline *)obj)->Translate(delta);
3149 // Polyline * pl = (Polyline *)obj;
3151 // for(long unsigned int i=0; i<pl->points.size(); i++)
3152 // pl->points[i] += delta;
3154 Polyline * pl = (Polyline *)obj;
3156 for(long unsigned int i=0; i<(pl->points.size()-1); i++)
3163 if (obj->hitPoint[0])
3165 else if (obj->hitPoint[1])
3167 else if (obj->hitObject)
3169 // Move measurement lines in/out
3172 Dimension * d = (Dimension *)obj;
3173 double dist = Geometry::DistanceToLineFromPoint(d->lp[0], d->lp[1], point);
3174 float scaledThickness = Global::scale * obj->thickness;
3175 // Looks like offset is 0 to +MAX, but line is at 10.0. So
3176 // anything less than 10.0 should set the offset to 0.
3179 if (dist > (10.0 * scaledThickness))
3180 d->offset = dist - (10.0 * scaledThickness);
3198 // This is shitty, but works for now until I can code up something
3201 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.
3203 // TranslateObject(obj, delta);
3204 TranslateContainer((Container *)obj, point, delta);
3212 void DrawingView::AddDimensionTo(void * o)
3214 Object * obj = (Object *)o;
3219 document.Add(new Dimension(obj->p[0], obj->p[1]));