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