]> Shamusworld >> Repos - architektonas/blob - src/drawingview.cpp
Fixes for grid background drawing of arbitrary size.
[architektonas] / src / drawingview.cpp
1 // drawingview.cpp
2 //
3 // Part of the Architektonas Project
4 // (C) 2011 Underground Software
5 // See the README and GPLv3 files for licensing and warranty information
6 //
7 // JLH = James Hammons <jlhamm@acm.org>
8 //
9 // Who  When        What
10 // ---  ----------  -------------------------------------------------------------
11 // JLH  03/22/2011  Created this file
12 // JLH  09/29/2011  Added middle mouse button panning
13 //
14
15 // FIXED:
16 //
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]
19 //
20 // STILL TO BE DONE:
21 //
22 //
23
24 // Uncomment this for debugging...
25 //#define DEBUG
26 //#define DEBUGFOO                              // Various tool debugging...
27 //#define DEBUGTP                               // Toolpalette debugging...
28
29 #include "drawingview.h"
30
31 #include <stdint.h>
32 #include "mathconstants.h"
33
34 #include "arc.h"
35 #include "circle.h"
36 #include "dimension.h"
37 #include "drawcircleaction.h"
38 #include "drawdimensionaction.h"
39 #include "drawlineaction.h"
40 #include "line.h"
41 #include "painter.h"
42
43
44 #define BACKGROUND_MAX_SIZE     512
45
46
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),
54         rx(150.0), ry(150.0),
55         scrollDrag(false), addLineTool(false), addCircleTool(false),
56         addDimensionTool(false),
57         toolAction(NULL)
58 {
59         document.isTopLevelContainer = true;
60         setBackgroundRole(QPalette::Base);
61         setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
62
63 //      toolPalette = new ToolWindow();
64 //      CreateCursors();
65 //      setCursor(cur[TOOLSelect]);
66 //      setMouseTracking(true);
67
68         Line * line = new Line(Vector(5, 5), Vector(50, 40), &document);
69         document.Add(line);
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));
76 #if 1
77         Dimension * dimension = new Dimension(Vector(0, 0), Vector(0, 0), DTLinear, &document);
78         line->SetDimensionOnLine(dimension);
79         document.Add(dimension);
80 #else
81         // Alternate way to do the above...
82         line->SetDimensionOnLine();
83 #endif
84
85 /*
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 :-).
91
92 The drawing enforces the grid spacing through the drawing->gridSpacing variable.
93
94         drawing->gridSpacing = 12.0 / Painter::zoom;
95
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.)
99
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.
104
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
115 of that.
116
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.
120
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.
125
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.
128
129 */
130 #if 0
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));
134
135         for(int i=0; i<(BACKGROUND_MAX_SIZE-1); i+=12)
136         {
137                 pmp.drawLine(i, 0, i, BACKGROUND_MAX_SIZE - 1);
138                 pmp.drawLine(0, i, BACKGROUND_MAX_SIZE - 1, i);
139         }
140
141         pmp.end();
142         UpdateGridBackground();
143 #else
144         SetGridSize(12);
145 #endif
146 }
147
148
149 void DrawingView::SetRotateToolActive(bool state/*= true*/)
150 {
151         rotateTool = state;
152         update();
153 }
154
155
156 void DrawingView::SetAddLineToolActive(bool state/*= true*/)
157 {
158         if (state)
159         {
160                 toolAction = new DrawLineAction();
161                 connect(toolAction, SIGNAL(ObjectReady(Object *)), this,
162                         SLOT(AddNewObjectToDocument(Object *)));
163         }
164
165         update();
166 //printf("DrawingView::SetAddLineToolActive(). toolAction=%08X\n", toolAction);
167 }
168
169
170 void DrawingView::SetAddCircleToolActive(bool state/*= true*/)
171 {
172         if (state)
173         {
174                 toolAction = new DrawCircleAction();
175                 connect(toolAction, SIGNAL(ObjectReady(Object *)), this,
176                         SLOT(AddNewObjectToDocument(Object *)));
177         }
178
179         update();
180 }
181
182
183 void DrawingView::SetAddDimensionToolActive(bool state/*= true*/)
184 {
185         if (state)
186         {
187                 toolAction = new DrawDimensionAction();
188                 connect(toolAction, SIGNAL(ObjectReady(Object *)), this,
189                         SLOT(AddNewObjectToDocument(Object *)));
190         }
191
192         update();
193 }
194
195
196 void DrawingView::SetGridSize(uint32_t size)
197 {
198         // Sanity check
199         if (size == gridPixels)
200                 return;
201
202         // Recreate the background bitmap
203         gridPixels = size;
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));
207
208         for(int i=0; i<(BACKGROUND_MAX_SIZE-1); i+=gridPixels)
209         {
210                 pmp.drawLine(i, 0, i, BACKGROUND_MAX_SIZE - 1);
211                 pmp.drawLine(0, i, BACKGROUND_MAX_SIZE - 1, i);
212         }
213
214         pmp.end();
215
216         // Set up new BG brush & zoom level (pixels per base unit)
217         Painter::zoom = gridPixels / gridSpacing;
218         UpdateGridBackground();
219 }
220
221
222 void DrawingView::UpdateGridBackground(void)
223 {
224 //was: 128
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]
233 /*
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
238 grid at x<0.
239
240 The bitmap looks like this:
241
242 +---+---+---+---+---
243 |   |   |   |   |
244 |   |   |   |   |
245 +---+---+---+---+---
246 |   |   |   |   |
247 |   |   |   |   |
248 |   |   |   |   |
249
250 @ x = 1, we want it to look like:
251
252 -+---+---+---+---+---
253  |   |   |   |   |
254  |   |   |   |   |
255 -+---+---+---+---+---
256  |   |   |   |   |
257  |   |   |   |   |
258  |   |   |   |   |
259
260 Which means we need to grab the sample from x = 3. @ x = -1:
261
262 ---+---+---+---+---
263    |   |   |   |
264    |   |   |   |
265 ---+---+---+---+---
266    |   |   |   |
267    |   |   |   |
268    |   |   |   |
269
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.
272
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
276
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
284
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
291
292
293
294 */
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);
301         if (x < 0)
302                 x = -x % BG_BRUSH_SPAN;
303         else
304                 x = (BG_BRUSH_SPAN - (x % BG_BRUSH_SPAN)) % BG_BRUSH_SPAN;
305
306 //      y = (y < 0 ? 0 : BG_BRUSH_SPAN - 1) - ((y < 0 ? -y : y) % BG_BRUSH_SPAN);
307         if (y < 0)
308                 y = -y % BG_BRUSH_SPAN;
309         else
310                 y = (BG_BRUSH_SPAN - (y % BG_BRUSH_SPAN)) % BG_BRUSH_SPAN;
311 //printf("new x=%i\n", x);
312
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);
319         setPalette(pal);
320 }
321
322
323 void DrawingView::AddNewObjectToDocument(Object * object)
324 {
325         if (object)
326         {
327                 object->Reparent(&document);
328                 document.Add(object);
329                 update();
330         }
331 //printf("DrawingView::AddNewObjectToDocument(). object=%08X\n", object);
332 }
333
334
335 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
336 {
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()));
341 }
342
343
344 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
345 {
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);
350 }
351
352
353 void DrawingView::paintEvent(QPaintEvent * /*event*/)
354 {
355         QPainter qtPainter(this);
356         Painter painter(&qtPainter);
357
358         if (useAntialiasing)
359                 qtPainter.setRenderHint(QPainter::Antialiasing);
360
361 //      Painter::screenSize = Vector(size().width(), size().height());
362         Object::SetViewportHeight(size().height());
363
364         // Draw coordinate axes
365
366         painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
367         painter.DrawLine(0, -16384, 0, 16384);
368         painter.DrawLine(-16384, 0, 16384, 0);
369
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...]
374         if (rotateTool)
375         {
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);
379         }
380
381 // Maybe we can make the grid into a background brush instead, and let Qt deal
382 // with it??? YES!!
383
384         // The top level document takes care of rendering for us...
385         document.Draw(&painter);
386
387         if (toolAction)
388                 toolAction->Draw(&painter);
389
390         if (Object::selectionInProgress)
391         {
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);
397         }
398 }
399
400
401 void DrawingView::resizeEvent(QResizeEvent * event)
402 {
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();
407 }
408
409
410 void DrawingView::mousePressEvent(QMouseEvent * event)
411 {
412         if (event->button() == Qt::LeftButton)
413         {
414                 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
415                 collided = document.Collided(point);
416
417                 if (collided)
418                         update();       // Do an update if collided with at least *one* object in the document
419
420                 if (toolAction)
421                         toolAction->MouseDown(point);
422
423                 // Didn't hit any object and not using a tool, so do a selection rectangle
424                 if (!(collided || toolAction))
425                 {
426                         Object::selectionInProgress = true;
427                         Object::selection.setTopLeft(QPointF(point.x, point.y));
428                         Object::selection.setBottomRight(QPointF(point.x, point.y));
429                 }
430         }
431         else if (event->button() == Qt::MiddleButton)
432         {
433                 scrollDrag = true;
434                 oldPoint = Vector(event->x(), event->y());
435                 // Should also change the mouse pointer as well...
436                 setCursor(Qt::SizeAllCursor);
437         }
438 }
439
440
441 void DrawingView::mouseMoveEvent(QMouseEvent * event)
442 {
443         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
444         Object::selection.setBottomRight(QPointF(point.x, point.y));
445
446         if (event->buttons() & Qt::MiddleButton)
447         {
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;
453                 delta.y = -delta.y;
454                 Painter::origin -= delta;
455
456                 UpdateGridBackground();
457                 update();
458                 oldPoint = point;
459                 return;
460         }
461
462         // Grid processing...
463 #if 1
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)
471         {
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;
482         }
483 #endif
484 //we should keep track of the last point here and only pass this down *if* the point
485 //changed...
486         document.PointerMoved(point);
487
488         if (document.NeedsUpdate() || Object::selectionInProgress)
489                 update();
490
491         if (toolAction)
492         {
493                 toolAction->MouseMoved(point);
494                 update();
495         }
496 }
497
498
499 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
500 {
501         if (event->button() == Qt::LeftButton)
502         {
503                 document.PointerReleased();
504
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())
509 //              if (collided)
510                         update();       // Do an update if collided with at least *one* object in the document
511
512                 if (toolAction)
513                         toolAction->MouseReleased();
514
515                 if (Object::selectionInProgress)
516                 {
517                         // Select all the stuff inside of selection
518                         Object::selectionInProgress = false;
519                 }
520         }
521         else if (event->button() == Qt::MiddleButton)
522         {
523                 scrollDrag = false;
524                 setCursor(Qt::ArrowCursor);
525         }
526 }
527