]> Shamusworld >> Repos - architektonas/blob - src/drawingview.cpp
First step towards resizable grid and sane zoom setting.
[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 "drawcircleaction.h"
38 #include "drawdimensionaction.h"
39 #include "drawlineaction.h"
40 #include "line.h"
41 #include "painter.h"
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(512, 512),
48         scale(1.0), offsetX(-10), offsetY(-10),
49         document(Vector(0, 0)),
50 //      gridSpacing(32.0), collided(false), rotateTool(false), rx(150.0), ry(150.0),
51         gridSpacing(12.0), collided(false), rotateTool(false), rx(150.0), ry(150.0),
52         scrollDrag(false), addLineTool(false), addCircleTool(false),
53         addDimensionTool(false),
54 //      selectionInProgress(false),
55         toolAction(NULL)
56 {
57         document.isTopLevelContainer = true;
58         setBackgroundRole(QPalette::Base);
59         setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
60
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
116 */
117         QPainter pmp(&gridBackground);
118         pmp.fillRect(0, 0, 512, 512, QColor(240, 240, 240));
119         pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
120
121         for(int i=0; i<511; i+=12)
122         {
123                 pmp.drawLine(i, 0, i, 511);
124                 pmp.drawLine(0, i, 511, i);
125         }
126
127         pmp.end();
128         UpdateGridBackground();
129 }
130
131
132 void DrawingView::SetRotateToolActive(bool state/*= true*/)
133 {
134         rotateTool = state;
135         update();
136 }
137
138
139 void DrawingView::SetAddLineToolActive(bool state/*= true*/)
140 {
141         if (state)
142         {
143                 toolAction = new DrawLineAction();
144                 connect(toolAction, SIGNAL(ObjectReady(Object *)), this,
145                         SLOT(AddNewObjectToDocument(Object *)));
146         }
147
148         update();
149 //printf("DrawingView::SetAddLineToolActive(). toolAction=%08X\n", toolAction);
150 }
151
152
153 void DrawingView::SetAddCircleToolActive(bool state/*= true*/)
154 {
155         if (state)
156         {
157                 toolAction = new DrawCircleAction();
158                 connect(toolAction, SIGNAL(ObjectReady(Object *)), this,
159                         SLOT(AddNewObjectToDocument(Object *)));
160         }
161
162         update();
163 }
164
165
166 void DrawingView::SetAddDimensionToolActive(bool state/*= true*/)
167 {
168         if (state)
169         {
170                 toolAction = new DrawDimensionAction();
171                 connect(toolAction, SIGNAL(ObjectReady(Object *)), this,
172                         SLOT(AddNewObjectToDocument(Object *)));
173         }
174
175         update();
176 }
177
178
179 void DrawingView::UpdateGridBackground(void)
180 {
181 //was: 128
182 #define BG_BRUSH_SPAN 72
183         // Transform the origin to Qt coordinates
184         Vector pixmapOrigin = Painter::CartesianToQtCoords(Vector());
185         int x = (int)pixmapOrigin.x;
186         int y = (int)pixmapOrigin.y;
187         // Use mod arithmetic to grab the correct swatch of background
188         // Problem with mod 128: Negative numbers screw it up... [FIXED]
189         x = (x < 0 ? 0 : BG_BRUSH_SPAN - 1) - (x % BG_BRUSH_SPAN);
190         y = (y < 0 ? 0 : BG_BRUSH_SPAN - 1) - (y % BG_BRUSH_SPAN);
191
192         // Here we grab a section of the bigger pixmap, so that the background
193         // *looks* like it's scrolling...
194         QPixmap pm = gridBackground.copy(x, y, BG_BRUSH_SPAN, BG_BRUSH_SPAN);
195         QPalette pal = palette();
196         pal.setBrush(backgroundRole(), QBrush(pm));
197         setAutoFillBackground(true);
198         setPalette(pal);
199 }
200
201
202 void DrawingView::AddNewObjectToDocument(Object * object)
203 {
204         if (object)
205         {
206                 object->Reparent(&document);
207                 document.Add(object);
208                 update();
209         }
210 //printf("DrawingView::AddNewObjectToDocument(). object=%08X\n", object);
211 }
212
213
214 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
215 {
216         // This is undoing the transform, e.g. going from client coords to local coords.
217         // In essence, the height - y is height + (y * -1), the (y * -1) term doing the
218         // conversion of the y-axis from increasing bottom to top.
219         return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
220 }
221
222
223 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
224 {
225         // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
226         // No voodoo here, it's just grouped wrong to see it. It should be:
227         // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive
228         return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
229 }
230
231
232 void DrawingView::paintEvent(QPaintEvent * /*event*/)
233 {
234         QPainter qtPainter(this);
235         Painter painter(&qtPainter);
236
237         if (useAntialiasing)
238                 qtPainter.setRenderHint(QPainter::Antialiasing);
239
240         Painter::screenSize = Vector(size().width(), size().height());
241         Object::SetViewportHeight(size().height());
242
243         // Draw coordinate axes
244
245         painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
246         painter.DrawLine(0, -16384, 0, 16384);
247         painter.DrawLine(-16384, 0, 16384, 0);
248
249         // Draw supplemental (tool related) points
250 // NOTE that this can be done as an action!
251 // In that case, we would need access to the document...
252 // [We can do that by making the document a class object...]
253         if (rotateTool)
254         {
255                 painter.SetPen(QPen(QColor(0, 200, 0), 2.0, Qt::SolidLine));
256                 painter.DrawLine(rx - 10, ry, rx + 10, ry);
257                 painter.DrawLine(rx, ry - 10, rx, ry + 10);
258         }
259
260 // Maybe we can make the grid into a background brush instead, and let Qt deal
261 // with it??? YES!!
262
263         // The top level document takes care of rendering for us...
264         document.Draw(&painter);
265
266         if (toolAction)
267                 toolAction->Draw(&painter);
268
269         if (Object::selectionInProgress)
270         {
271 //              painter.SetPen(QPen(Qt::green, 1.0, Qt::SolidLine));
272                 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
273 //              painter.SetBrush(QBrush(Qt::NoBrush));
274                 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
275                 painter.DrawRect(Object::selection);
276         }
277 }
278
279
280 void DrawingView::mousePressEvent(QMouseEvent * event)
281 {
282         if (event->button() == Qt::LeftButton)
283         {
284                 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
285                 collided = document.Collided(point);
286
287                 if (collided)
288                         update();       // Do an update if collided with at least *one* object in the document
289
290                 if (toolAction)
291                         toolAction->MouseDown(point);
292
293                 // Didn't hit any object and not using a tool, so do a selection rectangle
294                 if (!(collided || toolAction))
295                 {
296                         Object::selectionInProgress = true;
297                         Object::selection.setTopLeft(QPointF(point.x, point.y));
298                         Object::selection.setBottomRight(QPointF(point.x, point.y));
299                 }
300         }
301         else if (event->button() == Qt::MiddleButton)
302         {
303                 scrollDrag = true;
304                 oldPoint = Vector(event->x(), event->y());
305                 // Should also change the mouse pointer as well...
306                 setCursor(Qt::SizeAllCursor);
307         }
308 }
309
310
311 void DrawingView::mouseMoveEvent(QMouseEvent * event)
312 {
313         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
314         Object::selection.setBottomRight(QPointF(point.x, point.y));
315
316         if (event->buttons() & Qt::MiddleButton)
317         {
318                 point = Vector(event->x(), event->y());
319                 // Since we're using Qt coords for scrolling, we have to adjust them here to
320                 // conform to Cartesian coords, since the origin is using Cartesian. :-)
321                 Vector delta(point, oldPoint);
322                 delta /= Painter::zoom;
323                 delta.y = -delta.y;
324                 Painter::origin -= delta;
325
326                 UpdateGridBackground();
327                 update();
328                 oldPoint = point;
329                 return;
330         }
331
332         // Grid processing...
333 #if 1
334         // This looks strange, but it's really quite simple: We want a point that's
335         // more than half-way to the next grid point to snap there while conversely
336         // we want a point that's less than half-way to to the next grid point then
337         // snap to the one before it. So we add half of the grid spacing to the
338         // point, then divide by it so that we can remove the fractional part, then
339         // multiply it back to get back to the correct answer.
340         if (event->buttons() & Qt::LeftButton)
341         {
342                 point += gridSpacing / 2.0;                                     // *This* adds to Z!!!
343                 point /= gridSpacing;
344 //200% is ok, gridSpacing = 6 in this case...
345 //won't run into problems until gridSpacing = 1.5 (zoom = 800%)
346 //run into problems with this approach: when zoom level is 200% this truncates to
347 //integers, which is *not* what's wanted here...
348                 point.x = floor(point.x);//need to fix this for negative numbers...
349                 point.y = floor(point.y);
350                 point.z = 0;                                                            // Make *sure* Z doesn't go anywhere!!!
351                 point *= gridSpacing;
352         }
353 #endif
354 //we should keep track of the last point here and only pass this down *if* the point
355 //changed...
356         document.PointerMoved(point);
357
358         if (document.NeedsUpdate() || Object::selectionInProgress)
359                 update();
360
361         if (toolAction)
362         {
363                 toolAction->MouseMoved(point);
364                 update();
365         }
366 }
367
368
369 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
370 {
371         if (event->button() == Qt::LeftButton)
372         {
373                 document.PointerReleased();
374
375 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
376 //could set it up to use the document's update function (assumes that all object updates
377 //are being reported correctly:
378 //              if (document.NeedsUpdate())
379 //              if (collided)
380                         update();       // Do an update if collided with at least *one* object in the document
381
382                 if (toolAction)
383                         toolAction->MouseReleased();
384
385                 if (Object::selectionInProgress)
386                 {
387                         // Select all the stuff inside of selection
388                         Object::selectionInProgress = false;
389                 }
390         }
391         else if (event->button() == Qt::MiddleButton)
392         {
393                 scrollDrag = false;
394                 setCursor(Qt::ArrowCursor);
395         }
396 }
397