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