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"
37 #include "drawcircleaction.h"
38 #include "drawdimensionaction.h"
39 #include "drawlineaction.h"
44 #define BACKGROUND_MAX_SIZE 512
47 DrawingView::DrawingView(QWidget * parent/*= NULL*/): QWidget(parent),
48 // The value in the settings file will override this.
49 useAntialiasing(true),
50 gridBackground(BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE),
51 scale(1.0), offsetX(-10), offsetY(-10),
52 document(Vector(0, 0)),
53 gridSpacing(12.0), gridPixels(0), collided(false), rotateTool(false),
55 scrollDrag(false), addLineTool(false), addCircleTool(false),
56 addDimensionTool(false),
59 document.isTopLevelContainer = true;
60 setBackgroundRole(QPalette::Base);
61 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
63 // toolPalette = new ToolWindow();
65 // setCursor(cur[TOOLSelect]);
66 // setMouseTracking(true);
68 Line * line = new Line(Vector(5, 5), Vector(50, 40), &document);
70 document.Add(new Line(Vector(50, 40), Vector(10, 83), &document));
71 document.Add(new Line(Vector(10, 83), Vector(17, 2), &document));
72 document.Add(new Circle(Vector(100, 100), 36, &document));
73 document.Add(new Circle(Vector(50, 150), 49, &document));
74 document.Add(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3, &document)),
75 document.Add(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5, &document));
77 Dimension * dimension = new Dimension(Vector(0, 0), Vector(0, 0), DTLinear, &document);
78 line->SetDimensionOnLine(dimension);
79 document.Add(dimension);
81 // Alternate way to do the above...
82 line->SetDimensionOnLine();
86 Here we set the grid size in pixels--12 in this case. Initially, we have our
87 zoom set to make this represent 12 inches at a zoom factor of 25%. (This is
88 arbitrary.) So, to be able to decouple the grid size from the zoom, we need
89 to be able to set the size of the background grid (which we do here at an
90 arbitrary 12 pixels) to anything we want (within reason, of course :-).
92 The drawing enforces the grid spacing through the drawing->gridSpacing variable.
94 drawing->gridSpacing = 12.0 / Painter::zoom;
96 Painter::zoom is the zoom factor for the drawing, and all mouse clicks are
97 translated to Cartesian coordinates through this. (Initially, Painter::zoom is
98 set to 1.0. SCREEN_ZOOM is set to 1.0/4.0.)
100 Really, the 100% zoom level can be set at *any* zoom level, it's more of a
101 convenience function than any measure of absolutes. Doing things that way we
102 could rid ourselves of the whole SCREEN_ZOOM parameter and all the attendant
103 shittyness that comes with it.
105 However, it seems that SCREEN_ZOOM is used to make text and arrow sizes show up
106 a certain way, which means we should probably create something else in those
107 objects to take its place--like some kind of scale factor. This would seem to
108 imply that certain point sizes actually *do* tie things like fonts to absolute
109 sizes on the screen, but not necessarily because you could have an inch scale
110 with text that is quite small relative to other objects on the screen, which
111 currently you have to zoom in to see (and which blows up the text). Point sizes
112 in an application like this are a bit meaningless; even though an inch is an
113 inch regardless of the zoom level a piece of text can be larger or smaller than
114 this. Maybe this is the case for having a base unit and basing point sizes off
117 Here's what's been figured out. Painter::zoom is simply the ratio of pixels to
118 base units. What that means is that if you have a 12px grid with a 6" grid size
119 (& base unit of "inches"), Painter::zoom becomes 12px / 6" = 2.0 px/in.
121 Dimensions now have a "size" parameter to set their absolute size in relation
122 to the base unit. ATM, the arrows are drawn in pixels, but also scaled by
123 Painter::zoom *and* size. Same with the dimension text; it's drawn at 10pt and
124 scaled the same way as the arrowheads.
126 Need a way to scale line widths as well. :-/ Shouldn't be too difficult, just
127 need a thickness parameter similar to the "size" param for dimensions.
131 QPainter pmp(&gridBackground);
132 pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
133 pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
135 for(int i=0; i<(BACKGROUND_MAX_SIZE-1); i+=12)
137 pmp.drawLine(i, 0, i, BACKGROUND_MAX_SIZE - 1);
138 pmp.drawLine(0, i, BACKGROUND_MAX_SIZE - 1, i);
142 UpdateGridBackground();
149 void DrawingView::SetRotateToolActive(bool state/*= true*/)
156 void DrawingView::SetAddLineToolActive(bool state/*= true*/)
160 toolAction = new DrawLineAction();
161 connect(toolAction, SIGNAL(ObjectReady(Object *)), this,
162 SLOT(AddNewObjectToDocument(Object *)));
166 //printf("DrawingView::SetAddLineToolActive(). toolAction=%08X\n", toolAction);
170 void DrawingView::SetAddCircleToolActive(bool state/*= true*/)
174 toolAction = new DrawCircleAction();
175 connect(toolAction, SIGNAL(ObjectReady(Object *)), this,
176 SLOT(AddNewObjectToDocument(Object *)));
183 void DrawingView::SetAddDimensionToolActive(bool state/*= true*/)
187 toolAction = new DrawDimensionAction();
188 connect(toolAction, SIGNAL(ObjectReady(Object *)), this,
189 SLOT(AddNewObjectToDocument(Object *)));
196 void DrawingView::SetGridSize(uint32_t size)
199 if (size == gridPixels)
202 // Recreate the background bitmap
204 QPainter pmp(&gridBackground);
205 pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
206 pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
208 for(int i=0; i<(BACKGROUND_MAX_SIZE-1); i+=gridPixels)
210 pmp.drawLine(i, 0, i, BACKGROUND_MAX_SIZE - 1);
211 pmp.drawLine(0, i, BACKGROUND_MAX_SIZE - 1, i);
216 // Set up new BG brush & zoom level (pixels per base unit)
217 Painter::zoom = gridPixels / gridSpacing;
218 UpdateGridBackground();
222 void DrawingView::UpdateGridBackground(void)
225 //#define BG_BRUSH_SPAN 72
226 #define BG_BRUSH_SPAN (gridPixels)
227 // Transform the origin to Qt coordinates
228 Vector pixmapOrigin = Painter::CartesianToQtCoords(Vector());
229 int x = (int)pixmapOrigin.x;
230 int y = (int)pixmapOrigin.y;
231 // Use mod arithmetic to grab the correct swatch of background
232 // Problem with mod 128: Negative numbers screw it up... [FIXED]
234 Negative numbers still screw it up... Need to think about what we're
235 trying to do here. The fact that it worked with 72 seems to have been pure luck.
236 It seems the problem is negative numbers: We can't let that happen.
237 When taking away the zero, it pops over 1 px at zero, then goes about 1/2 a
240 The bitmap looks like this:
250 @ x = 1, we want it to look like:
252 -+---+---+---+---+---
255 -+---+---+---+---+---
260 Which means we need to grab the sample from x = 3. @ x = -1:
270 Which means we need to grab the sample from x = 1. Which means we have to take
271 the inverse of the modulus of gridPixels.
273 Doing a mod of a negative number is problematic: 1st, the compiler converts the
274 negative number to an unsigned int, then it does the mod. Gets you wrong answers
275 most of the time, unless you use a power of 2. :-P
277 This has been solved below:
278 UGB: pmo=(2.000000,687.000000) X=2, X%48=2, new x=45
279 UGB: pmo=(1.000000,687.000000) X=1, X%48=1, new x=46
280 UGB: pmo=(0.000000,687.000000) X=0, X%48=0, new x=47
281 UGB: pmo=(-1.000000,687.000000) X=-1, X%48=15, new x=1
282 UGB: pmo=(-2.000000,686.000000) X=-2, X%48=14, new x=2
283 UGB: pmo=(-3.000000,686.000000) X=-3, X%48=13, new x=3
285 Problem with changing grid size causes x/y origin to be shown incorrectly:
286 UGB: pmo=(10.000000,812.000000) X=10, X%12=10, new x=2
287 UGB: pmo=(10.000000,812.000000) X=10, X%4=2, new x=2
288 UGB: pmo=(3.333333,818.666667) X=3, X%48=3, new x=45
289 UGB: pmo=(39.000000,782.000000) X=39, X%48=39, new x=9 <-- MMB move is here
290 UGB: pmo=(39.000000,781.000000) X=39, X%48=39, new x=9
295 //printf("UGB: pmo=(%f,%f) X=%i, X%%%i=%i, ", pixmapOrigin.x, pixmapOrigin.y, x, BG_BRUSH_SPAN, x % BG_BRUSH_SPAN);
296 // x = (x < 0 ? 0 : BG_BRUSH_SPAN - 1) - (x % BG_BRUSH_SPAN);
297 // y = (y < 0 ? 0 : BG_BRUSH_SPAN - 1) - (y % BG_BRUSH_SPAN);
298 // x = (BG_BRUSH_SPAN - 1) - (x % BG_BRUSH_SPAN);
299 // y = (BG_BRUSH_SPAN - 1) - (y % BG_BRUSH_SPAN);
300 // x = (x < 0 ? 0 : BG_BRUSH_SPAN - 1) - ((x < 0 ? -x : x) % BG_BRUSH_SPAN);
302 x = -x % BG_BRUSH_SPAN;
304 x = (BG_BRUSH_SPAN - (x % BG_BRUSH_SPAN)) % BG_BRUSH_SPAN;
306 // y = (y < 0 ? 0 : BG_BRUSH_SPAN - 1) - ((y < 0 ? -y : y) % BG_BRUSH_SPAN);
308 y = -y % BG_BRUSH_SPAN;
310 y = (BG_BRUSH_SPAN - (y % BG_BRUSH_SPAN)) % BG_BRUSH_SPAN;
311 //printf("new x=%i\n", x);
313 // Here we grab a section of the bigger pixmap, so that the background
314 // *looks* like it's scrolling...
315 QPixmap pm = gridBackground.copy(x, y, BG_BRUSH_SPAN, BG_BRUSH_SPAN);
316 QPalette pal = palette();
317 pal.setBrush(backgroundRole(), QBrush(pm));
318 setAutoFillBackground(true);
323 void DrawingView::AddNewObjectToDocument(Object * object)
327 object->Reparent(&document);
328 document.Add(object);
331 //printf("DrawingView::AddNewObjectToDocument(). object=%08X\n", object);
335 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
337 // This is undoing the transform, e.g. going from client coords to local coords.
338 // In essence, the height - y is height + (y * -1), the (y * -1) term doing the
339 // conversion of the y-axis from increasing bottom to top.
340 return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
344 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
346 // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
347 // No voodoo here, it's just grouped wrong to see it. It should be:
348 // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive
349 return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
353 void DrawingView::paintEvent(QPaintEvent * /*event*/)
355 QPainter qtPainter(this);
356 Painter painter(&qtPainter);
359 qtPainter.setRenderHint(QPainter::Antialiasing);
361 // Painter::screenSize = Vector(size().width(), size().height());
362 Object::SetViewportHeight(size().height());
364 // Draw coordinate axes
366 painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
367 painter.DrawLine(0, -16384, 0, 16384);
368 painter.DrawLine(-16384, 0, 16384, 0);
370 // Draw supplemental (tool related) points
371 // NOTE that this can be done as an action!
372 // In that case, we would need access to the document...
373 // [We can do that by making the document a class object...]
376 painter.SetPen(QPen(QColor(0, 200, 0), 2.0, Qt::SolidLine));
377 painter.DrawLine(rx - 10, ry, rx + 10, ry);
378 painter.DrawLine(rx, ry - 10, rx, ry + 10);
381 // Maybe we can make the grid into a background brush instead, and let Qt deal
384 // The top level document takes care of rendering for us...
385 document.Draw(&painter);
388 toolAction->Draw(&painter);
390 if (Object::selectionInProgress)
392 // painter.SetPen(QPen(Qt::green, 1.0, Qt::SolidLine));
393 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
394 // painter.SetBrush(QBrush(Qt::NoBrush));
395 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
396 painter.DrawRect(Object::selection);
401 void DrawingView::resizeEvent(QResizeEvent * event)
403 Painter::screenSize = Vector(size().width(), size().height());
404 // Painter::screenSize = Vector(event->size().width(), event->size().height());
405 //printf("DrawingView::resizeEvent: new size:
406 UpdateGridBackground();
410 void DrawingView::mousePressEvent(QMouseEvent * event)
412 if (event->button() == Qt::LeftButton)
414 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
415 collided = document.Collided(point);
418 update(); // Do an update if collided with at least *one* object in the document
421 toolAction->MouseDown(point);
423 // Didn't hit any object and not using a tool, so do a selection rectangle
424 if (!(collided || toolAction))
426 Object::selectionInProgress = true;
427 Object::selection.setTopLeft(QPointF(point.x, point.y));
428 Object::selection.setBottomRight(QPointF(point.x, point.y));
431 else if (event->button() == Qt::MiddleButton)
434 oldPoint = Vector(event->x(), event->y());
435 // Should also change the mouse pointer as well...
436 setCursor(Qt::SizeAllCursor);
441 void DrawingView::mouseMoveEvent(QMouseEvent * event)
443 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
444 Object::selection.setBottomRight(QPointF(point.x, point.y));
446 if (event->buttons() & Qt::MiddleButton)
448 point = Vector(event->x(), event->y());
449 // Since we're using Qt coords for scrolling, we have to adjust them here to
450 // conform to Cartesian coords, since the origin is using Cartesian. :-)
451 Vector delta(point, oldPoint);
452 delta /= Painter::zoom;
454 Painter::origin -= delta;
456 UpdateGridBackground();
462 // Grid processing...
464 // This looks strange, but it's really quite simple: We want a point that's
465 // more than half-way to the next grid point to snap there while conversely
466 // we want a point that's less than half-way to to the next grid point then
467 // snap to the one before it. So we add half of the grid spacing to the
468 // point, then divide by it so that we can remove the fractional part, then
469 // multiply it back to get back to the correct answer.
470 if (event->buttons() & Qt::LeftButton)
472 point += gridSpacing / 2.0; // *This* adds to Z!!!
473 point /= gridSpacing;
474 //200% is ok, gridSpacing = 6 in this case...
475 //won't run into problems until gridSpacing = 1.5 (zoom = 800%)
476 //run into problems with this approach: when zoom level is 200% this truncates to
477 //integers, which is *not* what's wanted here...
478 point.x = floor(point.x);//need to fix this for negative numbers...
479 point.y = floor(point.y);
480 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
481 point *= gridSpacing;
484 //we should keep track of the last point here and only pass this down *if* the point
486 document.PointerMoved(point);
488 if (document.NeedsUpdate() || Object::selectionInProgress)
493 toolAction->MouseMoved(point);
499 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
501 if (event->button() == Qt::LeftButton)
503 document.PointerReleased();
505 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
506 //could set it up to use the document's update function (assumes that all object updates
507 //are being reported correctly:
508 // if (document.NeedsUpdate())
510 update(); // Do an update if collided with at least *one* object in the document
513 toolAction->MouseReleased();
515 if (Object::selectionInProgress)
517 // Select all the stuff inside of selection
518 Object::selectionInProgress = false;
521 else if (event->button() == Qt::MiddleButton)
524 setCursor(Qt::ArrowCursor);