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);
327 if (!informativeText.isEmpty())
328 painter.DrawInformativeText(informativeText);
333 // Renders objects in the passed in vector
335 void DrawingView::RenderObjects(Painter * painter, std::vector<void *> & v)
337 std::vector<void *>::iterator i;
339 for(i=v.begin(); i!=v.end(); i++)
341 Object * obj = (Object *)(*i);
342 float scaledThickness = Global::scale * obj->thickness;
344 if ((Global::tool == TTRotate) && ctrlDown && obj->selected)
346 painter->SetPen(0x00FF00, 2.0, LSSolid);
350 painter->SetPen(obj->color, Global::zoom * scaledThickness, obj->style);
351 painter->SetBrush(obj->color);
353 if (obj->selected || obj->hitObject)
354 painter->SetPen(0xFF0000, Global::zoom * scaledThickness, LSDash);
360 painter->DrawLine(obj->p[0], obj->p[1]);
362 if (obj->hitPoint[0])
363 painter->DrawHandle(obj->p[0]);
365 if (obj->hitPoint[1])
366 painter->DrawHandle(obj->p[1]);
370 painter->SetBrush(QBrush(Qt::NoBrush));
371 painter->DrawEllipse(obj->p[0], obj->radius[0], obj->radius[0]);
373 if (obj->hitPoint[0])
374 painter->DrawHandle(obj->p[0]);
378 painter->DrawArc(obj->p[0], obj->radius[0], obj->angle[0], obj->angle[1]);
382 Dimension * d = (Dimension *)obj;
384 Vector v(d->p[0], d->p[1]);
385 double angle = v.Angle();
386 Vector unit = v.Unit();
387 Vector linePt1 = d->p[0], linePt2 = d->p[1];
389 double x1, y1, length;
391 if (d->subtype == DTLinearVert)
393 if ((angle < 0) || (angle > PI))
395 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
396 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
397 ortho = Vector(1.0, 0);
402 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
403 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
404 ortho = Vector(-1.0, 0);
408 linePt1.x = linePt2.x = x1;
409 length = fabs(d->p[0].y - d->p[1].y);
411 else if (d->subtype == DTLinearHorz)
413 if ((angle < PI_OVER_2) || (angle > PI3_OVER_2))
415 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
416 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
417 ortho = Vector(0, 1.0);
422 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
423 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
424 ortho = Vector(0, -1.0);
428 linePt1.y = linePt2.y = y1;
429 length = fabs(d->p[0].x - d->p[1].x);
431 else if (d->subtype == DTLinear)
433 angle = Vector(linePt1, linePt2).Angle();
434 ortho = Vector::Normal(linePt1, linePt2);
435 length = v.Magnitude();
438 unit = Vector(linePt1, linePt2).Unit();
440 Point p1 = linePt1 + (ortho * 10.0 * scaledThickness);
441 Point p2 = linePt2 + (ortho * 10.0 * scaledThickness);
442 Point p3 = linePt1 + (ortho * 16.0 * scaledThickness);
443 Point p4 = linePt2 + (ortho * 16.0 * scaledThickness);
444 Point p5 = d->p[0] + (ortho * 4.0 * scaledThickness);
445 Point p6 = d->p[1] + (ortho * 4.0 * scaledThickness);
448 The numbers hardcoded into here, what are they?
449 I believe they are pixels.
451 // Draw extension lines (if certain type)
452 painter->DrawLine(p3, p5);
453 painter->DrawLine(p4, p6);
455 // Calculate whether or not the arrowheads are too crowded to put inside
456 // the extension lines. 9.0 is the length of the arrowhead.
457 double t = Geometry::ParameterOfLineAndPoint(linePt1, linePt2, linePt2 - (unit * 9.0 * scaledThickness));
458 //printf("Dimension::Draw(): t = %lf\n", t);
460 // On the screen, it's acting like this is actually 58%...
461 // This is correct, we want it to happen at > 50%
464 // Draw main dimension line + arrowheads
465 painter->DrawLine(p1, p2);
466 painter->DrawArrowhead(p1, p2, scaledThickness);
467 painter->DrawArrowhead(p2, p1, scaledThickness);
471 // Draw outside arrowheads
472 Point p7 = p1 - (unit * 9.0 * scaledThickness);
473 Point p8 = p2 + (unit * 9.0 * scaledThickness);
474 painter->DrawArrowhead(p1, p7, scaledThickness);
475 painter->DrawArrowhead(p2, p8, scaledThickness);
476 painter->DrawLine(p1, p1 - (unit * 14.0 * scaledThickness));
477 painter->DrawLine(p2, p2 + (unit * 14.0 * scaledThickness));
480 // Draw length of dimension line...
481 painter->SetFont(QFont("Arial", 8.0 * Global::zoom * scaledThickness));
482 Point ctr = p2 + (Vector(p2, p1) / 2.0);
485 QString dimText = QString("%1\"").arg(Vector(endpoint - position).Magnitude());
490 dimText = QString("%1\"").arg(length);
493 double feet = (double)((int)length / 12);
494 double inches = length - (feet * 12.0);
497 dimText = QString("%1'").arg(feet);
499 dimText = QString("%1' %2\"").arg(feet).arg(inches);
503 painter->DrawAngledText(ctr, angle, dimText, scaledThickness);
509 Text * t = (Text *)obj;
510 painter->DrawTextObject(t->p[0], t->s.c_str(), scaledThickness);
520 void DrawingView::AddHoveredToSelection(void)
522 std::vector<void *>::iterator i;
524 for(i=document.objects.begin(); i!=document.objects.end(); i++)
526 if (((Object *)(*i))->hovered)
527 ((Object *)(*i))->selected = true;
532 void DrawingView::GetSelection(std::vector<void *> & v)
535 std::vector<void *>::iterator i;
537 for(i=document.objects.begin(); i!=document.objects.end(); i++)
539 if (((Object *)(*i))->selected)
545 void DrawingView::GetHovered(std::vector<void *> & v)
548 std::vector<void *>::iterator i;
550 for(i=document.objects.begin(); i!=document.objects.end(); i++)
552 if (((Object *)(*i))->hovered)
554 //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"));
561 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
563 Global::screenSize = Vector(size().width(), size().height());
564 UpdateGridBackground();
568 void DrawingView::ToolHandler(int mode, Point p)
570 if (Global::tool == TTLine)
571 LineHandler(mode, p);
572 else if (Global::tool == TTCircle)
573 CircleHandler(mode, p);
574 else if (Global::tool == TTArc)
576 else if (Global::tool == TTRotate)
577 RotateHandler(mode, p);
581 void DrawingView::ToolDraw(Painter * painter)
583 if (Global::tool == TTLine)
585 if (Global::toolState == TSNone)
587 painter->DrawHandle(toolPoint[0]);
589 else if ((Global::toolState == TSPoint2) && shiftDown)
591 painter->DrawHandle(toolPoint[1]);
595 painter->DrawLine(toolPoint[0], toolPoint[1]);
596 painter->DrawHandle(toolPoint[1]);
598 Vector v(toolPoint[0], toolPoint[1]);
599 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
600 double absLength = v.Magnitude();
601 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
602 informativeText = text.arg(absLength).arg(absAngle);
605 else if (Global::tool == TTCircle)
607 if (Global::toolState == TSNone)
609 painter->DrawHandle(toolPoint[0]);
611 else if ((Global::toolState == TSPoint2) && shiftDown)
613 painter->DrawHandle(toolPoint[1]);
617 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
618 // painter->DrawLine(toolPoint[0], toolPoint[1]);
619 // painter->DrawHandle(toolPoint[1]);
620 painter->SetBrush(QBrush(Qt::NoBrush));
621 painter->DrawEllipse(toolPoint[0], length, length);
622 QString text = tr("Radius: %1 in.");//\n") + QChar(0x2221) + tr(": %2");
623 informativeText = text.arg(length);//.arg(absAngle);
626 else if (Global::tool == TTArc)
628 if (Global::toolState == TSNone)
630 painter->DrawHandle(toolPoint[0]);
632 else if (Global::toolState == TSPoint2)
634 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
635 painter->SetBrush(QBrush(Qt::NoBrush));
636 painter->DrawEllipse(toolPoint[0], length, length);
637 painter->DrawLine(toolPoint[0], toolPoint[1]);
638 painter->DrawHandle(toolPoint[1]);
639 QString text = tr("Radius: %1 in.");
640 informativeText = text.arg(length);
642 else if (Global::toolState == TSPoint3)
644 double angle = Vector::Angle(toolPoint[0], toolPoint[2]);
645 painter->DrawLine(toolPoint[0], toolPoint[2]);
646 painter->SetBrush(QBrush(Qt::NoBrush));
647 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
648 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
649 QString text = tr("Angle start: %1") + QChar(0x00B0);
650 informativeText = text.arg(RADIANS_TO_DEGREES * angle);
654 double angle = Vector::Angle(toolPoint[0], toolPoint[3]);
655 double span = angle - toolPoint[2].x;
660 painter->DrawLine(toolPoint[0], toolPoint[3]);
661 painter->SetBrush(QBrush(Qt::NoBrush));
662 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
663 painter->SetPen(0xFF00FF, 2.0, LSSolid);
664 painter->DrawArc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
665 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
666 QString text = tr("Arc span: %1") + QChar(0x00B0);
667 informativeText = text.arg(RADIANS_TO_DEGREES * span);
670 else if (Global::tool == TTRotate)
672 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
673 painter->DrawHandle(toolPoint[0]);
674 else if ((Global::toolState == TSPoint2) && shiftDown)
675 painter->DrawHandle(toolPoint[1]);
678 if (toolPoint[0] == toolPoint[1])
681 painter->DrawLine(toolPoint[0], toolPoint[1]);
682 // Likely we need a tool container for this... (now we do!)
686 painter->SetPen(0x00FF00, 2.0, LSSolid);
687 overrideColor = true;
690 RenderObjects(painter, toolObjects);
691 overrideColor = false;
694 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
696 QString text = QChar(0x2221) + QObject::tr(": %1");
697 informativeText = text.arg(absAngle);
700 informativeText += " (Copy)";
702 // painter->DrawInformativeText(text);
708 void DrawingView::LineHandler(int mode, Point p)
713 if (Global::toolState == TSNone)
720 if (Global::toolState == TSNone)
727 if (Global::toolState == TSNone)
729 Global::toolState = TSPoint2;
730 // Prevent spurious line from drawing...
731 toolPoint[1] = toolPoint[0];
733 else if ((Global::toolState == TSPoint2) && shiftDown)
735 // Key override is telling us to make a new line, not continue the
737 toolPoint[0] = toolPoint[1];
741 Line * l = new Line(toolPoint[0], toolPoint[1]);
742 document.objects.push_back(l);
743 toolPoint[0] = toolPoint[1];
749 void DrawingView::CircleHandler(int mode, Point p)
754 if (Global::toolState == TSNone)
761 if (Global::toolState == TSNone)
768 if (Global::toolState == TSNone)
770 Global::toolState = TSPoint2;
771 // Prevent spurious line from drawing...
772 toolPoint[1] = toolPoint[0];
774 else if ((Global::toolState == TSPoint2) && shiftDown)
776 // Key override is telling us to make a new line, not continue the
778 toolPoint[0] = toolPoint[1];
782 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
783 Circle * c = new Circle(toolPoint[0], length);
784 document.objects.push_back(c);
785 toolPoint[0] = toolPoint[1];
786 Global::toolState = TSNone;
792 void DrawingView::ArcHandler(int mode, Point p)
797 if (Global::toolState == TSNone)
799 else if (Global::toolState == TSPoint2)
801 else if (Global::toolState == TSPoint3)
808 if (Global::toolState == TSNone)
810 else if (Global::toolState == TSPoint2)
812 else if (Global::toolState == TSPoint3)
819 if (Global::toolState == TSNone)
821 // Prevent spurious line from drawing...
822 toolPoint[1] = toolPoint[0];
823 Global::toolState = TSPoint2;
825 else if (Global::toolState == TSPoint2)
829 // Key override is telling us to start circle at new center, not
830 // continue the current one.
831 toolPoint[0] = toolPoint[1];
835 // Set the radius in toolPoint[1].x
836 toolPoint[1].x = Vector::Magnitude(toolPoint[0], toolPoint[1]);
837 Global::toolState = TSPoint3;
839 else if (Global::toolState == TSPoint3)
841 // Set the angle in toolPoint[2].x
842 toolPoint[2].x = Vector::Angle(toolPoint[0], toolPoint[2]);
843 Global::toolState = TSPoint4;
847 double endAngle = Vector::Angle(toolPoint[0], toolPoint[3]);
848 double span = endAngle - toolPoint[2].x;
853 Arc * arc = new Arc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
854 document.objects.push_back(arc);
855 Global::toolState = TSNone;
861 void DrawingView::RotateHandler(int mode, Point p)
866 if (Global::toolState == TSNone)
869 SavePointsFrom(select, toolScratch);
870 Global::toolState = TSPoint1;
872 else if (Global::toolState == TSPoint1)
879 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
881 else if (Global::toolState == TSPoint2)
888 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
889 std::vector<void *>::iterator j = select.begin();
890 std::vector<Object>::iterator i = toolScratch.begin();
892 for(; i!=toolScratch.end(); i++, j++)
895 Point p1 = Geometry::RotatePointAroundPoint(obj.p[0], toolPoint[0], angle);
896 Point p2 = Geometry::RotatePointAroundPoint(obj.p[1], toolPoint[0], angle);
897 Object * obj2 = (Object *)(*j);
901 if (obj.type == OTArc)
903 obj2->angle[0] = obj.angle[0] + angle;
905 if (obj2->angle[0] > PI_TIMES_2)
906 obj2->angle[0] -= PI_TIMES_2;
913 if (Global::toolState == TSPoint1)
915 Global::toolState = TSPoint2;
916 // Prevent spurious line from drawing...
917 toolPoint[1] = toolPoint[0];
919 else if ((Global::toolState == TSPoint2) && shiftDown)
921 // Key override is telling us to make a new line, not continue the
923 toolPoint[0] = toolPoint[1];
927 // Either we're finished with our rotate, or we're stamping a copy.
930 // Stamp a copy of the selection at the current rotation & bail
931 std::vector<void *> temp;
932 CopyObjects(select, temp);
934 AddObjectsTo(document.objects, temp);
935 RestorePointsTo(select, toolScratch);
940 Global::toolState = TSPoint1;
941 SavePointsFrom(select, toolScratch);
946 // Reset the selection if shift held down...
948 RestorePointsTo(select, toolScratch);
952 // Reset selection when key is let up
954 RotateHandler(ToolMouseMove, toolPoint[1]);
958 RestorePointsTo(select, toolScratch);
963 void DrawingView::mousePressEvent(QMouseEvent * event)
965 if (event->button() == Qt::LeftButton)
967 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
969 // Handle tool processing, if any
972 if (Global::snapToGrid)
973 point = SnapPointToGrid(point);
975 //Also, may want to figure out if hovering over a snap point on an object,
976 //snap to grid if not.
977 // Snap to object point if valid...
978 // if (Global::snapPointIsValid)
979 // point = Global::snapPoint;
981 ToolHandler(ToolMouseDown, point);
985 // Clear the selection only if CTRL isn't being held on click
987 ClearSelected(document.objects);
990 // If any objects are being hovered on click, add them to the selection
994 AddHoveredToSelection();
995 update(); // needed??
996 GetHovered(hover); // prolly needed
998 // Needed for grab & moving objects
999 // We do it *after*... why? (doesn't seem to confer any advantage...)
1000 if (Global::snapToGrid)
1001 oldPoint = SnapPointToGrid(point);
1006 // Didn't hit any object and not using a tool, so do a selection rectangle
1007 Global::selectionInProgress = true;
1008 Global::selection.setTopLeft(QPointF(point.x, point.y));
1009 Global::selection.setBottomRight(QPointF(point.x, point.y));
1011 else if (event->button() == Qt::MiddleButton)
1014 oldPoint = Vector(event->x(), event->y());
1015 // Should also change the mouse pointer as well...
1016 setCursor(Qt::SizeAllCursor);
1021 void DrawingView::mouseMoveEvent(QMouseEvent * event)
1023 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1024 Global::selection.setBottomRight(QPointF(point.x, point.y));
1025 // Only needs to be done here, as mouse down is always preceded by movement
1026 Global::snapPointIsValid = false;
1029 if (event->buttons() & Qt::MiddleButton)
1031 point = Vector(event->x(), event->y());
1032 // Since we're using Qt coords for scrolling, we have to adjust them here to
1033 // conform to Cartesian coords, since the origin is using Cartesian. :-)
1034 Vector delta(oldPoint, point);
1035 delta /= Global::zoom;
1037 Global::origin -= delta;
1039 UpdateGridBackground();
1045 // If we're doing a selection rect, see if any objects are engulfed by it
1046 // (implies left mouse button held down)
1047 if (Global::selectionInProgress)
1049 CheckObjectBounds();
1054 // Handle object movement (left button down & over an object)
1055 if ((event->buttons() & Qt::LeftButton) && numHovered && !Global::tool)
1057 if (Global::snapToGrid)
1058 point = SnapPointToGrid(point);
1060 HandleObjectMovement(point);
1066 // Do object hit testing...
1067 bool needUpdate = HitTestObjects(point);
1069 // Do tool handling, if any are active...
1072 if (Global::snapToGrid)
1073 point = SnapPointToGrid(point);
1075 ToolHandler(ToolMouseMove, point);
1078 // This is used to draw the tool crosshair...
1081 if (needUpdate || Global::tool)
1086 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
1088 if (event->button() == Qt::LeftButton)
1090 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
1091 //could set it up to use the document's update function (assumes that all object updates
1092 //are being reported correctly:
1093 // if (document.NeedsUpdate())
1094 // Do an update if collided with at least *one* object in the document
1100 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1101 ToolHandler(ToolMouseUp, point);
1105 if (Global::selectionInProgress)
1106 Global::selectionInProgress = false;
1108 informativeText.clear();
1109 // Should we be doing this automagically? Hmm...
1110 // Clear our vectors
1115 std::vector<void *>::iterator i;
1117 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1119 if (((Object *)(*i))->selected)
1120 select.push_back(*i);
1122 //hmm, this is no good, too late to do any good :-P
1123 // if ((*i)->hovered)
1124 // hover.push_back(*i);
1127 else if (event->button() == Qt::MiddleButton)
1130 setCursor(Qt::ArrowCursor);
1135 void DrawingView::wheelEvent(QWheelEvent * event)
1137 double zoomFactor = 1.25;
1138 QSize sizeWin = size();
1139 Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
1140 center = Painter::QtToCartesianCoords(center);
1142 // This is not centering for some reason. Need to figure out why. :-/
1143 if (event->delta() > 0)
1145 Vector newOrigin = center - ((center - Global::origin) / zoomFactor);
1146 Global::origin = newOrigin;
1147 Global::zoom *= zoomFactor;
1151 Vector newOrigin = center + ((-center + Global::origin) * zoomFactor);
1152 Global::origin = newOrigin;
1153 Global::zoom /= zoomFactor;
1156 // Global::gridSpacing = gridPixels / Painter::zoom;
1157 // UpdateGridBackground();
1158 SetGridSize(Global::gridSpacing * Global::zoom);
1160 // zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Global::gridSpacing));
1164 void DrawingView::keyPressEvent(QKeyEvent * event)
1166 bool oldShift = shiftDown;
1167 bool oldCtrl = ctrlDown;
1169 if (event->key() == Qt::Key_Shift)
1171 else if (event->key() == Qt::Key_Control)
1174 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1177 ToolHandler(ToolKeyDown, Point(0, 0));
1184 void DrawingView::keyReleaseEvent(QKeyEvent * event)
1186 bool oldShift = shiftDown;
1187 bool oldCtrl = ctrlDown;
1189 if (event->key() == Qt::Key_Shift)
1191 else if (event->key() == Qt::Key_Control)
1194 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1197 ToolHandler(ToolKeyUp, Point(0, 0));
1205 // This looks strange, but it's really quite simple: We want a point that's
1206 // more than half-way to the next grid point to snap there while conversely we
1207 // want a point that's less than half-way to to the next grid point then snap
1208 // to the one before it. So we add half of the grid spacing to the point, then
1209 // divide by it so that we can remove the fractional part, then multiply it
1210 // back to get back to the correct answer.
1212 Point DrawingView::SnapPointToGrid(Point point)
1214 point += Global::gridSpacing / 2.0; // *This* adds to Z!!!
1215 point /= Global::gridSpacing;
1216 point.x = floor(point.x);//need to fix this for negative numbers...
1217 point.y = floor(point.y);
1218 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
1219 point *= Global::gridSpacing;
1224 void DrawingView::CheckObjectBounds(void)
1226 std::vector<void *>::iterator i;
1229 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1231 Object * obj = (Object *)(*i);
1232 obj->selected = false;
1238 Line * l = (Line *)obj;
1240 if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
1247 Circle * c = (Circle *)obj;
1249 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]))
1256 Arc * a = (Arc *)obj;
1258 double start = a->angle[0];
1259 double end = start + a->angle[1];
1260 QPointF p1(cos(start), sin(start));
1261 QPointF p2(cos(end), sin(end));
1262 QRectF bounds(p1, p2);
1265 // Swap X/Y coordinates if they're backwards...
1266 if (bounds.left() > bounds.right())
1268 double temp = bounds.left();
1269 bounds.setLeft(bounds.right());
1270 bounds.setRight(temp);
1273 if (bounds.bottom() > bounds.top())
1275 double temp = bounds.bottom();
1276 bounds.setBottom(bounds.top());
1277 bounds.setTop(temp);
1280 // Doesn't work as advertised! For shame!
1281 bounds = bounds.normalized();
1284 // If the end of the arc is before the beginning, add 360 degrees to it
1288 // Adjust the bounds depending on which axes are crossed
1289 if ((start < PI_OVER_2) && (end > PI_OVER_2))
1292 if ((start < PI) && (end > PI))
1293 bounds.setLeft(-1.0);
1295 if ((start < (PI + PI_OVER_2)) && (end > (PI + PI_OVER_2)))
1296 bounds.setBottom(-1.0);
1298 if ((start < (2.0 * PI)) && (end > (2.0 * PI)))
1299 bounds.setRight(1.0);
1301 if ((start < ((2.0 * PI) + PI_OVER_2)) && (end > ((2.0 * PI) + PI_OVER_2)))
1304 if ((start < (3.0 * PI)) && (end > (3.0 * PI)))
1305 bounds.setLeft(-1.0);
1307 if ((start < ((3.0 * PI) + PI_OVER_2)) && (end > ((3.0 * PI) + PI_OVER_2)))
1308 bounds.setBottom(-1.0);
1310 bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
1311 bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
1312 bounds.translate(a->p[0].x, a->p[0].y);
1314 if (Global::selection.contains(bounds))
1329 bool DrawingView::HitTestObjects(Point point)
1331 std::vector<void *>::iterator i;
1333 bool needUpdate = false;
1335 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1337 Object * obj = (Object *)(*i);
1343 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
1344 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
1345 Vector lineSegment = obj->p[1] - obj->p[0];
1346 Vector v1 = point - obj->p[0];
1347 Vector v2 = point - obj->p[1];
1348 double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
1352 distance = v1.Magnitude();
1354 distance = v2.Magnitude();
1356 // distance = ?Det?(ls, v1) / |ls|
1357 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
1358 / lineSegment.Magnitude());
1360 if ((v1.Magnitude() * Global::zoom) < 8.0)
1361 obj->hitPoint[0] = true;
1362 else if ((v2.Magnitude() * Global::zoom) < 8.0)
1363 obj->hitPoint[1] = true;
1364 else if ((distance * Global::zoom) < 5.0)
1365 obj->hitObject = true;
1367 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
1369 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
1376 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
1377 obj->hitPoint[0] = obj->hitObject = false;
1378 double length = Vector::Magnitude(obj->p[0], point);
1380 if ((length * Global::zoom) < 8.0)
1381 obj->hitPoint[0] = true;
1382 else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
1383 obj->hitObject = true;
1385 obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
1387 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
1394 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
1395 obj->hitPoint[0] = obj->hitObject = false;
1396 double length = Vector::Magnitude(obj->p[0], point);
1397 double angle = Vector::Angle(obj->p[0], point);
1399 // Make sure we get the angle in the correct spot
1400 if (angle < obj->angle[0])
1401 angle += PI_TIMES_2;
1403 // Get the span that we're pointing at...
1404 double span = angle - obj->angle[0];
1406 // N.B.: Still need to hit test the arc start & arc span handles...
1408 if ((length * Global::zoom) < 8.0)
1409 obj->hitPoint[0] = true;
1410 else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
1411 obj->hitObject = true;
1413 obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
1415 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
1427 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
1435 void DrawingView::HandleObjectMovement(Point point)
1437 Point delta = point - oldPoint;
1438 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
1439 Object * obj = (Object *)hover[0];
1440 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
1441 //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"));
1446 if (obj->hitPoint[0])
1448 else if (obj->hitPoint[1])
1450 else if (obj->hitObject)
1458 if (obj->hitPoint[0])
1460 else if (obj->hitObject)
1462 //this doesn't work. we need to save this on mouse down for this to work correctly!
1463 // double oldRadius = obj->radius[0];
1464 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
1466 QString text = QObject::tr("Radius: %1");//\nScale: %2%");
1467 informativeText = text.arg(obj->radius[0], 0, 'd', 4);//.arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0);
1479 // This returns true if we've moved over an object...
1480 if (document.PointerMoved(point)) // <-- This
1481 // This is where the object would do automagic dragging & shit. Since we don't
1482 // do that anymore, we need a strategy to handle it.
1486 Now objects handle mouse move snapping as well. The code below mainly works only
1487 for tools; we need to fix it so that objects work as well...
1489 There's a problem with the object point snapping in that it's dependent on the
1490 order of the objects in the document. Most likely this is because it counts the
1491 selected object last and thus fucks up the algorithm. Need to fix this...
1495 // Do object snapping here. Grid snapping on mouse down is done in the
1496 // objects themselves, only because we have to hit test the raw point,
1497 // not the snapped point. There has to be a better way...!
1498 if (document.penultimateObjectHovered)
1500 // Two objects are hovered, see if we have an intersection point
1501 if ((document.lastObjectHovered->type == OTLine) && (document.penultimateObjectHovered->type == OTLine))
1504 int n = Geometry::Intersects((Line *)document.lastObjectHovered, (Line *)document.penultimateObjectHovered, &t);
1508 Global::snapPoint = document.lastObjectHovered->GetPointAtParameter(t);
1509 Global::snapPointIsValid = true;
1512 else if ((document.lastObjectHovered->type == OTCircle) && (document.penultimateObjectHovered->type == OTCircle))
1515 int n = Geometry::Intersects((Circle *)document.lastObjectHovered, (Circle *)document.penultimateObjectHovered, 0, 0, 0, 0, &p1, &p2);
1519 Global::snapPoint = p1;
1520 Global::snapPointIsValid = true;
1524 double d1 = Vector(point, p1).Magnitude();
1525 double d2 = Vector(point, p2).Magnitude();
1528 Global::snapPoint = p1;
1530 Global::snapPoint = p2;
1532 Global::snapPointIsValid = true;
1538 // Otherwise, it was a single object hovered...
1544 if (Global::snapToGrid)
1545 point = Global::SnapPointToGrid(point);
1547 // We always snap to object points, and they take precendence over
1549 if (Global::snapPointIsValid)
1550 point = Global::snapPoint;
1552 toolAction->MouseMoved(point);