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);
2129 // Need to convert this, since it's in Qt coordinates (for wheelEvent)
2130 oldPoint = Painter::QtToCartesianCoords(oldPoint);
2135 void DrawingView::wheelEvent(QWheelEvent * event)
2137 double zoomFactor = 1.20;
2138 scrollWheelSeen = true;
2140 if (event->angleDelta().y() < 0)
2142 if (Global::zoom > 400.0)
2145 Global::zoom *= zoomFactor;
2149 if (Global::zoom < 0.125)
2152 Global::zoom /= zoomFactor;
2155 Point np = Painter::QtToCartesianCoords(oldScrollPoint);
2156 Global::origin += (oldPoint - np);
2158 emit(NeedZoomUpdate());
2162 void DrawingView::keyPressEvent(QKeyEvent * event)
2164 bool oldShift = shiftDown;
2165 bool oldCtrl = ctrlDown;
2166 bool oldAlt = altDown;
2168 if (event->key() == Qt::Key_Shift)
2170 else if (event->key() == Qt::Key_Control)
2172 else if (event->key() == Qt::Key_Alt)
2175 // If there's a change in any of the modifier key states, pass it on to
2176 // the current tool's handler
2177 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
2180 ToolHandler(ToolKeyDown, Point(0, 0));
2185 if (oldAlt != altDown)
2188 setCursor(Qt::SizeAllCursor);
2189 oldPoint = oldScrollPoint;
2192 if (select.size() > 0)
2194 if (event->key() == Qt::Key_Up)
2196 TranslateObjects(select, Point(0, +1.0));
2199 else if (event->key() == Qt::Key_Down)
2201 TranslateObjects(select, Point(0, -1.0));
2204 else if (event->key() == Qt::Key_Right)
2206 TranslateObjects(select, Point(+1.0, 0));
2209 else if (event->key() == Qt::Key_Left)
2211 TranslateObjects(select, Point(-1.0, 0));
2218 void DrawingView::keyReleaseEvent(QKeyEvent * event)
2220 bool oldShift = shiftDown;
2221 bool oldCtrl = ctrlDown;
2222 bool oldAlt = altDown;
2224 if (event->key() == Qt::Key_Shift)
2226 else if (event->key() == Qt::Key_Control)
2228 else if (event->key() == Qt::Key_Alt)
2231 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
2234 ToolHandler(ToolKeyUp, Point(0, 0));
2239 if (oldAlt != altDown)
2242 setCursor(Qt::ArrowCursor);
2248 // This looks strange, but it's really quite simple: We want a point that's
2249 // more than half-way to the next grid point to snap there while conversely we
2250 // want a point that's less than half-way to to the next grid point then snap
2251 // to the one before it. So we add half of the grid spacing to the point, then
2252 // divide by it so that we can remove the fractional part, then multiply it
2253 // back to get back to the correct answer.
2255 Point DrawingView::SnapPointToGrid(Point point)
2257 point += Global::gridSpacing / 2.0; // *This* adds to Z!!!
2258 point /= Global::gridSpacing;
2259 point.x = floor(point.x);//need to fix this for negative numbers...
2260 point.y = floor(point.y);
2261 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
2262 point *= Global::gridSpacing;
2267 Point DrawingView::SnapPointToAngle(Point point)
2269 // Snap to a single digit angle (using toolpoint #1 as the center)
2270 double angle = Vector::Angle(toolPoint[0], point);
2271 double length = Vector::Magnitude(toolPoint[0], point);
2273 // Convert from radians to degrees
2274 double degAngle = angle * RADIANS_TO_DEGREES;
2275 double snapAngle = (double)((int)(degAngle + 0.5));
2278 v.SetAngleAndLength(snapAngle * DEGREES_TO_RADIANS, length);
2279 point = toolPoint[0] + v;
2285 Rect DrawingView::GetObjectExtents(Object * obj)
2287 // Default to empty rect, if object checks below fail for some reason
2295 rect = Rect(obj->p[0], obj->p[1]);
2301 rect = Rect(obj->p[0], obj->p[0]);
2302 rect.Expand(obj->radius[0]);
2308 Arc * a = (Arc *)obj;
2310 double start = a->angle[0];
2311 double end = start + a->angle[1];
2312 rect = Rect(Point(cos(start), sin(start)), Point(cos(end), sin(end)));
2314 // If the end of the arc is before the beginning, add 360 degrees to it
2318 // Adjust the bounds depending on which axes are crossed
2319 if ((start < QTR_TAU) && (end > QTR_TAU))
2322 if ((start < HALF_TAU) && (end > HALF_TAU))
2325 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
2328 if ((start < TAU) && (end > TAU))
2331 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
2334 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
2337 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
2340 rect *= a->radius[0];
2341 rect.Translate(a->p[0]);
2347 Text * t = (Text *)obj;
2348 rect = Rect(t->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
2354 Container * c = (Container *)obj;
2355 VPVectorIter i = c->objects.begin();
2356 rect = GetObjectExtents((Object *)*i);
2359 for(; i!=c->objects.end(); i++)
2360 rect |= GetObjectExtents((Object *)*i);
2371 void DrawingView::CheckObjectBounds(void)
2375 for(i=document.objects.begin(); i!=document.objects.end(); i++)
2377 Object * obj = (Object *)(*i);
2378 obj->selected = false;
2385 Line * l = (Line *)obj;
2387 if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
2395 Circle * c = (Circle *)obj;
2397 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]))
2405 Arc * a = (Arc *)obj;
2407 double start = a->angle[0];
2408 double end = start + a->angle[1];
2409 QPointF p1(cos(start), sin(start));
2410 QPointF p2(cos(end), sin(end));
2411 QRectF bounds(p1, p2);
2414 // Swap X/Y coordinates if they're backwards...
2415 if (bounds.left() > bounds.right())
2417 double temp = bounds.left();
2418 bounds.setLeft(bounds.right());
2419 bounds.setRight(temp);
2422 if (bounds.bottom() > bounds.top())
2424 double temp = bounds.bottom();
2425 bounds.setBottom(bounds.top());
2426 bounds.setTop(temp);
2429 // Doesn't work as advertised! For shame!
2430 bounds = bounds.normalized();
2433 // If the end of the arc is before the beginning, add 360 degrees
2438 // Adjust the bounds depending on which axes are crossed
2439 if ((start < QTR_TAU) && (end > QTR_TAU))
2442 if ((start < HALF_TAU) && (end > HALF_TAU))
2443 bounds.setLeft(-1.0);
2445 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
2446 bounds.setBottom(-1.0);
2448 if ((start < TAU) && (end > TAU))
2449 bounds.setRight(1.0);
2451 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
2454 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
2455 bounds.setLeft(-1.0);
2457 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
2458 bounds.setBottom(-1.0);
2460 bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
2461 bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
2462 bounds.translate(a->p[0].x, a->p[0].y);
2464 if (Global::selection.contains(bounds))
2472 Text * t = (Text *)obj;
2473 Rect r(obj->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
2475 if (Global::selection.contains(r.l, r.t) && Global::selection.contains(r.r, r.b))
2483 Container * c = (Container *)obj;
2485 if (Global::selection.contains(c->p[0].x, c->p[0].y) && Global::selection.contains(c->p[1].x, c->p[1].y))
2498 bool DrawingView::HitTestObjects(Point point)
2502 bool needUpdate = false;
2503 hoverPointValid = false;
2505 for(i=document.objects.begin(); i!=document.objects.end(); i++)
2507 Object * obj = (Object *)(*i);
2509 // If we're seeing the object we're dragging, skip it
2510 if (draggingObject && (obj == dragged))
2513 if (HitTest(obj, point) == true)
2519 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
2520 emit ObjectHovered(obj);
2528 bool DrawingView::HitTest(Object * obj, Point point)
2530 bool needUpdate = false;
2536 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
2537 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
2538 Vector lineSegment = obj->p[1] - obj->p[0];
2539 Vector v1 = point - obj->p[0];
2540 Vector v2 = point - obj->p[1];
2541 double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
2545 distance = v1.Magnitude();
2547 distance = v2.Magnitude();
2549 // distance = ?Det?(ls, v1) / |ls|
2550 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
2551 / lineSegment.Magnitude());
2553 if ((v1.Magnitude() * Global::zoom) < 8.0)
2555 obj->hitPoint[0] = true;
2556 hoverPoint = obj->p[0];
2557 hoverPointValid = true;
2559 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2561 obj->hitPoint[1] = true;
2562 hoverPoint = obj->p[1];
2563 hoverPointValid = true;
2565 else if ((distance * Global::zoom) < 5.0)
2566 obj->hitObject = true;
2568 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
2570 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
2578 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
2579 obj->hitPoint[0] = obj->hitObject = false;
2580 double length = Vector::Magnitude(obj->p[0], point);
2582 if ((length * Global::zoom) < 8.0)
2584 obj->hitPoint[0] = true;
2585 hoverPoint = obj->p[0];
2586 hoverPointValid = true;
2588 else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
2589 obj->hitObject = true;
2591 obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
2593 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
2601 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
2602 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
2603 double length = Vector::Magnitude(obj->p[0], point);
2604 double angle = Vector::Angle(obj->p[0], point);
2606 // Make sure we get the angle in the correct spot
2607 if (angle < obj->angle[0])
2610 // Get the span that we're pointing at...
2611 double span = angle - obj->angle[0];
2613 // N.B.: Still need to hit test the arc start & arc span handles...
2614 double spanAngle = obj->angle[0] + obj->angle[1];
2615 Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
2616 Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
2617 double length2 = Vector::Magnitude(point, handle1);
2618 double length3 = Vector::Magnitude(point, handle2);
2620 if ((length * Global::zoom) < 8.0)
2622 obj->hitPoint[0] = true;
2623 hoverPoint = obj->p[0];
2624 hoverPointValid = true;
2626 else if ((length2 * Global::zoom) < 8.0)
2628 obj->hitPoint[1] = true;
2629 hoverPoint = handle1;
2630 hoverPointValid = true;
2632 else if ((length3 * Global::zoom) < 8.0)
2634 obj->hitPoint[2] = true;
2635 hoverPoint = handle2;
2636 hoverPointValid = true;
2638 else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
2639 obj->hitObject = true;
2641 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
2643 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
2651 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHP3 = obj->hitPoint[3], oldHP4 = obj->hitPoint[4], oldHO = obj->hitObject;
2652 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitPoint[3] = obj->hitPoint[4] = obj->hitObject = false;
2654 Dimension * d = (Dimension *)obj;
2656 Vector orthogonal = Vector::Normal(d->lp[0], d->lp[1]);
2657 // Get our line parallel to our points
2658 float scaledThickness = Global::scale * obj->thickness;
2660 Point p1 = d->lp[0] + (orthogonal * (d->offset + (10.0 * scaledThickness)));
2661 Point p2 = d->lp[1] + (orthogonal * (d->offset + (10.0 * scaledThickness)));
2663 Point p1 = d->lp[0] + (orthogonal * (10.0 + d->offset) * scaledThickness);
2664 Point p2 = d->lp[1] + (orthogonal * (10.0 + d->offset) * scaledThickness);
2666 Point p3(p1, point);
2668 Vector v1(d->p[0], point);
2669 Vector v2(d->p[1], point);
2670 Vector lineSegment(p1, p2);
2671 double t = Geometry::ParameterOfLineAndPoint(p1, p2, point);
2673 Point midpoint = (p1 + p2) / 2.0;
2674 Point hFSPoint = Point(midpoint, point);
2675 Point hCS1Point = Point((p1 + midpoint) / 2.0, point);
2676 Point hCS2Point = Point((midpoint + p2) / 2.0, point);
2679 distance = v1.Magnitude();
2681 distance = v2.Magnitude();
2683 // distance = ?Det?(ls, v1) / |ls|
2684 distance = fabs((lineSegment.x * p3.y - p3.x * lineSegment.y)
2685 / lineSegment.Magnitude());
2687 if ((v1.Magnitude() * Global::zoom) < 8.0)
2688 obj->hitPoint[0] = true;
2689 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2690 obj->hitPoint[1] = true;
2691 else if ((distance * Global::zoom) < 5.0)
2692 obj->hitObject = true;
2694 if ((hFSPoint.Magnitude() * Global::zoom) < 8.0)
2695 obj->hitPoint[2] = true;
2696 else if ((hCS1Point.Magnitude() * Global::zoom) < 8.0)
2697 obj->hitPoint[3] = true;
2698 else if ((hCS2Point.Magnitude() * Global::zoom) < 8.0)
2699 obj->hitPoint[4] = true;
2701 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitPoint[3] || obj->hitPoint[4] || obj->hitObject ? true : false);
2703 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHP3 != obj->hitPoint[3]) || (oldHP4 != obj->hitPoint[4]) || (oldHO != obj->hitObject))
2711 Text * t = (Text *)obj;
2712 bool oldHO = obj->hitObject;
2713 obj->hitObject = false;
2715 Rect r(obj->p[0], Point(obj->p[0].x + t->extents.Width(), obj->p[0].y - t->extents.Height()));
2716 //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);
2718 if (r.Contains(point))
2719 obj->hitObject = true;
2721 obj->hovered = (obj->hitObject ? true : false);
2723 if (oldHO != obj->hitObject)
2731 // Containers must be recursively tested... Or do they???
2733 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.
2735 // bool oldHitObj = c->hitObject, oldHovered = c->hovered;
2736 // Object * oldClicked = c->clicked;
2738 still need to compare old state to new state, and set things up based upon that...
2739 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);
2740 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.
2742 Container * c = (Container *)obj;
2743 c->hitObject = false;
2747 VPVector flat = Flatten(c);
2749 //printf("HitTest::OTContainer (size=%li)\n", flat.size());
2750 for(VPVectorIter i=flat.begin(); i!=flat.end(); i++)
2752 Object * cObj = (Object *)(*i);
2754 // Skip the flattened containers (if any)...
2755 if (cObj->type == OTContainer)
2758 // We do it this way instead of needUpdate = HitTest() because we
2759 // are checking more than one object, and that way of doing will
2760 // not return consistent results.
2761 if (HitTest(cObj, point) == true)
2763 //printf("HitTest::OTContainer, subobj ($%llX) hit!\n", cObj);
2765 // c->hitObject = true;
2766 // c->clicked = cObj;
2767 // c->hovered = true;
2770 // Same reasons for doing it this way here apply.
2771 if (cObj->hitObject == true)
2773 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2774 c->hitObject = true;
2778 if (cObj->hitPoint[0] == true)
2780 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2781 c->hitPoint[0] = true;
2785 if (cObj->hitPoint[1] == true)
2787 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2788 c->hitPoint[1] = true;
2792 if (cObj->hitPoint[2] == true)
2794 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2795 c->hitPoint[2] = true;
2799 if (cObj->hovered == true)
2800 c->hovered = true;//*/
2814 bool DrawingView::HandleObjectClicked(void)
2816 if (dragged->type == OTDimension)
2818 Dimension * d = (Dimension *)dragged;
2822 // Hit the "flip sides" switch, so flip 'em
2823 Point temp = d->p[0];
2828 else if (d->hitPoint[3])
2830 // There are three cases here: aligned, horizontal, & vertical.
2831 // Aligned and horizontal do the same thing, vertical goes back to
2833 if (d->subtype == DTLinearVert)
2834 d->subtype = DTLinear;
2836 d->subtype = DTLinearVert;
2840 else if (d->hitPoint[4])
2842 // There are three cases here: aligned, horizontal, & vertical.
2843 // Aligned and vertical do the same thing, horizontal goes back to
2845 if (d->subtype == DTLinearHorz)
2846 d->subtype = DTLinear;
2848 d->subtype = DTLinearHorz;
2858 void DrawingView::HandleObjectMovement(Point point)
2860 Point delta = point - oldPoint;
2861 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
2862 // Object * obj = (Object *)hover[0];
2863 Object * obj = dragged;
2864 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
2865 //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"));
2870 if (obj->hitPoint[0])
2872 if (Global::fixedLength)
2874 Vector line = point - obj->p[1];
2875 Vector unit = line.Unit();
2876 point = obj->p[1] + (unit * obj->length);
2881 else if (obj->hitPoint[1])
2883 if (Global::fixedLength)
2885 Vector line = point - obj->p[0];
2886 Vector unit = line.Unit();
2887 point = obj->p[0] + (unit * obj->length);
2892 else if (obj->hitObject)
2901 if (obj->hitPoint[0])
2903 else if (obj->hitObject)
2905 double oldRadius = obj->length;
2906 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
2908 QString text = QObject::tr("Radius: %1\nScale: %2%");
2909 informativeText = text.arg(obj->radius[0], 0, 'd', 4).arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0);
2915 if (obj->hitPoint[0])
2917 else if (obj->hitPoint[1])
2919 // Change the Arc's span (handle #1)
2922 double angle = Vector::Angle(obj->p[0], point);
2923 double delta = angle - obj->angle[0];
2928 obj->angle[1] -= delta;
2929 obj->angle[0] = angle;
2931 if (obj->angle[1] < 0)
2932 obj->angle[1] += TAU;
2934 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
2935 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);
2939 double angle = Vector::Angle(obj->p[0], point);
2940 obj->angle[0] = angle;
2941 QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
2942 informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 4);
2944 else if (obj->hitPoint[2])
2946 // Change the Arc's span (handle #2)
2949 double angle = Vector::Angle(obj->p[0], point);
2950 obj->angle[1] = angle - obj->angle[0];
2952 if (obj->angle[1] < 0)
2953 obj->angle[1] += TAU;
2955 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
2956 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);
2960 double angle = Vector::Angle(obj->p[0], point);
2961 obj->angle[0] = angle - obj->angle[1];
2963 if (obj->angle[0] < 0)
2964 obj->angle[0] += TAU;
2966 QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
2967 informativeText = text.arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 4);
2969 else if (obj->hitObject)
2976 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
2977 QString text = QObject::tr("Radius: %1");
2978 informativeText = text.arg(obj->radius[0], 0, 'd', 4);
2984 if (obj->hitPoint[0])
2986 else if (obj->hitPoint[1])
2988 else if (obj->hitObject)
2990 // Move measurement lines in/out
2993 Dimension * d = (Dimension *)obj;
2994 double dist = Geometry::DistanceToLineFromPoint(d->lp[0], d->lp[1], point);
2995 float scaledThickness = Global::scale * obj->thickness;
2996 // Looks like offset is 0 to +MAX, but line is at 10.0. So
2997 // anything less than 10.0 should set the offset to 0.
3000 if (dist > (10.0 * scaledThickness))
3001 d->offset = dist - (10.0 * scaledThickness);
3019 // This is shitty, but works for now until I can code up something
3022 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.
3024 // TranslateObject(obj, delta);
3025 TranslateContainer((Container *)obj, point, delta);
3034 void DrawingView::AddDimensionTo(void * o)
3036 Object * obj = (Object *)o;
3041 document.Add(new Dimension(obj->p[0], obj->p[1]));