]> Shamusworld >> Repos - architektonas/blob - src/drawingview.cpp
Beginning to make the Layer widget functional.
[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 //
23
24 // Uncomment this for debugging...
25 //#define DEBUG
26 //#define DEBUGFOO                              // Various tool debugging...
27 //#define DEBUGTP                               // Toolpalette debugging...
28
29 #include "drawingview.h"
30
31 #include <stdint.h>
32 #include "mathconstants.h"
33
34 #include "arc.h"
35 #include "circle.h"
36 #include "dimension.h"
37 #include "line.h"
38 #include "painter.h"
39
40
41 #define BACKGROUND_MAX_SIZE     512
42
43
44 DrawingView::DrawingView(QWidget * parent/*= NULL*/): QWidget(parent),
45         // The value in the settings file will override this.
46         useAntialiasing(true),
47         gridBackground(BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE),
48         scale(1.0), offsetX(-10), offsetY(-10),
49         document(Vector(0, 0)),
50         /*gridSpacing(12.0),*/ gridPixels(0), collided(false), rotateTool(false),
51         rx(150.0), ry(150.0),
52 //      scrollDrag(false), addLineTool(false), addCircleTool(false),
53 //      addDimensionTool(false),
54         toolAction(NULL)
55 {
56         document.isTopLevelContainer = true;
57         setBackgroundRole(QPalette::Base);
58         setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
59
60         Object::gridSpacing = 12.0;
61 //      toolPalette = new ToolWindow();
62 //      CreateCursors();
63 //      setCursor(cur[TOOLSelect]);
64 //      setMouseTracking(true);
65
66         Line * line = new Line(Vector(5, 5), Vector(50, 40), &document);
67         document.Add(line);
68         document.Add(new Line(Vector(50, 40), Vector(10, 83), &document));
69         document.Add(new Line(Vector(10, 83), Vector(17, 2), &document));
70         document.Add(new Circle(Vector(100, 100), 36, &document));
71         document.Add(new Circle(Vector(50, 150), 49, &document));
72         document.Add(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3, &document)),
73         document.Add(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5, &document));
74 #if 1
75         Dimension * dimension = new Dimension(Vector(0, 0), Vector(0, 0), DTLinear, &document);
76         line->SetDimensionOnLine(dimension);
77         document.Add(dimension);
78 #else
79         // Alternate way to do the above...
80         line->SetDimensionOnLine();
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 shittyness 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);
130 }
131
132
133 void DrawingView::SetRotateToolActive(bool state/*= true*/)
134 {
135         rotateTool = state;
136         update();
137 }
138
139
140 void DrawingView::SetToolActive(Action * action)
141 {
142         if (action != NULL)
143         {
144                 toolAction = action;
145                 connect(toolAction, SIGNAL(ObjectReady(Object *)), this,
146                         SLOT(AddNewObjectToDocument(Object *)));
147         }
148 }
149
150
151 void DrawingView::SetGridSize(uint32_t size)
152 {
153         // Sanity check
154         if (size == gridPixels)
155                 return;
156
157         // Recreate the background bitmap
158         gridPixels = size;
159         QPainter pmp(&gridBackground);
160         pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
161         pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
162
163         for(int i=0; i<(BACKGROUND_MAX_SIZE-1); i+=gridPixels)
164         {
165                 pmp.drawLine(i, 0, i, BACKGROUND_MAX_SIZE - 1);
166                 pmp.drawLine(0, i, BACKGROUND_MAX_SIZE - 1, i);
167         }
168
169         pmp.end();
170
171         // Set up new BG brush & zoom level (pixels per base unit)
172 //      Painter::zoom = gridPixels / gridSpacing;
173         Painter::zoom = gridPixels / Object::gridSpacing;
174         UpdateGridBackground();
175 }
176
177
178 void DrawingView::UpdateGridBackground(void)
179 {
180         // Transform the origin to Qt coordinates
181         Vector pixmapOrigin = Painter::CartesianToQtCoords(Vector());
182         int x = (int)pixmapOrigin.x;
183         int y = (int)pixmapOrigin.y;
184         // Use mod arithmetic to grab the correct swatch of background
185 /*
186 Negative numbers still screw it up... Need to think about what we're
187 trying to do here. The fact that it worked with 72 seems to have been pure luck.
188 It seems the problem is negative numbers: We can't let that happen.
189 When taking away the zero, it pops over 1 px at zero, then goes about 1/2 a
190 grid at x<0.
191
192 The bitmap looks like this:
193
194 +---+---+---+---+---
195 |   |   |   |   |
196 |   |   |   |   |
197 +---+---+---+---+---
198 |   |   |   |   |
199 |   |   |   |   |
200 |   |   |   |   |
201
202 @ x = 1, we want it to look like:
203
204 -+---+---+---+---+---
205  |   |   |   |   |
206  |   |   |   |   |
207 -+---+---+---+---+---
208  |   |   |   |   |
209  |   |   |   |   |
210  |   |   |   |   |
211
212 Which means we need to grab the sample from x = 3. @ x = -1:
213
214 ---+---+---+---+---
215    |   |   |   |
216    |   |   |   |
217 ---+---+---+---+---
218    |   |   |   |
219    |   |   |   |
220    |   |   |   |
221
222 Which means we need to grab the sample from x = 1. Which means we have to take
223 the mirror of the modulus of gridPixels.
224
225 Doing a mod of a negative number is problematic: 1st, the compiler converts the
226 negative number to an unsigned int, then it does the mod. Gets you wrong answers
227 most of the time, unless you use a power of 2. :-P So what we do here is just
228 take the modulus of the negation, which means we don't have to worry about
229 mirroring it later.
230
231 The positive case looks gruesome (and it is) but it boils down to this: We take
232 the modulus of the X coordinate, then mirror it by subtraction from the
233 maximum (in this case, gridPixels). This gives us a number in the range of 1 to
234 gridPixels. But we need the case where the result equalling gridPixels to be
235 zero; so we do another modulus operation on the result to achieve this.
236 */
237         if (x < 0)
238                 x = -x % gridPixels;
239         else
240                 x = (gridPixels - (x % gridPixels)) % gridPixels;
241
242         if (y < 0)
243                 y = -y % gridPixels;
244         else
245                 y = (gridPixels - (y % gridPixels)) % gridPixels;
246
247         // Here we grab a section of the bigger pixmap, so that the background
248         // *looks* like it's scrolling...
249         QPixmap pm = gridBackground.copy(x, y, gridPixels, gridPixels);
250         QPalette pal = palette();
251         pal.setBrush(backgroundRole(), QBrush(pm));
252         setAutoFillBackground(true);
253         setPalette(pal);
254 }
255
256
257 void DrawingView::AddNewObjectToDocument(Object * object)
258 {
259         if (object)
260         {
261                 object->Reparent(&document);
262                 document.Add(object);
263                 update();
264         }
265 //printf("DrawingView::AddNewObjectToDocument(). object=%08X\n", object);
266 }
267
268
269 void DrawingView::SetCurrentLayer(int layer)
270 {
271         Object::currentLayer = layer;
272 //printf("DrawingView::CurrentLayer = %i\n", layer);
273 }
274
275
276 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
277 {
278         // This is undoing the transform, e.g. going from client coords to local coords.
279         // In essence, the height - y is height + (y * -1), the (y * -1) term doing the
280         // conversion of the y-axis from increasing bottom to top.
281         return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
282 }
283
284
285 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
286 {
287         // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
288         // No voodoo here, it's just grouped wrong to see it. It should be:
289         // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive
290         return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
291 }
292
293
294 #if 0
295 //
296 // This looks strange, but it's really quite simple: We want a point that's
297 // more than half-way to the next grid point to snap there while conversely we
298 // want a point that's less than half-way to to the next grid point then snap
299 // to the one before it. So we add half of the grid spacing to the point, then
300 // divide by it so that we can remove the fractional part, then multiply it
301 // back to get back to the correct answer.
302 //
303 Vector DrawingView::SnapPointToGrid(Vector point)
304 {
305         point += gridSpacing / 2.0;             // *This* adds to Z!!!
306         point /= gridSpacing;
307         point.x = floor(point.x);//need to fix this for negative numbers...
308         point.y = floor(point.y);
309         point.z = 0;                                    // Make *sure* Z doesn't go anywhere!!!
310         point *= gridSpacing;
311         return point;
312 }
313 #endif
314
315
316 void DrawingView::paintEvent(QPaintEvent * /*event*/)
317 {
318         QPainter qtPainter(this);
319         Painter painter(&qtPainter);
320
321         if (useAntialiasing)
322                 qtPainter.setRenderHint(QPainter::Antialiasing);
323
324 //      Painter::screenSize = Vector(size().width(), size().height());
325         Object::SetViewportHeight(size().height());
326
327         // Draw coordinate axes
328
329         painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
330         painter.DrawLine(0, -16384, 0, 16384);
331         painter.DrawLine(-16384, 0, 16384, 0);
332
333         // Draw supplemental (tool related) points
334 // NOTE that this can be done as an action!
335 // In that case, we would need access to the document...
336 // [We can do that by making the document a class object...]
337         if (rotateTool)
338         {
339                 painter.SetPen(QPen(QColor(0, 200, 0), 2.0, Qt::SolidLine));
340                 painter.DrawLine(rx - 10, ry, rx + 10, ry);
341                 painter.DrawLine(rx, ry - 10, rx, ry + 10);
342         }
343
344 // Maybe we can make the grid into a background brush instead, and let Qt deal
345 // with it??? YES!!
346
347         // The top level document takes care of rendering for us...
348         document.Draw(&painter);
349
350         if (toolAction)
351         {
352 //              painter.SetPen(QPen(Qt::green, 1.0, Qt::DashLine));
353                 painter.SetPen(QPen(QColor(200, 100, 0, 255), 1.0, Qt::DashLine));
354                 painter.DrawCrosshair(oldPoint);
355                 toolAction->Draw(&painter);
356         }
357
358         if (Object::selectionInProgress)
359         {
360 //              painter.SetPen(QPen(Qt::green, 1.0, Qt::SolidLine));
361                 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
362 //              painter.SetBrush(QBrush(Qt::NoBrush));
363                 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
364                 painter.DrawRect(Object::selection);
365         }
366 }
367
368
369 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
370 {
371         Painter::screenSize = Vector(size().width(), size().height());
372         UpdateGridBackground();
373 }
374
375
376 void DrawingView::mousePressEvent(QMouseEvent * event)
377 {
378         if (event->button() == Qt::LeftButton)
379         {
380                 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
381
382 // Problem with this: Can't select stuff very well with the snap grid on.
383 // Completely screws things up, as sometimes things don't fall on the grid.
384 /*
385 So, how to fix this? Have the Object check itself?
386 Maybe we can fix this by having the initial point not be snapped, but when there's
387 a drag, we substitute the snapped point 'oldPoint' which the Object keeps track of
388 internally to know how far it was dragged...
389
390 Now we do... :-/
391 */
392 #if 0
393                 if (Object::snapToGrid)
394                         point = Object::SnapPointToGrid(point);
395 #endif
396
397                 collided = document.Collided(point);
398
399                 if (collided)
400                         update();       // Do an update if collided with at least *one* object in the document
401
402                 if (toolAction)
403                 {
404                         if (Object::snapToGrid)
405                                 point = Object::SnapPointToGrid(point);
406
407                         toolAction->MouseDown(point);
408                 }
409
410                 // Didn't hit any object and not using a tool, so do a selection rectangle
411                 if (!(collided || toolAction))
412                 {
413                         Object::selectionInProgress = true;
414                         Object::selection.setTopLeft(QPointF(point.x, point.y));
415                         Object::selection.setBottomRight(QPointF(point.x, point.y));
416                 }
417         }
418         else if (event->button() == Qt::MiddleButton)
419         {
420                 scrollDrag = true;
421                 oldPoint = Vector(event->x(), event->y());
422                 // Should also change the mouse pointer as well...
423                 setCursor(Qt::SizeAllCursor);
424         }
425 }
426
427
428 void DrawingView::mouseMoveEvent(QMouseEvent * event)
429 {
430         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
431         Object::selection.setBottomRight(QPointF(point.x, point.y));
432
433         if (event->buttons() & Qt::MiddleButton)
434         {
435                 point = Vector(event->x(), event->y());
436                 // Since we're using Qt coords for scrolling, we have to adjust them here to
437                 // conform to Cartesian coords, since the origin is using Cartesian. :-)
438                 Vector delta(point, oldPoint);
439                 delta /= Painter::zoom;
440                 delta.y = -delta.y;
441                 Painter::origin -= delta;
442
443                 UpdateGridBackground();
444                 update();
445                 oldPoint = point;
446                 return;
447         }
448
449         // Grid processing...
450         if ((event->buttons() & Qt::LeftButton) && Object::snapToGrid)
451         {
452                 point = Object::SnapPointToGrid(point);
453         }
454
455         oldPoint = point;
456 //we should keep track of the last point here and only pass this down *if* the point
457 //changed...
458         document.PointerMoved(point);
459
460         if (document.NeedsUpdate() || Object::selectionInProgress)
461                 update();
462
463         if (toolAction)
464         {
465                 if (Object::snapToGrid)
466                 {
467                         point = Object::SnapPointToGrid(point);
468                         oldPoint = point;
469                 }
470
471                 toolAction->MouseMoved(point);
472                 update();
473         }
474 }
475
476
477 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
478 {
479         if (event->button() == Qt::LeftButton)
480         {
481                 document.PointerReleased();
482
483 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
484 //could set it up to use the document's update function (assumes that all object updates
485 //are being reported correctly:
486 //              if (document.NeedsUpdate())
487 //              if (collided)
488                         update();       // Do an update if collided with at least *one* object in the document
489
490                 if (toolAction)
491                         toolAction->MouseReleased();
492
493                 if (Object::selectionInProgress)
494                 {
495                         // Select all the stuff inside of selection
496                         Object::selectionInProgress = false;
497                 }
498         }
499         else if (event->button() == Qt::MiddleButton)
500         {
501                 scrollDrag = false;
502                 setCursor(Qt::ArrowCursor);
503         }
504 }
505
506
507 void DrawingView::keyPressEvent(QKeyEvent * event)
508 {
509         if (toolAction)
510         {
511                 bool needUpdate = toolAction->KeyDown(event->key());
512
513                 if (needUpdate)
514                         update();
515         }
516 }
517
518
519 void DrawingView::keyReleaseEvent(QKeyEvent * event)
520 {
521         if (toolAction)
522         {
523                 bool needUpdate = toolAction->KeyReleased(event->key());
524
525                 if (needUpdate)
526                         update();
527         }
528 }
529