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;
465 for(VPVectorIter i=pl->points.begin(); i!=pl->points.end(); i++)
467 if (i != pl->points.begin())
469 Point p = ((Object *)(*i))->p[0];
470 double bump = ((Object *)(*i))->length;
473 painter->DrawLine(lastp, p);
476 Arc a = Geometry::Unpack(lastp, p, lastbump);
477 painter->DrawArc(a.p[0], a.radius[0], a.angle[0], a.angle[1]);
485 lastp = ((Object *)(*i))->p[0];
486 lastbump = ((Object *)(*i))->length;
495 Dimension * d = (Dimension *)obj;
497 Vector v(d->p[0], d->p[1]);
498 double angle = v.Angle();
499 Vector unit = v.Unit();
500 d->lp[0] = d->p[0], d->lp[1] = d->p[1];
502 double x1, y1, length;
504 if (d->subtype == DTLinearVert)
506 if ((angle < 0) || (angle > HALF_TAU))
508 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
509 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
510 ortho = Vector(1.0, 0);
511 angle = THREE_QTR_TAU;
515 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
516 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
517 ortho = Vector(-1.0, 0);
521 d->lp[0].x = d->lp[1].x = x1;
522 length = fabs(d->p[0].y - d->p[1].y);
524 else if (d->subtype == DTLinearHorz)
526 if ((angle < QTR_TAU) || (angle > THREE_QTR_TAU))
528 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
529 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
530 ortho = Vector(0, 1.0);
535 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
536 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
537 ortho = Vector(0, -1.0);
541 d->lp[0].y = d->lp[1].y = y1;
542 length = fabs(d->p[0].x - d->p[1].x);
544 else if (d->subtype == DTLinear)
546 angle = Vector(d->lp[0], d->lp[1]).Angle();
547 ortho = Vector::Normal(d->lp[0], d->lp[1]);
548 length = v.Magnitude();
551 unit = Vector(d->lp[0], d->lp[1]).Unit();
553 Point p1 = d->lp[0] + (ortho * (d->offset + (10.0 * scaledThickness)));
554 Point p2 = d->lp[1] + (ortho * (d->offset + (10.0 * scaledThickness)));
555 Point p3 = d->lp[0] + (ortho * (d->offset + (16.0 * scaledThickness)));
556 Point p4 = d->lp[1] + (ortho * (d->offset + (16.0 * scaledThickness)));
557 Point p5 = d->p[0] + (ortho * 4.0 * scaledThickness);
558 Point p6 = d->p[1] + (ortho * 4.0 * scaledThickness);
561 The numbers hardcoded into here, what are they?
562 I believe they are pixels.
564 // Draw extension lines (if certain type)
565 painter->DrawLine(p3, p5);
566 painter->DrawLine(p4, p6);
568 // Calculate whether or not the arrowheads are too crowded to put
569 // inside the extension lines. 9.0 is the length of the arrowhead.
570 double t = Geometry::ParameterOfLineAndPoint(d->lp[0], d->lp[1], d->lp[1] - (unit * 9.0 * scaledThickness));
572 // On the screen, it's acting like this is actually 58%...
573 // This is correct, we want it to happen at > 50%
576 // Draw main dimension line + arrowheads
577 painter->DrawLine(p1, p2);
578 painter->DrawArrowhead(p1, p2, scaledThickness);
579 painter->DrawArrowhead(p2, p1, scaledThickness);
583 // Draw outside arrowheads
584 Point p7 = p1 - (unit * 9.0 * scaledThickness);
585 Point p8 = p2 + (unit * 9.0 * scaledThickness);
586 painter->DrawArrowhead(p1, p7, scaledThickness);
587 painter->DrawArrowhead(p2, p8, scaledThickness);
588 painter->DrawLine(p1, p1 - (unit * 14.0 * scaledThickness));
589 painter->DrawLine(p2, p2 + (unit * 14.0 * scaledThickness));
592 // Draw length of dimension line...
593 painter->SetFont(QFont("Arial", 8.0 * Global::zoom * scaledThickness));
594 Point ctr = p2 + (Vector(p2, p1) / 2.0);
596 QString dimText = GetDimensionText(&document, length);
599 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().
601 painter->DrawAngledText(ctr, angle, dimText, scaledThickness);
605 Point hp1 = (p1 + p2) / 2.0;
606 Point hp2 = (p1 + hp1) / 2.0;
607 Point hp3 = (hp1 + p2) / 2.0;
611 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
612 painter->SetBrush(QBrush(QColor(Qt::magenta)));
613 painter->DrawArrowHandle(hp1, ortho.Angle() + HALF_TAU);
614 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
617 painter->DrawHandle(hp1);
618 painter->SetPen(QPen(Qt::blue, 1.0 * Global::zoom * scaledThickness, Qt::SolidLine));
622 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
623 painter->SetBrush(QBrush(QColor(Qt::magenta)));
624 painter->DrawArrowToLineHandle(hp2, (d->subtype == DTLinearVert ? v.Angle() - QTR_TAU : (v.Angle() < HALF_TAU ? HALF_TAU : 0)));
625 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
628 painter->DrawHandle(hp2);
629 painter->SetPen(QPen(Qt::blue, 1.0 * Global::zoom * scaledThickness, Qt::SolidLine));
633 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
634 painter->SetBrush(QBrush(QColor(Qt::magenta)));
635 painter->DrawArrowToLineHandle(hp3, (d->subtype == DTLinearHorz ? v.Angle() - QTR_TAU : (v.Angle() > HALF_TAU && v.Angle() < THREE_QTR_TAU ? THREE_QTR_TAU : QTR_TAU)));
636 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
639 painter->DrawHandle(hp3);
642 if (obj->hitPoint[0])
643 painter->DrawHandle(obj->p[0]);
645 if (obj->hitPoint[1])
646 painter->DrawHandle(obj->p[1]);
653 Text * t = (Text *)obj;
655 if (t->measured == false)
657 t->extents = painter->MeasureTextObject(t->s.c_str(), scaledThickness);
661 painter->DrawTextObject(t->p[0], t->s.c_str(), scaledThickness, t->angle[0]);
679 // Containers require recursive rendering...
680 Container * c = (Container *)obj;
681 //printf("About to render container: # objs=%i, layer=%i\n", (*c).objects.size(), layer);
682 RenderObjects(painter, (*c).objects, layer, ignoreLayer);
684 //printf("Container extents: <%lf, %lf>, <%lf, %lf>\nsize: %i\n", r.l, r.t, r.r, r.b, c->objects.size());
685 // Containers also have special indicators showing they are selected
686 if (c->selected || c->hitObject)
688 // Rect r = GetObjectExtents(obj);
689 // painter->DrawRectCorners(r);
690 painter->DrawRectCorners(Rect(c->p[0], c->p[1]));
701 supressSelected = false;
705 // This toggles the selection being hovered (typically, only 1 object). We
706 // toggle because the CTRL key might be held, in which case, we want to
707 // deselect a selected object.
709 void DrawingView::HandleSelectionClick(VPVector & v)
713 for(VPVectorIter i=v.begin(); i!=v.end(); i++)
715 Object * obj = (Object *)(*i);
718 obj->selected = !obj->selected;
724 for(VPVectorIter i=v.begin(); i!=v.end(); i++)
725 ((Object *)(*i))->selected = false;
727 // Check if the hover changed, and if so, reset the selection stack
728 if (oldHover.size() != v.size())
735 // Select next object in the stack under the cursor
738 if (currentSelect >= v.size())
742 dragged = (Object *)v[currentSelect];
743 dragged->selected = true;
746 VPVector DrawingView::GetSelection(void)
750 for(VPVectorIter i=document.objects.begin(); i!=document.objects.end(); i++)
752 if (((Object *)(*i))->selected)
760 // When testing for hovered intersections, we need to be able to exclude some
761 // objects which have funky characteristics or handles; so we allow for that
764 VPVector DrawingView::GetHovered(bool exclude/*= false*/)
768 for(VPVectorIter i=document.objects.begin(); i!=document.objects.end(); i++)
770 Object * obj = (Object *)(*i);
775 && ((obj->type == OTDimension)
776 || ((obj->type == OTCircle) && (obj->hitPoint[0] == true))
777 || ((obj->type == OTArc) && (obj->hitPoint[0] == true))
778 || (draggingObject && (obj == dragged))))
788 void DrawingView::MoveSelectedToLayer(int layer)
790 for(VPVectorIter i=document.objects.begin(); i!=document.objects.end(); i++)
792 Object * obj = (Object *)(*i);
794 if (obj->selected || obj->hovered)
799 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
801 Global::screenSize = Vector(size().width(), size().height());
804 void DrawingView::ToolHandler(int mode, Point p)
806 // Drop angle snap until it's needed
809 if (Global::tool == TTLine)
810 LineHandler(mode, p);
811 else if (Global::tool == TTCircle)
812 CircleHandler(mode, p);
813 else if (Global::tool == TTArc)
815 else if (Global::tool == TTRotate)
816 RotateHandler(mode, p);
817 else if (Global::tool == TTMirror)
818 MirrorHandler(mode, p);
819 else if (Global::tool == TTDimension)
820 DimensionHandler(mode, p);
821 else if (Global::tool == TTDelete)
822 DeleteHandler(mode, p);
823 else if (Global::tool == TTTriangulate)
824 TriangulateHandler(mode, p);
825 else if (Global::tool == TTTrim)
826 TrimHandler(mode, p);
827 else if (Global::tool == TTParallel)
828 ParallelHandler(mode, p);
831 void DrawingView::ToolDraw(Painter * painter)
833 switch (Global::tool)
836 if (Global::toolState == TSNone)
838 painter->DrawHandle(toolPoint[0]);
840 else if ((Global::toolState == TSPoint2) && shiftDown)
842 painter->DrawHandle(toolPoint[1]);
846 painter->DrawLine(toolPoint[0], toolPoint[1]);
847 painter->DrawHandle(toolPoint[1]);
849 Vector v(toolPoint[0], toolPoint[1]);
850 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
851 double absLength = v.Magnitude();
852 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
853 informativeText = text.arg(absLength).arg(absAngle);
859 if (Global::toolState == TSNone)
861 painter->DrawHandle(toolPoint[0]);
863 else if ((Global::toolState == TSPoint2) && shiftDown)
865 painter->DrawHandle(toolPoint[1]);
869 painter->DrawCross(toolPoint[0]);
870 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
871 painter->SetBrush(QBrush(Qt::NoBrush));
872 painter->DrawEllipse(toolPoint[0], length, length);
873 QString text = tr("Radius: %1 in.");
874 informativeText = text.arg(length);
880 if (Global::toolState == TSNone)
882 painter->DrawHandle(toolPoint[0]);
884 else if (Global::toolState == TSPoint2)
886 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
887 painter->SetBrush(QBrush(Qt::NoBrush));
888 painter->DrawEllipse(toolPoint[0], length, length);
889 painter->DrawLine(toolPoint[0], toolPoint[1]);
890 painter->DrawHandle(toolPoint[1]);
891 QString text = tr("Radius: %1 in.");
892 informativeText = text.arg(length);
894 else if (Global::toolState == TSPoint3)
896 double angle = Vector::Angle(toolPoint[0], toolPoint[2]);
897 painter->DrawLine(toolPoint[0], toolPoint[2]);
898 painter->SetBrush(QBrush(Qt::NoBrush));
899 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
900 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
901 QString text = tr("Angle start: %1") + QChar(0x00B0);
902 informativeText = text.arg(RADIANS_TO_DEGREES * angle);
906 double angle = Vector::Angle(toolPoint[0], toolPoint[3]);
907 double span = angle - toolPoint[2].x;
912 painter->DrawLine(toolPoint[0], toolPoint[3]);
913 painter->SetBrush(QBrush(Qt::NoBrush));
914 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
915 painter->SetPen(0xFF00FF, 2.0, LSSolid);
916 painter->DrawArc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
917 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
918 QString text = tr("Arc span: %1") + QChar(0x00B0);
919 informativeText = text.arg(RADIANS_TO_DEGREES * span);
925 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
926 painter->DrawHandle(toolPoint[0]);
927 else if ((Global::toolState == TSPoint2) && shiftDown)
928 painter->DrawHandle(toolPoint[1]);
931 if (toolPoint[0] == toolPoint[1])
934 painter->DrawLine(toolPoint[0], toolPoint[1]);
936 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
937 QString text = QChar(0x2221) + QObject::tr(": %1");
938 informativeText = text.arg(absAngle);
941 informativeText += " (Copy)";
947 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
948 painter->DrawHandle(toolPoint[0]);
949 else if ((Global::toolState == TSPoint2) && shiftDown)
950 painter->DrawHandle(toolPoint[1]);
953 if (toolPoint[0] == toolPoint[1])
956 Point mirrorPoint = toolPoint[0] + Vector(toolPoint[1], toolPoint[0]);
957 painter->DrawLine(mirrorPoint, toolPoint[1]);
959 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
961 if (absAngle > 180.0)
964 QString text = QChar(0x2221) + QObject::tr(": %1");
965 informativeText = text.arg(absAngle);
968 informativeText += " (Copy)";
974 if (Global::toolState == TSNone)
976 painter->DrawHandle(toolPoint[0]);
978 else if ((Global::toolState == TSPoint2) && shiftDown)
980 painter->DrawHandle(toolPoint[1]);
984 painter->DrawLine(toolPoint[0], toolPoint[1]);
985 painter->DrawHandle(toolPoint[1]);
987 Vector v(toolPoint[0], toolPoint[1]);
988 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
989 double absLength = v.Magnitude();
990 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
991 informativeText = text.arg(absLength).arg(absAngle);
997 if (toolObj[0] != NULL)
999 // We're assuming ATM it's just a line...
1000 painter->SetPen(0xAF0000, 3.0, LSSolid);
1001 painter->DrawLine(toolPoint[0], toolPoint[1]);
1002 // QString text = tr("Arc span: %1") + QChar(0x00B0);
1003 // informativeText = text.arg(RADIANS_TO_DEGREES * span);
1009 if (Global::toolState == TSPoint1)
1011 painter->SetPen(0xFF00FF, 2.0, LSSolid);
1012 painter->SetBrush(QBrush(Qt::NoBrush));
1014 double length = Vector::Magnitude(toolObj[0]->p[0], toolPoint[0]);
1015 bool inside = (length >= toolObj[0]->radius[0] ? false : true);
1017 for(int i=1; i<=Global::parallelNum; i++)
1019 if (toolObj[0]->type == OTLine)
1021 painter->DrawLine(toolObj[0]->p[0] + (toolPoint[0] * Global::parallelDist * (double)i), toolObj[0]->p[1] + (toolPoint[0] * Global::parallelDist * (double)i));
1023 else if ((toolObj[0]->type == OTCircle) || (toolObj[0]->type == OTArc))
1025 double radius = toolObj[0]->radius[0] + ((double)i * Global::parallelDist * (inside ? -1.0 : 1.0));
1029 if (toolObj[0]->type == OTCircle)
1030 painter->DrawEllipse(toolObj[0]->p[0], radius, radius);
1032 painter->DrawArc(toolObj[0]->p[0], radius, toolObj[0]->angle[0], toolObj[0]->angle[1]);
1045 void DrawingView::LineHandler(int mode, Point p)
1050 /* toolObj[0] = NULL;
1052 // Check to see if we can do a circle tangent snap
1053 if (numHovered == 1)
1055 VPVector hover = GetHovered();
1056 Object * obj = (Object *)hover[0];
1058 // 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! :-)
1059 if ((obj->type == OTCircle) && (obj->hitPoint[0] == false))
1063 if (Global::toolState == TSNone)
1071 if (Global::toolState == TSNone)
1076 /* bool isCircle = false;
1078 if (numHovered == 1)
1080 VPVector hover = GetHovered();
1081 Object * obj = (Object *)hover[0];
1083 if ((obj->type == OTCircle) && (obj->hitPoint[0] == false))
1090 // Adjust initial point if it's on a circle (tangent point)
1091 if (toolObj[0] != NULL)
1095 Geometry::FindTangents(toolObj[0], toolObj[1]);
1097 if (Global::numIntersectPoints > 0)
1099 toolPoint[0] = Global::intersectPoint[0];
1100 toolPoint[1] = Global::intersectPoint[1];
1105 Geometry::FindTangents(toolObj[0], p);
1107 if (Global::numIntersectPoints > 0)
1108 toolPoint[0] = Global::intersectPoint[0];
1115 Geometry::FindTangents(toolObj[1], toolPoint[0]);
1117 if (Global::numIntersectPoints > 0)
1118 toolPoint[1] = Global::intersectPoint[0];
1126 if (Global::toolState == TSNone)
1128 Global::toolState = TSPoint2;
1129 // Prevent spurious line from drawing...
1130 toolPoint[1] = toolPoint[0];
1132 else if ((Global::toolState == TSPoint2) && shiftDown)
1134 // Key override is telling us to make a new line, not continue the
1136 toolPoint[0] = toolPoint[1];
1140 Line * l = new Line(toolPoint[0], toolPoint[1], Global::penWidth, Global::penColor, Global::penStyle);
1141 l->layer = Global::activeLayer;
1142 document.objects.push_back(l);
1143 toolPoint[0] = toolPoint[1];
1148 void DrawingView::CircleHandler(int mode, Point p)
1153 if (Global::toolState == TSNone)
1161 if (Global::toolState == TSNone)
1169 if (Global::toolState == TSNone)
1171 Global::toolState = TSPoint2;
1172 // Prevent spurious line from drawing...
1173 toolPoint[1] = toolPoint[0];
1175 else if ((Global::toolState == TSPoint2) && shiftDown)
1177 // Key override is telling us to make a new line, not continue the
1179 toolPoint[0] = toolPoint[1];
1183 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
1184 Circle * c = new Circle(toolPoint[0], length, Global::penWidth, Global::penColor, Global::penStyle);
1185 c->layer = Global::activeLayer;
1186 document.objects.push_back(c);
1187 toolPoint[0] = toolPoint[1];
1188 Global::toolState = TSNone;
1193 void DrawingView::ArcHandler(int mode, Point p)
1198 if (Global::toolState == TSNone)
1200 else if (Global::toolState == TSPoint2)
1202 else if (Global::toolState == TSPoint3)
1210 if (Global::toolState == TSNone)
1212 else if (Global::toolState == TSPoint2)
1214 else if (Global::toolState == TSPoint3)
1228 if (Global::toolState == TSNone)
1230 // Prevent spurious line from drawing...
1231 toolPoint[1] = toolPoint[0];
1232 Global::toolState = TSPoint2;
1234 else if (Global::toolState == TSPoint2)
1238 // Key override is telling us to start arc at new center, not
1239 // continue the current one.
1240 toolPoint[0] = toolPoint[1];
1244 // Set the radius in toolPoint[1].x
1245 toolPoint[1].x = Vector::Magnitude(toolPoint[0], toolPoint[1]);
1246 Global::toolState = TSPoint3;
1248 else if (Global::toolState == TSPoint3)
1250 // Set the angle in toolPoint[2].x
1251 toolPoint[2].x = Vector::Angle(toolPoint[0], toolPoint[2]);
1252 Global::toolState = TSPoint4;
1256 double endAngle = Vector::Angle(toolPoint[0], toolPoint[3]);
1257 double span = endAngle - toolPoint[2].x;
1262 Arc * arc = new Arc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span, Global::penWidth, Global::penColor, Global::penStyle);
1263 arc->layer = Global::activeLayer;
1264 document.objects.push_back(arc);
1265 Global::toolState = TSNone;
1270 void DrawingView::RotateHandler(int mode, Point p)
1275 if (Global::toolState == TSNone)
1278 // SavePointsFrom(select, toolScratch);
1279 CopyObjects(select, toolScratch2);
1280 Global::toolState = TSPoint1;
1282 else if (Global::toolState == TSPoint1)
1290 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1292 else if (Global::toolState == TSPoint2)
1300 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1301 VPVectorIter j = select.begin();
1302 // std::vector<Object>::iterator i = toolScratch.begin();
1303 VPVectorIter i = toolScratch2.begin();
1305 // for(; i!=toolScratch.end(); i++, j++)
1306 for(; i!=toolScratch2.end(); i++, j++)
1308 // Object objT = *i;
1309 // Point p1 = Geometry::RotatePointAroundPoint(objT.p[0], toolPoint[0], angle);
1310 // Point p2 = Geometry::RotatePointAroundPoint(objT.p[1], toolPoint[0], angle);
1311 Object * objT = (Object *)(*i);
1312 Object * objS = (Object *)(*j);
1314 Point p1 = Geometry::RotatePointAroundPoint(objT->p[0], toolPoint[0], angle);
1315 Point p2 = Geometry::RotatePointAroundPoint(objT->p[1], toolPoint[0], angle);
1320 // if (objT.type == OTArc || objT.type == OTText)
1321 if (objT->type == OTArc || objT->type == OTText)
1323 // objS->angle[0] = objT.angle[0] + angle;
1324 objS->angle[0] = objT->angle[0] + angle;
1326 if (objS->angle[0] > TAU)
1327 objS->angle[0] -= TAU;
1329 // else if (objT.type == OTContainer)
1330 else if (objT->type == OTContainer)
1332 // 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...]
1333 // Container * c = (Container *)&objT;
1334 Container * c = (Container *)objT;
1335 Container * c2 = (Container *)objS;
1336 VPVectorIter l = c->objects.begin();
1337 // TODO: Rotate items in the container
1338 // TODO: Make this recursive
1339 for(VPVectorIter k=c2->objects.begin(); k!=c2->objects.end(); k++, l++)
1341 Object * obj3 = (Object *)(*k);
1342 Object * obj4 = (Object *)(*l);
1344 p1 = Geometry::RotatePointAroundPoint(obj4->p[0], toolPoint[0], angle);
1345 p2 = Geometry::RotatePointAroundPoint(obj4->p[1], toolPoint[0], angle);
1349 // obj3->angle[0] = objT.angle[0] + angle;
1350 obj3->angle[0] = obj4->angle[0] + angle;
1352 if (obj3->angle[0] > TAU)
1353 obj3->angle[0] -= TAU;
1356 Rect r = GetObjectExtents(objS);
1357 c2->p[0] = r.TopLeft();
1358 c2->p[1] = r.BottomRight();
1366 if (Global::toolState == TSPoint1)
1368 Global::toolState = TSPoint2;
1369 // Prevent spurious line from drawing...
1370 toolPoint[1] = toolPoint[0];
1372 else if ((Global::toolState == TSPoint2) && shiftDown)
1374 // Key override is telling us to make a new line, not continue the
1376 toolPoint[0] = toolPoint[1];
1380 // Either we're finished with our rotate, or we're stamping a copy.
1383 // Stamp a copy of the selection at the current rotation & bail
1385 CopyObjects(select, temp);
1386 ClearSelected(temp);
1387 AddObjectsTo(document.objects, temp);
1388 // RestorePointsTo(select, toolScratch);
1389 RestorePointsTo(select, toolScratch2);
1394 Global::toolState = TSPoint1;
1395 // SavePointsFrom(select, toolScratch);
1396 DeleteContents(toolScratch2);
1397 CopyObjects(select, toolScratch2);
1403 // Reset the selection if shift held down...
1405 // RestorePointsTo(select, toolScratch);
1406 RestorePointsTo(select, toolScratch2);
1411 // Reset selection when key is let up
1413 RotateHandler(ToolMouseMove, toolPoint[1]);
1418 // RestorePointsTo(select, toolScratch);
1419 RestorePointsTo(select, toolScratch2);
1420 DeleteContents(toolScratch2);
1424 void DrawingView::MirrorHandler(int mode, Point p)
1429 if (Global::toolState == TSNone)
1432 SavePointsFrom(select, toolScratch);
1433 Global::toolState = TSPoint1;
1435 else if (Global::toolState == TSPoint1)
1443 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1445 else if (Global::toolState == TSPoint2)
1453 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1454 VPVectorIter j = select.begin();
1455 std::vector<Object>::iterator i = toolScratch.begin();
1457 for(; i!=toolScratch.end(); i++, j++)
1460 Point p1 = Geometry::MirrorPointAroundLine(obj.p[0], toolPoint[0], toolPoint[1]);
1461 Point p2 = Geometry::MirrorPointAroundLine(obj.p[1], toolPoint[0], toolPoint[1]);
1462 Object * obj2 = (Object *)(*j);
1467 N.B.: When mirroring an arc thru a horizontal axis, this causes the arc to have
1468 a negative start angle which makes it impossible to interact with.
1471 if (obj.type == OTArc)
1473 // This is 2*mirror angle - obj angle - obj span
1474 obj2->angle[0] = (2.0 * angle) - obj.angle[0] - obj.angle[1];
1476 if (obj2->angle[0] > TAU)
1477 obj2->angle[0] -= TAU;
1485 if (Global::toolState == TSPoint1)
1487 Global::toolState = TSPoint2;
1488 // Prevent spurious line from drawing...
1489 toolPoint[1] = toolPoint[0];
1491 else if ((Global::toolState == TSPoint2) && shiftDown)
1493 // Key override is telling us to make a new line, not continue the
1495 toolPoint[0] = toolPoint[1];
1499 // Either we're finished with our rotate, or we're stamping a copy.
1502 // Stamp a copy of the selection at the current rotation & bail
1504 CopyObjects(select, temp);
1505 ClearSelected(temp);
1506 AddObjectsTo(document.objects, temp);
1507 RestorePointsTo(select, toolScratch);
1512 Global::toolState = TSPoint1;
1513 SavePointsFrom(select, toolScratch);
1519 // Reset the selection if shift held down...
1521 RestorePointsTo(select, toolScratch);
1526 // Reset selection when key is let up
1528 MirrorHandler(ToolMouseMove, toolPoint[1]);
1533 RestorePointsTo(select, toolScratch);
1537 void DrawingView::DimensionHandler(int mode, Point p)
1542 if (Global::toolState == TSNone)
1550 if (Global::toolState == TSNone)
1558 if (Global::toolState == TSNone)
1560 Global::toolState = TSPoint2;
1561 // Prevent spurious line from drawing...
1562 toolPoint[1] = toolPoint[0];
1564 else if ((Global::toolState == TSPoint2) && shiftDown)
1566 // Key override is telling us to make a new line, not continue the
1568 toolPoint[0] = toolPoint[1];
1572 Dimension * d = new Dimension(toolPoint[0], toolPoint[1], DTLinear, 0, Global::penWidth);
1573 d->layer = Global::activeLayer;
1574 document.objects.push_back(d);
1575 Global::toolState = TSNone;
1580 void DrawingView::DeleteHandler(int mode, Point /*p*/)
1586 VPVector hovered = GetHovered();
1588 RemoveHoveredObjects(document.objects);
1589 DeleteContents(hovered);
1610 void DrawingView::TriangulateHandler(int mode, Point /*p*/)
1616 // Skip if nothing hovered...
1617 if (numHovered != 1)
1620 VPVector hover = GetHovered();
1621 Object * obj = (Object *)hover[0];
1623 // Skip if it's not a line...
1624 if (obj->type != OTLine)
1627 if (Global::toolState == TSNone)
1629 else if (Global::toolState == TSPoint2)
1638 if (Global::toolState == TSNone)
1640 else if (Global::toolState == TSPoint2)
1642 else if (Global::toolState == TSPoint3)
1656 if (Global::toolState == TSNone)
1658 Global::toolState = TSPoint2;
1660 else if (Global::toolState == TSPoint2)
1664 // Key override is telling us to start arc at new center, not
1665 // continue the current one.
1666 toolPoint[0] = toolPoint[1];
1670 Global::toolState = TSPoint3;
1674 double len2 = Vector::Magnitude(toolObj[1]->p[0], toolObj[1]->p[1]);
1675 double len3 = Vector::Magnitude(toolObj[2]->p[0], toolObj[2]->p[1]);
1677 Circle c1(toolObj[0]->p[0], len2);
1678 Circle c2(toolObj[0]->p[1], len3);
1680 Geometry::CheckCircleToCircleIntersection((Object *)&c1, (Object *)&c2);
1682 // Only move lines if the triangle formed by them is not degenerate
1683 if (Global::numIntersectPoints > 0)
1685 toolObj[1]->p[0] = toolObj[0]->p[0];
1686 toolObj[1]->p[1] = Global::intersectPoint[0];
1688 toolObj[2]->p[0] = Global::intersectPoint[0];
1689 toolObj[2]->p[1] = toolObj[0]->p[1];
1692 Global::toolState = TSNone;
1697 void DrawingView::TrimHandler(int mode, Point p)
1708 // Bail out if nothing hovered...
1709 if (numHovered != 1)
1715 VPVector hover = GetHovered();
1716 Object * obj = (Object *)hover[0];
1718 // Skip if it's not a line...
1719 if (obj->type != OTLine)
1726 double hoveredParam = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], p);
1727 double t = 0, u = 1.0;
1729 // Currently only deal with line against line trimming, can expand to
1730 // others as well (line/circle, circle/circle, line/arc, etc)
1732 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1734 obj = (Object *)(*i);
1736 if (obj == toolObj[0])
1738 else if (obj->type != OTLine)
1741 Geometry::CheckLineToLineIntersection(toolObj[0], obj);
1743 if (Global::numIntersectParams > 0)
1745 // Skip endpoint-endpoint intersections
1746 if ((Global::numIntersectParams == 2)
1747 && (Global::intersectParam[0] == 0
1748 || Global::intersectParam[0] == 1.0)
1749 && (Global::intersectParam[1] == 0
1750 || Global::intersectParam[1] == 1.0))
1753 // Mark the line segment somehow (the side the mouse is on) so that it can be drawn & trimmed when we hit ToolMouseDown.
1754 if ((Global::intersectParam[0] > t) && (Global::intersectParam[0] < hoveredParam))
1755 t = Global::intersectParam[0];
1757 if ((Global::intersectParam[0] < u) && (Global::intersectParam[0] > hoveredParam))
1758 u = Global::intersectParam[0];
1764 toolPoint[0] = Geometry::GetPointForParameter(toolObj[0], t);
1765 toolPoint[1] = Geometry::GetPointForParameter(toolObj[0], u);
1771 // Bail out if there's no object to trim
1772 if (toolObj[0] == NULL)
1775 Vector v(toolObj[0]->p[0], toolObj[0]->p[1]);
1777 // Check to see which case we have.
1778 if ((toolParam[0] == 0) && (toolParam[1] == 1.0))
1780 // There was no intersection, so delete the object
1781 toolObj[0]->selected = true;
1782 DeleteSelectedObjects(document.objects);
1784 else if (toolParam[0] == 0)
1786 // We delete the end near point #1
1787 toolObj[0]->p[0] = toolObj[0]->p[0] + (v * toolParam[1]);
1789 else if (toolParam[1] == 1.0)
1791 // We delete the end near point #2
1792 toolObj[0]->p[1] = toolObj[0]->p[0] + (v * toolParam[0]);
1796 // We delete the segment in between, and create a new line in the process
1797 Point p1 = toolObj[0]->p[0] + (v * toolParam[0]);
1798 Point p2 = toolObj[0]->p[0] + (v * toolParam[1]);
1799 Point p3 = toolObj[0]->p[1];
1800 toolObj[0]->p[1] = p1;
1801 Line * l = new Line(p2, p3, toolObj[0]->thickness, toolObj[0]->color, toolObj[0]->style);
1802 document.objects.push_back(l);
1803 // Global::toolState = TSNone;
1806 toolObj[0]->hitObject = toolObj[0]->hitPoint[0] = toolObj[0]->hitPoint[1] = false;
1822 void DrawingView::ParallelHandler(int mode, Point p)
1827 if (numHovered == 1)
1829 // New selection made...
1830 VPVector hover = GetHovered();
1831 toolObj[0] = (Object *)hover[0];
1832 Global::toolState = TSNone;
1834 else if ((numHovered == 0) && (toolObj[0] != NULL))
1836 double length = Vector::Magnitude(toolObj[0]->p[0], toolPoint[0]);
1837 bool inside = (length >= toolObj[0]->radius[0] ? false : true);
1839 // Stamp out new parallel object(s)...
1840 for(int i=1; i<=Global::parallelNum; i++)
1842 if (toolObj[0]->type == OTLine)
1844 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);
1845 // Should probably have a user selection for this whether it goes into the selected objects layer or the global layer...
1846 l->layer = toolObj[0]->layer;
1847 document.objects.push_back(l);
1849 else if (toolObj[0]->type == OTCircle)
1851 double radius = toolObj[0]->radius[0] + ((double)i * Global::parallelDist * (inside ? -1.0 : 1.0));
1855 Circle * c = new Circle(toolObj[0]->p[0], radius, Global::penWidth, Global::penColor, Global::penStyle);
1856 c->layer = toolObj[0]->layer;
1857 document.objects.push_back(c);
1860 else if (toolObj[0]->type == OTArc)
1862 double radius = toolObj[0]->radius[0] + ((double)i * Global::parallelDist * (inside ? -1.0 : 1.0));
1866 Arc * a = new Arc(toolObj[0]->p[0], radius, toolObj[0]->angle[0], toolObj[0]->angle[1], Global::penWidth, Global::penColor, Global::penStyle);
1867 a->layer = toolObj[0]->layer;
1868 document.objects.push_back(a);
1873 // Then reset the state
1874 toolObj[0]->selected = false;
1876 Global::toolState = TSNone;
1882 if ((numHovered == 0) && toolObj[0] != NULL)
1883 Global::toolState = TSPoint1;
1885 Global::toolState = TSNone;
1887 if (Global::toolState == TSPoint1)
1889 // Figure out which side of the object we're on, and draw the preview on that side...
1890 if (toolObj[0]->type == OTLine)
1892 Vector normal = Geometry::GetNormalOfPointAndLine(p, (Line *)toolObj[0]);
1893 toolPoint[0] = normal;
1895 else if ((toolObj[0]->type == OTCircle) || (toolObj[0]->type == OTArc))
1917 void DrawingView::mousePressEvent(QMouseEvent * event)
1919 if (event->button() == Qt::LeftButton)
1921 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1923 // Handle tool processing, if any
1926 if (hoveringIntersection)
1927 point = intersectionPoint;
1928 else if (hoverPointValid)
1930 else if (Global::snapToGrid)
1931 point = SnapPointToGrid(point);
1933 ToolHandler(ToolMouseDown, point);
1937 // Clear the selection only if CTRL isn't being held on click
1939 ClearSelected(document.objects);
1941 // If any objects are being hovered on click, deal with 'em
1944 VPVector hover2 = GetHovered();
1945 dragged = (Object *)hover2[0];
1947 // Alert the pen widget
1948 if (Global::penDropper)
1950 Global::penColor = dragged->color;
1951 Global::penWidth = dragged->thickness;
1952 Global::penStyle = dragged->style;
1953 emit ObjectSelected(dragged);
1956 else if (Global::penStamp)
1958 dragged->color = Global::penColor;
1959 dragged->thickness = Global::penWidth;
1960 dragged->style = Global::penStyle;
1963 // See if anything is using just a straight click on a custom
1964 // object handle (like Dimension objects)
1965 else if (HandleObjectClicked())
1971 draggingObject = true;
1972 HandleSelectionClick(hover2);
1973 update(); // needed??
1975 // Needed for grab & moving objects
1976 // We do it *after*... why? (doesn't seem to confer any advantage...)
1977 if (hoveringIntersection)
1978 oldPoint = intersectionPoint;
1979 else if (hoverPointValid)
1980 oldPoint = hoverPoint;
1981 else if (Global::snapToGrid)
1982 oldPoint = SnapPointToGrid(point);
1984 // Needed for fixed length handling
1985 if (Global::fixedLength)
1987 if (dragged->type == OTLine)
1988 dragged->length = ((Line *)dragged)->Length();
1991 // Needed for fixed angle handling
1992 if (Global::fixedAngle)
1994 if (dragged->type == OTLine)
1995 dragged->p[2] = ((Line *)dragged)->Unit();
1998 if (dragged->type == OTCircle)
2000 // Save for informative text, uh, er, informing
2001 dragged->length = dragged->radius[0];
2007 // Didn't hit any object and not using a tool, so do a selection
2009 Global::selectionInProgress = true;
2010 Global::selection.l = Global::selection.r = point.x;
2011 Global::selection.t = Global::selection.b = point.y;
2012 select = GetSelection();
2014 else if (event->button() == Qt::MiddleButton)
2017 oldPoint = Vector(event->x(), event->y());
2018 // Should also change the mouse pointer as well...
2019 setCursor(Qt::SizeAllCursor);
2023 void DrawingView::mouseMoveEvent(QMouseEvent * event)
2025 // It seems that wheelEvent() triggers this for some reason...
2026 if (scrollWheelSeen)
2028 scrollWheelSeen = false;
2032 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
2033 Global::selection.r = point.x;
2034 Global::selection.b = point.y;
2035 // Only needs to be done here, as mouse down is always preceded by movement
2036 Global::snapPointIsValid = false;
2037 hoveringIntersection = false;
2038 oldScrollPoint = Vector(event->x(), event->y());
2041 if ((event->buttons() & Qt::MiddleButton) || scrollDrag)
2043 point = Vector(event->x(), event->y());
2044 // Since we're using Qt coords for scrolling, we have to adjust them
2045 // here to conform to Cartesian coords, since the origin is using
2047 Vector delta(oldPoint, point);
2048 delta /= Global::zoom;
2050 Global::origin -= delta;
2052 // UpdateGridBackground();
2058 // If we're doing a selection rect, see if any objects are engulfed by it
2059 // (implies left mouse button held down)
2060 if (Global::selectionInProgress)
2062 CheckObjectBounds();
2064 // Make sure previously selected objects stay selected (CTRL held)
2065 for(VPVectorIter i=select.begin(); i!=select.end(); i++)
2067 // Make sure *not* to select items on hidden layers
2068 if (Global::layerHidden[((Object *)(*i))->layer] == false)
2069 ((Object *)(*i))->selected = true;
2076 // Do object hit testing...
2077 bool needUpdate = HitTestObjects(point);
2078 VPVector hover2 = GetHovered(true); // Exclude dimension objects and circle centers (probably need to add arc centers too) from hover (also dragged objects...)
2083 printf("mouseMoveEvent:: numHovered=%li, hover2.size()=%li\n", numHovered, hover2.size());
2085 if (hover2.size() > 0)
2086 printf(" (hover2[0]=$%llX, type=%s)\n", hover2[0], objName[((Object *)hover2[0])->type]);
2091 // Check for multi-hover...
2092 if (hover2.size() > 1)
2094 //need to check for case where hover is over 2 circles and a 3rd's center (no longer a problem, I think)...
2095 Object * obj1 = (Object *)hover2[0], * obj2 = (Object *)hover2[1];
2097 Geometry::Intersects(obj1, obj2);
2098 int numIntersecting = Global::numIntersectParams;
2099 double t = Global::intersectParam[0];
2100 double u = Global::intersectParam[1];
2102 if (numIntersecting > 0)
2104 Vector v1 = Geometry::GetPointForParameter(obj1, t);
2105 Vector v2 = Geometry::GetPointForParameter(obj2, u);
2106 QString text = tr("Intersection t=%1 (%3, %4), u=%2 (%5, %6)");
2107 informativeText = text.arg(t).arg(u).arg(v1.x).arg(v1.y).arg(v2.x).arg(v2.y);
2109 hoveringIntersection = true;
2110 intersectionPoint = v1;
2113 numIntersecting = Global::numIntersectPoints;
2115 if (numIntersecting > 0)
2117 Vector v1 = Global::intersectPoint[0];
2119 if (numIntersecting == 2)
2121 Vector v2 = Global::intersectPoint[1];
2123 if (Vector::Magnitude(v2, point) < Vector::Magnitude(v1, point))
2127 QString text = tr("Intersection <%1, %2>");
2128 informativeText = text.arg(v1.x).arg(v1.y);
2129 hoveringIntersection = true;
2130 intersectionPoint = v1;
2133 else if (hover2.size() == 1)
2135 Object * obj = (Object *)hover2[0];
2137 if (obj->type == OTLine)
2140 Not sure that this is the best way to handle this, but it works(TM)...
2142 Point midpoint = Geometry::Midpoint((Line *)obj);
2143 Vector v1 = Vector::Magnitude(midpoint, point);
2145 if ((v1.Magnitude() * Global::zoom) < 8.0)
2147 QString text = tr("Midpoint <%1, %2>");
2148 informativeText = text.arg(midpoint.x).arg(midpoint.y);
2149 hoverPointValid = true;
2150 hoverPoint = midpoint;
2154 else if (obj->type == OTCircle)
2156 if ((draggingObject && (dragged->type == OTLine)) && (dragged->hitPoint[0] || dragged->hitPoint[1]))
2158 Point p = (dragged->hitPoint[0] ? dragged->p[1] : dragged->p[0]);
2159 Geometry::FindTangents(obj, p);
2161 if (Global::numIntersectPoints > 0)
2163 hoveringIntersection = true;
2164 intersectionPoint = Geometry::NearestTo(point, Global::intersectPoint[0], Global::intersectPoint[1]);
2167 else if ((Global::tool == TTLine) && (Global::toolState == TSPoint2))
2169 Geometry::FindTangents(obj, toolPoint[0]);
2171 if (Global::numIntersectPoints > 0)
2173 hoveringIntersection = true;
2174 intersectionPoint = Geometry::NearestTo(point, Global::intersectPoint[0], Global::intersectPoint[1]);
2180 // Handle object movement (left button down & over an object)
2181 if ((event->buttons() & Qt::LeftButton) && draggingObject && !Global::tool)
2183 if (hoveringIntersection)
2184 point = intersectionPoint;
2185 else if (hoverPointValid)
2187 else if (Global::snapToGrid)
2188 point = SnapPointToGrid(point);
2190 HandleObjectMovement(point);
2196 // Do tool handling, if any are active...
2199 if (hoveringIntersection)
2200 point = intersectionPoint;
2201 else if (hoverPointValid)
2203 else if (Global::snapToGrid)
2206 point = SnapPointToAngle(point);
2208 point = SnapPointToGrid(point);
2211 ToolHandler(ToolMouseMove, point);
2214 // This is used to draw the tool crosshair...
2217 if (needUpdate || Global::tool)
2221 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
2223 if (event->button() == Qt::LeftButton)
2225 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
2226 //could set it up to use the document's update function (assumes that all object
2227 //updates are being reported correctly:
2228 // if (document.NeedsUpdate())
2229 // Do an update if collided with at least *one* object in the document
2235 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
2236 ToolHandler(ToolMouseUp, point);
2240 Global::selectionInProgress = false;
2241 informativeText.clear();
2243 // Should we be doing this automagically? Hmm...
2244 // Clear our vectors
2248 // 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???])
2249 select = GetSelection();
2251 draggingObject = false;
2253 else if (event->button() == Qt::MiddleButton)
2257 if (Global::penStamp)
2258 setCursor(curMarker);
2259 else if (Global::penDropper)
2260 setCursor(curDropper);
2262 setCursor(Qt::ArrowCursor);
2264 // Need to convert this, since it's in Qt coordinates (for wheelEvent)
2265 oldPoint = Painter::QtToCartesianCoords(oldPoint);
2269 void DrawingView::wheelEvent(QWheelEvent * event)
2271 double zoomFactor = 1.20;
2272 scrollWheelSeen = true;
2274 if (event->angleDelta().y() < 0)
2276 if (Global::zoom > 400.0)
2279 Global::zoom *= zoomFactor;
2283 if (Global::zoom < 0.125)
2286 Global::zoom /= zoomFactor;
2289 Point np = Painter::QtToCartesianCoords(oldScrollPoint);
2290 Global::origin += (oldPoint - np);
2292 emit(NeedZoomUpdate());
2295 void DrawingView::keyPressEvent(QKeyEvent * event)
2297 bool oldShift = shiftDown;
2298 bool oldCtrl = ctrlDown;
2299 bool oldAlt = altDown;
2301 if (event->key() == Qt::Key_Shift)
2303 else if (event->key() == Qt::Key_Control)
2305 else if (event->key() == Qt::Key_Alt)
2308 // If there's a change in any of the modifier key states, pass it on to
2309 // the current tool's handler
2310 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
2313 ToolHandler(ToolKeyDown, Point(0, 0));
2318 if (oldAlt != altDown)
2321 setCursor(Qt::SizeAllCursor);
2322 oldPoint = oldScrollPoint;
2325 if (select.size() > 0)
2327 if (event->key() == Qt::Key_Up)
2329 TranslateObjects(select, Point(0, +1.0));
2332 else if (event->key() == Qt::Key_Down)
2334 TranslateObjects(select, Point(0, -1.0));
2337 else if (event->key() == Qt::Key_Right)
2339 TranslateObjects(select, Point(+1.0, 0));
2342 else if (event->key() == Qt::Key_Left)
2344 TranslateObjects(select, Point(-1.0, 0));
2350 void DrawingView::keyReleaseEvent(QKeyEvent * event)
2352 bool oldShift = shiftDown;
2353 bool oldCtrl = ctrlDown;
2354 bool oldAlt = altDown;
2356 if (event->key() == Qt::Key_Shift)
2358 else if (event->key() == Qt::Key_Control)
2360 else if (event->key() == Qt::Key_Alt)
2363 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
2366 ToolHandler(ToolKeyUp, Point(0, 0));
2371 if (oldAlt != altDown)
2375 if (Global::penStamp)
2376 setCursor(curMarker);
2377 else if (Global::penDropper)
2378 setCursor(curDropper);
2380 setCursor(Qt::ArrowCursor);
2385 // This looks strange, but it's really quite simple: We want a point that's
2386 // more than half-way to the next grid point to snap there while conversely we
2387 // want a point that's less than half-way to to the next grid point then snap
2388 // to the one before it. So we add half of the grid spacing to the point, then
2389 // divide by it so that we can remove the fractional part, then multiply it
2390 // back to get back to the correct answer.
2392 Point DrawingView::SnapPointToGrid(Point point)
2394 point += Global::gridSpacing / 2.0; // *This* adds to Z!!!
2395 point /= Global::gridSpacing;
2396 point.x = floor(point.x);//need to fix this for negative numbers...
2397 point.y = floor(point.y);
2398 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
2399 point *= Global::gridSpacing;
2403 Point DrawingView::SnapPointToAngle(Point point)
2405 // Snap to a single digit angle (using toolpoint #1 as the center)
2406 double angle = Vector::Angle(toolPoint[0], point);
2407 double length = Vector::Magnitude(toolPoint[0], point);
2409 // Convert from radians to degrees
2410 double degAngle = angle * RADIANS_TO_DEGREES;
2411 double snapAngle = (double)((int)(degAngle + 0.5));
2414 v.SetAngleAndLength(snapAngle * DEGREES_TO_RADIANS, length);
2415 point = toolPoint[0] + v;
2420 Rect DrawingView::GetObjectExtents(Object * obj)
2422 // Default to empty rect, if object checks below fail for some reason
2430 rect = Rect(obj->p[0], obj->p[1]);
2436 rect = Rect(obj->p[0], obj->p[0]);
2437 rect.Expand(obj->radius[0]);
2443 Arc * a = (Arc *)obj;
2445 double start = a->angle[0];
2446 double end = start + a->angle[1];
2448 // Swap 'em if the span is negative...
2449 if (a->angle[1] < 0)
2452 start = end + a->angle[1];
2455 rect = Rect(Point(cos(start), sin(start)), Point(cos(end), sin(end)));
2457 // If the end of the arc is before the beginning, add 360 degrees to it
2462 Find which quadrant the start angle is in (consider the beginning of the 90° angle to be in the quadrant, the end to be in the next quadrant). Then, divide the span into 90° segments. The integer portion is the definite axis crossings; the remainder needs more scrutiny. There will be an additional axis crossing if the the sum of the start angle and the remainder is > 90°.
2465 int quadStart = (int)(a->angle[0] / QTR_TAU);
2466 double qsRemain = a->angle[0] - ((double)quadStart * QTR_TAU);
2467 int numAxes = (int)((a->angle[1] + qsRemain) / QTR_TAU);
2469 double axis[4] = { 0, 0, 0, 0 };
2470 axis[0] = rect.t, axis[1] = rect.l, axis[2] = rect.b, axis[3] = rect.r;
2471 double box[4] = { 1.0, -1.0, -1.0, 1.0 };
2473 for(int i=0; i<numAxes; i++)
2474 axis[(quadStart + i) % 4] = box[(quadStart + i) % 4];
2476 // The rect is constructed the same way we traverse the axes: TLBR
2477 Rect r2(axis[0], axis[1], axis[2], axis[3]);
2481 // Adjust the bounds depending on which axes are crossed
2482 if ((start < QTR_TAU) && (end > QTR_TAU))
2485 if ((start < HALF_TAU) && (end > HALF_TAU))
2488 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
2491 if ((start < TAU) && (end > TAU))
2494 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
2497 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
2500 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
2504 rect *= a->radius[0];
2505 rect.Translate(a->p[0]);
2511 Text * t = (Text *)obj;
2512 rect = Rect(t->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
2518 Container * c = (Container *)obj;
2519 VPVectorIter i = c->objects.begin();
2520 rect = GetObjectExtents((Object *)*i);
2523 for(; i!=c->objects.end(); i++)
2524 rect |= GetObjectExtents((Object *)*i);
2534 void DrawingView::CheckObjectBounds(void)
2538 for(i=document.objects.begin(); i!=document.objects.end(); i++)
2540 Object * obj = (Object *)(*i);
2541 obj->selected = false;
2542 Rect selection = Global::selection;
2543 selection.Normalize();
2548 case OTDimension: // N.B.: We don't check this properly...
2550 Line * l = (Line *)obj;
2552 if (selection.Contains(l->p[0]) && selection.Contains(l->p[1]))
2560 Circle * c = (Circle *)obj;
2561 Vector radVec(c->radius[0], c->radius[0]);
2563 if (selection.Contains(c->p[0] - radVec) && selection.Contains(c->p[0] + radVec))
2571 Arc * a = (Arc *)obj;
2573 double start = a->angle[0];
2574 double end = start + a->angle[1];
2576 // Swap 'em if the span is negative...
2577 if (a->angle[1] < 0)
2580 start = end + a->angle[1];
2583 // If the end of the arc is before the beginning, add 360 degrees
2589 int quadStart = (int)(a->angle[0] / QTR_TAU);
2590 double qsRemain = a->angle[0] - ((double)quadStart * QTR_TAU);
2591 int numAxes = (int)((a->angle[1] + qsRemain) / QTR_TAU);
2593 Rect bounds(sin(start), cos(start), sin(end), cos(end));
2594 const double box[4] = { 1.0, -1.0, -1.0, 1.0 };
2596 for(int i=0; i<numAxes; i++)
2597 bounds[(quadStart + i) % 4] = box[(quadStart + i) % 4];
2599 // Adjust the bounds depending on which axes are crossed
2600 if ((start < QTR_TAU) && (end > QTR_TAU))
2603 if ((start < HALF_TAU) && (end > HALF_TAU))
2606 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
2609 if ((start < TAU) && (end > TAU))
2612 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
2615 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
2618 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
2622 bounds *= a->radius[0];
2623 bounds.Translate(a->p[0]);
2625 if (selection.Contains(bounds))
2633 Polyline * pl = (Polyline *)obj;
2634 Rect r(((Object *)(pl->points[0]))->p[0]);
2636 for(int i=0; i<(pl->points.size()-1); i++)
2638 r += ((Object *)(pl->points[i]))->p[0];
2639 r += ((Object *)(pl->points[i + 1]))->p[0];
2642 if (selection.Contains(r))
2643 pl->selected = true;
2650 Text * t = (Text *)obj;
2651 Rect r(obj->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
2653 if (selection.Contains(r))
2661 Container * c = (Container *)obj;
2663 if (selection.Contains(c->p[0]) && selection.Contains(c->p[1]))
2675 bool DrawingView::HitTestObjects(Point point)
2679 bool needUpdate = false;
2680 hoverPointValid = false;
2682 for(i=document.objects.begin(); i!=document.objects.end(); i++)
2684 Object * obj = (Object *)(*i);
2686 // If we're seeing the object we're dragging, skip it
2687 if (draggingObject && (obj == dragged))
2690 if (HitTest(obj, point) == true)
2696 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
2697 emit ObjectHovered(obj);
2704 bool DrawingView::HitTest(Object * obj, Point point)
2706 bool needUpdate = false;
2708 // Make sure we don't hit test stuff on an invisible layer...
2709 if (Global::layerHidden[obj->layer] == true)
2716 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
2717 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
2718 Vector lineSegment = obj->p[1] - obj->p[0];
2719 Vector v1 = point - obj->p[0];
2720 Vector v2 = point - obj->p[1];
2721 double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
2725 distance = v1.Magnitude();
2727 distance = v2.Magnitude();
2729 // distance = ?Det?(ls, v1) / |ls|
2730 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
2731 / lineSegment.Magnitude());
2733 if ((v1.Magnitude() * Global::zoom) < 8.0)
2735 obj->hitPoint[0] = true;
2736 hoverPoint = obj->p[0];
2737 hoverPointValid = true;
2739 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2741 obj->hitPoint[1] = true;
2742 hoverPoint = obj->p[1];
2743 hoverPointValid = true;
2745 else if ((distance * Global::zoom) < 5.0)
2746 obj->hitObject = true;
2748 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
2750 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
2758 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
2759 obj->hitPoint[0] = obj->hitObject = false;
2760 double length = Vector::Magnitude(obj->p[0], point);
2762 if ((length * Global::zoom) < 8.0)
2764 obj->hitPoint[0] = true;
2765 hoverPoint = obj->p[0];
2766 hoverPointValid = true;
2768 else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
2769 obj->hitObject = true;
2771 obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
2773 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
2781 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
2782 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
2783 double length = Vector::Magnitude(obj->p[0], point);
2784 double angle = Vector::Angle(obj->p[0], point);
2786 // Make sure we get the angle in the correct spot
2787 if (angle < obj->angle[0])
2790 // Get the span that we're pointing at...
2791 double span = angle - obj->angle[0];
2793 // N.B.: Still need to hit test the arc start & arc span handles...
2794 double spanAngle = obj->angle[0] + obj->angle[1];
2795 Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
2796 Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
2797 double length2 = Vector::Magnitude(point, handle1);
2798 double length3 = Vector::Magnitude(point, handle2);
2800 if ((length * Global::zoom) < 8.0)
2802 obj->hitPoint[0] = true;
2803 hoverPoint = obj->p[0];
2804 hoverPointValid = true;
2806 else if ((length2 * Global::zoom) < 8.0)
2808 obj->hitPoint[1] = true;
2809 hoverPoint = handle1;
2810 hoverPointValid = true;
2812 else if ((length3 * Global::zoom) < 8.0)
2814 obj->hitPoint[2] = true;
2815 hoverPoint = handle2;
2816 hoverPointValid = true;
2818 else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
2819 obj->hitObject = true;
2821 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
2823 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
2831 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHP3 = obj->hitPoint[3], oldHP4 = obj->hitPoint[4], oldHO = obj->hitObject;
2832 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitPoint[3] = obj->hitPoint[4] = obj->hitObject = false;
2834 Dimension * d = (Dimension *)obj;
2836 Vector orthogonal = Vector::Normal(d->lp[0], d->lp[1]);
2837 // Get our line parallel to our points
2838 float scaledThickness = Global::scale * obj->thickness;
2840 Point p1 = d->lp[0] + (orthogonal * (d->offset + (10.0 * scaledThickness)));
2841 Point p2 = d->lp[1] + (orthogonal * (d->offset + (10.0 * scaledThickness)));
2843 Point p1 = d->lp[0] + (orthogonal * (10.0 + d->offset) * scaledThickness);
2844 Point p2 = d->lp[1] + (orthogonal * (10.0 + d->offset) * scaledThickness);
2846 Point p3(p1, point);
2848 Vector v1(d->p[0], point);
2849 Vector v2(d->p[1], point);
2850 Vector lineSegment(p1, p2);
2851 double t = Geometry::ParameterOfLineAndPoint(p1, p2, point);
2853 Point midpoint = (p1 + p2) / 2.0;
2854 Point hFSPoint = Point(midpoint, point);
2855 Point hCS1Point = Point((p1 + midpoint) / 2.0, point);
2856 Point hCS2Point = Point((midpoint + p2) / 2.0, point);
2859 distance = v1.Magnitude();
2861 distance = v2.Magnitude();
2863 // distance = ?Det?(ls, v1) / |ls|
2864 distance = fabs((lineSegment.x * p3.y - p3.x * lineSegment.y)
2865 / lineSegment.Magnitude());
2867 if ((v1.Magnitude() * Global::zoom) < 8.0)
2868 obj->hitPoint[0] = true;
2869 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2870 obj->hitPoint[1] = true;
2871 else if ((distance * Global::zoom) < 5.0)
2872 obj->hitObject = true;
2874 if ((hFSPoint.Magnitude() * Global::zoom) < 8.0)
2875 obj->hitPoint[2] = true;
2876 else if ((hCS1Point.Magnitude() * Global::zoom) < 8.0)
2877 obj->hitPoint[3] = true;
2878 else if ((hCS2Point.Magnitude() * Global::zoom) < 8.0)
2879 obj->hitPoint[4] = true;
2881 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitPoint[3] || obj->hitPoint[4] || obj->hitObject ? true : false);
2883 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHP3 != obj->hitPoint[3]) || (oldHP4 != obj->hitPoint[4]) || (oldHO != obj->hitObject))
2891 Text * t = (Text *)obj;
2892 bool oldHO = obj->hitObject;
2893 obj->hitObject = false;
2895 Rect r(obj->p[0], Point(obj->p[0].x + t->extents.Width(), obj->p[0].y - t->extents.Height()));
2896 //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);
2898 if (r.Contains(point))
2899 obj->hitObject = true;
2901 obj->hovered = (obj->hitObject ? true : false);
2903 if (oldHO != obj->hitObject)
2911 // Containers must be recursively tested... Or do they???
2913 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.
2915 // bool oldHitObj = c->hitObject, oldHovered = c->hovered;
2916 // Object * oldClicked = c->clicked;
2918 still need to compare old state to new state, and set things up based upon that...
2919 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);
2920 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.
2922 Container * c = (Container *)obj;
2923 c->hitObject = false;
2927 VPVector flat = Flatten(c);
2929 //printf("HitTest::OTContainer (size=%li)\n", flat.size());
2930 for(VPVectorIter i=flat.begin(); i!=flat.end(); i++)
2932 Object * cObj = (Object *)(*i);
2934 // Skip the flattened containers (if any)...
2935 if (cObj->type == OTContainer)
2938 // We do it this way instead of needUpdate = HitTest() because we
2939 // are checking more than one object, and that way of doing will
2940 // not return consistent results.
2941 if (HitTest(cObj, point) == true)
2943 //printf("HitTest::OTContainer, subobj ($%llX) hit!\n", cObj);
2945 // c->hitObject = true;
2946 // c->clicked = cObj;
2947 // c->hovered = true;
2950 // Same reasons for doing it this way here apply.
2951 if (cObj->hitObject == true)
2953 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2954 c->hitObject = true;
2958 if (cObj->hitPoint[0] == true)
2960 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2961 c->hitPoint[0] = true;
2965 if (cObj->hitPoint[1] == true)
2967 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2968 c->hitPoint[1] = true;
2972 if (cObj->hitPoint[2] == true)
2974 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2975 c->hitPoint[2] = true;
2979 if (cObj->hovered == true)
2980 c->hovered = true;//*/
2993 bool DrawingView::HandleObjectClicked(void)
2995 if (dragged->type == OTDimension)
2997 Dimension * d = (Dimension *)dragged;
3001 // Hit the "flip sides" switch, so flip 'em
3002 Point temp = d->p[0];
3007 else if (d->hitPoint[3])
3009 // There are three cases here: aligned, horizontal, & vertical.
3010 // Aligned and horizontal do the same thing, vertical goes back to
3012 if (d->subtype == DTLinearVert)
3013 d->subtype = DTLinear;
3015 d->subtype = DTLinearVert;
3019 else if (d->hitPoint[4])
3021 // There are three cases here: aligned, horizontal, & vertical.
3022 // Aligned and vertical do the same thing, horizontal goes back to
3024 if (d->subtype == DTLinearHorz)
3025 d->subtype = DTLinear;
3027 d->subtype = DTLinearHorz;
3036 void DrawingView::HandleObjectMovement(Point point)
3038 Point delta = point - oldPoint;
3039 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
3040 // Object * obj = (Object *)hover[0];
3041 Object * obj = dragged;
3042 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
3043 //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"));
3048 if (obj->hitPoint[0])
3051 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
3053 if (Global::fixedLength)
3055 Vector unit = Vector::Unit(obj->p[1], point);
3056 point = obj->p[1] + (unit * obj->length);
3059 if (Global::fixedAngle)
3061 // Calculate the component of the current vector along the
3062 // fixed angle: A_compB = (A • Bu) * Bu (p[2] has the unit
3064 double magnitudeAlongB = Vector::Dot(Vector(point - obj->p[1]), obj->p[2]);
3065 point = obj->p[1] + (obj->p[2] * magnitudeAlongB);
3070 else if (obj->hitPoint[1])
3072 if (Global::fixedLength)
3074 Vector unit = Vector::Unit(obj->p[0], point);
3075 point = obj->p[0] + (unit * obj->length);
3078 if (Global::fixedAngle)
3080 double magnitudeAlongB = Vector::Dot(Vector(point - obj->p[0]), obj->p[2]);
3081 point = obj->p[0] + (obj->p[2] * magnitudeAlongB);
3086 else if (obj->hitObject)
3095 if (obj->hitPoint[0])
3097 else if (obj->hitObject)
3099 double oldRadius = obj->length;
3100 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
3102 QString text = QObject::tr("Radius: %1\nScale: %2%");
3103 informativeText = text.arg(obj->radius[0], 0, 'f', 4).arg(obj->radius[0] / oldRadius * 100.0, 0, 'f', 0);
3109 if (obj->hitPoint[0])
3111 else if (obj->hitPoint[1])
3113 // Change the Arc's span (handle #1)
3116 double angle = Vector::Angle(obj->p[0], point);
3117 double delta = angle - obj->angle[0];
3122 obj->angle[1] -= delta;
3123 obj->angle[0] = angle;
3125 if (obj->angle[1] < 0)
3126 obj->angle[1] += TAU;
3128 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
3129 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);
3133 double angle = Vector::Angle(obj->p[0], point);
3134 obj->angle[0] = angle;
3135 QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
3136 informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'f', 4);
3138 else if (obj->hitPoint[2])
3140 // Change the Arc's span (handle #2)
3143 double angle = Vector::Angle(obj->p[0], point);
3144 obj->angle[1] = angle - obj->angle[0];
3146 if (obj->angle[1] < 0)
3147 obj->angle[1] += TAU;
3149 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
3150 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);
3154 double angle = Vector::Angle(obj->p[0], point);
3155 obj->angle[0] = angle - obj->angle[1];
3157 if (obj->angle[0] < 0)
3158 obj->angle[0] += TAU;
3160 double endAngle = obj->angle[0] + obj->angle[1];
3165 QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
3166 informativeText = text.arg(endAngle * RADIANS_TO_DEGREES, 0, 'f', 4);
3168 else if (obj->hitObject)
3175 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
3176 QString text = QObject::tr("Radius: %1");
3177 informativeText = text.arg(obj->radius[0], 0, 'f', 4);
3183 if (obj->hitPoint[0])
3185 else if (obj->hitPoint[1])
3187 else if (obj->hitObject)
3189 // Move measurement lines in/out
3192 Dimension * d = (Dimension *)obj;
3193 double dist = Geometry::DistanceToLineFromPoint(d->lp[0], d->lp[1], point);
3194 float scaledThickness = Global::scale * obj->thickness;
3195 // Looks like offset is 0 to +MAX, but line is at 10.0. So
3196 // anything less than 10.0 should set the offset to 0.
3199 if (dist > (10.0 * scaledThickness))
3200 d->offset = dist - (10.0 * scaledThickness);
3218 // This is shitty, but works for now until I can code up something
3221 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.
3223 // TranslateObject(obj, delta);
3224 TranslateContainer((Container *)obj, point, delta);
3232 void DrawingView::AddDimensionTo(void * o)
3234 Object * obj = (Object *)o;
3239 document.Add(new Dimension(obj->p[0], obj->p[1]));