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]);
380 if (obj->hitPoint[0])
381 painter->DrawHandle(obj->p[0]);
383 if (obj->hitPoint[1])
384 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]));
386 if (obj->hitPoint[2])
387 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0] + obj->angle[1]), sin(obj->angle[0] + obj->angle[1])) * obj->radius[0]));
392 Dimension * d = (Dimension *)obj;
394 Vector v(d->p[0], d->p[1]);
395 double angle = v.Angle();
396 Vector unit = v.Unit();
397 Vector linePt1 = d->p[0], linePt2 = d->p[1];
399 double x1, y1, length;
401 if (d->subtype == DTLinearVert)
403 if ((angle < 0) || (angle > PI))
405 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
406 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
407 ortho = Vector(1.0, 0);
412 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
413 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
414 ortho = Vector(-1.0, 0);
418 linePt1.x = linePt2.x = x1;
419 length = fabs(d->p[0].y - d->p[1].y);
421 else if (d->subtype == DTLinearHorz)
423 if ((angle < PI_OVER_2) || (angle > PI3_OVER_2))
425 x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
426 y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
427 ortho = Vector(0, 1.0);
432 x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
433 y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
434 ortho = Vector(0, -1.0);
438 linePt1.y = linePt2.y = y1;
439 length = fabs(d->p[0].x - d->p[1].x);
441 else if (d->subtype == DTLinear)
443 angle = Vector(linePt1, linePt2).Angle();
444 ortho = Vector::Normal(linePt1, linePt2);
445 length = v.Magnitude();
448 unit = Vector(linePt1, linePt2).Unit();
450 Point p1 = linePt1 + (ortho * 10.0 * scaledThickness);
451 Point p2 = linePt2 + (ortho * 10.0 * scaledThickness);
452 Point p3 = linePt1 + (ortho * 16.0 * scaledThickness);
453 Point p4 = linePt2 + (ortho * 16.0 * scaledThickness);
454 Point p5 = d->p[0] + (ortho * 4.0 * scaledThickness);
455 Point p6 = d->p[1] + (ortho * 4.0 * scaledThickness);
458 The numbers hardcoded into here, what are they?
459 I believe they are pixels.
461 // Draw extension lines (if certain type)
462 painter->DrawLine(p3, p5);
463 painter->DrawLine(p4, p6);
465 // Calculate whether or not the arrowheads are too crowded to put inside
466 // the extension lines. 9.0 is the length of the arrowhead.
467 double t = Geometry::ParameterOfLineAndPoint(linePt1, linePt2, linePt2 - (unit * 9.0 * scaledThickness));
468 //printf("Dimension::Draw(): t = %lf\n", t);
470 // On the screen, it's acting like this is actually 58%...
471 // This is correct, we want it to happen at > 50%
474 // Draw main dimension line + arrowheads
475 painter->DrawLine(p1, p2);
476 painter->DrawArrowhead(p1, p2, scaledThickness);
477 painter->DrawArrowhead(p2, p1, scaledThickness);
481 // Draw outside arrowheads
482 Point p7 = p1 - (unit * 9.0 * scaledThickness);
483 Point p8 = p2 + (unit * 9.0 * scaledThickness);
484 painter->DrawArrowhead(p1, p7, scaledThickness);
485 painter->DrawArrowhead(p2, p8, scaledThickness);
486 painter->DrawLine(p1, p1 - (unit * 14.0 * scaledThickness));
487 painter->DrawLine(p2, p2 + (unit * 14.0 * scaledThickness));
490 // Draw length of dimension line...
491 painter->SetFont(QFont("Arial", 8.0 * Global::zoom * scaledThickness));
492 Point ctr = p2 + (Vector(p2, p1) / 2.0);
495 QString dimText = QString("%1\"").arg(Vector(endpoint - position).Magnitude());
500 dimText = QString("%1\"").arg(length);
503 double feet = (double)((int)length / 12);
504 double inches = length - (feet * 12.0);
507 dimText = QString("%1'").arg(feet);
509 dimText = QString("%1' %2\"").arg(feet).arg(inches);
513 painter->DrawAngledText(ctr, angle, dimText, scaledThickness);
519 Text * t = (Text *)obj;
520 painter->DrawTextObject(t->p[0], t->s.c_str(), scaledThickness);
530 void DrawingView::AddHoveredToSelection(void)
532 std::vector<void *>::iterator i;
534 for(i=document.objects.begin(); i!=document.objects.end(); i++)
536 if (((Object *)(*i))->hovered)
537 ((Object *)(*i))->selected = true;
542 void DrawingView::GetSelection(std::vector<void *> & v)
545 std::vector<void *>::iterator i;
547 for(i=document.objects.begin(); i!=document.objects.end(); i++)
549 if (((Object *)(*i))->selected)
555 void DrawingView::GetHovered(std::vector<void *> & v)
558 std::vector<void *>::iterator i;
560 for(i=document.objects.begin(); i!=document.objects.end(); i++)
562 if (((Object *)(*i))->hovered)
564 //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"));
571 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
573 Global::screenSize = Vector(size().width(), size().height());
574 UpdateGridBackground();
578 void DrawingView::ToolHandler(int mode, Point p)
580 if (Global::tool == TTLine)
581 LineHandler(mode, p);
582 else if (Global::tool == TTCircle)
583 CircleHandler(mode, p);
584 else if (Global::tool == TTArc)
586 else if (Global::tool == TTRotate)
587 RotateHandler(mode, p);
591 void DrawingView::ToolDraw(Painter * painter)
593 if (Global::tool == TTLine)
595 if (Global::toolState == TSNone)
597 painter->DrawHandle(toolPoint[0]);
599 else if ((Global::toolState == TSPoint2) && shiftDown)
601 painter->DrawHandle(toolPoint[1]);
605 painter->DrawLine(toolPoint[0], toolPoint[1]);
606 painter->DrawHandle(toolPoint[1]);
608 Vector v(toolPoint[0], toolPoint[1]);
609 double absAngle = v.Angle() * RADIANS_TO_DEGREES;
610 double absLength = v.Magnitude();
611 QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
612 informativeText = text.arg(absLength).arg(absAngle);
615 else if (Global::tool == TTCircle)
617 if (Global::toolState == TSNone)
619 painter->DrawHandle(toolPoint[0]);
621 else if ((Global::toolState == TSPoint2) && shiftDown)
623 painter->DrawHandle(toolPoint[1]);
627 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
628 // painter->DrawLine(toolPoint[0], toolPoint[1]);
629 // painter->DrawHandle(toolPoint[1]);
630 painter->SetBrush(QBrush(Qt::NoBrush));
631 painter->DrawEllipse(toolPoint[0], length, length);
632 QString text = tr("Radius: %1 in.");//\n") + QChar(0x2221) + tr(": %2");
633 informativeText = text.arg(length);//.arg(absAngle);
636 else if (Global::tool == TTArc)
638 if (Global::toolState == TSNone)
640 painter->DrawHandle(toolPoint[0]);
642 else if (Global::toolState == TSPoint2)
644 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
645 painter->SetBrush(QBrush(Qt::NoBrush));
646 painter->DrawEllipse(toolPoint[0], length, length);
647 painter->DrawLine(toolPoint[0], toolPoint[1]);
648 painter->DrawHandle(toolPoint[1]);
649 QString text = tr("Radius: %1 in.");
650 informativeText = text.arg(length);
652 else if (Global::toolState == TSPoint3)
654 double angle = Vector::Angle(toolPoint[0], toolPoint[2]);
655 painter->DrawLine(toolPoint[0], toolPoint[2]);
656 painter->SetBrush(QBrush(Qt::NoBrush));
657 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
658 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
659 QString text = tr("Angle start: %1") + QChar(0x00B0);
660 informativeText = text.arg(RADIANS_TO_DEGREES * angle);
664 double angle = Vector::Angle(toolPoint[0], toolPoint[3]);
665 double span = angle - toolPoint[2].x;
670 painter->DrawLine(toolPoint[0], toolPoint[3]);
671 painter->SetBrush(QBrush(Qt::NoBrush));
672 painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
673 painter->SetPen(0xFF00FF, 2.0, LSSolid);
674 painter->DrawArc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
675 painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
676 QString text = tr("Arc span: %1") + QChar(0x00B0);
677 informativeText = text.arg(RADIANS_TO_DEGREES * span);
680 else if (Global::tool == TTRotate)
682 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
683 painter->DrawHandle(toolPoint[0]);
684 else if ((Global::toolState == TSPoint2) && shiftDown)
685 painter->DrawHandle(toolPoint[1]);
688 if (toolPoint[0] == toolPoint[1])
691 painter->DrawLine(toolPoint[0], toolPoint[1]);
692 // Likely we need a tool container for this... (now we do!)
696 painter->SetPen(0x00FF00, 2.0, LSSolid);
697 overrideColor = true;
700 RenderObjects(painter, toolObjects);
701 overrideColor = false;
704 double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
706 QString text = QChar(0x2221) + QObject::tr(": %1");
707 informativeText = text.arg(absAngle);
710 informativeText += " (Copy)";
712 // painter->DrawInformativeText(text);
718 void DrawingView::LineHandler(int mode, Point p)
723 if (Global::toolState == TSNone)
730 if (Global::toolState == TSNone)
737 if (Global::toolState == TSNone)
739 Global::toolState = TSPoint2;
740 // Prevent spurious line from drawing...
741 toolPoint[1] = toolPoint[0];
743 else if ((Global::toolState == TSPoint2) && shiftDown)
745 // Key override is telling us to make a new line, not continue the
747 toolPoint[0] = toolPoint[1];
751 Line * l = new Line(toolPoint[0], toolPoint[1]);
752 document.objects.push_back(l);
753 toolPoint[0] = toolPoint[1];
759 void DrawingView::CircleHandler(int mode, Point p)
764 if (Global::toolState == TSNone)
771 if (Global::toolState == TSNone)
778 if (Global::toolState == TSNone)
780 Global::toolState = TSPoint2;
781 // Prevent spurious line from drawing...
782 toolPoint[1] = toolPoint[0];
784 else if ((Global::toolState == TSPoint2) && shiftDown)
786 // Key override is telling us to make a new line, not continue the
788 toolPoint[0] = toolPoint[1];
792 double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
793 Circle * c = new Circle(toolPoint[0], length);
794 document.objects.push_back(c);
795 toolPoint[0] = toolPoint[1];
796 Global::toolState = TSNone;
802 void DrawingView::ArcHandler(int mode, Point p)
807 if (Global::toolState == TSNone)
809 else if (Global::toolState == TSPoint2)
811 else if (Global::toolState == TSPoint3)
818 if (Global::toolState == TSNone)
820 else if (Global::toolState == TSPoint2)
822 else if (Global::toolState == TSPoint3)
829 if (Global::toolState == TSNone)
831 // Prevent spurious line from drawing...
832 toolPoint[1] = toolPoint[0];
833 Global::toolState = TSPoint2;
835 else if (Global::toolState == TSPoint2)
839 // Key override is telling us to start circle at new center, not
840 // continue the current one.
841 toolPoint[0] = toolPoint[1];
845 // Set the radius in toolPoint[1].x
846 toolPoint[1].x = Vector::Magnitude(toolPoint[0], toolPoint[1]);
847 Global::toolState = TSPoint3;
849 else if (Global::toolState == TSPoint3)
851 // Set the angle in toolPoint[2].x
852 toolPoint[2].x = Vector::Angle(toolPoint[0], toolPoint[2]);
853 Global::toolState = TSPoint4;
857 double endAngle = Vector::Angle(toolPoint[0], toolPoint[3]);
858 double span = endAngle - toolPoint[2].x;
863 Arc * arc = new Arc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
864 document.objects.push_back(arc);
865 Global::toolState = TSNone;
871 void DrawingView::RotateHandler(int mode, Point p)
876 if (Global::toolState == TSNone)
879 SavePointsFrom(select, toolScratch);
880 Global::toolState = TSPoint1;
882 else if (Global::toolState == TSPoint1)
889 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
891 else if (Global::toolState == TSPoint2)
898 double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
899 std::vector<void *>::iterator j = select.begin();
900 std::vector<Object>::iterator i = toolScratch.begin();
902 for(; i!=toolScratch.end(); i++, j++)
905 Point p1 = Geometry::RotatePointAroundPoint(obj.p[0], toolPoint[0], angle);
906 Point p2 = Geometry::RotatePointAroundPoint(obj.p[1], toolPoint[0], angle);
907 Object * obj2 = (Object *)(*j);
911 if (obj.type == OTArc)
913 obj2->angle[0] = obj.angle[0] + angle;
915 if (obj2->angle[0] > PI_TIMES_2)
916 obj2->angle[0] -= PI_TIMES_2;
923 if (Global::toolState == TSPoint1)
925 Global::toolState = TSPoint2;
926 // Prevent spurious line from drawing...
927 toolPoint[1] = toolPoint[0];
929 else if ((Global::toolState == TSPoint2) && shiftDown)
931 // Key override is telling us to make a new line, not continue the
933 toolPoint[0] = toolPoint[1];
937 // Either we're finished with our rotate, or we're stamping a copy.
940 // Stamp a copy of the selection at the current rotation & bail
941 std::vector<void *> temp;
942 CopyObjects(select, temp);
944 AddObjectsTo(document.objects, temp);
945 RestorePointsTo(select, toolScratch);
950 Global::toolState = TSPoint1;
951 SavePointsFrom(select, toolScratch);
956 // Reset the selection if shift held down...
958 RestorePointsTo(select, toolScratch);
962 // Reset selection when key is let up
964 RotateHandler(ToolMouseMove, toolPoint[1]);
968 RestorePointsTo(select, toolScratch);
973 void DrawingView::mousePressEvent(QMouseEvent * event)
975 if (event->button() == Qt::LeftButton)
977 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
979 // Handle tool processing, if any
982 if (Global::snapToGrid)
983 point = SnapPointToGrid(point);
985 //Also, may want to figure out if hovering over a snap point on an object,
986 //snap to grid if not.
987 // Snap to object point if valid...
988 // if (Global::snapPointIsValid)
989 // point = Global::snapPoint;
991 ToolHandler(ToolMouseDown, point);
995 // Clear the selection only if CTRL isn't being held on click
997 ClearSelected(document.objects);
1000 // If any objects are being hovered on click, add them to the selection
1004 AddHoveredToSelection();
1005 update(); // needed??
1006 GetHovered(hover); // prolly needed
1008 // Needed for grab & moving objects
1009 // We do it *after*... why? (doesn't seem to confer any advantage...)
1010 if (Global::snapToGrid)
1011 oldPoint = SnapPointToGrid(point);
1016 // Didn't hit any object and not using a tool, so do a selection rectangle
1017 Global::selectionInProgress = true;
1018 Global::selection.setTopLeft(QPointF(point.x, point.y));
1019 Global::selection.setBottomRight(QPointF(point.x, point.y));
1021 else if (event->button() == Qt::MiddleButton)
1024 oldPoint = Vector(event->x(), event->y());
1025 // Should also change the mouse pointer as well...
1026 setCursor(Qt::SizeAllCursor);
1031 void DrawingView::mouseMoveEvent(QMouseEvent * event)
1033 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1034 Global::selection.setBottomRight(QPointF(point.x, point.y));
1035 // Only needs to be done here, as mouse down is always preceded by movement
1036 Global::snapPointIsValid = false;
1039 if (event->buttons() & Qt::MiddleButton)
1041 point = Vector(event->x(), event->y());
1042 // Since we're using Qt coords for scrolling, we have to adjust them here to
1043 // conform to Cartesian coords, since the origin is using Cartesian. :-)
1044 Vector delta(oldPoint, point);
1045 delta /= Global::zoom;
1047 Global::origin -= delta;
1049 UpdateGridBackground();
1055 // If we're doing a selection rect, see if any objects are engulfed by it
1056 // (implies left mouse button held down)
1057 if (Global::selectionInProgress)
1059 CheckObjectBounds();
1064 // Handle object movement (left button down & over an object)
1065 if ((event->buttons() & Qt::LeftButton) && numHovered && !Global::tool)
1067 if (Global::snapToGrid)
1068 point = SnapPointToGrid(point);
1070 HandleObjectMovement(point);
1076 // Do object hit testing...
1077 bool needUpdate = HitTestObjects(point);
1079 // Do tool handling, if any are active...
1082 if (Global::snapToGrid)
1083 point = SnapPointToGrid(point);
1085 ToolHandler(ToolMouseMove, point);
1088 // This is used to draw the tool crosshair...
1091 if (needUpdate || Global::tool)
1096 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
1098 if (event->button() == Qt::LeftButton)
1100 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
1101 //could set it up to use the document's update function (assumes that all object updates
1102 //are being reported correctly:
1103 // if (document.NeedsUpdate())
1104 // Do an update if collided with at least *one* object in the document
1110 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1111 ToolHandler(ToolMouseUp, point);
1115 if (Global::selectionInProgress)
1116 Global::selectionInProgress = false;
1118 informativeText.clear();
1119 // Should we be doing this automagically? Hmm...
1120 // Clear our vectors
1125 std::vector<void *>::iterator i;
1127 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1129 if (((Object *)(*i))->selected)
1130 select.push_back(*i);
1132 //hmm, this is no good, too late to do any good :-P
1133 // if ((*i)->hovered)
1134 // hover.push_back(*i);
1137 else if (event->button() == Qt::MiddleButton)
1140 setCursor(Qt::ArrowCursor);
1145 void DrawingView::wheelEvent(QWheelEvent * event)
1147 double zoomFactor = 1.25;
1148 QSize sizeWin = size();
1149 Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
1150 center = Painter::QtToCartesianCoords(center);
1152 // This is not centering for some reason. Need to figure out why. :-/
1153 if (event->delta() > 0)
1155 Vector newOrigin = center - ((center - Global::origin) / zoomFactor);
1156 Global::origin = newOrigin;
1157 Global::zoom *= zoomFactor;
1161 Vector newOrigin = center + ((-center + Global::origin) * zoomFactor);
1162 Global::origin = newOrigin;
1163 Global::zoom /= zoomFactor;
1166 // Global::gridSpacing = gridPixels / Painter::zoom;
1167 // UpdateGridBackground();
1168 SetGridSize(Global::gridSpacing * Global::zoom);
1170 // zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Global::gridSpacing));
1174 void DrawingView::keyPressEvent(QKeyEvent * event)
1176 bool oldShift = shiftDown;
1177 bool oldCtrl = ctrlDown;
1179 if (event->key() == Qt::Key_Shift)
1181 else if (event->key() == Qt::Key_Control)
1184 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1187 ToolHandler(ToolKeyDown, Point(0, 0));
1194 void DrawingView::keyReleaseEvent(QKeyEvent * event)
1196 bool oldShift = shiftDown;
1197 bool oldCtrl = ctrlDown;
1199 if (event->key() == Qt::Key_Shift)
1201 else if (event->key() == Qt::Key_Control)
1204 if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1207 ToolHandler(ToolKeyUp, Point(0, 0));
1215 // This looks strange, but it's really quite simple: We want a point that's
1216 // more than half-way to the next grid point to snap there while conversely we
1217 // want a point that's less than half-way to to the next grid point then snap
1218 // to the one before it. So we add half of the grid spacing to the point, then
1219 // divide by it so that we can remove the fractional part, then multiply it
1220 // back to get back to the correct answer.
1222 Point DrawingView::SnapPointToGrid(Point point)
1224 point += Global::gridSpacing / 2.0; // *This* adds to Z!!!
1225 point /= Global::gridSpacing;
1226 point.x = floor(point.x);//need to fix this for negative numbers...
1227 point.y = floor(point.y);
1228 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
1229 point *= Global::gridSpacing;
1234 void DrawingView::CheckObjectBounds(void)
1236 std::vector<void *>::iterator i;
1239 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1241 Object * obj = (Object *)(*i);
1242 obj->selected = false;
1248 Line * l = (Line *)obj;
1250 if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
1257 Circle * c = (Circle *)obj;
1259 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]))
1266 Arc * a = (Arc *)obj;
1268 double start = a->angle[0];
1269 double end = start + a->angle[1];
1270 QPointF p1(cos(start), sin(start));
1271 QPointF p2(cos(end), sin(end));
1272 QRectF bounds(p1, p2);
1275 // Swap X/Y coordinates if they're backwards...
1276 if (bounds.left() > bounds.right())
1278 double temp = bounds.left();
1279 bounds.setLeft(bounds.right());
1280 bounds.setRight(temp);
1283 if (bounds.bottom() > bounds.top())
1285 double temp = bounds.bottom();
1286 bounds.setBottom(bounds.top());
1287 bounds.setTop(temp);
1290 // Doesn't work as advertised! For shame!
1291 bounds = bounds.normalized();
1294 // If the end of the arc is before the beginning, add 360 degrees to it
1298 // Adjust the bounds depending on which axes are crossed
1299 if ((start < PI_OVER_2) && (end > PI_OVER_2))
1302 if ((start < PI) && (end > PI))
1303 bounds.setLeft(-1.0);
1305 if ((start < (PI + PI_OVER_2)) && (end > (PI + PI_OVER_2)))
1306 bounds.setBottom(-1.0);
1308 if ((start < (2.0 * PI)) && (end > (2.0 * PI)))
1309 bounds.setRight(1.0);
1311 if ((start < ((2.0 * PI) + PI_OVER_2)) && (end > ((2.0 * PI) + PI_OVER_2)))
1314 if ((start < (3.0 * PI)) && (end > (3.0 * PI)))
1315 bounds.setLeft(-1.0);
1317 if ((start < ((3.0 * PI) + PI_OVER_2)) && (end > ((3.0 * PI) + PI_OVER_2)))
1318 bounds.setBottom(-1.0);
1320 bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
1321 bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
1322 bounds.translate(a->p[0].x, a->p[0].y);
1324 if (Global::selection.contains(bounds))
1339 bool DrawingView::HitTestObjects(Point point)
1341 std::vector<void *>::iterator i;
1343 bool needUpdate = false;
1345 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1347 Object * obj = (Object *)(*i);
1353 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
1354 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
1355 Vector lineSegment = obj->p[1] - obj->p[0];
1356 Vector v1 = point - obj->p[0];
1357 Vector v2 = point - obj->p[1];
1358 double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
1362 distance = v1.Magnitude();
1364 distance = v2.Magnitude();
1366 // distance = ?Det?(ls, v1) / |ls|
1367 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
1368 / lineSegment.Magnitude());
1370 if ((v1.Magnitude() * Global::zoom) < 8.0)
1371 obj->hitPoint[0] = true;
1372 else if ((v2.Magnitude() * Global::zoom) < 8.0)
1373 obj->hitPoint[1] = true;
1374 else if ((distance * Global::zoom) < 5.0)
1375 obj->hitObject = true;
1377 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
1379 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
1386 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
1387 obj->hitPoint[0] = obj->hitObject = false;
1388 double length = Vector::Magnitude(obj->p[0], point);
1390 if ((length * Global::zoom) < 8.0)
1391 obj->hitPoint[0] = true;
1392 else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
1393 obj->hitObject = true;
1395 obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
1397 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
1404 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
1405 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
1406 double length = Vector::Magnitude(obj->p[0], point);
1407 double angle = Vector::Angle(obj->p[0], point);
1409 // Make sure we get the angle in the correct spot
1410 if (angle < obj->angle[0])
1411 angle += PI_TIMES_2;
1413 // Get the span that we're pointing at...
1414 double span = angle - obj->angle[0];
1416 // N.B.: Still need to hit test the arc start & arc span handles...
1417 double spanAngle = obj->angle[0] + obj->angle[1];
1418 Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
1419 Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
1420 double length2 = Vector::Magnitude(point, handle1);
1421 double length3 = Vector::Magnitude(point, handle2);
1423 if ((length * Global::zoom) < 8.0)
1424 obj->hitPoint[0] = true;
1425 else if ((length2 * Global::zoom) < 8.0)
1426 obj->hitPoint[1] = true;
1427 else if ((length3 * Global::zoom) < 8.0)
1428 obj->hitPoint[2] = true;
1429 else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
1430 obj->hitObject = true;
1432 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
1434 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
1446 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
1454 void DrawingView::HandleObjectMovement(Point point)
1456 Point delta = point - oldPoint;
1457 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
1458 Object * obj = (Object *)hover[0];
1459 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
1460 //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"));
1465 if (obj->hitPoint[0])
1467 else if (obj->hitPoint[1])
1469 else if (obj->hitObject)
1477 if (obj->hitPoint[0])
1479 else if (obj->hitObject)
1481 //this doesn't work. we need to save this on mouse down for this to work correctly!
1482 // double oldRadius = obj->radius[0];
1483 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
1485 QString text = QObject::tr("Radius: %1");//\nScale: %2%");
1486 informativeText = text.arg(obj->radius[0], 0, 'd', 4);//.arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0);
1491 if (obj->hitPoint[0])
1493 else if (obj->hitPoint[1])
1495 // Change the Arc's span (handle #1)
1498 double angle = Vector::Angle(obj->p[0], point);
1499 double delta = angle - obj->angle[0];
1502 delta += PI_TIMES_2;
1504 obj->angle[1] -= delta;
1505 obj->angle[0] = angle;
1507 if (obj->angle[1] < 0)
1508 obj->angle[1] += PI_TIMES_2;
1510 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
1511 informativeText = text.arg(obj->angle[1] * RADIANS_TO_DEGREES, 0, 'd', 4).arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 2).arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 2);
1515 double angle = Vector::Angle(obj->p[0], point);
1516 obj->angle[0] = angle;
1517 QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
1518 informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 4);
1520 else if (obj->hitPoint[2])
1522 // Change the Arc's span (handle #2)
1525 double angle = Vector::Angle(obj->p[0], point);
1526 obj->angle[1] = angle - obj->angle[0];
1528 if (obj->angle[1] < 0)
1529 obj->angle[1] += PI_TIMES_2;
1531 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
1532 informativeText = text.arg(obj->angle[1] * RADIANS_TO_DEGREES, 0, 'd', 4).arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 2).arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 2);
1536 double angle = Vector::Angle(obj->p[0], point);
1537 obj->angle[0] = angle - obj->angle[1];
1539 if (obj->angle[0] < 0)
1540 obj->angle[0] += PI_TIMES_2;
1542 QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
1543 informativeText = text.arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 4);
1545 else if (obj->hitObject)
1552 obj->radius[0] = Vector::Magnitude(obj->p[0], point);
1553 QString text = QObject::tr("Radius: %1");
1554 informativeText = text.arg(obj->radius[0], 0, 'd', 4);
1566 // This returns true if we've moved over an object...
1567 if (document.PointerMoved(point)) // <-- This
1568 // This is where the object would do automagic dragging & shit. Since we don't
1569 // do that anymore, we need a strategy to handle it.
1573 Now objects handle mouse move snapping as well. The code below mainly works only
1574 for tools; we need to fix it so that objects work as well...
1576 There's a problem with the object point snapping in that it's dependent on the
1577 order of the objects in the document. Most likely this is because it counts the
1578 selected object last and thus fucks up the algorithm. Need to fix this...
1582 // Do object snapping here. Grid snapping on mouse down is done in the
1583 // objects themselves, only because we have to hit test the raw point,
1584 // not the snapped point. There has to be a better way...!
1585 if (document.penultimateObjectHovered)
1587 // Two objects are hovered, see if we have an intersection point
1588 if ((document.lastObjectHovered->type == OTLine) && (document.penultimateObjectHovered->type == OTLine))
1591 int n = Geometry::Intersects((Line *)document.lastObjectHovered, (Line *)document.penultimateObjectHovered, &t);
1595 Global::snapPoint = document.lastObjectHovered->GetPointAtParameter(t);
1596 Global::snapPointIsValid = true;
1599 else if ((document.lastObjectHovered->type == OTCircle) && (document.penultimateObjectHovered->type == OTCircle))
1602 int n = Geometry::Intersects((Circle *)document.lastObjectHovered, (Circle *)document.penultimateObjectHovered, 0, 0, 0, 0, &p1, &p2);
1606 Global::snapPoint = p1;
1607 Global::snapPointIsValid = true;
1611 double d1 = Vector(point, p1).Magnitude();
1612 double d2 = Vector(point, p2).Magnitude();
1615 Global::snapPoint = p1;
1617 Global::snapPoint = p2;
1619 Global::snapPointIsValid = true;
1625 // Otherwise, it was a single object hovered...
1631 if (Global::snapToGrid)
1632 point = Global::SnapPointToGrid(point);
1634 // We always snap to object points, and they take precendence over
1636 if (Global::snapPointIsValid)
1637 point = Global::snapPoint;
1639 toolAction->MouseMoved(point);