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