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"
40 #define BACKGROUND_MAX_SIZE 512
42 enum { ToolMouseDown, ToolMouseMove, ToolMouseUp };
45 //Container DrawingView::document(Vector(0, 0));
48 DrawingView::DrawingView(QWidget * parent/*= NULL*/): QWidget(parent),
49 // The value in the settings file will override this.
50 useAntialiasing(true), numSelected(0), numHovered(0), shiftDown(false),
52 gridBackground(BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE),
53 scale(1.0), offsetX(-10), offsetY(-10),// document(Vector(0, 0)),
54 gridPixels(0), collided(false)//, toolAction(NULL)
56 // document.isTopLevelContainer = true;
57 //wtf? doesn't work except in c++11??? document = { 0 };
58 setBackgroundRole(QPalette::Base);
59 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
61 Global::gridSpacing = 12.0; // In base units (inch is default)
64 Line * line = new Line(Vector(5, 5), Vector(50, 40), &document);
66 document.Add(new Line(Vector(50, 40), Vector(10, 83), &document));
67 document.Add(new Line(Vector(10, 83), Vector(17, 2), &document));
68 document.Add(new Circle(Vector(100, 100), 36, &document));
69 document.Add(new Circle(Vector(50, 150), 49, &document));
70 document.Add(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3, &document)),
71 document.Add(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5, &document));
73 Dimension * dimension = new Dimension(Vector(0, 0), Vector(0, 0), DTLinear, &document);
74 line->SetDimensionOnLine(dimension);
75 document.Add(dimension);
77 // Alternate way to do the above...
78 line->SetDimensionOnLine();
81 Line * line = new Line;//(Vector(5, 5), Vector(50, 40), &document);
82 line->p1 = Vector(5, 5);
83 line->p2 = Vector(50, 40);
85 line->thickness = 2.0;
87 line->color = 0xFF7F00;
88 document.objects.push_back(line);
89 document.objects.push_back(new Line(Vector(50, 40), Vector(10, 83)));
90 document.objects.push_back(new Line(Vector(10, 83), Vector(17, 2)));
91 document.objects.push_back(new Circle(Vector(100, 100), 36));
92 document.objects.push_back(new Circle(Vector(50, 150), 49));
93 document.objects.push_back(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3)),
94 document.objects.push_back(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5));
95 document.objects.push_back(new Dimension(Vector(50, 40), Vector(5, 5)));
96 document.objects.push_back(new Text(Vector(10, 83), "Here is some awesome text!"));
100 Here we set the grid size in pixels--12 in this case. Initially, we have our
101 zoom set to make this represent 12 inches at a zoom factor of 25%. (This is
102 arbitrary.) So, to be able to decouple the grid size from the zoom, we need
103 to be able to set the size of the background grid (which we do here at an
104 arbitrary 12 pixels) to anything we want (within reason, of course :-).
106 The drawing enforces the grid spacing through the drawing->gridSpacing variable.
108 drawing->gridSpacing = 12.0 / Global::zoom;
110 Global::zoom is the zoom factor for the drawing, and all mouse clicks are
111 translated to Cartesian coordinates through this. (Initially, Global::zoom is
112 set to 1.0. SCREEN_ZOOM is set to 1.0/4.0.)
114 Really, the 100% zoom level can be set at *any* zoom level, it's more of a
115 convenience function than any measure of absolutes. Doing things that way we
116 could rid ourselves of the whole SCREEN_ZOOM parameter and all the attendant
117 shittiness that comes with it.
119 However, it seems that SCREEN_ZOOM is used to make text and arrow sizes show up
120 a certain way, which means we should probably create something else in those
121 objects to take its place--like some kind of scale factor. This would seem to
122 imply that certain point sizes actually *do* tie things like fonts to absolute
123 sizes on the screen, but not necessarily because you could have an inch scale
124 with text that is quite small relative to other objects on the screen, which
125 currently you have to zoom in to see (and which blows up the text). Point sizes
126 in an application like this are a bit meaningless; even though an inch is an
127 inch regardless of the zoom level a piece of text can be larger or smaller than
128 this. Maybe this is the case for having a base unit and basing point sizes off
131 Here's what's been figured out. Global::zoom is simply the ratio of pixels to
132 base units. What that means is that if you have a 12px grid with a 6" grid size
133 (& base unit of "inches"), Global::zoom becomes 12px / 6" = 2.0 px/in.
135 Dimensions now have a "size" parameter to set their absolute size in relation
136 to the base unit. ATM, the arrows are drawn in pixels, but also scaled by
137 Global::zoom *and* size. Same with the dimension text; it's drawn at 10pt and
138 scaled the same way as the arrowheads.
140 Need a way to scale line widths as well. :-/ Shouldn't be too difficult, just
141 need a thickness parameter similar to the "size" param for dimensions. (And now
145 SetGridSize(12); // This is in pixels
150 void DrawingView::SetToolActive(Action * action)
155 connect(toolAction, SIGNAL(ObjectReady(Object *)), this,
156 SLOT(AddNewObjectToDocument(Object *)));
157 connect(toolAction, SIGNAL(NeedRefresh()), this, SLOT(HandleActionUpdate()));
163 void DrawingView::SetGridSize(uint32_t size)
166 if (size == gridPixels)
169 // Recreate the background bitmap
171 QPainter pmp(&gridBackground);
172 pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
173 pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
175 for(int i=0; i<(BACKGROUND_MAX_SIZE-1); i+=gridPixels)
177 pmp.drawLine(i, 0, i, BACKGROUND_MAX_SIZE - 1);
178 pmp.drawLine(0, i, BACKGROUND_MAX_SIZE - 1, i);
183 // Set up new BG brush & zoom level (pixels per base unit)
184 // Painter::zoom = gridPixels / gridSpacing;
185 Global::zoom = gridPixels / Global::gridSpacing;
186 UpdateGridBackground();
190 void DrawingView::UpdateGridBackground(void)
192 // Transform the origin to Qt coordinates
193 Vector pixmapOrigin = Painter::CartesianToQtCoords(Vector());
194 int x = (int)pixmapOrigin.x;
195 int y = (int)pixmapOrigin.y;
196 // Use mod arithmetic to grab the correct swatch of background
198 Negative numbers still screw it up... Need to think about what we're
199 trying to do here. The fact that it worked with 72 seems to have been pure luck.
200 It seems the problem is negative numbers: We can't let that happen.
201 When taking away the zero, it pops over 1 px at zero, then goes about 1/2 a
204 The bitmap looks like this:
214 @ x = 1, we want it to look like:
216 -+---+---+---+---+---
219 -+---+---+---+---+---
224 Which means we need to grab the sample from x = 3. @ x = -1:
234 Which means we need to grab the sample from x = 1. Which means we have to take
235 the mirror of the modulus of gridPixels.
237 Doing a mod of a negative number is problematic: 1st, the compiler converts the
238 negative number to an unsigned int, then it does the mod. Gets you wrong answers
239 most of the time, unless you use a power of 2. :-P So what we do here is just
240 take the modulus of the negation, which means we don't have to worry about
243 The positive case looks gruesome (and it is) but it boils down to this: We take
244 the modulus of the X coordinate, then mirror it by subtraction from the
245 maximum (in this case, gridPixels). This gives us a number in the range of 1 to
246 gridPixels. But we need the case where the result equalling gridPixels to be
247 zero; so we do another modulus operation on the result to achieve this.
252 x = (gridPixels - (x % gridPixels)) % gridPixels;
257 y = (gridPixels - (y % gridPixels)) % gridPixels;
259 // Here we grab a section of the bigger pixmap, so that the background
260 // *looks* like it's scrolling...
261 QPixmap pm = gridBackground.copy(x, y, gridPixels, gridPixels);
262 QPalette pal = palette();
263 pal.setBrush(backgroundRole(), QBrush(pm));
264 setAutoFillBackground(true);
269 void DrawingView::AddNewObjectToDocument(Object * object)
273 // object->Reparent(&document);
274 // document.Add(object);
277 //printf("DrawingView::AddNewObjectToDocument(). object=%08X\n", object);
281 void DrawingView::HandleActionUpdate(void)
287 void DrawingView::SetCurrentLayer(int layer)
289 Global::currentLayer = layer;
290 //printf("DrawingView::CurrentLayer = %i\n", layer);
294 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
296 // This is undoing the transform, e.g. going from client coords to local coords.
297 // In essence, the height - y is height + (y * -1), the (y * -1) term doing the
298 // conversion of the y-axis from increasing bottom to top.
299 return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
303 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
305 // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
306 // No voodoo here, it's just grouped wrong to see it. It should be:
307 // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive
308 return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
312 void DrawingView::paintEvent(QPaintEvent * /*event*/)
314 QPainter qtPainter(this);
315 Painter painter(&qtPainter);
318 qtPainter.setRenderHint(QPainter::Antialiasing);
320 Global::viewportHeight = size().height();
322 // Draw coordinate axes
323 painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
324 painter.DrawLine(0, -16384, 0, 16384);
325 painter.DrawLine(-16384, 0, 16384, 0);
327 // The top level document takes care of rendering for us...
328 // document.Draw(&painter);
329 // Not any more it doesn't...
330 RenderObjects(&painter, &document);
335 painter.SetPen(QPen(QColor(200, 100, 0, 255), 1.0, Qt::DashLine));
336 painter.DrawCrosshair(oldPoint);
337 toolAction->Draw(&painter);
342 painter.SetPen(QPen(QColor(200, 100, 0, 255), 1.0, Qt::DashLine));
343 painter.DrawCrosshair(oldPoint);
349 if (Global::selectionInProgress)
351 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
352 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
353 painter.DrawRect(Global::selection);
359 void DrawingView::RenderObjects(Painter * painter, Container * c)
361 std::vector<void *>::iterator i;
363 for(i=c->objects.begin(); i!=c->objects.end(); i++)
365 Object * obj = (Object *)(*i);
366 float scaledThickness = Global::scale * obj->thickness;
367 painter->SetPen(obj->color, Global::zoom * scaledThickness, obj->style);
368 painter->SetBrush(obj->color);
370 if (obj->selected || obj->hitObject)
371 painter->SetPen(0xFF0000, Global::zoom * scaledThickness, LSDash);
377 Line * l = (Line *)obj;
378 painter->DrawLine(l->p1, l->p2);
381 painter->DrawHandle(l->p1);
384 painter->DrawHandle(l->p2);
390 Circle * ci = (Circle *)obj;
391 painter->SetBrush(QBrush(Qt::NoBrush));
392 painter->DrawEllipse(ci->p1, ci->radius, ci->radius);
397 Arc * a = (Arc *)obj;
398 painter->DrawArc(a->p1, a->radius, a->angle1, a->angle2);
403 Dimension * d = (Dimension *)obj;
405 Vector v(d->p1, d->p2);
406 double angle = v.Angle();
407 Vector unit = v.Unit();
408 Vector linePt1 = d->p1, linePt2 = d->p2;
410 double x1, y1, length;
412 if (d->subtype == DTLinearVert)
414 if ((angle < 0) || (angle > PI))
416 x1 = (d->p1.x > d->p2.x ? d->p1.x : d->p2.x);
417 y1 = (d->p1.y > d->p2.y ? d->p1.y : d->p2.y);
418 ortho = Vector(1.0, 0);
423 x1 = (d->p1.x > d->p2.x ? d->p2.x : d->p1.x);
424 y1 = (d->p1.y > d->p2.y ? d->p2.y : d->p1.y);
425 ortho = Vector(-1.0, 0);
429 linePt1.x = linePt2.x = x1;
430 length = fabs(d->p1.y - d->p2.y);
432 else if (d->subtype == DTLinearHorz)
434 if ((angle < PI_OVER_2) || (angle > PI3_OVER_2))
436 x1 = (d->p1.x > d->p2.x ? d->p1.x : d->p2.x);
437 y1 = (d->p1.y > d->p2.y ? d->p1.y : d->p2.y);
438 ortho = Vector(0, 1.0);
443 x1 = (d->p1.x > d->p2.x ? d->p2.x : d->p1.x);
444 y1 = (d->p1.y > d->p2.y ? d->p2.y : d->p1.y);
445 ortho = Vector(0, -1.0);
449 linePt1.y = linePt2.y = y1;
450 length = fabs(d->p1.x - d->p2.x);
452 else if (d->subtype == DTLinear)
454 angle = Vector(linePt1, linePt2).Angle();
455 ortho = Vector::Normal(linePt1, linePt2);
456 length = v.Magnitude();
459 unit = Vector(linePt1, linePt2).Unit();
461 Point p1 = linePt1 + (ortho * 10.0 * scaledThickness);
462 Point p2 = linePt2 + (ortho * 10.0 * scaledThickness);
463 Point p3 = linePt1 + (ortho * 16.0 * scaledThickness);
464 Point p4 = linePt2 + (ortho * 16.0 * scaledThickness);
465 Point p5 = d->p1 + (ortho * 4.0 * scaledThickness);
466 Point p6 = d->p2 + (ortho * 4.0 * scaledThickness);
469 The numbers hardcoded into here, what are they?
470 I believe they are pixels.
472 // Draw extension lines (if certain type)
473 painter->DrawLine(p3, p5);
474 painter->DrawLine(p4, p6);
476 // Calculate whether or not the arrowheads are too crowded to put inside
477 // the extension lines. 9.0 is the length of the arrowhead.
478 double t = Geometry::ParameterOfLineAndPoint(linePt1, linePt2, linePt2 - (unit * 9.0 * scaledThickness));
479 //printf("Dimension::Draw(): t = %lf\n", t);
481 // On the screen, it's acting like this is actually 58%...
482 // This is correct, we want it to happen at > 50%
485 // Draw main dimension line + arrowheads
486 painter->DrawLine(p1, p2);
487 painter->DrawArrowhead(p1, p2, scaledThickness);
488 painter->DrawArrowhead(p2, p1, scaledThickness);
492 // Draw outside arrowheads
493 Point p7 = p1 - (unit * 9.0 * scaledThickness);
494 Point p8 = p2 + (unit * 9.0 * scaledThickness);
495 painter->DrawArrowhead(p1, p7, scaledThickness);
496 painter->DrawArrowhead(p2, p8, scaledThickness);
497 painter->DrawLine(p1, p1 - (unit * 14.0 * scaledThickness));
498 painter->DrawLine(p2, p2 + (unit * 14.0 * scaledThickness));
501 // Draw length of dimension line...
502 painter->SetFont(QFont("Arial", 8.0 * Global::zoom * scaledThickness));
503 Point ctr = p2 + (Vector(p2, p1) / 2.0);
506 QString dimText = QString("%1\"").arg(Vector(endpoint - position).Magnitude());
511 dimText = QString("%1\"").arg(length);
514 double feet = (double)((int)length / 12);
515 double inches = length - (feet * 12.0);
518 dimText = QString("%1'").arg(feet);
520 dimText = QString("%1' %2\"").arg(feet).arg(inches);
524 painter->DrawAngledText(ctr, angle, dimText, scaledThickness);
530 Text * t = (Text *)obj;
531 painter->DrawTextObject(t->p1, t->s.c_str(), scaledThickness);
541 void DrawingView::DeleteSelectedItems(void)
543 std::vector<void *>::iterator i = document.objects.begin();
545 while (i != document.objects.end())
547 Object * obj = (Object *)(*i);
552 document.objects.erase(i);
560 void DrawingView::ClearSelection(void)
562 std::vector<void *>::iterator i;
564 for(i=document.objects.begin(); i!=document.objects.end(); i++)
565 ((Object *)(*i))->selected = false;
569 void DrawingView::AddHoveredToSelection(void)
571 std::vector<void *>::iterator i;
573 for(i=document.objects.begin(); i!=document.objects.end(); i++)
575 if (((Object *)(*i))->hovered)
576 ((Object *)(*i))->selected = true;
581 void DrawingView::GetSelection(std::vector<void *> & v)
584 std::vector<void *>::iterator i;
586 for(i=document.objects.begin(); i!=document.objects.end(); i++)
588 if (((Object *)(*i))->selected)
594 void DrawingView::GetHovered(std::vector<void *> & v)
597 std::vector<void *>::iterator i;
599 for(i=document.objects.begin(); i!=document.objects.end(); i++)
601 if (((Object *)(*i))->hovered)
603 //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"));
610 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
612 Global::screenSize = Vector(size().width(), size().height());
613 UpdateGridBackground();
617 void DrawingView::ToolMouse(int mode, Point p)
619 if (Global::tool == TTLine)
620 LineHandler(mode, p);
624 void DrawingView::ToolDraw(Painter * painter)
626 if (Global::tool == TTLine)
628 if (Global::toolState == TSNone)
630 painter->DrawHandle(toolPoint[0]);
632 else if ((Global::toolState == TSPoint2) && shiftDown)
634 painter->DrawHandle(toolPoint[1]);
638 painter->DrawLine(toolPoint[0], toolPoint[1]);
639 painter->DrawHandle(toolPoint[1]);
641 Vector v(toolPoint[0], toolPoint[1]);
642 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
643 double absLength = v.Magnitude();
644 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
645 text = text.arg(absLength).arg(absAngle);
646 painter->DrawInformativeText(text);
652 void DrawingView::LineHandler(int mode, Point p)
657 if (Global::toolState == TSNone)
664 if (Global::toolState == TSNone)
671 if (Global::toolState == TSNone)
673 Global::toolState = TSPoint2;
674 // Prevent spurious line from drawing...
675 toolPoint[1] = toolPoint[0];
677 else if ((Global::toolState == TSPoint2) && shiftDown)
679 // Key override is telling us to make a new line, not continue the
681 toolPoint[0] = toolPoint[1];
685 Line * l = new Line(toolPoint[0], toolPoint[1]);
686 document.objects.push_back(l);
687 toolPoint[0] = toolPoint[1];
693 void DrawingView::mousePressEvent(QMouseEvent * event)
695 if (event->button() == Qt::LeftButton)
697 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
699 // Handle tool processing, if any
702 if (Global::snapToGrid)
703 point = SnapPointToGrid(point);
705 //Also, may want to figure out if hovering over a snap point on an object,
706 //snap to grid if not.
707 // Snap to object point if valid...
708 // if (Global::snapPointIsValid)
709 // point = Global::snapPoint;
711 ToolMouse(ToolMouseDown, point);
715 // Clear the selection only if CTRL isn't being held on click
719 // If any objects are being hovered on click, add them to the selection
723 AddHoveredToSelection();
724 update(); // needed??
725 GetHovered(hover); // prolly needed
727 // Needed for grab & moving objects
728 if (Global::snapToGrid)
729 oldPoint = SnapPointToGrid(point);
734 // Didn't hit any object and not using a tool, so do a selection rectangle
735 Global::selectionInProgress = true;
736 Global::selection.setTopLeft(QPointF(point.x, point.y));
737 Global::selection.setBottomRight(QPointF(point.x, point.y));
739 else if (event->button() == Qt::MiddleButton)
742 oldPoint = Vector(event->x(), event->y());
743 // Should also change the mouse pointer as well...
744 setCursor(Qt::SizeAllCursor);
749 void DrawingView::mouseMoveEvent(QMouseEvent * event)
751 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
752 Global::selection.setBottomRight(QPointF(point.x, point.y));
753 // Only needs to be done here, as mouse down is always preceded by movement
754 Global::snapPointIsValid = false;
757 if (event->buttons() & Qt::MiddleButton)
759 point = Vector(event->x(), event->y());
760 // Since we're using Qt coords for scrolling, we have to adjust them here to
761 // conform to Cartesian coords, since the origin is using Cartesian. :-)
762 Vector delta(oldPoint, point);
763 delta /= Global::zoom;
765 Global::origin -= delta;
767 UpdateGridBackground();
773 // If we're doing a selection rect, see if any objects are engulfed by it
774 // (implies left mouse button held down)
775 if (Global::selectionInProgress)
782 // Handle object movement (left button down & over an object)
783 if ((event->buttons() & Qt::LeftButton) && numHovered)
785 if (Global::snapToGrid)
786 point = SnapPointToGrid(point);
788 Point delta = point - oldPoint;
789 Object * obj = (Object *)hover[0];
790 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
791 //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"));
797 Line * l = (Line *)obj;
801 else if (l->hitPoint[1])
803 else if (l->hitObject)
820 // Do object hit testing...
821 bool needUpdate = HitTestObjects(point);
823 // Do tool handling, if any are active...
826 if (Global::snapToGrid)
827 point = SnapPointToGrid(point);
829 ToolMouse(ToolMouseMove, point);
832 // This is used to draw the tool crosshair...
835 if (needUpdate || Global::tool)
840 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
842 if (event->button() == Qt::LeftButton)
845 document.PointerReleased();
848 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
849 //could set it up to use the document's update function (assumes that all object updates
850 //are being reported correctly:
851 // if (document.NeedsUpdate())
853 update(); // Do an update if collided with at least *one* object in the document
857 toolAction->MouseReleased();
861 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
862 // ToolMouseUp(point);
863 ToolMouse(ToolMouseUp, point);
868 if (Global::selectionInProgress)
870 // Select all the stuff inside of selection
871 Global::selectionInProgress = false;
878 std::vector<void *>::iterator i;
880 for(i=document.objects.begin(); i!=document.objects.end(); i++)
882 if (((Object *)(*i))->selected)
883 select.push_back(*i);
885 //hmm, this is no good, too late to do any good :-P
886 // if ((*i)->hovered)
887 // hover.push_back(*i);
891 else if (event->button() == Qt::MiddleButton)
894 setCursor(Qt::ArrowCursor);
899 void DrawingView::wheelEvent(QWheelEvent * event)
901 double zoomFactor = 1.25;
902 QSize sizeWin = size();
903 Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
904 center = Painter::QtToCartesianCoords(center);
906 // This is not centering for some reason. Need to figure out why. :-/
907 if (event->delta() > 0)
909 Vector newOrigin = center - ((center - Global::origin) / zoomFactor);
910 Global::origin = newOrigin;
911 Global::zoom *= zoomFactor;
915 Vector newOrigin = center + ((-center + Global::origin) * zoomFactor);
916 Global::origin = newOrigin;
917 Global::zoom /= zoomFactor;
921 // Global::gridSpacing = gridPixels / Painter::zoom;
922 // UpdateGridBackground();
923 SetGridSize(Global::gridSpacing * Global::zoom);
925 // zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Global::gridSpacing));
930 void DrawingView::keyPressEvent(QKeyEvent * event)
934 toolAction->KeyDown(event->key());
936 bool oldShift = shiftDown;
937 bool oldCtrl = ctrlDown;
939 if (event->key() == Qt::Key_Shift)
941 else if (event->key() == Qt::Key_Control)
944 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
949 void DrawingView::keyReleaseEvent(QKeyEvent * event)
953 toolAction->KeyReleased(event->key());
955 bool oldShift = shiftDown;
956 bool oldCtrl = ctrlDown;
958 if (event->key() == Qt::Key_Shift)
960 else if (event->key() == Qt::Key_Control)
963 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
968 // This looks strange, but it's really quite simple: We want a point that's
969 // more than half-way to the next grid point to snap there while conversely we
970 // want a point that's less than half-way to to the next grid point then snap
971 // to the one before it. So we add half of the grid spacing to the point, then
972 // divide by it so that we can remove the fractional part, then multiply it
973 // back to get back to the correct answer.
975 Point DrawingView::SnapPointToGrid(Point point)
977 point += Global::gridSpacing / 2.0; // *This* adds to Z!!!
978 point /= Global::gridSpacing;
979 point.x = floor(point.x);//need to fix this for negative numbers...
980 point.y = floor(point.y);
981 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
982 point *= Global::gridSpacing;
987 void DrawingView::CheckObjectBounds(void)
989 std::vector<void *>::iterator i;
992 for(i=document.objects.begin(); i!=document.objects.end(); i++)
994 Object * obj = (Object *)(*i);
995 obj->selected = false;
1001 Line * l = (Line *)obj;
1003 if (Global::selection.contains(l->p1.x, l->p1.y) && Global::selection.contains(l->p2.x, l->p2.y))
1010 Circle * c = (Circle *)obj;
1012 if (Global::selection.contains(c->p1.x - c->radius, c->p1.y - c->radius) && Global::selection.contains(c->p1.x + c->radius, c->p1.y + c->radius))
1019 Arc * a = (Arc *)obj;
1021 double start = a->angle1;
1022 double end = start + a->angle2;
1023 QPointF p1(cos(start), sin(start));
1024 QPointF p2(cos(end), sin(end));
1025 QRectF bounds(p1, p2);
1028 // Swap X/Y coordinates if they're backwards...
1029 if (bounds.left() > bounds.right())
1031 double temp = bounds.left();
1032 bounds.setLeft(bounds.right());
1033 bounds.setRight(temp);
1036 if (bounds.bottom() > bounds.top())
1038 double temp = bounds.bottom();
1039 bounds.setBottom(bounds.top());
1040 bounds.setTop(temp);
1043 // Doesn't work as advertised! For shame!
1044 bounds = bounds.normalized();
1047 // If the end of the arc is before the beginning, add 360 degrees to it
1051 // Adjust the bounds depending on which axes are crossed
1052 if ((start < PI_OVER_2) && (end > PI_OVER_2))
1055 if ((start < PI) && (end > PI))
1056 bounds.setLeft(-1.0);
1058 if ((start < (PI + PI_OVER_2)) && (end > (PI + PI_OVER_2)))
1059 bounds.setBottom(-1.0);
1061 if ((start < (2.0 * PI)) && (end > (2.0 * PI)))
1062 bounds.setRight(1.0);
1064 if ((start < ((2.0 * PI) + PI_OVER_2)) && (end > ((2.0 * PI) + PI_OVER_2)))
1067 if ((start < (3.0 * PI)) && (end > (3.0 * PI)))
1068 bounds.setLeft(-1.0);
1070 if ((start < ((3.0 * PI) + PI_OVER_2)) && (end > ((3.0 * PI) + PI_OVER_2)))
1071 bounds.setBottom(-1.0);
1073 bounds.setTopLeft(QPointF(bounds.left() * a->radius, bounds.top() * a->radius));
1074 bounds.setBottomRight(QPointF(bounds.right() * a->radius, bounds.bottom() * a->radius));
1075 bounds.translate(a->p1.x, a->p1.y);
1077 if (Global::selection.contains(bounds))
1092 bool DrawingView::HitTestObjects(Point point)
1094 std::vector<void *>::iterator i;
1096 bool needUpdate = false;
1098 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1100 Object * obj = (Object *)(*i);
1106 Line * l = (Line *)obj;
1107 bool oldHP0 = l->hitPoint[0], oldHP1 = l->hitPoint[1], oldHO = l->hitObject;
1108 l->hitPoint[0] = l->hitPoint[1] = l->hitObject = false;
1109 Vector lineSegment = l->p2 - l->p1;
1110 Vector v1 = point - l->p1;
1111 Vector v2 = point - l->p2;
1112 double t = Geometry::ParameterOfLineAndPoint(l->p1, l->p2, point);
1116 distance = v1.Magnitude();
1118 distance = v2.Magnitude();
1120 // distance = ?Det?(ls, v1) / |ls|
1121 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
1122 / lineSegment.Magnitude());
1124 if ((v1.Magnitude() * Global::zoom) < 8.0)
1126 l->hitPoint[0] = true;
1127 // snapPoint = l->p1;
1128 // snapPointIsValid = true;
1130 else if ((v2.Magnitude() * Global::zoom) < 8.0)
1132 l->hitPoint[1] = true;
1133 // snapPoint = l->p2;
1134 // snapPointIsValid = true;
1136 else if ((distance * Global::zoom) < 5.0)
1137 l->hitObject = true;
1139 // bool oldHovered = l->hovered;
1140 l->hovered = (l->hitPoint[0] || l->hitPoint[1] || l->hitObject ? true : false);
1141 // l->hovered = l->hitObject;
1143 // if (oldHovered != l->hovered)
1144 if ((oldHP0 != l->hitPoint[0]) || (oldHP1 != l->hitPoint[1]) || (oldHO != l->hitObject))
1151 Circle * c = (Circle *)obj;
1163 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
1172 // This returns true if we've moved over an object...
1173 if (document.PointerMoved(point)) // <-- This
1174 // This is where the object would do automagic dragging & shit. Since we don't
1175 // do that anymore, we need a strategy to handle it.
1179 Now objects handle mouse move snapping as well. The code below mainly works only
1180 for tools; we need to fix it so that objects work as well...
1182 There's a problem with the object point snapping in that it's dependent on the
1183 order of the objects in the document. Most likely this is because it counts the
1184 selected object last and thus fucks up the algorithm. Need to fix this...
1188 // Do object snapping here. Grid snapping on mouse down is done in the
1189 // objects themselves, only because we have to hit test the raw point,
1190 // not the snapped point. There has to be a better way...!
1191 if (document.penultimateObjectHovered)
1193 // Two objects are hovered, see if we have an intersection point
1194 if ((document.lastObjectHovered->type == OTLine) && (document.penultimateObjectHovered->type == OTLine))
1197 int n = Geometry::Intersects((Line *)document.lastObjectHovered, (Line *)document.penultimateObjectHovered, &t);
1201 Global::snapPoint = document.lastObjectHovered->GetPointAtParameter(t);
1202 Global::snapPointIsValid = true;
1205 else if ((document.lastObjectHovered->type == OTCircle) && (document.penultimateObjectHovered->type == OTCircle))
1208 int n = Geometry::Intersects((Circle *)document.lastObjectHovered, (Circle *)document.penultimateObjectHovered, 0, 0, 0, 0, &p1, &p2);
1212 Global::snapPoint = p1;
1213 Global::snapPointIsValid = true;
1217 double d1 = Vector(point, p1).Magnitude();
1218 double d2 = Vector(point, p2).Magnitude();
1221 Global::snapPoint = p1;
1223 Global::snapPoint = p2;
1225 Global::snapPointIsValid = true;
1231 // Otherwise, it was a single object hovered...
1237 if (Global::snapToGrid)
1238 point = Global::SnapPointToGrid(point);
1240 // We always snap to object points, and they take precendence over
1242 if (Global::snapPointIsValid)
1243 point = Global::snapPoint;
1245 toolAction->MouseMoved(point);