]> Shamusworld >> Repos - architektonas/blob - src/drawingview.cpp
31aeaf028193e6fe9c43cb530a9a166d7c6befce
[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 "drawarcaction.h"
38 #include "drawcircleaction.h"
39 #include "drawdimensionaction.h"
40 #include "drawlineaction.h"
41 #include "line.h"
42 #include "painter.h"
43
44
45 #define BACKGROUND_MAX_SIZE     512
46
47
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),
53         document(Vector(0, 0)),
54         gridSpacing(12.0), gridPixels(0), collided(false), rotateTool(false),
55         rx(150.0), ry(150.0),
56         scrollDrag(false), addLineTool(false), addCircleTool(false),
57         addDimensionTool(false),
58         toolAction(NULL)
59 {
60         document.isTopLevelContainer = true;
61         setBackgroundRole(QPalette::Base);
62         setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
63
64 //      toolPalette = new ToolWindow();
65 //      CreateCursors();
66 //      setCursor(cur[TOOLSelect]);
67 //      setMouseTracking(true);
68
69         Line * line = new Line(Vector(5, 5), Vector(50, 40), &document);
70         document.Add(line);
71         document.Add(new Line(Vector(50, 40), Vector(10, 83), &document));
72         document.Add(new Line(Vector(10, 83), Vector(17, 2), &document));
73         document.Add(new Circle(Vector(100, 100), 36, &document));
74         document.Add(new Circle(Vector(50, 150), 49, &document));
75         document.Add(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3, &document)),
76         document.Add(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5, &document));
77 #if 1
78         Dimension * dimension = new Dimension(Vector(0, 0), Vector(0, 0), DTLinear, &document);
79         line->SetDimensionOnLine(dimension);
80         document.Add(dimension);
81 #else
82         // Alternate way to do the above...
83         line->SetDimensionOnLine();
84 #endif
85
86 /*
87 Here we set the grid size in pixels--12 in this case. Initially, we have our
88 zoom set to make this represent 12 inches at a zoom factor of 25%. (This is
89 arbitrary.) So, to be able to decouple the grid size from the zoom, we need
90 to be able to set the size of the background grid (which we do here at an
91 arbitrary 12 pixels) to anything we want (within reason, of course :-).
92
93 The drawing enforces the grid spacing through the drawing->gridSpacing variable.
94
95         drawing->gridSpacing = 12.0 / Painter::zoom;
96
97 Painter::zoom is the zoom factor for the drawing, and all mouse clicks are
98 translated to Cartesian coordinates through this. (Initially, Painter::zoom is
99 set to 1.0. SCREEN_ZOOM is set to 1.0/4.0.)
100
101 Really, the 100% zoom level can be set at *any* zoom level, it's more of a
102 convenience function than any measure of absolutes. Doing things that way we
103 could rid ourselves of the whole SCREEN_ZOOM parameter and all the attendant
104 shittyness that comes with it.
105
106 However, it seems that SCREEN_ZOOM is used to make text and arrow sizes show up
107 a certain way, which means we should probably create something else in those
108 objects to take its place--like some kind of scale factor. This would seem to
109 imply that certain point sizes actually *do* tie things like fonts to absolute
110 sizes on the screen, but not necessarily because you could have an inch scale
111 with text that is quite small relative to other objects on the screen, which
112 currently you have to zoom in to see (and which blows up the text). Point sizes
113 in an application like this are a bit meaningless; even though an inch is an
114 inch regardless of the zoom level a piece of text can be larger or smaller than
115 this. Maybe this is the case for having a base unit and basing point sizes off
116 of that.
117
118 Here's what's been figured out. Painter::zoom is simply the ratio of pixels to
119 base units. What that means is that if you have a 12px grid with a 6" grid size
120 (& base unit of "inches"), Painter::zoom becomes 12px / 6" = 2.0 px/in.
121
122 Dimensions now have a "size" parameter to set their absolute size in relation
123 to the base unit. ATM, the arrows are drawn in pixels, but also scaled by
124 Painter::zoom *and* size. Same with the dimension text; it's drawn at 10pt and
125 scaled the same way as the arrowheads.
126
127 Need a way to scale line widths as well. :-/ Shouldn't be too difficult, just
128 need a thickness parameter similar to the "size" param for dimensions. (And now
129 we do! :-)
130
131 */
132 #if 0
133         QPainter pmp(&gridBackground);
134         pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
135         pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
136
137         for(int i=0; i<(BACKGROUND_MAX_SIZE-1); i+=12)
138         {
139                 pmp.drawLine(i, 0, i, BACKGROUND_MAX_SIZE - 1);
140                 pmp.drawLine(0, i, BACKGROUND_MAX_SIZE - 1, i);
141         }
142
143         pmp.end();
144         UpdateGridBackground();
145 #else
146         SetGridSize(12);
147 #endif
148 }
149
150
151 void DrawingView::SetRotateToolActive(bool state/*= true*/)
152 {
153         rotateTool = state;
154         update();
155 }
156
157
158 void DrawingView::SetAddLineToolActive(bool state/*= true*/)
159 {
160         if (state)
161         {
162                 toolAction = new DrawLineAction();
163                 connect(toolAction, SIGNAL(ObjectReady(Object *)), this,
164                         SLOT(AddNewObjectToDocument(Object *)));
165         }
166
167         update();
168 //printf("DrawingView::SetAddLineToolActive(). toolAction=%08X\n", toolAction);
169 }
170
171
172 void DrawingView::SetAddCircleToolActive(bool state/*= true*/)
173 {
174         if (state)
175         {
176                 toolAction = new DrawCircleAction();
177                 connect(toolAction, SIGNAL(ObjectReady(Object *)), this,
178                         SLOT(AddNewObjectToDocument(Object *)));
179         }
180
181         update();
182 }
183
184
185 void DrawingView::SetAddArcToolActive(bool state/*= true*/)
186 {
187         if (state)
188         {
189                 toolAction = new DrawArcAction();
190                 connect(toolAction, SIGNAL(ObjectReady(Object *)), this,
191                         SLOT(AddNewObjectToDocument(Object *)));
192         }
193
194         update();
195 }
196
197
198 void DrawingView::SetAddDimensionToolActive(bool state/*= true*/)
199 {
200         if (state)
201         {
202                 toolAction = new DrawDimensionAction();
203                 connect(toolAction, SIGNAL(ObjectReady(Object *)), this,
204                         SLOT(AddNewObjectToDocument(Object *)));
205         }
206
207         update();
208 }
209
210
211 void DrawingView::SetGridSize(uint32_t size)
212 {
213         // Sanity check
214         if (size == gridPixels)
215                 return;
216
217         // Recreate the background bitmap
218         gridPixels = size;
219         QPainter pmp(&gridBackground);
220         pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
221         pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
222
223         for(int i=0; i<(BACKGROUND_MAX_SIZE-1); i+=gridPixels)
224         {
225                 pmp.drawLine(i, 0, i, BACKGROUND_MAX_SIZE - 1);
226                 pmp.drawLine(0, i, BACKGROUND_MAX_SIZE - 1, i);
227         }
228
229         pmp.end();
230
231         // Set up new BG brush & zoom level (pixels per base unit)
232         Painter::zoom = gridPixels / gridSpacing;
233         UpdateGridBackground();
234 }
235
236
237 void DrawingView::UpdateGridBackground(void)
238 {
239         // Transform the origin to Qt coordinates
240         Vector pixmapOrigin = Painter::CartesianToQtCoords(Vector());
241         int x = (int)pixmapOrigin.x;
242         int y = (int)pixmapOrigin.y;
243         // Use mod arithmetic to grab the correct swatch of background
244 /*
245 Negative numbers still screw it up... Need to think about what we're
246 trying to do here. The fact that it worked with 72 seems to have been pure luck.
247 It seems the problem is negative numbers: We can't let that happen.
248 When taking away the zero, it pops over 1 px at zero, then goes about 1/2 a
249 grid at x<0.
250
251 The bitmap looks like this:
252
253 +---+---+---+---+---
254 |   |   |   |   |
255 |   |   |   |   |
256 +---+---+---+---+---
257 |   |   |   |   |
258 |   |   |   |   |
259 |   |   |   |   |
260
261 @ x = 1, we want it to look like:
262
263 -+---+---+---+---+---
264  |   |   |   |   |
265  |   |   |   |   |
266 -+---+---+---+---+---
267  |   |   |   |   |
268  |   |   |   |   |
269  |   |   |   |   |
270
271 Which means we need to grab the sample from x = 3. @ x = -1:
272
273 ---+---+---+---+---
274    |   |   |   |
275    |   |   |   |
276 ---+---+---+---+---
277    |   |   |   |
278    |   |   |   |
279    |   |   |   |
280
281 Which means we need to grab the sample from x = 1. Which means we have to take
282 the mirror of the modulus of gridPixels.
283
284 Doing a mod of a negative number is problematic: 1st, the compiler converts the
285 negative number to an unsigned int, then it does the mod. Gets you wrong answers
286 most of the time, unless you use a power of 2. :-P So what we do here is just
287 take the modulus of the negation, which means we don't have to worry about
288 mirroring it later.
289
290 The positive case looks gruesome (and it is) but it boils down to this: We take
291 the modulus of the X coordinate, then mirror it by subtraction from the
292 maximum (in this case, gridPixels). This gives us a number in the range of 1 to
293 gridPixels. But we need the case where the result equalling gridPixels to be
294 zero; so we do another modulus operation on the result to achieve this.
295 */
296         if (x < 0)
297                 x = -x % gridPixels;
298         else
299                 x = (gridPixels - (x % gridPixels)) % gridPixels;
300
301         if (y < 0)
302                 y = -y % gridPixels;
303         else
304                 y = (gridPixels - (y % gridPixels)) % gridPixels;
305
306         // Here we grab a section of the bigger pixmap, so that the background
307         // *looks* like it's scrolling...
308         QPixmap pm = gridBackground.copy(x, y, gridPixels, gridPixels);
309         QPalette pal = palette();
310         pal.setBrush(backgroundRole(), QBrush(pm));
311         setAutoFillBackground(true);
312         setPalette(pal);
313 }
314
315
316 void DrawingView::AddNewObjectToDocument(Object * object)
317 {
318         if (object)
319         {
320                 object->Reparent(&document);
321                 document.Add(object);
322                 update();
323         }
324 //printf("DrawingView::AddNewObjectToDocument(). object=%08X\n", object);
325 }
326
327
328 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
329 {
330         // This is undoing the transform, e.g. going from client coords to local coords.
331         // In essence, the height - y is height + (y * -1), the (y * -1) term doing the
332         // conversion of the y-axis from increasing bottom to top.
333         return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
334 }
335
336
337 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
338 {
339         // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
340         // No voodoo here, it's just grouped wrong to see it. It should be:
341         // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive
342         return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
343 }
344
345
346 //
347 // This looks strange, but it's really quite simple: We want a point that's
348 // more than half-way to the next grid point to snap there while conversely we
349 // want a point that's less than half-way to to the next grid point then snap
350 // to the one before it. So we add half of the grid spacing to the point, then
351 // divide by it so that we can remove the fractional part, then multiply it
352 // back to get back to the correct answer.
353 //
354 Vector DrawingView::SnapPointToGrid(Vector point)
355 {
356         point += gridSpacing / 2.0;             // *This* adds to Z!!!
357         point /= gridSpacing;
358         point.x = floor(point.x);//need to fix this for negative numbers...
359         point.y = floor(point.y);
360         point.z = 0;                                    // Make *sure* Z doesn't go anywhere!!!
361         point *= gridSpacing;
362         return point;
363 }
364
365
366 void DrawingView::paintEvent(QPaintEvent * /*event*/)
367 {
368         QPainter qtPainter(this);
369         Painter painter(&qtPainter);
370
371         if (useAntialiasing)
372                 qtPainter.setRenderHint(QPainter::Antialiasing);
373
374 //      Painter::screenSize = Vector(size().width(), size().height());
375         Object::SetViewportHeight(size().height());
376
377         // Draw coordinate axes
378
379         painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
380         painter.DrawLine(0, -16384, 0, 16384);
381         painter.DrawLine(-16384, 0, 16384, 0);
382
383         // Draw supplemental (tool related) points
384 // NOTE that this can be done as an action!
385 // In that case, we would need access to the document...
386 // [We can do that by making the document a class object...]
387         if (rotateTool)
388         {
389                 painter.SetPen(QPen(QColor(0, 200, 0), 2.0, Qt::SolidLine));
390                 painter.DrawLine(rx - 10, ry, rx + 10, ry);
391                 painter.DrawLine(rx, ry - 10, rx, ry + 10);
392         }
393
394 // Maybe we can make the grid into a background brush instead, and let Qt deal
395 // with it??? YES!!
396
397         // The top level document takes care of rendering for us...
398         document.Draw(&painter);
399
400         if (toolAction)
401                 toolAction->Draw(&painter);
402
403         if (Object::selectionInProgress)
404         {
405 //              painter.SetPen(QPen(Qt::green, 1.0, Qt::SolidLine));
406                 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
407 //              painter.SetBrush(QBrush(Qt::NoBrush));
408                 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
409                 painter.DrawRect(Object::selection);
410         }
411 }
412
413
414 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
415 {
416         Painter::screenSize = Vector(size().width(), size().height());
417         UpdateGridBackground();
418 }
419
420
421 void DrawingView::mousePressEvent(QMouseEvent * event)
422 {
423         if (event->button() == Qt::LeftButton)
424         {
425                 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
426
427                 if (Object::snapToGrid)
428                         point = SnapPointToGrid(point);
429
430                 collided = document.Collided(point);
431
432                 if (collided)
433                         update();       // Do an update if collided with at least *one* object in the document
434
435                 if (toolAction)
436                         toolAction->MouseDown(point);
437
438                 // Didn't hit any object and not using a tool, so do a selection rectangle
439                 if (!(collided || toolAction))
440                 {
441                         Object::selectionInProgress = true;
442                         Object::selection.setTopLeft(QPointF(point.x, point.y));
443                         Object::selection.setBottomRight(QPointF(point.x, point.y));
444                 }
445         }
446         else if (event->button() == Qt::MiddleButton)
447         {
448                 scrollDrag = true;
449                 oldPoint = Vector(event->x(), event->y());
450                 // Should also change the mouse pointer as well...
451                 setCursor(Qt::SizeAllCursor);
452         }
453 }
454
455
456 void DrawingView::mouseMoveEvent(QMouseEvent * event)
457 {
458         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
459         Object::selection.setBottomRight(QPointF(point.x, point.y));
460
461         if (event->buttons() & Qt::MiddleButton)
462         {
463                 point = Vector(event->x(), event->y());
464                 // Since we're using Qt coords for scrolling, we have to adjust them here to
465                 // conform to Cartesian coords, since the origin is using Cartesian. :-)
466                 Vector delta(point, oldPoint);
467                 delta /= Painter::zoom;
468                 delta.y = -delta.y;
469                 Painter::origin -= delta;
470
471                 UpdateGridBackground();
472                 update();
473                 oldPoint = point;
474                 return;
475         }
476
477         // Grid processing...
478 #if 1
479         if ((event->buttons() & Qt::LeftButton) && Object::snapToGrid)
480         {
481 #if 0
482                 point += gridSpacing / 2.0;                                     // *This* adds to Z!!!
483                 point /= gridSpacing;
484 //200% is ok, gridSpacing = 6 in this case...
485 //won't run into problems until gridSpacing = 1.5 (zoom = 800%)
486 //run into problems with this approach: when zoom level is 200% this truncates to
487 //integers, which is *not* what's wanted here...
488                 point.x = floor(point.x);//need to fix this for negative numbers...
489                 point.y = floor(point.y);
490                 point.z = 0;                                                            // Make *sure* Z doesn't go anywhere!!!
491                 point *= gridSpacing;
492 #else
493                 point = SnapPointToGrid(point);
494 #endif
495         }
496 #endif
497 //we should keep track of the last point here and only pass this down *if* the point
498 //changed...
499         document.PointerMoved(point);
500
501         if (document.NeedsUpdate() || Object::selectionInProgress)
502                 update();
503
504         if (toolAction)
505         {
506                 toolAction->MouseMoved(point);
507                 update();
508         }
509 }
510
511
512 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
513 {
514         if (event->button() == Qt::LeftButton)
515         {
516                 document.PointerReleased();
517
518 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
519 //could set it up to use the document's update function (assumes that all object updates
520 //are being reported correctly:
521 //              if (document.NeedsUpdate())
522 //              if (collided)
523                         update();       // Do an update if collided with at least *one* object in the document
524
525                 if (toolAction)
526                         toolAction->MouseReleased();
527
528                 if (Object::selectionInProgress)
529                 {
530                         // Select all the stuff inside of selection
531                         Object::selectionInProgress = false;
532                 }
533         }
534         else if (event->button() == Qt::MiddleButton)
535         {
536                 scrollDrag = false;
537                 setCursor(Qt::ArrowCursor);
538         }
539 }
540