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);
357 painter->DrawLine(obj->p[0], obj->p[1]);
359 if (obj->hitPoint[0])
360 painter->DrawHandle(obj->p[0]);
362 if (obj->hitPoint[1])
363 painter->DrawHandle(obj->p[1]);
367 painter->SetBrush(QBrush(Qt::NoBrush));
368 painter->DrawEllipse(obj->p[0], obj->radius[0], obj->radius[0]);
370 if (obj->hitPoint[0])
371 painter->DrawHandle(obj->p[0]);
375 painter->DrawArc(obj->p[0], obj->radius[0], obj->angle[0], obj->angle[1]);
379 Dimension * d = (Dimension *)obj;
381 Vector v(d->p[0], d->p[1]);
382 double angle = v.Angle();
383 Vector unit = v.Unit();
384 Vector linePt1 = d->p[0], linePt2 = d->p[1];
386 double x1, y1, length;
388 if (d->subtype == DTLinearVert)
390 if ((angle < 0) || (angle > PI))
392 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
393 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
394 ortho = Vector(1.0, 0);
399 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
400 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
401 ortho = Vector(-1.0, 0);
405 linePt1.x = linePt2.x = x1;
406 length = fabs(d->p[0].y - d->p[1].y);
408 else if (d->subtype == DTLinearHorz)
410 if ((angle < PI_OVER_2) || (angle > PI3_OVER_2))
412 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
413 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
414 ortho = Vector(0, 1.0);
419 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
420 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
421 ortho = Vector(0, -1.0);
425 linePt1.y = linePt2.y = y1;
426 length = fabs(d->p[0].x - d->p[1].x);
428 else if (d->subtype == DTLinear)
430 angle = Vector(linePt1, linePt2).Angle();
431 ortho = Vector::Normal(linePt1, linePt2);
432 length = v.Magnitude();
435 unit = Vector(linePt1, linePt2).Unit();
437 Point p1 = linePt1 + (ortho * 10.0 * scaledThickness);
438 Point p2 = linePt2 + (ortho * 10.0 * scaledThickness);
439 Point p3 = linePt1 + (ortho * 16.0 * scaledThickness);
440 Point p4 = linePt2 + (ortho * 16.0 * scaledThickness);
441 Point p5 = d->p[0] + (ortho * 4.0 * scaledThickness);
442 Point p6 = d->p[1] + (ortho * 4.0 * scaledThickness);
445 The numbers hardcoded into here, what are they?
446 I believe they are pixels.
448 // Draw extension lines (if certain type)
449 painter->DrawLine(p3, p5);
450 painter->DrawLine(p4, p6);
452 // Calculate whether or not the arrowheads are too crowded to put inside
453 // the extension lines. 9.0 is the length of the arrowhead.
454 double t = Geometry::ParameterOfLineAndPoint(linePt1, linePt2, linePt2 - (unit * 9.0 * scaledThickness));
455 //printf("Dimension::Draw(): t = %lf\n", t);
457 // On the screen, it's acting like this is actually 58%...
458 // This is correct, we want it to happen at > 50%
461 // Draw main dimension line + arrowheads
462 painter->DrawLine(p1, p2);
463 painter->DrawArrowhead(p1, p2, scaledThickness);
464 painter->DrawArrowhead(p2, p1, scaledThickness);
468 // Draw outside arrowheads
469 Point p7 = p1 - (unit * 9.0 * scaledThickness);
470 Point p8 = p2 + (unit * 9.0 * scaledThickness);
471 painter->DrawArrowhead(p1, p7, scaledThickness);
472 painter->DrawArrowhead(p2, p8, scaledThickness);
473 painter->DrawLine(p1, p1 - (unit * 14.0 * scaledThickness));
474 painter->DrawLine(p2, p2 + (unit * 14.0 * scaledThickness));
477 // Draw length of dimension line...
478 painter->SetFont(QFont("Arial", 8.0 * Global::zoom * scaledThickness));
479 Point ctr = p2 + (Vector(p2, p1) / 2.0);
482 QString dimText = QString("%1\"").arg(Vector(endpoint - position).Magnitude());
487 dimText = QString("%1\"").arg(length);
490 double feet = (double)((int)length / 12);
491 double inches = length - (feet * 12.0);
494 dimText = QString("%1'").arg(feet);
496 dimText = QString("%1' %2\"").arg(feet).arg(inches);
500 painter->DrawAngledText(ctr, angle, dimText, scaledThickness);
506 Text * t = (Text *)obj;
507 painter->DrawTextObject(t->p[0], t->s.c_str(), scaledThickness);
517 void DrawingView::AddHoveredToSelection(void)
519 std::vector<void *>::iterator i;
521 for(i=document.objects.begin(); i!=document.objects.end(); i++)
523 if (((Object *)(*i))->hovered)
524 ((Object *)(*i))->selected = true;
529 void DrawingView::GetSelection(std::vector<void *> & v)
532 std::vector<void *>::iterator i;
534 for(i=document.objects.begin(); i!=document.objects.end(); i++)
536 if (((Object *)(*i))->selected)
542 void DrawingView::GetHovered(std::vector<void *> & v)
545 std::vector<void *>::iterator i;
547 for(i=document.objects.begin(); i!=document.objects.end(); i++)
549 if (((Object *)(*i))->hovered)
551 //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"));
558 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
560 Global::screenSize = Vector(size().width(), size().height());
561 UpdateGridBackground();
565 void DrawingView::ToolHandler(int mode, Point p)
567 if (Global::tool == TTLine)
568 LineHandler(mode, p);
569 else if (Global::tool == TTRotate)
570 RotateHandler(mode, p);
574 void DrawingView::ToolDraw(Painter * painter)
576 if (Global::tool == TTLine)
578 if (Global::toolState == TSNone)
580 painter->DrawHandle(toolPoint[0]);
582 else if ((Global::toolState == TSPoint2) && shiftDown)
584 painter->DrawHandle(toolPoint[1]);
588 painter->DrawLine(toolPoint[0], toolPoint[1]);
589 painter->DrawHandle(toolPoint[1]);
591 Vector v(toolPoint[0], toolPoint[1]);
592 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
593 double absLength = v.Magnitude();
594 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
595 text = text.arg(absLength).arg(absAngle);
596 painter->DrawInformativeText(text);
599 else if (Global::tool == TTRotate)
601 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
602 painter->DrawHandle(toolPoint[0]);
603 else if ((Global::toolState == TSPoint2) && shiftDown)
604 painter->DrawHandle(toolPoint[1]);
607 if (toolPoint[0] == toolPoint[1])
610 painter->DrawLine(toolPoint[0], toolPoint[1]);
611 // Likely we need a tool container for this... (now we do!)
615 painter->SetPen(0x00FF00, 2.0, LSSolid);
616 overrideColor = true;
619 RenderObjects(painter, toolObjects);
620 overrideColor = false;
623 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
625 QString text = QChar(0x2221) + QObject::tr(": %1");
626 text = text.arg(absAngle);
631 painter->DrawInformativeText(text);
637 void DrawingView::LineHandler(int mode, Point p)
642 if (Global::toolState == TSNone)
649 if (Global::toolState == TSNone)
656 if (Global::toolState == TSNone)
658 Global::toolState = TSPoint2;
659 // Prevent spurious line from drawing...
660 toolPoint[1] = toolPoint[0];
662 else if ((Global::toolState == TSPoint2) && shiftDown)
664 // Key override is telling us to make a new line, not continue the
666 toolPoint[0] = toolPoint[1];
670 Line * l = new Line(toolPoint[0], toolPoint[1]);
671 document.objects.push_back(l);
672 toolPoint[0] = toolPoint[1];
678 void DrawingView::RotateHandler(int mode, Point p)
683 if (Global::toolState == TSNone)
686 SavePointsFrom(select, toolScratch);
687 Global::toolState = TSPoint1;
689 else if (Global::toolState == TSPoint1)
696 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
698 else if (Global::toolState == TSPoint2)
705 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
706 std::vector<void *>::iterator j = select.begin();
707 std::vector<Object>::iterator i = toolScratch.begin();
709 for(; i!=toolScratch.end(); i++, j++)
712 Point p1 = Geometry::RotatePointAroundPoint(obj.p[0], toolPoint[0], angle);
713 Point p2 = Geometry::RotatePointAroundPoint(obj.p[1], toolPoint[0], angle);
714 Object * obj2 = (Object *)(*j);
718 if (obj.type == OTArc)
720 //printf("Obj2->angle[0] = %f, obj.angle[0] = %f, angle = %f\n", obj2->angle[0], obj.angle[0], angle);
721 obj2->angle[0] = obj.angle[0] + angle;
723 if (obj2->angle[0] > PI_TIMES_2)
724 obj2->angle[0] -= PI_TIMES_2;
731 if (Global::toolState == TSPoint1)
733 Global::toolState = TSPoint2;
734 // Prevent spurious line from drawing...
735 toolPoint[1] = toolPoint[0];
737 else if ((Global::toolState == TSPoint2) && shiftDown)
739 // Key override is telling us to make a new line, not continue the
741 toolPoint[0] = toolPoint[1];
745 // Either we're finished with our rotate, or we're stamping a copy.
748 // Stamp a copy of the selection at the current rotation & bail
749 std::vector<void *> temp;
750 CopyObjects(select, temp);
752 AddObjectsTo(document.objects, temp);
753 RestorePointsTo(select, toolScratch);
758 Global::toolState = TSPoint1;
759 SavePointsFrom(select, toolScratch);
764 // Reset the selection if shift held down...
766 RestorePointsTo(select, toolScratch);
770 // Reset selection when key is let up
772 RotateHandler(ToolMouseMove, toolPoint[1]);
776 RestorePointsTo(select, toolScratch);
781 void DrawingView::mousePressEvent(QMouseEvent * event)
783 if (event->button() == Qt::LeftButton)
785 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
787 // Handle tool processing, if any
790 if (Global::snapToGrid)
791 point = SnapPointToGrid(point);
793 //Also, may want to figure out if hovering over a snap point on an object,
794 //snap to grid if not.
795 // Snap to object point if valid...
796 // if (Global::snapPointIsValid)
797 // point = Global::snapPoint;
799 ToolHandler(ToolMouseDown, point);
803 // Clear the selection only if CTRL isn't being held on click
805 ClearSelected(document.objects);
808 // If any objects are being hovered on click, add them to the selection
812 AddHoveredToSelection();
813 update(); // needed??
814 GetHovered(hover); // prolly needed
816 // Needed for grab & moving objects
817 if (Global::snapToGrid)
818 oldPoint = SnapPointToGrid(point);
823 // Didn't hit any object and not using a tool, so do a selection rectangle
824 Global::selectionInProgress = true;
825 Global::selection.setTopLeft(QPointF(point.x, point.y));
826 Global::selection.setBottomRight(QPointF(point.x, point.y));
828 else if (event->button() == Qt::MiddleButton)
831 oldPoint = Vector(event->x(), event->y());
832 // Should also change the mouse pointer as well...
833 setCursor(Qt::SizeAllCursor);
838 void DrawingView::mouseMoveEvent(QMouseEvent * event)
840 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
841 Global::selection.setBottomRight(QPointF(point.x, point.y));
842 // Only needs to be done here, as mouse down is always preceded by movement
843 Global::snapPointIsValid = false;
846 if (event->buttons() & Qt::MiddleButton)
848 point = Vector(event->x(), event->y());
849 // Since we're using Qt coords for scrolling, we have to adjust them here to
850 // conform to Cartesian coords, since the origin is using Cartesian. :-)
851 Vector delta(oldPoint, point);
852 delta /= Global::zoom;
854 Global::origin -= delta;
856 UpdateGridBackground();
862 // If we're doing a selection rect, see if any objects are engulfed by it
863 // (implies left mouse button held down)
864 if (Global::selectionInProgress)
871 // Handle object movement (left button down & over an object)
872 if ((event->buttons() & Qt::LeftButton) && numHovered && !Global::tool)
874 HandleObjectMovement(point);
880 // Do object hit testing...
881 bool needUpdate = HitTestObjects(point);
883 // Do tool handling, if any are active...
886 if (Global::snapToGrid)
887 point = SnapPointToGrid(point);
889 ToolHandler(ToolMouseMove, point);
892 // This is used to draw the tool crosshair...
895 if (needUpdate || Global::tool)
900 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
902 if (event->button() == Qt::LeftButton)
904 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
905 //could set it up to use the document's update function (assumes that all object updates
906 //are being reported correctly:
907 // if (document.NeedsUpdate())
908 // Do an update if collided with at least *one* object in the document
914 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
915 ToolHandler(ToolMouseUp, point);
919 if (Global::selectionInProgress)
920 Global::selectionInProgress = false;
922 // Should be we do this automagically? Hmm...
928 std::vector<void *>::iterator i;
930 for(i=document.objects.begin(); i!=document.objects.end(); i++)
932 if (((Object *)(*i))->selected)
933 select.push_back(*i);
935 //hmm, this is no good, too late to do any good :-P
936 // if ((*i)->hovered)
937 // hover.push_back(*i);
941 else if (event->button() == Qt::MiddleButton)
944 setCursor(Qt::ArrowCursor);
949 void DrawingView::wheelEvent(QWheelEvent * event)
951 double zoomFactor = 1.25;
952 QSize sizeWin = size();
953 Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
954 center = Painter::QtToCartesianCoords(center);
956 // This is not centering for some reason. Need to figure out why. :-/
957 if (event->delta() > 0)
959 Vector newOrigin = center - ((center - Global::origin) / zoomFactor);
960 Global::origin = newOrigin;
961 Global::zoom *= zoomFactor;
965 Vector newOrigin = center + ((-center + Global::origin) * zoomFactor);
966 Global::origin = newOrigin;
967 Global::zoom /= zoomFactor;
971 // Global::gridSpacing = gridPixels / Painter::zoom;
972 // UpdateGridBackground();
973 SetGridSize(Global::gridSpacing * Global::zoom);
975 // zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Global::gridSpacing));
980 void DrawingView::keyPressEvent(QKeyEvent * event)
982 bool oldShift = shiftDown;
983 bool oldCtrl = ctrlDown;
985 if (event->key() == Qt::Key_Shift)
987 else if (event->key() == Qt::Key_Control)
990 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
993 ToolHandler(ToolKeyDown, Point(0, 0));
1000 void DrawingView::keyReleaseEvent(QKeyEvent * event)
1002 bool oldShift = shiftDown;
1003 bool oldCtrl = ctrlDown;
1005 if (event->key() == Qt::Key_Shift)
1007 else if (event->key() == Qt::Key_Control)
1010 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1013 ToolHandler(ToolKeyUp, Point(0, 0));
1020 // This looks strange, but it's really quite simple: We want a point that's
1021 // more than half-way to the next grid point to snap there while conversely we
1022 // want a point that's less than half-way to to the next grid point then snap
1023 // to the one before it. So we add half of the grid spacing to the point, then
1024 // divide by it so that we can remove the fractional part, then multiply it
1025 // back to get back to the correct answer.
1027 Point DrawingView::SnapPointToGrid(Point point)
1029 point += Global::gridSpacing / 2.0; // *This* adds to Z!!!
1030 point /= Global::gridSpacing;
1031 point.x = floor(point.x);//need to fix this for negative numbers...
1032 point.y = floor(point.y);
1033 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
1034 point *= Global::gridSpacing;
1039 void DrawingView::CheckObjectBounds(void)
1041 std::vector<void *>::iterator i;
1044 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1046 Object * obj = (Object *)(*i);
1047 obj->selected = false;
1053 Line * l = (Line *)obj;
1055 if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
1062 Circle * c = (Circle *)obj;
1064 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]))
1071 Arc * a = (Arc *)obj;
1073 double start = a->angle[0];
1074 double end = start + a->angle[1];
1075 QPointF p1(cos(start), sin(start));
1076 QPointF p2(cos(end), sin(end));
1077 QRectF bounds(p1, p2);
1080 // Swap X/Y coordinates if they're backwards...
1081 if (bounds.left() > bounds.right())
1083 double temp = bounds.left();
1084 bounds.setLeft(bounds.right());
1085 bounds.setRight(temp);
1088 if (bounds.bottom() > bounds.top())
1090 double temp = bounds.bottom();
1091 bounds.setBottom(bounds.top());
1092 bounds.setTop(temp);
1095 // Doesn't work as advertised! For shame!
1096 bounds = bounds.normalized();
1099 // If the end of the arc is before the beginning, add 360 degrees to it
1103 // Adjust the bounds depending on which axes are crossed
1104 if ((start < PI_OVER_2) && (end > PI_OVER_2))
1107 if ((start < PI) && (end > PI))
1108 bounds.setLeft(-1.0);
1110 if ((start < (PI + PI_OVER_2)) && (end > (PI + PI_OVER_2)))
1111 bounds.setBottom(-1.0);
1113 if ((start < (2.0 * PI)) && (end > (2.0 * PI)))
1114 bounds.setRight(1.0);
1116 if ((start < ((2.0 * PI) + PI_OVER_2)) && (end > ((2.0 * PI) + PI_OVER_2)))
1119 if ((start < (3.0 * PI)) && (end > (3.0 * PI)))
1120 bounds.setLeft(-1.0);
1122 if ((start < ((3.0 * PI) + PI_OVER_2)) && (end > ((3.0 * PI) + PI_OVER_2)))
1123 bounds.setBottom(-1.0);
1125 bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
1126 bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
1127 bounds.translate(a->p[0].x, a->p[0].y);
1129 if (Global::selection.contains(bounds))
1144 bool DrawingView::HitTestObjects(Point point)
1146 std::vector<void *>::iterator i;
1148 bool needUpdate = false;
1150 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1152 Object * obj = (Object *)(*i);
1158 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
1159 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
1160 Vector lineSegment = obj->p[1] - obj->p[0];
1161 Vector v1 = point - obj->p[0];
1162 Vector v2 = point - obj->p[1];
1163 double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
1167 distance = v1.Magnitude();
1169 distance = v2.Magnitude();
1171 // distance = ?Det?(ls, v1) / |ls|
1172 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
1173 / lineSegment.Magnitude());
1175 if ((v1.Magnitude() * Global::zoom) < 8.0)
1176 obj->hitPoint[0] = true;
1177 else if ((v2.Magnitude() * Global::zoom) < 8.0)
1178 obj->hitPoint[1] = true;
1179 else if ((distance * Global::zoom) < 5.0)
1180 obj->hitObject = true;
1182 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
1184 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
1191 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
1192 obj->hitPoint[0] = obj->hitObject = false;
1193 double length = Vector::Magnitude(obj->p[0], point);
1195 if ((length * Global::zoom) < 8.0)
1196 obj->hitPoint[0] = true;
1197 else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
1198 obj->hitObject = true;
1200 obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
1202 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
1214 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
1222 void DrawingView::HandleObjectMovement(Point point)
1224 if (Global::snapToGrid)
1225 point = SnapPointToGrid(point);
1227 Point delta = point - oldPoint;
1228 Object * obj = (Object *)hover[0];
1229 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
1230 //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"));
1236 // Line * l = (Line *)obj;
1238 if (obj->hitPoint[0])
1240 else if (obj->hitPoint[1])
1242 else if (obj->hitObject)
1258 // This returns true if we've moved over an object...
1259 if (document.PointerMoved(point)) // <-- This
1260 // This is where the object would do automagic dragging & shit. Since we don't
1261 // do that anymore, we need a strategy to handle it.
1265 Now objects handle mouse move snapping as well. The code below mainly works only
1266 for tools; we need to fix it so that objects work as well...
1268 There's a problem with the object point snapping in that it's dependent on the
1269 order of the objects in the document. Most likely this is because it counts the
1270 selected object last and thus fucks up the algorithm. Need to fix this...
1274 // Do object snapping here. Grid snapping on mouse down is done in the
1275 // objects themselves, only because we have to hit test the raw point,
1276 // not the snapped point. There has to be a better way...!
1277 if (document.penultimateObjectHovered)
1279 // Two objects are hovered, see if we have an intersection point
1280 if ((document.lastObjectHovered->type == OTLine) && (document.penultimateObjectHovered->type == OTLine))
1283 int n = Geometry::Intersects((Line *)document.lastObjectHovered, (Line *)document.penultimateObjectHovered, &t);
1287 Global::snapPoint = document.lastObjectHovered->GetPointAtParameter(t);
1288 Global::snapPointIsValid = true;
1291 else if ((document.lastObjectHovered->type == OTCircle) && (document.penultimateObjectHovered->type == OTCircle))
1294 int n = Geometry::Intersects((Circle *)document.lastObjectHovered, (Circle *)document.penultimateObjectHovered, 0, 0, 0, 0, &p1, &p2);
1298 Global::snapPoint = p1;
1299 Global::snapPointIsValid = true;
1303 double d1 = Vector(point, p1).Magnitude();
1304 double d2 = Vector(point, p2).Magnitude();
1307 Global::snapPoint = p1;
1309 Global::snapPoint = p2;
1311 Global::snapPointIsValid = true;
1317 // Otherwise, it was a single object hovered...
1323 if (Global::snapToGrid)
1324 point = Global::SnapPointToGrid(point);
1326 // We always snap to object points, and they take precendence over
1328 if (Global::snapPointIsValid)
1329 point = Global::snapPoint;
1331 toolAction->MouseMoved(point);