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"
42 #define BACKGROUND_MAX_SIZE 512
45 //Container DrawingView::document(Vector(0, 0));
48 DrawingView::DrawingView(QWidget * parent/*= NULL*/): QWidget(parent),
49 // The value in the settings file will override this.
50 useAntialiasing(true),
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 setBackgroundRole(QPalette::Base);
57 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
59 Object::gridSpacing = 12.0; // In base units (inch is default)
61 Line * line = new Line(Vector(5, 5), Vector(50, 40), &document);
63 document.Add(new Line(Vector(50, 40), Vector(10, 83), &document));
64 document.Add(new Line(Vector(10, 83), Vector(17, 2), &document));
65 document.Add(new Circle(Vector(100, 100), 36, &document));
66 document.Add(new Circle(Vector(50, 150), 49, &document));
67 document.Add(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3, &document)),
68 document.Add(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5, &document));
70 Dimension * dimension = new Dimension(Vector(0, 0), Vector(0, 0), DTLinear, &document);
71 line->SetDimensionOnLine(dimension);
72 document.Add(dimension);
74 // Alternate way to do the above...
75 line->SetDimensionOnLine();
79 Here we set the grid size in pixels--12 in this case. Initially, we have our
80 zoom set to make this represent 12 inches at a zoom factor of 25%. (This is
81 arbitrary.) So, to be able to decouple the grid size from the zoom, we need
82 to be able to set the size of the background grid (which we do here at an
83 arbitrary 12 pixels) to anything we want (within reason, of course :-).
85 The drawing enforces the grid spacing through the drawing->gridSpacing variable.
87 drawing->gridSpacing = 12.0 / Painter::zoom;
89 Painter::zoom is the zoom factor for the drawing, and all mouse clicks are
90 translated to Cartesian coordinates through this. (Initially, Painter::zoom is
91 set to 1.0. SCREEN_ZOOM is set to 1.0/4.0.)
93 Really, the 100% zoom level can be set at *any* zoom level, it's more of a
94 convenience function than any measure of absolutes. Doing things that way we
95 could rid ourselves of the whole SCREEN_ZOOM parameter and all the attendant
96 shittyness that comes with it.
98 However, it seems that SCREEN_ZOOM is used to make text and arrow sizes show up
99 a certain way, which means we should probably create something else in those
100 objects to take its place--like some kind of scale factor. This would seem to
101 imply that certain point sizes actually *do* tie things like fonts to absolute
102 sizes on the screen, but not necessarily because you could have an inch scale
103 with text that is quite small relative to other objects on the screen, which
104 currently you have to zoom in to see (and which blows up the text). Point sizes
105 in an application like this are a bit meaningless; even though an inch is an
106 inch regardless of the zoom level a piece of text can be larger or smaller than
107 this. Maybe this is the case for having a base unit and basing point sizes off
110 Here's what's been figured out. Painter::zoom is simply the ratio of pixels to
111 base units. What that means is that if you have a 12px grid with a 6" grid size
112 (& base unit of "inches"), Painter::zoom becomes 12px / 6" = 2.0 px/in.
114 Dimensions now have a "size" parameter to set their absolute size in relation
115 to the base unit. ATM, the arrows are drawn in pixels, but also scaled by
116 Painter::zoom *and* size. Same with the dimension text; it's drawn at 10pt and
117 scaled the same way as the arrowheads.
119 Need a way to scale line widths as well. :-/ Shouldn't be too difficult, just
120 need a thickness parameter similar to the "size" param for dimensions. (And now
124 SetGridSize(12); // This is in pixels
128 void DrawingView::SetToolActive(Action * action)
133 connect(toolAction, SIGNAL(ObjectReady(Object *)), this,
134 SLOT(AddNewObjectToDocument(Object *)));
135 connect(toolAction, SIGNAL(NeedRefresh()), this, SLOT(HandleActionUpdate()));
140 void DrawingView::SetGridSize(uint32_t size)
143 if (size == gridPixels)
146 // Recreate the background bitmap
148 QPainter pmp(&gridBackground);
149 pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
150 pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
152 for(int i=0; i<(BACKGROUND_MAX_SIZE-1); i+=gridPixels)
154 pmp.drawLine(i, 0, i, BACKGROUND_MAX_SIZE - 1);
155 pmp.drawLine(0, i, BACKGROUND_MAX_SIZE - 1, i);
160 // Set up new BG brush & zoom level (pixels per base unit)
161 // Painter::zoom = gridPixels / gridSpacing;
162 Painter::zoom = gridPixels / Object::gridSpacing;
163 UpdateGridBackground();
167 void DrawingView::UpdateGridBackground(void)
169 // Transform the origin to Qt coordinates
170 Vector pixmapOrigin = Painter::CartesianToQtCoords(Vector());
171 int x = (int)pixmapOrigin.x;
172 int y = (int)pixmapOrigin.y;
173 // Use mod arithmetic to grab the correct swatch of background
175 Negative numbers still screw it up... Need to think about what we're
176 trying to do here. The fact that it worked with 72 seems to have been pure luck.
177 It seems the problem is negative numbers: We can't let that happen.
178 When taking away the zero, it pops over 1 px at zero, then goes about 1/2 a
181 The bitmap looks like this:
191 @ x = 1, we want it to look like:
193 -+---+---+---+---+---
196 -+---+---+---+---+---
201 Which means we need to grab the sample from x = 3. @ x = -1:
211 Which means we need to grab the sample from x = 1. Which means we have to take
212 the mirror of the modulus of gridPixels.
214 Doing a mod of a negative number is problematic: 1st, the compiler converts the
215 negative number to an unsigned int, then it does the mod. Gets you wrong answers
216 most of the time, unless you use a power of 2. :-P So what we do here is just
217 take the modulus of the negation, which means we don't have to worry about
220 The positive case looks gruesome (and it is) but it boils down to this: We take
221 the modulus of the X coordinate, then mirror it by subtraction from the
222 maximum (in this case, gridPixels). This gives us a number in the range of 1 to
223 gridPixels. But we need the case where the result equalling gridPixels to be
224 zero; so we do another modulus operation on the result to achieve this.
229 x = (gridPixels - (x % gridPixels)) % gridPixels;
234 y = (gridPixels - (y % gridPixels)) % gridPixels;
236 // Here we grab a section of the bigger pixmap, so that the background
237 // *looks* like it's scrolling...
238 QPixmap pm = gridBackground.copy(x, y, gridPixels, gridPixels);
239 QPalette pal = palette();
240 pal.setBrush(backgroundRole(), QBrush(pm));
241 setAutoFillBackground(true);
246 void DrawingView::AddNewObjectToDocument(Object * object)
250 object->Reparent(&document);
251 document.Add(object);
254 //printf("DrawingView::AddNewObjectToDocument(). object=%08X\n", object);
258 void DrawingView::HandleActionUpdate(void)
264 void DrawingView::SetCurrentLayer(int layer)
266 Object::currentLayer = layer;
267 //printf("DrawingView::CurrentLayer = %i\n", layer);
271 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
273 // This is undoing the transform, e.g. going from client coords to local coords.
274 // In essence, the height - y is height + (y * -1), the (y * -1) term doing the
275 // conversion of the y-axis from increasing bottom to top.
276 return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
280 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
282 // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
283 // No voodoo here, it's just grouped wrong to see it. It should be:
284 // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive
285 return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
289 void DrawingView::paintEvent(QPaintEvent * /*event*/)
291 QPainter qtPainter(this);
292 Painter painter(&qtPainter);
295 qtPainter.setRenderHint(QPainter::Antialiasing);
297 Object::SetViewportHeight(size().height());
299 // Draw coordinate axes
300 painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
301 painter.DrawLine(0, -16384, 0, 16384);
302 painter.DrawLine(-16384, 0, 16384, 0);
304 // The top level document takes care of rendering for us...
305 document.Draw(&painter);
309 painter.SetPen(QPen(QColor(200, 100, 0, 255), 1.0, Qt::DashLine));
310 painter.DrawCrosshair(oldPoint);
311 toolAction->Draw(&painter);
314 if (Object::selectionInProgress)
316 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
317 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
318 painter.DrawRect(Object::selection);
323 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
325 Painter::screenSize = Vector(size().width(), size().height());
326 UpdateGridBackground();
330 void DrawingView::mousePressEvent(QMouseEvent * event)
332 if (event->button() == Qt::LeftButton)
334 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
335 collided = document.Collided(point);
337 // Do an update if collided with at least *one* object in the document
343 if (Object::snapToGrid)
344 point = Object::SnapPointToGrid(point);
346 toolAction->MouseDown(point);
349 // Didn't hit any object and not using a tool, so do a selection rectangle
350 if (!(collided || toolAction))
352 Object::selectionInProgress = true;
353 Object::selection.setTopLeft(QPointF(point.x, point.y));
354 Object::selection.setBottomRight(QPointF(point.x, point.y));
357 else if (event->button() == Qt::MiddleButton)
360 oldPoint = Vector(event->x(), event->y());
361 // Should also change the mouse pointer as well...
362 setCursor(Qt::SizeAllCursor);
367 void DrawingView::mouseMoveEvent(QMouseEvent * event)
369 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
370 Object::selection.setBottomRight(QPointF(point.x, point.y));
372 if (event->buttons() & Qt::MiddleButton)
374 point = Vector(event->x(), event->y());
375 // Since we're using Qt coords for scrolling, we have to adjust them here to
376 // conform to Cartesian coords, since the origin is using Cartesian. :-)
377 // Vector delta(point, oldPoint);
378 Vector delta(oldPoint, point);
379 delta /= Painter::zoom;
381 Painter::origin -= delta;
383 UpdateGridBackground();
389 // Grid processing...
390 if ((event->buttons() & Qt::LeftButton) && Object::snapToGrid)
392 point = Object::SnapPointToGrid(point);
396 //we should keep track of the last point here and only pass this down *if* the point
398 document.PointerMoved(point);
400 if (document.NeedsUpdate() || Object::selectionInProgress)
405 if (Object::snapToGrid)
407 point = Object::SnapPointToGrid(point);
411 toolAction->MouseMoved(point);
417 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
419 if (event->button() == Qt::LeftButton)
421 document.PointerReleased();
423 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
424 //could set it up to use the document's update function (assumes that all object updates
425 //are being reported correctly:
426 // if (document.NeedsUpdate())
428 update(); // Do an update if collided with at least *one* object in the document
431 toolAction->MouseReleased();
433 if (Object::selectionInProgress)
435 // Select all the stuff inside of selection
436 Object::selectionInProgress = false;
439 else if (event->button() == Qt::MiddleButton)
442 setCursor(Qt::ArrowCursor);
447 void DrawingView::wheelEvent(QWheelEvent * event)
449 double zoomFactor = 1.25;
450 QSize sizeWin = size();
451 Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
452 center = Painter::QtToCartesianCoords(center);
454 // This is not centering for some reason. Need to figure out why. :-/
455 if (event->delta() > 0)
457 Vector newOrigin = center - ((center - Painter::origin) / zoomFactor);
458 Painter::origin = newOrigin;
459 Painter::zoom *= zoomFactor;
463 Vector newOrigin = center + ((-center + Painter::origin) * zoomFactor);
464 Painter::origin = newOrigin;
465 Painter::zoom /= zoomFactor;
468 // Object::gridSpacing = gridPixels / Painter::zoom;
469 // UpdateGridBackground();
470 SetGridSize(Object::gridSpacing * Painter::zoom);
472 // zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Object::gridSpacing));
476 void DrawingView::keyPressEvent(QKeyEvent * event)
479 toolAction->KeyDown(event->key());
483 void DrawingView::keyReleaseEvent(QKeyEvent * event)
486 toolAction->KeyReleased(event->key());