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"
46 #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), document(true),
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 Global::gridSpacing = 12.0; // In base units (inch is default)
65 Line * line = new Line(Vector(5, 5), Vector(50, 40), 2.0, 0xFF7F00, LSDash);
67 document.Add(new Line(Vector(50, 40), Vector(10, 83)));
68 document.Add(new Line(Vector(10, 83), Vector(17, 2)));
69 document.Add(new Circle(Vector(100, 100), 36));
70 document.Add(new Circle(Vector(50, 150), 49));
71 document.Add(new Arc(Vector(300, 300), 32, TAU / 8.0, TAU * 0.65)),
72 document.Add(new Arc(Vector(200, 200), 60, TAU / 4.0, TAU * 0.75));
73 document.Add(new Text(Vector(10, 83), "Here is some awesome text!"));
78 Here we set the grid size in pixels--12 in this case. Initially, we have our
79 zoom set to make this represent 12 inches at a zoom factor of 25%. (This is
80 arbitrary.) So, to be able to decouple the grid size from the zoom, we need
81 to be able to set the size of the background grid (which we do here at an
82 arbitrary 12 pixels) to anything we want (within reason, of course :-).
84 The drawing enforces the grid spacing through the drawing->gridSpacing variable.
86 drawing->gridSpacing = 12.0 / Global::zoom;
88 Global::zoom is the zoom factor for the drawing, and all mouse clicks are
89 translated to Cartesian coordinates through this. (Initially, Global::zoom is
90 set to 1.0. SCREEN_ZOOM is set to 1.0/4.0.)
92 Really, the 100% zoom level can be set at *any* zoom level, it's more of a
93 convenience function than any measure of absolutes. Doing things that way we
94 could rid ourselves of the whole SCREEN_ZOOM parameter and all the attendant
95 shittiness that comes with it.
97 However, it seems that SCREEN_ZOOM is used to make text and arrow sizes show up
98 a certain way, which means we should probably create something else in those
99 objects to take its place--like some kind of scale factor. This would seem to
100 imply that certain point sizes actually *do* tie things like fonts to absolute
101 sizes on the screen, but not necessarily because you could have an inch scale
102 with text that is quite small relative to other objects on the screen, which
103 currently you have to zoom in to see (and which blows up the text). Point sizes
104 in an application like this are a bit meaningless; even though an inch is an
105 inch regardless of the zoom level a piece of text can be larger or smaller than
106 this. Maybe this is the case for having a base unit and basing point sizes off
109 Here's what's been figured out. Global::zoom is simply the ratio of pixels to
110 base units. What that means is that if you have a 12px grid with a 6" grid size
111 (& base unit of "inches"), Global::zoom becomes 12px / 6" = 2.0 px/in.
113 Dimensions now have a "size" parameter to set their absolute size in relation
114 to the base unit. ATM, the arrows are drawn in pixels, but also scaled by
115 Global::zoom *and* size. Same with the dimension text; it's drawn at 10pt and
116 scaled the same way as the arrowheads.
118 Need a way to scale line widths as well. :-/ Shouldn't be too difficult, just
119 need a thickness parameter similar to the "size" param for dimensions. (And now
123 gridPixels = 12; //tmp
124 SetGridSize(12.0); // This is in pixels
128 void DrawingView::DrawBackground(Painter * painter)
130 Point ul = Painter::QtToCartesianCoords(Vector(0, 0));
131 Point br = Painter::QtToCartesianCoords(Vector(Global::screenSize.x, Global::screenSize.y));
133 painter->SetBrush(0xF0F0F0);
134 painter->SetPen(0xF0F0F0, 1, 1);
135 painter->DrawRect(QRectF(QPointF(ul.x, ul.y), QPointF(br.x, br.y)));
137 double spacing = Global::gridSpacing;
142 double leftx = floor(ul.x / spacing) * spacing;
143 double bottomy = floor(br.y / spacing) * spacing;
145 double w = (br.x - ul.x) + Global::gridSpacing + 1.0;
146 double h = (ul.y - br.y) + Global::gridSpacing + 1.0;
148 Vector start(leftx, bottomy), size(w, h);
150 if (Global::gridSpacing <= 0.015625)
151 DrawSubGrid(painter, 0xFFD2D2, 0.015625, start, size);
153 if (Global::gridSpacing <= 0.03125)
154 DrawSubGrid(painter, 0xFFD2D2, 0.03125, start, size);
156 if (Global::gridSpacing <= 0.0625)
157 DrawSubGrid(painter, 0xB8ECFF, 0.0625, start, size);
159 if (Global::gridSpacing <= 0.125)
160 DrawSubGrid(painter, 0xB8ECFF, 0.125, start, size);
162 if (Global::gridSpacing <= 0.25)
163 DrawSubGrid(painter, 0xDBDBFF, 0.25, start, size);
165 if (Global::gridSpacing <= 0.5)
166 DrawSubGrid(painter, 0xDBDBFF, 0.5, start, size);
168 painter->SetPen(QPen(QColor(0xD2, 0xD2, 0xFF), 2.0, Qt::SolidLine));
170 for(double i=0; i<=w; i+=spacing)
171 painter->DrawVLine(leftx + i);
173 for(double i=0; i<=h; i+=spacing)
174 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 void DrawingView::SetGridSize(uint32_t size)
194 if (size == gridPixels)
201 // Recreate the background bitmap
203 QPainter pmp(&gridBackground);
204 pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
205 pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
207 for(int i=0; i<(BACKGROUND_MAX_SIZE-1); i+=gridPixels)
209 pmp.drawLine(i, 0, i, BACKGROUND_MAX_SIZE - 1);
210 pmp.drawLine(0, i, BACKGROUND_MAX_SIZE - 1, i);
215 // Set up new BG brush & zoom level (pixels per base unit)
216 // This shouldn't be done here, because it fucks up the scrollwheel zooming...
217 // Global::zoom = gridPixels / Global::gridSpacing;
218 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);
304 void DrawingView::SetGridSize(double size)
308 if (size == gridPixelsF)
315 // Recreate the background bitmap
317 QPainter pmp(&gridBackground);
318 pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
319 pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
321 for(double i=0; i<(BACKGROUND_MAX_SIZE-1); i+=gridPixelsF)
323 pmp.drawLine(i, 0, i, (double)(BACKGROUND_MAX_SIZE - 1));
324 pmp.drawLine(0, i, (double)(BACKGROUND_MAX_SIZE - 1), i);
329 // Set up new BG brush & zoom level (pixels per base unit)
330 // This shouldn't be done here, because it fucks up the scrollwheel zooming...
331 // Global::zoom = gridPixels / Global::gridSpacing;
332 UpdateGridBackgroundF();
337 void DrawingView::UpdateGridBackgroundF(void)
340 // Transform the origin to Qt coordinates
341 Vector pixmapOrigin = Painter::CartesianToQtCoords(Vector());
342 int x = 0;// (int)pixmapOrigin.x;
343 int y = 0;// (int)pixmapOrigin.y;
344 // Use mod arithmetic to grab the correct swatch of background
349 x = (gridPixels - (x % gridPixels)) % gridPixels;
354 y = (gridPixels - (y % gridPixels)) % gridPixels;*/
356 // Here we grab a section of the bigger pixmap, so that the background
357 // *looks* like it's scrolling...
358 QPixmap pm = gridBackground.copy(x, y, gridPixelsF, gridPixelsF);
359 QPalette pal = palette();
360 pal.setBrush(backgroundRole(), QBrush(pm));
361 setAutoFillBackground(true);
368 // Basically, we just make a single pass through the Container. If the layer #
369 // is less than the layer # being deleted, then do nothing. If the layer # is
370 // equal to the layer # being deleted, then delete the object. If the layer #
371 // is greater than the layer # being deleted, then set the layer # to its layer
374 void DrawingView::DeleteCurrentLayer(int layer)
376 //printf("DrawingView::DeleteCurrentLayer(): currentLayer = %i\n", layer);
377 VPVectorIter i = document.objects.begin();
379 while (i != document.objects.end())
381 Object * obj = (Object *)(*i);
383 if (obj->layer < layer)
385 else if (obj->layer == layer)
387 document.objects.erase(i);
397 // We've just done a destructive action, so update the screen!
402 void DrawingView::HandleLayerToggle(void)
404 // A layer's visibility was toggled, so update the screen...
410 // A layer was moved up or down in the layer list, so we have to swap the
411 // document's object's layer numbers in the layers that were swapped.
413 void DrawingView::HandleLayerSwap(int layer1, int layer2)
415 // !!! FIX !!! This doesn't properly handle container contents...
416 //printf("DrawingView: Swapping layers %i and %i.\n", layer1, layer2);
417 for(VPVectorIter i=document.objects.begin(); i!=document.objects.end(); i++)
419 Object * obj = (Object *)(*i);
421 if (obj->layer == layer1)
423 else if (obj->layer == layer2)
429 void DrawingView::HandlePenWidth(float width)
431 for(VPVectorIter i=select.begin(); i!=select.end(); i++)
433 Object * obj = (Object *)(*i);
434 obj->thickness = width;
439 void DrawingView::HandlePenStyle(int style)
441 for(VPVectorIter i=select.begin(); i!=select.end(); i++)
443 Object * obj = (Object *)(*i);
449 void DrawingView::HandlePenColor(uint32_t color)
451 for(VPVectorIter i=select.begin(); i!=select.end(); i++)
453 Object * obj = (Object *)(*i);
459 void DrawingView::HandlePenStamp(void)
461 VPVector flat = Flatten(select);
463 for(VPVectorIter i=flat.begin(); i!=flat.end(); i++)
465 Object * obj = (Object *)(*i);
467 if (obj->type != OTText)
468 obj->thickness = Global::penWidth;
470 obj->style = Global::penStyle;
471 obj->color = Global::penColor;
478 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
480 // This is undoing the transform, e.g. going from client coords to local
481 // coords. In essence, the height - y is height + (y * -1), the (y * -1)
482 // term doing the conversion of the y-axis from increasing bottom to top.
483 return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
487 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
489 // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
490 // No voodoo here, it's just grouped wrong to see it. It should be:
491 // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive [why? we use -offsetX after all]
492 return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
496 void DrawingView::focusOutEvent(QFocusEvent * /*event*/)
498 // printf("DrawingView::focusOutEvent()...\n");
499 // Make sure all modkeys being held are marked as released when the app
500 // loses focus (N.B.: This only works because the app sets the focus policy
501 // of this object to something other than Qt::NoFocus)
502 shiftDown = ctrlDown = altDown = false;
504 setCursor(Qt::ArrowCursor);
508 void DrawingView::paintEvent(QPaintEvent * /*event*/)
510 QPainter qtPainter(this);
511 Painter painter(&qtPainter);
514 qtPainter.setRenderHint(QPainter::Antialiasing);
516 Global::viewportHeight = size().height();
518 DrawBackground(&painter);
520 // Draw coordinate axes
521 painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
522 painter.DrawLine(0, -16384, 0, 16384);
523 painter.DrawLine(-16384, 0, 16384, 0);
525 // Do object rendering...
526 for(int i=0; i<Global::numLayers; i++)
528 if (Global::layerHidden[i] == false)
529 RenderObjects(&painter, document.objects, i);
532 // Do tool rendering, if any...
535 painter.SetPen(QPen(QColor(200, 100, 0, 255), 1.0, Qt::DashLine));
536 painter.DrawCrosshair(oldPoint);
540 // Do selection rectangle rendering, if any
541 if (Global::selectionInProgress)
543 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
544 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
545 painter.DrawRect(Global::selection);
548 if (hoveringIntersection)
549 painter.DrawHandle(intersectionPoint);
552 painter.DrawHandle(hoverPoint);
554 if (!informativeText.isEmpty())
555 painter.DrawInformativeText(informativeText);
560 // Renders objects in the passed in vector
563 N.B.: Since we have "hoverPointValid" drawing regular object handles above,
564 we can probably do away with a lot of them that are being done down below.
566 [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...]
568 void DrawingView::RenderObjects(Painter * painter, VPVector & v, int layer, bool ignoreLayer/*= false*/)
570 for(VPVectorIter i=v.begin(); i!=v.end(); i++)
572 Object * obj = (Object *)(*i);
573 float scaledThickness = Global::scale * obj->thickness;
575 // If the object isn't on the current layer being drawn, skip it
576 if (!ignoreLayer && (obj->layer != layer))
579 if ((Global::tool == TTRotate) && ctrlDown && obj->selected)
581 painter->SetPen(0x00FF00, 2.0, LSSolid);
585 painter->SetPen(obj->color, Global::zoom * scaledThickness, obj->style);
586 painter->SetBrush(obj->color);
588 if (obj->selected || obj->hitObject)
589 painter->SetPen(0xFF0000, Global::zoom * scaledThickness, LSDash);
595 painter->DrawLine(obj->p[0], obj->p[1]);
597 if (obj->hitPoint[0])
598 painter->DrawHandle(obj->p[0]);
600 if (obj->hitPoint[1])
601 painter->DrawHandle(obj->p[1]);
604 painter->DrawSmallHandle(Geometry::Midpoint((Line *)obj));
609 painter->SetBrush(QBrush(Qt::NoBrush));
610 painter->DrawEllipse(obj->p[0], obj->radius[0], obj->radius[0]);
612 if (obj->hitPoint[0])
613 painter->DrawHandle(obj->p[0]);
618 painter->DrawArc(obj->p[0], obj->radius[0], obj->angle[0], obj->angle[1]);
620 if (obj->hitPoint[0])
621 painter->DrawHandle(obj->p[0]);
623 if (obj->hitPoint[1])
624 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]));
626 if (obj->hitPoint[2])
627 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0] + obj->angle[1]), sin(obj->angle[0] + obj->angle[1])) * obj->radius[0]));
633 Dimension * d = (Dimension *)obj;
635 Vector v(d->p[0], d->p[1]);
636 double angle = v.Angle();
637 Vector unit = v.Unit();
638 d->lp[0] = d->p[0], d->lp[1] = d->p[1];
640 double x1, y1, length;
642 if (d->subtype == DTLinearVert)
644 if ((angle < 0) || (angle > HALF_TAU))
646 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
647 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
648 ortho = Vector(1.0, 0);
649 angle = THREE_QTR_TAU;
653 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
654 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
655 ortho = Vector(-1.0, 0);
659 d->lp[0].x = d->lp[1].x = x1;
660 length = fabs(d->p[0].y - d->p[1].y);
662 else if (d->subtype == DTLinearHorz)
664 if ((angle < QTR_TAU) || (angle > THREE_QTR_TAU))
666 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
667 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
668 ortho = Vector(0, 1.0);
673 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
674 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
675 ortho = Vector(0, -1.0);
679 d->lp[0].y = d->lp[1].y = y1;
680 length = fabs(d->p[0].x - d->p[1].x);
682 else if (d->subtype == DTLinear)
684 angle = Vector(d->lp[0], d->lp[1]).Angle();
685 ortho = Vector::Normal(d->lp[0], d->lp[1]);
686 length = v.Magnitude();
689 unit = Vector(d->lp[0], d->lp[1]).Unit();
691 Point p1 = d->lp[0] + (ortho * (d->offset + (10.0 * scaledThickness)));
692 Point p2 = d->lp[1] + (ortho * (d->offset + (10.0 * scaledThickness)));
693 Point p3 = d->lp[0] + (ortho * (d->offset + (16.0 * scaledThickness)));
694 Point p4 = d->lp[1] + (ortho * (d->offset + (16.0 * scaledThickness)));
695 Point p5 = d->p[0] + (ortho * 4.0 * scaledThickness);
696 Point p6 = d->p[1] + (ortho * 4.0 * scaledThickness);
699 The numbers hardcoded into here, what are they?
700 I believe they are pixels.
702 // Draw extension lines (if certain type)
703 painter->DrawLine(p3, p5);
704 painter->DrawLine(p4, p6);
706 // Calculate whether or not the arrowheads are too crowded to put
707 // inside the extension lines. 9.0 is the length of the arrowhead.
708 double t = Geometry::ParameterOfLineAndPoint(d->lp[0], d->lp[1], d->lp[1] - (unit * 9.0 * scaledThickness));
710 // On the screen, it's acting like this is actually 58%...
711 // This is correct, we want it to happen at > 50%
714 // Draw main dimension line + arrowheads
715 painter->DrawLine(p1, p2);
716 painter->DrawArrowhead(p1, p2, scaledThickness);
717 painter->DrawArrowhead(p2, p1, scaledThickness);
721 // Draw outside arrowheads
722 Point p7 = p1 - (unit * 9.0 * scaledThickness);
723 Point p8 = p2 + (unit * 9.0 * scaledThickness);
724 painter->DrawArrowhead(p1, p7, scaledThickness);
725 painter->DrawArrowhead(p2, p8, scaledThickness);
726 painter->DrawLine(p1, p1 - (unit * 14.0 * scaledThickness));
727 painter->DrawLine(p2, p2 + (unit * 14.0 * scaledThickness));
730 // Draw length of dimension line...
731 painter->SetFont(QFont("Arial", 8.0 * Global::zoom * scaledThickness));
732 Point ctr = p2 + (Vector(p2, p1) / 2.0);
737 dimText = QString("%1\"").arg(length);
740 double feet = (double)((int)length / 12);
741 double inches = length - (feet * 12.0);
744 dimText = QString("%1'").arg(feet);
746 dimText = QString("%1' %2\"").arg(feet).arg(inches);
750 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().
752 painter->DrawAngledText(ctr, angle, dimText, scaledThickness);
756 Point hp1 = (p1 + p2) / 2.0;
757 Point hp2 = (p1 + hp1) / 2.0;
758 Point hp3 = (hp1 + p2) / 2.0;
762 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
763 painter->SetBrush(QBrush(QColor(Qt::magenta)));
764 painter->DrawArrowHandle(hp1, ortho.Angle() + HALF_TAU);
765 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
768 painter->DrawHandle(hp1);
769 painter->SetPen(QPen(Qt::blue, 1.0 * Global::zoom * scaledThickness, Qt::SolidLine));
773 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
774 painter->SetBrush(QBrush(QColor(Qt::magenta)));
775 painter->DrawArrowToLineHandle(hp2, (d->subtype == DTLinearVert ? v.Angle() - QTR_TAU : (v.Angle() < HALF_TAU ? HALF_TAU : 0)));
776 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
779 painter->DrawHandle(hp2);
780 painter->SetPen(QPen(Qt::blue, 1.0 * Global::zoom * scaledThickness, Qt::SolidLine));
784 painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
785 painter->SetBrush(QBrush(QColor(Qt::magenta)));
786 painter->DrawArrowToLineHandle(hp3, (d->subtype == DTLinearHorz ? v.Angle() - QTR_TAU : (v.Angle() > HALF_TAU && v.Angle() < THREE_QTR_TAU ? THREE_QTR_TAU : QTR_TAU)));
787 painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
790 painter->DrawHandle(hp3);
793 if (obj->hitPoint[0])
794 painter->DrawHandle(obj->p[0]);
796 if (obj->hitPoint[1])
797 painter->DrawHandle(obj->p[1]);
804 Text * t = (Text *)obj;
806 if (t->measured == false)
808 t->extents = painter->MeasureTextObject(t->s.c_str(), scaledThickness);
812 painter->DrawTextObject(t->p[0], t->s.c_str(), scaledThickness, t->angle[0]);
828 // Containers require recursive rendering...
829 Container * c = (Container *)obj;
830 printf("About to render container: # objs=%i, layer=%i\n", (*c).objects.size(), layer);
831 RenderObjects(painter, (*c).objects, layer, ignoreLayer);
833 //printf("Container extents: <%lf, %lf>, <%lf, %lf>\nsize: %i\n", r.l, r.t, r.r, r.b, c->objects.size());
834 // Containers also have special indicators showing they are selected
835 if (c->selected || c->hitObject)
837 // Rect r = GetObjectExtents(obj);
838 // painter->DrawRectCorners(r);
839 painter->DrawRectCorners(Rect(c->p[0], c->p[1]));
853 // This toggles the selection being hovered (typically, only 1 object)
855 void DrawingView::AddHoveredToSelection(void)
857 for(VPVectorIter i=document.objects.begin(); i!=document.objects.end(); i++)
859 if (((Object *)(*i))->hovered)
860 // ((Object *)(*i))->selected = true;
861 ((Object *)(*i))->selected = !((Object *)(*i))->selected;
866 VPVector DrawingView::GetSelection(void)
870 for(VPVectorIter i=document.objects.begin(); i!=document.objects.end(); i++)
872 if (((Object *)(*i))->selected)
880 VPVector DrawingView::GetHovered(void)
884 for(VPVectorIter i=document.objects.begin(); i!=document.objects.end(); i++)
886 if (((Object *)(*i))->hovered)
894 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
896 Global::screenSize = Vector(size().width(), size().height());
897 UpdateGridBackground();
901 void DrawingView::ToolHandler(int mode, Point p)
903 // Drop angle snap until it's needed
906 if (Global::tool == TTLine)
907 LineHandler(mode, p);
908 else if (Global::tool == TTCircle)
909 CircleHandler(mode, p);
910 else if (Global::tool == TTArc)
912 else if (Global::tool == TTRotate)
913 RotateHandler(mode, p);
914 else if (Global::tool == TTMirror)
915 MirrorHandler(mode, p);
916 else if (Global::tool == TTDimension)
917 DimensionHandler(mode, p);
918 else if (Global::tool == TTTriangulate)
919 TriangulateHandler(mode, p);
920 else if (Global::tool == TTTrim)
921 TrimHandler(mode, p);
922 else if (Global::tool == TTParallel)
923 ParallelHandler(mode, p);
927 void DrawingView::ToolDraw(Painter * painter)
929 if (Global::tool == TTLine)
931 if (Global::toolState == TSNone)
933 painter->DrawHandle(toolPoint[0]);
935 else if ((Global::toolState == TSPoint2) && shiftDown)
937 painter->DrawHandle(toolPoint[1]);
941 painter->DrawLine(toolPoint[0], toolPoint[1]);
942 painter->DrawHandle(toolPoint[1]);
944 Vector v(toolPoint[0], toolPoint[1]);
945 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
946 double absLength = v.Magnitude();
947 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
948 informativeText = text.arg(absLength).arg(absAngle);
951 else if (Global::tool == TTCircle)
953 if (Global::toolState == TSNone)
955 painter->DrawHandle(toolPoint[0]);
957 else if ((Global::toolState == TSPoint2) && shiftDown)
959 painter->DrawHandle(toolPoint[1]);
963 painter->DrawCross(toolPoint[0]);
964 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
965 painter->SetBrush(QBrush(Qt::NoBrush));
966 painter->DrawEllipse(toolPoint[0], length, length);
967 QString text = tr("Radius: %1 in.");
968 informativeText = text.arg(length);
971 else if (Global::tool == TTArc)
973 if (Global::toolState == TSNone)
975 painter->DrawHandle(toolPoint[0]);
977 else if (Global::toolState == TSPoint2)
979 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
980 painter->SetBrush(QBrush(Qt::NoBrush));
981 painter->DrawEllipse(toolPoint[0], length, length);
982 painter->DrawLine(toolPoint[0], toolPoint[1]);
983 painter->DrawHandle(toolPoint[1]);
984 QString text = tr("Radius: %1 in.");
985 informativeText = text.arg(length);
987 else if (Global::toolState == TSPoint3)
989 double angle = Vector::Angle(toolPoint[0], toolPoint[2]);
990 painter->DrawLine(toolPoint[0], toolPoint[2]);
991 painter->SetBrush(QBrush(Qt::NoBrush));
992 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
993 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
994 QString text = tr("Angle start: %1") + QChar(0x00B0);
995 informativeText = text.arg(RADIANS_TO_DEGREES * angle);
999 double angle = Vector::Angle(toolPoint[0], toolPoint[3]);
1000 double span = angle - toolPoint[2].x;
1005 painter->DrawLine(toolPoint[0], toolPoint[3]);
1006 painter->SetBrush(QBrush(Qt::NoBrush));
1007 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
1008 painter->SetPen(0xFF00FF, 2.0, LSSolid);
1009 painter->DrawArc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
1010 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
1011 QString text = tr("Arc span: %1") + QChar(0x00B0);
1012 informativeText = text.arg(RADIANS_TO_DEGREES * span);
1015 else if (Global::tool == TTRotate)
1017 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
1018 painter->DrawHandle(toolPoint[0]);
1019 else if ((Global::toolState == TSPoint2) && shiftDown)
1020 painter->DrawHandle(toolPoint[1]);
1023 if (toolPoint[0] == toolPoint[1])
1026 painter->DrawLine(toolPoint[0], toolPoint[1]);
1028 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
1029 QString text = QChar(0x2221) + QObject::tr(": %1");
1030 informativeText = text.arg(absAngle);
1033 informativeText += " (Copy)";
1036 else if (Global::tool == TTMirror)
1038 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
1039 painter->DrawHandle(toolPoint[0]);
1040 else if ((Global::toolState == TSPoint2) && shiftDown)
1041 painter->DrawHandle(toolPoint[1]);
1044 if (toolPoint[0] == toolPoint[1])
1047 Point mirrorPoint = toolPoint[0] + Vector(toolPoint[1], toolPoint[0]);
1048 painter->DrawLine(mirrorPoint, toolPoint[1]);
1050 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
1052 if (absAngle > 180.0)
1055 QString text = QChar(0x2221) + QObject::tr(": %1");
1056 informativeText = text.arg(absAngle);
1059 informativeText += " (Copy)";
1062 if (Global::tool == TTDimension)
1064 if (Global::toolState == TSNone)
1066 painter->DrawHandle(toolPoint[0]);
1068 else if ((Global::toolState == TSPoint2) && shiftDown)
1070 painter->DrawHandle(toolPoint[1]);
1074 painter->DrawLine(toolPoint[0], toolPoint[1]);
1075 painter->DrawHandle(toolPoint[1]);
1077 Vector v(toolPoint[0], toolPoint[1]);
1078 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
1079 double absLength = v.Magnitude();
1080 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
1081 informativeText = text.arg(absLength).arg(absAngle);
1087 void DrawingView::LineHandler(int mode, Point p)
1092 if (Global::toolState == TSNone)
1100 if (Global::toolState == TSNone)
1108 if (Global::toolState == TSNone)
1110 Global::toolState = TSPoint2;
1111 // Prevent spurious line from drawing...
1112 toolPoint[1] = toolPoint[0];
1114 else if ((Global::toolState == TSPoint2) && shiftDown)
1116 // Key override is telling us to make a new line, not continue the
1118 toolPoint[0] = toolPoint[1];
1122 Line * l = new Line(toolPoint[0], toolPoint[1], Global::penWidth, Global::penColor, Global::penStyle);
1123 l->layer = Global::activeLayer;
1124 document.objects.push_back(l);
1125 toolPoint[0] = toolPoint[1];
1131 void DrawingView::CircleHandler(int mode, Point p)
1136 if (Global::toolState == TSNone)
1144 if (Global::toolState == TSNone)
1152 if (Global::toolState == TSNone)
1154 Global::toolState = TSPoint2;
1155 // Prevent spurious line from drawing...
1156 toolPoint[1] = toolPoint[0];
1158 else if ((Global::toolState == TSPoint2) && shiftDown)
1160 // Key override is telling us to make a new line, not continue the
1162 toolPoint[0] = toolPoint[1];
1166 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
1167 Circle * c = new Circle(toolPoint[0], length, Global::penWidth, Global::penColor, Global::penStyle);
1168 c->layer = Global::activeLayer;
1169 document.objects.push_back(c);
1170 toolPoint[0] = toolPoint[1];
1171 Global::toolState = TSNone;
1177 void DrawingView::ArcHandler(int mode, Point p)
1182 if (Global::toolState == TSNone)
1184 else if (Global::toolState == TSPoint2)
1186 else if (Global::toolState == TSPoint3)
1194 if (Global::toolState == TSNone)
1196 else if (Global::toolState == TSPoint2)
1198 else if (Global::toolState == TSPoint3)
1212 if (Global::toolState == TSNone)
1214 // Prevent spurious line from drawing...
1215 toolPoint[1] = toolPoint[0];
1216 Global::toolState = TSPoint2;
1218 else if (Global::toolState == TSPoint2)
1222 // Key override is telling us to start arc at new center, not
1223 // continue the current one.
1224 toolPoint[0] = toolPoint[1];
1228 // Set the radius in toolPoint[1].x
1229 toolPoint[1].x = Vector::Magnitude(toolPoint[0], toolPoint[1]);
1230 Global::toolState = TSPoint3;
1232 else if (Global::toolState == TSPoint3)
1234 // Set the angle in toolPoint[2].x
1235 toolPoint[2].x = Vector::Angle(toolPoint[0], toolPoint[2]);
1236 Global::toolState = TSPoint4;
1240 double endAngle = Vector::Angle(toolPoint[0], toolPoint[3]);
1241 double span = endAngle - toolPoint[2].x;
1246 Arc * arc = new Arc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span, Global::penWidth, Global::penColor, Global::penStyle);
1247 arc->layer = Global::activeLayer;
1248 document.objects.push_back(arc);
1249 Global::toolState = TSNone;
1255 void DrawingView::RotateHandler(int mode, Point p)
1260 if (Global::toolState == TSNone)
1263 // SavePointsFrom(select, toolScratch);
1264 CopyObjects(select, toolScratch2);
1265 Global::toolState = TSPoint1;
1267 else if (Global::toolState == TSPoint1)
1275 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1277 else if (Global::toolState == TSPoint2)
1285 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1286 VPVectorIter j = select.begin();
1287 // std::vector<Object>::iterator i = toolScratch.begin();
1288 VPVectorIter i = toolScratch2.begin();
1290 // for(; i!=toolScratch.end(); i++, j++)
1291 for(; i!=toolScratch2.end(); i++, j++)
1293 // Object objT = *i;
1294 // Point p1 = Geometry::RotatePointAroundPoint(objT.p[0], toolPoint[0], angle);
1295 // Point p2 = Geometry::RotatePointAroundPoint(objT.p[1], toolPoint[0], angle);
1296 Object * objT = (Object *)(*i);
1297 Object * objS = (Object *)(*j);
1299 Point p1 = Geometry::RotatePointAroundPoint(objT->p[0], toolPoint[0], angle);
1300 Point p2 = Geometry::RotatePointAroundPoint(objT->p[1], toolPoint[0], angle);
1305 // if (objT.type == OTArc || objT.type == OTText)
1306 if (objT->type == OTArc || objT->type == OTText)
1308 // objS->angle[0] = objT.angle[0] + angle;
1309 objS->angle[0] = objT->angle[0] + angle;
1311 if (objS->angle[0] > TAU)
1312 objS->angle[0] -= TAU;
1314 // else if (objT.type == OTContainer)
1315 else if (objT->type == OTContainer)
1317 // 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...]
1318 // Container * c = (Container *)&objT;
1319 Container * c = (Container *)objT;
1320 Container * c2 = (Container *)objS;
1321 VPVectorIter l = c->objects.begin();
1322 // TODO: Rotate items in the container
1323 // TODO: Make this recursive
1324 for(VPVectorIter k=c2->objects.begin(); k!=c2->objects.end(); k++, l++)
1326 Object * obj3 = (Object *)(*k);
1327 Object * obj4 = (Object *)(*l);
1329 p1 = Geometry::RotatePointAroundPoint(obj4->p[0], toolPoint[0], angle);
1330 p2 = Geometry::RotatePointAroundPoint(obj4->p[1], toolPoint[0], angle);
1334 // obj3->angle[0] = objT.angle[0] + angle;
1335 obj3->angle[0] = obj4->angle[0] + angle;
1337 if (obj3->angle[0] > TAU)
1338 obj3->angle[0] -= TAU;
1341 Rect r = GetObjectExtents(objS);
1342 c2->p[0] = r.TopLeft();
1343 c2->p[1] = r.BottomRight();
1351 if (Global::toolState == TSPoint1)
1353 Global::toolState = TSPoint2;
1354 // Prevent spurious line from drawing...
1355 toolPoint[1] = toolPoint[0];
1357 else if ((Global::toolState == TSPoint2) && shiftDown)
1359 // Key override is telling us to make a new line, not continue the
1361 toolPoint[0] = toolPoint[1];
1365 // Either we're finished with our rotate, or we're stamping a copy.
1368 // Stamp a copy of the selection at the current rotation & bail
1370 CopyObjects(select, temp);
1371 ClearSelected(temp);
1372 AddObjectsTo(document.objects, temp);
1373 // RestorePointsTo(select, toolScratch);
1374 RestorePointsTo(select, toolScratch2);
1379 Global::toolState = TSPoint1;
1380 // SavePointsFrom(select, toolScratch);
1381 DeleteContents(toolScratch2);
1382 CopyObjects(select, toolScratch2);
1388 // Reset the selection if shift held down...
1390 // RestorePointsTo(select, toolScratch);
1391 RestorePointsTo(select, toolScratch2);
1396 // Reset selection when key is let up
1398 RotateHandler(ToolMouseMove, toolPoint[1]);
1403 // RestorePointsTo(select, toolScratch);
1404 RestorePointsTo(select, toolScratch2);
1405 DeleteContents(toolScratch2);
1410 void DrawingView::MirrorHandler(int mode, Point p)
1415 if (Global::toolState == TSNone)
1418 SavePointsFrom(select, toolScratch);
1419 Global::toolState = TSPoint1;
1421 else if (Global::toolState == TSPoint1)
1429 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1431 else if (Global::toolState == TSPoint2)
1439 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1440 VPVectorIter j = select.begin();
1441 std::vector<Object>::iterator i = toolScratch.begin();
1443 for(; i!=toolScratch.end(); i++, j++)
1446 Point p1 = Geometry::MirrorPointAroundLine(obj.p[0], toolPoint[0], toolPoint[1]);
1447 Point p2 = Geometry::MirrorPointAroundLine(obj.p[1], toolPoint[0], toolPoint[1]);
1448 Object * obj2 = (Object *)(*j);
1453 N.B.: When mirroring an arc thru a horizontal axis, this causes the arc to have
1454 a negative start angle which makes it impossible to interact with.
1457 if (obj.type == OTArc)
1459 // This is 2*mirror angle - obj angle - obj span
1460 obj2->angle[0] = (2.0 * angle) - obj.angle[0] - obj.angle[1];
1462 if (obj2->angle[0] > TAU)
1463 obj2->angle[0] -= TAU;
1471 if (Global::toolState == TSPoint1)
1473 Global::toolState = TSPoint2;
1474 // Prevent spurious line from drawing...
1475 toolPoint[1] = toolPoint[0];
1477 else if ((Global::toolState == TSPoint2) && shiftDown)
1479 // Key override is telling us to make a new line, not continue the
1481 toolPoint[0] = toolPoint[1];
1485 // Either we're finished with our rotate, or we're stamping a copy.
1488 // Stamp a copy of the selection at the current rotation & bail
1490 CopyObjects(select, temp);
1491 ClearSelected(temp);
1492 AddObjectsTo(document.objects, temp);
1493 RestorePointsTo(select, toolScratch);
1498 Global::toolState = TSPoint1;
1499 SavePointsFrom(select, toolScratch);
1505 // Reset the selection if shift held down...
1507 RestorePointsTo(select, toolScratch);
1512 // Reset selection when key is let up
1514 MirrorHandler(ToolMouseMove, toolPoint[1]);
1519 RestorePointsTo(select, toolScratch);
1524 void DrawingView::DimensionHandler(int mode, Point p)
1529 if (Global::toolState == TSNone)
1537 if (Global::toolState == TSNone)
1545 if (Global::toolState == TSNone)
1547 Global::toolState = TSPoint2;
1548 // Prevent spurious line from drawing...
1549 toolPoint[1] = toolPoint[0];
1551 else if ((Global::toolState == TSPoint2) && shiftDown)
1553 // Key override is telling us to make a new line, not continue the
1555 toolPoint[0] = toolPoint[1];
1559 Dimension * d = new Dimension(toolPoint[0], toolPoint[1], DTLinear);
1560 d->layer = Global::activeLayer;
1561 document.objects.push_back(d);
1562 Global::toolState = TSNone;
1568 void DrawingView::TriangulateHandler(int mode, Point/*p*/)
1574 // Skip if nothing hovered...
1575 if (numHovered != 1)
1578 VPVector hover = GetHovered();
1579 Object * obj = (Object *)hover[0];
1581 // Skip if it's not a line...
1582 if (obj->type != OTLine)
1585 if (Global::toolState == TSNone)
1587 else if (Global::toolState == TSPoint2)
1596 if (Global::toolState == TSNone)
1598 else if (Global::toolState == TSPoint2)
1600 else if (Global::toolState == TSPoint3)
1614 if (Global::toolState == TSNone)
1616 Global::toolState = TSPoint2;
1618 else if (Global::toolState == TSPoint2)
1622 // Key override is telling us to start arc at new center, not
1623 // continue the current one.
1624 toolPoint[0] = toolPoint[1];
1628 Global::toolState = TSPoint3;
1632 double len2 = Vector::Magnitude(toolObj[1]->p[0], toolObj[1]->p[1]);
1633 double len3 = Vector::Magnitude(toolObj[2]->p[0], toolObj[2]->p[1]);
1635 Circle c1(toolObj[0]->p[0], len2);
1636 Circle c2(toolObj[0]->p[1], len3);
1638 Geometry::CheckCircleToCircleIntersection((Object *)&c1, (Object *)&c2);
1640 // Only move lines if the triangle formed by them is not degenerate
1641 if (Global::numIntersectPoints > 0)
1643 toolObj[1]->p[0] = toolObj[0]->p[0];
1644 toolObj[1]->p[1] = Global::intersectPoint[0];
1646 toolObj[2]->p[0] = Global::intersectPoint[0];
1647 toolObj[2]->p[1] = toolObj[0]->p[1];
1650 Global::toolState = TSNone;
1656 void DrawingView::TrimHandler(int mode, Point p)
1659 n.b.: this code is lifted straight out of the old oo code. needs to be updated.
1666 Object * toTrim = doc->lastObjectHovered;
1671 Vector v(((Line *)toTrim)->position, ((Line *)toTrim)->endpoint);
1673 // Check to see which case we have...
1674 // We're trimming point #1...
1677 ((Line *)toTrim)->position = ((Line *)toTrim)->position + (v * u);
1681 ((Line *)toTrim)->endpoint = ((Line *)toTrim)->position + (v * t);
1685 Point p1 = ((Line *)toTrim)->position + (v * t);
1686 Point p2 = ((Line *)toTrim)->position + (v * u);
1687 Point p3 = ((Line *)toTrim)->endpoint;
1688 ((Line *)toTrim)->endpoint = p1;
1689 Line * line = new Line(p2, p3);
1690 emit ObjectReady(line);
1693 doc->lastObjectHovered = NULL;
1701 Object * toTrim = doc->lastObjectHovered;
1707 if (toTrim->type != OTLine)
1710 double pointHoveredT = Geometry::ParameterOfLineAndPoint(((Line *)toTrim)->position, ((Line *)toTrim)->endpoint, point);
1712 std::vector<Object *>::iterator i;
1714 for(i=doc->objects.begin(); i!=doc->objects.end(); i++)
1716 // Can't trim against yourself... :-P
1720 Object * trimAgainst = *i;
1723 if ((toTrim->type != OTLine) || (trimAgainst->type != OTLine))
1726 int intersects = Geometry::Intersects((Line *)toTrim, (Line *)trimAgainst, &t1);//, &u1);
1730 // Now what? We don't know which side to trim!
1731 // ... now we do, we know which side of the Line we're on!
1732 if ((t1 > t) && (t1 < pointHoveredT))
1735 if ((t1 < u) && (t1 > pointHoveredT))
1740 // Bail out if nothing hovered...
1741 if (numHovered != 1)
1747 VPVector hover = GetHovered();
1748 Object * obj = (Object *)hover[0];
1750 // Skip if it's not a line...
1751 if (obj->type != OTLine)
1758 double hoveredParam = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], p);
1760 // Currently only deal with line against line trimming, can expand to
1761 // others as well (line/circle, circle/circle, line/arc, etc)
1763 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1765 obj = (Object *)(*i);
1767 if (obj == toolObj[0])
1769 else if (obj->type != OTLine)
1772 Geometry::CheckLineToLineIntersection(toolObj[0], obj);
1774 if (Global::numIntersectParams > 0)
1776 // Mark the line segment somehow (the side the mouse is on) so that it can be drawn & trimmed when we hit ToolMouseDown.
1797 void DrawingView::ParallelHandler(int mode, Point /*p*/)
1822 void DrawingView::mousePressEvent(QMouseEvent * event)
1824 if (event->button() == Qt::LeftButton)
1826 //printf("mousePressEvent::Qt::LeftButton numHovered=%li\n", numHovered);
1827 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1829 // Handle tool processing, if any
1832 if (hoveringIntersection)
1833 point = intersectionPoint;
1834 else if (hoverPointValid)
1836 else if (Global::snapToGrid)
1837 point = SnapPointToGrid(point);
1839 //Also, may want to figure out if hovering over a snap point on an
1840 //object, snap to grid if not.
1841 // Snap to object point if valid...
1842 // if (Global::snapPointIsValid)
1843 // point = Global::snapPoint;
1845 ToolHandler(ToolMouseDown, point);
1849 // Clear the selection only if CTRL isn't being held on click
1851 ClearSelected(document.objects);
1852 // ClearSelection();
1854 // If any objects are being hovered on click, add them to the selection
1858 AddHoveredToSelection();
1859 update(); // needed??
1860 // GetHovered(hover); // prolly needed
1861 VPVector hover2 = GetHovered();
1862 dragged = (Object *)hover2[0];
1863 draggingObject = true;
1864 //printf("mousePressEvent::numHovered > 0 (hover2[0]=$%llx, type=%s)\n", dragged, objName[dragged->type]);
1866 // Alert the pen widget
1867 emit ObjectSelected(dragged);
1869 // See if anything is using just a straight click on a handle
1870 if (HandleObjectClicked())
1872 draggingObject = false;
1877 // Needed for grab & moving objects
1878 // We do it *after*... why? (doesn't seem to confer any advantage...)
1879 if (hoveringIntersection)
1880 oldPoint = intersectionPoint;
1881 else if (hoverPointValid)
1882 oldPoint = hoverPoint;
1883 else if (Global::snapToGrid)
1884 oldPoint = SnapPointToGrid(point);
1886 // Needed for fixed length handling
1887 if (Global::fixedLength)
1889 if (dragged->type == OTLine)
1891 dragged->length = Vector::Magnitude(dragged->p[0], dragged->p[1]);
1895 if (dragged->type == OTCircle)
1897 // Save for informative text, uh, er, informing
1898 dragged->length = dragged->radius[0];
1904 // Didn't hit any object and not using a tool, so do a selection
1906 Global::selectionInProgress = true;
1907 Global::selection.setTopLeft(QPointF(point.x, point.y));
1908 Global::selection.setBottomRight(QPointF(point.x, point.y));
1909 select = GetSelection();
1911 else if (event->button() == Qt::MiddleButton)
1914 oldPoint = Vector(event->x(), event->y());
1915 // Should also change the mouse pointer as well...
1916 setCursor(Qt::SizeAllCursor);
1921 void DrawingView::mouseMoveEvent(QMouseEvent * event)
1923 // It seems that wheelEvent() triggers this for some reason...
1924 if (scrollWheelSeen)
1926 scrollWheelSeen = false;
1930 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1931 Global::selection.setBottomRight(QPointF(point.x, point.y));
1932 // Only needs to be done here, as mouse down is always preceded by movement
1933 Global::snapPointIsValid = false;
1934 hoveringIntersection = false;
1935 oldScrollPoint = Vector(event->x(), event->y());
1938 if ((event->buttons() & Qt::MiddleButton) || scrollDrag)
1940 point = Vector(event->x(), event->y());
1941 // Since we're using Qt coords for scrolling, we have to adjust them
1942 // here to conform to Cartesian coords, since the origin is using
1944 Vector delta(oldPoint, point);
1945 delta /= Global::zoom;
1947 Global::origin -= delta;
1949 UpdateGridBackground();
1955 // If we're doing a selection rect, see if any objects are engulfed by it
1956 // (implies left mouse button held down)
1957 if (Global::selectionInProgress)
1959 CheckObjectBounds();
1961 // Make sure previously selected objects stay selected (CTRL held)
1962 for(VPVectorIter i=select.begin(); i!=select.end(); i++)
1963 ((Object *)(*i))->selected = true;
1969 // Do object hit testing...
1970 bool needUpdate = HitTestObjects(point);
1971 VPVector hover2 = GetHovered();
1976 printf("mouseMoveEvent:: numHovered=%li, hover2.size()=%li\n", numHovered, hover2.size());
1978 if (hover2.size() > 0)
1979 printf(" (hover2[0]=$%llX, type=%s)\n", hover2[0], objName[((Object *)hover2[0])->type]);
1984 // Check for multi-hover...
1987 //need to check for case where hover is over 2 circles and a 3rd's center...
1988 Object * obj1 = (Object *)hover2[0], * obj2 = (Object *)hover2[1];
1990 Geometry::Intersects(obj1, obj2);
1991 int numIntersecting = Global::numIntersectParams;
1992 double t = Global::intersectParam[0];
1993 double u = Global::intersectParam[1];
1995 if (numIntersecting > 0)
1997 Vector v1 = Geometry::GetPointForParameter(obj1, t);
1998 Vector v2 = Geometry::GetPointForParameter(obj2, u);
1999 QString text = tr("Intersection t=%1 (%3, %4), u=%2 (%5, %6)");
2000 informativeText = text.arg(t).arg(u).arg(v1.x).arg(v1.y).arg(v2.x).arg(v2.y);
2002 hoveringIntersection = true;
2003 intersectionPoint = v1;
2006 numIntersecting = Global::numIntersectPoints;
2008 if (numIntersecting > 0)
2010 Vector v1 = Global::intersectPoint[0];
2012 if (numIntersecting == 2)
2014 Vector v2 = Global::intersectPoint[1];
2016 if (Vector::Magnitude(v2, point) < Vector::Magnitude(v1, point))
2020 QString text = tr("Intersection <%1, %2>");
2021 informativeText = text.arg(v1.x).arg(v1.y);
2022 hoveringIntersection = true;
2023 intersectionPoint = v1;
2026 else if (numHovered == 1)
2028 Object * obj = (Object *)hover2[0];
2030 if (obj->type == OTLine)
2033 Not sure that this is the best way to handle this, but it works(TM)...
2034 Except when lines are overlapping, then it doesn't work... !!! FIX !!!
2036 Point midpoint = Geometry::Midpoint((Line *)obj);
2037 Vector v1 = Vector::Magnitude(midpoint, point);
2039 if ((v1.Magnitude() * Global::zoom) < 8.0)
2041 QString text = tr("Midpoint <%1, %2>");
2042 informativeText = text.arg(midpoint.x).arg(midpoint.y);
2043 hoverPointValid = true;
2044 hoverPoint = midpoint;
2050 // Handle object movement (left button down & over an object)
2051 if ((event->buttons() & Qt::LeftButton) && draggingObject && !Global::tool)
2053 if (hoveringIntersection)
2054 point = intersectionPoint;
2055 else if (hoverPointValid)
2057 else if (Global::snapToGrid)
2058 point = SnapPointToGrid(point);
2060 HandleObjectMovement(point);
2066 // Do tool handling, if any are active...
2069 if (hoveringIntersection)
2070 point = intersectionPoint;
2071 else if (hoverPointValid)
2073 else if (Global::snapToGrid)
2076 point = SnapPointToAngle(point);
2078 point = SnapPointToGrid(point);
2081 ToolHandler(ToolMouseMove, point);
2084 // This is used to draw the tool crosshair...
2087 if (needUpdate || Global::tool)
2092 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
2094 if (event->button() == Qt::LeftButton)
2096 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
2097 //could set it up to use the document's update function (assumes that all object
2098 //updates are being reported correctly:
2099 // if (document.NeedsUpdate())
2100 // Do an update if collided with at least *one* object in the document
2106 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
2107 ToolHandler(ToolMouseUp, point);
2111 // if (Global::selectionInProgress)
2112 Global::selectionInProgress = false;
2114 informativeText.clear();
2115 // Should we be doing this automagically? Hmm...
2116 // Clear our vectors
2120 // 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)
2121 select = GetSelection();
2123 draggingObject = false;
2125 else if (event->button() == Qt::MiddleButton)
2128 setCursor(Qt::ArrowCursor);
2133 void DrawingView::wheelEvent(QWheelEvent * event)
2135 double zoomFactor = 1.20;
2136 scrollWheelSeen = true;
2138 if (event->angleDelta().y() < 0)
2140 if (Global::zoom > 400.0)
2143 Global::zoom *= zoomFactor;
2147 if (Global::zoom < 0.125)
2150 Global::zoom /= zoomFactor;
2153 Point np = Painter::QtToCartesianCoords(oldScrollPoint);
2154 Global::origin += (oldPoint - np);
2156 emit(NeedZoomUpdate());
2160 void DrawingView::keyPressEvent(QKeyEvent * event)
2162 bool oldShift = shiftDown;
2163 bool oldCtrl = ctrlDown;
2164 bool oldAlt = altDown;
2166 if (event->key() == Qt::Key_Shift)
2168 else if (event->key() == Qt::Key_Control)
2170 else if (event->key() == Qt::Key_Alt)
2173 // If there's a change in any of the modifier key states, pass it on to
2174 // the current tool's handler
2175 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
2178 ToolHandler(ToolKeyDown, Point(0, 0));
2183 if (oldAlt != altDown)
2186 setCursor(Qt::SizeAllCursor);
2187 oldPoint = oldScrollPoint;
2190 if (select.size() > 0)
2192 if (event->key() == Qt::Key_Up)
2194 TranslateObjects(select, Point(0, +1.0));
2197 else if (event->key() == Qt::Key_Down)
2199 TranslateObjects(select, Point(0, -1.0));
2202 else if (event->key() == Qt::Key_Right)
2204 TranslateObjects(select, Point(+1.0, 0));
2207 else if (event->key() == Qt::Key_Left)
2209 TranslateObjects(select, Point(-1.0, 0));
2216 void DrawingView::keyReleaseEvent(QKeyEvent * event)
2218 bool oldShift = shiftDown;
2219 bool oldCtrl = ctrlDown;
2220 bool oldAlt = altDown;
2222 if (event->key() == Qt::Key_Shift)
2224 else if (event->key() == Qt::Key_Control)
2226 else if (event->key() == Qt::Key_Alt)
2229 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
2232 ToolHandler(ToolKeyUp, Point(0, 0));
2237 if (oldAlt != altDown)
2240 setCursor(Qt::ArrowCursor);
2246 // This looks strange, but it's really quite simple: We want a point that's
2247 // more than half-way to the next grid point to snap there while conversely we
2248 // want a point that's less than half-way to to the next grid point then snap
2249 // to the one before it. So we add half of the grid spacing to the point, then
2250 // divide by it so that we can remove the fractional part, then multiply it
2251 // back to get back to the correct answer.
2253 Point DrawingView::SnapPointToGrid(Point point)
2255 point += Global::gridSpacing / 2.0; // *This* adds to Z!!!
2256 point /= Global::gridSpacing;
2257 point.x = floor(point.x);//need to fix this for negative numbers...
2258 point.y = floor(point.y);
2259 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
2260 point *= Global::gridSpacing;
2265 Point DrawingView::SnapPointToAngle(Point point)
2267 // Snap to a single digit angle (using toolpoint #1 as the center)
2268 double angle = Vector::Angle(toolPoint[0], point);
2269 double length = Vector::Magnitude(toolPoint[0], point);
2271 // Convert from radians to degrees
2272 double degAngle = angle * RADIANS_TO_DEGREES;
2273 double snapAngle = (double)((int)(degAngle + 0.5));
2276 v.SetAngleAndLength(snapAngle * DEGREES_TO_RADIANS, length);
2277 point = toolPoint[0] + v;
2283 Rect DrawingView::GetObjectExtents(Object * obj)
2285 // Default to empty rect, if object checks below fail for some reason
2293 rect = Rect(obj->p[0], obj->p[1]);
2299 rect = Rect(obj->p[0], obj->p[0]);
2300 rect.Expand(obj->radius[0]);
2306 Arc * a = (Arc *)obj;
2308 double start = a->angle[0];
2309 double end = start + a->angle[1];
2310 rect = Rect(Point(cos(start), sin(start)), Point(cos(end), sin(end)));
2312 // If the end of the arc is before the beginning, add 360 degrees to it
2316 // Adjust the bounds depending on which axes are crossed
2317 if ((start < QTR_TAU) && (end > QTR_TAU))
2320 if ((start < HALF_TAU) && (end > HALF_TAU))
2323 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
2326 if ((start < TAU) && (end > TAU))
2329 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
2332 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
2335 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
2338 rect *= a->radius[0];
2339 rect.Translate(a->p[0]);
2345 Text * t = (Text *)obj;
2346 rect = Rect(t->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
2352 Container * c = (Container *)obj;
2353 VPVectorIter i = c->objects.begin();
2354 rect = GetObjectExtents((Object *)*i);
2357 for(; i!=c->objects.end(); i++)
2358 rect |= GetObjectExtents((Object *)*i);
2369 void DrawingView::CheckObjectBounds(void)
2373 for(i=document.objects.begin(); i!=document.objects.end(); i++)
2375 Object * obj = (Object *)(*i);
2376 obj->selected = false;
2383 Line * l = (Line *)obj;
2385 if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
2393 Circle * c = (Circle *)obj;
2395 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]))
2403 Arc * a = (Arc *)obj;
2405 double start = a->angle[0];
2406 double end = start + a->angle[1];
2407 QPointF p1(cos(start), sin(start));
2408 QPointF p2(cos(end), sin(end));
2409 QRectF bounds(p1, p2);
2412 // Swap X/Y coordinates if they're backwards...
2413 if (bounds.left() > bounds.right())
2415 double temp = bounds.left();
2416 bounds.setLeft(bounds.right());
2417 bounds.setRight(temp);
2420 if (bounds.bottom() > bounds.top())
2422 double temp = bounds.bottom();
2423 bounds.setBottom(bounds.top());
2424 bounds.setTop(temp);
2427 // Doesn't work as advertised! For shame!
2428 bounds = bounds.normalized();
2431 // If the end of the arc is before the beginning, add 360 degrees
2436 // Adjust the bounds depending on which axes are crossed
2437 if ((start < QTR_TAU) && (end > QTR_TAU))
2440 if ((start < HALF_TAU) && (end > HALF_TAU))
2441 bounds.setLeft(-1.0);
2443 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
2444 bounds.setBottom(-1.0);
2446 if ((start < TAU) && (end > TAU))
2447 bounds.setRight(1.0);
2449 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
2452 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
2453 bounds.setLeft(-1.0);
2455 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
2456 bounds.setBottom(-1.0);
2458 bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
2459 bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
2460 bounds.translate(a->p[0].x, a->p[0].y);
2462 if (Global::selection.contains(bounds))
2470 Text * t = (Text *)obj;
2471 Rect r(obj->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
2473 if (Global::selection.contains(r.l, r.t) && Global::selection.contains(r.r, r.b))
2481 Container * c = (Container *)obj;
2483 if (Global::selection.contains(c->p[0].x, c->p[0].y) && Global::selection.contains(c->p[1].x, c->p[1].y))
2496 bool DrawingView::HitTestObjects(Point point)
2500 bool needUpdate = false;
2501 hoverPointValid = false;
2503 for(i=document.objects.begin(); i!=document.objects.end(); i++)
2505 Object * obj = (Object *)(*i);
2507 // If we're seeing the object we're dragging, skip it
2508 if (draggingObject && (obj == dragged))
2511 if (HitTest(obj, point) == true)
2517 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
2518 emit ObjectHovered(obj);
2526 bool DrawingView::HitTest(Object * obj, Point point)
2528 bool needUpdate = false;
2534 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
2535 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
2536 Vector lineSegment = obj->p[1] - obj->p[0];
2537 Vector v1 = point - obj->p[0];
2538 Vector v2 = point - obj->p[1];
2539 double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
2543 distance = v1.Magnitude();
2545 distance = v2.Magnitude();
2547 // distance = ?Det?(ls, v1) / |ls|
2548 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
2549 / lineSegment.Magnitude());
2551 if ((v1.Magnitude() * Global::zoom) < 8.0)
2553 obj->hitPoint[0] = true;
2554 hoverPoint = obj->p[0];
2555 hoverPointValid = true;
2557 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2559 obj->hitPoint[1] = true;
2560 hoverPoint = obj->p[1];
2561 hoverPointValid = true;
2563 else if ((distance * Global::zoom) < 5.0)
2564 obj->hitObject = true;
2566 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
2568 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
2576 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
2577 obj->hitPoint[0] = obj->hitObject = false;
2578 double length = Vector::Magnitude(obj->p[0], point);
2580 if ((length * Global::zoom) < 8.0)
2582 obj->hitPoint[0] = true;
2583 hoverPoint = obj->p[0];
2584 hoverPointValid = true;
2586 else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
2587 obj->hitObject = true;
2589 obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
2591 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
2599 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
2600 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
2601 double length = Vector::Magnitude(obj->p[0], point);
2602 double angle = Vector::Angle(obj->p[0], point);
2604 // Make sure we get the angle in the correct spot
2605 if (angle < obj->angle[0])
2608 // Get the span that we're pointing at...
2609 double span = angle - obj->angle[0];
2611 // N.B.: Still need to hit test the arc start & arc span handles...
2612 double spanAngle = obj->angle[0] + obj->angle[1];
2613 Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
2614 Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
2615 double length2 = Vector::Magnitude(point, handle1);
2616 double length3 = Vector::Magnitude(point, handle2);
2618 if ((length * Global::zoom) < 8.0)
2620 obj->hitPoint[0] = true;
2621 hoverPoint = obj->p[0];
2622 hoverPointValid = true;
2624 else if ((length2 * Global::zoom) < 8.0)
2626 obj->hitPoint[1] = true;
2627 hoverPoint = handle1;
2628 hoverPointValid = true;
2630 else if ((length3 * Global::zoom) < 8.0)
2632 obj->hitPoint[2] = true;
2633 hoverPoint = handle2;
2634 hoverPointValid = true;
2636 else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
2637 obj->hitObject = true;
2639 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
2641 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
2649 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHP3 = obj->hitPoint[3], oldHP4 = obj->hitPoint[4], oldHO = obj->hitObject;
2650 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitPoint[3] = obj->hitPoint[4] = obj->hitObject = false;
2652 Dimension * d = (Dimension *)obj;
2654 Vector orthogonal = Vector::Normal(d->lp[0], d->lp[1]);
2655 // Get our line parallel to our points
2656 float scaledThickness = Global::scale * obj->thickness;
2658 Point p1 = d->lp[0] + (orthogonal * (d->offset + (10.0 * scaledThickness)));
2659 Point p2 = d->lp[1] + (orthogonal * (d->offset + (10.0 * scaledThickness)));
2661 Point p1 = d->lp[0] + (orthogonal * (10.0 + d->offset) * scaledThickness);
2662 Point p2 = d->lp[1] + (orthogonal * (10.0 + d->offset) * scaledThickness);
2664 Point p3(p1, point);
2666 Vector v1(d->p[0], point);
2667 Vector v2(d->p[1], point);
2668 Vector lineSegment(p1, p2);
2669 double t = Geometry::ParameterOfLineAndPoint(p1, p2, point);
2671 Point midpoint = (p1 + p2) / 2.0;
2672 Point hFSPoint = Point(midpoint, point);
2673 Point hCS1Point = Point((p1 + midpoint) / 2.0, point);
2674 Point hCS2Point = Point((midpoint + p2) / 2.0, point);
2677 distance = v1.Magnitude();
2679 distance = v2.Magnitude();
2681 // distance = ?Det?(ls, v1) / |ls|
2682 distance = fabs((lineSegment.x * p3.y - p3.x * lineSegment.y)
2683 / lineSegment.Magnitude());
2685 if ((v1.Magnitude() * Global::zoom) < 8.0)
2686 obj->hitPoint[0] = true;
2687 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2688 obj->hitPoint[1] = true;
2689 else if ((distance * Global::zoom) < 5.0)
2690 obj->hitObject = true;
2692 if ((hFSPoint.Magnitude() * Global::zoom) < 8.0)
2693 obj->hitPoint[2] = true;
2694 else if ((hCS1Point.Magnitude() * Global::zoom) < 8.0)
2695 obj->hitPoint[3] = true;
2696 else if ((hCS2Point.Magnitude() * Global::zoom) < 8.0)
2697 obj->hitPoint[4] = true;
2699 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitPoint[3] || obj->hitPoint[4] || obj->hitObject ? true : false);
2701 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHP3 != obj->hitPoint[3]) || (oldHP4 != obj->hitPoint[4]) || (oldHO != obj->hitObject))
2709 Text * t = (Text *)obj;
2710 bool oldHO = obj->hitObject;
2711 obj->hitObject = false;
2713 Rect r(obj->p[0], Point(obj->p[0].x + t->extents.Width(), obj->p[0].y - t->extents.Height()));
2714 //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);
2716 if (r.Contains(point))
2717 obj->hitObject = true;
2719 obj->hovered = (obj->hitObject ? true : false);
2721 if (oldHO != obj->hitObject)
2729 // Containers must be recursively tested... Or do they???
2731 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.
2733 // bool oldHitObj = c->hitObject, oldHovered = c->hovered;
2734 // Object * oldClicked = c->clicked;
2736 still need to compare old state to new state, and set things up based upon that...
2737 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);
2738 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.
2740 Container * c = (Container *)obj;
2741 c->hitObject = false;
2745 VPVector flat = Flatten(c);
2747 //printf("HitTest::OTContainer (size=%li)\n", flat.size());
2748 for(VPVectorIter i=flat.begin(); i!=flat.end(); i++)
2750 Object * cObj = (Object *)(*i);
2752 // Skip the flattened containers (if any)...
2753 if (cObj->type == OTContainer)
2756 // We do it this way instead of needUpdate = HitTest() because we
2757 // are checking more than one object, and that way of doing will
2758 // not return consistent results.
2759 if (HitTest(cObj, point) == true)
2761 //printf("HitTest::OTContainer, subobj ($%llX) hit!\n", cObj);
2763 // c->hitObject = true;
2764 // c->clicked = cObj;
2765 // c->hovered = true;
2768 // Same reasons for doing it this way here apply.
2769 if (cObj->hitObject == true)
2771 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2772 c->hitObject = true;
2776 if (cObj->hitPoint[0] == true)
2778 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2779 c->hitPoint[0] = true;
2783 if (cObj->hitPoint[1] == true)
2785 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2786 c->hitPoint[1] = true;
2790 if (cObj->hitPoint[2] == true)
2792 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2793 c->hitPoint[2] = true;
2797 if (cObj->hovered == true)
2798 c->hovered = true;//*/
2812 bool DrawingView::HandleObjectClicked(void)
2814 if (dragged->type == OTDimension)
2816 Dimension * d = (Dimension *)dragged;
2820 // Hit the "flip sides" switch, so flip 'em
2821 Point temp = d->p[0];
2826 else if (d->hitPoint[3])
2828 // There are three cases here: aligned, horizontal, & vertical.
2829 // Aligned and horizontal do the same thing, vertical goes back to
2831 if (d->subtype == DTLinearVert)
2832 d->subtype = DTLinear;
2834 d->subtype = DTLinearVert;
2838 else if (d->hitPoint[4])
2840 // There are three cases here: aligned, horizontal, & vertical.
2841 // Aligned and vertical do the same thing, horizontal goes back to
2843 if (d->subtype == DTLinearHorz)
2844 d->subtype = DTLinear;
2846 d->subtype = DTLinearHorz;
2856 void DrawingView::HandleObjectMovement(Point point)
2858 Point delta = point - oldPoint;
2859 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
2860 // Object * obj = (Object *)hover[0];
2861 Object * obj = dragged;
2862 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
2863 //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"));
2868 if (obj->hitPoint[0])
2870 if (Global::fixedLength)
2872 Vector line = point - obj->p[1];
2873 Vector unit = line.Unit();
2874 point = obj->p[1] + (unit * obj->length);
2879 else if (obj->hitPoint[1])
2881 if (Global::fixedLength)
2883 Vector line = point - obj->p[0];
2884 Vector unit = line.Unit();
2885 point = obj->p[0] + (unit * obj->length);
2890 else if (obj->hitObject)
2899 if (obj->hitPoint[0])
2901 else if (obj->hitObject)
2903 double oldRadius = obj->length;
2904 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
2906 QString text = QObject::tr("Radius: %1\nScale: %2%");
2907 informativeText = text.arg(obj->radius[0], 0, 'd', 4).arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0);
2913 if (obj->hitPoint[0])
2915 else if (obj->hitPoint[1])
2917 // Change the Arc's span (handle #1)
2920 double angle = Vector::Angle(obj->p[0], point);
2921 double delta = angle - obj->angle[0];
2926 obj->angle[1] -= delta;
2927 obj->angle[0] = angle;
2929 if (obj->angle[1] < 0)
2930 obj->angle[1] += TAU;
2932 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
2933 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);
2937 double angle = Vector::Angle(obj->p[0], point);
2938 obj->angle[0] = angle;
2939 QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
2940 informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 4);
2942 else if (obj->hitPoint[2])
2944 // Change the Arc's span (handle #2)
2947 double angle = Vector::Angle(obj->p[0], point);
2948 obj->angle[1] = angle - obj->angle[0];
2950 if (obj->angle[1] < 0)
2951 obj->angle[1] += TAU;
2953 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
2954 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);
2958 double angle = Vector::Angle(obj->p[0], point);
2959 obj->angle[0] = angle - obj->angle[1];
2961 if (obj->angle[0] < 0)
2962 obj->angle[0] += TAU;
2964 QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
2965 informativeText = text.arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 4);
2967 else if (obj->hitObject)
2974 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
2975 QString text = QObject::tr("Radius: %1");
2976 informativeText = text.arg(obj->radius[0], 0, 'd', 4);
2982 if (obj->hitPoint[0])
2984 else if (obj->hitPoint[1])
2986 else if (obj->hitObject)
2988 // Move measurement lines in/out
2991 Dimension * d = (Dimension *)obj;
2992 double dist = Geometry::DistanceToLineFromPoint(d->lp[0], d->lp[1], point);
2993 float scaledThickness = Global::scale * obj->thickness;
2994 // Looks like offset is 0 to +MAX, but line is at 10.0. So
2995 // anything less than 10.0 should set the offset to 0.
2998 if (dist > (10.0 * scaledThickness))
2999 d->offset = dist - (10.0 * scaledThickness);
3017 // This is shitty, but works for now until I can code up something
3020 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.
3022 // TranslateObject(obj, delta);
3023 TranslateContainer((Container *)obj, point, delta);
3032 void DrawingView::AddDimensionTo(void * o)
3034 Object * obj = (Object *)o;
3039 document.Add(new Dimension(obj->p[0], obj->p[1]));