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