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