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