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"
43 #define BACKGROUND_MAX_SIZE 512
46 //Container DrawingView::document(Vector(0, 0));
49 DrawingView::DrawingView(QWidget * parent/*= NULL*/): QWidget(parent),
50 // The value in the settings file will override this.
51 useAntialiasing(true),
52 gridBackground(BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE),
53 scale(1.0), offsetX(-10), offsetY(-10), document(Vector(0, 0)),
54 gridPixels(0), collided(false), toolAction(NULL)
56 document.isTopLevelContainer = true;
57 setBackgroundRole(QPalette::Base);
58 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
60 Object::gridSpacing = 12.0; // In base units (inch is default)
62 Line * line = new Line(Vector(5, 5), Vector(50, 40), &document);
64 document.Add(new Line(Vector(50, 40), Vector(10, 83), &document));
65 document.Add(new Line(Vector(10, 83), Vector(17, 2), &document));
66 document.Add(new Circle(Vector(100, 100), 36, &document));
67 document.Add(new Circle(Vector(50, 150), 49, &document));
68 document.Add(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3, &document)),
69 document.Add(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5, &document));
71 Dimension * dimension = new Dimension(Vector(0, 0), Vector(0, 0), DTLinear, &document);
72 line->SetDimensionOnLine(dimension);
73 document.Add(dimension);
75 // Alternate way to do the above...
76 line->SetDimensionOnLine();
80 Here we set the grid size in pixels--12 in this case. Initially, we have our
81 zoom set to make this represent 12 inches at a zoom factor of 25%. (This is
82 arbitrary.) So, to be able to decouple the grid size from the zoom, we need
83 to be able to set the size of the background grid (which we do here at an
84 arbitrary 12 pixels) to anything we want (within reason, of course :-).
86 The drawing enforces the grid spacing through the drawing->gridSpacing variable.
88 drawing->gridSpacing = 12.0 / Painter::zoom;
90 Painter::zoom is the zoom factor for the drawing, and all mouse clicks are
91 translated to Cartesian coordinates through this. (Initially, Painter::zoom is
92 set to 1.0. SCREEN_ZOOM is set to 1.0/4.0.)
94 Really, the 100% zoom level can be set at *any* zoom level, it's more of a
95 convenience function than any measure of absolutes. Doing things that way we
96 could rid ourselves of the whole SCREEN_ZOOM parameter and all the attendant
97 shittyness that comes with it.
99 However, it seems that SCREEN_ZOOM is used to make text and arrow sizes show up
100 a certain way, which means we should probably create something else in those
101 objects to take its place--like some kind of scale factor. This would seem to
102 imply that certain point sizes actually *do* tie things like fonts to absolute
103 sizes on the screen, but not necessarily because you could have an inch scale
104 with text that is quite small relative to other objects on the screen, which
105 currently you have to zoom in to see (and which blows up the text). Point sizes
106 in an application like this are a bit meaningless; even though an inch is an
107 inch regardless of the zoom level a piece of text can be larger or smaller than
108 this. Maybe this is the case for having a base unit and basing point sizes off
111 Here's what's been figured out. Painter::zoom is simply the ratio of pixels to
112 base units. What that means is that if you have a 12px grid with a 6" grid size
113 (& base unit of "inches"), Painter::zoom becomes 12px / 6" = 2.0 px/in.
115 Dimensions now have a "size" parameter to set their absolute size in relation
116 to the base unit. ATM, the arrows are drawn in pixels, but also scaled by
117 Painter::zoom *and* size. Same with the dimension text; it's drawn at 10pt and
118 scaled the same way as the arrowheads.
120 Need a way to scale line widths as well. :-/ Shouldn't be too difficult, just
121 need a thickness parameter similar to the "size" param for dimensions. (And now
125 SetGridSize(12); // This is in pixels
129 void DrawingView::SetToolActive(Action * action)
134 connect(toolAction, SIGNAL(ObjectReady(Object *)), this,
135 SLOT(AddNewObjectToDocument(Object *)));
136 connect(toolAction, SIGNAL(NeedRefresh()), this, SLOT(HandleActionUpdate()));
141 void DrawingView::SetGridSize(uint32_t size)
144 if (size == gridPixels)
147 // Recreate the background bitmap
149 QPainter pmp(&gridBackground);
150 pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
151 pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
153 for(int i=0; i<(BACKGROUND_MAX_SIZE-1); i+=gridPixels)
155 pmp.drawLine(i, 0, i, BACKGROUND_MAX_SIZE - 1);
156 pmp.drawLine(0, i, BACKGROUND_MAX_SIZE - 1, i);
161 // Set up new BG brush & zoom level (pixels per base unit)
162 // Painter::zoom = gridPixels / gridSpacing;
163 Painter::zoom = gridPixels / Object::gridSpacing;
164 UpdateGridBackground();
168 void DrawingView::UpdateGridBackground(void)
170 // Transform the origin to Qt coordinates
171 Vector pixmapOrigin = Painter::CartesianToQtCoords(Vector());
172 int x = (int)pixmapOrigin.x;
173 int y = (int)pixmapOrigin.y;
174 // Use mod arithmetic to grab the correct swatch of background
176 Negative numbers still screw it up... Need to think about what we're
177 trying to do here. The fact that it worked with 72 seems to have been pure luck.
178 It seems the problem is negative numbers: We can't let that happen.
179 When taking away the zero, it pops over 1 px at zero, then goes about 1/2 a
182 The bitmap looks like this:
192 @ x = 1, we want it to look like:
194 -+---+---+---+---+---
197 -+---+---+---+---+---
202 Which means we need to grab the sample from x = 3. @ x = -1:
212 Which means we need to grab the sample from x = 1. Which means we have to take
213 the mirror of the modulus of gridPixels.
215 Doing a mod of a negative number is problematic: 1st, the compiler converts the
216 negative number to an unsigned int, then it does the mod. Gets you wrong answers
217 most of the time, unless you use a power of 2. :-P So what we do here is just
218 take the modulus of the negation, which means we don't have to worry about
221 The positive case looks gruesome (and it is) but it boils down to this: We take
222 the modulus of the X coordinate, then mirror it by subtraction from the
223 maximum (in this case, gridPixels). This gives us a number in the range of 1 to
224 gridPixels. But we need the case where the result equalling gridPixels to be
225 zero; so we do another modulus operation on the result to achieve this.
230 x = (gridPixels - (x % gridPixels)) % gridPixels;
235 y = (gridPixels - (y % gridPixels)) % gridPixels;
237 // Here we grab a section of the bigger pixmap, so that the background
238 // *looks* like it's scrolling...
239 QPixmap pm = gridBackground.copy(x, y, gridPixels, gridPixels);
240 QPalette pal = palette();
241 pal.setBrush(backgroundRole(), QBrush(pm));
242 setAutoFillBackground(true);
247 void DrawingView::AddNewObjectToDocument(Object * object)
251 object->Reparent(&document);
252 document.Add(object);
255 //printf("DrawingView::AddNewObjectToDocument(). object=%08X\n", object);
259 void DrawingView::HandleActionUpdate(void)
265 void DrawingView::SetCurrentLayer(int layer)
267 Object::currentLayer = layer;
268 //printf("DrawingView::CurrentLayer = %i\n", layer);
272 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
274 // This is undoing the transform, e.g. going from client coords to local coords.
275 // In essence, the height - y is height + (y * -1), the (y * -1) term doing the
276 // conversion of the y-axis from increasing bottom to top.
277 return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
281 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
283 // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
284 // No voodoo here, it's just grouped wrong to see it. It should be:
285 // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive
286 return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
290 void DrawingView::paintEvent(QPaintEvent * /*event*/)
292 QPainter qtPainter(this);
293 Painter painter(&qtPainter);
296 qtPainter.setRenderHint(QPainter::Antialiasing);
298 Object::SetViewportHeight(size().height());
300 // Draw coordinate axes
301 painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
302 painter.DrawLine(0, -16384, 0, 16384);
303 painter.DrawLine(-16384, 0, 16384, 0);
305 // The top level document takes care of rendering for us...
306 document.Draw(&painter);
310 painter.SetPen(QPen(QColor(200, 100, 0, 255), 1.0, Qt::DashLine));
311 painter.DrawCrosshair(oldPoint);
312 toolAction->Draw(&painter);
315 if (Object::selectionInProgress)
317 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
318 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
319 painter.DrawRect(Object::selection);
324 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
326 Painter::screenSize = Vector(size().width(), size().height());
327 UpdateGridBackground();
331 void DrawingView::mousePressEvent(QMouseEvent * event)
333 if (event->button() == Qt::LeftButton)
335 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
336 collided = document.Collided(point);
338 // Do an update if collided with at least *one* object in the document
344 if (Object::snapToGrid)
345 point = Object::SnapPointToGrid(point);
347 // We always snap to object points, and they take precendence over
349 if (Object::snapPointIsValid)
350 point = Object::snapPoint;
352 toolAction->MouseDown(point);
355 // Didn't hit any object and not using a tool, so do a selection rectangle
356 if (!(collided || toolAction))
358 Object::selectionInProgress = true;
359 Object::selection.setTopLeft(QPointF(point.x, point.y));
360 Object::selection.setBottomRight(QPointF(point.x, point.y));
363 else if (event->button() == Qt::MiddleButton)
366 oldPoint = Vector(event->x(), event->y());
367 // Should also change the mouse pointer as well...
368 setCursor(Qt::SizeAllCursor);
373 void DrawingView::mouseMoveEvent(QMouseEvent * event)
375 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
376 Object::selection.setBottomRight(QPointF(point.x, point.y));
377 // Only needs to be done here, as mouse down is always preceded by movement
378 Object::snapPointIsValid = false;
381 if (event->buttons() & Qt::MiddleButton)
383 point = Vector(event->x(), event->y());
384 // Since we're using Qt coords for scrolling, we have to adjust them here to
385 // conform to Cartesian coords, since the origin is using Cartesian. :-)
386 Vector delta(oldPoint, point);
387 delta /= Painter::zoom;
389 Painter::origin -= delta;
391 UpdateGridBackground();
398 // Grid processing... (only snap here is left button is down)
399 if ((event->buttons() & Qt::LeftButton) && Object::snapToGrid)
401 point = Object::SnapPointToGrid(point);
404 // Snap points on objects always take precedence over the grid, whether
405 // dragging an object or not...
407 if (Object::snapPointIsValid)
409 // Uncommenting this causes the cursor to become unresponsive after the first
411 // point = Object::snapPoint;
416 //we should keep track of the last point here and only pass this down *if* the point
419 // This returns true if we've moved over an object...
420 if (document.PointerMoved(point))
423 Now objects handle mouse move snapping as well. The code below mainly works only
424 for tools; we need to fix it so that objects work as well...
426 There's a problem with the object point snapping in that it's dependent on the
427 order of the objects in the document. Most likely this is because it counts the
428 selected object last and thus fucks up the algorithm. Need to fix this...
432 // Do object snapping here. Grid snapping on mouse down is done in the
433 // objects themselves, only because we have to hit test the raw point,
434 // not the snapped point. There has to be a better way...!
435 if (document.penultimateObjectHovered)
437 // Two objects are hovered, see if we have an intersection point
438 if ((document.lastObjectHovered->type == OTLine) && (document.penultimateObjectHovered->type == OTLine))
441 int n = Geometry::Intersects((Line *)document.lastObjectHovered, (Line *)document.penultimateObjectHovered, &t);
445 Object::snapPoint = document.lastObjectHovered->GetPointAtParameter(t);
446 Object::snapPointIsValid = true;
449 else if ((document.lastObjectHovered->type == OTCircle) && (document.penultimateObjectHovered->type == OTCircle))
452 int n = Geometry::Intersects((Circle *)document.lastObjectHovered, (Circle *)document.penultimateObjectHovered, 0, 0, 0, 0, &p1, &p2);
456 Object::snapPoint = p1;
457 Object::snapPointIsValid = true;
461 double d1 = Vector(point, p1).Magnitude();
462 double d2 = Vector(point, p2).Magnitude();
465 Object::snapPoint = p1;
467 Object::snapPoint = p2;
469 Object::snapPointIsValid = true;
475 // Otherwise, it was a single object hovered...
481 if (Object::snapToGrid)
482 point = Object::SnapPointToGrid(point);
484 // We always snap to object points, and they take precendence over
486 if (Object::snapPointIsValid)
487 point = Object::snapPoint;
489 toolAction->MouseMoved(point);
492 // This is used to draw the tool crosshair...
495 if (document.NeedsUpdate() || Object::selectionInProgress || toolAction)
500 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
502 if (event->button() == Qt::LeftButton)
504 document.PointerReleased();
506 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
507 //could set it up to use the document's update function (assumes that all object updates
508 //are being reported correctly:
509 // if (document.NeedsUpdate())
511 update(); // Do an update if collided with at least *one* object in the document
514 toolAction->MouseReleased();
516 if (Object::selectionInProgress)
518 // Select all the stuff inside of selection
519 Object::selectionInProgress = false;
522 else if (event->button() == Qt::MiddleButton)
525 setCursor(Qt::ArrowCursor);
530 void DrawingView::wheelEvent(QWheelEvent * event)
532 double zoomFactor = 1.25;
533 QSize sizeWin = size();
534 Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
535 center = Painter::QtToCartesianCoords(center);
537 // This is not centering for some reason. Need to figure out why. :-/
538 if (event->delta() > 0)
540 Vector newOrigin = center - ((center - Painter::origin) / zoomFactor);
541 Painter::origin = newOrigin;
542 Painter::zoom *= zoomFactor;
546 Vector newOrigin = center + ((-center + Painter::origin) * zoomFactor);
547 Painter::origin = newOrigin;
548 Painter::zoom /= zoomFactor;
551 // Object::gridSpacing = gridPixels / Painter::zoom;
552 // UpdateGridBackground();
553 SetGridSize(Object::gridSpacing * Painter::zoom);
555 // zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Object::gridSpacing));
559 void DrawingView::keyPressEvent(QKeyEvent * event)
562 toolAction->KeyDown(event->key());
566 void DrawingView::keyReleaseEvent(QKeyEvent * event)
569 toolAction->KeyReleased(event->key());