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]
24 // Uncomment this for debugging...
26 //#define DEBUGFOO // Various tool debugging...
27 //#define DEBUGTP // Toolpalette debugging...
29 #include "drawingview.h"
32 #include "mathconstants.h"
36 #include "dimension.h"
41 #define BACKGROUND_MAX_SIZE 512
44 DrawingView::DrawingView(QWidget * parent/*= NULL*/): QWidget(parent),
45 // The value in the settings file will override this.
46 useAntialiasing(true),
47 gridBackground(BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE),
48 scale(1.0), offsetX(-10), offsetY(-10),
49 document(Vector(0, 0)),
50 /*gridSpacing(12.0),*/ gridPixels(0), collided(false), rotateTool(false),
52 scrollDrag(false), addLineTool(false), addCircleTool(false),
53 addDimensionTool(false),
56 document.isTopLevelContainer = true;
57 setBackgroundRole(QPalette::Base);
58 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
60 Object::gridSpacing = 12.0;
61 // toolPalette = new ToolWindow();
63 // setCursor(cur[TOOLSelect]);
64 // setMouseTracking(true);
66 Line * line = new Line(Vector(5, 5), Vector(50, 40), &document);
68 document.Add(new Line(Vector(50, 40), Vector(10, 83), &document));
69 document.Add(new Line(Vector(10, 83), Vector(17, 2), &document));
70 document.Add(new Circle(Vector(100, 100), 36, &document));
71 document.Add(new Circle(Vector(50, 150), 49, &document));
72 document.Add(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3, &document)),
73 document.Add(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5, &document));
75 Dimension * dimension = new Dimension(Vector(0, 0), Vector(0, 0), DTLinear, &document);
76 line->SetDimensionOnLine(dimension);
77 document.Add(dimension);
79 // Alternate way to do the above...
80 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 shittyness 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
133 void DrawingView::SetRotateToolActive(bool state/*= true*/)
140 void DrawingView::SetToolActive(Action * action)
145 connect(toolAction, SIGNAL(ObjectReady(Object *)), this,
146 SLOT(AddNewObjectToDocument(Object *)));
151 void DrawingView::SetGridSize(uint32_t size)
154 if (size == gridPixels)
157 // Recreate the background bitmap
159 QPainter pmp(&gridBackground);
160 pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
161 pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
163 for(int i=0; i<(BACKGROUND_MAX_SIZE-1); i+=gridPixels)
165 pmp.drawLine(i, 0, i, BACKGROUND_MAX_SIZE - 1);
166 pmp.drawLine(0, i, BACKGROUND_MAX_SIZE - 1, i);
171 // Set up new BG brush & zoom level (pixels per base unit)
172 // Painter::zoom = gridPixels / gridSpacing;
173 Painter::zoom = gridPixels / Object::gridSpacing;
174 UpdateGridBackground();
178 void DrawingView::UpdateGridBackground(void)
180 // Transform the origin to Qt coordinates
181 Vector pixmapOrigin = Painter::CartesianToQtCoords(Vector());
182 int x = (int)pixmapOrigin.x;
183 int y = (int)pixmapOrigin.y;
184 // Use mod arithmetic to grab the correct swatch of background
186 Negative numbers still screw it up... Need to think about what we're
187 trying to do here. The fact that it worked with 72 seems to have been pure luck.
188 It seems the problem is negative numbers: We can't let that happen.
189 When taking away the zero, it pops over 1 px at zero, then goes about 1/2 a
192 The bitmap looks like this:
202 @ x = 1, we want it to look like:
204 -+---+---+---+---+---
207 -+---+---+---+---+---
212 Which means we need to grab the sample from x = 3. @ x = -1:
222 Which means we need to grab the sample from x = 1. Which means we have to take
223 the mirror of the modulus of gridPixels.
225 Doing a mod of a negative number is problematic: 1st, the compiler converts the
226 negative number to an unsigned int, then it does the mod. Gets you wrong answers
227 most of the time, unless you use a power of 2. :-P So what we do here is just
228 take the modulus of the negation, which means we don't have to worry about
231 The positive case looks gruesome (and it is) but it boils down to this: We take
232 the modulus of the X coordinate, then mirror it by subtraction from the
233 maximum (in this case, gridPixels). This gives us a number in the range of 1 to
234 gridPixels. But we need the case where the result equalling gridPixels to be
235 zero; so we do another modulus operation on the result to achieve this.
240 x = (gridPixels - (x % gridPixels)) % gridPixels;
245 y = (gridPixels - (y % gridPixels)) % gridPixels;
247 // Here we grab a section of the bigger pixmap, so that the background
248 // *looks* like it's scrolling...
249 QPixmap pm = gridBackground.copy(x, y, gridPixels, gridPixels);
250 QPalette pal = palette();
251 pal.setBrush(backgroundRole(), QBrush(pm));
252 setAutoFillBackground(true);
257 void DrawingView::AddNewObjectToDocument(Object * object)
261 object->Reparent(&document);
262 document.Add(object);
265 //printf("DrawingView::AddNewObjectToDocument(). object=%08X\n", object);
269 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
271 // This is undoing the transform, e.g. going from client coords to local coords.
272 // In essence, the height - y is height + (y * -1), the (y * -1) term doing the
273 // conversion of the y-axis from increasing bottom to top.
274 return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
278 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
280 // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
281 // No voodoo here, it's just grouped wrong to see it. It should be:
282 // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive
283 return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
289 // This looks strange, but it's really quite simple: We want a point that's
290 // more than half-way to the next grid point to snap there while conversely we
291 // want a point that's less than half-way to to the next grid point then snap
292 // to the one before it. So we add half of the grid spacing to the point, then
293 // divide by it so that we can remove the fractional part, then multiply it
294 // back to get back to the correct answer.
296 Vector DrawingView::SnapPointToGrid(Vector point)
298 point += gridSpacing / 2.0; // *This* adds to Z!!!
299 point /= gridSpacing;
300 point.x = floor(point.x);//need to fix this for negative numbers...
301 point.y = floor(point.y);
302 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
303 point *= gridSpacing;
309 void DrawingView::paintEvent(QPaintEvent * /*event*/)
311 QPainter qtPainter(this);
312 Painter painter(&qtPainter);
315 qtPainter.setRenderHint(QPainter::Antialiasing);
317 // Painter::screenSize = Vector(size().width(), size().height());
318 Object::SetViewportHeight(size().height());
320 // Draw coordinate axes
322 painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
323 painter.DrawLine(0, -16384, 0, 16384);
324 painter.DrawLine(-16384, 0, 16384, 0);
326 // Draw supplemental (tool related) points
327 // NOTE that this can be done as an action!
328 // In that case, we would need access to the document...
329 // [We can do that by making the document a class object...]
332 painter.SetPen(QPen(QColor(0, 200, 0), 2.0, Qt::SolidLine));
333 painter.DrawLine(rx - 10, ry, rx + 10, ry);
334 painter.DrawLine(rx, ry - 10, rx, ry + 10);
337 // Maybe we can make the grid into a background brush instead, and let Qt deal
340 // The top level document takes care of rendering for us...
341 document.Draw(&painter);
345 // painter.SetPen(QPen(Qt::green, 1.0, Qt::DashLine));
346 painter.SetPen(QPen(QColor(200, 100, 0, 255), 1.0, Qt::DashLine));
347 painter.DrawCrosshair(oldPoint);
348 toolAction->Draw(&painter);
351 if (Object::selectionInProgress)
353 // painter.SetPen(QPen(Qt::green, 1.0, Qt::SolidLine));
354 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
355 // painter.SetBrush(QBrush(Qt::NoBrush));
356 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
357 painter.DrawRect(Object::selection);
362 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
364 Painter::screenSize = Vector(size().width(), size().height());
365 UpdateGridBackground();
369 void DrawingView::mousePressEvent(QMouseEvent * event)
371 if (event->button() == Qt::LeftButton)
373 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
375 // Problem with this: Can't select stuff very well with the snap grid on.
376 // Completely screws things up, as sometimes things don't fall on the grid.
378 So, how to fix this? Have the Object check itself?
379 Maybe we can fix this by having the initial point not be snapped, but when there's
380 a drag, we substitute the snapped point 'oldPoint' which the Object keeps track of
381 internally to know how far it was dragged...
386 if (Object::snapToGrid)
387 point = Object::SnapPointToGrid(point);
390 collided = document.Collided(point);
393 update(); // Do an update if collided with at least *one* object in the document
397 if (Object::snapToGrid)
398 point = Object::SnapPointToGrid(point);
400 toolAction->MouseDown(point);
403 // Didn't hit any object and not using a tool, so do a selection rectangle
404 if (!(collided || toolAction))
406 Object::selectionInProgress = true;
407 Object::selection.setTopLeft(QPointF(point.x, point.y));
408 Object::selection.setBottomRight(QPointF(point.x, point.y));
411 else if (event->button() == Qt::MiddleButton)
414 oldPoint = Vector(event->x(), event->y());
415 // Should also change the mouse pointer as well...
416 setCursor(Qt::SizeAllCursor);
421 void DrawingView::mouseMoveEvent(QMouseEvent * event)
423 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
424 Object::selection.setBottomRight(QPointF(point.x, point.y));
426 if (event->buttons() & Qt::MiddleButton)
428 point = Vector(event->x(), event->y());
429 // Since we're using Qt coords for scrolling, we have to adjust them here to
430 // conform to Cartesian coords, since the origin is using Cartesian. :-)
431 Vector delta(point, oldPoint);
432 delta /= Painter::zoom;
434 Painter::origin -= delta;
436 UpdateGridBackground();
442 // Grid processing...
443 if ((event->buttons() & Qt::LeftButton) && Object::snapToGrid)
445 point = Object::SnapPointToGrid(point);
449 //we should keep track of the last point here and only pass this down *if* the point
451 document.PointerMoved(point);
453 if (document.NeedsUpdate() || Object::selectionInProgress)
458 if (Object::snapToGrid)
460 point = Object::SnapPointToGrid(point);
464 toolAction->MouseMoved(point);
470 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
472 if (event->button() == Qt::LeftButton)
474 document.PointerReleased();
476 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
477 //could set it up to use the document's update function (assumes that all object updates
478 //are being reported correctly:
479 // if (document.NeedsUpdate())
481 update(); // Do an update if collided with at least *one* object in the document
484 toolAction->MouseReleased();
486 if (Object::selectionInProgress)
488 // Select all the stuff inside of selection
489 Object::selectionInProgress = false;
492 else if (event->button() == Qt::MiddleButton)
495 setCursor(Qt::ArrowCursor);