]> Shamusworld >> Repos - architektonas/blob - src/drawingview.cpp
9341a76279044ef78c64e3d21a39f95d85cda41c
[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),
53         document(Vector(0, 0)),
54         /*gridSpacing(12.0),*/ gridPixels(0), collided(false), //rotateTool(false),
55 //      rx(150.0), ry(150.0),
56 //      scrollDrag(false), addLineTool(false), addCircleTool(false),
57 //      addDimensionTool(false),
58         toolAction(NULL)
59 {
60         document.isTopLevelContainer = true;
61         setBackgroundRole(QPalette::Base);
62         setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
63
64         Object::gridSpacing = 12.0;
65 //      toolPalette = new ToolWindow();
66 //      CreateCursors();
67 //      setCursor(cur[TOOLSelect]);
68 //      setMouseTracking(true);
69
70         Line * line = new Line(Vector(5, 5), Vector(50, 40), &document);
71         document.Add(line);
72         document.Add(new Line(Vector(50, 40), Vector(10, 83), &document));
73         document.Add(new Line(Vector(10, 83), Vector(17, 2), &document));
74         document.Add(new Circle(Vector(100, 100), 36, &document));
75         document.Add(new Circle(Vector(50, 150), 49, &document));
76         document.Add(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3, &document)),
77         document.Add(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5, &document));
78 #if 1
79         Dimension * dimension = new Dimension(Vector(0, 0), Vector(0, 0), DTLinear, &document);
80         line->SetDimensionOnLine(dimension);
81         document.Add(dimension);
82 #else
83         // Alternate way to do the above...
84         line->SetDimensionOnLine();
85 #endif
86
87 /*
88 Here we set the grid size in pixels--12 in this case. Initially, we have our
89 zoom set to make this represent 12 inches at a zoom factor of 25%. (This is
90 arbitrary.) So, to be able to decouple the grid size from the zoom, we need
91 to be able to set the size of the background grid (which we do here at an
92 arbitrary 12 pixels) to anything we want (within reason, of course :-).
93
94 The drawing enforces the grid spacing through the drawing->gridSpacing variable.
95
96         drawing->gridSpacing = 12.0 / Painter::zoom;
97
98 Painter::zoom is the zoom factor for the drawing, and all mouse clicks are
99 translated to Cartesian coordinates through this. (Initially, Painter::zoom is
100 set to 1.0. SCREEN_ZOOM is set to 1.0/4.0.)
101
102 Really, the 100% zoom level can be set at *any* zoom level, it's more of a
103 convenience function than any measure of absolutes. Doing things that way we
104 could rid ourselves of the whole SCREEN_ZOOM parameter and all the attendant
105 shittyness that comes with it.
106
107 However, it seems that SCREEN_ZOOM is used to make text and arrow sizes show up
108 a certain way, which means we should probably create something else in those
109 objects to take its place--like some kind of scale factor. This would seem to
110 imply that certain point sizes actually *do* tie things like fonts to absolute
111 sizes on the screen, but not necessarily because you could have an inch scale
112 with text that is quite small relative to other objects on the screen, which
113 currently you have to zoom in to see (and which blows up the text). Point sizes
114 in an application like this are a bit meaningless; even though an inch is an
115 inch regardless of the zoom level a piece of text can be larger or smaller than
116 this. Maybe this is the case for having a base unit and basing point sizes off
117 of that.
118
119 Here's what's been figured out. Painter::zoom is simply the ratio of pixels to
120 base units. What that means is that if you have a 12px grid with a 6" grid size
121 (& base unit of "inches"), Painter::zoom becomes 12px / 6" = 2.0 px/in.
122
123 Dimensions now have a "size" parameter to set their absolute size in relation
124 to the base unit. ATM, the arrows are drawn in pixels, but also scaled by
125 Painter::zoom *and* size. Same with the dimension text; it's drawn at 10pt and
126 scaled the same way as the arrowheads.
127
128 Need a way to scale line widths as well. :-/ Shouldn't be too difficult, just
129 need a thickness parameter similar to the "size" param for dimensions. (And now
130 we do! :-)
131
132 */
133         SetGridSize(12);
134 }
135
136
137 void DrawingView::SetToolActive(Action * action)
138 {
139         if (action != NULL)
140         {
141                 toolAction = action;
142                 connect(toolAction, SIGNAL(ObjectReady(Object *)), this,
143                         SLOT(AddNewObjectToDocument(Object *)));
144                 connect(toolAction, SIGNAL(NeedRefresh()), this, SLOT(HandleActionUpdate()));
145         }
146 }
147
148
149 void DrawingView::SetGridSize(uint32_t size)
150 {
151         // Sanity check
152         if (size == gridPixels)
153                 return;
154
155         // Recreate the background bitmap
156         gridPixels = size;
157         QPainter pmp(&gridBackground);
158         pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
159         pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
160
161         for(int i=0; i<(BACKGROUND_MAX_SIZE-1); i+=gridPixels)
162         {
163                 pmp.drawLine(i, 0, i, BACKGROUND_MAX_SIZE - 1);
164                 pmp.drawLine(0, i, BACKGROUND_MAX_SIZE - 1, i);
165         }
166
167         pmp.end();
168
169         // Set up new BG brush & zoom level (pixels per base unit)
170 //      Painter::zoom = gridPixels / gridSpacing;
171         Painter::zoom = gridPixels / Object::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 void DrawingView::HandleActionUpdate(void)
268 {
269         update();
270 }
271
272
273 void DrawingView::SetCurrentLayer(int layer)
274 {
275         Object::currentLayer = layer;
276 //printf("DrawingView::CurrentLayer = %i\n", layer);
277 }
278
279
280 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
281 {
282         // This is undoing the transform, e.g. going from client coords to local coords.
283         // In essence, the height - y is height + (y * -1), the (y * -1) term doing the
284         // conversion of the y-axis from increasing bottom to top.
285         return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
286 }
287
288
289 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
290 {
291         // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
292         // No voodoo here, it's just grouped wrong to see it. It should be:
293         // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive
294         return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
295 }
296
297
298 void DrawingView::paintEvent(QPaintEvent * /*event*/)
299 {
300         QPainter qtPainter(this);
301         Painter painter(&qtPainter);
302
303         if (useAntialiasing)
304                 qtPainter.setRenderHint(QPainter::Antialiasing);
305
306         Object::SetViewportHeight(size().height());
307
308         // Draw coordinate axes
309         painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
310         painter.DrawLine(0, -16384, 0, 16384);
311         painter.DrawLine(-16384, 0, 16384, 0);
312
313         // The top level document takes care of rendering for us...
314         document.Draw(&painter);
315
316         if (toolAction)
317         {
318                 painter.SetPen(QPen(QColor(200, 100, 0, 255), 1.0, Qt::DashLine));
319                 painter.DrawCrosshair(oldPoint);
320                 toolAction->Draw(&painter);
321         }
322
323         if (Object::selectionInProgress)
324         {
325                 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
326                 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
327                 painter.DrawRect(Object::selection);
328         }
329 }
330
331
332 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
333 {
334         Painter::screenSize = Vector(size().width(), size().height());
335         UpdateGridBackground();
336 }
337
338
339 void DrawingView::mousePressEvent(QMouseEvent * event)
340 {
341         if (event->button() == Qt::LeftButton)
342         {
343                 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
344
345 // Problem with this: Can't select stuff very well with the snap grid on.
346 // Completely screws things up, as sometimes things don't fall on the grid.
347 /*
348 So, how to fix this? Have the Object check itself?
349 Maybe we can fix this by having the initial point not be snapped, but when there's
350 a drag, we substitute the snapped point 'oldPoint' which the Object keeps track of
351 internally to know how far it was dragged...
352
353 Now we do... :-/
354 */
355 #if 0
356                 if (Object::snapToGrid)
357                         point = Object::SnapPointToGrid(point);
358 #endif
359
360                 collided = document.Collided(point);
361
362                 if (collided)
363                         update();       // Do an update if collided with at least *one* object in the document
364
365                 if (toolAction)
366                 {
367                         if (Object::snapToGrid)
368                                 point = Object::SnapPointToGrid(point);
369
370                         toolAction->MouseDown(point);
371                 }
372
373                 // Didn't hit any object and not using a tool, so do a selection rectangle
374                 if (!(collided || toolAction))
375                 {
376                         Object::selectionInProgress = true;
377                         Object::selection.setTopLeft(QPointF(point.x, point.y));
378                         Object::selection.setBottomRight(QPointF(point.x, point.y));
379                 }
380         }
381         else if (event->button() == Qt::MiddleButton)
382         {
383                 scrollDrag = true;
384                 oldPoint = Vector(event->x(), event->y());
385                 // Should also change the mouse pointer as well...
386                 setCursor(Qt::SizeAllCursor);
387         }
388 }
389
390
391 void DrawingView::mouseMoveEvent(QMouseEvent * event)
392 {
393         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
394         Object::selection.setBottomRight(QPointF(point.x, point.y));
395
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(point, oldPoint);
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         // Grid processing...
413         if ((event->buttons() & Qt::LeftButton) && Object::snapToGrid)
414         {
415                 point = Object::SnapPointToGrid(point);
416         }
417
418         oldPoint = point;
419 //we should keep track of the last point here and only pass this down *if* the point
420 //changed...
421         document.PointerMoved(point);
422
423         if (document.NeedsUpdate() || Object::selectionInProgress)
424                 update();
425
426         if (toolAction)
427         {
428                 if (Object::snapToGrid)
429                 {
430                         point = Object::SnapPointToGrid(point);
431                         oldPoint = point;
432                 }
433
434                 toolAction->MouseMoved(point);
435                 update();
436         }
437 }
438
439
440 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
441 {
442         if (event->button() == Qt::LeftButton)
443         {
444                 document.PointerReleased();
445
446 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
447 //could set it up to use the document's update function (assumes that all object updates
448 //are being reported correctly:
449 //              if (document.NeedsUpdate())
450 //              if (collided)
451                         update();       // Do an update if collided with at least *one* object in the document
452
453                 if (toolAction)
454                         toolAction->MouseReleased();
455
456                 if (Object::selectionInProgress)
457                 {
458                         // Select all the stuff inside of selection
459                         Object::selectionInProgress = false;
460                 }
461         }
462         else if (event->button() == Qt::MiddleButton)
463         {
464                 scrollDrag = false;
465                 setCursor(Qt::ArrowCursor);
466         }
467 }
468
469
470 void DrawingView::wheelEvent(QWheelEvent * event)
471 {
472         double zoomFactor = 1.25;
473         QSize sizeWin = /*drawing->*/size();
474         Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
475         center = Painter::QtToCartesianCoords(center);
476
477         if (event->delta() > 0)
478         {
479                 Vector newOrigin = center - ((center - Painter::origin) / zoomFactor);
480                 Painter::origin = newOrigin;
481                 Painter::zoom *= zoomFactor;
482         }
483         else
484         {
485                 Vector newOrigin = center + ((-center + Painter::origin) * zoomFactor);
486                 Painter::origin = newOrigin;
487                 Painter::zoom /= zoomFactor;
488         }
489
490 //      Object::gridSpacing = /*drawing->*/gridPixels / Painter::zoom;
491         SetGridSize(Object::gridSpacing * Painter::zoom);
492 //      /*drawing->*/UpdateGridBackground();
493         /*drawing->*/update();
494 //      zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Object::gridSpacing));
495 }
496
497
498 void DrawingView::keyPressEvent(QKeyEvent * event)
499 {
500         if (toolAction)
501                 toolAction->KeyDown(event->key());
502 }
503
504
505 void DrawingView::keyReleaseEvent(QKeyEvent * event)
506 {
507         if (toolAction)
508                 toolAction->KeyReleased(event->key());
509 }
510