]> Shamusworld >> Repos - architektonas/blob - src/drawingview.cpp
62f240a1c931a32c5b49f4cf77e79f872c89136c
[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 // - Layer locking (hiding works)
24 //
25
26 // Uncomment this for debugging...
27 //#define DEBUG
28 //#define DEBUGFOO                              // Various tool debugging...
29 //#define DEBUGTP                               // Toolpalette debugging...
30
31 #include "drawingview.h"
32
33 #include <stdint.h>
34 #include "geometry.h"
35 #include "global.h"
36 #include "mathconstants.h"
37 #include "painter.h"
38 #include "structs.h"
39 #include "utils.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), numSelected(0), numHovered(0), shiftDown(false),
51         ctrlDown(false),
52         gridBackground(BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE),
53         scale(1.0), offsetX(-10), offsetY(-10),
54         gridPixels(0), collided(false), hoveringIntersection(false)
55 {
56 //      document.isTopLevelContainer = true;
57 //wtf? doesn't work except in c++11???  document = { 0 };
58         setBackgroundRole(QPalette::Base);
59         setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
60
61         Global::gridSpacing = 12.0;             // In base units (inch is default)
62
63 #if 0
64         Line * line = new Line(Vector(5, 5), Vector(50, 40), &document);
65         document.Add(line);
66         document.Add(new Line(Vector(50, 40), Vector(10, 83), &document));
67         document.Add(new Line(Vector(10, 83), Vector(17, 2), &document));
68         document.Add(new Circle(Vector(100, 100), 36, &document));
69         document.Add(new Circle(Vector(50, 150), 49, &document));
70         document.Add(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3, &document)),
71         document.Add(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5, &document));
72 #if 1
73         Dimension * dimension = new Dimension(Vector(0, 0), Vector(0, 0), DTLinear, &document);
74         line->SetDimensionOnLine(dimension);
75         document.Add(dimension);
76 #else
77         // Alternate way to do the above...
78         line->SetDimensionOnLine();
79 #endif
80 #else
81         Line * line = new Line;//(Vector(5, 5), Vector(50, 40), &document);
82         line->p[0] = Vector(5, 5);
83         line->p[1] = Vector(50, 40);
84         line->type = OTLine;
85         line->thickness = 2.0;
86         line->style = LSDash;
87         line->color = 0xFF7F00;
88         line->layer = 0;
89         document.objects.push_back(line);
90         document.objects.push_back(new Line(Vector(50, 40), Vector(10, 83)));
91         document.objects.push_back(new Line(Vector(10, 83), Vector(17, 2)));
92         document.objects.push_back(new Circle(Vector(100, 100), 36));
93         document.objects.push_back(new Circle(Vector(50, 150), 49));
94         document.objects.push_back(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3)),
95         document.objects.push_back(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5));
96         document.objects.push_back(new Dimension(Vector(50, 40), Vector(5, 5)));
97         document.objects.push_back(new Text(Vector(10, 83), "Here is some awesome text!"));
98 #endif
99
100 /*
101 Here we set the grid size in pixels--12 in this case. Initially, we have our
102 zoom set to make this represent 12 inches at a zoom factor of 25%. (This is
103 arbitrary.) So, to be able to decouple the grid size from the zoom, we need
104 to be able to set the size of the background grid (which we do here at an
105 arbitrary 12 pixels) to anything we want (within reason, of course :-).
106
107 The drawing enforces the grid spacing through the drawing->gridSpacing variable.
108
109         drawing->gridSpacing = 12.0 / Global::zoom;
110
111 Global::zoom is the zoom factor for the drawing, and all mouse clicks are
112 translated to Cartesian coordinates through this. (Initially, Global::zoom is
113 set to 1.0. SCREEN_ZOOM is set to 1.0/4.0.)
114
115 Really, the 100% zoom level can be set at *any* zoom level, it's more of a
116 convenience function than any measure of absolutes. Doing things that way we
117 could rid ourselves of the whole SCREEN_ZOOM parameter and all the attendant
118 shittiness that comes with it.
119
120 However, it seems that SCREEN_ZOOM is used to make text and arrow sizes show up
121 a certain way, which means we should probably create something else in those
122 objects to take its place--like some kind of scale factor. This would seem to
123 imply that certain point sizes actually *do* tie things like fonts to absolute
124 sizes on the screen, but not necessarily because you could have an inch scale
125 with text that is quite small relative to other objects on the screen, which
126 currently you have to zoom in to see (and which blows up the text). Point sizes
127 in an application like this are a bit meaningless; even though an inch is an
128 inch regardless of the zoom level a piece of text can be larger or smaller than
129 this. Maybe this is the case for having a base unit and basing point sizes off
130 of that.
131
132 Here's what's been figured out. Global::zoom is simply the ratio of pixels to
133 base units. What that means is that if you have a 12px grid with a 6" grid size
134 (& base unit of "inches"), Global::zoom becomes 12px / 6" = 2.0 px/in.
135
136 Dimensions now have a "size" parameter to set their absolute size in relation
137 to the base unit. ATM, the arrows are drawn in pixels, but also scaled by
138 Global::zoom *and* size. Same with the dimension text; it's drawn at 10pt and
139 scaled the same way as the arrowheads.
140
141 Need a way to scale line widths as well. :-/ Shouldn't be too difficult, just
142 need a thickness parameter similar to the "size" param for dimensions. (And now
143 we do! :-)
144
145 */
146         SetGridSize(12);        // This is in pixels
147 }
148
149
150 #if 0
151 void DrawingView::SetToolActive(Action * action)
152 {
153         if (action != NULL)
154         {
155                 toolAction = action;
156                 connect(toolAction, SIGNAL(ObjectReady(Object *)), this,
157                         SLOT(AddNewObjectToDocument(Object *)));
158                 connect(toolAction, SIGNAL(NeedRefresh()), this, SLOT(HandleActionUpdate()));
159         }
160 }
161 #endif
162
163
164 void DrawingView::SetGridSize(uint32_t size)
165 {
166         // Sanity check
167         if (size == gridPixels)
168                 return;
169
170         // Recreate the background bitmap
171         gridPixels = size;
172         QPainter pmp(&gridBackground);
173         pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
174         pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
175
176         for(int i=0; i<(BACKGROUND_MAX_SIZE-1); i+=gridPixels)
177         {
178                 pmp.drawLine(i, 0, i, BACKGROUND_MAX_SIZE - 1);
179                 pmp.drawLine(0, i, BACKGROUND_MAX_SIZE - 1, i);
180         }
181
182         pmp.end();
183
184         // Set up new BG brush & zoom level (pixels per base unit)
185 //      Painter::zoom = gridPixels / gridSpacing;
186         Global::zoom = gridPixels / Global::gridSpacing;
187         UpdateGridBackground();
188 }
189
190
191 void DrawingView::UpdateGridBackground(void)
192 {
193         // Transform the origin to Qt coordinates
194         Vector pixmapOrigin = Painter::CartesianToQtCoords(Vector());
195         int x = (int)pixmapOrigin.x;
196         int y = (int)pixmapOrigin.y;
197         // Use mod arithmetic to grab the correct swatch of background
198 /*
199 Negative numbers still screw it up... Need to think about what we're
200 trying to do here. The fact that it worked with 72 seems to have been pure luck.
201 It seems the problem is negative numbers: We can't let that happen.
202 When taking away the zero, it pops over 1 px at zero, then goes about 1/2 a
203 grid at x<0.
204
205 The bitmap looks like this:
206
207 +---+---+---+---+---
208 |   |   |   |   |
209 |   |   |   |   |
210 +---+---+---+---+---
211 |   |   |   |   |
212 |   |   |   |   |
213 |   |   |   |   |
214
215 @ x = 1, we want it to look like:
216
217 -+---+---+---+---+---
218  |   |   |   |   |
219  |   |   |   |   |
220 -+---+---+---+---+---
221  |   |   |   |   |
222  |   |   |   |   |
223  |   |   |   |   |
224
225 Which means we need to grab the sample from x = 3. @ x = -1:
226
227 ---+---+---+---+---
228    |   |   |   |
229    |   |   |   |
230 ---+---+---+---+---
231    |   |   |   |
232    |   |   |   |
233    |   |   |   |
234
235 Which means we need to grab the sample from x = 1. Which means we have to take
236 the mirror of the modulus of gridPixels.
237
238 Doing a mod of a negative number is problematic: 1st, the compiler converts the
239 negative number to an unsigned int, then it does the mod. Gets you wrong answers
240 most of the time, unless you use a power of 2. :-P So what we do here is just
241 take the modulus of the negation, which means we don't have to worry about
242 mirroring it later.
243
244 The positive case looks gruesome (and it is) but it boils down to this: We take
245 the modulus of the X coordinate, then mirror it by subtraction from the
246 maximum (in this case, gridPixels). This gives us a number in the range of 1 to
247 gridPixels. But we need the case where the result equalling gridPixels to be
248 zero; so we do another modulus operation on the result to achieve this.
249 */
250         if (x < 0)
251                 x = -x % gridPixels;
252         else
253                 x = (gridPixels - (x % gridPixels)) % gridPixels;
254
255         if (y < 0)
256                 y = -y % gridPixels;
257         else
258                 y = (gridPixels - (y % gridPixels)) % gridPixels;
259
260         // Here we grab a section of the bigger pixmap, so that the background
261         // *looks* like it's scrolling...
262         QPixmap pm = gridBackground.copy(x, y, gridPixels, gridPixels);
263         QPalette pal = palette();
264         pal.setBrush(backgroundRole(), QBrush(pm));
265         setAutoFillBackground(true);
266         setPalette(pal);
267 }
268
269
270 void DrawingView::SetCurrentLayer(int /*layer*/)
271 {
272 //Not needed anymore...
273 //      Global::currentLayer = layer;
274 //printf("DrawingView::CurrentLayer = %i\n", layer);
275 }
276
277
278 //
279 // Basically, we just make a single pass through the Container. If the layer #
280 // is less than the layer # being deleted, then do nothing. If the layer # is
281 // equal to the layer # being deleted, then delete the object. If the layer #
282 // is greater than the layer # being deleted, then set the layer # to its layer
283 // # - 1.
284 //
285 void DrawingView::DeleteCurrentLayer(int layer)
286 {
287 //printf("DrawingView::DeleteCurrentLayer(): currentLayer = %i\n", layer);
288         std::vector<void *>::iterator i = document.objects.begin();
289
290         while (i != document.objects.end())
291         {
292                 Object * obj = (Object *)(*i);
293
294                 if (obj->layer < layer)
295                         i++;
296                 else if (obj->layer == layer)
297                 {
298                         document.objects.erase(i);
299                         delete obj;
300                 }
301                 else
302                 {
303                         obj->layer--;
304                         i++;
305                 }
306         }
307
308         // We've just done a destructive action, so update the screen!
309         update();
310 }
311
312
313 void DrawingView::HandleLayerToggle(void)
314 {
315         // A layer's visibility was toggled, so update the screen...
316         update();
317 }
318
319
320 //
321 // A layer was moved up or down in the layer list, so we have to swap the
322 // document's object's layer numbers in the layers that were swapped.
323 //
324 void DrawingView::HandleLayerSwap(int layer1, int layer2)
325 {
326 //printf("DrawingView: Swapping layers %i and %i.\n", layer1, layer2);
327         std::vector<void *>::iterator i;
328
329         for(i=document.objects.begin(); i!=document.objects.end(); i++)
330         {
331                 Object * obj = (Object *)(*i);
332
333                 if (obj->layer == layer1)
334                         obj->layer = layer2;
335                 else if (obj->layer == layer2)
336                         obj->layer = layer1;
337         }
338 }
339
340
341 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
342 {
343         // This is undoing the transform, e.g. going from client coords to local coords.
344         // In essence, the height - y is height + (y * -1), the (y * -1) term doing the
345         // conversion of the y-axis from increasing bottom to top.
346         return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
347 }
348
349
350 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
351 {
352         // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
353         // No voodoo here, it's just grouped wrong to see it. It should be:
354         // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive
355         return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
356 }
357
358
359 void DrawingView::paintEvent(QPaintEvent * /*event*/)
360 {
361         QPainter qtPainter(this);
362         Painter painter(&qtPainter);
363
364         if (useAntialiasing)
365                 qtPainter.setRenderHint(QPainter::Antialiasing);
366
367         Global::viewportHeight = size().height();
368
369         // Draw coordinate axes
370         painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
371         painter.DrawLine(0, -16384, 0, 16384);
372         painter.DrawLine(-16384, 0, 16384, 0);
373
374         // Do object rendering...
375         for(int i=0; i<Global::numLayers; i++)
376         {
377                 if (Global::layerHidden[i] == false)
378                         RenderObjects(&painter, document.objects, i);
379         }
380
381         // Do tool rendering, if any...
382         if (Global::tool)
383         {
384                 painter.SetPen(QPen(QColor(200, 100, 0, 255), 1.0, Qt::DashLine));
385                 painter.DrawCrosshair(oldPoint);
386                 ToolDraw(&painter);
387         }
388
389         // Do selection rectangle rendering, if any
390         if (Global::selectionInProgress)
391         {
392                 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
393                 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
394                 painter.DrawRect(Global::selection);
395         }
396
397         if (hoveringIntersection)
398                 painter.DrawHandle(intersectionPoint);
399
400         if (!informativeText.isEmpty())
401                 painter.DrawInformativeText(informativeText);
402 }
403
404
405 //
406 // Renders objects in the passed in vector
407 //
408 void DrawingView::RenderObjects(Painter * painter, std::vector<void *> & v, int layer)
409 {
410         std::vector<void *>::iterator i;
411
412         for(i=v.begin(); i!=v.end(); i++)
413         {
414                 Object * obj = (Object *)(*i);
415                 float scaledThickness = Global::scale * obj->thickness;
416
417                 // If the object isn't on the current layer being drawn, skip it
418                 if (obj->layer != layer)
419                         continue;
420
421                 if ((Global::tool == TTRotate) && ctrlDown && obj->selected)
422                 {
423                         painter->SetPen(0x00FF00, 2.0, LSSolid);
424                 }
425                 else
426                 {
427                         painter->SetPen(obj->color, Global::zoom * scaledThickness, obj->style);
428                         painter->SetBrush(obj->color);
429
430                         if (obj->selected || obj->hitObject)
431                                 painter->SetPen(0xFF0000, Global::zoom * scaledThickness, LSDash);
432                 }
433
434                 switch (obj->type)
435                 {
436                 case OTLine:
437                         painter->DrawLine(obj->p[0], obj->p[1]);
438
439                         if (obj->hitPoint[0])
440                                 painter->DrawHandle(obj->p[0]);
441
442                         if (obj->hitPoint[1])
443                                 painter->DrawHandle(obj->p[1]);
444
445                         break;
446                 case OTCircle:
447                         painter->SetBrush(QBrush(Qt::NoBrush));
448                         painter->DrawEllipse(obj->p[0], obj->radius[0], obj->radius[0]);
449
450                         if (obj->hitPoint[0])
451                                 painter->DrawHandle(obj->p[0]);
452
453                         break;
454                 case OTArc:
455                         painter->DrawArc(obj->p[0], obj->radius[0], obj->angle[0], obj->angle[1]);
456
457                         if (obj->hitPoint[0])
458                                 painter->DrawHandle(obj->p[0]);
459
460                         if (obj->hitPoint[1])
461                                 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]));
462
463                         if (obj->hitPoint[2])
464                                 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0] + obj->angle[1]), sin(obj->angle[0] + obj->angle[1])) * obj->radius[0]));
465
466                         break;
467                 case OTDimension:
468                 {
469                         Dimension * d = (Dimension *)obj;
470
471                         Vector v(d->p[0], d->p[1]);
472                         double angle = v.Angle();
473                         Vector unit = v.Unit();
474                         Vector linePt1 = d->p[0], linePt2 = d->p[1];
475                         Vector ortho;
476                         double x1, y1, length;
477
478                         if (d->subtype == DTLinearVert)
479                         {
480                                 if ((angle < 0) || (angle > PI))
481                                 {
482                                         x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
483                                         y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
484                                         ortho = Vector(1.0, 0);
485                                         angle = PI3_OVER_2;
486                                 }
487                                 else
488                                 {
489                                         x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
490                                         y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
491                                         ortho = Vector(-1.0, 0);
492                                         angle = PI_OVER_2;
493                                 }
494
495                                 linePt1.x = linePt2.x = x1;
496                                 length = fabs(d->p[0].y - d->p[1].y);
497                         }
498                         else if (d->subtype == DTLinearHorz)
499                         {
500                                 if ((angle < PI_OVER_2) || (angle > PI3_OVER_2))
501                                 {
502                                         x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
503                                         y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
504                                         ortho = Vector(0, 1.0);
505                                         angle = 0;
506                                 }
507                                 else
508                                 {
509                                         x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
510                                         y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
511                                         ortho = Vector(0, -1.0);
512                                         angle = PI;
513                                 }
514
515                                 linePt1.y = linePt2.y = y1;
516                                 length = fabs(d->p[0].x - d->p[1].x);
517                         }
518                         else if (d->subtype == DTLinear)
519                         {
520                                 angle = Vector(linePt1, linePt2).Angle();
521                                 ortho = Vector::Normal(linePt1, linePt2);
522                                 length = v.Magnitude();
523                         }
524
525                         unit = Vector(linePt1, linePt2).Unit();
526
527                         Point p1 = linePt1 + (ortho * 10.0 * scaledThickness);
528                         Point p2 = linePt2 + (ortho * 10.0 * scaledThickness);
529                         Point p3 = linePt1 + (ortho * 16.0 * scaledThickness);
530                         Point p4 = linePt2 + (ortho * 16.0 * scaledThickness);
531                         Point p5 = d->p[0] + (ortho * 4.0 * scaledThickness);
532                         Point p6 = d->p[1] + (ortho * 4.0 * scaledThickness);
533
534                 /*
535                 The numbers hardcoded into here, what are they?
536                 I believe they are pixels.
537                 */
538                         // Draw extension lines (if certain type)
539                         painter->DrawLine(p3, p5);
540                         painter->DrawLine(p4, p6);
541
542                         // Calculate whether or not the arrowheads are too crowded to put inside
543                         // the extension lines. 9.0 is the length of the arrowhead.
544                         double t = Geometry::ParameterOfLineAndPoint(linePt1, linePt2, linePt2 - (unit * 9.0 * scaledThickness));
545                 //printf("Dimension::Draw(): t = %lf\n", t);
546
547                         // On the screen, it's acting like this is actually 58%...
548                         // This is correct, we want it to happen at > 50%
549                         if (t > 0.58)
550                         {
551                                 // Draw main dimension line + arrowheads
552                                 painter->DrawLine(p1, p2);
553                                 painter->DrawArrowhead(p1, p2, scaledThickness);
554                                 painter->DrawArrowhead(p2, p1, scaledThickness);
555                         }
556                         else
557                         {
558                                 // Draw outside arrowheads
559                                 Point p7 = p1 - (unit * 9.0 * scaledThickness);
560                                 Point p8 = p2 + (unit * 9.0 * scaledThickness);
561                                 painter->DrawArrowhead(p1, p7, scaledThickness);
562                                 painter->DrawArrowhead(p2, p8, scaledThickness);
563                                 painter->DrawLine(p1, p1 - (unit * 14.0 * scaledThickness));
564                                 painter->DrawLine(p2, p2 + (unit * 14.0 * scaledThickness));
565                         }
566
567                         // Draw length of dimension line...
568                         painter->SetFont(QFont("Arial", 8.0 * Global::zoom * scaledThickness));
569                         Point ctr = p2 + (Vector(p2, p1) / 2.0);
570
571                 #if 0
572                         QString dimText = QString("%1\"").arg(Vector(endpoint - position).Magnitude());
573                 #else
574                         QString dimText;
575
576                         if (length < 12.0)
577                                 dimText = QString("%1\"").arg(length);
578                         else
579                         {
580                                 double feet = (double)((int)length / 12);
581                                 double inches = length - (feet * 12.0);
582
583                                 if (inches == 0)
584                                         dimText = QString("%1'").arg(feet);
585                                 else
586                                         dimText = QString("%1' %2\"").arg(feet).arg(inches);
587                         }
588                 #endif
589
590                         painter->DrawAngledText(ctr, angle, dimText, scaledThickness);
591
592                         break;
593                 }
594                 case OTText:
595                 {
596                         Text * t = (Text *)obj;
597                         painter->DrawTextObject(t->p[0], t->s.c_str(), scaledThickness);
598                         break;
599                 }
600                 default:
601                         break;
602                 }
603         }
604 }
605
606
607 void DrawingView::AddHoveredToSelection(void)
608 {
609         std::vector<void *>::iterator i;
610
611         for(i=document.objects.begin(); i!=document.objects.end(); i++)
612         {
613                 if (((Object *)(*i))->hovered)
614                         ((Object *)(*i))->selected = true;
615         }
616 }
617
618
619 void DrawingView::GetSelection(std::vector<void *> & v)
620 {
621         v.clear();
622         std::vector<void *>::iterator i;
623
624         for(i=document.objects.begin(); i!=document.objects.end(); i++)
625         {
626                 if (((Object *)(*i))->selected)
627                         v.push_back(*i);
628         }
629 }
630
631
632 void DrawingView::GetHovered(std::vector<void *> & v)
633 {
634         v.clear();
635         std::vector<void *>::iterator i;
636
637         for(i=document.objects.begin(); i!=document.objects.end(); i++)
638         {
639                 if (((Object *)(*i))->hovered)
640 //              {
641 //printf("GetHovered: adding object (%X) to hover... hp1=%s, hp2=%s, hl=%s\n", (*i), (((Line *)(*i))->hitPoint[0] ? "true" : "false"), (((Line *)(*i))->hitPoint[1] ? "true" : "false"), (((Line *)(*i))->hitObject ? "true" : "false"));
642                         v.push_back(*i);
643 //              }
644         }
645 }
646
647
648 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
649 {
650         Global::screenSize = Vector(size().width(), size().height());
651         UpdateGridBackground();
652 }
653
654
655 void DrawingView::ToolHandler(int mode, Point p)
656 {
657         if (Global::tool == TTLine)
658                 LineHandler(mode, p);
659         else if (Global::tool == TTCircle)
660                 CircleHandler(mode, p);
661         else if (Global::tool == TTArc)
662                 ArcHandler(mode, p);
663         else if (Global::tool == TTRotate)
664                 RotateHandler(mode, p);
665         else if (Global::tool == TTMirror)
666                 MirrorHandler(mode, p);
667 }
668
669
670 void DrawingView::ToolDraw(Painter * painter)
671 {
672         if (Global::tool == TTLine)
673         {
674                 if (Global::toolState == TSNone)
675                 {
676                         painter->DrawHandle(toolPoint[0]);
677                 }
678                 else if ((Global::toolState == TSPoint2) && shiftDown)
679                 {
680                         painter->DrawHandle(toolPoint[1]);
681                 }
682                 else
683                 {
684                         painter->DrawLine(toolPoint[0], toolPoint[1]);
685                         painter->DrawHandle(toolPoint[1]);
686
687                         Vector v(toolPoint[0], toolPoint[1]);
688                         double absAngle = v.Angle() * RADIANS_TO_DEGREES;
689                         double absLength = v.Magnitude();
690                         QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
691                         informativeText = text.arg(absLength).arg(absAngle);
692                 }
693         }
694         else if (Global::tool == TTCircle)
695         {
696                 if (Global::toolState == TSNone)
697                 {
698                         painter->DrawHandle(toolPoint[0]);
699                 }
700                 else if ((Global::toolState == TSPoint2) && shiftDown)
701                 {
702                         painter->DrawHandle(toolPoint[1]);
703                 }
704                 else
705                 {
706                         double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
707 //                      painter->DrawLine(toolPoint[0], toolPoint[1]);
708 //                      painter->DrawHandle(toolPoint[1]);
709                         painter->SetBrush(QBrush(Qt::NoBrush));
710                         painter->DrawEllipse(toolPoint[0], length, length);
711                         QString text = tr("Radius: %1 in.");//\n") + QChar(0x2221) + tr(": %2");
712                         informativeText = text.arg(length);//.arg(absAngle);
713                 }
714         }
715         else if (Global::tool == TTArc)
716         {
717                 if (Global::toolState == TSNone)
718                 {
719                         painter->DrawHandle(toolPoint[0]);
720                 }
721                 else if (Global::toolState == TSPoint2)
722                 {
723                         double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
724                         painter->SetBrush(QBrush(Qt::NoBrush));
725                         painter->DrawEllipse(toolPoint[0], length, length);
726                         painter->DrawLine(toolPoint[0], toolPoint[1]);
727                         painter->DrawHandle(toolPoint[1]);
728                         QString text = tr("Radius: %1 in.");
729                         informativeText = text.arg(length);
730                 }
731                 else if (Global::toolState == TSPoint3)
732                 {
733                         double angle = Vector::Angle(toolPoint[0], toolPoint[2]);
734                         painter->DrawLine(toolPoint[0], toolPoint[2]);
735                         painter->SetBrush(QBrush(Qt::NoBrush));
736                         painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
737                         painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
738                         QString text = tr("Angle start: %1") + QChar(0x00B0);
739                         informativeText = text.arg(RADIANS_TO_DEGREES * angle);
740                 }
741                 else
742                 {
743                         double angle = Vector::Angle(toolPoint[0], toolPoint[3]);
744                         double span = angle - toolPoint[2].x;
745
746                         if (span < 0)
747                                 span += PI_TIMES_2;
748
749                         painter->DrawLine(toolPoint[0], toolPoint[3]);
750                         painter->SetBrush(QBrush(Qt::NoBrush));
751                         painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
752                         painter->SetPen(0xFF00FF, 2.0, LSSolid);
753                         painter->DrawArc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
754                         painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
755                         QString text = tr("Arc span: %1") + QChar(0x00B0);
756                         informativeText = text.arg(RADIANS_TO_DEGREES * span);
757                 }
758         }
759         else if (Global::tool == TTRotate)
760         {
761                 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
762                         painter->DrawHandle(toolPoint[0]);
763                 else if ((Global::toolState == TSPoint2) && shiftDown)
764                         painter->DrawHandle(toolPoint[1]);
765                 else
766                 {
767                         if (toolPoint[0] == toolPoint[1])
768                                 return;
769
770                         painter->DrawLine(toolPoint[0], toolPoint[1]);
771                         // Likely we need a tool container for this... (now we do!)
772 #if 0
773                         if (ctrlDown)
774                         {
775                                 painter->SetPen(0x00FF00, 2.0, LSSolid);
776                                 overrideColor = true;
777                         }
778
779                         RenderObjects(painter, toolObjects);
780                         overrideColor = false;
781 #endif
782
783                         double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
784                         QString text = QChar(0x2221) + QObject::tr(": %1");
785                         informativeText = text.arg(absAngle);
786
787                         if (ctrlDown)
788                                 informativeText += " (Copy)";
789                 }
790         }
791         else if (Global::tool == TTMirror)
792         {
793                 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
794                         painter->DrawHandle(toolPoint[0]);
795                 else if ((Global::toolState == TSPoint2) && shiftDown)
796                         painter->DrawHandle(toolPoint[1]);
797                 else
798                 {
799                         if (toolPoint[0] == toolPoint[1])
800                                 return;
801                         
802                         Point mirrorPoint = toolPoint[0] + Vector(toolPoint[1], toolPoint[0]);
803 //                      painter->DrawLine(toolPoint[0], toolPoint[1]);
804                         painter->DrawLine(mirrorPoint, toolPoint[1]);
805                         // Likely we need a tool container for this... (now we do!)
806 #if 0
807                         if (ctrlDown)
808                         {
809                                 painter->SetPen(0x00FF00, 2.0, LSSolid);
810                                 overrideColor = true;
811                         }
812
813                         RenderObjects(painter, toolObjects);
814                         overrideColor = false;
815 #endif
816
817                         double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
818
819                         if (absAngle > 180.0)
820                                 absAngle -= 180.0;
821
822                         QString text = QChar(0x2221) + QObject::tr(": %1");
823                         informativeText = text.arg(absAngle);
824
825                         if (ctrlDown)
826                                 informativeText += " (Copy)";
827                 }
828         }
829 }
830
831
832 void DrawingView::LineHandler(int mode, Point p)
833 {
834         switch (mode)
835         {
836         case ToolMouseDown:
837                 if (Global::toolState == TSNone)
838                         toolPoint[0] = p;
839                 else
840                         toolPoint[1] = p;
841
842                 break;
843         case ToolMouseMove:
844                 if (Global::toolState == TSNone)
845                         toolPoint[0] = p;
846                 else
847                         toolPoint[1] = p;
848
849                 break;
850         case ToolMouseUp:
851                 if (Global::toolState == TSNone)
852                 {
853                         Global::toolState = TSPoint2;
854                         // Prevent spurious line from drawing...
855                         toolPoint[1] = toolPoint[0];
856                 }
857                 else if ((Global::toolState == TSPoint2) && shiftDown)
858                 {
859                         // Key override is telling us to make a new line, not continue the
860                         // previous one.
861                         toolPoint[0] = toolPoint[1];
862                 }
863                 else
864                 {
865                         Line * l = new Line(toolPoint[0], toolPoint[1]);
866                         l->layer = Global::activeLayer;
867                         document.objects.push_back(l);
868                         toolPoint[0] = toolPoint[1];
869                 }
870         }
871 }
872
873
874 void DrawingView::CircleHandler(int mode, Point p)
875 {
876         switch (mode)
877         {
878         case ToolMouseDown:
879                 if (Global::toolState == TSNone)
880                         toolPoint[0] = p;
881                 else
882                         toolPoint[1] = p;
883
884                 break;
885         case ToolMouseMove:
886                 if (Global::toolState == TSNone)
887                         toolPoint[0] = p;
888                 else
889                         toolPoint[1] = p;
890
891                 break;
892         case ToolMouseUp:
893                 if (Global::toolState == TSNone)
894                 {
895                         Global::toolState = TSPoint2;
896                         // Prevent spurious line from drawing...
897                         toolPoint[1] = toolPoint[0];
898                 }
899                 else if ((Global::toolState == TSPoint2) && shiftDown)
900                 {
901                         // Key override is telling us to make a new line, not continue the
902                         // previous one.
903                         toolPoint[0] = toolPoint[1];
904                 }
905                 else
906                 {
907                         double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
908                         Circle * c = new Circle(toolPoint[0], length);
909                         c->layer = Global::activeLayer;
910                         document.objects.push_back(c);
911                         toolPoint[0] = toolPoint[1];
912                         Global::toolState = TSNone;
913                 }
914         }
915 }
916
917
918 void DrawingView::ArcHandler(int mode, Point p)
919 {
920         switch (mode)
921         {
922         case ToolMouseDown:
923                 if (Global::toolState == TSNone)
924                         toolPoint[0] = p;
925                 else if (Global::toolState == TSPoint2)
926                         toolPoint[1] = p;
927                 else if (Global::toolState == TSPoint3)
928                         toolPoint[2] = p;
929                 else
930                         toolPoint[3] = p;
931
932                 break;
933         case ToolMouseMove:
934                 if (Global::toolState == TSNone)
935                         toolPoint[0] = p;
936                 else if (Global::toolState == TSPoint2)
937                         toolPoint[1] = p;
938                 else if (Global::toolState == TSPoint3)
939                         toolPoint[2] = p;
940                 else
941                         toolPoint[3] = p;
942
943                 break;
944         case ToolMouseUp:
945                 if (Global::toolState == TSNone)
946                 {
947                         // Prevent spurious line from drawing...
948                         toolPoint[1] = toolPoint[0];
949                         Global::toolState = TSPoint2;
950                 }
951                 else if (Global::toolState == TSPoint2)
952                 {
953                         if (shiftDown)
954                         {
955                                 // Key override is telling us to start circle at new center, not
956                                 // continue the current one.
957                                 toolPoint[0] = toolPoint[1];
958                                 return;
959                         }
960
961                         // Set the radius in toolPoint[1].x
962                         toolPoint[1].x = Vector::Magnitude(toolPoint[0], toolPoint[1]);
963                         Global::toolState = TSPoint3;
964                 }
965                 else if (Global::toolState == TSPoint3)
966                 {
967                         // Set the angle in toolPoint[2].x
968                         toolPoint[2].x = Vector::Angle(toolPoint[0], toolPoint[2]);
969                         Global::toolState = TSPoint4;
970                 }
971                 else
972                 {
973                         double endAngle = Vector::Angle(toolPoint[0], toolPoint[3]);
974                         double span = endAngle - toolPoint[2].x;
975
976                         if (span < 0)
977                                 span += PI_TIMES_2;
978
979                         Arc * arc = new Arc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
980                         arc->layer = Global::activeLayer;
981                         document.objects.push_back(arc);
982                         Global::toolState = TSNone;
983                 }
984         }
985 }
986
987
988 void DrawingView::RotateHandler(int mode, Point p)
989 {
990         switch (mode)
991         {
992         case ToolMouseDown:
993                 if (Global::toolState == TSNone)
994                 {
995                         toolPoint[0] = p;
996                         SavePointsFrom(select, toolScratch);
997                         Global::toolState = TSPoint1;
998                 }
999                 else if (Global::toolState == TSPoint1)
1000                         toolPoint[0] = p;
1001                 else
1002                         toolPoint[1] = p;
1003
1004                 break;
1005         case ToolMouseMove:
1006                 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1007                         toolPoint[0] = p;
1008                 else if (Global::toolState == TSPoint2)
1009                 {
1010                         toolPoint[1] = p;
1011
1012                         if (shiftDown)
1013                                 return;
1014
1015                         double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1016                         std::vector<void *>::iterator j = select.begin();
1017                         std::vector<Object>::iterator i = toolScratch.begin();
1018
1019                         for(; i!=toolScratch.end(); i++, j++)
1020                         {
1021                                 Object obj = *i;
1022                                 Point p1 = Geometry::RotatePointAroundPoint(obj.p[0], toolPoint[0], angle);
1023                                 Point p2 = Geometry::RotatePointAroundPoint(obj.p[1], toolPoint[0], angle);
1024                                 Object * obj2 = (Object *)(*j);
1025                                 obj2->p[0] = p1;
1026                                 obj2->p[1] = p2;
1027
1028                                 if (obj.type == OTArc)
1029                                 {
1030                                         obj2->angle[0] = obj.angle[0] + angle;
1031
1032                                         if (obj2->angle[0] > PI_TIMES_2)
1033                                                 obj2->angle[0] -= PI_TIMES_2;
1034                                 }
1035                         }
1036                 }
1037
1038                 break;
1039         case ToolMouseUp:
1040                 if (Global::toolState == TSPoint1)
1041                 {
1042                         Global::toolState = TSPoint2;
1043                         // Prevent spurious line from drawing...
1044                         toolPoint[1] = toolPoint[0];
1045                 }
1046                 else if ((Global::toolState == TSPoint2) && shiftDown)
1047                 {
1048                         // Key override is telling us to make a new line, not continue the
1049                         // previous one.
1050                         toolPoint[0] = toolPoint[1];
1051                 }
1052                 else
1053                 {
1054                         // Either we're finished with our rotate, or we're stamping a copy.
1055                         if (ctrlDown)
1056                         {
1057                                 // Stamp a copy of the selection at the current rotation & bail
1058                                 std::vector<void *> temp;
1059                                 CopyObjects(select, temp);
1060                                 ClearSelected(temp);
1061                                 AddObjectsTo(document.objects, temp);
1062                                 RestorePointsTo(select, toolScratch);
1063                                 return;
1064                         }
1065
1066                         toolPoint[0] = p;
1067                         Global::toolState = TSPoint1;
1068                         SavePointsFrom(select, toolScratch);
1069                 }
1070
1071                 break;
1072         case ToolKeyDown:
1073                 // Reset the selection if shift held down...
1074                 if (shiftDown)
1075                         RestorePointsTo(select, toolScratch);
1076
1077                 break;
1078         case ToolKeyUp:
1079                 // Reset selection when key is let up
1080                 if (!shiftDown)
1081                         RotateHandler(ToolMouseMove, toolPoint[1]);
1082
1083                 break;
1084         case ToolCleanup:
1085                 RestorePointsTo(select, toolScratch);
1086         }
1087 }
1088
1089
1090 void DrawingView::MirrorHandler(int mode, Point p)
1091 {
1092         switch (mode)
1093         {
1094         case ToolMouseDown:
1095                 if (Global::toolState == TSNone)
1096                 {
1097                         toolPoint[0] = p;
1098                         SavePointsFrom(select, toolScratch);
1099                         Global::toolState = TSPoint1;
1100                 }
1101                 else if (Global::toolState == TSPoint1)
1102                         toolPoint[0] = p;
1103                 else
1104                         toolPoint[1] = p;
1105
1106                 break;
1107         case ToolMouseMove:
1108                 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1109                         toolPoint[0] = p;
1110                 else if (Global::toolState == TSPoint2)
1111                 {
1112                         toolPoint[1] = p;
1113
1114                         if (shiftDown)
1115                                 return;
1116
1117                         double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1118                         std::vector<void *>::iterator j = select.begin();
1119                         std::vector<Object>::iterator i = toolScratch.begin();
1120
1121                         for(; i!=toolScratch.end(); i++, j++)
1122                         {
1123                                 Object obj = *i;
1124                                 Point p1 = Geometry::MirrorPointAroundLine(obj.p[0], toolPoint[0], toolPoint[1]);
1125                                 Point p2 = Geometry::MirrorPointAroundLine(obj.p[1], toolPoint[0], toolPoint[1]);
1126                                 Object * obj2 = (Object *)(*j);
1127                                 obj2->p[0] = p1;
1128                                 obj2->p[1] = p2;
1129
1130                                 if (obj.type == OTArc)
1131                                 {
1132                                         // This is 2*mirror angle - obj angle - obj span
1133                                         obj2->angle[0] = (2.0 * angle) - obj.angle[0] - obj.angle[1];
1134
1135                                         if (obj2->angle[0] > PI_TIMES_2)
1136                                                 obj2->angle[0] -= PI_TIMES_2;
1137                                 }
1138                         }
1139                 }
1140
1141                 break;
1142         case ToolMouseUp:
1143                 if (Global::toolState == TSPoint1)
1144                 {
1145                         Global::toolState = TSPoint2;
1146                         // Prevent spurious line from drawing...
1147                         toolPoint[1] = toolPoint[0];
1148                 }
1149                 else if ((Global::toolState == TSPoint2) && shiftDown)
1150                 {
1151                         // Key override is telling us to make a new line, not continue the
1152                         // previous one.
1153                         toolPoint[0] = toolPoint[1];
1154                 }
1155                 else
1156                 {
1157                         // Either we're finished with our rotate, or we're stamping a copy.
1158                         if (ctrlDown)
1159                         {
1160                                 // Stamp a copy of the selection at the current rotation & bail
1161                                 std::vector<void *> temp;
1162                                 CopyObjects(select, temp);
1163                                 ClearSelected(temp);
1164                                 AddObjectsTo(document.objects, temp);
1165                                 RestorePointsTo(select, toolScratch);
1166                                 return;
1167                         }
1168
1169                         toolPoint[0] = p;
1170                         Global::toolState = TSPoint1;
1171                         SavePointsFrom(select, toolScratch);
1172                 }
1173
1174                 break;
1175         case ToolKeyDown:
1176                 // Reset the selection if shift held down...
1177                 if (shiftDown)
1178                         RestorePointsTo(select, toolScratch);
1179
1180                 break;
1181         case ToolKeyUp:
1182                 // Reset selection when key is let up
1183                 if (!shiftDown)
1184                         MirrorHandler(ToolMouseMove, toolPoint[1]);
1185
1186                 break;
1187         case ToolCleanup:
1188                 RestorePointsTo(select, toolScratch);
1189         }
1190 }
1191
1192
1193 void DrawingView::mousePressEvent(QMouseEvent * event)
1194 {
1195         if (event->button() == Qt::LeftButton)
1196         {
1197                 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1198
1199                 // Handle tool processing, if any
1200                 if (Global::tool)
1201                 {
1202                         if (Global::snapToGrid)
1203                                 point = SnapPointToGrid(point);
1204
1205                         //Also, may want to figure out if hovering over a snap point on an object,
1206                         //snap to grid if not.
1207                         // Snap to object point if valid...
1208 //                      if (Global::snapPointIsValid)
1209 //                              point = Global::snapPoint;
1210                         
1211                         ToolHandler(ToolMouseDown, point);
1212                         return;
1213                 }
1214
1215                 // Clear the selection only if CTRL isn't being held on click
1216                 if (!ctrlDown)
1217                         ClearSelected(document.objects);
1218 //                      ClearSelection();
1219
1220                 // If any objects are being hovered on click, add them to the selection
1221                 // & return
1222                 if (numHovered > 0)
1223                 {
1224                         AddHoveredToSelection();
1225                         update();       // needed??
1226                         GetHovered(hover);      // prolly needed
1227
1228                         // Needed for grab & moving objects
1229                         // We do it *after*... why? (doesn't seem to confer any advantage...)
1230                         if (Global::snapToGrid)
1231                                 oldPoint = SnapPointToGrid(point);
1232
1233                         return;
1234                 }
1235
1236                 // Didn't hit any object and not using a tool, so do a selection rectangle
1237                 Global::selectionInProgress = true;
1238                 Global::selection.setTopLeft(QPointF(point.x, point.y));
1239                 Global::selection.setBottomRight(QPointF(point.x, point.y));
1240         }
1241         else if (event->button() == Qt::MiddleButton)
1242         {
1243                 scrollDrag = true;
1244                 oldPoint = Vector(event->x(), event->y());
1245                 // Should also change the mouse pointer as well...
1246                 setCursor(Qt::SizeAllCursor);
1247         }
1248 }
1249
1250
1251 void DrawingView::mouseMoveEvent(QMouseEvent * event)
1252 {
1253         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1254         Global::selection.setBottomRight(QPointF(point.x, point.y));
1255         // Only needs to be done here, as mouse down is always preceded by movement
1256         Global::snapPointIsValid = false;
1257         hoveringIntersection = false;
1258
1259         // Scrolling...
1260         if (event->buttons() & Qt::MiddleButton)
1261         {
1262                 point = Vector(event->x(), event->y());
1263                 // Since we're using Qt coords for scrolling, we have to adjust them here to
1264                 // conform to Cartesian coords, since the origin is using Cartesian. :-)
1265                 Vector delta(oldPoint, point);
1266                 delta /= Global::zoom;
1267                 delta.y = -delta.y;
1268                 Global::origin -= delta;
1269
1270                 UpdateGridBackground();
1271                 update();
1272                 oldPoint = point;
1273                 return;
1274         }
1275
1276         // If we're doing a selection rect, see if any objects are engulfed by it
1277         // (implies left mouse button held down)
1278         if (Global::selectionInProgress)
1279         {
1280                 CheckObjectBounds();
1281                 update();
1282                 return;
1283         }
1284
1285         // Handle object movement (left button down & over an object)
1286         if ((event->buttons() & Qt::LeftButton) && numHovered && !Global::tool)
1287         {
1288                 if (Global::snapToGrid)
1289                         point = SnapPointToGrid(point);
1290
1291                 HandleObjectMovement(point);
1292                 update();
1293                 oldPoint = point;
1294                 return;
1295         }
1296
1297         // Do object hit testing...
1298         bool needUpdate = HitTestObjects(point);
1299
1300         // Check for multi-hover...
1301         if (numHovered > 1)
1302         {
1303                 GetHovered(hover);
1304
1305                 double t, u;
1306                 int numIntersecting = Geometry::Intersects((Object *)hover[0], (Object *)hover[1], &t, &u);
1307
1308                 if (numIntersecting > 0)
1309                 {
1310                         Vector v1 = Geometry::GetPointForParameter((Object *)hover[0], t);
1311                         Vector v2 = Geometry::GetPointForParameter((Object *)hover[1], u);
1312                         QString text = tr("Intersection t=%1 (%3, %4), u=%2 (%5, %6)");
1313                         informativeText = text.arg(t).arg(u).arg(v1.x).arg(v1.y).arg(v2.x).arg(v2.y);
1314
1315                         hoveringIntersection = true;
1316                         intersectionPoint = v1;
1317                 }
1318         }
1319
1320         // Do tool handling, if any are active...
1321         if (Global::tool)
1322         {
1323                 if (Global::snapToGrid)
1324                         point = SnapPointToGrid(point);
1325
1326                 ToolHandler(ToolMouseMove, point);
1327         }
1328
1329         // This is used to draw the tool crosshair...
1330         oldPoint = point;
1331
1332         if (needUpdate || Global::tool)
1333                 update();
1334 }
1335
1336
1337 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
1338 {
1339         if (event->button() == Qt::LeftButton)
1340         {
1341 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
1342 //could set it up to use the document's update function (assumes that all object updates
1343 //are being reported correctly:
1344 //              if (document.NeedsUpdate())
1345                 // Do an update if collided with at least *one* object in the document
1346 //              if (collided)
1347                         update();
1348
1349                 if (Global::tool)
1350                 {
1351                         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1352                         ToolHandler(ToolMouseUp, point);
1353                         return;
1354                 }
1355
1356                 if (Global::selectionInProgress)
1357                         Global::selectionInProgress = false;
1358
1359                 informativeText.clear();
1360 // Should we be doing this automagically? Hmm...
1361                 // Clear our vectors
1362                 select.clear();
1363                 hover.clear();
1364
1365                 // Scoop 'em up
1366                 std::vector<void *>::iterator i;
1367
1368                 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1369                 {
1370                         if (((Object *)(*i))->selected)
1371                                 select.push_back(*i);
1372
1373 //hmm, this is no good, too late to do any good :-P
1374 //                      if ((*i)->hovered)
1375 //                              hover.push_back(*i);
1376                 }
1377         }
1378         else if (event->button() == Qt::MiddleButton)
1379         {
1380                 scrollDrag = false;
1381                 setCursor(Qt::ArrowCursor);
1382         }
1383 }
1384
1385
1386 void DrawingView::wheelEvent(QWheelEvent * event)
1387 {
1388         double zoomFactor = 1.25;
1389         QSize sizeWin = size();
1390         Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
1391         center = Painter::QtToCartesianCoords(center);
1392
1393         // This is not centering for some reason. Need to figure out why. :-/
1394         if (event->delta() > 0)
1395         {
1396                 Vector newOrigin = center - ((center - Global::origin) / zoomFactor);
1397                 Global::origin = newOrigin;
1398                 Global::zoom *= zoomFactor;
1399         }
1400         else
1401         {
1402                 Vector newOrigin = center + ((-center + Global::origin) * zoomFactor);
1403                 Global::origin = newOrigin;
1404                 Global::zoom /= zoomFactor;
1405         }
1406
1407 //      Global::gridSpacing = gridPixels / Painter::zoom;
1408 //      UpdateGridBackground();
1409         SetGridSize(Global::gridSpacing * Global::zoom);
1410         update();
1411 //      zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Global::gridSpacing));
1412 }
1413
1414
1415 void DrawingView::keyPressEvent(QKeyEvent * event)
1416 {
1417         bool oldShift = shiftDown;
1418         bool oldCtrl = ctrlDown;
1419
1420         if (event->key() == Qt::Key_Shift)
1421                 shiftDown = true;
1422         else if (event->key() == Qt::Key_Control)
1423                 ctrlDown = true;
1424
1425         if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1426         {
1427                 if (Global::tool)
1428                         ToolHandler(ToolKeyDown, Point(0, 0));
1429
1430                 update();
1431         }
1432 }
1433
1434
1435 void DrawingView::keyReleaseEvent(QKeyEvent * event)
1436 {
1437         bool oldShift = shiftDown;
1438         bool oldCtrl = ctrlDown;
1439
1440         if (event->key() == Qt::Key_Shift)
1441                 shiftDown = false;
1442         else if (event->key() == Qt::Key_Control)
1443                 ctrlDown = false;
1444
1445         if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1446         {
1447                 if (Global::tool)
1448                         ToolHandler(ToolKeyUp, Point(0, 0));
1449
1450                 update();
1451         }
1452 }
1453
1454
1455 //
1456 // This looks strange, but it's really quite simple: We want a point that's
1457 // more than half-way to the next grid point to snap there while conversely we
1458 // want a point that's less than half-way to to the next grid point then snap
1459 // to the one before it. So we add half of the grid spacing to the point, then
1460 // divide by it so that we can remove the fractional part, then multiply it
1461 // back to get back to the correct answer.
1462 //
1463 Point DrawingView::SnapPointToGrid(Point point)
1464 {
1465         point += Global::gridSpacing / 2.0;             // *This* adds to Z!!!
1466         point /= Global::gridSpacing;
1467         point.x = floor(point.x);//need to fix this for negative numbers...
1468         point.y = floor(point.y);
1469         point.z = 0;                                    // Make *sure* Z doesn't go anywhere!!!
1470         point *= Global::gridSpacing;
1471         return point;
1472 }
1473
1474
1475 void DrawingView::CheckObjectBounds(void)
1476 {
1477         std::vector<void *>::iterator i;
1478         numSelected = 0;
1479
1480         for(i=document.objects.begin(); i!=document.objects.end(); i++)
1481         {
1482                 Object * obj = (Object *)(*i);
1483                 obj->selected = false;
1484
1485                 switch (obj->type)
1486                 {
1487                 case OTLine:
1488                 {
1489                         Line * l = (Line *)obj;
1490
1491                         if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
1492                                 l->selected = true;
1493
1494                         break;
1495                 }
1496                 case OTCircle:
1497                 {
1498                         Circle * c = (Circle *)obj;
1499
1500                         if (Global::selection.contains(c->p[0].x - c->radius[0], c->p[0].y - c->radius[0]) && Global::selection.contains(c->p[0].x + c->radius[0], c->p[0].y + c->radius[0]))
1501                                 c->selected = true;
1502
1503                         break;
1504                 }
1505                 case OTArc:
1506                 {
1507                         Arc * a = (Arc *)obj;
1508
1509                         double start = a->angle[0];
1510                         double end = start + a->angle[1];
1511                         QPointF p1(cos(start), sin(start));
1512                         QPointF p2(cos(end), sin(end));
1513                         QRectF bounds(p1, p2);
1514
1515 #if 1
1516                         // Swap X/Y coordinates if they're backwards...
1517                         if (bounds.left() > bounds.right())
1518                         {
1519                                 double temp = bounds.left();
1520                                 bounds.setLeft(bounds.right());
1521                                 bounds.setRight(temp);
1522                         }
1523
1524                         if (bounds.bottom() > bounds.top())
1525                         {
1526                                 double temp = bounds.bottom();
1527                                 bounds.setBottom(bounds.top());
1528                                 bounds.setTop(temp);
1529                         }
1530 #else
1531                         // Doesn't work as advertised! For shame!
1532                         bounds = bounds.normalized();
1533 #endif
1534
1535                         // If the end of the arc is before the beginning, add 360 degrees to it
1536                         if (end < start)
1537                                 end += 2.0 * PI;
1538
1539                         // Adjust the bounds depending on which axes are crossed
1540                         if ((start < PI_OVER_2) && (end > PI_OVER_2))
1541                                 bounds.setTop(1.0);
1542
1543                         if ((start < PI) && (end > PI))
1544                                 bounds.setLeft(-1.0);
1545
1546                         if ((start < (PI + PI_OVER_2)) && (end > (PI + PI_OVER_2)))
1547                                 bounds.setBottom(-1.0);
1548
1549                         if ((start < (2.0 * PI)) && (end > (2.0 * PI)))
1550                                 bounds.setRight(1.0);
1551
1552                         if ((start < ((2.0 * PI) + PI_OVER_2)) && (end > ((2.0 * PI) + PI_OVER_2)))
1553                                 bounds.setTop(1.0);
1554
1555                         if ((start < (3.0 * PI)) && (end > (3.0 * PI)))
1556                                 bounds.setLeft(-1.0);
1557
1558                         if ((start < ((3.0 * PI) + PI_OVER_2)) && (end > ((3.0 * PI) + PI_OVER_2)))
1559                                 bounds.setBottom(-1.0);
1560
1561                         bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
1562                         bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
1563                         bounds.translate(a->p[0].x, a->p[0].y);
1564
1565                         if (Global::selection.contains(bounds))
1566                                 a->selected = true;
1567
1568                         break;
1569                 }
1570                 default:
1571                         break;
1572                 }
1573
1574                 if (obj->selected)
1575                         numSelected++;
1576         }
1577 }
1578
1579
1580 bool DrawingView::HitTestObjects(Point point)
1581 {
1582         std::vector<void *>::iterator i;
1583         numHovered = 0;
1584         bool needUpdate = false;
1585
1586         for(i=document.objects.begin(); i!=document.objects.end(); i++)
1587         {
1588                 Object * obj = (Object *)(*i);
1589
1590                 switch (obj->type)
1591                 {
1592                 case OTLine:
1593                 {
1594                         bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
1595                         obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
1596                         Vector lineSegment = obj->p[1] - obj->p[0];
1597                         Vector v1 = point - obj->p[0];
1598                         Vector v2 = point - obj->p[1];
1599                         double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
1600                         double distance;
1601
1602                         if (t < 0.0)
1603                                 distance = v1.Magnitude();
1604                         else if (t > 1.0)
1605                                 distance = v2.Magnitude();
1606                         else
1607                                 // distance = ?Det?(ls, v1) / |ls|
1608                                 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
1609                                         / lineSegment.Magnitude());
1610
1611                         if ((v1.Magnitude() * Global::zoom) < 8.0)
1612                                 obj->hitPoint[0] = true;
1613                         else if ((v2.Magnitude() * Global::zoom) < 8.0)
1614                                 obj->hitPoint[1] = true;
1615                         else if ((distance * Global::zoom) < 5.0)
1616                                 obj->hitObject = true;
1617
1618                         obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
1619
1620                         if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
1621                                 needUpdate = true;
1622
1623                         break;
1624                 }
1625                 case OTCircle:
1626                 {
1627                         bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
1628                         obj->hitPoint[0] = obj->hitObject = false;
1629                         double length = Vector::Magnitude(obj->p[0], point);
1630
1631                         if ((length * Global::zoom) < 8.0)
1632                                 obj->hitPoint[0] = true;
1633                         else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
1634                                 obj->hitObject = true;
1635
1636                         obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
1637
1638                         if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
1639                                 needUpdate = true;
1640
1641                         break;
1642                 }
1643                 case OTArc:
1644                 {
1645                         bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
1646                         obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
1647                         double length = Vector::Magnitude(obj->p[0], point);
1648                         double angle = Vector::Angle(obj->p[0], point);
1649
1650                         // Make sure we get the angle in the correct spot
1651                         if (angle < obj->angle[0])
1652                                 angle += PI_TIMES_2;
1653
1654                         // Get the span that we're pointing at...
1655                         double span = angle - obj->angle[0];
1656
1657                         // N.B.: Still need to hit test the arc start & arc span handles...
1658                         double spanAngle = obj->angle[0] + obj->angle[1];
1659                         Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
1660                         Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
1661                         double length2 = Vector::Magnitude(point, handle1);
1662                         double length3 = Vector::Magnitude(point, handle2);
1663
1664                         if ((length * Global::zoom) < 8.0)
1665                                 obj->hitPoint[0] = true;
1666                         else if ((length2 * Global::zoom) < 8.0)
1667                                 obj->hitPoint[1] = true;
1668                         else if ((length3 * Global::zoom) < 8.0)
1669                                 obj->hitPoint[2] = true;
1670                         else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
1671                                 obj->hitObject = true;
1672
1673                         obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
1674
1675                         if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
1676                                 needUpdate = true;
1677
1678                         break;
1679                 }
1680                 default:
1681                         break;
1682                 }
1683
1684                 if (obj->hovered)
1685 //              {
1686                         numHovered++;
1687 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
1688 //              }
1689         }
1690
1691         return needUpdate;
1692 }
1693
1694
1695 void DrawingView::HandleObjectMovement(Point point)
1696 {
1697         Point delta = point - oldPoint;
1698 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
1699         Object * obj = (Object *)hover[0];
1700 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
1701 //printf("Object (%X) move: hp1=%s, hp2=%s, hl=%s\n", obj, (obj->hitPoint[0] ? "true" : "false"), (obj->hitPoint[1] ? "true" : "false"), (obj->hitObject ? "true" : "false"));
1702
1703         switch (obj->type)
1704         {
1705         case OTLine:
1706                 if (obj->hitPoint[0])
1707                         obj->p[0] = point;
1708                 else if (obj->hitPoint[1])
1709                         obj->p[1] = point;
1710                 else if (obj->hitObject)
1711                 {
1712                         obj->p[0] += delta;
1713                         obj->p[1] += delta;
1714                 }
1715
1716                 break;
1717         case OTCircle:
1718                 if (obj->hitPoint[0])
1719                         obj->p[0] = point;
1720                 else if (obj->hitObject)
1721                 {
1722 //this doesn't work. we need to save this on mouse down for this to work correctly!
1723 //                      double oldRadius = obj->radius[0];
1724                         obj->radius[0] = Vector::Magnitude(obj->p[0], point);
1725
1726                         QString text = QObject::tr("Radius: %1");//\nScale: %2%");
1727                         informativeText = text.arg(obj->radius[0], 0, 'd', 4);//.arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0);
1728                 }
1729
1730                 break;
1731         case OTArc:
1732                 if (obj->hitPoint[0])
1733                         obj->p[0] = point;
1734                 else if (obj->hitPoint[1])
1735                 {
1736                         // Change the Arc's span (handle #1)
1737                         if (shiftDown)
1738                         {
1739                                 double angle = Vector::Angle(obj->p[0], point);
1740                                 double delta = angle - obj->angle[0];
1741
1742                                 if (delta < 0)
1743                                         delta += PI_TIMES_2;
1744
1745                                 obj->angle[1] -= delta;
1746                                 obj->angle[0] = angle;
1747
1748                                 if (obj->angle[1] < 0)
1749                                         obj->angle[1] += PI_TIMES_2;
1750
1751                                 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
1752                                 informativeText = text.arg(obj->angle[1] * RADIANS_TO_DEGREES, 0, 'd', 4).arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 2).arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 2);
1753                                 return;
1754                         }
1755
1756                         double angle = Vector::Angle(obj->p[0], point);
1757                         obj->angle[0] = angle;
1758                         QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
1759                         informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 4);
1760                 }
1761                 else if (obj->hitPoint[2])
1762                 {
1763                         // Change the Arc's span (handle #2)
1764                         if (shiftDown)
1765                         {
1766                                 double angle = Vector::Angle(obj->p[0], point);
1767                                 obj->angle[1] = angle - obj->angle[0];
1768
1769                                 if (obj->angle[1] < 0)
1770                                         obj->angle[1] += PI_TIMES_2;
1771
1772                                 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
1773                                 informativeText = text.arg(obj->angle[1] * RADIANS_TO_DEGREES, 0, 'd', 4).arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 2).arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 2);
1774                                 return;
1775                         }
1776
1777                         double angle = Vector::Angle(obj->p[0], point);
1778                         obj->angle[0] = angle - obj->angle[1];
1779
1780                         if (obj->angle[0] < 0)
1781                                 obj->angle[0] += PI_TIMES_2;
1782
1783                         QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
1784                         informativeText = text.arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 4);
1785                 }
1786                 else if (obj->hitObject)
1787                 {
1788                         if (shiftDown)
1789                         {
1790                                 return;
1791                         }
1792
1793                         obj->radius[0] = Vector::Magnitude(obj->p[0], point);
1794                         QString text = QObject::tr("Radius: %1");
1795                         informativeText = text.arg(obj->radius[0], 0, 'd', 4);
1796                 }
1797
1798                 break;
1799         default:
1800                 break;
1801         }
1802 }
1803
1804
1805
1806 #if 0
1807         // This returns true if we've moved over an object...
1808         if (document.PointerMoved(point)) // <-- This
1809         // This is where the object would do automagic dragging & shit. Since we don't
1810         // do that anymore, we need a strategy to handle it.
1811         {
1812
1813 /*
1814 Now objects handle mouse move snapping as well. The code below mainly works only
1815 for tools; we need to fix it so that objects work as well...
1816
1817 There's a problem with the object point snapping in that it's dependent on the
1818 order of the objects in the document. Most likely this is because it counts the
1819 selected object last and thus fucks up the algorithm. Need to fix this...
1820
1821
1822 */
1823                 // Do object snapping here. Grid snapping on mouse down is done in the
1824                 // objects themselves, only because we have to hit test the raw point,
1825                 // not the snapped point. There has to be a better way...!
1826                 if (document.penultimateObjectHovered)
1827                 {
1828                         // Two objects are hovered, see if we have an intersection point
1829                         if ((document.lastObjectHovered->type == OTLine) && (document.penultimateObjectHovered->type == OTLine))
1830                         {
1831                                 double t;
1832                                 int n = Geometry::Intersects((Line *)document.lastObjectHovered, (Line *)document.penultimateObjectHovered, &t);
1833
1834                                 if (n == 1)
1835                                 {
1836                                         Global::snapPoint = document.lastObjectHovered->GetPointAtParameter(t);
1837                                         Global::snapPointIsValid = true;
1838                                 }
1839                         }
1840                         else if ((document.lastObjectHovered->type == OTCircle) && (document.penultimateObjectHovered->type == OTCircle))
1841                         {
1842                                 Point p1, p2;
1843                                 int n = Geometry::Intersects((Circle *)document.lastObjectHovered, (Circle *)document.penultimateObjectHovered, 0, 0, 0, 0, &p1, &p2);
1844
1845                                 if (n == 1)
1846                                 {
1847                                         Global::snapPoint = p1;
1848                                         Global::snapPointIsValid = true;
1849                                 }
1850                                 else if (n == 2)
1851                                 {
1852                                         double d1 = Vector(point, p1).Magnitude();
1853                                         double d2 = Vector(point, p2).Magnitude();
1854
1855                                         if (d1 < d2)
1856                                                 Global::snapPoint = p1;
1857                                         else
1858                                                 Global::snapPoint = p2;
1859
1860                                         Global::snapPointIsValid = true;
1861                                 }
1862                         }
1863                 }
1864 //              else
1865 //              {
1866                         // Otherwise, it was a single object hovered...
1867 //              }
1868         }
1869
1870         if (toolAction)
1871         {
1872                 if (Global::snapToGrid)
1873                         point = Global::SnapPointToGrid(point);
1874
1875                 // We always snap to object points, and they take precendence over
1876                 // grid points...
1877                 if (Global::snapPointIsValid)
1878                         point = Global::snapPoint;
1879
1880                 toolAction->MouseMoved(point);
1881         }
1882 #else
1883 #endif
1884