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