]> Shamusworld >> Repos - architektonas/blob - src/drawingview.cpp
8f689a80bd0e33cd2ef3000ba7bff14fea6d5c59
[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         // Transform the origin to Qt coordinates
225         Vector pixmapOrigin = Painter::CartesianToQtCoords(Vector());
226         int x = (int)pixmapOrigin.x;
227         int y = (int)pixmapOrigin.y;
228         // Use mod arithmetic to grab the correct swatch of background
229 /*
230 Negative numbers still screw it up... Need to think about what we're
231 trying to do here. The fact that it worked with 72 seems to have been pure luck.
232 It seems the problem is negative numbers: We can't let that happen.
233 When taking away the zero, it pops over 1 px at zero, then goes about 1/2 a
234 grid at x<0.
235
236 The bitmap looks like this:
237
238 +---+---+---+---+---
239 |   |   |   |   |
240 |   |   |   |   |
241 +---+---+---+---+---
242 |   |   |   |   |
243 |   |   |   |   |
244 |   |   |   |   |
245
246 @ x = 1, we want it to look like:
247
248 -+---+---+---+---+---
249  |   |   |   |   |
250  |   |   |   |   |
251 -+---+---+---+---+---
252  |   |   |   |   |
253  |   |   |   |   |
254  |   |   |   |   |
255
256 Which means we need to grab the sample from x = 3. @ x = -1:
257
258 ---+---+---+---+---
259    |   |   |   |
260    |   |   |   |
261 ---+---+---+---+---
262    |   |   |   |
263    |   |   |   |
264    |   |   |   |
265
266 Which means we need to grab the sample from x = 1. Which means we have to take
267 the mirror of the modulus of gridPixels.
268
269 Doing a mod of a negative number is problematic: 1st, the compiler converts the
270 negative number to an unsigned int, then it does the mod. Gets you wrong answers
271 most of the time, unless you use a power of 2. :-P So what we do here is just
272 take the modulus of the negation, which means we don't have to worry about
273 mirroring it later.
274
275 The positive case looks gruesome (and it is) but it boils down to this: We take
276 the modulus of the X coordinate, then mirror it by subtraction from the
277 maximum (in this case, gridPixels). This gives us a number in the range of 1 to
278 gridPixels. But we need the case where the result equalling gridPixels to be
279 zero; so we do another modulus operation on the result to achieve this.
280 */
281         if (x < 0)
282                 x = -x % gridPixels;
283         else
284                 x = (gridPixels - (x % gridPixels)) % gridPixels;
285
286         if (y < 0)
287                 y = -y % gridPixels;
288         else
289                 y = (gridPixels - (y % gridPixels)) % gridPixels;
290
291         // Here we grab a section of the bigger pixmap, so that the background
292         // *looks* like it's scrolling...
293         QPixmap pm = gridBackground.copy(x, y, gridPixels, gridPixels);
294         QPalette pal = palette();
295         pal.setBrush(backgroundRole(), QBrush(pm));
296         setAutoFillBackground(true);
297         setPalette(pal);
298 }
299
300
301 void DrawingView::AddNewObjectToDocument(Object * object)
302 {
303         if (object)
304         {
305                 object->Reparent(&document);
306                 document.Add(object);
307                 update();
308         }
309 //printf("DrawingView::AddNewObjectToDocument(). object=%08X\n", object);
310 }
311
312
313 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
314 {
315         // This is undoing the transform, e.g. going from client coords to local coords.
316         // In essence, the height - y is height + (y * -1), the (y * -1) term doing the
317         // conversion of the y-axis from increasing bottom to top.
318         return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
319 }
320
321
322 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
323 {
324         // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
325         // No voodoo here, it's just grouped wrong to see it. It should be:
326         // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive
327         return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
328 }
329
330
331 void DrawingView::paintEvent(QPaintEvent * /*event*/)
332 {
333         QPainter qtPainter(this);
334         Painter painter(&qtPainter);
335
336         if (useAntialiasing)
337                 qtPainter.setRenderHint(QPainter::Antialiasing);
338
339 //      Painter::screenSize = Vector(size().width(), size().height());
340         Object::SetViewportHeight(size().height());
341
342         // Draw coordinate axes
343
344         painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
345         painter.DrawLine(0, -16384, 0, 16384);
346         painter.DrawLine(-16384, 0, 16384, 0);
347
348         // Draw supplemental (tool related) points
349 // NOTE that this can be done as an action!
350 // In that case, we would need access to the document...
351 // [We can do that by making the document a class object...]
352         if (rotateTool)
353         {
354                 painter.SetPen(QPen(QColor(0, 200, 0), 2.0, Qt::SolidLine));
355                 painter.DrawLine(rx - 10, ry, rx + 10, ry);
356                 painter.DrawLine(rx, ry - 10, rx, ry + 10);
357         }
358
359 // Maybe we can make the grid into a background brush instead, and let Qt deal
360 // with it??? YES!!
361
362         // The top level document takes care of rendering for us...
363         document.Draw(&painter);
364
365         if (toolAction)
366                 toolAction->Draw(&painter);
367
368         if (Object::selectionInProgress)
369         {
370 //              painter.SetPen(QPen(Qt::green, 1.0, Qt::SolidLine));
371                 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
372 //              painter.SetBrush(QBrush(Qt::NoBrush));
373                 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
374                 painter.DrawRect(Object::selection);
375         }
376 }
377
378
379 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
380 {
381         Painter::screenSize = Vector(size().width(), size().height());
382         UpdateGridBackground();
383 }
384
385
386 void DrawingView::mousePressEvent(QMouseEvent * event)
387 {
388         if (event->button() == Qt::LeftButton)
389         {
390                 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
391                 collided = document.Collided(point);
392
393                 if (collided)
394                         update();       // Do an update if collided with at least *one* object in the document
395
396                 if (toolAction)
397                         toolAction->MouseDown(point);
398
399                 // Didn't hit any object and not using a tool, so do a selection rectangle
400                 if (!(collided || toolAction))
401                 {
402                         Object::selectionInProgress = true;
403                         Object::selection.setTopLeft(QPointF(point.x, point.y));
404                         Object::selection.setBottomRight(QPointF(point.x, point.y));
405                 }
406         }
407         else if (event->button() == Qt::MiddleButton)
408         {
409                 scrollDrag = true;
410                 oldPoint = Vector(event->x(), event->y());
411                 // Should also change the mouse pointer as well...
412                 setCursor(Qt::SizeAllCursor);
413         }
414 }
415
416
417 void DrawingView::mouseMoveEvent(QMouseEvent * event)
418 {
419         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
420         Object::selection.setBottomRight(QPointF(point.x, point.y));
421
422         if (event->buttons() & Qt::MiddleButton)
423         {
424                 point = Vector(event->x(), event->y());
425                 // Since we're using Qt coords for scrolling, we have to adjust them here to
426                 // conform to Cartesian coords, since the origin is using Cartesian. :-)
427                 Vector delta(point, oldPoint);
428                 delta /= Painter::zoom;
429                 delta.y = -delta.y;
430                 Painter::origin -= delta;
431
432                 UpdateGridBackground();
433                 update();
434                 oldPoint = point;
435                 return;
436         }
437
438         // Grid processing...
439 #if 1
440         // This looks strange, but it's really quite simple: We want a point that's
441         // more than half-way to the next grid point to snap there while conversely
442         // we want a point that's less than half-way to to the next grid point then
443         // snap to the one before it. So we add half of the grid spacing to the
444         // point, then divide by it so that we can remove the fractional part, then
445         // multiply it back to get back to the correct answer.
446         if (event->buttons() & Qt::LeftButton)
447         {
448                 point += gridSpacing / 2.0;                                     // *This* adds to Z!!!
449                 point /= gridSpacing;
450 //200% is ok, gridSpacing = 6 in this case...
451 //won't run into problems until gridSpacing = 1.5 (zoom = 800%)
452 //run into problems with this approach: when zoom level is 200% this truncates to
453 //integers, which is *not* what's wanted here...
454                 point.x = floor(point.x);//need to fix this for negative numbers...
455                 point.y = floor(point.y);
456                 point.z = 0;                                                            // Make *sure* Z doesn't go anywhere!!!
457                 point *= gridSpacing;
458         }
459 #endif
460 //we should keep track of the last point here and only pass this down *if* the point
461 //changed...
462         document.PointerMoved(point);
463
464         if (document.NeedsUpdate() || Object::selectionInProgress)
465                 update();
466
467         if (toolAction)
468         {
469                 toolAction->MouseMoved(point);
470                 update();
471         }
472 }
473
474
475 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
476 {
477         if (event->button() == Qt::LeftButton)
478         {
479                 document.PointerReleased();
480
481 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
482 //could set it up to use the document's update function (assumes that all object updates
483 //are being reported correctly:
484 //              if (document.NeedsUpdate())
485 //              if (collided)
486                         update();       // Do an update if collided with at least *one* object in the document
487
488                 if (toolAction)
489                         toolAction->MouseReleased();
490
491                 if (Object::selectionInProgress)
492                 {
493                         // Select all the stuff inside of selection
494                         Object::selectionInProgress = false;
495                 }
496         }
497         else if (event->button() == Qt::MiddleButton)
498         {
499                 scrollDrag = false;
500                 setCursor(Qt::ArrowCursor);
501         }
502 }
503