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
43 enum { ToolMouseDown, ToolMouseMove, ToolMouseUp };
46 //Container DrawingView::document(Vector(0, 0));
49 DrawingView::DrawingView(QWidget * parent/*= NULL*/): QWidget(parent),
50 // The value in the settings file will override this.
51 useAntialiasing(true), numSelected(0), numHovered(0), shiftDown(false),
52 ctrlDown(false), overrideColor(false),
53 gridBackground(BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE),
54 scale(1.0), offsetX(-10), offsetY(-10),// document(Vector(0, 0)),
55 gridPixels(0), collided(false)//, toolAction(NULL)
57 // document.isTopLevelContainer = true;
58 //wtf? doesn't work except in c++11??? document = { 0 };
59 setBackgroundRole(QPalette::Base);
60 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
62 Global::gridSpacing = 12.0; // In base units (inch is default)
65 Line * line = new Line(Vector(5, 5), Vector(50, 40), &document);
67 document.Add(new Line(Vector(50, 40), Vector(10, 83), &document));
68 document.Add(new Line(Vector(10, 83), Vector(17, 2), &document));
69 document.Add(new Circle(Vector(100, 100), 36, &document));
70 document.Add(new Circle(Vector(50, 150), 49, &document));
71 document.Add(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3, &document)),
72 document.Add(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5, &document));
74 Dimension * dimension = new Dimension(Vector(0, 0), Vector(0, 0), DTLinear, &document);
75 line->SetDimensionOnLine(dimension);
76 document.Add(dimension);
78 // Alternate way to do the above...
79 line->SetDimensionOnLine();
82 Line * line = new Line;//(Vector(5, 5), Vector(50, 40), &document);
83 line->p[0] = Vector(5, 5);
84 line->p[1] = Vector(50, 40);
86 line->thickness = 2.0;
88 line->color = 0xFF7F00;
89 document.objects.push_back(line);
90 document.objects.push_back(new Line(Vector(50, 40), Vector(10, 83)));
91 document.objects.push_back(new Line(Vector(10, 83), Vector(17, 2)));
92 document.objects.push_back(new Circle(Vector(100, 100), 36));
93 document.objects.push_back(new Circle(Vector(50, 150), 49));
94 document.objects.push_back(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3)),
95 document.objects.push_back(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5));
96 document.objects.push_back(new Dimension(Vector(50, 40), Vector(5, 5)));
97 document.objects.push_back(new Text(Vector(10, 83), "Here is some awesome text!"));
101 Here we set the grid size in pixels--12 in this case. Initially, we have our
102 zoom set to make this represent 12 inches at a zoom factor of 25%. (This is
103 arbitrary.) So, to be able to decouple the grid size from the zoom, we need
104 to be able to set the size of the background grid (which we do here at an
105 arbitrary 12 pixels) to anything we want (within reason, of course :-).
107 The drawing enforces the grid spacing through the drawing->gridSpacing variable.
109 drawing->gridSpacing = 12.0 / Global::zoom;
111 Global::zoom is the zoom factor for the drawing, and all mouse clicks are
112 translated to Cartesian coordinates through this. (Initially, Global::zoom is
113 set to 1.0. SCREEN_ZOOM is set to 1.0/4.0.)
115 Really, the 100% zoom level can be set at *any* zoom level, it's more of a
116 convenience function than any measure of absolutes. Doing things that way we
117 could rid ourselves of the whole SCREEN_ZOOM parameter and all the attendant
118 shittiness that comes with it.
120 However, it seems that SCREEN_ZOOM is used to make text and arrow sizes show up
121 a certain way, which means we should probably create something else in those
122 objects to take its place--like some kind of scale factor. This would seem to
123 imply that certain point sizes actually *do* tie things like fonts to absolute
124 sizes on the screen, but not necessarily because you could have an inch scale
125 with text that is quite small relative to other objects on the screen, which
126 currently you have to zoom in to see (and which blows up the text). Point sizes
127 in an application like this are a bit meaningless; even though an inch is an
128 inch regardless of the zoom level a piece of text can be larger or smaller than
129 this. Maybe this is the case for having a base unit and basing point sizes off
132 Here's what's been figured out. Global::zoom is simply the ratio of pixels to
133 base units. What that means is that if you have a 12px grid with a 6" grid size
134 (& base unit of "inches"), Global::zoom becomes 12px / 6" = 2.0 px/in.
136 Dimensions now have a "size" parameter to set their absolute size in relation
137 to the base unit. ATM, the arrows are drawn in pixels, but also scaled by
138 Global::zoom *and* size. Same with the dimension text; it's drawn at 10pt and
139 scaled the same way as the arrowheads.
141 Need a way to scale line widths as well. :-/ Shouldn't be too difficult, just
142 need a thickness parameter similar to the "size" param for dimensions. (And now
146 SetGridSize(12); // This is in pixels
151 void DrawingView::SetToolActive(Action * action)
156 connect(toolAction, SIGNAL(ObjectReady(Object *)), this,
157 SLOT(AddNewObjectToDocument(Object *)));
158 connect(toolAction, SIGNAL(NeedRefresh()), this, SLOT(HandleActionUpdate()));
164 void DrawingView::SetGridSize(uint32_t size)
167 if (size == gridPixels)
170 // Recreate the background bitmap
172 QPainter pmp(&gridBackground);
173 pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
174 pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
176 for(int i=0; i<(BACKGROUND_MAX_SIZE-1); i+=gridPixels)
178 pmp.drawLine(i, 0, i, BACKGROUND_MAX_SIZE - 1);
179 pmp.drawLine(0, i, BACKGROUND_MAX_SIZE - 1, i);
184 // Set up new BG brush & zoom level (pixels per base unit)
185 // Painter::zoom = gridPixels / gridSpacing;
186 Global::zoom = gridPixels / Global::gridSpacing;
187 UpdateGridBackground();
191 void DrawingView::UpdateGridBackground(void)
193 // Transform the origin to Qt coordinates
194 Vector pixmapOrigin = Painter::CartesianToQtCoords(Vector());
195 int x = (int)pixmapOrigin.x;
196 int y = (int)pixmapOrigin.y;
197 // Use mod arithmetic to grab the correct swatch of background
199 Negative numbers still screw it up... Need to think about what we're
200 trying to do here. The fact that it worked with 72 seems to have been pure luck.
201 It seems the problem is negative numbers: We can't let that happen.
202 When taking away the zero, it pops over 1 px at zero, then goes about 1/2 a
205 The bitmap looks like this:
215 @ x = 1, we want it to look like:
217 -+---+---+---+---+---
220 -+---+---+---+---+---
225 Which means we need to grab the sample from x = 3. @ x = -1:
235 Which means we need to grab the sample from x = 1. Which means we have to take
236 the mirror of the modulus of gridPixels.
238 Doing a mod of a negative number is problematic: 1st, the compiler converts the
239 negative number to an unsigned int, then it does the mod. Gets you wrong answers
240 most of the time, unless you use a power of 2. :-P So what we do here is just
241 take the modulus of the negation, which means we don't have to worry about
244 The positive case looks gruesome (and it is) but it boils down to this: We take
245 the modulus of the X coordinate, then mirror it by subtraction from the
246 maximum (in this case, gridPixels). This gives us a number in the range of 1 to
247 gridPixels. But we need the case where the result equalling gridPixels to be
248 zero; so we do another modulus operation on the result to achieve this.
253 x = (gridPixels - (x % gridPixels)) % gridPixels;
258 y = (gridPixels - (y % gridPixels)) % gridPixels;
260 // Here we grab a section of the bigger pixmap, so that the background
261 // *looks* like it's scrolling...
262 QPixmap pm = gridBackground.copy(x, y, gridPixels, gridPixels);
263 QPalette pal = palette();
264 pal.setBrush(backgroundRole(), QBrush(pm));
265 setAutoFillBackground(true);
270 void DrawingView::AddNewObjectToDocument(Object * object)
274 // object->Reparent(&document);
275 // document.Add(object);
278 //printf("DrawingView::AddNewObjectToDocument(). object=%08X\n", object);
282 void DrawingView::HandleActionUpdate(void)
288 void DrawingView::SetCurrentLayer(int layer)
290 Global::currentLayer = layer;
291 //printf("DrawingView::CurrentLayer = %i\n", layer);
295 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
297 // This is undoing the transform, e.g. going from client coords to local coords.
298 // In essence, the height - y is height + (y * -1), the (y * -1) term doing the
299 // conversion of the y-axis from increasing bottom to top.
300 return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
304 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
306 // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
307 // No voodoo here, it's just grouped wrong to see it. It should be:
308 // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive
309 return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
313 void DrawingView::paintEvent(QPaintEvent * /*event*/)
315 QPainter qtPainter(this);
316 Painter painter(&qtPainter);
319 qtPainter.setRenderHint(QPainter::Antialiasing);
321 Global::viewportHeight = size().height();
323 // Draw coordinate axes
324 painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
325 painter.DrawLine(0, -16384, 0, 16384);
326 painter.DrawLine(-16384, 0, 16384, 0);
328 // Do object rendering...
329 RenderObjects(&painter, document.objects);
331 // Do tool rendering, if any...
334 painter.SetPen(QPen(QColor(200, 100, 0, 255), 1.0, Qt::DashLine));
335 painter.DrawCrosshair(oldPoint);
339 // Do selection rectangle rendering, if any
340 if (Global::selectionInProgress)
342 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
343 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
344 painter.DrawRect(Global::selection);
350 // Renders objects in the passed in vector
352 void DrawingView::RenderObjects(Painter * painter, std::vector<void *> & v)
354 std::vector<void *>::iterator i;
356 for(i=v.begin(); i!=v.end(); i++)
358 Object * obj = (Object *)(*i);
359 float scaledThickness = Global::scale * obj->thickness;
363 painter->SetPen(obj->color, Global::zoom * scaledThickness, obj->style);
364 painter->SetBrush(obj->color);
366 if (obj->selected || obj->hitObject)
367 painter->SetPen(0xFF0000, Global::zoom * scaledThickness, LSDash);
374 Line * l = (Line *)obj;
375 painter->DrawLine(l->p[0], l->p[1]);
378 painter->DrawHandle(l->p[0]);
381 painter->DrawHandle(l->p[1]);
387 Circle * ci = (Circle *)obj;
388 painter->SetBrush(QBrush(Qt::NoBrush));
389 painter->DrawEllipse(ci->p[0], ci->radius, ci->radius);
394 Arc * a = (Arc *)obj;
395 painter->DrawArc(a->p[0], a->radius, a->angle1, a->angle2);
400 Dimension * d = (Dimension *)obj;
402 Vector v(d->p[0], d->p[1]);
403 double angle = v.Angle();
404 Vector unit = v.Unit();
405 Vector linePt1 = d->p[0], linePt2 = d->p[1];
407 double x1, y1, length;
409 if (d->subtype == DTLinearVert)
411 if ((angle < 0) || (angle > PI))
413 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
414 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
415 ortho = Vector(1.0, 0);
420 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
421 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
422 ortho = Vector(-1.0, 0);
426 linePt1.x = linePt2.x = x1;
427 length = fabs(d->p[0].y - d->p[1].y);
429 else if (d->subtype == DTLinearHorz)
431 if ((angle < PI_OVER_2) || (angle > PI3_OVER_2))
433 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
434 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
435 ortho = Vector(0, 1.0);
440 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
441 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
442 ortho = Vector(0, -1.0);
446 linePt1.y = linePt2.y = y1;
447 length = fabs(d->p[0].x - d->p[1].x);
449 else if (d->subtype == DTLinear)
451 angle = Vector(linePt1, linePt2).Angle();
452 ortho = Vector::Normal(linePt1, linePt2);
453 length = v.Magnitude();
456 unit = Vector(linePt1, linePt2).Unit();
458 Point p1 = linePt1 + (ortho * 10.0 * scaledThickness);
459 Point p2 = linePt2 + (ortho * 10.0 * scaledThickness);
460 Point p3 = linePt1 + (ortho * 16.0 * scaledThickness);
461 Point p4 = linePt2 + (ortho * 16.0 * scaledThickness);
462 Point p5 = d->p[0] + (ortho * 4.0 * scaledThickness);
463 Point p6 = d->p[1] + (ortho * 4.0 * scaledThickness);
466 The numbers hardcoded into here, what are they?
467 I believe they are pixels.
469 // Draw extension lines (if certain type)
470 painter->DrawLine(p3, p5);
471 painter->DrawLine(p4, p6);
473 // Calculate whether or not the arrowheads are too crowded to put inside
474 // the extension lines. 9.0 is the length of the arrowhead.
475 double t = Geometry::ParameterOfLineAndPoint(linePt1, linePt2, linePt2 - (unit * 9.0 * scaledThickness));
476 //printf("Dimension::Draw(): t = %lf\n", t);
478 // On the screen, it's acting like this is actually 58%...
479 // This is correct, we want it to happen at > 50%
482 // Draw main dimension line + arrowheads
483 painter->DrawLine(p1, p2);
484 painter->DrawArrowhead(p1, p2, scaledThickness);
485 painter->DrawArrowhead(p2, p1, scaledThickness);
489 // Draw outside arrowheads
490 Point p7 = p1 - (unit * 9.0 * scaledThickness);
491 Point p8 = p2 + (unit * 9.0 * scaledThickness);
492 painter->DrawArrowhead(p1, p7, scaledThickness);
493 painter->DrawArrowhead(p2, p8, scaledThickness);
494 painter->DrawLine(p1, p1 - (unit * 14.0 * scaledThickness));
495 painter->DrawLine(p2, p2 + (unit * 14.0 * scaledThickness));
498 // Draw length of dimension line...
499 painter->SetFont(QFont("Arial", 8.0 * Global::zoom * scaledThickness));
500 Point ctr = p2 + (Vector(p2, p1) / 2.0);
503 QString dimText = QString("%1\"").arg(Vector(endpoint - position).Magnitude());
508 dimText = QString("%1\"").arg(length);
511 double feet = (double)((int)length / 12);
512 double inches = length - (feet * 12.0);
515 dimText = QString("%1'").arg(feet);
517 dimText = QString("%1' %2\"").arg(feet).arg(inches);
521 painter->DrawAngledText(ctr, angle, dimText, scaledThickness);
527 Text * t = (Text *)obj;
528 painter->DrawTextObject(t->p[0], t->s.c_str(), scaledThickness);
538 void DrawingView::DeleteSelectedItems(void)
540 std::vector<void *>::iterator i = document.objects.begin();
542 while (i != document.objects.end())
544 Object * obj = (Object *)(*i);
549 document.objects.erase(i);
557 void DrawingView::ClearSelection(void)
559 std::vector<void *>::iterator i;
561 for(i=document.objects.begin(); i!=document.objects.end(); i++)
562 ((Object *)(*i))->selected = false;
566 void DrawingView::AddHoveredToSelection(void)
568 std::vector<void *>::iterator i;
570 for(i=document.objects.begin(); i!=document.objects.end(); i++)
572 if (((Object *)(*i))->hovered)
573 ((Object *)(*i))->selected = true;
578 void DrawingView::GetSelection(std::vector<void *> & v)
581 std::vector<void *>::iterator i;
583 for(i=document.objects.begin(); i!=document.objects.end(); i++)
585 if (((Object *)(*i))->selected)
591 void DrawingView::GetHovered(std::vector<void *> & v)
594 std::vector<void *>::iterator i;
596 for(i=document.objects.begin(); i!=document.objects.end(); i++)
598 if (((Object *)(*i))->hovered)
600 //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"));
607 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
609 Global::screenSize = Vector(size().width(), size().height());
610 UpdateGridBackground();
614 void DrawingView::ToolMouse(int mode, Point p)
616 if (Global::tool == TTLine)
617 LineHandler(mode, p);
618 else if (Global::tool == TTRotate)
619 RotateHandler(mode, p);
623 void DrawingView::ToolDraw(Painter * painter)
625 if (Global::tool == TTLine)
627 if (Global::toolState == TSNone)
629 painter->DrawHandle(toolPoint[0]);
631 else if ((Global::toolState == TSPoint2) && shiftDown)
633 painter->DrawHandle(toolPoint[1]);
637 painter->DrawLine(toolPoint[0], toolPoint[1]);
638 painter->DrawHandle(toolPoint[1]);
640 Vector v(toolPoint[0], toolPoint[1]);
641 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
642 double absLength = v.Magnitude();
643 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
644 text = text.arg(absLength).arg(absAngle);
645 painter->DrawInformativeText(text);
648 else if (Global::tool == TTRotate)
650 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
651 painter->DrawHandle(toolPoint[0]);
652 else if ((Global::toolState == TSPoint2) && shiftDown)
653 painter->DrawHandle(toolPoint[1]);
656 if (toolPoint[0] == toolPoint[1])
659 painter->DrawLine(toolPoint[0], toolPoint[1]);
660 // Likely we need a tool container for this... (now we do!)
664 painter->SetPen(0x00FF00, 2.0, LSSolid);
665 overrideColor = true;
668 RenderObjects(painter, toolObjects);
669 overrideColor = false;
672 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
674 QString text = QChar(0x2221) + QObject::tr(": %1");
675 text = text.arg(absAngle);
680 painter->DrawInformativeText(text);
686 void DrawingView::LineHandler(int mode, Point p)
691 if (Global::toolState == TSNone)
698 if (Global::toolState == TSNone)
705 if (Global::toolState == TSNone)
707 Global::toolState = TSPoint2;
708 // Prevent spurious line from drawing...
709 toolPoint[1] = toolPoint[0];
711 else if ((Global::toolState == TSPoint2) && shiftDown)
713 // Key override is telling us to make a new line, not continue the
715 toolPoint[0] = toolPoint[1];
719 Line * l = new Line(toolPoint[0], toolPoint[1]);
720 document.objects.push_back(l);
721 toolPoint[0] = toolPoint[1];
727 void DrawingView::RotateHandler(int mode, Point p)
732 if (Global::toolState == TSNone)
736 CopyObjects(select, toolObjects);
737 // ClearSelected(toolObjects);
738 Global::toolState = TSPoint1;
740 else if (Global::toolState == TSPoint1)
748 There's two approaches to this that we can do:
750 -- Keep a copy of selected objects & rotate those (drawing rotated + selected)
751 -- Rotate the selected (drawing selected only)
753 Either way, we need to have a copy of the points before we change them; we also need
754 to know whether or not to discard any changes made--maybe with a ToolCleanup()
757 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
759 else if (Global::toolState == TSPoint2)
761 // need to reset the selected points to their non-rotated state in this case...
767 double angle = Vector(toolPoint[1], toolPoint[0]).Angle();
768 std::vector<void *>::iterator j = select.begin();
769 std::vector<void *>::iterator i = toolObjects.begin();
771 // for(; i!=select.end(); i++, j++)
772 for(; i!=toolObjects.end(); i++, j++)
774 Object * obj = (Object *)(*i);
775 Point p1 = Geometry::RotatePointAroundPoint(obj->p[0], toolPoint[0], angle);
776 Point p2 = Geometry::RotatePointAroundPoint(obj->p[1], toolPoint[0], angle);
777 Object * obj2 = (Object *)(*j);
785 if (Global::toolState == TSPoint1)
787 Global::toolState = TSPoint2;
788 // Prevent spurious line from drawing...
789 toolPoint[1] = toolPoint[0];
791 else if ((Global::toolState == TSPoint2) && shiftDown)
793 // Key override is telling us to make a new line, not continue the
795 toolPoint[0] = toolPoint[1];
800 Line * l = new Line(toolPoint[0], toolPoint[1]);
801 document.objects.push_back(l);
802 toolPoint[0] = toolPoint[1];
809 void DrawingView::mousePressEvent(QMouseEvent * event)
811 if (event->button() == Qt::LeftButton)
813 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
815 // Handle tool processing, if any
818 if (Global::snapToGrid)
819 point = SnapPointToGrid(point);
821 //Also, may want to figure out if hovering over a snap point on an object,
822 //snap to grid if not.
823 // Snap to object point if valid...
824 // if (Global::snapPointIsValid)
825 // point = Global::snapPoint;
827 ToolMouse(ToolMouseDown, point);
831 // Clear the selection only if CTRL isn't being held on click
835 // If any objects are being hovered on click, add them to the selection
839 AddHoveredToSelection();
840 update(); // needed??
841 GetHovered(hover); // prolly needed
843 // Needed for grab & moving objects
844 if (Global::snapToGrid)
845 oldPoint = SnapPointToGrid(point);
850 // Didn't hit any object and not using a tool, so do a selection rectangle
851 Global::selectionInProgress = true;
852 Global::selection.setTopLeft(QPointF(point.x, point.y));
853 Global::selection.setBottomRight(QPointF(point.x, point.y));
855 else if (event->button() == Qt::MiddleButton)
858 oldPoint = Vector(event->x(), event->y());
859 // Should also change the mouse pointer as well...
860 setCursor(Qt::SizeAllCursor);
865 void DrawingView::mouseMoveEvent(QMouseEvent * event)
867 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
868 Global::selection.setBottomRight(QPointF(point.x, point.y));
869 // Only needs to be done here, as mouse down is always preceded by movement
870 Global::snapPointIsValid = false;
873 if (event->buttons() & Qt::MiddleButton)
875 point = Vector(event->x(), event->y());
876 // Since we're using Qt coords for scrolling, we have to adjust them here to
877 // conform to Cartesian coords, since the origin is using Cartesian. :-)
878 Vector delta(oldPoint, point);
879 delta /= Global::zoom;
881 Global::origin -= delta;
883 UpdateGridBackground();
889 // If we're doing a selection rect, see if any objects are engulfed by it
890 // (implies left mouse button held down)
891 if (Global::selectionInProgress)
898 // Handle object movement (left button down & over an object)
899 if ((event->buttons() & Qt::LeftButton) && numHovered && !Global::tool)
901 HandleObjectMovement(point);
907 // Do object hit testing...
908 bool needUpdate = HitTestObjects(point);
910 // Do tool handling, if any are active...
913 if (Global::snapToGrid)
914 point = SnapPointToGrid(point);
916 ToolMouse(ToolMouseMove, point);
919 // This is used to draw the tool crosshair...
922 if (needUpdate || Global::tool)
927 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
929 if (event->button() == Qt::LeftButton)
931 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
932 //could set it up to use the document's update function (assumes that all object updates
933 //are being reported correctly:
934 // if (document.NeedsUpdate())
935 // Do an update if collided with at least *one* object in the document
941 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
942 ToolMouse(ToolMouseUp, point);
946 if (Global::selectionInProgress)
948 // Select all the stuff inside of selection
949 Global::selectionInProgress = false;
956 std::vector<void *>::iterator i;
958 for(i=document.objects.begin(); i!=document.objects.end(); i++)
960 if (((Object *)(*i))->selected)
961 select.push_back(*i);
963 //hmm, this is no good, too late to do any good :-P
964 // if ((*i)->hovered)
965 // hover.push_back(*i);
969 else if (event->button() == Qt::MiddleButton)
972 setCursor(Qt::ArrowCursor);
977 void DrawingView::wheelEvent(QWheelEvent * event)
979 double zoomFactor = 1.25;
980 QSize sizeWin = size();
981 Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
982 center = Painter::QtToCartesianCoords(center);
984 // This is not centering for some reason. Need to figure out why. :-/
985 if (event->delta() > 0)
987 Vector newOrigin = center - ((center - Global::origin) / zoomFactor);
988 Global::origin = newOrigin;
989 Global::zoom *= zoomFactor;
993 Vector newOrigin = center + ((-center + Global::origin) * zoomFactor);
994 Global::origin = newOrigin;
995 Global::zoom /= zoomFactor;
999 // Global::gridSpacing = gridPixels / Painter::zoom;
1000 // UpdateGridBackground();
1001 SetGridSize(Global::gridSpacing * Global::zoom);
1003 // zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Global::gridSpacing));
1008 void DrawingView::keyPressEvent(QKeyEvent * event)
1012 toolAction->KeyDown(event->key());
1014 bool oldShift = shiftDown;
1015 bool oldCtrl = ctrlDown;
1017 if (event->key() == Qt::Key_Shift)
1019 else if (event->key() == Qt::Key_Control)
1022 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1027 void DrawingView::keyReleaseEvent(QKeyEvent * event)
1031 toolAction->KeyReleased(event->key());
1033 bool oldShift = shiftDown;
1034 bool oldCtrl = ctrlDown;
1036 if (event->key() == Qt::Key_Shift)
1038 else if (event->key() == Qt::Key_Control)
1041 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1046 // This looks strange, but it's really quite simple: We want a point that's
1047 // more than half-way to the next grid point to snap there while conversely we
1048 // want a point that's less than half-way to to the next grid point then snap
1049 // to the one before it. So we add half of the grid spacing to the point, then
1050 // divide by it so that we can remove the fractional part, then multiply it
1051 // back to get back to the correct answer.
1053 Point DrawingView::SnapPointToGrid(Point point)
1055 point += Global::gridSpacing / 2.0; // *This* adds to Z!!!
1056 point /= Global::gridSpacing;
1057 point.x = floor(point.x);//need to fix this for negative numbers...
1058 point.y = floor(point.y);
1059 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
1060 point *= Global::gridSpacing;
1065 void DrawingView::CheckObjectBounds(void)
1067 std::vector<void *>::iterator i;
1070 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1072 Object * obj = (Object *)(*i);
1073 obj->selected = false;
1079 Line * l = (Line *)obj;
1081 if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
1088 Circle * c = (Circle *)obj;
1090 if (Global::selection.contains(c->p[0].x - c->radius, c->p[0].y - c->radius) && Global::selection.contains(c->p[0].x + c->radius, c->p[0].y + c->radius))
1097 Arc * a = (Arc *)obj;
1099 double start = a->angle1;
1100 double end = start + a->angle2;
1101 QPointF p1(cos(start), sin(start));
1102 QPointF p2(cos(end), sin(end));
1103 QRectF bounds(p1, p2);
1106 // Swap X/Y coordinates if they're backwards...
1107 if (bounds.left() > bounds.right())
1109 double temp = bounds.left();
1110 bounds.setLeft(bounds.right());
1111 bounds.setRight(temp);
1114 if (bounds.bottom() > bounds.top())
1116 double temp = bounds.bottom();
1117 bounds.setBottom(bounds.top());
1118 bounds.setTop(temp);
1121 // Doesn't work as advertised! For shame!
1122 bounds = bounds.normalized();
1125 // If the end of the arc is before the beginning, add 360 degrees to it
1129 // Adjust the bounds depending on which axes are crossed
1130 if ((start < PI_OVER_2) && (end > PI_OVER_2))
1133 if ((start < PI) && (end > PI))
1134 bounds.setLeft(-1.0);
1136 if ((start < (PI + PI_OVER_2)) && (end > (PI + PI_OVER_2)))
1137 bounds.setBottom(-1.0);
1139 if ((start < (2.0 * PI)) && (end > (2.0 * PI)))
1140 bounds.setRight(1.0);
1142 if ((start < ((2.0 * PI) + PI_OVER_2)) && (end > ((2.0 * PI) + PI_OVER_2)))
1145 if ((start < (3.0 * PI)) && (end > (3.0 * PI)))
1146 bounds.setLeft(-1.0);
1148 if ((start < ((3.0 * PI) + PI_OVER_2)) && (end > ((3.0 * PI) + PI_OVER_2)))
1149 bounds.setBottom(-1.0);
1151 bounds.setTopLeft(QPointF(bounds.left() * a->radius, bounds.top() * a->radius));
1152 bounds.setBottomRight(QPointF(bounds.right() * a->radius, bounds.bottom() * a->radius));
1153 bounds.translate(a->p[0].x, a->p[0].y);
1155 if (Global::selection.contains(bounds))
1170 bool DrawingView::HitTestObjects(Point point)
1172 std::vector<void *>::iterator i;
1174 bool needUpdate = false;
1176 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1178 Object * obj = (Object *)(*i);
1184 Line * l = (Line *)obj;
1185 bool oldHP0 = l->hitPoint[0], oldHP1 = l->hitPoint[1], oldHO = l->hitObject;
1186 l->hitPoint[0] = l->hitPoint[1] = l->hitObject = false;
1187 Vector lineSegment = l->p[1] - l->p[0];
1188 Vector v1 = point - l->p[0];
1189 Vector v2 = point - l->p[1];
1190 double t = Geometry::ParameterOfLineAndPoint(l->p[0], l->p[1], point);
1194 distance = v1.Magnitude();
1196 distance = v2.Magnitude();
1198 // distance = ?Det?(ls, v1) / |ls|
1199 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
1200 / lineSegment.Magnitude());
1202 if ((v1.Magnitude() * Global::zoom) < 8.0)
1204 l->hitPoint[0] = true;
1205 // snapPoint = l->p1;
1206 // snapPointIsValid = true;
1208 else if ((v2.Magnitude() * Global::zoom) < 8.0)
1210 l->hitPoint[1] = true;
1211 // snapPoint = l->p2;
1212 // snapPointIsValid = true;
1214 else if ((distance * Global::zoom) < 5.0)
1215 l->hitObject = true;
1217 // bool oldHovered = l->hovered;
1218 l->hovered = (l->hitPoint[0] || l->hitPoint[1] || l->hitObject ? true : false);
1219 // l->hovered = l->hitObject;
1221 // if (oldHovered != l->hovered)
1222 if ((oldHP0 != l->hitPoint[0]) || (oldHP1 != l->hitPoint[1]) || (oldHO != l->hitObject))
1229 Circle * c = (Circle *)obj;
1241 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
1249 void DrawingView::HandleObjectMovement(Point point)
1251 if (Global::snapToGrid)
1252 point = SnapPointToGrid(point);
1254 Point delta = point - oldPoint;
1255 Object * obj = (Object *)hover[0];
1256 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
1257 //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"));
1263 Line * l = (Line *)obj;
1267 else if (l->hitPoint[1])
1269 else if (l->hitObject)
1285 // This returns true if we've moved over an object...
1286 if (document.PointerMoved(point)) // <-- This
1287 // This is where the object would do automagic dragging & shit. Since we don't
1288 // do that anymore, we need a strategy to handle it.
1292 Now objects handle mouse move snapping as well. The code below mainly works only
1293 for tools; we need to fix it so that objects work as well...
1295 There's a problem with the object point snapping in that it's dependent on the
1296 order of the objects in the document. Most likely this is because it counts the
1297 selected object last and thus fucks up the algorithm. Need to fix this...
1301 // Do object snapping here. Grid snapping on mouse down is done in the
1302 // objects themselves, only because we have to hit test the raw point,
1303 // not the snapped point. There has to be a better way...!
1304 if (document.penultimateObjectHovered)
1306 // Two objects are hovered, see if we have an intersection point
1307 if ((document.lastObjectHovered->type == OTLine) && (document.penultimateObjectHovered->type == OTLine))
1310 int n = Geometry::Intersects((Line *)document.lastObjectHovered, (Line *)document.penultimateObjectHovered, &t);
1314 Global::snapPoint = document.lastObjectHovered->GetPointAtParameter(t);
1315 Global::snapPointIsValid = true;
1318 else if ((document.lastObjectHovered->type == OTCircle) && (document.penultimateObjectHovered->type == OTCircle))
1321 int n = Geometry::Intersects((Circle *)document.lastObjectHovered, (Circle *)document.penultimateObjectHovered, 0, 0, 0, 0, &p1, &p2);
1325 Global::snapPoint = p1;
1326 Global::snapPointIsValid = true;
1330 double d1 = Vector(point, p1).Magnitude();
1331 double d2 = Vector(point, p2).Magnitude();
1334 Global::snapPoint = p1;
1336 Global::snapPoint = p2;
1338 Global::snapPointIsValid = true;
1344 // Otherwise, it was a single object hovered...
1350 if (Global::snapToGrid)
1351 point = Global::SnapPointToGrid(point);
1353 // We always snap to object points, and they take precendence over
1355 if (Global::snapPointIsValid)
1356 point = Global::snapPoint;
1358 toolAction->MouseMoved(point);