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(QRectF(QPointF(ul.x, ul.y), QPointF(br.x, br.y)));
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 Dimension * d = (Dimension *)obj;
462 Vector v(d->p[0], d->p[1]);
463 double angle = v.Angle();
464 Vector unit = v.Unit();
465 d->lp[0] = d->p[0], d->lp[1] = d->p[1];
467 double x1, y1, length;
469 if (d->subtype == DTLinearVert)
471 if ((angle < 0) || (angle > HALF_TAU))
473 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
474 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
475 ortho = Vector(1.0, 0);
476 angle = THREE_QTR_TAU;
480 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
481 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
482 ortho = Vector(-1.0, 0);
486 d->lp[0].x = d->lp[1].x = x1;
487 length = fabs(d->p[0].y - d->p[1].y);
489 else if (d->subtype == DTLinearHorz)
491 if ((angle < QTR_TAU) || (angle > THREE_QTR_TAU))
493 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
494 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
495 ortho = Vector(0, 1.0);
500 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
501 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
502 ortho = Vector(0, -1.0);
506 d->lp[0].y = d->lp[1].y = y1;
507 length = fabs(d->p[0].x - d->p[1].x);
509 else if (d->subtype == DTLinear)
511 angle = Vector(d->lp[0], d->lp[1]).Angle();
512 ortho = Vector::Normal(d->lp[0], d->lp[1]);
513 length = v.Magnitude();
516 unit = Vector(d->lp[0], d->lp[1]).Unit();
518 Point p1 = d->lp[0] + (ortho * (d->offset + (10.0 * scaledThickness)));
519 Point p2 = d->lp[1] + (ortho * (d->offset + (10.0 * scaledThickness)));
520 Point p3 = d->lp[0] + (ortho * (d->offset + (16.0 * scaledThickness)));
521 Point p4 = d->lp[1] + (ortho * (d->offset + (16.0 * scaledThickness)));
522 Point p5 = d->p[0] + (ortho * 4.0 * scaledThickness);
523 Point p6 = d->p[1] + (ortho * 4.0 * scaledThickness);
526 The numbers hardcoded into here, what are they?
527 I believe they are pixels.
529 // Draw extension lines (if certain type)
530 painter->DrawLine(p3, p5);
531 painter->DrawLine(p4, p6);
533 // Calculate whether or not the arrowheads are too crowded to put
534 // inside the extension lines. 9.0 is the length of the arrowhead.
535 double t = Geometry::ParameterOfLineAndPoint(d->lp[0], d->lp[1], d->lp[1] - (unit * 9.0 * scaledThickness));
537 // On the screen, it's acting like this is actually 58%...
538 // This is correct, we want it to happen at > 50%
541 // Draw main dimension line + arrowheads
542 painter->DrawLine(p1, p2);
543 painter->DrawArrowhead(p1, p2, scaledThickness);
544 painter->DrawArrowhead(p2, p1, scaledThickness);
548 // Draw outside arrowheads
549 Point p7 = p1 - (unit * 9.0 * scaledThickness);
550 Point p8 = p2 + (unit * 9.0 * scaledThickness);
551 painter->DrawArrowhead(p1, p7, scaledThickness);
552 painter->DrawArrowhead(p2, p8, scaledThickness);
553 painter->DrawLine(p1, p1 - (unit * 14.0 * scaledThickness));
554 painter->DrawLine(p2, p2 + (unit * 14.0 * scaledThickness));
557 // Draw length of dimension line...
558 painter->SetFont(QFont("Arial", 8.0 * Global::zoom * scaledThickness));
559 Point ctr = p2 + (Vector(p2, p1) / 2.0);
561 QString dimText = GetDimensionText(&document, length);
564 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().
566 painter->DrawAngledText(ctr, angle, dimText, scaledThickness);
570 Point hp1 = (p1 + p2) / 2.0;
571 Point hp2 = (p1 + hp1) / 2.0;
572 Point hp3 = (hp1 + p2) / 2.0;
576 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
577 painter->SetBrush(QBrush(QColor(Qt::magenta)));
578 painter->DrawArrowHandle(hp1, ortho.Angle() + HALF_TAU);
579 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
582 painter->DrawHandle(hp1);
583 painter->SetPen(QPen(Qt::blue, 1.0 * Global::zoom * scaledThickness, Qt::SolidLine));
587 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
588 painter->SetBrush(QBrush(QColor(Qt::magenta)));
589 painter->DrawArrowToLineHandle(hp2, (d->subtype == DTLinearVert ? v.Angle() - QTR_TAU : (v.Angle() < HALF_TAU ? HALF_TAU : 0)));
590 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
593 painter->DrawHandle(hp2);
594 painter->SetPen(QPen(Qt::blue, 1.0 * Global::zoom * scaledThickness, Qt::SolidLine));
598 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
599 painter->SetBrush(QBrush(QColor(Qt::magenta)));
600 painter->DrawArrowToLineHandle(hp3, (d->subtype == DTLinearHorz ? v.Angle() - QTR_TAU : (v.Angle() > HALF_TAU && v.Angle() < THREE_QTR_TAU ? THREE_QTR_TAU : QTR_TAU)));
601 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
604 painter->DrawHandle(hp3);
607 if (obj->hitPoint[0])
608 painter->DrawHandle(obj->p[0]);
610 if (obj->hitPoint[1])
611 painter->DrawHandle(obj->p[1]);
618 Text * t = (Text *)obj;
620 if (t->measured == false)
622 t->extents = painter->MeasureTextObject(t->s.c_str(), scaledThickness);
626 painter->DrawTextObject(t->p[0], t->s.c_str(), scaledThickness, t->angle[0]);
647 // Containers require recursive rendering...
648 Container * c = (Container *)obj;
649 //printf("About to render container: # objs=%i, layer=%i\n", (*c).objects.size(), layer);
650 RenderObjects(painter, (*c).objects, layer, ignoreLayer);
652 //printf("Container extents: <%lf, %lf>, <%lf, %lf>\nsize: %i\n", r.l, r.t, r.r, r.b, c->objects.size());
653 // Containers also have special indicators showing they are selected
654 if (c->selected || c->hitObject)
656 // Rect r = GetObjectExtents(obj);
657 // painter->DrawRectCorners(r);
658 painter->DrawRectCorners(Rect(c->p[0], c->p[1]));
669 supressSelected = false;
673 // This toggles the selection being hovered (typically, only 1 object). We
674 // toggle because the CTRL key might be held, in which case, we want to
675 // deselect a selected object.
677 void DrawingView::HandleSelectionClick(VPVector & v)
681 for(VPVectorIter i=v.begin(); i!=v.end(); i++)
683 Object * obj = (Object *)(*i);
686 obj->selected = !obj->selected;
692 for(VPVectorIter i=v.begin(); i!=v.end(); i++)
693 ((Object *)(*i))->selected = false;
695 // Check if the hover changed, and if so, reset the selection stack
696 if (oldHover.size() != v.size())
703 // Select next object in the stack under the cursor
706 if (currentSelect >= v.size())
710 dragged = (Object *)v[currentSelect];
711 dragged->selected = true;
714 VPVector DrawingView::GetSelection(void)
718 for(VPVectorIter i=document.objects.begin(); i!=document.objects.end(); i++)
720 if (((Object *)(*i))->selected)
728 // When testing for hovered intersections, we need to be able to exclude some
729 // objects which have funky characteristics or handles; so we allow for that
732 VPVector DrawingView::GetHovered(bool exclude/*= false*/)
736 for(VPVectorIter i=document.objects.begin(); i!=document.objects.end(); i++)
738 Object * obj = (Object *)(*i);
743 && ((obj->type == OTDimension)
744 || ((obj->type == OTCircle) && (obj->hitPoint[0] == true))
745 || ((obj->type == OTArc) && (obj->hitPoint[0] == true))
746 || (draggingObject && (obj == dragged))))
756 void DrawingView::MoveSelectedToLayer(int layer)
758 for(VPVectorIter i=document.objects.begin(); i!=document.objects.end(); i++)
760 Object * obj = (Object *)(*i);
762 if (obj->selected || obj->hovered)
767 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
769 Global::screenSize = Vector(size().width(), size().height());
772 void DrawingView::ToolHandler(int mode, Point p)
774 // Drop angle snap until it's needed
777 if (Global::tool == TTLine)
778 LineHandler(mode, p);
779 else if (Global::tool == TTCircle)
780 CircleHandler(mode, p);
781 else if (Global::tool == TTArc)
783 else if (Global::tool == TTRotate)
784 RotateHandler(mode, p);
785 else if (Global::tool == TTMirror)
786 MirrorHandler(mode, p);
787 else if (Global::tool == TTDimension)
788 DimensionHandler(mode, p);
789 else if (Global::tool == TTDelete)
790 DeleteHandler(mode, p);
791 else if (Global::tool == TTTriangulate)
792 TriangulateHandler(mode, p);
793 else if (Global::tool == TTTrim)
794 TrimHandler(mode, p);
795 else if (Global::tool == TTParallel)
796 ParallelHandler(mode, p);
799 void DrawingView::ToolDraw(Painter * painter)
801 switch (Global::tool)
804 if (Global::toolState == TSNone)
806 painter->DrawHandle(toolPoint[0]);
808 else if ((Global::toolState == TSPoint2) && shiftDown)
810 painter->DrawHandle(toolPoint[1]);
814 painter->DrawLine(toolPoint[0], toolPoint[1]);
815 painter->DrawHandle(toolPoint[1]);
817 Vector v(toolPoint[0], toolPoint[1]);
818 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
819 double absLength = v.Magnitude();
820 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
821 informativeText = text.arg(absLength).arg(absAngle);
827 if (Global::toolState == TSNone)
829 painter->DrawHandle(toolPoint[0]);
831 else if ((Global::toolState == TSPoint2) && shiftDown)
833 painter->DrawHandle(toolPoint[1]);
837 painter->DrawCross(toolPoint[0]);
838 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
839 painter->SetBrush(QBrush(Qt::NoBrush));
840 painter->DrawEllipse(toolPoint[0], length, length);
841 QString text = tr("Radius: %1 in.");
842 informativeText = text.arg(length);
848 if (Global::toolState == TSNone)
850 painter->DrawHandle(toolPoint[0]);
852 else if (Global::toolState == TSPoint2)
854 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
855 painter->SetBrush(QBrush(Qt::NoBrush));
856 painter->DrawEllipse(toolPoint[0], length, length);
857 painter->DrawLine(toolPoint[0], toolPoint[1]);
858 painter->DrawHandle(toolPoint[1]);
859 QString text = tr("Radius: %1 in.");
860 informativeText = text.arg(length);
862 else if (Global::toolState == TSPoint3)
864 double angle = Vector::Angle(toolPoint[0], toolPoint[2]);
865 painter->DrawLine(toolPoint[0], toolPoint[2]);
866 painter->SetBrush(QBrush(Qt::NoBrush));
867 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
868 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
869 QString text = tr("Angle start: %1") + QChar(0x00B0);
870 informativeText = text.arg(RADIANS_TO_DEGREES * angle);
874 double angle = Vector::Angle(toolPoint[0], toolPoint[3]);
875 double span = angle - toolPoint[2].x;
880 painter->DrawLine(toolPoint[0], toolPoint[3]);
881 painter->SetBrush(QBrush(Qt::NoBrush));
882 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
883 painter->SetPen(0xFF00FF, 2.0, LSSolid);
884 painter->DrawArc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
885 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
886 QString text = tr("Arc span: %1") + QChar(0x00B0);
887 informativeText = text.arg(RADIANS_TO_DEGREES * span);
893 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
894 painter->DrawHandle(toolPoint[0]);
895 else if ((Global::toolState == TSPoint2) && shiftDown)
896 painter->DrawHandle(toolPoint[1]);
899 if (toolPoint[0] == toolPoint[1])
902 painter->DrawLine(toolPoint[0], toolPoint[1]);
904 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
905 QString text = QChar(0x2221) + QObject::tr(": %1");
906 informativeText = text.arg(absAngle);
909 informativeText += " (Copy)";
915 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
916 painter->DrawHandle(toolPoint[0]);
917 else if ((Global::toolState == TSPoint2) && shiftDown)
918 painter->DrawHandle(toolPoint[1]);
921 if (toolPoint[0] == toolPoint[1])
924 Point mirrorPoint = toolPoint[0] + Vector(toolPoint[1], toolPoint[0]);
925 painter->DrawLine(mirrorPoint, toolPoint[1]);
927 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
929 if (absAngle > 180.0)
932 QString text = QChar(0x2221) + QObject::tr(": %1");
933 informativeText = text.arg(absAngle);
936 informativeText += " (Copy)";
942 if (Global::toolState == TSNone)
944 painter->DrawHandle(toolPoint[0]);
946 else if ((Global::toolState == TSPoint2) && shiftDown)
948 painter->DrawHandle(toolPoint[1]);
952 painter->DrawLine(toolPoint[0], toolPoint[1]);
953 painter->DrawHandle(toolPoint[1]);
955 Vector v(toolPoint[0], toolPoint[1]);
956 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
957 double absLength = v.Magnitude();
958 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
959 informativeText = text.arg(absLength).arg(absAngle);
965 if (toolObj[0] != NULL)
967 // We're assuming ATM it's just a line...
968 painter->SetPen(0xAF0000, 3.0, LSSolid);
969 painter->DrawLine(toolPoint[0], toolPoint[1]);
970 // QString text = tr("Arc span: %1") + QChar(0x00B0);
971 // informativeText = text.arg(RADIANS_TO_DEGREES * span);
977 if (Global::toolState == TSPoint1)
979 painter->SetPen(0xFF00FF, 2.0, LSSolid);
980 painter->SetBrush(QBrush(Qt::NoBrush));
982 double length = Vector::Magnitude(toolObj[0]->p[0], toolPoint[0]);
983 bool inside = (length >= toolObj[0]->radius[0] ? false : true);
985 for(int i=1; i<=Global::parallelNum; i++)
987 if (toolObj[0]->type == OTLine)
989 painter->DrawLine(toolObj[0]->p[0] + (toolPoint[0] * Global::parallelDist * (double)i), toolObj[0]->p[1] + (toolPoint[0] * Global::parallelDist * (double)i));
991 else if ((toolObj[0]->type == OTCircle) || (toolObj[0]->type == OTArc))
993 double radius = toolObj[0]->radius[0] + ((double)i * Global::parallelDist * (inside ? -1.0 : 1.0));
997 if (toolObj[0]->type == OTCircle)
998 painter->DrawEllipse(toolObj[0]->p[0], radius, radius);
1000 painter->DrawArc(toolObj[0]->p[0], radius, toolObj[0]->angle[0], toolObj[0]->angle[1]);
1013 void DrawingView::LineHandler(int mode, Point p)
1018 /* toolObj[0] = NULL;
1020 // Check to see if we can do a circle tangent snap
1021 if (numHovered == 1)
1023 VPVector hover = GetHovered();
1024 Object * obj = (Object *)hover[0];
1026 // 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! :-)
1027 if ((obj->type == OTCircle) && (obj->hitPoint[0] == false))
1031 if (Global::toolState == TSNone)
1039 if (Global::toolState == TSNone)
1044 /* bool isCircle = false;
1046 if (numHovered == 1)
1048 VPVector hover = GetHovered();
1049 Object * obj = (Object *)hover[0];
1051 if ((obj->type == OTCircle) && (obj->hitPoint[0] == false))
1058 // Adjust initial point if it's on a circle (tangent point)
1059 if (toolObj[0] != NULL)
1063 Geometry::FindTangents(toolObj[0], toolObj[1]);
1065 if (Global::numIntersectPoints > 0)
1067 toolPoint[0] = Global::intersectPoint[0];
1068 toolPoint[1] = Global::intersectPoint[1];
1073 Geometry::FindTangents(toolObj[0], p);
1075 if (Global::numIntersectPoints > 0)
1076 toolPoint[0] = Global::intersectPoint[0];
1083 Geometry::FindTangents(toolObj[1], toolPoint[0]);
1085 if (Global::numIntersectPoints > 0)
1086 toolPoint[1] = Global::intersectPoint[0];
1094 if (Global::toolState == TSNone)
1096 Global::toolState = TSPoint2;
1097 // Prevent spurious line from drawing...
1098 toolPoint[1] = toolPoint[0];
1100 else if ((Global::toolState == TSPoint2) && shiftDown)
1102 // Key override is telling us to make a new line, not continue the
1104 toolPoint[0] = toolPoint[1];
1108 Line * l = new Line(toolPoint[0], toolPoint[1], Global::penWidth, Global::penColor, Global::penStyle);
1109 l->layer = Global::activeLayer;
1110 document.objects.push_back(l);
1111 toolPoint[0] = toolPoint[1];
1116 void DrawingView::CircleHandler(int mode, Point p)
1121 if (Global::toolState == TSNone)
1129 if (Global::toolState == TSNone)
1137 if (Global::toolState == TSNone)
1139 Global::toolState = TSPoint2;
1140 // Prevent spurious line from drawing...
1141 toolPoint[1] = toolPoint[0];
1143 else if ((Global::toolState == TSPoint2) && shiftDown)
1145 // Key override is telling us to make a new line, not continue the
1147 toolPoint[0] = toolPoint[1];
1151 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
1152 Circle * c = new Circle(toolPoint[0], length, Global::penWidth, Global::penColor, Global::penStyle);
1153 c->layer = Global::activeLayer;
1154 document.objects.push_back(c);
1155 toolPoint[0] = toolPoint[1];
1156 Global::toolState = TSNone;
1161 void DrawingView::ArcHandler(int mode, Point p)
1166 if (Global::toolState == TSNone)
1168 else if (Global::toolState == TSPoint2)
1170 else if (Global::toolState == TSPoint3)
1178 if (Global::toolState == TSNone)
1180 else if (Global::toolState == TSPoint2)
1182 else if (Global::toolState == TSPoint3)
1196 if (Global::toolState == TSNone)
1198 // Prevent spurious line from drawing...
1199 toolPoint[1] = toolPoint[0];
1200 Global::toolState = TSPoint2;
1202 else if (Global::toolState == TSPoint2)
1206 // Key override is telling us to start arc at new center, not
1207 // continue the current one.
1208 toolPoint[0] = toolPoint[1];
1212 // Set the radius in toolPoint[1].x
1213 toolPoint[1].x = Vector::Magnitude(toolPoint[0], toolPoint[1]);
1214 Global::toolState = TSPoint3;
1216 else if (Global::toolState == TSPoint3)
1218 // Set the angle in toolPoint[2].x
1219 toolPoint[2].x = Vector::Angle(toolPoint[0], toolPoint[2]);
1220 Global::toolState = TSPoint4;
1224 double endAngle = Vector::Angle(toolPoint[0], toolPoint[3]);
1225 double span = endAngle - toolPoint[2].x;
1230 Arc * arc = new Arc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span, Global::penWidth, Global::penColor, Global::penStyle);
1231 arc->layer = Global::activeLayer;
1232 document.objects.push_back(arc);
1233 Global::toolState = TSNone;
1238 void DrawingView::RotateHandler(int mode, Point p)
1243 if (Global::toolState == TSNone)
1246 // SavePointsFrom(select, toolScratch);
1247 CopyObjects(select, toolScratch2);
1248 Global::toolState = TSPoint1;
1250 else if (Global::toolState == TSPoint1)
1258 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1260 else if (Global::toolState == TSPoint2)
1268 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1269 VPVectorIter j = select.begin();
1270 // std::vector<Object>::iterator i = toolScratch.begin();
1271 VPVectorIter i = toolScratch2.begin();
1273 // for(; i!=toolScratch.end(); i++, j++)
1274 for(; i!=toolScratch2.end(); i++, j++)
1276 // Object objT = *i;
1277 // Point p1 = Geometry::RotatePointAroundPoint(objT.p[0], toolPoint[0], angle);
1278 // Point p2 = Geometry::RotatePointAroundPoint(objT.p[1], toolPoint[0], angle);
1279 Object * objT = (Object *)(*i);
1280 Object * objS = (Object *)(*j);
1282 Point p1 = Geometry::RotatePointAroundPoint(objT->p[0], toolPoint[0], angle);
1283 Point p2 = Geometry::RotatePointAroundPoint(objT->p[1], toolPoint[0], angle);
1288 // if (objT.type == OTArc || objT.type == OTText)
1289 if (objT->type == OTArc || objT->type == OTText)
1291 // objS->angle[0] = objT.angle[0] + angle;
1292 objS->angle[0] = objT->angle[0] + angle;
1294 if (objS->angle[0] > TAU)
1295 objS->angle[0] -= TAU;
1297 // else if (objT.type == OTContainer)
1298 else if (objT->type == OTContainer)
1300 // 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...]
1301 // Container * c = (Container *)&objT;
1302 Container * c = (Container *)objT;
1303 Container * c2 = (Container *)objS;
1304 VPVectorIter l = c->objects.begin();
1305 // TODO: Rotate items in the container
1306 // TODO: Make this recursive
1307 for(VPVectorIter k=c2->objects.begin(); k!=c2->objects.end(); k++, l++)
1309 Object * obj3 = (Object *)(*k);
1310 Object * obj4 = (Object *)(*l);
1312 p1 = Geometry::RotatePointAroundPoint(obj4->p[0], toolPoint[0], angle);
1313 p2 = Geometry::RotatePointAroundPoint(obj4->p[1], toolPoint[0], angle);
1317 // obj3->angle[0] = objT.angle[0] + angle;
1318 obj3->angle[0] = obj4->angle[0] + angle;
1320 if (obj3->angle[0] > TAU)
1321 obj3->angle[0] -= TAU;
1324 Rect r = GetObjectExtents(objS);
1325 c2->p[0] = r.TopLeft();
1326 c2->p[1] = r.BottomRight();
1334 if (Global::toolState == TSPoint1)
1336 Global::toolState = TSPoint2;
1337 // Prevent spurious line from drawing...
1338 toolPoint[1] = toolPoint[0];
1340 else if ((Global::toolState == TSPoint2) && shiftDown)
1342 // Key override is telling us to make a new line, not continue the
1344 toolPoint[0] = toolPoint[1];
1348 // Either we're finished with our rotate, or we're stamping a copy.
1351 // Stamp a copy of the selection at the current rotation & bail
1353 CopyObjects(select, temp);
1354 ClearSelected(temp);
1355 AddObjectsTo(document.objects, temp);
1356 // RestorePointsTo(select, toolScratch);
1357 RestorePointsTo(select, toolScratch2);
1362 Global::toolState = TSPoint1;
1363 // SavePointsFrom(select, toolScratch);
1364 DeleteContents(toolScratch2);
1365 CopyObjects(select, toolScratch2);
1371 // Reset the selection if shift held down...
1373 // RestorePointsTo(select, toolScratch);
1374 RestorePointsTo(select, toolScratch2);
1379 // Reset selection when key is let up
1381 RotateHandler(ToolMouseMove, toolPoint[1]);
1386 // RestorePointsTo(select, toolScratch);
1387 RestorePointsTo(select, toolScratch2);
1388 DeleteContents(toolScratch2);
1392 void DrawingView::MirrorHandler(int mode, Point p)
1397 if (Global::toolState == TSNone)
1400 SavePointsFrom(select, toolScratch);
1401 Global::toolState = TSPoint1;
1403 else if (Global::toolState == TSPoint1)
1411 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1413 else if (Global::toolState == TSPoint2)
1421 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1422 VPVectorIter j = select.begin();
1423 std::vector<Object>::iterator i = toolScratch.begin();
1425 for(; i!=toolScratch.end(); i++, j++)
1428 Point p1 = Geometry::MirrorPointAroundLine(obj.p[0], toolPoint[0], toolPoint[1]);
1429 Point p2 = Geometry::MirrorPointAroundLine(obj.p[1], toolPoint[0], toolPoint[1]);
1430 Object * obj2 = (Object *)(*j);
1435 N.B.: When mirroring an arc thru a horizontal axis, this causes the arc to have
1436 a negative start angle which makes it impossible to interact with.
1439 if (obj.type == OTArc)
1441 // This is 2*mirror angle - obj angle - obj span
1442 obj2->angle[0] = (2.0 * angle) - obj.angle[0] - obj.angle[1];
1444 if (obj2->angle[0] > TAU)
1445 obj2->angle[0] -= TAU;
1453 if (Global::toolState == TSPoint1)
1455 Global::toolState = TSPoint2;
1456 // Prevent spurious line from drawing...
1457 toolPoint[1] = toolPoint[0];
1459 else if ((Global::toolState == TSPoint2) && shiftDown)
1461 // Key override is telling us to make a new line, not continue the
1463 toolPoint[0] = toolPoint[1];
1467 // Either we're finished with our rotate, or we're stamping a copy.
1470 // Stamp a copy of the selection at the current rotation & bail
1472 CopyObjects(select, temp);
1473 ClearSelected(temp);
1474 AddObjectsTo(document.objects, temp);
1475 RestorePointsTo(select, toolScratch);
1480 Global::toolState = TSPoint1;
1481 SavePointsFrom(select, toolScratch);
1487 // Reset the selection if shift held down...
1489 RestorePointsTo(select, toolScratch);
1494 // Reset selection when key is let up
1496 MirrorHandler(ToolMouseMove, toolPoint[1]);
1501 RestorePointsTo(select, toolScratch);
1505 void DrawingView::DimensionHandler(int mode, Point p)
1510 if (Global::toolState == TSNone)
1518 if (Global::toolState == TSNone)
1526 if (Global::toolState == TSNone)
1528 Global::toolState = TSPoint2;
1529 // Prevent spurious line from drawing...
1530 toolPoint[1] = toolPoint[0];
1532 else if ((Global::toolState == TSPoint2) && shiftDown)
1534 // Key override is telling us to make a new line, not continue the
1536 toolPoint[0] = toolPoint[1];
1540 Dimension * d = new Dimension(toolPoint[0], toolPoint[1], DTLinear, 0, Global::penWidth);
1541 d->layer = Global::activeLayer;
1542 document.objects.push_back(d);
1543 Global::toolState = TSNone;
1548 void DrawingView::DeleteHandler(int mode, Point /*p*/)
1554 VPVector hovered = GetHovered();
1556 RemoveHoveredObjects(document.objects);
1557 DeleteContents(hovered);
1578 void DrawingView::TriangulateHandler(int mode, Point /*p*/)
1584 // Skip if nothing hovered...
1585 if (numHovered != 1)
1588 VPVector hover = GetHovered();
1589 Object * obj = (Object *)hover[0];
1591 // Skip if it's not a line...
1592 if (obj->type != OTLine)
1595 if (Global::toolState == TSNone)
1597 else if (Global::toolState == TSPoint2)
1606 if (Global::toolState == TSNone)
1608 else if (Global::toolState == TSPoint2)
1610 else if (Global::toolState == TSPoint3)
1624 if (Global::toolState == TSNone)
1626 Global::toolState = TSPoint2;
1628 else if (Global::toolState == TSPoint2)
1632 // Key override is telling us to start arc at new center, not
1633 // continue the current one.
1634 toolPoint[0] = toolPoint[1];
1638 Global::toolState = TSPoint3;
1642 double len2 = Vector::Magnitude(toolObj[1]->p[0], toolObj[1]->p[1]);
1643 double len3 = Vector::Magnitude(toolObj[2]->p[0], toolObj[2]->p[1]);
1645 Circle c1(toolObj[0]->p[0], len2);
1646 Circle c2(toolObj[0]->p[1], len3);
1648 Geometry::CheckCircleToCircleIntersection((Object *)&c1, (Object *)&c2);
1650 // Only move lines if the triangle formed by them is not degenerate
1651 if (Global::numIntersectPoints > 0)
1653 toolObj[1]->p[0] = toolObj[0]->p[0];
1654 toolObj[1]->p[1] = Global::intersectPoint[0];
1656 toolObj[2]->p[0] = Global::intersectPoint[0];
1657 toolObj[2]->p[1] = toolObj[0]->p[1];
1660 Global::toolState = TSNone;
1665 void DrawingView::TrimHandler(int mode, Point p)
1676 // Bail out if nothing hovered...
1677 if (numHovered != 1)
1683 VPVector hover = GetHovered();
1684 Object * obj = (Object *)hover[0];
1686 // Skip if it's not a line...
1687 if (obj->type != OTLine)
1694 double hoveredParam = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], p);
1695 double t = 0, u = 1.0;
1697 // Currently only deal with line against line trimming, can expand to
1698 // others as well (line/circle, circle/circle, line/arc, etc)
1700 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1702 obj = (Object *)(*i);
1704 if (obj == toolObj[0])
1706 else if (obj->type != OTLine)
1709 Geometry::CheckLineToLineIntersection(toolObj[0], obj);
1711 if (Global::numIntersectParams > 0)
1713 // Skip endpoint-endpoint intersections
1714 if ((Global::numIntersectParams == 2)
1715 && (Global::intersectParam[0] == 0
1716 || Global::intersectParam[0] == 1.0)
1717 && (Global::intersectParam[1] == 0
1718 || Global::intersectParam[1] == 1.0))
1721 // Mark the line segment somehow (the side the mouse is on) so that it can be drawn & trimmed when we hit ToolMouseDown.
1722 if ((Global::intersectParam[0] > t) && (Global::intersectParam[0] < hoveredParam))
1723 t = Global::intersectParam[0];
1725 if ((Global::intersectParam[0] < u) && (Global::intersectParam[0] > hoveredParam))
1726 u = Global::intersectParam[0];
1732 toolPoint[0] = Geometry::GetPointForParameter(toolObj[0], t);
1733 toolPoint[1] = Geometry::GetPointForParameter(toolObj[0], u);
1739 // Bail out if there's no object to trim
1740 if (toolObj[0] == NULL)
1743 Vector v(toolObj[0]->p[0], toolObj[0]->p[1]);
1745 // Check to see which case we have.
1746 if ((toolParam[0] == 0) && (toolParam[1] == 1.0))
1748 // There was no intersection, so delete the object
1749 toolObj[0]->selected = true;
1750 DeleteSelectedObjects(document.objects);
1752 else if (toolParam[0] == 0)
1754 // We delete the end near point #1
1755 toolObj[0]->p[0] = toolObj[0]->p[0] + (v * toolParam[1]);
1757 else if (toolParam[1] == 1.0)
1759 // We delete the end near point #2
1760 toolObj[0]->p[1] = toolObj[0]->p[0] + (v * toolParam[0]);
1764 // We delete the segment in between, and create a new line in the process
1765 Point p1 = toolObj[0]->p[0] + (v * toolParam[0]);
1766 Point p2 = toolObj[0]->p[0] + (v * toolParam[1]);
1767 Point p3 = toolObj[0]->p[1];
1768 toolObj[0]->p[1] = p1;
1769 Line * l = new Line(p2, p3, toolObj[0]->thickness, toolObj[0]->color, toolObj[0]->style);
1770 document.objects.push_back(l);
1771 // Global::toolState = TSNone;
1774 toolObj[0]->hitObject = toolObj[0]->hitPoint[0] = toolObj[0]->hitPoint[1] = false;
1790 void DrawingView::ParallelHandler(int mode, Point p)
1795 if (numHovered == 1)
1797 // New selection made...
1798 VPVector hover = GetHovered();
1799 toolObj[0] = (Object *)hover[0];
1800 Global::toolState = TSNone;
1802 else if ((numHovered == 0) && (toolObj[0] != NULL))
1804 double length = Vector::Magnitude(toolObj[0]->p[0], toolPoint[0]);
1805 bool inside = (length >= toolObj[0]->radius[0] ? false : true);
1807 // Stamp out new parallel object(s)...
1808 for(int i=1; i<=Global::parallelNum; i++)
1810 if (toolObj[0]->type == OTLine)
1812 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);
1813 // Should probably have a user selection for this whether it goes into the selected objects layer or the global layer...
1814 l->layer = toolObj[0]->layer;
1815 document.objects.push_back(l);
1817 else if (toolObj[0]->type == OTCircle)
1819 double radius = toolObj[0]->radius[0] + ((double)i * Global::parallelDist * (inside ? -1.0 : 1.0));
1823 Circle * c = new Circle(toolObj[0]->p[0], radius, Global::penWidth, Global::penColor, Global::penStyle);
1824 c->layer = toolObj[0]->layer;
1825 document.objects.push_back(c);
1828 else if (toolObj[0]->type == OTArc)
1830 double radius = toolObj[0]->radius[0] + ((double)i * Global::parallelDist * (inside ? -1.0 : 1.0));
1834 Arc * a = new Arc(toolObj[0]->p[0], radius, toolObj[0]->angle[0], toolObj[0]->angle[1], Global::penWidth, Global::penColor, Global::penStyle);
1835 a->layer = toolObj[0]->layer;
1836 document.objects.push_back(a);
1841 // Then reset the state
1842 toolObj[0]->selected = false;
1844 Global::toolState = TSNone;
1850 if ((numHovered == 0) && toolObj[0] != NULL)
1851 Global::toolState = TSPoint1;
1853 Global::toolState = TSNone;
1855 if (Global::toolState == TSPoint1)
1857 // Figure out which side of the object we're on, and draw the preview on that side...
1858 if (toolObj[0]->type == OTLine)
1860 Vector normal = Geometry::GetNormalOfPointAndLine(p, (Line *)toolObj[0]);
1861 toolPoint[0] = normal;
1863 else if ((toolObj[0]->type == OTCircle) || (toolObj[0]->type == OTArc))
1885 void DrawingView::mousePressEvent(QMouseEvent * event)
1887 if (event->button() == Qt::LeftButton)
1889 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1891 // Handle tool processing, if any
1894 if (hoveringIntersection)
1895 point = intersectionPoint;
1896 else if (hoverPointValid)
1898 else if (Global::snapToGrid)
1899 point = SnapPointToGrid(point);
1901 ToolHandler(ToolMouseDown, point);
1905 // Clear the selection only if CTRL isn't being held on click
1907 ClearSelected(document.objects);
1909 // If any objects are being hovered on click, deal with 'em
1912 VPVector hover2 = GetHovered();
1913 dragged = (Object *)hover2[0];
1915 // Alert the pen widget
1916 if (Global::penDropper)
1918 Global::penColor = dragged->color;
1919 Global::penWidth = dragged->thickness;
1920 Global::penStyle = dragged->style;
1921 emit ObjectSelected(dragged);
1924 else if (Global::penStamp)
1926 dragged->color = Global::penColor;
1927 dragged->thickness = Global::penWidth;
1928 dragged->style = Global::penStyle;
1931 // See if anything is using just a straight click on a custom
1932 // object handle (like Dimension objects)
1933 else if (HandleObjectClicked())
1939 draggingObject = true;
1940 HandleSelectionClick(hover2);
1941 update(); // needed??
1943 // Needed for grab & moving objects
1944 // We do it *after*... why? (doesn't seem to confer any advantage...)
1945 if (hoveringIntersection)
1946 oldPoint = intersectionPoint;
1947 else if (hoverPointValid)
1948 oldPoint = hoverPoint;
1949 else if (Global::snapToGrid)
1950 oldPoint = SnapPointToGrid(point);
1952 // Needed for fixed length handling
1953 if (Global::fixedLength)
1955 if (dragged->type == OTLine)
1956 dragged->length = ((Line *)dragged)->Length();
1959 // Needed for fixed angle handling
1960 if (Global::fixedAngle)
1962 if (dragged->type == OTLine)
1963 dragged->p[2] = ((Line *)dragged)->Unit();
1966 if (dragged->type == OTCircle)
1968 // Save for informative text, uh, er, informing
1969 dragged->length = dragged->radius[0];
1975 // Didn't hit any object and not using a tool, so do a selection
1977 Global::selectionInProgress = true;
1978 Global::selection.setTopLeft(QPointF(point.x, point.y));
1979 Global::selection.setBottomRight(QPointF(point.x, point.y));
1980 select = GetSelection();
1982 else if (event->button() == Qt::MiddleButton)
1985 oldPoint = Vector(event->x(), event->y());
1986 // Should also change the mouse pointer as well...
1987 setCursor(Qt::SizeAllCursor);
1991 void DrawingView::mouseMoveEvent(QMouseEvent * event)
1993 // It seems that wheelEvent() triggers this for some reason...
1994 if (scrollWheelSeen)
1996 scrollWheelSeen = false;
2000 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
2001 Global::selection.setBottomRight(QPointF(point.x, point.y));
2002 // Only needs to be done here, as mouse down is always preceded by movement
2003 Global::snapPointIsValid = false;
2004 hoveringIntersection = false;
2005 oldScrollPoint = Vector(event->x(), event->y());
2008 if ((event->buttons() & Qt::MiddleButton) || scrollDrag)
2010 point = Vector(event->x(), event->y());
2011 // Since we're using Qt coords for scrolling, we have to adjust them
2012 // here to conform to Cartesian coords, since the origin is using
2014 Vector delta(oldPoint, point);
2015 delta /= Global::zoom;
2017 Global::origin -= delta;
2019 // UpdateGridBackground();
2025 // If we're doing a selection rect, see if any objects are engulfed by it
2026 // (implies left mouse button held down)
2027 if (Global::selectionInProgress)
2029 CheckObjectBounds();
2031 // Make sure previously selected objects stay selected (CTRL held)
2032 for(VPVectorIter i=select.begin(); i!=select.end(); i++)
2034 // Make sure *not* to select items on hidden layers
2035 if (Global::layerHidden[((Object *)(*i))->layer] == false)
2036 ((Object *)(*i))->selected = true;
2043 // Do object hit testing...
2044 bool needUpdate = HitTestObjects(point);
2045 VPVector hover2 = GetHovered(true); // Exclude dimension objects and circle centers (probably need to add arc centers too) from hover (also dragged objects...)
2050 printf("mouseMoveEvent:: numHovered=%li, hover2.size()=%li\n", numHovered, hover2.size());
2052 if (hover2.size() > 0)
2053 printf(" (hover2[0]=$%llX, type=%s)\n", hover2[0], objName[((Object *)hover2[0])->type]);
2058 // Check for multi-hover...
2059 if (hover2.size() > 1)
2061 //need to check for case where hover is over 2 circles and a 3rd's center (no longer a problem, I think)...
2062 Object * obj1 = (Object *)hover2[0], * obj2 = (Object *)hover2[1];
2064 Geometry::Intersects(obj1, obj2);
2065 int numIntersecting = Global::numIntersectParams;
2066 double t = Global::intersectParam[0];
2067 double u = Global::intersectParam[1];
2069 if (numIntersecting > 0)
2071 Vector v1 = Geometry::GetPointForParameter(obj1, t);
2072 Vector v2 = Geometry::GetPointForParameter(obj2, u);
2073 QString text = tr("Intersection t=%1 (%3, %4), u=%2 (%5, %6)");
2074 informativeText = text.arg(t).arg(u).arg(v1.x).arg(v1.y).arg(v2.x).arg(v2.y);
2076 hoveringIntersection = true;
2077 intersectionPoint = v1;
2080 numIntersecting = Global::numIntersectPoints;
2082 if (numIntersecting > 0)
2084 Vector v1 = Global::intersectPoint[0];
2086 if (numIntersecting == 2)
2088 Vector v2 = Global::intersectPoint[1];
2090 if (Vector::Magnitude(v2, point) < Vector::Magnitude(v1, point))
2094 QString text = tr("Intersection <%1, %2>");
2095 informativeText = text.arg(v1.x).arg(v1.y);
2096 hoveringIntersection = true;
2097 intersectionPoint = v1;
2100 else if (hover2.size() == 1)
2102 Object * obj = (Object *)hover2[0];
2104 if (obj->type == OTLine)
2107 Not sure that this is the best way to handle this, but it works(TM)...
2109 Point midpoint = Geometry::Midpoint((Line *)obj);
2110 Vector v1 = Vector::Magnitude(midpoint, point);
2112 if ((v1.Magnitude() * Global::zoom) < 8.0)
2114 QString text = tr("Midpoint <%1, %2>");
2115 informativeText = text.arg(midpoint.x).arg(midpoint.y);
2116 hoverPointValid = true;
2117 hoverPoint = midpoint;
2121 else if (obj->type == OTCircle)
2123 if ((draggingObject && (dragged->type == OTLine)) && (dragged->hitPoint[0] || dragged->hitPoint[1]))
2125 Point p = (dragged->hitPoint[0] ? dragged->p[1] : dragged->p[0]);
2126 Geometry::FindTangents(obj, p);
2128 if (Global::numIntersectPoints > 0)
2130 hoveringIntersection = true;
2131 intersectionPoint = Geometry::NearestTo(point, Global::intersectPoint[0], Global::intersectPoint[1]);
2134 else if ((Global::tool == TTLine) && (Global::toolState == TSPoint2))
2136 Geometry::FindTangents(obj, toolPoint[0]);
2138 if (Global::numIntersectPoints > 0)
2140 hoveringIntersection = true;
2141 intersectionPoint = Geometry::NearestTo(point, Global::intersectPoint[0], Global::intersectPoint[1]);
2147 // Handle object movement (left button down & over an object)
2148 if ((event->buttons() & Qt::LeftButton) && draggingObject && !Global::tool)
2150 if (hoveringIntersection)
2151 point = intersectionPoint;
2152 else if (hoverPointValid)
2154 else if (Global::snapToGrid)
2155 point = SnapPointToGrid(point);
2157 HandleObjectMovement(point);
2163 // Do tool handling, if any are active...
2166 if (hoveringIntersection)
2167 point = intersectionPoint;
2168 else if (hoverPointValid)
2170 else if (Global::snapToGrid)
2173 point = SnapPointToAngle(point);
2175 point = SnapPointToGrid(point);
2178 ToolHandler(ToolMouseMove, point);
2181 // This is used to draw the tool crosshair...
2184 if (needUpdate || Global::tool)
2188 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
2190 if (event->button() == Qt::LeftButton)
2192 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
2193 //could set it up to use the document's update function (assumes that all object
2194 //updates are being reported correctly:
2195 // if (document.NeedsUpdate())
2196 // Do an update if collided with at least *one* object in the document
2202 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
2203 ToolHandler(ToolMouseUp, point);
2207 Global::selectionInProgress = false;
2208 informativeText.clear();
2210 // Should we be doing this automagically? Hmm...
2211 // Clear our vectors
2215 // 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???])
2216 select = GetSelection();
2218 draggingObject = false;
2220 else if (event->button() == Qt::MiddleButton)
2224 if (Global::penStamp)
2225 setCursor(curMarker);
2226 else if (Global::penDropper)
2227 setCursor(curDropper);
2229 setCursor(Qt::ArrowCursor);
2231 // Need to convert this, since it's in Qt coordinates (for wheelEvent)
2232 oldPoint = Painter::QtToCartesianCoords(oldPoint);
2236 void DrawingView::wheelEvent(QWheelEvent * event)
2238 double zoomFactor = 1.20;
2239 scrollWheelSeen = true;
2241 if (event->angleDelta().y() < 0)
2243 if (Global::zoom > 400.0)
2246 Global::zoom *= zoomFactor;
2250 if (Global::zoom < 0.125)
2253 Global::zoom /= zoomFactor;
2256 Point np = Painter::QtToCartesianCoords(oldScrollPoint);
2257 Global::origin += (oldPoint - np);
2259 emit(NeedZoomUpdate());
2262 void DrawingView::keyPressEvent(QKeyEvent * event)
2264 bool oldShift = shiftDown;
2265 bool oldCtrl = ctrlDown;
2266 bool oldAlt = altDown;
2268 if (event->key() == Qt::Key_Shift)
2270 else if (event->key() == Qt::Key_Control)
2272 else if (event->key() == Qt::Key_Alt)
2275 // If there's a change in any of the modifier key states, pass it on to
2276 // the current tool's handler
2277 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
2280 ToolHandler(ToolKeyDown, Point(0, 0));
2285 if (oldAlt != altDown)
2288 setCursor(Qt::SizeAllCursor);
2289 oldPoint = oldScrollPoint;
2292 if (select.size() > 0)
2294 if (event->key() == Qt::Key_Up)
2296 TranslateObjects(select, Point(0, +1.0));
2299 else if (event->key() == Qt::Key_Down)
2301 TranslateObjects(select, Point(0, -1.0));
2304 else if (event->key() == Qt::Key_Right)
2306 TranslateObjects(select, Point(+1.0, 0));
2309 else if (event->key() == Qt::Key_Left)
2311 TranslateObjects(select, Point(-1.0, 0));
2317 void DrawingView::keyReleaseEvent(QKeyEvent * event)
2319 bool oldShift = shiftDown;
2320 bool oldCtrl = ctrlDown;
2321 bool oldAlt = altDown;
2323 if (event->key() == Qt::Key_Shift)
2325 else if (event->key() == Qt::Key_Control)
2327 else if (event->key() == Qt::Key_Alt)
2330 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
2333 ToolHandler(ToolKeyUp, Point(0, 0));
2338 if (oldAlt != altDown)
2342 if (Global::penStamp)
2343 setCursor(curMarker);
2344 else if (Global::penDropper)
2345 setCursor(curDropper);
2347 setCursor(Qt::ArrowCursor);
2352 // This looks strange, but it's really quite simple: We want a point that's
2353 // more than half-way to the next grid point to snap there while conversely we
2354 // want a point that's less than half-way to to the next grid point then snap
2355 // to the one before it. So we add half of the grid spacing to the point, then
2356 // divide by it so that we can remove the fractional part, then multiply it
2357 // back to get back to the correct answer.
2359 Point DrawingView::SnapPointToGrid(Point point)
2361 point += Global::gridSpacing / 2.0; // *This* adds to Z!!!
2362 point /= Global::gridSpacing;
2363 point.x = floor(point.x);//need to fix this for negative numbers...
2364 point.y = floor(point.y);
2365 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
2366 point *= Global::gridSpacing;
2370 Point DrawingView::SnapPointToAngle(Point point)
2372 // Snap to a single digit angle (using toolpoint #1 as the center)
2373 double angle = Vector::Angle(toolPoint[0], point);
2374 double length = Vector::Magnitude(toolPoint[0], point);
2376 // Convert from radians to degrees
2377 double degAngle = angle * RADIANS_TO_DEGREES;
2378 double snapAngle = (double)((int)(degAngle + 0.5));
2381 v.SetAngleAndLength(snapAngle * DEGREES_TO_RADIANS, length);
2382 point = toolPoint[0] + v;
2387 Rect DrawingView::GetObjectExtents(Object * obj)
2389 // Default to empty rect, if object checks below fail for some reason
2397 rect = Rect(obj->p[0], obj->p[1]);
2403 rect = Rect(obj->p[0], obj->p[0]);
2404 rect.Expand(obj->radius[0]);
2410 Arc * a = (Arc *)obj;
2412 double start = a->angle[0];
2413 double end = start + a->angle[1];
2414 rect = Rect(Point(cos(start), sin(start)), Point(cos(end), sin(end)));
2416 // If the end of the arc is before the beginning, add 360 degrees to it
2420 // Adjust the bounds depending on which axes are crossed
2421 if ((start < QTR_TAU) && (end > QTR_TAU))
2424 if ((start < HALF_TAU) && (end > HALF_TAU))
2427 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
2430 if ((start < TAU) && (end > TAU))
2433 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
2436 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
2439 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
2442 rect *= a->radius[0];
2443 rect.Translate(a->p[0]);
2449 Text * t = (Text *)obj;
2450 rect = Rect(t->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
2456 Container * c = (Container *)obj;
2457 VPVectorIter i = c->objects.begin();
2458 rect = GetObjectExtents((Object *)*i);
2461 for(; i!=c->objects.end(); i++)
2462 rect |= GetObjectExtents((Object *)*i);
2472 void DrawingView::CheckObjectBounds(void)
2476 for(i=document.objects.begin(); i!=document.objects.end(); i++)
2478 Object * obj = (Object *)(*i);
2479 obj->selected = false;
2484 case OTDimension: // N.B.: We don't check this properly...
2486 Line * l = (Line *)obj;
2488 if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
2496 Circle * c = (Circle *)obj;
2498 if (Global::selection.contains(c->p[0].x - c->radius[0], c->p[0].y - c->radius[0]) && Global::selection.contains(c->p[0].x + c->radius[0], c->p[0].y + c->radius[0]))
2506 Arc * a = (Arc *)obj;
2508 double start = a->angle[0];
2509 double end = start + a->angle[1];
2510 QPointF p1(cos(start), sin(start));
2511 QPointF p2(cos(end), sin(end));
2512 QRectF bounds(p1, p2);
2515 // Swap X/Y coordinates if they're backwards...
2516 if (bounds.left() > bounds.right())
2518 double temp = bounds.left();
2519 bounds.setLeft(bounds.right());
2520 bounds.setRight(temp);
2523 if (bounds.bottom() > bounds.top())
2525 double temp = bounds.bottom();
2526 bounds.setBottom(bounds.top());
2527 bounds.setTop(temp);
2530 // Doesn't work as advertised! For shame!
2531 bounds = bounds.normalized();
2534 // If the end of the arc is before the beginning, add 360 degrees
2539 // Adjust the bounds depending on which axes are crossed
2540 if ((start < QTR_TAU) && (end > QTR_TAU))
2543 if ((start < HALF_TAU) && (end > HALF_TAU))
2544 bounds.setLeft(-1.0);
2546 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
2547 bounds.setBottom(-1.0);
2549 if ((start < TAU) && (end > TAU))
2550 bounds.setRight(1.0);
2552 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
2555 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
2556 bounds.setLeft(-1.0);
2558 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
2559 bounds.setBottom(-1.0);
2561 bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
2562 bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
2563 bounds.translate(a->p[0].x, a->p[0].y);
2565 if (Global::selection.contains(bounds))
2573 Text * t = (Text *)obj;
2574 Rect r(obj->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
2576 if (Global::selection.contains(r.l, r.t) && Global::selection.contains(r.r, r.b))
2584 Container * c = (Container *)obj;
2586 if (Global::selection.contains(c->p[0].x, c->p[0].y) && Global::selection.contains(c->p[1].x, c->p[1].y))
2598 bool DrawingView::HitTestObjects(Point point)
2602 bool needUpdate = false;
2603 hoverPointValid = false;
2605 for(i=document.objects.begin(); i!=document.objects.end(); i++)
2607 Object * obj = (Object *)(*i);
2609 // If we're seeing the object we're dragging, skip it
2610 if (draggingObject && (obj == dragged))
2613 if (HitTest(obj, point) == true)
2619 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
2620 emit ObjectHovered(obj);
2627 bool DrawingView::HitTest(Object * obj, Point point)
2629 bool needUpdate = false;
2631 // Make sure we don't hit test stuff on an invisible layer...
2632 if (Global::layerHidden[obj->layer] == true)
2639 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
2640 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
2641 Vector lineSegment = obj->p[1] - obj->p[0];
2642 Vector v1 = point - obj->p[0];
2643 Vector v2 = point - obj->p[1];
2644 double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
2648 distance = v1.Magnitude();
2650 distance = v2.Magnitude();
2652 // distance = ?Det?(ls, v1) / |ls|
2653 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
2654 / lineSegment.Magnitude());
2656 if ((v1.Magnitude() * Global::zoom) < 8.0)
2658 obj->hitPoint[0] = true;
2659 hoverPoint = obj->p[0];
2660 hoverPointValid = true;
2662 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2664 obj->hitPoint[1] = true;
2665 hoverPoint = obj->p[1];
2666 hoverPointValid = true;
2668 else if ((distance * Global::zoom) < 5.0)
2669 obj->hitObject = true;
2671 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
2673 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
2681 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
2682 obj->hitPoint[0] = obj->hitObject = false;
2683 double length = Vector::Magnitude(obj->p[0], point);
2685 if ((length * Global::zoom) < 8.0)
2687 obj->hitPoint[0] = true;
2688 hoverPoint = obj->p[0];
2689 hoverPointValid = true;
2691 else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
2692 obj->hitObject = true;
2694 obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
2696 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
2704 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
2705 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
2706 double length = Vector::Magnitude(obj->p[0], point);
2707 double angle = Vector::Angle(obj->p[0], point);
2709 // Make sure we get the angle in the correct spot
2710 if (angle < obj->angle[0])
2713 // Get the span that we're pointing at...
2714 double span = angle - obj->angle[0];
2716 // N.B.: Still need to hit test the arc start & arc span handles...
2717 double spanAngle = obj->angle[0] + obj->angle[1];
2718 Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
2719 Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
2720 double length2 = Vector::Magnitude(point, handle1);
2721 double length3 = Vector::Magnitude(point, handle2);
2723 if ((length * Global::zoom) < 8.0)
2725 obj->hitPoint[0] = true;
2726 hoverPoint = obj->p[0];
2727 hoverPointValid = true;
2729 else if ((length2 * Global::zoom) < 8.0)
2731 obj->hitPoint[1] = true;
2732 hoverPoint = handle1;
2733 hoverPointValid = true;
2735 else if ((length3 * Global::zoom) < 8.0)
2737 obj->hitPoint[2] = true;
2738 hoverPoint = handle2;
2739 hoverPointValid = true;
2741 else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
2742 obj->hitObject = true;
2744 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
2746 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
2754 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHP3 = obj->hitPoint[3], oldHP4 = obj->hitPoint[4], oldHO = obj->hitObject;
2755 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitPoint[3] = obj->hitPoint[4] = obj->hitObject = false;
2757 Dimension * d = (Dimension *)obj;
2759 Vector orthogonal = Vector::Normal(d->lp[0], d->lp[1]);
2760 // Get our line parallel to our points
2761 float scaledThickness = Global::scale * obj->thickness;
2763 Point p1 = d->lp[0] + (orthogonal * (d->offset + (10.0 * scaledThickness)));
2764 Point p2 = d->lp[1] + (orthogonal * (d->offset + (10.0 * scaledThickness)));
2766 Point p1 = d->lp[0] + (orthogonal * (10.0 + d->offset) * scaledThickness);
2767 Point p2 = d->lp[1] + (orthogonal * (10.0 + d->offset) * scaledThickness);
2769 Point p3(p1, point);
2771 Vector v1(d->p[0], point);
2772 Vector v2(d->p[1], point);
2773 Vector lineSegment(p1, p2);
2774 double t = Geometry::ParameterOfLineAndPoint(p1, p2, point);
2776 Point midpoint = (p1 + p2) / 2.0;
2777 Point hFSPoint = Point(midpoint, point);
2778 Point hCS1Point = Point((p1 + midpoint) / 2.0, point);
2779 Point hCS2Point = Point((midpoint + p2) / 2.0, point);
2782 distance = v1.Magnitude();
2784 distance = v2.Magnitude();
2786 // distance = ?Det?(ls, v1) / |ls|
2787 distance = fabs((lineSegment.x * p3.y - p3.x * lineSegment.y)
2788 / lineSegment.Magnitude());
2790 if ((v1.Magnitude() * Global::zoom) < 8.0)
2791 obj->hitPoint[0] = true;
2792 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2793 obj->hitPoint[1] = true;
2794 else if ((distance * Global::zoom) < 5.0)
2795 obj->hitObject = true;
2797 if ((hFSPoint.Magnitude() * Global::zoom) < 8.0)
2798 obj->hitPoint[2] = true;
2799 else if ((hCS1Point.Magnitude() * Global::zoom) < 8.0)
2800 obj->hitPoint[3] = true;
2801 else if ((hCS2Point.Magnitude() * Global::zoom) < 8.0)
2802 obj->hitPoint[4] = true;
2804 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitPoint[3] || obj->hitPoint[4] || obj->hitObject ? true : false);
2806 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHP3 != obj->hitPoint[3]) || (oldHP4 != obj->hitPoint[4]) || (oldHO != obj->hitObject))
2814 Text * t = (Text *)obj;
2815 bool oldHO = obj->hitObject;
2816 obj->hitObject = false;
2818 Rect r(obj->p[0], Point(obj->p[0].x + t->extents.Width(), obj->p[0].y - t->extents.Height()));
2819 //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);
2821 if (r.Contains(point))
2822 obj->hitObject = true;
2824 obj->hovered = (obj->hitObject ? true : false);
2826 if (oldHO != obj->hitObject)
2834 // Containers must be recursively tested... Or do they???
2836 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.
2838 // bool oldHitObj = c->hitObject, oldHovered = c->hovered;
2839 // Object * oldClicked = c->clicked;
2841 still need to compare old state to new state, and set things up based upon that...
2842 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);
2843 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.
2845 Container * c = (Container *)obj;
2846 c->hitObject = false;
2850 VPVector flat = Flatten(c);
2852 //printf("HitTest::OTContainer (size=%li)\n", flat.size());
2853 for(VPVectorIter i=flat.begin(); i!=flat.end(); i++)
2855 Object * cObj = (Object *)(*i);
2857 // Skip the flattened containers (if any)...
2858 if (cObj->type == OTContainer)
2861 // We do it this way instead of needUpdate = HitTest() because we
2862 // are checking more than one object, and that way of doing will
2863 // not return consistent results.
2864 if (HitTest(cObj, point) == true)
2866 //printf("HitTest::OTContainer, subobj ($%llX) hit!\n", cObj);
2868 // c->hitObject = true;
2869 // c->clicked = cObj;
2870 // c->hovered = true;
2873 // Same reasons for doing it this way here apply.
2874 if (cObj->hitObject == true)
2876 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2877 c->hitObject = true;
2881 if (cObj->hitPoint[0] == true)
2883 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2884 c->hitPoint[0] = true;
2888 if (cObj->hitPoint[1] == true)
2890 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2891 c->hitPoint[1] = true;
2895 if (cObj->hitPoint[2] == true)
2897 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2898 c->hitPoint[2] = true;
2902 if (cObj->hovered == true)
2903 c->hovered = true;//*/
2916 bool DrawingView::HandleObjectClicked(void)
2918 if (dragged->type == OTDimension)
2920 Dimension * d = (Dimension *)dragged;
2924 // Hit the "flip sides" switch, so flip 'em
2925 Point temp = d->p[0];
2930 else if (d->hitPoint[3])
2932 // There are three cases here: aligned, horizontal, & vertical.
2933 // Aligned and horizontal do the same thing, vertical goes back to
2935 if (d->subtype == DTLinearVert)
2936 d->subtype = DTLinear;
2938 d->subtype = DTLinearVert;
2942 else if (d->hitPoint[4])
2944 // There are three cases here: aligned, horizontal, & vertical.
2945 // Aligned and vertical do the same thing, horizontal goes back to
2947 if (d->subtype == DTLinearHorz)
2948 d->subtype = DTLinear;
2950 d->subtype = DTLinearHorz;
2959 void DrawingView::HandleObjectMovement(Point point)
2961 Point delta = point - oldPoint;
2962 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
2963 // Object * obj = (Object *)hover[0];
2964 Object * obj = dragged;
2965 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
2966 //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"));
2971 if (obj->hitPoint[0])
2974 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
2976 if (Global::fixedLength)
2978 Vector unit = Vector::Unit(obj->p[1], point);
2979 point = obj->p[1] + (unit * obj->length);
2982 if (Global::fixedAngle)
2984 // Calculate the component of the current vector along the
2985 // fixed angle: A_compB = (A • Bu) * Bu (p[2] has the unit
2987 double magnitudeAlongB = Vector::Dot(Vector(point - obj->p[1]), obj->p[2]);
2988 point = obj->p[1] + (obj->p[2] * magnitudeAlongB);
2993 else if (obj->hitPoint[1])
2995 if (Global::fixedLength)
2997 Vector unit = Vector::Unit(obj->p[0], point);
2998 point = obj->p[0] + (unit * obj->length);
3001 if (Global::fixedAngle)
3003 double magnitudeAlongB = Vector::Dot(Vector(point - obj->p[0]), obj->p[2]);
3004 point = obj->p[0] + (obj->p[2] * magnitudeAlongB);
3009 else if (obj->hitObject)
3018 if (obj->hitPoint[0])
3020 else if (obj->hitObject)
3022 double oldRadius = obj->length;
3023 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
3025 QString text = QObject::tr("Radius: %1\nScale: %2%");
3026 informativeText = text.arg(obj->radius[0], 0, 'f', 4).arg(obj->radius[0] / oldRadius * 100.0, 0, 'f', 0);
3032 if (obj->hitPoint[0])
3034 else if (obj->hitPoint[1])
3036 // Change the Arc's span (handle #1)
3039 double angle = Vector::Angle(obj->p[0], point);
3040 double delta = angle - obj->angle[0];
3045 obj->angle[1] -= delta;
3046 obj->angle[0] = angle;
3048 if (obj->angle[1] < 0)
3049 obj->angle[1] += TAU;
3051 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
3052 informativeText = text.arg(obj->angle[1] * RADIANS_TO_DEGREES, 0, 'd', 4).arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 2).arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 2);
3056 double angle = Vector::Angle(obj->p[0], point);
3057 obj->angle[0] = angle;
3058 QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
3059 informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 4);
3061 else if (obj->hitPoint[2])
3063 // Change the Arc's span (handle #2)
3066 double angle = Vector::Angle(obj->p[0], point);
3067 obj->angle[1] = angle - obj->angle[0];
3069 if (obj->angle[1] < 0)
3070 obj->angle[1] += TAU;
3072 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
3073 informativeText = text.arg(obj->angle[1] * RADIANS_TO_DEGREES, 0, 'd', 4).arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 2).arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 2);
3077 double angle = Vector::Angle(obj->p[0], point);
3078 obj->angle[0] = angle - obj->angle[1];
3080 if (obj->angle[0] < 0)
3081 obj->angle[0] += TAU;
3083 QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
3084 informativeText = text.arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 4);
3086 else if (obj->hitObject)
3093 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
3094 QString text = QObject::tr("Radius: %1");
3095 informativeText = text.arg(obj->radius[0], 0, 'd', 4);
3101 if (obj->hitPoint[0])
3103 else if (obj->hitPoint[1])
3105 else if (obj->hitObject)
3107 // Move measurement lines in/out
3110 Dimension * d = (Dimension *)obj;
3111 double dist = Geometry::DistanceToLineFromPoint(d->lp[0], d->lp[1], point);
3112 float scaledThickness = Global::scale * obj->thickness;
3113 // Looks like offset is 0 to +MAX, but line is at 10.0. So
3114 // anything less than 10.0 should set the offset to 0.
3117 if (dist > (10.0 * scaledThickness))
3118 d->offset = dist - (10.0 * scaledThickness);
3136 // This is shitty, but works for now until I can code up something
3139 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.
3141 // TranslateObject(obj, delta);
3142 TranslateContainer((Container *)obj, point, delta);
3150 void DrawingView::AddDimensionTo(void * o)
3152 Object * obj = (Object *)o;
3157 document.Add(new Dimension(obj->p[0], obj->p[1]));