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"
34 #include "mathconstants.h"
39 #define BACKGROUND_MAX_SIZE 512
42 //Container DrawingView::document(Vector(0, 0));
45 DrawingView::DrawingView(QWidget * parent/*= NULL*/): QWidget(parent),
46 // The value in the settings file will override this.
47 useAntialiasing(true),
48 gridBackground(BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE),
49 scale(1.0), offsetX(-10), offsetY(-10),// document(Vector(0, 0)),
50 gridPixels(0), collided(false)//, toolAction(NULL)
52 // document.isTopLevelContainer = true;
53 //wtf? doesn't work except in c++11??? document = { 0 };
54 setBackgroundRole(QPalette::Base);
55 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
57 Global::gridSpacing = 12.0; // In base units (inch is default)
60 Line * line = new Line(Vector(5, 5), Vector(50, 40), &document);
62 document.Add(new Line(Vector(50, 40), Vector(10, 83), &document));
63 document.Add(new Line(Vector(10, 83), Vector(17, 2), &document));
64 document.Add(new Circle(Vector(100, 100), 36, &document));
65 document.Add(new Circle(Vector(50, 150), 49, &document));
66 document.Add(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3, &document)),
67 document.Add(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5, &document));
69 Dimension * dimension = new Dimension(Vector(0, 0), Vector(0, 0), DTLinear, &document);
70 line->SetDimensionOnLine(dimension);
71 document.Add(dimension);
73 // Alternate way to do the above...
74 line->SetDimensionOnLine();
77 Line * line = new Line;//(Vector(5, 5), Vector(50, 40), &document);
78 line->p1 = Vector(5, 5);
79 line->p2 = Vector(50, 40);
81 line->thickness = 1.0;
82 document.objects.push_back(line);
83 document.objects.push_back(new Line(Vector(50, 40), Vector(10, 83)));
84 document.objects.push_back(new Line(Vector(10, 83), Vector(17, 2)));
85 document.objects.push_back(new Circle(Vector(100, 100), 36));
86 document.objects.push_back(new Circle(Vector(50, 150), 49));
87 document.objects.push_back(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3)),
88 document.objects.push_back(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5));
92 Here we set the grid size in pixels--12 in this case. Initially, we have our
93 zoom set to make this represent 12 inches at a zoom factor of 25%. (This is
94 arbitrary.) So, to be able to decouple the grid size from the zoom, we need
95 to be able to set the size of the background grid (which we do here at an
96 arbitrary 12 pixels) to anything we want (within reason, of course :-).
98 The drawing enforces the grid spacing through the drawing->gridSpacing variable.
100 drawing->gridSpacing = 12.0 / Global::zoom;
102 Global::zoom is the zoom factor for the drawing, and all mouse clicks are
103 translated to Cartesian coordinates through this. (Initially, Global::zoom is
104 set to 1.0. SCREEN_ZOOM is set to 1.0/4.0.)
106 Really, the 100% zoom level can be set at *any* zoom level, it's more of a
107 convenience function than any measure of absolutes. Doing things that way we
108 could rid ourselves of the whole SCREEN_ZOOM parameter and all the attendant
109 shittiness that comes with it.
111 However, it seems that SCREEN_ZOOM is used to make text and arrow sizes show up
112 a certain way, which means we should probably create something else in those
113 objects to take its place--like some kind of scale factor. This would seem to
114 imply that certain point sizes actually *do* tie things like fonts to absolute
115 sizes on the screen, but not necessarily because you could have an inch scale
116 with text that is quite small relative to other objects on the screen, which
117 currently you have to zoom in to see (and which blows up the text). Point sizes
118 in an application like this are a bit meaningless; even though an inch is an
119 inch regardless of the zoom level a piece of text can be larger or smaller than
120 this. Maybe this is the case for having a base unit and basing point sizes off
123 Here's what's been figured out. Global::zoom is simply the ratio of pixels to
124 base units. What that means is that if you have a 12px grid with a 6" grid size
125 (& base unit of "inches"), Global::zoom becomes 12px / 6" = 2.0 px/in.
127 Dimensions now have a "size" parameter to set their absolute size in relation
128 to the base unit. ATM, the arrows are drawn in pixels, but also scaled by
129 Global::zoom *and* size. Same with the dimension text; it's drawn at 10pt and
130 scaled the same way as the arrowheads.
132 Need a way to scale line widths as well. :-/ Shouldn't be too difficult, just
133 need a thickness parameter similar to the "size" param for dimensions. (And now
137 SetGridSize(12); // This is in pixels
142 void DrawingView::SetToolActive(Action * action)
147 connect(toolAction, SIGNAL(ObjectReady(Object *)), this,
148 SLOT(AddNewObjectToDocument(Object *)));
149 connect(toolAction, SIGNAL(NeedRefresh()), this, SLOT(HandleActionUpdate()));
155 void DrawingView::SetGridSize(uint32_t size)
158 if (size == gridPixels)
161 // Recreate the background bitmap
163 QPainter pmp(&gridBackground);
164 pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
165 pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
167 for(int i=0; i<(BACKGROUND_MAX_SIZE-1); i+=gridPixels)
169 pmp.drawLine(i, 0, i, BACKGROUND_MAX_SIZE - 1);
170 pmp.drawLine(0, i, BACKGROUND_MAX_SIZE - 1, i);
175 // Set up new BG brush & zoom level (pixels per base unit)
176 // Painter::zoom = gridPixels / gridSpacing;
177 Global::zoom = gridPixels / Global::gridSpacing;
178 UpdateGridBackground();
182 void DrawingView::UpdateGridBackground(void)
184 // Transform the origin to Qt coordinates
185 Vector pixmapOrigin = Painter::CartesianToQtCoords(Vector());
186 int x = (int)pixmapOrigin.x;
187 int y = (int)pixmapOrigin.y;
188 // Use mod arithmetic to grab the correct swatch of background
190 Negative numbers still screw it up... Need to think about what we're
191 trying to do here. The fact that it worked with 72 seems to have been pure luck.
192 It seems the problem is negative numbers: We can't let that happen.
193 When taking away the zero, it pops over 1 px at zero, then goes about 1/2 a
196 The bitmap looks like this:
206 @ x = 1, we want it to look like:
208 -+---+---+---+---+---
211 -+---+---+---+---+---
216 Which means we need to grab the sample from x = 3. @ x = -1:
226 Which means we need to grab the sample from x = 1. Which means we have to take
227 the mirror of the modulus of gridPixels.
229 Doing a mod of a negative number is problematic: 1st, the compiler converts the
230 negative number to an unsigned int, then it does the mod. Gets you wrong answers
231 most of the time, unless you use a power of 2. :-P So what we do here is just
232 take the modulus of the negation, which means we don't have to worry about
235 The positive case looks gruesome (and it is) but it boils down to this: We take
236 the modulus of the X coordinate, then mirror it by subtraction from the
237 maximum (in this case, gridPixels). This gives us a number in the range of 1 to
238 gridPixels. But we need the case where the result equalling gridPixels to be
239 zero; so we do another modulus operation on the result to achieve this.
244 x = (gridPixels - (x % gridPixels)) % gridPixels;
249 y = (gridPixels - (y % gridPixels)) % gridPixels;
251 // Here we grab a section of the bigger pixmap, so that the background
252 // *looks* like it's scrolling...
253 QPixmap pm = gridBackground.copy(x, y, gridPixels, gridPixels);
254 QPalette pal = palette();
255 pal.setBrush(backgroundRole(), QBrush(pm));
256 setAutoFillBackground(true);
261 void DrawingView::AddNewObjectToDocument(Object * object)
265 // object->Reparent(&document);
266 // document.Add(object);
269 //printf("DrawingView::AddNewObjectToDocument(). object=%08X\n", object);
273 void DrawingView::HandleActionUpdate(void)
279 void DrawingView::SetCurrentLayer(int layer)
281 Global::currentLayer = layer;
282 //printf("DrawingView::CurrentLayer = %i\n", layer);
286 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
288 // This is undoing the transform, e.g. going from client coords to local coords.
289 // In essence, the height - y is height + (y * -1), the (y * -1) term doing the
290 // conversion of the y-axis from increasing bottom to top.
291 return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
295 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
297 // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
298 // No voodoo here, it's just grouped wrong to see it. It should be:
299 // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive
300 return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
304 void DrawingView::paintEvent(QPaintEvent * /*event*/)
306 QPainter qtPainter(this);
307 Painter painter(&qtPainter);
310 qtPainter.setRenderHint(QPainter::Antialiasing);
312 Global::viewportHeight = size().height();
314 // Draw coordinate axes
315 painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
316 painter.DrawLine(0, -16384, 0, 16384);
317 painter.DrawLine(-16384, 0, 16384, 0);
319 // The top level document takes care of rendering for us...
320 // document.Draw(&painter);
321 // Not any more it doesn't...
322 RenderObjects(&painter, &document);
327 painter.SetPen(QPen(QColor(200, 100, 0, 255), 1.0, Qt::DashLine));
328 painter.DrawCrosshair(oldPoint);
329 toolAction->Draw(&painter);
334 if (Global::selectionInProgress)
336 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
337 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
338 painter.DrawRect(Global::selection);
344 void DrawingView::RenderObjects(Painter * p, Container * c)
346 std::vector<void *>::iterator i;
348 for(i=c->objects.begin(); i!=c->objects.end(); i++)
350 Object * obj = (Object *)(*i);
351 p->SetPen(QPen(Qt::black, 1.0 * Global::zoom * obj->thickness, Qt::SolidLine));
357 Line * l = (Line *)obj;
358 p->DrawLine(l->p1, l->p2);
363 Circle * ci = (Circle *)obj;
364 p->DrawEllipse(ci->p1, ci->radius, ci->radius);
369 Arc * a = (Arc *)obj;
370 p->DrawArc(a->p1, a->radius, a->angle1, a->angle2);
380 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
382 Global::screenSize = Vector(size().width(), size().height());
383 UpdateGridBackground();
387 void DrawingView::mousePressEvent(QMouseEvent * event)
389 if (event->button() == Qt::LeftButton)
391 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
392 // collided = document.Collided(point);
395 // Do an update if collided with at least *one* object in the document
402 if (Global::snapToGrid)
403 point = Global::SnapPointToGrid(point);
405 // We always snap to object points, and they take precendence over
407 if (Global::snapPointIsValid)
408 point = Global::snapPoint;
410 toolAction->MouseDown(point);
415 // Didn't hit any object and not using a tool, so do a selection rectangle
416 if (!(collided))// || toolAction))
418 Global::selectionInProgress = true;
419 Global::selection.setTopLeft(QPointF(point.x, point.y));
420 Global::selection.setBottomRight(QPointF(point.x, point.y));
424 else if (event->button() == Qt::MiddleButton)
427 oldPoint = Vector(event->x(), event->y());
428 // Should also change the mouse pointer as well...
429 setCursor(Qt::SizeAllCursor);
434 void DrawingView::mouseMoveEvent(QMouseEvent * event)
436 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
437 Global::selection.setBottomRight(QPointF(point.x, point.y));
438 // Only needs to be done here, as mouse down is always preceded by movement
439 Global::snapPointIsValid = false;
442 if (event->buttons() & Qt::MiddleButton)
444 point = Vector(event->x(), event->y());
445 // Since we're using Qt coords for scrolling, we have to adjust them here to
446 // conform to Cartesian coords, since the origin is using Cartesian. :-)
447 Vector delta(oldPoint, point);
448 delta /= Global::zoom;
450 Global::origin -= delta;
452 UpdateGridBackground();
459 // Grid processing... (only snap here is left button is down)
460 if ((event->buttons() & Qt::LeftButton) && Global::snapToGrid)
462 point = SnapPointToGrid(point);
465 // Snap points on objects always take precedence over the grid, whether
466 // dragging an object or not...
468 if (Global::snapPointIsValid)
470 // Uncommenting this causes the cursor to become unresponsive after the first
472 // point = Global::snapPoint;
477 //we should keep track of the last point here and only pass this down *if* the point
481 // This returns true if we've moved over an object...
482 if (document.PointerMoved(point))
485 Now objects handle mouse move snapping as well. The code below mainly works only
486 for tools; we need to fix it so that objects work as well...
488 There's a problem with the object point snapping in that it's dependent on the
489 order of the objects in the document. Most likely this is because it counts the
490 selected object last and thus fucks up the algorithm. Need to fix this...
494 // Do object snapping here. Grid snapping on mouse down is done in the
495 // objects themselves, only because we have to hit test the raw point,
496 // not the snapped point. There has to be a better way...!
497 if (document.penultimateObjectHovered)
499 // Two objects are hovered, see if we have an intersection point
500 if ((document.lastObjectHovered->type == OTLine) && (document.penultimateObjectHovered->type == OTLine))
503 int n = Geometry::Intersects((Line *)document.lastObjectHovered, (Line *)document.penultimateObjectHovered, &t);
507 Global::snapPoint = document.lastObjectHovered->GetPointAtParameter(t);
508 Global::snapPointIsValid = true;
511 else if ((document.lastObjectHovered->type == OTCircle) && (document.penultimateObjectHovered->type == OTCircle))
514 int n = Geometry::Intersects((Circle *)document.lastObjectHovered, (Circle *)document.penultimateObjectHovered, 0, 0, 0, 0, &p1, &p2);
518 Global::snapPoint = p1;
519 Global::snapPointIsValid = true;
523 double d1 = Vector(point, p1).Magnitude();
524 double d2 = Vector(point, p2).Magnitude();
527 Global::snapPoint = p1;
529 Global::snapPoint = p2;
531 Global::snapPointIsValid = true;
537 // Otherwise, it was a single object hovered...
543 if (Global::snapToGrid)
544 point = Global::SnapPointToGrid(point);
546 // We always snap to object points, and they take precendence over
548 if (Global::snapPointIsValid)
549 point = Global::snapPoint;
551 toolAction->MouseMoved(point);
555 // This is used to draw the tool crosshair...
559 if (/*document.NeedsUpdate() ||*/ Global::selectionInProgress /*|| toolAction*/)
565 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
567 if (event->button() == Qt::LeftButton)
570 document.PointerReleased();
573 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
574 //could set it up to use the document's update function (assumes that all object updates
575 //are being reported correctly:
576 // if (document.NeedsUpdate())
578 update(); // Do an update if collided with at least *one* object in the document
582 toolAction->MouseReleased();
585 if (Global::selectionInProgress)
587 // Select all the stuff inside of selection
588 Global::selectionInProgress = false;
591 else if (event->button() == Qt::MiddleButton)
594 setCursor(Qt::ArrowCursor);
599 void DrawingView::wheelEvent(QWheelEvent * event)
601 double zoomFactor = 1.25;
602 QSize sizeWin = size();
603 Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
604 center = Painter::QtToCartesianCoords(center);
606 // This is not centering for some reason. Need to figure out why. :-/
607 if (event->delta() > 0)
609 Vector newOrigin = center - ((center - Global::origin) / zoomFactor);
610 Global::origin = newOrigin;
611 Global::zoom *= zoomFactor;
615 Vector newOrigin = center + ((-center + Global::origin) * zoomFactor);
616 Global::origin = newOrigin;
617 Global::zoom /= zoomFactor;
621 // Global::gridSpacing = gridPixels / Painter::zoom;
622 // UpdateGridBackground();
623 SetGridSize(Global::gridSpacing * Global::zoom);
625 // zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Global::gridSpacing));
630 void DrawingView::keyPressEvent(QKeyEvent * event)
634 toolAction->KeyDown(event->key());
639 void DrawingView::keyReleaseEvent(QKeyEvent * event)
643 toolAction->KeyReleased(event->key());
648 // This looks strange, but it's really quite simple: We want a point that's
649 // more than half-way to the next grid point to snap there while conversely we
650 // want a point that's less than half-way to to the next grid point then snap
651 // to the one before it. So we add half of the grid spacing to the point, then
652 // divide by it so that we can remove the fractional part, then multiply it
653 // back to get back to the correct answer.
655 Point DrawingView::SnapPointToGrid(Point point)
657 point += Global::gridSpacing / 2.0; // *This* adds to Z!!!
658 point /= Global::gridSpacing;
659 point.x = floor(point.x);//need to fix this for negative numbers...
660 point.y = floor(point.y);
661 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
662 point *= Global::gridSpacing;