3 // Part of the Architektonas Project
4 // (C) 2011 Underground Software
5 // See the README and GPLv3 files for licensing and warranty information
7 // JLH = James Hammons <jlhamm@acm.org>
10 // --- ---------- -------------------------------------------------------------
11 // JLH 03/22/2011 Created this file
12 // JLH 09/29/2011 Added middle mouse button panning
17 // - Redo rendering code to *not* use Qt's transform functions, as they are tied
18 // to a left-handed system and we need a right-handed one. [DONE]
25 // Uncomment this for debugging...
27 //#define DEBUGFOO // Various tool debugging...
28 //#define DEBUGTP // Toolpalette debugging...
30 #include "drawingview.h"
35 #include "mathconstants.h"
41 #define BACKGROUND_MAX_SIZE 512
44 //Container DrawingView::document(Vector(0, 0));
47 DrawingView::DrawingView(QWidget * parent/*= NULL*/): QWidget(parent),
48 // The value in the settings file will override this.
49 useAntialiasing(true), numSelected(0), numHovered(0), shiftDown(false),
51 gridBackground(BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE),
52 scale(1.0), offsetX(-10), offsetY(-10),// document(Vector(0, 0)),
53 gridPixels(0), collided(false)//, toolAction(NULL)
55 // document.isTopLevelContainer = true;
56 //wtf? doesn't work except in c++11??? document = { 0 };
57 setBackgroundRole(QPalette::Base);
58 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
60 Global::gridSpacing = 12.0; // In base units (inch is default)
63 Line * line = new Line(Vector(5, 5), Vector(50, 40), &document);
65 document.Add(new Line(Vector(50, 40), Vector(10, 83), &document));
66 document.Add(new Line(Vector(10, 83), Vector(17, 2), &document));
67 document.Add(new Circle(Vector(100, 100), 36, &document));
68 document.Add(new Circle(Vector(50, 150), 49, &document));
69 document.Add(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3, &document)),
70 document.Add(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5, &document));
72 Dimension * dimension = new Dimension(Vector(0, 0), Vector(0, 0), DTLinear, &document);
73 line->SetDimensionOnLine(dimension);
74 document.Add(dimension);
76 // Alternate way to do the above...
77 line->SetDimensionOnLine();
80 Line * line = new Line;//(Vector(5, 5), Vector(50, 40), &document);
81 line->p[0] = Vector(5, 5);
82 line->p[1] = Vector(50, 40);
84 line->thickness = 2.0;
86 line->color = 0xFF7F00;
87 document.objects.push_back(line);
88 document.objects.push_back(new Line(Vector(50, 40), Vector(10, 83)));
89 document.objects.push_back(new Line(Vector(10, 83), Vector(17, 2)));
90 document.objects.push_back(new Circle(Vector(100, 100), 36));
91 document.objects.push_back(new Circle(Vector(50, 150), 49));
92 document.objects.push_back(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3)),
93 document.objects.push_back(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5));
94 document.objects.push_back(new Dimension(Vector(50, 40), Vector(5, 5)));
95 document.objects.push_back(new Text(Vector(10, 83), "Here is some awesome text!"));
99 Here we set the grid size in pixels--12 in this case. Initially, we have our
100 zoom set to make this represent 12 inches at a zoom factor of 25%. (This is
101 arbitrary.) So, to be able to decouple the grid size from the zoom, we need
102 to be able to set the size of the background grid (which we do here at an
103 arbitrary 12 pixels) to anything we want (within reason, of course :-).
105 The drawing enforces the grid spacing through the drawing->gridSpacing variable.
107 drawing->gridSpacing = 12.0 / Global::zoom;
109 Global::zoom is the zoom factor for the drawing, and all mouse clicks are
110 translated to Cartesian coordinates through this. (Initially, Global::zoom is
111 set to 1.0. SCREEN_ZOOM is set to 1.0/4.0.)
113 Really, the 100% zoom level can be set at *any* zoom level, it's more of a
114 convenience function than any measure of absolutes. Doing things that way we
115 could rid ourselves of the whole SCREEN_ZOOM parameter and all the attendant
116 shittiness that comes with it.
118 However, it seems that SCREEN_ZOOM is used to make text and arrow sizes show up
119 a certain way, which means we should probably create something else in those
120 objects to take its place--like some kind of scale factor. This would seem to
121 imply that certain point sizes actually *do* tie things like fonts to absolute
122 sizes on the screen, but not necessarily because you could have an inch scale
123 with text that is quite small relative to other objects on the screen, which
124 currently you have to zoom in to see (and which blows up the text). Point sizes
125 in an application like this are a bit meaningless; even though an inch is an
126 inch regardless of the zoom level a piece of text can be larger or smaller than
127 this. Maybe this is the case for having a base unit and basing point sizes off
130 Here's what's been figured out. Global::zoom is simply the ratio of pixels to
131 base units. What that means is that if you have a 12px grid with a 6" grid size
132 (& base unit of "inches"), Global::zoom becomes 12px / 6" = 2.0 px/in.
134 Dimensions now have a "size" parameter to set their absolute size in relation
135 to the base unit. ATM, the arrows are drawn in pixels, but also scaled by
136 Global::zoom *and* size. Same with the dimension text; it's drawn at 10pt and
137 scaled the same way as the arrowheads.
139 Need a way to scale line widths as well. :-/ Shouldn't be too difficult, just
140 need a thickness parameter similar to the "size" param for dimensions. (And now
144 SetGridSize(12); // This is in pixels
149 void DrawingView::SetToolActive(Action * action)
154 connect(toolAction, SIGNAL(ObjectReady(Object *)), this,
155 SLOT(AddNewObjectToDocument(Object *)));
156 connect(toolAction, SIGNAL(NeedRefresh()), this, SLOT(HandleActionUpdate()));
162 void DrawingView::SetGridSize(uint32_t size)
165 if (size == gridPixels)
168 // Recreate the background bitmap
170 QPainter pmp(&gridBackground);
171 pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
172 pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
174 for(int i=0; i<(BACKGROUND_MAX_SIZE-1); i+=gridPixels)
176 pmp.drawLine(i, 0, i, BACKGROUND_MAX_SIZE - 1);
177 pmp.drawLine(0, i, BACKGROUND_MAX_SIZE - 1, i);
182 // Set up new BG brush & zoom level (pixels per base unit)
183 // Painter::zoom = gridPixels / gridSpacing;
184 Global::zoom = gridPixels / Global::gridSpacing;
185 UpdateGridBackground();
189 void DrawingView::UpdateGridBackground(void)
191 // Transform the origin to Qt coordinates
192 Vector pixmapOrigin = Painter::CartesianToQtCoords(Vector());
193 int x = (int)pixmapOrigin.x;
194 int y = (int)pixmapOrigin.y;
195 // Use mod arithmetic to grab the correct swatch of background
197 Negative numbers still screw it up... Need to think about what we're
198 trying to do here. The fact that it worked with 72 seems to have been pure luck.
199 It seems the problem is negative numbers: We can't let that happen.
200 When taking away the zero, it pops over 1 px at zero, then goes about 1/2 a
203 The bitmap looks like this:
213 @ x = 1, we want it to look like:
215 -+---+---+---+---+---
218 -+---+---+---+---+---
223 Which means we need to grab the sample from x = 3. @ x = -1:
233 Which means we need to grab the sample from x = 1. Which means we have to take
234 the mirror of the modulus of gridPixels.
236 Doing a mod of a negative number is problematic: 1st, the compiler converts the
237 negative number to an unsigned int, then it does the mod. Gets you wrong answers
238 most of the time, unless you use a power of 2. :-P So what we do here is just
239 take the modulus of the negation, which means we don't have to worry about
242 The positive case looks gruesome (and it is) but it boils down to this: We take
243 the modulus of the X coordinate, then mirror it by subtraction from the
244 maximum (in this case, gridPixels). This gives us a number in the range of 1 to
245 gridPixels. But we need the case where the result equalling gridPixels to be
246 zero; so we do another modulus operation on the result to achieve this.
251 x = (gridPixels - (x % gridPixels)) % gridPixels;
256 y = (gridPixels - (y % gridPixels)) % gridPixels;
258 // Here we grab a section of the bigger pixmap, so that the background
259 // *looks* like it's scrolling...
260 QPixmap pm = gridBackground.copy(x, y, gridPixels, gridPixels);
261 QPalette pal = palette();
262 pal.setBrush(backgroundRole(), QBrush(pm));
263 setAutoFillBackground(true);
268 void DrawingView::SetCurrentLayer(int layer)
270 Global::currentLayer = layer;
271 //printf("DrawingView::CurrentLayer = %i\n", layer);
275 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
277 // This is undoing the transform, e.g. going from client coords to local coords.
278 // In essence, the height - y is height + (y * -1), the (y * -1) term doing the
279 // conversion of the y-axis from increasing bottom to top.
280 return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
284 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
286 // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
287 // No voodoo here, it's just grouped wrong to see it. It should be:
288 // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive
289 return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
293 void DrawingView::paintEvent(QPaintEvent * /*event*/)
295 QPainter qtPainter(this);
296 Painter painter(&qtPainter);
299 qtPainter.setRenderHint(QPainter::Antialiasing);
301 Global::viewportHeight = size().height();
303 // Draw coordinate axes
304 painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
305 painter.DrawLine(0, -16384, 0, 16384);
306 painter.DrawLine(-16384, 0, 16384, 0);
308 // Do object rendering...
309 RenderObjects(&painter, document.objects);
311 // Do tool rendering, if any...
314 painter.SetPen(QPen(QColor(200, 100, 0, 255), 1.0, Qt::DashLine));
315 painter.DrawCrosshair(oldPoint);
319 // Do selection rectangle rendering, if any
320 if (Global::selectionInProgress)
322 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
323 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
324 painter.DrawRect(Global::selection);
330 // Renders objects in the passed in vector
332 void DrawingView::RenderObjects(Painter * painter, std::vector<void *> & v)
334 std::vector<void *>::iterator i;
336 for(i=v.begin(); i!=v.end(); i++)
338 Object * obj = (Object *)(*i);
339 float scaledThickness = Global::scale * obj->thickness;
341 if ((Global::tool == TTRotate) && ctrlDown && obj->selected)
343 painter->SetPen(0x00FF00, 2.0, LSSolid);
347 painter->SetPen(obj->color, Global::zoom * scaledThickness, obj->style);
348 painter->SetBrush(obj->color);
350 if (obj->selected || obj->hitObject)
351 painter->SetPen(0xFF0000, Global::zoom * scaledThickness, LSDash);
358 Line * l = (Line *)obj;
359 painter->DrawLine(l->p[0], l->p[1]);
362 painter->DrawHandle(l->p[0]);
365 painter->DrawHandle(l->p[1]);
371 Circle * ci = (Circle *)obj;
372 painter->SetBrush(QBrush(Qt::NoBrush));
373 painter->DrawEllipse(ci->p[0], ci->radius[0], ci->radius[0]);
378 Arc * a = (Arc *)obj;
379 painter->DrawArc(a->p[0], a->radius[0], a->angle[0], a->angle[1]);
384 Dimension * d = (Dimension *)obj;
386 Vector v(d->p[0], d->p[1]);
387 double angle = v.Angle();
388 Vector unit = v.Unit();
389 Vector linePt1 = d->p[0], linePt2 = d->p[1];
391 double x1, y1, length;
393 if (d->subtype == DTLinearVert)
395 if ((angle < 0) || (angle > PI))
397 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
398 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
399 ortho = Vector(1.0, 0);
404 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
405 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
406 ortho = Vector(-1.0, 0);
410 linePt1.x = linePt2.x = x1;
411 length = fabs(d->p[0].y - d->p[1].y);
413 else if (d->subtype == DTLinearHorz)
415 if ((angle < PI_OVER_2) || (angle > PI3_OVER_2))
417 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
418 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
419 ortho = Vector(0, 1.0);
424 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
425 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
426 ortho = Vector(0, -1.0);
430 linePt1.y = linePt2.y = y1;
431 length = fabs(d->p[0].x - d->p[1].x);
433 else if (d->subtype == DTLinear)
435 angle = Vector(linePt1, linePt2).Angle();
436 ortho = Vector::Normal(linePt1, linePt2);
437 length = v.Magnitude();
440 unit = Vector(linePt1, linePt2).Unit();
442 Point p1 = linePt1 + (ortho * 10.0 * scaledThickness);
443 Point p2 = linePt2 + (ortho * 10.0 * scaledThickness);
444 Point p3 = linePt1 + (ortho * 16.0 * scaledThickness);
445 Point p4 = linePt2 + (ortho * 16.0 * scaledThickness);
446 Point p5 = d->p[0] + (ortho * 4.0 * scaledThickness);
447 Point p6 = d->p[1] + (ortho * 4.0 * scaledThickness);
450 The numbers hardcoded into here, what are they?
451 I believe they are pixels.
453 // Draw extension lines (if certain type)
454 painter->DrawLine(p3, p5);
455 painter->DrawLine(p4, p6);
457 // Calculate whether or not the arrowheads are too crowded to put inside
458 // the extension lines. 9.0 is the length of the arrowhead.
459 double t = Geometry::ParameterOfLineAndPoint(linePt1, linePt2, linePt2 - (unit * 9.0 * scaledThickness));
460 //printf("Dimension::Draw(): t = %lf\n", t);
462 // On the screen, it's acting like this is actually 58%...
463 // This is correct, we want it to happen at > 50%
466 // Draw main dimension line + arrowheads
467 painter->DrawLine(p1, p2);
468 painter->DrawArrowhead(p1, p2, scaledThickness);
469 painter->DrawArrowhead(p2, p1, scaledThickness);
473 // Draw outside arrowheads
474 Point p7 = p1 - (unit * 9.0 * scaledThickness);
475 Point p8 = p2 + (unit * 9.0 * scaledThickness);
476 painter->DrawArrowhead(p1, p7, scaledThickness);
477 painter->DrawArrowhead(p2, p8, scaledThickness);
478 painter->DrawLine(p1, p1 - (unit * 14.0 * scaledThickness));
479 painter->DrawLine(p2, p2 + (unit * 14.0 * scaledThickness));
482 // Draw length of dimension line...
483 painter->SetFont(QFont("Arial", 8.0 * Global::zoom * scaledThickness));
484 Point ctr = p2 + (Vector(p2, p1) / 2.0);
487 QString dimText = QString("%1\"").arg(Vector(endpoint - position).Magnitude());
492 dimText = QString("%1\"").arg(length);
495 double feet = (double)((int)length / 12);
496 double inches = length - (feet * 12.0);
499 dimText = QString("%1'").arg(feet);
501 dimText = QString("%1' %2\"").arg(feet).arg(inches);
505 painter->DrawAngledText(ctr, angle, dimText, scaledThickness);
511 Text * t = (Text *)obj;
512 painter->DrawTextObject(t->p[0], t->s.c_str(), scaledThickness);
522 void DrawingView::AddHoveredToSelection(void)
524 std::vector<void *>::iterator i;
526 for(i=document.objects.begin(); i!=document.objects.end(); i++)
528 if (((Object *)(*i))->hovered)
529 ((Object *)(*i))->selected = true;
534 void DrawingView::GetSelection(std::vector<void *> & v)
537 std::vector<void *>::iterator i;
539 for(i=document.objects.begin(); i!=document.objects.end(); i++)
541 if (((Object *)(*i))->selected)
547 void DrawingView::GetHovered(std::vector<void *> & v)
550 std::vector<void *>::iterator i;
552 for(i=document.objects.begin(); i!=document.objects.end(); i++)
554 if (((Object *)(*i))->hovered)
556 //printf("GetHovered: adding object (%X) to hover... hp1=%s, hp2=%s, hl=%s\n", (*i), (((Line *)(*i))->hitPoint[0] ? "true" : "false"), (((Line *)(*i))->hitPoint[1] ? "true" : "false"), (((Line *)(*i))->hitObject ? "true" : "false"));
563 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
565 Global::screenSize = Vector(size().width(), size().height());
566 UpdateGridBackground();
570 void DrawingView::ToolHandler(int mode, Point p)
572 if (Global::tool == TTLine)
573 LineHandler(mode, p);
574 else if (Global::tool == TTRotate)
575 RotateHandler(mode, p);
579 void DrawingView::ToolDraw(Painter * painter)
581 if (Global::tool == TTLine)
583 if (Global::toolState == TSNone)
585 painter->DrawHandle(toolPoint[0]);
587 else if ((Global::toolState == TSPoint2) && shiftDown)
589 painter->DrawHandle(toolPoint[1]);
593 painter->DrawLine(toolPoint[0], toolPoint[1]);
594 painter->DrawHandle(toolPoint[1]);
596 Vector v(toolPoint[0], toolPoint[1]);
597 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
598 double absLength = v.Magnitude();
599 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
600 text = text.arg(absLength).arg(absAngle);
601 painter->DrawInformativeText(text);
604 else if (Global::tool == TTRotate)
606 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
607 painter->DrawHandle(toolPoint[0]);
608 else if ((Global::toolState == TSPoint2) && shiftDown)
609 painter->DrawHandle(toolPoint[1]);
612 if (toolPoint[0] == toolPoint[1])
615 painter->DrawLine(toolPoint[0], toolPoint[1]);
616 // Likely we need a tool container for this... (now we do!)
620 painter->SetPen(0x00FF00, 2.0, LSSolid);
621 overrideColor = true;
624 RenderObjects(painter, toolObjects);
625 overrideColor = false;
628 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
630 QString text = QChar(0x2221) + QObject::tr(": %1");
631 text = text.arg(absAngle);
636 painter->DrawInformativeText(text);
642 void DrawingView::LineHandler(int mode, Point p)
647 if (Global::toolState == TSNone)
654 if (Global::toolState == TSNone)
661 if (Global::toolState == TSNone)
663 Global::toolState = TSPoint2;
664 // Prevent spurious line from drawing...
665 toolPoint[1] = toolPoint[0];
667 else if ((Global::toolState == TSPoint2) && shiftDown)
669 // Key override is telling us to make a new line, not continue the
671 toolPoint[0] = toolPoint[1];
675 Line * l = new Line(toolPoint[0], toolPoint[1]);
676 document.objects.push_back(l);
677 toolPoint[0] = toolPoint[1];
683 void DrawingView::RotateHandler(int mode, Point p)
688 if (Global::toolState == TSNone)
691 SavePointsFrom(select, toolScratch);
692 Global::toolState = TSPoint1;
694 else if (Global::toolState == TSPoint1)
701 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
703 else if (Global::toolState == TSPoint2)
710 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
711 std::vector<void *>::iterator j = select.begin();
712 std::vector<Object>::iterator i = toolScratch.begin();
714 for(; i!=toolScratch.end(); i++, j++)
717 Point p1 = Geometry::RotatePointAroundPoint(obj.p[0], toolPoint[0], angle);
718 Point p2 = Geometry::RotatePointAroundPoint(obj.p[1], toolPoint[0], angle);
719 Object * obj2 = (Object *)(*j);
723 if (obj.type == OTArc)
725 //printf("Obj2->angle[0] = %f, obj.angle[0] = %f, angle = %f\n", obj2->angle[0], obj.angle[0], angle);
726 obj2->angle[0] = obj.angle[0] + angle;
728 if (obj2->angle[0] > PI_TIMES_2)
729 obj2->angle[0] -= PI_TIMES_2;
736 if (Global::toolState == TSPoint1)
738 Global::toolState = TSPoint2;
739 // Prevent spurious line from drawing...
740 toolPoint[1] = toolPoint[0];
742 else if ((Global::toolState == TSPoint2) && shiftDown)
744 // Key override is telling us to make a new line, not continue the
746 toolPoint[0] = toolPoint[1];
750 // Either we're finished with our rotate, or we're stamping a copy.
753 // Stamp a copy of the selection at the current rotation & bail
754 std::vector<void *> temp;
755 CopyObjects(select, temp);
757 AddObjectsTo(document.objects, temp);
758 RestorePointsTo(select, toolScratch);
763 Global::toolState = TSPoint1;
764 SavePointsFrom(select, toolScratch);
769 // Reset the selection if shift held down...
771 RestorePointsTo(select, toolScratch);
775 // Reset selection when key is let up
777 RotateHandler(ToolMouseMove, toolPoint[1]);
781 RestorePointsTo(select, toolScratch);
786 void DrawingView::mousePressEvent(QMouseEvent * event)
788 if (event->button() == Qt::LeftButton)
790 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
792 // Handle tool processing, if any
795 if (Global::snapToGrid)
796 point = SnapPointToGrid(point);
798 //Also, may want to figure out if hovering over a snap point on an object,
799 //snap to grid if not.
800 // Snap to object point if valid...
801 // if (Global::snapPointIsValid)
802 // point = Global::snapPoint;
804 ToolHandler(ToolMouseDown, point);
808 // Clear the selection only if CTRL isn't being held on click
810 ClearSelected(document.objects);
813 // If any objects are being hovered on click, add them to the selection
817 AddHoveredToSelection();
818 update(); // needed??
819 GetHovered(hover); // prolly needed
821 // Needed for grab & moving objects
822 if (Global::snapToGrid)
823 oldPoint = SnapPointToGrid(point);
828 // Didn't hit any object and not using a tool, so do a selection rectangle
829 Global::selectionInProgress = true;
830 Global::selection.setTopLeft(QPointF(point.x, point.y));
831 Global::selection.setBottomRight(QPointF(point.x, point.y));
833 else if (event->button() == Qt::MiddleButton)
836 oldPoint = Vector(event->x(), event->y());
837 // Should also change the mouse pointer as well...
838 setCursor(Qt::SizeAllCursor);
843 void DrawingView::mouseMoveEvent(QMouseEvent * event)
845 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
846 Global::selection.setBottomRight(QPointF(point.x, point.y));
847 // Only needs to be done here, as mouse down is always preceded by movement
848 Global::snapPointIsValid = false;
851 if (event->buttons() & Qt::MiddleButton)
853 point = Vector(event->x(), event->y());
854 // Since we're using Qt coords for scrolling, we have to adjust them here to
855 // conform to Cartesian coords, since the origin is using Cartesian. :-)
856 Vector delta(oldPoint, point);
857 delta /= Global::zoom;
859 Global::origin -= delta;
861 UpdateGridBackground();
867 // If we're doing a selection rect, see if any objects are engulfed by it
868 // (implies left mouse button held down)
869 if (Global::selectionInProgress)
876 // Handle object movement (left button down & over an object)
877 if ((event->buttons() & Qt::LeftButton) && numHovered && !Global::tool)
879 HandleObjectMovement(point);
885 // Do object hit testing...
886 bool needUpdate = HitTestObjects(point);
888 // Do tool handling, if any are active...
891 if (Global::snapToGrid)
892 point = SnapPointToGrid(point);
894 ToolHandler(ToolMouseMove, point);
897 // This is used to draw the tool crosshair...
900 if (needUpdate || Global::tool)
905 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
907 if (event->button() == Qt::LeftButton)
909 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
910 //could set it up to use the document's update function (assumes that all object updates
911 //are being reported correctly:
912 // if (document.NeedsUpdate())
913 // Do an update if collided with at least *one* object in the document
919 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
920 ToolHandler(ToolMouseUp, point);
924 if (Global::selectionInProgress)
925 Global::selectionInProgress = false;
927 // Should be we do this automagically? Hmm...
933 std::vector<void *>::iterator i;
935 for(i=document.objects.begin(); i!=document.objects.end(); i++)
937 if (((Object *)(*i))->selected)
938 select.push_back(*i);
940 //hmm, this is no good, too late to do any good :-P
941 // if ((*i)->hovered)
942 // hover.push_back(*i);
946 else if (event->button() == Qt::MiddleButton)
949 setCursor(Qt::ArrowCursor);
954 void DrawingView::wheelEvent(QWheelEvent * event)
956 double zoomFactor = 1.25;
957 QSize sizeWin = size();
958 Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
959 center = Painter::QtToCartesianCoords(center);
961 // This is not centering for some reason. Need to figure out why. :-/
962 if (event->delta() > 0)
964 Vector newOrigin = center - ((center - Global::origin) / zoomFactor);
965 Global::origin = newOrigin;
966 Global::zoom *= zoomFactor;
970 Vector newOrigin = center + ((-center + Global::origin) * zoomFactor);
971 Global::origin = newOrigin;
972 Global::zoom /= zoomFactor;
976 // Global::gridSpacing = gridPixels / Painter::zoom;
977 // UpdateGridBackground();
978 SetGridSize(Global::gridSpacing * Global::zoom);
980 // zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Global::gridSpacing));
985 void DrawingView::keyPressEvent(QKeyEvent * event)
987 bool oldShift = shiftDown;
988 bool oldCtrl = ctrlDown;
990 if (event->key() == Qt::Key_Shift)
992 else if (event->key() == Qt::Key_Control)
995 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
998 ToolHandler(ToolKeyDown, Point(0, 0));
1005 void DrawingView::keyReleaseEvent(QKeyEvent * event)
1007 bool oldShift = shiftDown;
1008 bool oldCtrl = ctrlDown;
1010 if (event->key() == Qt::Key_Shift)
1012 else if (event->key() == Qt::Key_Control)
1015 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1018 ToolHandler(ToolKeyUp, Point(0, 0));
1025 // This looks strange, but it's really quite simple: We want a point that's
1026 // more than half-way to the next grid point to snap there while conversely we
1027 // want a point that's less than half-way to to the next grid point then snap
1028 // to the one before it. So we add half of the grid spacing to the point, then
1029 // divide by it so that we can remove the fractional part, then multiply it
1030 // back to get back to the correct answer.
1032 Point DrawingView::SnapPointToGrid(Point point)
1034 point += Global::gridSpacing / 2.0; // *This* adds to Z!!!
1035 point /= Global::gridSpacing;
1036 point.x = floor(point.x);//need to fix this for negative numbers...
1037 point.y = floor(point.y);
1038 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
1039 point *= Global::gridSpacing;
1044 void DrawingView::CheckObjectBounds(void)
1046 std::vector<void *>::iterator i;
1049 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1051 Object * obj = (Object *)(*i);
1052 obj->selected = false;
1058 Line * l = (Line *)obj;
1060 if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
1067 Circle * c = (Circle *)obj;
1069 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]))
1076 Arc * a = (Arc *)obj;
1078 double start = a->angle[0];
1079 double end = start + a->angle[1];
1080 QPointF p1(cos(start), sin(start));
1081 QPointF p2(cos(end), sin(end));
1082 QRectF bounds(p1, p2);
1085 // Swap X/Y coordinates if they're backwards...
1086 if (bounds.left() > bounds.right())
1088 double temp = bounds.left();
1089 bounds.setLeft(bounds.right());
1090 bounds.setRight(temp);
1093 if (bounds.bottom() > bounds.top())
1095 double temp = bounds.bottom();
1096 bounds.setBottom(bounds.top());
1097 bounds.setTop(temp);
1100 // Doesn't work as advertised! For shame!
1101 bounds = bounds.normalized();
1104 // If the end of the arc is before the beginning, add 360 degrees to it
1108 // Adjust the bounds depending on which axes are crossed
1109 if ((start < PI_OVER_2) && (end > PI_OVER_2))
1112 if ((start < PI) && (end > PI))
1113 bounds.setLeft(-1.0);
1115 if ((start < (PI + PI_OVER_2)) && (end > (PI + PI_OVER_2)))
1116 bounds.setBottom(-1.0);
1118 if ((start < (2.0 * PI)) && (end > (2.0 * PI)))
1119 bounds.setRight(1.0);
1121 if ((start < ((2.0 * PI) + PI_OVER_2)) && (end > ((2.0 * PI) + PI_OVER_2)))
1124 if ((start < (3.0 * PI)) && (end > (3.0 * PI)))
1125 bounds.setLeft(-1.0);
1127 if ((start < ((3.0 * PI) + PI_OVER_2)) && (end > ((3.0 * PI) + PI_OVER_2)))
1128 bounds.setBottom(-1.0);
1130 bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
1131 bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
1132 bounds.translate(a->p[0].x, a->p[0].y);
1134 if (Global::selection.contains(bounds))
1149 bool DrawingView::HitTestObjects(Point point)
1151 std::vector<void *>::iterator i;
1153 bool needUpdate = false;
1155 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1157 Object * obj = (Object *)(*i);
1163 Line * l = (Line *)obj;
1164 bool oldHP0 = l->hitPoint[0], oldHP1 = l->hitPoint[1], oldHO = l->hitObject;
1165 l->hitPoint[0] = l->hitPoint[1] = l->hitObject = false;
1166 Vector lineSegment = l->p[1] - l->p[0];
1167 Vector v1 = point - l->p[0];
1168 Vector v2 = point - l->p[1];
1169 double t = Geometry::ParameterOfLineAndPoint(l->p[0], l->p[1], point);
1173 distance = v1.Magnitude();
1175 distance = v2.Magnitude();
1177 // distance = ?Det?(ls, v1) / |ls|
1178 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
1179 / lineSegment.Magnitude());
1181 if ((v1.Magnitude() * Global::zoom) < 8.0)
1183 l->hitPoint[0] = true;
1184 // snapPoint = l->p1;
1185 // snapPointIsValid = true;
1187 else if ((v2.Magnitude() * Global::zoom) < 8.0)
1189 l->hitPoint[1] = true;
1190 // snapPoint = l->p2;
1191 // snapPointIsValid = true;
1193 else if ((distance * Global::zoom) < 5.0)
1194 l->hitObject = true;
1196 // bool oldHovered = l->hovered;
1197 l->hovered = (l->hitPoint[0] || l->hitPoint[1] || l->hitObject ? true : false);
1198 // l->hovered = l->hitObject;
1200 // if (oldHovered != l->hovered)
1201 if ((oldHP0 != l->hitPoint[0]) || (oldHP1 != l->hitPoint[1]) || (oldHO != l->hitObject))
1208 Circle * c = (Circle *)obj;
1220 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
1228 void DrawingView::HandleObjectMovement(Point point)
1230 if (Global::snapToGrid)
1231 point = SnapPointToGrid(point);
1233 Point delta = point - oldPoint;
1234 Object * obj = (Object *)hover[0];
1235 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
1236 //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"));
1242 Line * l = (Line *)obj;
1246 else if (l->hitPoint[1])
1248 else if (l->hitObject)
1264 // This returns true if we've moved over an object...
1265 if (document.PointerMoved(point)) // <-- This
1266 // This is where the object would do automagic dragging & shit. Since we don't
1267 // do that anymore, we need a strategy to handle it.
1271 Now objects handle mouse move snapping as well. The code below mainly works only
1272 for tools; we need to fix it so that objects work as well...
1274 There's a problem with the object point snapping in that it's dependent on the
1275 order of the objects in the document. Most likely this is because it counts the
1276 selected object last and thus fucks up the algorithm. Need to fix this...
1280 // Do object snapping here. Grid snapping on mouse down is done in the
1281 // objects themselves, only because we have to hit test the raw point,
1282 // not the snapped point. There has to be a better way...!
1283 if (document.penultimateObjectHovered)
1285 // Two objects are hovered, see if we have an intersection point
1286 if ((document.lastObjectHovered->type == OTLine) && (document.penultimateObjectHovered->type == OTLine))
1289 int n = Geometry::Intersects((Line *)document.lastObjectHovered, (Line *)document.penultimateObjectHovered, &t);
1293 Global::snapPoint = document.lastObjectHovered->GetPointAtParameter(t);
1294 Global::snapPointIsValid = true;
1297 else if ((document.lastObjectHovered->type == OTCircle) && (document.penultimateObjectHovered->type == OTCircle))
1300 int n = Geometry::Intersects((Circle *)document.lastObjectHovered, (Circle *)document.penultimateObjectHovered, 0, 0, 0, 0, &p1, &p2);
1304 Global::snapPoint = p1;
1305 Global::snapPointIsValid = true;
1309 double d1 = Vector(point, p1).Magnitude();
1310 double d2 = Vector(point, p2).Magnitude();
1313 Global::snapPoint = p1;
1315 Global::snapPoint = p2;
1317 Global::snapPointIsValid = true;
1323 // Otherwise, it was a single object hovered...
1329 if (Global::snapToGrid)
1330 point = Global::SnapPointToGrid(point);
1332 // We always snap to object points, and they take precendence over
1334 if (Global::snapPointIsValid)
1335 point = Global::snapPoint;
1337 toolAction->MouseMoved(point);