4 // Part of the Architektonas Project
5 // (C) 2011-2020 Underground Software
6 // See the README and GPLv3 files for licensing and warranty information
8 // JLH = James Hammons <jlhamm@acm.org>
11 // --- ---------- ------------------------------------------------------------
12 // JLH 03/22/2011 Created this file
13 // JLH 09/29/2011 Added middle mouse button panning
18 // - Redo rendering code to *not* use Qt's transform functions, as they are tied
19 // to a left-handed system and we need a right-handed one. [DONE]
20 // - Fixed length tool doesn't work on lines [DONE]
25 // - Layer locking (hiding works)
26 // - Fixed angle tool doesn't work on lines
27 // - Make it so "dirty" flag reflects drawing state
30 // Uncomment this for debugging...
32 //#define DEBUGFOO // Various tool debugging...
33 //#define DEBUGTP // Toolpalette debugging...
35 #include "drawingview.h"
40 #include "mathconstants.h"
42 #include "penwidget.h"
46 #define BACKGROUND_MAX_SIZE 512
48 DrawingView::DrawingView(QWidget * parent/*= NULL*/): QWidget(parent),
49 // The value in the settings file will override this.
50 useAntialiasing(true), numHovered(0), shiftDown(false),
51 ctrlDown(false), altDown(false),
52 gridBackground(BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE),
53 scale(1.0), offsetX(-10), offsetY(-10), supressSelected(false),
55 gridPixels(0), collided(false), scrollDrag(false), hoverPointValid(false),
56 hoveringIntersection(false), dragged(NULL), draggingObject(false),
57 angleSnap(false), dirty(false)
59 //wtf? doesn't work except in c++11??? document = { 0 };
60 setBackgroundRole(QPalette::Base);
61 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
63 curMarker = QCursor(QPixmap(":/res/cursor-marker.png"), 1, 18);
64 curDropper = QCursor(QPixmap(":/res/cursor-dropper.png"), 1, 20);
66 Global::gridSpacing = 12.0; // In base units (inch is default)
68 Line * line = new Line(Vector(5, 5), Vector(50, 40), 2.0, 0xFF7F00, LSDash);
70 document.Add(new Line(Vector(50, 40), Vector(10, 83)));
71 document.Add(new Line(Vector(10, 83), Vector(17, 2)));
72 document.Add(new Circle(Vector(100, 100), 36));
73 document.Add(new Circle(Vector(50, 150), 49));
74 document.Add(new Arc(Vector(300, 300), 32, TAU / 8.0, TAU * 0.65)),
75 document.Add(new Arc(Vector(200, 200), 60, TAU / 4.0, TAU * 0.75));
76 document.Add(new Text(Vector(10, 83), "Here is some awesome text!"));
81 Here we set the grid size in pixels--12 in this case. Initially, we have our
82 zoom set to make this represent 12 inches at a zoom factor of 25%. (This is
83 arbitrary.) So, to be able to decouple the grid size from the zoom, we need
84 to be able to set the size of the background grid (which we do here at an
85 arbitrary 12 pixels) to anything we want (within reason, of course :-).
87 The drawing enforces the grid spacing through the drawing->gridSpacing variable.
89 drawing->gridSpacing = 12.0 / Global::zoom;
91 Global::zoom is the zoom factor for the drawing, and all mouse clicks are
92 translated to Cartesian coordinates through this. (Initially, Global::zoom is
93 set to 1.0. SCREEN_ZOOM is set to 1.0/4.0.)
95 Really, the 100% zoom level can be set at *any* zoom level, it's more of a
96 convenience function than any measure of absolutes. Doing things that way we
97 could rid ourselves of the whole SCREEN_ZOOM parameter and all the attendant
98 shittiness that comes with it.
100 However, it seems that SCREEN_ZOOM is used to make text and arrow sizes show up
101 a certain way, which means we should probably create something else in those
102 objects to take its place--like some kind of scale factor. This would seem to
103 imply that certain point sizes actually *do* tie things like fonts to absolute
104 sizes on the screen, but not necessarily because you could have an inch scale
105 with text that is quite small relative to other objects on the screen, which
106 currently you have to zoom in to see (and which blows up the text). Point sizes
107 in an application like this are a bit meaningless; even though an inch is an
108 inch regardless of the zoom level a piece of text can be larger or smaller than
109 this. Maybe this is the case for having a base unit and basing point sizes off
112 Here's what's been figured out. Global::zoom is simply the ratio of pixels to
113 base units. What that means is that if you have a 12px grid with a 6" grid size
114 (& base unit of "inches"), Global::zoom becomes 12px / 6" = 2.0 px/in.
116 Dimensions now have a "size" parameter to set their absolute size in relation
117 to the base unit. ATM, the arrows are drawn in pixels, but also scaled by
118 Global::zoom *and* size. Same with the dimension text; it's drawn at 10pt and
119 scaled the same way as the arrowheads.
121 Need a way to scale line widths as well. :-/ Shouldn't be too difficult, just
122 need a thickness parameter similar to the "size" param for dimensions. (And now
126 // gridPixels = 12; //tmp???
127 // SetGridSize(12.0); // This is in pixels
130 void DrawingView::DrawBackground(Painter * painter)
132 Point ul = Painter::QtToCartesianCoords(Vector(0, 0));
133 Point br = Painter::QtToCartesianCoords(Vector(Global::screenSize.x, Global::screenSize.y));
135 painter->SetBrush(0xF0F0F0);
136 painter->SetPen(0xF0F0F0, 1, 1);
137 painter->DrawRect(QRectF(QPointF(ul.x, ul.y), QPointF(br.x, br.y)));
139 double spacing = Global::gridSpacing;
144 double leftx = floor(ul.x / spacing) * spacing;
145 double bottomy = floor(br.y / spacing) * spacing;
147 double w = (br.x - ul.x) + Global::gridSpacing + 1.0;
148 double h = (ul.y - br.y) + Global::gridSpacing + 1.0;
150 Vector start(leftx, bottomy), size(w, h);
152 if (Global::gridSpacing <= 0.015625)
153 DrawSubGrid(painter, 0xFFD2D2, 0.015625, start, size);
155 if (Global::gridSpacing <= 0.03125)
156 DrawSubGrid(painter, 0xFFD2D2, 0.03125, start, size);
158 if (Global::gridSpacing <= 0.0625)
159 DrawSubGrid(painter, 0xB8ECFF, 0.0625, start, size);
161 if (Global::gridSpacing <= 0.125)
162 DrawSubGrid(painter, 0xB8ECFF, 0.125, start, size);
164 if (Global::gridSpacing <= 0.25)
165 DrawSubGrid(painter, 0xDBDBFF, 0.25, start, size);
167 if (Global::gridSpacing <= 0.5)
168 DrawSubGrid(painter, 0xDBDBFF, 0.5, start, size);
170 painter->SetPen(QPen(QColor(0xD2, 0xD2, 0xFF), 2.0, Qt::SolidLine));
172 for(double i=0; i<=w; i+=spacing)
173 painter->DrawVLine(leftx + i);
175 for(double i=0; i<=h; i+=spacing)
176 painter->DrawHLine(bottomy + i);
179 void DrawingView::DrawSubGrid(Painter * painter, uint32_t color, double step, Vector start, Vector size)
181 painter->SetPen(color, 1, 1);
183 for(double i=-step; i<=size.x; i+=step*2.0)
184 painter->DrawVLine(start.x + i);
186 for(double i=-step; i<=size.y; i+=step*2.0)
187 painter->DrawHLine(start.y + i);
191 void DrawingView::SetGridSize(uint32_t size)
195 if (size == gridPixels)
202 // Recreate the background bitmap
204 QPainter pmp(&gridBackground);
205 pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
206 pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
208 for(int i=0; i<(BACKGROUND_MAX_SIZE-1); i+=gridPixels)
210 pmp.drawLine(i, 0, i, BACKGROUND_MAX_SIZE - 1);
211 pmp.drawLine(0, i, BACKGROUND_MAX_SIZE - 1, i);
216 // Set up new BG brush & zoom level (pixels per base unit)
217 // This shouldn't be done here, because it fucks up the scrollwheel zooming...
218 // Global::zoom = gridPixels / Global::gridSpacing;
219 UpdateGridBackground();
223 void DrawingView::UpdateGridBackground(void)
226 // Transform the origin to Qt coordinates
227 Vector pixmapOrigin = Painter::CartesianToQtCoords(Vector());
228 int x = (int)pixmapOrigin.x;
229 int y = (int)pixmapOrigin.y;
230 // Use mod arithmetic to grab the correct swatch of background
232 Negative numbers still screw it up... Need to think about what we're
233 trying to do here. The fact that it worked with 72 seems to have been pure luck.
234 It seems the problem is negative numbers: We can't let that happen.
235 When taking away the zero, it pops over 1 px at zero, then goes about 1/2 a
238 The bitmap looks like this:
248 @ x = 1, we want it to look like:
250 -+---+---+---+---+---
253 -+---+---+---+---+---
258 Which means we need to grab the sample from x = 3. @ x = -1:
268 Which means we need to grab the sample from x = 1. Which means we have to take
269 the mirror of the modulus of gridPixels.
271 Doing a mod of a negative number is problematic: 1st, the compiler converts the
272 negative number to an unsigned int, then it does the mod. Gets you wrong answers
273 most of the time, unless you use a power of 2. :-P So what we do here is just
274 take the modulus of the negation, which means we don't have to worry about
277 The positive case looks gruesome (and it is) but it boils down to this: We take
278 the modulus of the X coordinate, then mirror it by subtraction from the
279 maximum (in this case, gridPixels). This gives us a number in the range of 1 to
280 gridPixels. But we need the case where the result equalling gridPixels to be
281 zero; so we do another modulus operation on the result to achieve this.
286 x = (gridPixels - (x % gridPixels)) % gridPixels;
291 y = (gridPixels - (y % gridPixels)) % gridPixels;
293 // Here we grab a section of the bigger pixmap, so that the background
294 // *looks* like it's scrolling...
295 QPixmap pm = gridBackground.copy(x, y, gridPixels, gridPixels);
296 QPalette pal = palette();
297 pal.setBrush(backgroundRole(), QBrush(pm));
298 setAutoFillBackground(true);
303 void DrawingView::SetGridSize(double size)
307 if (size == gridPixelsF)
314 // Recreate the background bitmap
316 QPainter pmp(&gridBackground);
317 pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
318 pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
320 for(double i=0; i<(BACKGROUND_MAX_SIZE-1); i+=gridPixelsF)
322 pmp.drawLine(i, 0, i, (double)(BACKGROUND_MAX_SIZE - 1));
323 pmp.drawLine(0, i, (double)(BACKGROUND_MAX_SIZE - 1), i);
328 // Set up new BG brush & zoom level (pixels per base unit)
329 // This shouldn't be done here, because it fucks up the scrollwheel zooming...
330 // Global::zoom = gridPixels / Global::gridSpacing;
331 UpdateGridBackgroundF();
336 void DrawingView::UpdateGridBackgroundF(void)
339 // Transform the origin to Qt coordinates
340 Vector pixmapOrigin = Painter::CartesianToQtCoords(Vector());
341 int x = 0;// (int)pixmapOrigin.x;
342 int y = 0;// (int)pixmapOrigin.y;
343 // Use mod arithmetic to grab the correct swatch of background
348 x = (gridPixels - (x % gridPixels)) % gridPixels;
353 y = (gridPixels - (y % gridPixels)) % gridPixels;*/
355 // Here we grab a section of the bigger pixmap, so that the background
356 // *looks* like it's scrolling...
357 QPixmap pm = gridBackground.copy(x, y, gridPixelsF, gridPixelsF);
358 QPalette pal = palette();
359 pal.setBrush(backgroundRole(), QBrush(pm));
360 setAutoFillBackground(true);
367 // Basically, we just make a single pass through the Container. If the layer #
368 // is less than the layer # being deleted, then do nothing. If the layer # is
369 // equal to the layer # being deleted, then delete the object. If the layer #
370 // is greater than the layer # being deleted, then set the layer # to its layer
373 void DrawingView::DeleteCurrentLayer(int layer)
375 //printf("DrawingView::DeleteCurrentLayer(): currentLayer = %i\n", layer);
376 VPVectorIter i = document.objects.begin();
378 while (i != document.objects.end())
380 Object * obj = (Object *)(*i);
382 if (obj->layer < layer)
384 else if (obj->layer == layer)
386 document.objects.erase(i);
396 // We've just done a destructive action, so update the screen!
400 void DrawingView::HandleLayerToggle(void)
402 // A layer's visibility was toggled, so update the screen...
407 // A layer was moved up or down in the layer list, so we have to swap the
408 // document's object's layer numbers in the layers that were swapped.
410 void DrawingView::HandleLayerSwap(int layer1, int layer2)
412 //printf("DrawingView: Swapping layers %i and %i.\n", layer1, layer2);
413 HandleLayerSwap(layer1, layer2, document.objects);
417 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
419 void DrawingView::HandleLayerSwap(int layer1, int layer2, VPVector & v)
421 for(VPVectorIter i=v.begin(); i!=v.end(); i++)
423 Object * obj = (Object *)(*i);
425 if (obj->layer == layer1)
427 else if (obj->layer == layer2)
430 if (obj->type == OTContainer)
431 HandleLayerSwap(layer1, layer2, ((Container *)obj)->objects);
435 void DrawingView::HandlePenWidth(float width)
437 for(VPVectorIter i=select.begin(); i!=select.end(); i++)
439 Object * obj = (Object *)(*i);
440 obj->thickness = width;
443 supressSelected = true;
447 void DrawingView::HandlePenStyle(int style)
449 for(VPVectorIter i=select.begin(); i!=select.end(); i++)
451 Object * obj = (Object *)(*i);
455 supressSelected = true;
459 void DrawingView::HandlePenColor(uint32_t color)
461 for(VPVectorIter i=select.begin(); i!=select.end(); i++)
463 Object * obj = (Object *)(*i);
467 supressSelected = true;
471 void DrawingView::HandlePenStamp(QAction * action)
473 PenWidget * pw = (PenWidget *)action->parentWidget();
474 pw->dropperAction->setChecked(false);
475 Global::penDropper = false;
476 Global::penStamp = action->isChecked();
478 if (Global::penStamp)
479 setCursor(curMarker);
481 setCursor(Qt::ArrowCursor);
483 if (Global::penStamp == false)
484 ClearSelected(document.objects);
489 void DrawingView::HandlePenDropper(QAction * action)
491 PenWidget * pw = (PenWidget *)action->parentWidget();
492 pw->stampAction->setChecked(false);
493 Global::penStamp = false;
494 Global::penDropper = action->isChecked();
496 if (Global::penDropper)
497 setCursor(curDropper);
499 setCursor(Qt::ArrowCursor);
504 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
506 // This is undoing the transform, e.g. going from client coords to local
507 // coords. In essence, the height - y is height + (y * -1), the (y * -1)
508 // term doing the conversion of the y-axis from increasing bottom to top.
509 return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
512 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
514 // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
515 // No voodoo here, it's just grouped wrong to see it. It should be:
516 // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive [why? we use -offsetX after all]
517 return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
520 void DrawingView::focusOutEvent(QFocusEvent * /*event*/)
522 // printf("DrawingView::focusOutEvent()...\n");
523 // Make sure all modkeys being held are marked as released when the app
524 // loses focus (N.B.: This only works because the app sets the focus policy
525 // of this object to something other than Qt::NoFocus)
526 shiftDown = ctrlDown = altDown = false;
528 setCursor(Qt::ArrowCursor);
531 void DrawingView::focusInEvent(QFocusEvent * /*event*/)
533 if (Global::penStamp)
534 setCursor(curMarker);
535 else if (Global::penDropper)
536 setCursor(curDropper);
537 //FocusOut already set this...
539 // setCursor(Qt::ArrowCursor);
542 void DrawingView::paintEvent(QPaintEvent * /*event*/)
544 QPainter qtPainter(this);
545 Painter painter(&qtPainter);
548 qtPainter.setRenderHint(QPainter::Antialiasing);
550 Global::viewportHeight = size().height();
552 DrawBackground(&painter);
554 // Draw coordinate axes
555 painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
556 painter.DrawLine(0, -16384, 0, 16384);
557 painter.DrawLine(-16384, 0, 16384, 0);
559 // Do object rendering...
560 for(int i=0; i<Global::numLayers; i++)
562 if (Global::layerHidden[i] == false)
563 RenderObjects(&painter, document.objects, i);
566 // Do tool rendering, if any...
569 painter.SetPen(QPen(QColor(200, 100, 0, 255), 1.0, Qt::DashLine));
570 painter.DrawCrosshair(oldPoint);
574 // Do selection rectangle rendering, if any
575 if (Global::selectionInProgress)
577 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
578 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
579 painter.DrawRect(Global::selection);
582 if (hoveringIntersection)
583 painter.DrawHandle(intersectionPoint);
586 painter.DrawHandle(hoverPoint);
588 if (!informativeText.isEmpty())
589 painter.DrawInformativeText(informativeText);
593 // Renders objects in the passed in vector
596 N.B.: Since we have "hoverPointValid" drawing regular object handles above,
597 we can probably do away with a lot of them that are being done down below.
599 [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...]
601 void DrawingView::RenderObjects(Painter * painter, VPVector & v, int layer, bool ignoreLayer/*= false*/)
603 for(VPVectorIter i=v.begin(); i!=v.end(); i++)
605 Object * obj = (Object *)(*i);
606 float scaledThickness = Global::scale * obj->thickness;
608 // If the object isn't on the current layer being drawn, skip it
609 if (!ignoreLayer && (obj->layer != layer))
612 if ((Global::tool == TTRotate) && ctrlDown && obj->selected)
614 painter->SetPen(0x00FF00, 2.0, LSSolid);
618 painter->SetPen(obj->color, Global::zoom * scaledThickness, obj->style);
619 painter->SetBrush(obj->color);
621 // penStamp supresses object highlighting, so that changes can be seen.
622 if (supressSelected || Global::penStamp)
626 painter->SetPen(Global::penColor, Global::zoom * Global::scale * Global::penWidth, Global::penStyle);
627 painter->SetBrush(Global::penColor);
630 else if (obj->selected || obj->hitObject)
631 painter->SetPen(0xFF0000, Global::zoom * scaledThickness, LSDash);
637 painter->DrawLine(obj->p[0], obj->p[1]);
639 if (obj->hitPoint[0])
640 painter->DrawHandle(obj->p[0]);
642 if (obj->hitPoint[1])
643 painter->DrawHandle(obj->p[1]);
646 painter->DrawSmallHandle(Geometry::Midpoint((Line *)obj));
651 painter->SetBrush(QBrush(Qt::NoBrush));
652 painter->DrawEllipse(obj->p[0], obj->radius[0], obj->radius[0]);
654 if (obj->hitPoint[0])
655 painter->DrawHandle(obj->p[0]);
660 painter->DrawArc(obj->p[0], obj->radius[0], obj->angle[0], obj->angle[1]);
662 if (obj->hitPoint[0])
663 painter->DrawHandle(obj->p[0]);
665 if (obj->hitPoint[1])
666 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]));
668 if (obj->hitPoint[2])
669 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0] + obj->angle[1]), sin(obj->angle[0] + obj->angle[1])) * obj->radius[0]));
675 Dimension * d = (Dimension *)obj;
677 Vector v(d->p[0], d->p[1]);
678 double angle = v.Angle();
679 Vector unit = v.Unit();
680 d->lp[0] = d->p[0], d->lp[1] = d->p[1];
682 double x1, y1, length;
684 if (d->subtype == DTLinearVert)
686 if ((angle < 0) || (angle > HALF_TAU))
688 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
689 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
690 ortho = Vector(1.0, 0);
691 angle = THREE_QTR_TAU;
695 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
696 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
697 ortho = Vector(-1.0, 0);
701 d->lp[0].x = d->lp[1].x = x1;
702 length = fabs(d->p[0].y - d->p[1].y);
704 else if (d->subtype == DTLinearHorz)
706 if ((angle < QTR_TAU) || (angle > THREE_QTR_TAU))
708 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
709 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
710 ortho = Vector(0, 1.0);
715 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
716 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
717 ortho = Vector(0, -1.0);
721 d->lp[0].y = d->lp[1].y = y1;
722 length = fabs(d->p[0].x - d->p[1].x);
724 else if (d->subtype == DTLinear)
726 angle = Vector(d->lp[0], d->lp[1]).Angle();
727 ortho = Vector::Normal(d->lp[0], d->lp[1]);
728 length = v.Magnitude();
731 unit = Vector(d->lp[0], d->lp[1]).Unit();
733 Point p1 = d->lp[0] + (ortho * (d->offset + (10.0 * scaledThickness)));
734 Point p2 = d->lp[1] + (ortho * (d->offset + (10.0 * scaledThickness)));
735 Point p3 = d->lp[0] + (ortho * (d->offset + (16.0 * scaledThickness)));
736 Point p4 = d->lp[1] + (ortho * (d->offset + (16.0 * scaledThickness)));
737 Point p5 = d->p[0] + (ortho * 4.0 * scaledThickness);
738 Point p6 = d->p[1] + (ortho * 4.0 * scaledThickness);
741 The numbers hardcoded into here, what are they?
742 I believe they are pixels.
744 // Draw extension lines (if certain type)
745 painter->DrawLine(p3, p5);
746 painter->DrawLine(p4, p6);
748 // Calculate whether or not the arrowheads are too crowded to put
749 // inside the extension lines. 9.0 is the length of the arrowhead.
750 double t = Geometry::ParameterOfLineAndPoint(d->lp[0], d->lp[1], d->lp[1] - (unit * 9.0 * scaledThickness));
752 // On the screen, it's acting like this is actually 58%...
753 // This is correct, we want it to happen at > 50%
756 // Draw main dimension line + arrowheads
757 painter->DrawLine(p1, p2);
758 painter->DrawArrowhead(p1, p2, scaledThickness);
759 painter->DrawArrowhead(p2, p1, scaledThickness);
763 // Draw outside arrowheads
764 Point p7 = p1 - (unit * 9.0 * scaledThickness);
765 Point p8 = p2 + (unit * 9.0 * scaledThickness);
766 painter->DrawArrowhead(p1, p7, scaledThickness);
767 painter->DrawArrowhead(p2, p8, scaledThickness);
768 painter->DrawLine(p1, p1 - (unit * 14.0 * scaledThickness));
769 painter->DrawLine(p2, p2 + (unit * 14.0 * scaledThickness));
772 // Draw length of dimension line...
773 painter->SetFont(QFont("Arial", 8.0 * Global::zoom * scaledThickness));
774 Point ctr = p2 + (Vector(p2, p1) / 2.0);
779 dimText = QString("%1\"").arg(length);
782 double feet = (double)((int)length / 12);
783 double inches = length - (feet * 12.0);
786 dimText = QString("%1'").arg(feet);
788 dimText = QString("%1' %2\"").arg(feet).arg(inches);
792 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().
794 painter->DrawAngledText(ctr, angle, dimText, scaledThickness);
798 Point hp1 = (p1 + p2) / 2.0;
799 Point hp2 = (p1 + hp1) / 2.0;
800 Point hp3 = (hp1 + p2) / 2.0;
804 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
805 painter->SetBrush(QBrush(QColor(Qt::magenta)));
806 painter->DrawArrowHandle(hp1, ortho.Angle() + HALF_TAU);
807 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
810 painter->DrawHandle(hp1);
811 painter->SetPen(QPen(Qt::blue, 1.0 * Global::zoom * scaledThickness, Qt::SolidLine));
815 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
816 painter->SetBrush(QBrush(QColor(Qt::magenta)));
817 painter->DrawArrowToLineHandle(hp2, (d->subtype == DTLinearVert ? v.Angle() - QTR_TAU : (v.Angle() < HALF_TAU ? HALF_TAU : 0)));
818 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
821 painter->DrawHandle(hp2);
822 painter->SetPen(QPen(Qt::blue, 1.0 * Global::zoom * scaledThickness, Qt::SolidLine));
826 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
827 painter->SetBrush(QBrush(QColor(Qt::magenta)));
828 painter->DrawArrowToLineHandle(hp3, (d->subtype == DTLinearHorz ? v.Angle() - QTR_TAU : (v.Angle() > HALF_TAU && v.Angle() < THREE_QTR_TAU ? THREE_QTR_TAU : QTR_TAU)));
829 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
832 painter->DrawHandle(hp3);
835 if (obj->hitPoint[0])
836 painter->DrawHandle(obj->p[0]);
838 if (obj->hitPoint[1])
839 painter->DrawHandle(obj->p[1]);
846 Text * t = (Text *)obj;
848 if (t->measured == false)
850 t->extents = painter->MeasureTextObject(t->s.c_str(), scaledThickness);
854 painter->DrawTextObject(t->p[0], t->s.c_str(), scaledThickness, t->angle[0]);
870 // Containers require recursive rendering...
871 Container * c = (Container *)obj;
872 //printf("About to render container: # objs=%i, layer=%i\n", (*c).objects.size(), layer);
873 RenderObjects(painter, (*c).objects, layer, ignoreLayer);
875 //printf("Container extents: <%lf, %lf>, <%lf, %lf>\nsize: %i\n", r.l, r.t, r.r, r.b, c->objects.size());
876 // Containers also have special indicators showing they are selected
877 if (c->selected || c->hitObject)
879 // Rect r = GetObjectExtents(obj);
880 // painter->DrawRectCorners(r);
881 painter->DrawRectCorners(Rect(c->p[0], c->p[1]));
892 supressSelected = false;
896 // This toggles the selection being hovered (typically, only 1 object)
898 void DrawingView::AddHoveredToSelection(void)
900 for(VPVectorIter i=document.objects.begin(); i!=document.objects.end(); i++)
902 if (((Object *)(*i))->hovered)
903 // ((Object *)(*i))->selected = true;
904 ((Object *)(*i))->selected = !((Object *)(*i))->selected;
908 VPVector DrawingView::GetSelection(void)
912 for(VPVectorIter i=document.objects.begin(); i!=document.objects.end(); i++)
914 if (((Object *)(*i))->selected)
921 VPVector DrawingView::GetHovered(void)
925 for(VPVectorIter i=document.objects.begin(); i!=document.objects.end(); i++)
927 if (((Object *)(*i))->hovered)
934 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
936 Global::screenSize = Vector(size().width(), size().height());
937 // UpdateGridBackground();
940 void DrawingView::ToolHandler(int mode, Point p)
942 // Drop angle snap until it's needed
945 if (Global::tool == TTLine)
946 LineHandler(mode, p);
947 else if (Global::tool == TTCircle)
948 CircleHandler(mode, p);
949 else if (Global::tool == TTArc)
951 else if (Global::tool == TTRotate)
952 RotateHandler(mode, p);
953 else if (Global::tool == TTMirror)
954 MirrorHandler(mode, p);
955 else if (Global::tool == TTDimension)
956 DimensionHandler(mode, p);
957 else if (Global::tool == TTTriangulate)
958 TriangulateHandler(mode, p);
959 else if (Global::tool == TTTrim)
960 TrimHandler(mode, p);
961 else if (Global::tool == TTParallel)
962 ParallelHandler(mode, p);
965 void DrawingView::ToolDraw(Painter * painter)
967 if (Global::tool == TTLine)
969 if (Global::toolState == TSNone)
971 painter->DrawHandle(toolPoint[0]);
973 else if ((Global::toolState == TSPoint2) && shiftDown)
975 painter->DrawHandle(toolPoint[1]);
979 painter->DrawLine(toolPoint[0], toolPoint[1]);
980 painter->DrawHandle(toolPoint[1]);
982 Vector v(toolPoint[0], toolPoint[1]);
983 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
984 double absLength = v.Magnitude();
985 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
986 informativeText = text.arg(absLength).arg(absAngle);
989 else if (Global::tool == TTCircle)
991 if (Global::toolState == TSNone)
993 painter->DrawHandle(toolPoint[0]);
995 else if ((Global::toolState == TSPoint2) && shiftDown)
997 painter->DrawHandle(toolPoint[1]);
1001 painter->DrawCross(toolPoint[0]);
1002 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
1003 painter->SetBrush(QBrush(Qt::NoBrush));
1004 painter->DrawEllipse(toolPoint[0], length, length);
1005 QString text = tr("Radius: %1 in.");
1006 informativeText = text.arg(length);
1009 else if (Global::tool == TTArc)
1011 if (Global::toolState == TSNone)
1013 painter->DrawHandle(toolPoint[0]);
1015 else if (Global::toolState == TSPoint2)
1017 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
1018 painter->SetBrush(QBrush(Qt::NoBrush));
1019 painter->DrawEllipse(toolPoint[0], length, length);
1020 painter->DrawLine(toolPoint[0], toolPoint[1]);
1021 painter->DrawHandle(toolPoint[1]);
1022 QString text = tr("Radius: %1 in.");
1023 informativeText = text.arg(length);
1025 else if (Global::toolState == TSPoint3)
1027 double angle = Vector::Angle(toolPoint[0], toolPoint[2]);
1028 painter->DrawLine(toolPoint[0], toolPoint[2]);
1029 painter->SetBrush(QBrush(Qt::NoBrush));
1030 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
1031 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
1032 QString text = tr("Angle start: %1") + QChar(0x00B0);
1033 informativeText = text.arg(RADIANS_TO_DEGREES * angle);
1037 double angle = Vector::Angle(toolPoint[0], toolPoint[3]);
1038 double span = angle - toolPoint[2].x;
1043 painter->DrawLine(toolPoint[0], toolPoint[3]);
1044 painter->SetBrush(QBrush(Qt::NoBrush));
1045 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
1046 painter->SetPen(0xFF00FF, 2.0, LSSolid);
1047 painter->DrawArc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
1048 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
1049 QString text = tr("Arc span: %1") + QChar(0x00B0);
1050 informativeText = text.arg(RADIANS_TO_DEGREES * span);
1053 else if (Global::tool == TTRotate)
1055 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
1056 painter->DrawHandle(toolPoint[0]);
1057 else if ((Global::toolState == TSPoint2) && shiftDown)
1058 painter->DrawHandle(toolPoint[1]);
1061 if (toolPoint[0] == toolPoint[1])
1064 painter->DrawLine(toolPoint[0], toolPoint[1]);
1066 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
1067 QString text = QChar(0x2221) + QObject::tr(": %1");
1068 informativeText = text.arg(absAngle);
1071 informativeText += " (Copy)";
1074 else if (Global::tool == TTMirror)
1076 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
1077 painter->DrawHandle(toolPoint[0]);
1078 else if ((Global::toolState == TSPoint2) && shiftDown)
1079 painter->DrawHandle(toolPoint[1]);
1082 if (toolPoint[0] == toolPoint[1])
1085 Point mirrorPoint = toolPoint[0] + Vector(toolPoint[1], toolPoint[0]);
1086 painter->DrawLine(mirrorPoint, toolPoint[1]);
1088 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
1090 if (absAngle > 180.0)
1093 QString text = QChar(0x2221) + QObject::tr(": %1");
1094 informativeText = text.arg(absAngle);
1097 informativeText += " (Copy)";
1100 if (Global::tool == TTDimension)
1102 if (Global::toolState == TSNone)
1104 painter->DrawHandle(toolPoint[0]);
1106 else if ((Global::toolState == TSPoint2) && shiftDown)
1108 painter->DrawHandle(toolPoint[1]);
1112 painter->DrawLine(toolPoint[0], toolPoint[1]);
1113 painter->DrawHandle(toolPoint[1]);
1115 Vector v(toolPoint[0], toolPoint[1]);
1116 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
1117 double absLength = v.Magnitude();
1118 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
1119 informativeText = text.arg(absLength).arg(absAngle);
1124 void DrawingView::LineHandler(int mode, Point p)
1129 if (Global::toolState == TSNone)
1137 if (Global::toolState == TSNone)
1145 if (Global::toolState == TSNone)
1147 Global::toolState = TSPoint2;
1148 // Prevent spurious line from drawing...
1149 toolPoint[1] = toolPoint[0];
1151 else if ((Global::toolState == TSPoint2) && shiftDown)
1153 // Key override is telling us to make a new line, not continue the
1155 toolPoint[0] = toolPoint[1];
1159 Line * l = new Line(toolPoint[0], toolPoint[1], Global::penWidth, Global::penColor, Global::penStyle);
1160 l->layer = Global::activeLayer;
1161 document.objects.push_back(l);
1162 toolPoint[0] = toolPoint[1];
1167 void DrawingView::CircleHandler(int mode, Point p)
1172 if (Global::toolState == TSNone)
1180 if (Global::toolState == TSNone)
1188 if (Global::toolState == TSNone)
1190 Global::toolState = TSPoint2;
1191 // Prevent spurious line from drawing...
1192 toolPoint[1] = toolPoint[0];
1194 else if ((Global::toolState == TSPoint2) && shiftDown)
1196 // Key override is telling us to make a new line, not continue the
1198 toolPoint[0] = toolPoint[1];
1202 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
1203 Circle * c = new Circle(toolPoint[0], length, Global::penWidth, Global::penColor, Global::penStyle);
1204 c->layer = Global::activeLayer;
1205 document.objects.push_back(c);
1206 toolPoint[0] = toolPoint[1];
1207 Global::toolState = TSNone;
1212 void DrawingView::ArcHandler(int mode, Point p)
1217 if (Global::toolState == TSNone)
1219 else if (Global::toolState == TSPoint2)
1221 else if (Global::toolState == TSPoint3)
1229 if (Global::toolState == TSNone)
1231 else if (Global::toolState == TSPoint2)
1233 else if (Global::toolState == TSPoint3)
1247 if (Global::toolState == TSNone)
1249 // Prevent spurious line from drawing...
1250 toolPoint[1] = toolPoint[0];
1251 Global::toolState = TSPoint2;
1253 else if (Global::toolState == TSPoint2)
1257 // Key override is telling us to start arc at new center, not
1258 // continue the current one.
1259 toolPoint[0] = toolPoint[1];
1263 // Set the radius in toolPoint[1].x
1264 toolPoint[1].x = Vector::Magnitude(toolPoint[0], toolPoint[1]);
1265 Global::toolState = TSPoint3;
1267 else if (Global::toolState == TSPoint3)
1269 // Set the angle in toolPoint[2].x
1270 toolPoint[2].x = Vector::Angle(toolPoint[0], toolPoint[2]);
1271 Global::toolState = TSPoint4;
1275 double endAngle = Vector::Angle(toolPoint[0], toolPoint[3]);
1276 double span = endAngle - toolPoint[2].x;
1281 Arc * arc = new Arc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span, Global::penWidth, Global::penColor, Global::penStyle);
1282 arc->layer = Global::activeLayer;
1283 document.objects.push_back(arc);
1284 Global::toolState = TSNone;
1289 void DrawingView::RotateHandler(int mode, Point p)
1294 if (Global::toolState == TSNone)
1297 // SavePointsFrom(select, toolScratch);
1298 CopyObjects(select, toolScratch2);
1299 Global::toolState = TSPoint1;
1301 else if (Global::toolState == TSPoint1)
1309 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1311 else if (Global::toolState == TSPoint2)
1319 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1320 VPVectorIter j = select.begin();
1321 // std::vector<Object>::iterator i = toolScratch.begin();
1322 VPVectorIter i = toolScratch2.begin();
1324 // for(; i!=toolScratch.end(); i++, j++)
1325 for(; i!=toolScratch2.end(); i++, j++)
1327 // Object objT = *i;
1328 // Point p1 = Geometry::RotatePointAroundPoint(objT.p[0], toolPoint[0], angle);
1329 // Point p2 = Geometry::RotatePointAroundPoint(objT.p[1], toolPoint[0], angle);
1330 Object * objT = (Object *)(*i);
1331 Object * objS = (Object *)(*j);
1333 Point p1 = Geometry::RotatePointAroundPoint(objT->p[0], toolPoint[0], angle);
1334 Point p2 = Geometry::RotatePointAroundPoint(objT->p[1], toolPoint[0], angle);
1339 // if (objT.type == OTArc || objT.type == OTText)
1340 if (objT->type == OTArc || objT->type == OTText)
1342 // objS->angle[0] = objT.angle[0] + angle;
1343 objS->angle[0] = objT->angle[0] + angle;
1345 if (objS->angle[0] > TAU)
1346 objS->angle[0] -= TAU;
1348 // else if (objT.type == OTContainer)
1349 else if (objT->type == OTContainer)
1351 // 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...]
1352 // Container * c = (Container *)&objT;
1353 Container * c = (Container *)objT;
1354 Container * c2 = (Container *)objS;
1355 VPVectorIter l = c->objects.begin();
1356 // TODO: Rotate items in the container
1357 // TODO: Make this recursive
1358 for(VPVectorIter k=c2->objects.begin(); k!=c2->objects.end(); k++, l++)
1360 Object * obj3 = (Object *)(*k);
1361 Object * obj4 = (Object *)(*l);
1363 p1 = Geometry::RotatePointAroundPoint(obj4->p[0], toolPoint[0], angle);
1364 p2 = Geometry::RotatePointAroundPoint(obj4->p[1], toolPoint[0], angle);
1368 // obj3->angle[0] = objT.angle[0] + angle;
1369 obj3->angle[0] = obj4->angle[0] + angle;
1371 if (obj3->angle[0] > TAU)
1372 obj3->angle[0] -= TAU;
1375 Rect r = GetObjectExtents(objS);
1376 c2->p[0] = r.TopLeft();
1377 c2->p[1] = r.BottomRight();
1385 if (Global::toolState == TSPoint1)
1387 Global::toolState = TSPoint2;
1388 // Prevent spurious line from drawing...
1389 toolPoint[1] = toolPoint[0];
1391 else if ((Global::toolState == TSPoint2) && shiftDown)
1393 // Key override is telling us to make a new line, not continue the
1395 toolPoint[0] = toolPoint[1];
1399 // Either we're finished with our rotate, or we're stamping a copy.
1402 // Stamp a copy of the selection at the current rotation & bail
1404 CopyObjects(select, temp);
1405 ClearSelected(temp);
1406 AddObjectsTo(document.objects, temp);
1407 // RestorePointsTo(select, toolScratch);
1408 RestorePointsTo(select, toolScratch2);
1413 Global::toolState = TSPoint1;
1414 // SavePointsFrom(select, toolScratch);
1415 DeleteContents(toolScratch2);
1416 CopyObjects(select, toolScratch2);
1422 // Reset the selection if shift held down...
1424 // RestorePointsTo(select, toolScratch);
1425 RestorePointsTo(select, toolScratch2);
1430 // Reset selection when key is let up
1432 RotateHandler(ToolMouseMove, toolPoint[1]);
1437 // RestorePointsTo(select, toolScratch);
1438 RestorePointsTo(select, toolScratch2);
1439 DeleteContents(toolScratch2);
1443 void DrawingView::MirrorHandler(int mode, Point p)
1448 if (Global::toolState == TSNone)
1451 SavePointsFrom(select, toolScratch);
1452 Global::toolState = TSPoint1;
1454 else if (Global::toolState == TSPoint1)
1462 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1464 else if (Global::toolState == TSPoint2)
1472 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1473 VPVectorIter j = select.begin();
1474 std::vector<Object>::iterator i = toolScratch.begin();
1476 for(; i!=toolScratch.end(); i++, j++)
1479 Point p1 = Geometry::MirrorPointAroundLine(obj.p[0], toolPoint[0], toolPoint[1]);
1480 Point p2 = Geometry::MirrorPointAroundLine(obj.p[1], toolPoint[0], toolPoint[1]);
1481 Object * obj2 = (Object *)(*j);
1486 N.B.: When mirroring an arc thru a horizontal axis, this causes the arc to have
1487 a negative start angle which makes it impossible to interact with.
1490 if (obj.type == OTArc)
1492 // This is 2*mirror angle - obj angle - obj span
1493 obj2->angle[0] = (2.0 * angle) - obj.angle[0] - obj.angle[1];
1495 if (obj2->angle[0] > TAU)
1496 obj2->angle[0] -= TAU;
1504 if (Global::toolState == TSPoint1)
1506 Global::toolState = TSPoint2;
1507 // Prevent spurious line from drawing...
1508 toolPoint[1] = toolPoint[0];
1510 else if ((Global::toolState == TSPoint2) && shiftDown)
1512 // Key override is telling us to make a new line, not continue the
1514 toolPoint[0] = toolPoint[1];
1518 // Either we're finished with our rotate, or we're stamping a copy.
1521 // Stamp a copy of the selection at the current rotation & bail
1523 CopyObjects(select, temp);
1524 ClearSelected(temp);
1525 AddObjectsTo(document.objects, temp);
1526 RestorePointsTo(select, toolScratch);
1531 Global::toolState = TSPoint1;
1532 SavePointsFrom(select, toolScratch);
1538 // Reset the selection if shift held down...
1540 RestorePointsTo(select, toolScratch);
1545 // Reset selection when key is let up
1547 MirrorHandler(ToolMouseMove, toolPoint[1]);
1552 RestorePointsTo(select, toolScratch);
1556 void DrawingView::DimensionHandler(int mode, Point p)
1561 if (Global::toolState == TSNone)
1569 if (Global::toolState == TSNone)
1577 if (Global::toolState == TSNone)
1579 Global::toolState = TSPoint2;
1580 // Prevent spurious line from drawing...
1581 toolPoint[1] = toolPoint[0];
1583 else if ((Global::toolState == TSPoint2) && shiftDown)
1585 // Key override is telling us to make a new line, not continue the
1587 toolPoint[0] = toolPoint[1];
1591 Dimension * d = new Dimension(toolPoint[0], toolPoint[1], DTLinear);
1592 d->layer = Global::activeLayer;
1593 document.objects.push_back(d);
1594 Global::toolState = TSNone;
1599 void DrawingView::TriangulateHandler(int mode, Point/*p*/)
1605 // Skip if nothing hovered...
1606 if (numHovered != 1)
1609 VPVector hover = GetHovered();
1610 Object * obj = (Object *)hover[0];
1612 // Skip if it's not a line...
1613 if (obj->type != OTLine)
1616 if (Global::toolState == TSNone)
1618 else if (Global::toolState == TSPoint2)
1627 if (Global::toolState == TSNone)
1629 else if (Global::toolState == TSPoint2)
1631 else if (Global::toolState == TSPoint3)
1645 if (Global::toolState == TSNone)
1647 Global::toolState = TSPoint2;
1649 else if (Global::toolState == TSPoint2)
1653 // Key override is telling us to start arc at new center, not
1654 // continue the current one.
1655 toolPoint[0] = toolPoint[1];
1659 Global::toolState = TSPoint3;
1663 double len2 = Vector::Magnitude(toolObj[1]->p[0], toolObj[1]->p[1]);
1664 double len3 = Vector::Magnitude(toolObj[2]->p[0], toolObj[2]->p[1]);
1666 Circle c1(toolObj[0]->p[0], len2);
1667 Circle c2(toolObj[0]->p[1], len3);
1669 Geometry::CheckCircleToCircleIntersection((Object *)&c1, (Object *)&c2);
1671 // Only move lines if the triangle formed by them is not degenerate
1672 if (Global::numIntersectPoints > 0)
1674 toolObj[1]->p[0] = toolObj[0]->p[0];
1675 toolObj[1]->p[1] = Global::intersectPoint[0];
1677 toolObj[2]->p[0] = Global::intersectPoint[0];
1678 toolObj[2]->p[1] = toolObj[0]->p[1];
1681 Global::toolState = TSNone;
1686 void DrawingView::TrimHandler(int mode, Point p)
1689 n.b.: this code is lifted straight out of the old oo code. needs to be updated.
1696 Object * toTrim = doc->lastObjectHovered;
1701 Vector v(((Line *)toTrim)->position, ((Line *)toTrim)->endpoint);
1703 // Check to see which case we have...
1704 // We're trimming point #1...
1707 ((Line *)toTrim)->position = ((Line *)toTrim)->position + (v * u);
1711 ((Line *)toTrim)->endpoint = ((Line *)toTrim)->position + (v * t);
1715 Point p1 = ((Line *)toTrim)->position + (v * t);
1716 Point p2 = ((Line *)toTrim)->position + (v * u);
1717 Point p3 = ((Line *)toTrim)->endpoint;
1718 ((Line *)toTrim)->endpoint = p1;
1719 Line * line = new Line(p2, p3);
1720 emit ObjectReady(line);
1723 doc->lastObjectHovered = NULL;
1731 Object * toTrim = doc->lastObjectHovered;
1737 if (toTrim->type != OTLine)
1740 double pointHoveredT = Geometry::ParameterOfLineAndPoint(((Line *)toTrim)->position, ((Line *)toTrim)->endpoint, point);
1742 std::vector<Object *>::iterator i;
1744 for(i=doc->objects.begin(); i!=doc->objects.end(); i++)
1746 // Can't trim against yourself... :-P
1750 Object * trimAgainst = *i;
1753 if ((toTrim->type != OTLine) || (trimAgainst->type != OTLine))
1756 int intersects = Geometry::Intersects((Line *)toTrim, (Line *)trimAgainst, &t1);//, &u1);
1760 // Now what? We don't know which side to trim!
1761 // ... now we do, we know which side of the Line we're on!
1762 if ((t1 > t) && (t1 < pointHoveredT))
1765 if ((t1 < u) && (t1 > pointHoveredT))
1770 // Bail out if nothing hovered...
1771 if (numHovered != 1)
1777 VPVector hover = GetHovered();
1778 Object * obj = (Object *)hover[0];
1780 // Skip if it's not a line...
1781 if (obj->type != OTLine)
1788 double hoveredParam = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], p);
1790 // Currently only deal with line against line trimming, can expand to
1791 // others as well (line/circle, circle/circle, line/arc, etc)
1793 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1795 obj = (Object *)(*i);
1797 if (obj == toolObj[0])
1799 else if (obj->type != OTLine)
1802 Geometry::CheckLineToLineIntersection(toolObj[0], obj);
1804 if (Global::numIntersectParams > 0)
1806 // Mark the line segment somehow (the side the mouse is on) so that it can be drawn & trimmed when we hit ToolMouseDown.
1826 void DrawingView::ParallelHandler(int mode, Point /*p*/)
1850 void DrawingView::mousePressEvent(QMouseEvent * event)
1852 if (event->button() == Qt::LeftButton)
1854 //printf("mousePressEvent::Qt::LeftButton numHovered=%li\n", numHovered);
1855 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1857 // Handle tool processing, if any
1860 if (hoveringIntersection)
1861 point = intersectionPoint;
1862 else if (hoverPointValid)
1864 else if (Global::snapToGrid)
1865 point = SnapPointToGrid(point);
1867 //Also, may want to figure out if hovering over a snap point on an
1868 //object, snap to grid if not.
1869 // Snap to object point if valid...
1870 // if (Global::snapPointIsValid)
1871 // point = Global::snapPoint;
1873 ToolHandler(ToolMouseDown, point);
1877 // Clear the selection only if CTRL isn't being held on click
1879 ClearSelected(document.objects);
1880 // ClearSelection();
1882 // If any objects are being hovered on click, add them to the selection
1886 AddHoveredToSelection();
1887 update(); // needed??
1888 // GetHovered(hover); // prolly needed
1889 VPVector hover2 = GetHovered();
1890 dragged = (Object *)hover2[0];
1891 draggingObject = true;
1892 //printf("mousePressEvent::numHovered > 0 (hover2[0]=$%llx, type=%s)\n", dragged, objName[dragged->type]);
1894 // Alert the pen widget
1895 // Maybe do this with an eyedropper tool on the pen bar? [YES]
1896 // emit ObjectSelected(dragged);
1897 if (Global::penDropper)
1899 Global::penColor = dragged->color;
1900 Global::penWidth = dragged->thickness;
1901 Global::penStyle = dragged->style;
1902 emit ObjectSelected(dragged);
1903 ClearSelected(document.objects);
1907 if (Global::penStamp)
1909 dragged->color = Global::penColor;
1910 dragged->thickness = Global::penWidth;
1911 dragged->style = Global::penStyle;
1915 // See if anything is using just a straight click on a handle
1916 if (HandleObjectClicked())
1918 draggingObject = false;
1923 // Needed for grab & moving objects
1924 // We do it *after*... why? (doesn't seem to confer any advantage...)
1925 if (hoveringIntersection)
1926 oldPoint = intersectionPoint;
1927 else if (hoverPointValid)
1928 oldPoint = hoverPoint;
1929 else if (Global::snapToGrid)
1930 oldPoint = SnapPointToGrid(point);
1932 // Needed for fixed length handling
1933 if (Global::fixedLength)
1935 if (dragged->type == OTLine)
1937 dragged->length = Vector::Magnitude(dragged->p[0], dragged->p[1]);
1941 if (dragged->type == OTCircle)
1943 // Save for informative text, uh, er, informing
1944 dragged->length = dragged->radius[0];
1950 // Didn't hit any object and not using a tool, so do a selection
1952 Global::selectionInProgress = true;
1953 Global::selection.setTopLeft(QPointF(point.x, point.y));
1954 Global::selection.setBottomRight(QPointF(point.x, point.y));
1955 select = GetSelection();
1957 else if (event->button() == Qt::MiddleButton)
1960 oldPoint = Vector(event->x(), event->y());
1961 // Should also change the mouse pointer as well...
1962 setCursor(Qt::SizeAllCursor);
1966 void DrawingView::mouseMoveEvent(QMouseEvent * event)
1968 // It seems that wheelEvent() triggers this for some reason...
1969 if (scrollWheelSeen)
1971 scrollWheelSeen = false;
1975 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1976 Global::selection.setBottomRight(QPointF(point.x, point.y));
1977 // Only needs to be done here, as mouse down is always preceded by movement
1978 Global::snapPointIsValid = false;
1979 hoveringIntersection = false;
1980 oldScrollPoint = Vector(event->x(), event->y());
1983 if ((event->buttons() & Qt::MiddleButton) || scrollDrag)
1985 point = Vector(event->x(), event->y());
1986 // Since we're using Qt coords for scrolling, we have to adjust them
1987 // here to conform to Cartesian coords, since the origin is using
1989 Vector delta(oldPoint, point);
1990 delta /= Global::zoom;
1992 Global::origin -= delta;
1994 // UpdateGridBackground();
2000 // If we're doing a selection rect, see if any objects are engulfed by it
2001 // (implies left mouse button held down)
2002 if (Global::selectionInProgress)
2004 CheckObjectBounds();
2006 // Make sure previously selected objects stay selected (CTRL held)
2007 for(VPVectorIter i=select.begin(); i!=select.end(); i++)
2009 // Make sure *not* to select items on hidden layers
2010 if (Global::layerHidden[((Object *)(*i))->layer] == false)
2011 ((Object *)(*i))->selected = true;
2018 // Do object hit testing...
2019 bool needUpdate = HitTestObjects(point);
2020 VPVector hover2 = GetHovered();
2025 printf("mouseMoveEvent:: numHovered=%li, hover2.size()=%li\n", numHovered, hover2.size());
2027 if (hover2.size() > 0)
2028 printf(" (hover2[0]=$%llX, type=%s)\n", hover2[0], objName[((Object *)hover2[0])->type]);
2033 // Check for multi-hover...
2036 //need to check for case where hover is over 2 circles and a 3rd's center...
2037 Object * obj1 = (Object *)hover2[0], * obj2 = (Object *)hover2[1];
2039 Geometry::Intersects(obj1, obj2);
2040 int numIntersecting = Global::numIntersectParams;
2041 double t = Global::intersectParam[0];
2042 double u = Global::intersectParam[1];
2044 if (numIntersecting > 0)
2046 Vector v1 = Geometry::GetPointForParameter(obj1, t);
2047 Vector v2 = Geometry::GetPointForParameter(obj2, u);
2048 QString text = tr("Intersection t=%1 (%3, %4), u=%2 (%5, %6)");
2049 informativeText = text.arg(t).arg(u).arg(v1.x).arg(v1.y).arg(v2.x).arg(v2.y);
2051 hoveringIntersection = true;
2052 intersectionPoint = v1;
2055 numIntersecting = Global::numIntersectPoints;
2057 if (numIntersecting > 0)
2059 Vector v1 = Global::intersectPoint[0];
2061 if (numIntersecting == 2)
2063 Vector v2 = Global::intersectPoint[1];
2065 if (Vector::Magnitude(v2, point) < Vector::Magnitude(v1, point))
2069 QString text = tr("Intersection <%1, %2>");
2070 informativeText = text.arg(v1.x).arg(v1.y);
2071 hoveringIntersection = true;
2072 intersectionPoint = v1;
2075 else if (numHovered == 1)
2077 Object * obj = (Object *)hover2[0];
2079 if (obj->type == OTLine)
2082 Not sure that this is the best way to handle this, but it works(TM)...
2083 Except when lines are overlapping, then it doesn't work... !!! FIX !!!
2085 Point midpoint = Geometry::Midpoint((Line *)obj);
2086 Vector v1 = Vector::Magnitude(midpoint, point);
2088 if ((v1.Magnitude() * Global::zoom) < 8.0)
2090 QString text = tr("Midpoint <%1, %2>");
2091 informativeText = text.arg(midpoint.x).arg(midpoint.y);
2092 hoverPointValid = true;
2093 hoverPoint = midpoint;
2099 // Handle object movement (left button down & over an object)
2100 if ((event->buttons() & Qt::LeftButton) && draggingObject && !Global::tool)
2102 if (hoveringIntersection)
2103 point = intersectionPoint;
2104 else if (hoverPointValid)
2106 else if (Global::snapToGrid)
2107 point = SnapPointToGrid(point);
2109 HandleObjectMovement(point);
2115 // Do tool handling, if any are active...
2118 if (hoveringIntersection)
2119 point = intersectionPoint;
2120 else if (hoverPointValid)
2122 else if (Global::snapToGrid)
2125 point = SnapPointToAngle(point);
2127 point = SnapPointToGrid(point);
2130 ToolHandler(ToolMouseMove, point);
2133 // This is used to draw the tool crosshair...
2136 if (needUpdate || Global::tool)
2140 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
2142 if (event->button() == Qt::LeftButton)
2144 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
2145 //could set it up to use the document's update function (assumes that all object
2146 //updates are being reported correctly:
2147 // if (document.NeedsUpdate())
2148 // Do an update if collided with at least *one* object in the document
2154 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
2155 ToolHandler(ToolMouseUp, point);
2159 // if (Global::selectionInProgress)
2160 Global::selectionInProgress = false;
2162 informativeText.clear();
2163 // Should we be doing this automagically? Hmm...
2164 // Clear our vectors
2168 // 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)
2169 select = GetSelection();
2171 draggingObject = false;
2173 else if (event->button() == Qt::MiddleButton)
2177 if (Global::penStamp)
2178 setCursor(curMarker);
2179 else if (Global::penDropper)
2180 setCursor(curDropper);
2182 setCursor(Qt::ArrowCursor);
2184 // Need to convert this, since it's in Qt coordinates (for wheelEvent)
2185 oldPoint = Painter::QtToCartesianCoords(oldPoint);
2189 void DrawingView::wheelEvent(QWheelEvent * event)
2191 double zoomFactor = 1.20;
2192 scrollWheelSeen = true;
2194 if (event->angleDelta().y() < 0)
2196 if (Global::zoom > 400.0)
2199 Global::zoom *= zoomFactor;
2203 if (Global::zoom < 0.125)
2206 Global::zoom /= zoomFactor;
2209 Point np = Painter::QtToCartesianCoords(oldScrollPoint);
2210 Global::origin += (oldPoint - np);
2212 emit(NeedZoomUpdate());
2215 void DrawingView::keyPressEvent(QKeyEvent * event)
2217 bool oldShift = shiftDown;
2218 bool oldCtrl = ctrlDown;
2219 bool oldAlt = altDown;
2221 if (event->key() == Qt::Key_Shift)
2223 else if (event->key() == Qt::Key_Control)
2225 else if (event->key() == Qt::Key_Alt)
2228 // If there's a change in any of the modifier key states, pass it on to
2229 // the current tool's handler
2230 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
2233 ToolHandler(ToolKeyDown, Point(0, 0));
2238 if (oldAlt != altDown)
2241 setCursor(Qt::SizeAllCursor);
2242 oldPoint = oldScrollPoint;
2245 if (select.size() > 0)
2247 if (event->key() == Qt::Key_Up)
2249 TranslateObjects(select, Point(0, +1.0));
2252 else if (event->key() == Qt::Key_Down)
2254 TranslateObjects(select, Point(0, -1.0));
2257 else if (event->key() == Qt::Key_Right)
2259 TranslateObjects(select, Point(+1.0, 0));
2262 else if (event->key() == Qt::Key_Left)
2264 TranslateObjects(select, Point(-1.0, 0));
2270 void DrawingView::keyReleaseEvent(QKeyEvent * event)
2272 bool oldShift = shiftDown;
2273 bool oldCtrl = ctrlDown;
2274 bool oldAlt = altDown;
2276 if (event->key() == Qt::Key_Shift)
2278 else if (event->key() == Qt::Key_Control)
2280 else if (event->key() == Qt::Key_Alt)
2283 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
2286 ToolHandler(ToolKeyUp, Point(0, 0));
2291 if (oldAlt != altDown)
2295 if (Global::penStamp)
2296 setCursor(curMarker);
2297 else if (Global::penDropper)
2298 setCursor(curDropper);
2300 setCursor(Qt::ArrowCursor);
2305 // This looks strange, but it's really quite simple: We want a point that's
2306 // more than half-way to the next grid point to snap there while conversely we
2307 // want a point that's less than half-way to to the next grid point then snap
2308 // to the one before it. So we add half of the grid spacing to the point, then
2309 // divide by it so that we can remove the fractional part, then multiply it
2310 // back to get back to the correct answer.
2312 Point DrawingView::SnapPointToGrid(Point point)
2314 point += Global::gridSpacing / 2.0; // *This* adds to Z!!!
2315 point /= Global::gridSpacing;
2316 point.x = floor(point.x);//need to fix this for negative numbers...
2317 point.y = floor(point.y);
2318 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
2319 point *= Global::gridSpacing;
2323 Point DrawingView::SnapPointToAngle(Point point)
2325 // Snap to a single digit angle (using toolpoint #1 as the center)
2326 double angle = Vector::Angle(toolPoint[0], point);
2327 double length = Vector::Magnitude(toolPoint[0], point);
2329 // Convert from radians to degrees
2330 double degAngle = angle * RADIANS_TO_DEGREES;
2331 double snapAngle = (double)((int)(degAngle + 0.5));
2334 v.SetAngleAndLength(snapAngle * DEGREES_TO_RADIANS, length);
2335 point = toolPoint[0] + v;
2340 Rect DrawingView::GetObjectExtents(Object * obj)
2342 // Default to empty rect, if object checks below fail for some reason
2350 rect = Rect(obj->p[0], obj->p[1]);
2356 rect = Rect(obj->p[0], obj->p[0]);
2357 rect.Expand(obj->radius[0]);
2363 Arc * a = (Arc *)obj;
2365 double start = a->angle[0];
2366 double end = start + a->angle[1];
2367 rect = Rect(Point(cos(start), sin(start)), Point(cos(end), sin(end)));
2369 // If the end of the arc is before the beginning, add 360 degrees to it
2373 // Adjust the bounds depending on which axes are crossed
2374 if ((start < QTR_TAU) && (end > QTR_TAU))
2377 if ((start < HALF_TAU) && (end > HALF_TAU))
2380 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
2383 if ((start < TAU) && (end > TAU))
2386 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
2389 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
2392 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
2395 rect *= a->radius[0];
2396 rect.Translate(a->p[0]);
2402 Text * t = (Text *)obj;
2403 rect = Rect(t->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
2409 Container * c = (Container *)obj;
2410 VPVectorIter i = c->objects.begin();
2411 rect = GetObjectExtents((Object *)*i);
2414 for(; i!=c->objects.end(); i++)
2415 rect |= GetObjectExtents((Object *)*i);
2425 void DrawingView::CheckObjectBounds(void)
2429 for(i=document.objects.begin(); i!=document.objects.end(); i++)
2431 Object * obj = (Object *)(*i);
2432 obj->selected = false;
2439 Line * l = (Line *)obj;
2441 if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
2449 Circle * c = (Circle *)obj;
2451 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]))
2459 Arc * a = (Arc *)obj;
2461 double start = a->angle[0];
2462 double end = start + a->angle[1];
2463 QPointF p1(cos(start), sin(start));
2464 QPointF p2(cos(end), sin(end));
2465 QRectF bounds(p1, p2);
2468 // Swap X/Y coordinates if they're backwards...
2469 if (bounds.left() > bounds.right())
2471 double temp = bounds.left();
2472 bounds.setLeft(bounds.right());
2473 bounds.setRight(temp);
2476 if (bounds.bottom() > bounds.top())
2478 double temp = bounds.bottom();
2479 bounds.setBottom(bounds.top());
2480 bounds.setTop(temp);
2483 // Doesn't work as advertised! For shame!
2484 bounds = bounds.normalized();
2487 // If the end of the arc is before the beginning, add 360 degrees
2492 // Adjust the bounds depending on which axes are crossed
2493 if ((start < QTR_TAU) && (end > QTR_TAU))
2496 if ((start < HALF_TAU) && (end > HALF_TAU))
2497 bounds.setLeft(-1.0);
2499 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
2500 bounds.setBottom(-1.0);
2502 if ((start < TAU) && (end > TAU))
2503 bounds.setRight(1.0);
2505 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
2508 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
2509 bounds.setLeft(-1.0);
2511 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
2512 bounds.setBottom(-1.0);
2514 bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
2515 bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
2516 bounds.translate(a->p[0].x, a->p[0].y);
2518 if (Global::selection.contains(bounds))
2526 Text * t = (Text *)obj;
2527 Rect r(obj->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
2529 if (Global::selection.contains(r.l, r.t) && Global::selection.contains(r.r, r.b))
2537 Container * c = (Container *)obj;
2539 if (Global::selection.contains(c->p[0].x, c->p[0].y) && Global::selection.contains(c->p[1].x, c->p[1].y))
2551 bool DrawingView::HitTestObjects(Point point)
2555 bool needUpdate = false;
2556 hoverPointValid = false;
2558 for(i=document.objects.begin(); i!=document.objects.end(); i++)
2560 Object * obj = (Object *)(*i);
2562 // If we're seeing the object we're dragging, skip it
2563 if (draggingObject && (obj == dragged))
2566 if (HitTest(obj, point) == true)
2572 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
2573 emit ObjectHovered(obj);
2580 bool DrawingView::HitTest(Object * obj, Point point)
2582 bool needUpdate = false;
2588 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
2589 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
2590 Vector lineSegment = obj->p[1] - obj->p[0];
2591 Vector v1 = point - obj->p[0];
2592 Vector v2 = point - obj->p[1];
2593 double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
2597 distance = v1.Magnitude();
2599 distance = v2.Magnitude();
2601 // distance = ?Det?(ls, v1) / |ls|
2602 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
2603 / lineSegment.Magnitude());
2605 if ((v1.Magnitude() * Global::zoom) < 8.0)
2607 obj->hitPoint[0] = true;
2608 hoverPoint = obj->p[0];
2609 hoverPointValid = true;
2611 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2613 obj->hitPoint[1] = true;
2614 hoverPoint = obj->p[1];
2615 hoverPointValid = true;
2617 else if ((distance * Global::zoom) < 5.0)
2618 obj->hitObject = true;
2620 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
2622 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
2630 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
2631 obj->hitPoint[0] = obj->hitObject = false;
2632 double length = Vector::Magnitude(obj->p[0], point);
2634 if ((length * Global::zoom) < 8.0)
2636 obj->hitPoint[0] = true;
2637 hoverPoint = obj->p[0];
2638 hoverPointValid = true;
2640 else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
2641 obj->hitObject = true;
2643 obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
2645 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
2653 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
2654 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
2655 double length = Vector::Magnitude(obj->p[0], point);
2656 double angle = Vector::Angle(obj->p[0], point);
2658 // Make sure we get the angle in the correct spot
2659 if (angle < obj->angle[0])
2662 // Get the span that we're pointing at...
2663 double span = angle - obj->angle[0];
2665 // N.B.: Still need to hit test the arc start & arc span handles...
2666 double spanAngle = obj->angle[0] + obj->angle[1];
2667 Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
2668 Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
2669 double length2 = Vector::Magnitude(point, handle1);
2670 double length3 = Vector::Magnitude(point, handle2);
2672 if ((length * Global::zoom) < 8.0)
2674 obj->hitPoint[0] = true;
2675 hoverPoint = obj->p[0];
2676 hoverPointValid = true;
2678 else if ((length2 * Global::zoom) < 8.0)
2680 obj->hitPoint[1] = true;
2681 hoverPoint = handle1;
2682 hoverPointValid = true;
2684 else if ((length3 * Global::zoom) < 8.0)
2686 obj->hitPoint[2] = true;
2687 hoverPoint = handle2;
2688 hoverPointValid = true;
2690 else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
2691 obj->hitObject = true;
2693 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
2695 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
2703 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHP3 = obj->hitPoint[3], oldHP4 = obj->hitPoint[4], oldHO = obj->hitObject;
2704 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitPoint[3] = obj->hitPoint[4] = obj->hitObject = false;
2706 Dimension * d = (Dimension *)obj;
2708 Vector orthogonal = Vector::Normal(d->lp[0], d->lp[1]);
2709 // Get our line parallel to our points
2710 float scaledThickness = Global::scale * obj->thickness;
2712 Point p1 = d->lp[0] + (orthogonal * (d->offset + (10.0 * scaledThickness)));
2713 Point p2 = d->lp[1] + (orthogonal * (d->offset + (10.0 * scaledThickness)));
2715 Point p1 = d->lp[0] + (orthogonal * (10.0 + d->offset) * scaledThickness);
2716 Point p2 = d->lp[1] + (orthogonal * (10.0 + d->offset) * scaledThickness);
2718 Point p3(p1, point);
2720 Vector v1(d->p[0], point);
2721 Vector v2(d->p[1], point);
2722 Vector lineSegment(p1, p2);
2723 double t = Geometry::ParameterOfLineAndPoint(p1, p2, point);
2725 Point midpoint = (p1 + p2) / 2.0;
2726 Point hFSPoint = Point(midpoint, point);
2727 Point hCS1Point = Point((p1 + midpoint) / 2.0, point);
2728 Point hCS2Point = Point((midpoint + p2) / 2.0, point);
2731 distance = v1.Magnitude();
2733 distance = v2.Magnitude();
2735 // distance = ?Det?(ls, v1) / |ls|
2736 distance = fabs((lineSegment.x * p3.y - p3.x * lineSegment.y)
2737 / lineSegment.Magnitude());
2739 if ((v1.Magnitude() * Global::zoom) < 8.0)
2740 obj->hitPoint[0] = true;
2741 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2742 obj->hitPoint[1] = true;
2743 else if ((distance * Global::zoom) < 5.0)
2744 obj->hitObject = true;
2746 if ((hFSPoint.Magnitude() * Global::zoom) < 8.0)
2747 obj->hitPoint[2] = true;
2748 else if ((hCS1Point.Magnitude() * Global::zoom) < 8.0)
2749 obj->hitPoint[3] = true;
2750 else if ((hCS2Point.Magnitude() * Global::zoom) < 8.0)
2751 obj->hitPoint[4] = true;
2753 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitPoint[3] || obj->hitPoint[4] || obj->hitObject ? true : false);
2755 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHP3 != obj->hitPoint[3]) || (oldHP4 != obj->hitPoint[4]) || (oldHO != obj->hitObject))
2763 Text * t = (Text *)obj;
2764 bool oldHO = obj->hitObject;
2765 obj->hitObject = false;
2767 Rect r(obj->p[0], Point(obj->p[0].x + t->extents.Width(), obj->p[0].y - t->extents.Height()));
2768 //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);
2770 if (r.Contains(point))
2771 obj->hitObject = true;
2773 obj->hovered = (obj->hitObject ? true : false);
2775 if (oldHO != obj->hitObject)
2783 // Containers must be recursively tested... Or do they???
2785 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.
2787 // bool oldHitObj = c->hitObject, oldHovered = c->hovered;
2788 // Object * oldClicked = c->clicked;
2790 still need to compare old state to new state, and set things up based upon that...
2791 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);
2792 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.
2794 Container * c = (Container *)obj;
2795 c->hitObject = false;
2799 VPVector flat = Flatten(c);
2801 //printf("HitTest::OTContainer (size=%li)\n", flat.size());
2802 for(VPVectorIter i=flat.begin(); i!=flat.end(); i++)
2804 Object * cObj = (Object *)(*i);
2806 // Skip the flattened containers (if any)...
2807 if (cObj->type == OTContainer)
2810 // We do it this way instead of needUpdate = HitTest() because we
2811 // are checking more than one object, and that way of doing will
2812 // not return consistent results.
2813 if (HitTest(cObj, point) == true)
2815 //printf("HitTest::OTContainer, subobj ($%llX) hit!\n", cObj);
2817 // c->hitObject = true;
2818 // c->clicked = cObj;
2819 // c->hovered = true;
2822 // Same reasons for doing it this way here apply.
2823 if (cObj->hitObject == true)
2825 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2826 c->hitObject = true;
2830 if (cObj->hitPoint[0] == true)
2832 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2833 c->hitPoint[0] = true;
2837 if (cObj->hitPoint[1] == true)
2839 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2840 c->hitPoint[1] = true;
2844 if (cObj->hitPoint[2] == true)
2846 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2847 c->hitPoint[2] = true;
2851 if (cObj->hovered == true)
2852 c->hovered = true;//*/
2865 bool DrawingView::HandleObjectClicked(void)
2867 if (dragged->type == OTDimension)
2869 Dimension * d = (Dimension *)dragged;
2873 // Hit the "flip sides" switch, so flip 'em
2874 Point temp = d->p[0];
2879 else if (d->hitPoint[3])
2881 // There are three cases here: aligned, horizontal, & vertical.
2882 // Aligned and horizontal do the same thing, vertical goes back to
2884 if (d->subtype == DTLinearVert)
2885 d->subtype = DTLinear;
2887 d->subtype = DTLinearVert;
2891 else if (d->hitPoint[4])
2893 // There are three cases here: aligned, horizontal, & vertical.
2894 // Aligned and vertical do the same thing, horizontal goes back to
2896 if (d->subtype == DTLinearHorz)
2897 d->subtype = DTLinear;
2899 d->subtype = DTLinearHorz;
2908 void DrawingView::HandleObjectMovement(Point point)
2910 Point delta = point - oldPoint;
2911 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
2912 // Object * obj = (Object *)hover[0];
2913 Object * obj = dragged;
2914 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
2915 //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"));
2920 if (obj->hitPoint[0])
2922 if (Global::fixedLength)
2924 Vector line = point - obj->p[1];
2925 Vector unit = line.Unit();
2926 point = obj->p[1] + (unit * obj->length);
2931 else if (obj->hitPoint[1])
2933 if (Global::fixedLength)
2935 Vector line = point - obj->p[0];
2936 Vector unit = line.Unit();
2937 point = obj->p[0] + (unit * obj->length);
2942 else if (obj->hitObject)
2951 if (obj->hitPoint[0])
2953 else if (obj->hitObject)
2955 double oldRadius = obj->length;
2956 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
2958 QString text = QObject::tr("Radius: %1\nScale: %2%");
2959 informativeText = text.arg(obj->radius[0], 0, 'd', 4).arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0);
2965 if (obj->hitPoint[0])
2967 else if (obj->hitPoint[1])
2969 // Change the Arc's span (handle #1)
2972 double angle = Vector::Angle(obj->p[0], point);
2973 double delta = angle - obj->angle[0];
2978 obj->angle[1] -= delta;
2979 obj->angle[0] = angle;
2981 if (obj->angle[1] < 0)
2982 obj->angle[1] += TAU;
2984 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
2985 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);
2989 double angle = Vector::Angle(obj->p[0], point);
2990 obj->angle[0] = angle;
2991 QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
2992 informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 4);
2994 else if (obj->hitPoint[2])
2996 // Change the Arc's span (handle #2)
2999 double angle = Vector::Angle(obj->p[0], point);
3000 obj->angle[1] = angle - obj->angle[0];
3002 if (obj->angle[1] < 0)
3003 obj->angle[1] += TAU;
3005 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
3006 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);
3010 double angle = Vector::Angle(obj->p[0], point);
3011 obj->angle[0] = angle - obj->angle[1];
3013 if (obj->angle[0] < 0)
3014 obj->angle[0] += TAU;
3016 QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
3017 informativeText = text.arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 4);
3019 else if (obj->hitObject)
3026 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
3027 QString text = QObject::tr("Radius: %1");
3028 informativeText = text.arg(obj->radius[0], 0, 'd', 4);
3034 if (obj->hitPoint[0])
3036 else if (obj->hitPoint[1])
3038 else if (obj->hitObject)
3040 // Move measurement lines in/out
3043 Dimension * d = (Dimension *)obj;
3044 double dist = Geometry::DistanceToLineFromPoint(d->lp[0], d->lp[1], point);
3045 float scaledThickness = Global::scale * obj->thickness;
3046 // Looks like offset is 0 to +MAX, but line is at 10.0. So
3047 // anything less than 10.0 should set the offset to 0.
3050 if (dist > (10.0 * scaledThickness))
3051 d->offset = dist - (10.0 * scaledThickness);
3069 // This is shitty, but works for now until I can code up something
3072 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.
3074 // TranslateObject(obj, delta);
3075 TranslateContainer((Container *)obj, point, delta);
3083 void DrawingView::AddDimensionTo(void * o)
3085 Object * obj = (Object *)o;
3090 document.Add(new Dimension(obj->p[0], obj->p[1]));