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"
33 #include "mathconstants.h"
37 //#include "dimension.h"
38 //#include "geometry.h"
44 #define BACKGROUND_MAX_SIZE 512
47 //Container DrawingView::document(Vector(0, 0));
50 DrawingView::DrawingView(QWidget * parent/*= NULL*/): QWidget(parent),
51 // The value in the settings file will override this.
52 useAntialiasing(true),
53 gridBackground(BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE),
54 scale(1.0), offsetX(-10), offsetY(-10),// document(Vector(0, 0)),
55 gridPixels(0), collided(false)//, toolAction(NULL)
57 // document.isTopLevelContainer = true;
58 //wtf? doesn't work except in c++11??? document = { 0 };
59 setBackgroundRole(QPalette::Base);
60 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
62 Global::gridSpacing = 12.0; // In base units (inch is default)
65 Line * line = new Line(Vector(5, 5), Vector(50, 40), &document);
67 document.Add(new Line(Vector(50, 40), Vector(10, 83), &document));
68 document.Add(new Line(Vector(10, 83), Vector(17, 2), &document));
69 document.Add(new Circle(Vector(100, 100), 36, &document));
70 document.Add(new Circle(Vector(50, 150), 49, &document));
71 document.Add(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3, &document)),
72 document.Add(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5, &document));
74 Dimension * dimension = new Dimension(Vector(0, 0), Vector(0, 0), DTLinear, &document);
75 line->SetDimensionOnLine(dimension);
76 document.Add(dimension);
78 // Alternate way to do the above...
79 line->SetDimensionOnLine();
84 Here we set the grid size in pixels--12 in this case. Initially, we have our
85 zoom set to make this represent 12 inches at a zoom factor of 25%. (This is
86 arbitrary.) So, to be able to decouple the grid size from the zoom, we need
87 to be able to set the size of the background grid (which we do here at an
88 arbitrary 12 pixels) to anything we want (within reason, of course :-).
90 The drawing enforces the grid spacing through the drawing->gridSpacing variable.
92 drawing->gridSpacing = 12.0 / Painter::zoom;
94 Painter::zoom is the zoom factor for the drawing, and all mouse clicks are
95 translated to Cartesian coordinates through this. (Initially, Painter::zoom is
96 set to 1.0. SCREEN_ZOOM is set to 1.0/4.0.)
98 Really, the 100% zoom level can be set at *any* zoom level, it's more of a
99 convenience function than any measure of absolutes. Doing things that way we
100 could rid ourselves of the whole SCREEN_ZOOM parameter and all the attendant
101 shittiness that comes with it.
103 However, it seems that SCREEN_ZOOM is used to make text and arrow sizes show up
104 a certain way, which means we should probably create something else in those
105 objects to take its place--like some kind of scale factor. This would seem to
106 imply that certain point sizes actually *do* tie things like fonts to absolute
107 sizes on the screen, but not necessarily because you could have an inch scale
108 with text that is quite small relative to other objects on the screen, which
109 currently you have to zoom in to see (and which blows up the text). Point sizes
110 in an application like this are a bit meaningless; even though an inch is an
111 inch regardless of the zoom level a piece of text can be larger or smaller than
112 this. Maybe this is the case for having a base unit and basing point sizes off
115 Here's what's been figured out. Painter::zoom is simply the ratio of pixels to
116 base units. What that means is that if you have a 12px grid with a 6" grid size
117 (& base unit of "inches"), Painter::zoom becomes 12px / 6" = 2.0 px/in.
119 Dimensions now have a "size" parameter to set their absolute size in relation
120 to the base unit. ATM, the arrows are drawn in pixels, but also scaled by
121 Painter::zoom *and* size. Same with the dimension text; it's drawn at 10pt and
122 scaled the same way as the arrowheads.
124 Need a way to scale line widths as well. :-/ Shouldn't be too difficult, just
125 need a thickness parameter similar to the "size" param for dimensions. (And now
129 SetGridSize(12); // This is in pixels
134 void DrawingView::SetToolActive(Action * action)
139 connect(toolAction, SIGNAL(ObjectReady(Object *)), this,
140 SLOT(AddNewObjectToDocument(Object *)));
141 connect(toolAction, SIGNAL(NeedRefresh()), this, SLOT(HandleActionUpdate()));
147 void DrawingView::SetGridSize(uint32_t size)
150 if (size == gridPixels)
153 // Recreate the background bitmap
155 QPainter pmp(&gridBackground);
156 pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
157 pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
159 for(int i=0; i<(BACKGROUND_MAX_SIZE-1); i+=gridPixels)
161 pmp.drawLine(i, 0, i, BACKGROUND_MAX_SIZE - 1);
162 pmp.drawLine(0, i, BACKGROUND_MAX_SIZE - 1, i);
167 // Set up new BG brush & zoom level (pixels per base unit)
168 // Painter::zoom = gridPixels / gridSpacing;
169 Painter::zoom = gridPixels / Global::gridSpacing;
170 UpdateGridBackground();
174 void DrawingView::UpdateGridBackground(void)
176 // Transform the origin to Qt coordinates
177 Vector pixmapOrigin = Painter::CartesianToQtCoords(Vector());
178 int x = (int)pixmapOrigin.x;
179 int y = (int)pixmapOrigin.y;
180 // Use mod arithmetic to grab the correct swatch of background
182 Negative numbers still screw it up... Need to think about what we're
183 trying to do here. The fact that it worked with 72 seems to have been pure luck.
184 It seems the problem is negative numbers: We can't let that happen.
185 When taking away the zero, it pops over 1 px at zero, then goes about 1/2 a
188 The bitmap looks like this:
198 @ x = 1, we want it to look like:
200 -+---+---+---+---+---
203 -+---+---+---+---+---
208 Which means we need to grab the sample from x = 3. @ x = -1:
218 Which means we need to grab the sample from x = 1. Which means we have to take
219 the mirror of the modulus of gridPixels.
221 Doing a mod of a negative number is problematic: 1st, the compiler converts the
222 negative number to an unsigned int, then it does the mod. Gets you wrong answers
223 most of the time, unless you use a power of 2. :-P So what we do here is just
224 take the modulus of the negation, which means we don't have to worry about
227 The positive case looks gruesome (and it is) but it boils down to this: We take
228 the modulus of the X coordinate, then mirror it by subtraction from the
229 maximum (in this case, gridPixels). This gives us a number in the range of 1 to
230 gridPixels. But we need the case where the result equalling gridPixels to be
231 zero; so we do another modulus operation on the result to achieve this.
236 x = (gridPixels - (x % gridPixels)) % gridPixels;
241 y = (gridPixels - (y % gridPixels)) % gridPixels;
243 // Here we grab a section of the bigger pixmap, so that the background
244 // *looks* like it's scrolling...
245 QPixmap pm = gridBackground.copy(x, y, gridPixels, gridPixels);
246 QPalette pal = palette();
247 pal.setBrush(backgroundRole(), QBrush(pm));
248 setAutoFillBackground(true);
253 void DrawingView::AddNewObjectToDocument(Object * object)
257 // object->Reparent(&document);
258 // document.Add(object);
261 //printf("DrawingView::AddNewObjectToDocument(). object=%08X\n", object);
265 void DrawingView::HandleActionUpdate(void)
271 void DrawingView::SetCurrentLayer(int layer)
273 Global::currentLayer = layer;
274 //printf("DrawingView::CurrentLayer = %i\n", layer);
278 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
280 // This is undoing the transform, e.g. going from client coords to local coords.
281 // In essence, the height - y is height + (y * -1), the (y * -1) term doing the
282 // conversion of the y-axis from increasing bottom to top.
283 return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
287 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
289 // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
290 // No voodoo here, it's just grouped wrong to see it. It should be:
291 // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive
292 return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
296 void DrawingView::paintEvent(QPaintEvent * /*event*/)
298 QPainter qtPainter(this);
299 Painter painter(&qtPainter);
302 qtPainter.setRenderHint(QPainter::Antialiasing);
304 Global::viewportHeight = size().height();
306 // Draw coordinate axes
307 painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
308 painter.DrawLine(0, -16384, 0, 16384);
309 painter.DrawLine(-16384, 0, 16384, 0);
311 // The top level document takes care of rendering for us...
312 // document.Draw(&painter);
317 painter.SetPen(QPen(QColor(200, 100, 0, 255), 1.0, Qt::DashLine));
318 painter.DrawCrosshair(oldPoint);
319 toolAction->Draw(&painter);
324 if (Global::selectionInProgress)
326 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
327 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
328 painter.DrawRect(Global::selection);
334 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
336 Painter::screenSize = Vector(size().width(), size().height());
337 UpdateGridBackground();
341 void DrawingView::mousePressEvent(QMouseEvent * event)
343 if (event->button() == Qt::LeftButton)
345 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
346 // collided = document.Collided(point);
349 // Do an update if collided with at least *one* object in the document
356 if (Global::snapToGrid)
357 point = Global::SnapPointToGrid(point);
359 // We always snap to object points, and they take precendence over
361 if (Global::snapPointIsValid)
362 point = Global::snapPoint;
364 toolAction->MouseDown(point);
369 // Didn't hit any object and not using a tool, so do a selection rectangle
370 if (!(collided))// || toolAction))
372 Global::selectionInProgress = true;
373 Global::selection.setTopLeft(QPointF(point.x, point.y));
374 Global::selection.setBottomRight(QPointF(point.x, point.y));
378 else if (event->button() == Qt::MiddleButton)
381 oldPoint = Vector(event->x(), event->y());
382 // Should also change the mouse pointer as well...
383 setCursor(Qt::SizeAllCursor);
388 void DrawingView::mouseMoveEvent(QMouseEvent * event)
390 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
391 Global::selection.setBottomRight(QPointF(point.x, point.y));
392 // Only needs to be done here, as mouse down is always preceded by movement
393 Global::snapPointIsValid = false;
396 if (event->buttons() & Qt::MiddleButton)
398 point = Vector(event->x(), event->y());
399 // Since we're using Qt coords for scrolling, we have to adjust them here to
400 // conform to Cartesian coords, since the origin is using Cartesian. :-)
401 Vector delta(oldPoint, point);
402 delta /= Painter::zoom;
404 Painter::origin -= delta;
406 UpdateGridBackground();
413 // Grid processing... (only snap here is left button is down)
414 if ((event->buttons() & Qt::LeftButton) && Global::snapToGrid)
416 point = SnapPointToGrid(point);
419 // Snap points on objects always take precedence over the grid, whether
420 // dragging an object or not...
422 if (Global::snapPointIsValid)
424 // Uncommenting this causes the cursor to become unresponsive after the first
426 // point = Global::snapPoint;
431 //we should keep track of the last point here and only pass this down *if* the point
435 // This returns true if we've moved over an object...
436 if (document.PointerMoved(point))
439 Now objects handle mouse move snapping as well. The code below mainly works only
440 for tools; we need to fix it so that objects work as well...
442 There's a problem with the object point snapping in that it's dependent on the
443 order of the objects in the document. Most likely this is because it counts the
444 selected object last and thus fucks up the algorithm. Need to fix this...
448 // Do object snapping here. Grid snapping on mouse down is done in the
449 // objects themselves, only because we have to hit test the raw point,
450 // not the snapped point. There has to be a better way...!
451 if (document.penultimateObjectHovered)
453 // Two objects are hovered, see if we have an intersection point
454 if ((document.lastObjectHovered->type == OTLine) && (document.penultimateObjectHovered->type == OTLine))
457 int n = Geometry::Intersects((Line *)document.lastObjectHovered, (Line *)document.penultimateObjectHovered, &t);
461 Global::snapPoint = document.lastObjectHovered->GetPointAtParameter(t);
462 Global::snapPointIsValid = true;
465 else if ((document.lastObjectHovered->type == OTCircle) && (document.penultimateObjectHovered->type == OTCircle))
468 int n = Geometry::Intersects((Circle *)document.lastObjectHovered, (Circle *)document.penultimateObjectHovered, 0, 0, 0, 0, &p1, &p2);
472 Global::snapPoint = p1;
473 Global::snapPointIsValid = true;
477 double d1 = Vector(point, p1).Magnitude();
478 double d2 = Vector(point, p2).Magnitude();
481 Global::snapPoint = p1;
483 Global::snapPoint = p2;
485 Global::snapPointIsValid = true;
491 // Otherwise, it was a single object hovered...
497 if (Global::snapToGrid)
498 point = Global::SnapPointToGrid(point);
500 // We always snap to object points, and they take precendence over
502 if (Global::snapPointIsValid)
503 point = Global::snapPoint;
505 toolAction->MouseMoved(point);
509 // This is used to draw the tool crosshair...
513 if (/*document.NeedsUpdate() ||*/ Global::selectionInProgress /*|| toolAction*/)
519 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
521 if (event->button() == Qt::LeftButton)
524 document.PointerReleased();
527 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
528 //could set it up to use the document's update function (assumes that all object updates
529 //are being reported correctly:
530 // if (document.NeedsUpdate())
532 update(); // Do an update if collided with at least *one* object in the document
536 toolAction->MouseReleased();
539 if (Global::selectionInProgress)
541 // Select all the stuff inside of selection
542 Global::selectionInProgress = false;
545 else if (event->button() == Qt::MiddleButton)
548 setCursor(Qt::ArrowCursor);
553 void DrawingView::wheelEvent(QWheelEvent * event)
555 double zoomFactor = 1.25;
556 QSize sizeWin = size();
557 Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
558 center = Painter::QtToCartesianCoords(center);
560 // This is not centering for some reason. Need to figure out why. :-/
561 if (event->delta() > 0)
563 Vector newOrigin = center - ((center - Painter::origin) / zoomFactor);
564 Painter::origin = newOrigin;
565 Painter::zoom *= zoomFactor;
569 Vector newOrigin = center + ((-center + Painter::origin) * zoomFactor);
570 Painter::origin = newOrigin;
571 Painter::zoom /= zoomFactor;
575 // Global::gridSpacing = gridPixels / Painter::zoom;
576 // UpdateGridBackground();
577 SetGridSize(Global::gridSpacing * Painter::zoom);
579 // zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Global::gridSpacing));
584 void DrawingView::keyPressEvent(QKeyEvent * event)
588 toolAction->KeyDown(event->key());
593 void DrawingView::keyReleaseEvent(QKeyEvent * event)
597 toolAction->KeyReleased(event->key());
602 // This looks strange, but it's really quite simple: We want a point that's
603 // more than half-way to the next grid point to snap there while conversely we
604 // want a point that's less than half-way to to the next grid point then snap
605 // to the one before it. So we add half of the grid spacing to the point, then
606 // divide by it so that we can remove the fractional part, then multiply it
607 // back to get back to the correct answer.
609 Point DrawingView::SnapPointToGrid(Point point)
611 point += Global::gridSpacing / 2.0; // *This* adds to Z!!!
612 point /= Global::gridSpacing;
613 point.x = floor(point.x);//need to fix this for negative numbers...
614 point.y = floor(point.y);
615 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
616 point *= Global::gridSpacing;