]> Shamusworld >> Repos - architektonas/blob - src/drawingview.cpp
8c581bfc1df8e904ccb41bd2b724b26b3c3ae7d5
[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),// document(Vector(0, 0)),
53         gridPixels(0), collided(false)//, toolAction(NULL)
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 (!informativeText.isEmpty())
328                 painter.DrawInformativeText(informativeText);
329 }
330
331
332 //
333 // Renders objects in the passed in vector
334 //
335 void DrawingView::RenderObjects(Painter * painter, std::vector<void *> & v)
336 {
337         std::vector<void *>::iterator i;
338
339         for(i=v.begin(); i!=v.end(); i++)
340         {
341                 Object * obj = (Object *)(*i);
342                 float scaledThickness = Global::scale * obj->thickness;
343
344                 if ((Global::tool == TTRotate) && ctrlDown && obj->selected)
345                 {
346                         painter->SetPen(0x00FF00, 2.0, LSSolid);
347                 }
348                 else
349                 {
350                         painter->SetPen(obj->color, Global::zoom * scaledThickness, obj->style);
351                         painter->SetBrush(obj->color);
352
353                         if (obj->selected || obj->hitObject)
354                                 painter->SetPen(0xFF0000, Global::zoom * scaledThickness, LSDash);
355                 }
356
357                 switch (obj->type)
358                 {
359                 case OTLine:
360                         painter->DrawLine(obj->p[0], obj->p[1]);
361
362                         if (obj->hitPoint[0])
363                                 painter->DrawHandle(obj->p[0]);
364
365                         if (obj->hitPoint[1])
366                                 painter->DrawHandle(obj->p[1]);
367
368                         break;
369                 case OTCircle:
370                         painter->SetBrush(QBrush(Qt::NoBrush));
371                         painter->DrawEllipse(obj->p[0], obj->radius[0], obj->radius[0]);
372
373                         if (obj->hitPoint[0])
374                                 painter->DrawHandle(obj->p[0]);
375
376                         break;
377                 case OTArc:
378                         painter->DrawArc(obj->p[0], obj->radius[0], obj->angle[0], obj->angle[1]);
379
380                         if (obj->hitPoint[0])
381                                 painter->DrawHandle(obj->p[0]);
382
383                         if (obj->hitPoint[1])
384                                 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]));
385
386                         if (obj->hitPoint[2])
387                                 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0] + obj->angle[1]), sin(obj->angle[0] + obj->angle[1])) * obj->radius[0]));
388
389                         break;
390                 case OTDimension:
391                 {
392                         Dimension * d = (Dimension *)obj;
393
394                         Vector v(d->p[0], d->p[1]);
395                         double angle = v.Angle();
396                         Vector unit = v.Unit();
397                         Vector linePt1 = d->p[0], linePt2 = d->p[1];
398                         Vector ortho;
399                         double x1, y1, length;
400
401                         if (d->subtype == DTLinearVert)
402                         {
403                                 if ((angle < 0) || (angle > PI))
404                                 {
405                                         x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
406                                         y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
407                                         ortho = Vector(1.0, 0);
408                                         angle = PI3_OVER_2;
409                                 }
410                                 else
411                                 {
412                                         x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
413                                         y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
414                                         ortho = Vector(-1.0, 0);
415                                         angle = PI_OVER_2;
416                                 }
417
418                                 linePt1.x = linePt2.x = x1;
419                                 length = fabs(d->p[0].y - d->p[1].y);
420                         }
421                         else if (d->subtype == DTLinearHorz)
422                         {
423                                 if ((angle < PI_OVER_2) || (angle > PI3_OVER_2))
424                                 {
425                                         x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
426                                         y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
427                                         ortho = Vector(0, 1.0);
428                                         angle = 0;
429                                 }
430                                 else
431                                 {
432                                         x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
433                                         y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
434                                         ortho = Vector(0, -1.0);
435                                         angle = PI;
436                                 }
437
438                                 linePt1.y = linePt2.y = y1;
439                                 length = fabs(d->p[0].x - d->p[1].x);
440                         }
441                         else if (d->subtype == DTLinear)
442                         {
443                                 angle = Vector(linePt1, linePt2).Angle();
444                                 ortho = Vector::Normal(linePt1, linePt2);
445                                 length = v.Magnitude();
446                         }
447
448                         unit = Vector(linePt1, linePt2).Unit();
449
450                         Point p1 = linePt1 + (ortho * 10.0 * scaledThickness);
451                         Point p2 = linePt2 + (ortho * 10.0 * scaledThickness);
452                         Point p3 = linePt1 + (ortho * 16.0 * scaledThickness);
453                         Point p4 = linePt2 + (ortho * 16.0 * scaledThickness);
454                         Point p5 = d->p[0] + (ortho * 4.0 * scaledThickness);
455                         Point p6 = d->p[1] + (ortho * 4.0 * scaledThickness);
456
457                 /*
458                 The numbers hardcoded into here, what are they?
459                 I believe they are pixels.
460                 */
461                         // Draw extension lines (if certain type)
462                         painter->DrawLine(p3, p5);
463                         painter->DrawLine(p4, p6);
464
465                         // Calculate whether or not the arrowheads are too crowded to put inside
466                         // the extension lines. 9.0 is the length of the arrowhead.
467                         double t = Geometry::ParameterOfLineAndPoint(linePt1, linePt2, linePt2 - (unit * 9.0 * scaledThickness));
468                 //printf("Dimension::Draw(): t = %lf\n", t);
469
470                         // On the screen, it's acting like this is actually 58%...
471                         // This is correct, we want it to happen at > 50%
472                         if (t > 0.58)
473                         {
474                                 // Draw main dimension line + arrowheads
475                                 painter->DrawLine(p1, p2);
476                                 painter->DrawArrowhead(p1, p2, scaledThickness);
477                                 painter->DrawArrowhead(p2, p1, scaledThickness);
478                         }
479                         else
480                         {
481                                 // Draw outside arrowheads
482                                 Point p7 = p1 - (unit * 9.0 * scaledThickness);
483                                 Point p8 = p2 + (unit * 9.0 * scaledThickness);
484                                 painter->DrawArrowhead(p1, p7, scaledThickness);
485                                 painter->DrawArrowhead(p2, p8, scaledThickness);
486                                 painter->DrawLine(p1, p1 - (unit * 14.0 * scaledThickness));
487                                 painter->DrawLine(p2, p2 + (unit * 14.0 * scaledThickness));
488                         }
489
490                         // Draw length of dimension line...
491                         painter->SetFont(QFont("Arial", 8.0 * Global::zoom * scaledThickness));
492                         Point ctr = p2 + (Vector(p2, p1) / 2.0);
493
494                 #if 0
495                         QString dimText = QString("%1\"").arg(Vector(endpoint - position).Magnitude());
496                 #else
497                         QString dimText;
498
499                         if (length < 12.0)
500                                 dimText = QString("%1\"").arg(length);
501                         else
502                         {
503                                 double feet = (double)((int)length / 12);
504                                 double inches = length - (feet * 12.0);
505
506                                 if (inches == 0)
507                                         dimText = QString("%1'").arg(feet);
508                                 else
509                                         dimText = QString("%1' %2\"").arg(feet).arg(inches);
510                         }
511                 #endif
512
513                         painter->DrawAngledText(ctr, angle, dimText, scaledThickness);
514
515                         break;
516                 }
517                 case OTText:
518                 {
519                         Text * t = (Text *)obj;
520                         painter->DrawTextObject(t->p[0], t->s.c_str(), scaledThickness);
521                         break;
522                 }
523                 default:
524                         break;
525                 }
526         }
527 }
528
529
530 void DrawingView::AddHoveredToSelection(void)
531 {
532         std::vector<void *>::iterator i;
533
534         for(i=document.objects.begin(); i!=document.objects.end(); i++)
535         {
536                 if (((Object *)(*i))->hovered)
537                         ((Object *)(*i))->selected = true;
538         }
539 }
540
541
542 void DrawingView::GetSelection(std::vector<void *> & v)
543 {
544         v.clear();
545         std::vector<void *>::iterator i;
546
547         for(i=document.objects.begin(); i!=document.objects.end(); i++)
548         {
549                 if (((Object *)(*i))->selected)
550                         v.push_back(*i);
551         }
552 }
553
554
555 void DrawingView::GetHovered(std::vector<void *> & v)
556 {
557         v.clear();
558         std::vector<void *>::iterator i;
559
560         for(i=document.objects.begin(); i!=document.objects.end(); i++)
561         {
562                 if (((Object *)(*i))->hovered)
563 //              {
564 //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"));
565                         v.push_back(*i);
566 //              }
567         }
568 }
569
570
571 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
572 {
573         Global::screenSize = Vector(size().width(), size().height());
574         UpdateGridBackground();
575 }
576
577
578 void DrawingView::ToolHandler(int mode, Point p)
579 {
580         if (Global::tool == TTLine)
581                 LineHandler(mode, p);
582         else if (Global::tool == TTCircle)
583                 CircleHandler(mode, p);
584         else if (Global::tool == TTArc)
585                 ArcHandler(mode, p);
586         else if (Global::tool == TTRotate)
587                 RotateHandler(mode, p);
588         else if (Global::tool == TTMirror)
589                 MirrorHandler(mode, p);
590 }
591
592
593 void DrawingView::ToolDraw(Painter * painter)
594 {
595         if (Global::tool == TTLine)
596         {
597                 if (Global::toolState == TSNone)
598                 {
599                         painter->DrawHandle(toolPoint[0]);
600                 }
601                 else if ((Global::toolState == TSPoint2) && shiftDown)
602                 {
603                         painter->DrawHandle(toolPoint[1]);
604                 }
605                 else
606                 {
607                         painter->DrawLine(toolPoint[0], toolPoint[1]);
608                         painter->DrawHandle(toolPoint[1]);
609
610                         Vector v(toolPoint[0], toolPoint[1]);
611                         double absAngle = v.Angle() * RADIANS_TO_DEGREES;
612                         double absLength = v.Magnitude();
613                         QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
614                         informativeText = text.arg(absLength).arg(absAngle);
615                 }
616         }
617         else if (Global::tool == TTCircle)
618         {
619                 if (Global::toolState == TSNone)
620                 {
621                         painter->DrawHandle(toolPoint[0]);
622                 }
623                 else if ((Global::toolState == TSPoint2) && shiftDown)
624                 {
625                         painter->DrawHandle(toolPoint[1]);
626                 }
627                 else
628                 {
629                         double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
630 //                      painter->DrawLine(toolPoint[0], toolPoint[1]);
631 //                      painter->DrawHandle(toolPoint[1]);
632                         painter->SetBrush(QBrush(Qt::NoBrush));
633                         painter->DrawEllipse(toolPoint[0], length, length);
634                         QString text = tr("Radius: %1 in.");//\n") + QChar(0x2221) + tr(": %2");
635                         informativeText = text.arg(length);//.arg(absAngle);
636                 }
637         }
638         else if (Global::tool == TTArc)
639         {
640                 if (Global::toolState == TSNone)
641                 {
642                         painter->DrawHandle(toolPoint[0]);
643                 }
644                 else if (Global::toolState == TSPoint2)
645                 {
646                         double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
647                         painter->SetBrush(QBrush(Qt::NoBrush));
648                         painter->DrawEllipse(toolPoint[0], length, length);
649                         painter->DrawLine(toolPoint[0], toolPoint[1]);
650                         painter->DrawHandle(toolPoint[1]);
651                         QString text = tr("Radius: %1 in.");
652                         informativeText = text.arg(length);
653                 }
654                 else if (Global::toolState == TSPoint3)
655                 {
656                         double angle = Vector::Angle(toolPoint[0], toolPoint[2]);
657                         painter->DrawLine(toolPoint[0], toolPoint[2]);
658                         painter->SetBrush(QBrush(Qt::NoBrush));
659                         painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
660                         painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
661                         QString text = tr("Angle start: %1") + QChar(0x00B0);
662                         informativeText = text.arg(RADIANS_TO_DEGREES * angle);
663                 }
664                 else
665                 {
666                         double angle = Vector::Angle(toolPoint[0], toolPoint[3]);
667                         double span = angle - toolPoint[2].x;
668
669                         if (span < 0)
670                                 span += PI_TIMES_2;
671
672                         painter->DrawLine(toolPoint[0], toolPoint[3]);
673                         painter->SetBrush(QBrush(Qt::NoBrush));
674                         painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
675                         painter->SetPen(0xFF00FF, 2.0, LSSolid);
676                         painter->DrawArc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
677                         painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
678                         QString text = tr("Arc span: %1") + QChar(0x00B0);
679                         informativeText = text.arg(RADIANS_TO_DEGREES * span);
680                 }
681         }
682         else if (Global::tool == TTRotate)
683         {
684                 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
685                         painter->DrawHandle(toolPoint[0]);
686                 else if ((Global::toolState == TSPoint2) && shiftDown)
687                         painter->DrawHandle(toolPoint[1]);
688                 else
689                 {
690                         if (toolPoint[0] == toolPoint[1])
691                                 return;
692
693                         painter->DrawLine(toolPoint[0], toolPoint[1]);
694                         // Likely we need a tool container for this... (now we do!)
695 #if 0
696                         if (ctrlDown)
697                         {
698                                 painter->SetPen(0x00FF00, 2.0, LSSolid);
699                                 overrideColor = true;
700                         }
701
702                         RenderObjects(painter, toolObjects);
703                         overrideColor = false;
704 #endif
705
706                         double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
707                         QString text = QChar(0x2221) + QObject::tr(": %1");
708                         informativeText = text.arg(absAngle);
709
710                         if (ctrlDown)
711                                 informativeText += " (Copy)";
712                 }
713         }
714         else if (Global::tool == TTMirror)
715         {
716                 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
717                         painter->DrawHandle(toolPoint[0]);
718                 else if ((Global::toolState == TSPoint2) && shiftDown)
719                         painter->DrawHandle(toolPoint[1]);
720                 else
721                 {
722                         if (toolPoint[0] == toolPoint[1])
723                                 return;
724                         
725                         Point mirrorPoint = toolPoint[0] + Vector(toolPoint[1], toolPoint[0]);
726 //                      painter->DrawLine(toolPoint[0], toolPoint[1]);
727                         painter->DrawLine(mirrorPoint, toolPoint[1]);
728                         // Likely we need a tool container for this... (now we do!)
729 #if 0
730                         if (ctrlDown)
731                         {
732                                 painter->SetPen(0x00FF00, 2.0, LSSolid);
733                                 overrideColor = true;
734                         }
735
736                         RenderObjects(painter, toolObjects);
737                         overrideColor = false;
738 #endif
739
740                         double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
741
742                         if (absAngle > 180.0)
743                                 absAngle -= 180.0;
744
745                         QString text = QChar(0x2221) + QObject::tr(": %1");
746                         informativeText = text.arg(absAngle);
747
748                         if (ctrlDown)
749                                 informativeText += " (Copy)";
750                 }
751         }
752 }
753
754
755 void DrawingView::LineHandler(int mode, Point p)
756 {
757         switch (mode)
758         {
759         case ToolMouseDown:
760                 if (Global::toolState == TSNone)
761                         toolPoint[0] = p;
762                 else
763                         toolPoint[1] = p;
764
765                 break;
766         case ToolMouseMove:
767                 if (Global::toolState == TSNone)
768                         toolPoint[0] = p;
769                 else
770                         toolPoint[1] = p;
771
772                 break;
773         case ToolMouseUp:
774                 if (Global::toolState == TSNone)
775                 {
776                         Global::toolState = TSPoint2;
777                         // Prevent spurious line from drawing...
778                         toolPoint[1] = toolPoint[0];
779                 }
780                 else if ((Global::toolState == TSPoint2) && shiftDown)
781                 {
782                         // Key override is telling us to make a new line, not continue the
783                         // previous one.
784                         toolPoint[0] = toolPoint[1];
785                 }
786                 else
787                 {
788                         Line * l = new Line(toolPoint[0], toolPoint[1]);
789                         document.objects.push_back(l);
790                         toolPoint[0] = toolPoint[1];
791                 }
792         }
793 }
794
795
796 void DrawingView::CircleHandler(int mode, Point p)
797 {
798         switch (mode)
799         {
800         case ToolMouseDown:
801                 if (Global::toolState == TSNone)
802                         toolPoint[0] = p;
803                 else
804                         toolPoint[1] = p;
805
806                 break;
807         case ToolMouseMove:
808                 if (Global::toolState == TSNone)
809                         toolPoint[0] = p;
810                 else
811                         toolPoint[1] = p;
812
813                 break;
814         case ToolMouseUp:
815                 if (Global::toolState == TSNone)
816                 {
817                         Global::toolState = TSPoint2;
818                         // Prevent spurious line from drawing...
819                         toolPoint[1] = toolPoint[0];
820                 }
821                 else if ((Global::toolState == TSPoint2) && shiftDown)
822                 {
823                         // Key override is telling us to make a new line, not continue the
824                         // previous one.
825                         toolPoint[0] = toolPoint[1];
826                 }
827                 else
828                 {
829                         double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
830                         Circle * c = new Circle(toolPoint[0], length);
831                         document.objects.push_back(c);
832                         toolPoint[0] = toolPoint[1];
833                         Global::toolState = TSNone;
834                 }
835         }
836 }
837
838
839 void DrawingView::ArcHandler(int mode, Point p)
840 {
841         switch (mode)
842         {
843         case ToolMouseDown:
844                 if (Global::toolState == TSNone)
845                         toolPoint[0] = p;
846                 else if (Global::toolState == TSPoint2)
847                         toolPoint[1] = p;
848                 else if (Global::toolState == TSPoint3)
849                         toolPoint[2] = p;
850                 else
851                         toolPoint[3] = p;
852
853                 break;
854         case ToolMouseMove:
855                 if (Global::toolState == TSNone)
856                         toolPoint[0] = p;
857                 else if (Global::toolState == TSPoint2)
858                         toolPoint[1] = p;
859                 else if (Global::toolState == TSPoint3)
860                         toolPoint[2] = p;
861                 else
862                         toolPoint[3] = p;
863
864                 break;
865         case ToolMouseUp:
866                 if (Global::toolState == TSNone)
867                 {
868                         // Prevent spurious line from drawing...
869                         toolPoint[1] = toolPoint[0];
870                         Global::toolState = TSPoint2;
871                 }
872                 else if (Global::toolState == TSPoint2)
873                 {
874                         if (shiftDown)
875                         {
876                                 // Key override is telling us to start circle at new center, not
877                                 // continue the current one.
878                                 toolPoint[0] = toolPoint[1];
879                                 return;
880                         }
881
882                         // Set the radius in toolPoint[1].x
883                         toolPoint[1].x = Vector::Magnitude(toolPoint[0], toolPoint[1]);
884                         Global::toolState = TSPoint3;
885                 }
886                 else if (Global::toolState == TSPoint3)
887                 {
888                         // Set the angle in toolPoint[2].x
889                         toolPoint[2].x = Vector::Angle(toolPoint[0], toolPoint[2]);
890                         Global::toolState = TSPoint4;
891                 }
892                 else
893                 {
894                         double endAngle = Vector::Angle(toolPoint[0], toolPoint[3]);
895                         double span = endAngle - toolPoint[2].x;
896
897                         if (span < 0)
898                                 span += PI_TIMES_2;
899
900                         Arc * arc = new Arc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
901                         document.objects.push_back(arc);
902                         Global::toolState = TSNone;
903                 }
904         }
905 }
906
907
908 void DrawingView::RotateHandler(int mode, Point p)
909 {
910         switch (mode)
911         {
912         case ToolMouseDown:
913                 if (Global::toolState == TSNone)
914                 {
915                         toolPoint[0] = p;
916                         SavePointsFrom(select, toolScratch);
917                         Global::toolState = TSPoint1;
918                 }
919                 else if (Global::toolState == TSPoint1)
920                         toolPoint[0] = p;
921                 else
922                         toolPoint[1] = p;
923
924                 break;
925         case ToolMouseMove:
926                 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
927                         toolPoint[0] = p;
928                 else if (Global::toolState == TSPoint2)
929                 {
930                         toolPoint[1] = p;
931
932                         if (shiftDown)
933                                 return;
934
935                         double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
936                         std::vector<void *>::iterator j = select.begin();
937                         std::vector<Object>::iterator i = toolScratch.begin();
938
939                         for(; i!=toolScratch.end(); i++, j++)
940                         {
941                                 Object obj = *i;
942                                 Point p1 = Geometry::RotatePointAroundPoint(obj.p[0], toolPoint[0], angle);
943                                 Point p2 = Geometry::RotatePointAroundPoint(obj.p[1], toolPoint[0], angle);
944                                 Object * obj2 = (Object *)(*j);
945                                 obj2->p[0] = p1;
946                                 obj2->p[1] = p2;
947
948                                 if (obj.type == OTArc)
949                                 {
950                                         obj2->angle[0] = obj.angle[0] + angle;
951
952                                         if (obj2->angle[0] > PI_TIMES_2)
953                                                 obj2->angle[0] -= PI_TIMES_2;
954                                 }
955                         }
956                 }
957
958                 break;
959         case ToolMouseUp:
960                 if (Global::toolState == TSPoint1)
961                 {
962                         Global::toolState = TSPoint2;
963                         // Prevent spurious line from drawing...
964                         toolPoint[1] = toolPoint[0];
965                 }
966                 else if ((Global::toolState == TSPoint2) && shiftDown)
967                 {
968                         // Key override is telling us to make a new line, not continue the
969                         // previous one.
970                         toolPoint[0] = toolPoint[1];
971                 }
972                 else
973                 {
974                         // Either we're finished with our rotate, or we're stamping a copy.
975                         if (ctrlDown)
976                         {
977                                 // Stamp a copy of the selection at the current rotation & bail
978                                 std::vector<void *> temp;
979                                 CopyObjects(select, temp);
980                                 ClearSelected(temp);
981                                 AddObjectsTo(document.objects, temp);
982                                 RestorePointsTo(select, toolScratch);
983                                 return;
984                         }
985
986                         toolPoint[0] = p;
987                         Global::toolState = TSPoint1;
988                         SavePointsFrom(select, toolScratch);
989                 }
990
991                 break;
992         case ToolKeyDown:
993                 // Reset the selection if shift held down...
994                 if (shiftDown)
995                         RestorePointsTo(select, toolScratch);
996
997                 break;
998         case ToolKeyUp:
999                 // Reset selection when key is let up
1000                 if (!shiftDown)
1001                         RotateHandler(ToolMouseMove, toolPoint[1]);
1002
1003                 break;
1004         case ToolCleanup:
1005                 RestorePointsTo(select, toolScratch);
1006         }
1007 }
1008
1009
1010 void DrawingView::MirrorHandler(int mode, Point p)
1011 {
1012         switch (mode)
1013         {
1014         case ToolMouseDown:
1015                 if (Global::toolState == TSNone)
1016                 {
1017                         toolPoint[0] = p;
1018                         SavePointsFrom(select, toolScratch);
1019                         Global::toolState = TSPoint1;
1020                 }
1021                 else if (Global::toolState == TSPoint1)
1022                         toolPoint[0] = p;
1023                 else
1024                         toolPoint[1] = p;
1025
1026                 break;
1027         case ToolMouseMove:
1028                 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1029                         toolPoint[0] = p;
1030                 else if (Global::toolState == TSPoint2)
1031                 {
1032                         toolPoint[1] = p;
1033
1034                         if (shiftDown)
1035                                 return;
1036
1037                         double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1038                         std::vector<void *>::iterator j = select.begin();
1039                         std::vector<Object>::iterator i = toolScratch.begin();
1040
1041                         for(; i!=toolScratch.end(); i++, j++)
1042                         {
1043                                 Object obj = *i;
1044                                 Point p1 = Geometry::MirrorPointAroundLine(obj.p[0], toolPoint[0], toolPoint[1]);
1045                                 Point p2 = Geometry::MirrorPointAroundLine(obj.p[1], toolPoint[0], toolPoint[1]);
1046                                 Object * obj2 = (Object *)(*j);
1047                                 obj2->p[0] = p1;
1048                                 obj2->p[1] = p2;
1049
1050                                 if (obj.type == OTArc)
1051                                 {
1052                                         // This is 2*mirror angle - obj angle - obj span
1053                                         obj2->angle[0] = (2.0 * angle) - obj.angle[0] - obj.angle[1];
1054
1055                                         if (obj2->angle[0] > PI_TIMES_2)
1056                                                 obj2->angle[0] -= PI_TIMES_2;
1057                                 }
1058                         }
1059                 }
1060
1061                 break;
1062         case ToolMouseUp:
1063                 if (Global::toolState == TSPoint1)
1064                 {
1065                         Global::toolState = TSPoint2;
1066                         // Prevent spurious line from drawing...
1067                         toolPoint[1] = toolPoint[0];
1068                 }
1069                 else if ((Global::toolState == TSPoint2) && shiftDown)
1070                 {
1071                         // Key override is telling us to make a new line, not continue the
1072                         // previous one.
1073                         toolPoint[0] = toolPoint[1];
1074                 }
1075                 else
1076                 {
1077                         // Either we're finished with our rotate, or we're stamping a copy.
1078                         if (ctrlDown)
1079                         {
1080                                 // Stamp a copy of the selection at the current rotation & bail
1081                                 std::vector<void *> temp;
1082                                 CopyObjects(select, temp);
1083                                 ClearSelected(temp);
1084                                 AddObjectsTo(document.objects, temp);
1085                                 RestorePointsTo(select, toolScratch);
1086                                 return;
1087                         }
1088
1089                         toolPoint[0] = p;
1090                         Global::toolState = TSPoint1;
1091                         SavePointsFrom(select, toolScratch);
1092                 }
1093
1094                 break;
1095         case ToolKeyDown:
1096                 // Reset the selection if shift held down...
1097                 if (shiftDown)
1098                         RestorePointsTo(select, toolScratch);
1099
1100                 break;
1101         case ToolKeyUp:
1102                 // Reset selection when key is let up
1103                 if (!shiftDown)
1104                         MirrorHandler(ToolMouseMove, toolPoint[1]);
1105
1106                 break;
1107         case ToolCleanup:
1108                 RestorePointsTo(select, toolScratch);
1109         }
1110 }
1111
1112
1113 void DrawingView::mousePressEvent(QMouseEvent * event)
1114 {
1115         if (event->button() == Qt::LeftButton)
1116         {
1117                 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1118
1119                 // Handle tool processing, if any
1120                 if (Global::tool)
1121                 {
1122                         if (Global::snapToGrid)
1123                                 point = SnapPointToGrid(point);
1124
1125                         //Also, may want to figure out if hovering over a snap point on an object,
1126                         //snap to grid if not.
1127                         // Snap to object point if valid...
1128 //                      if (Global::snapPointIsValid)
1129 //                              point = Global::snapPoint;
1130                         
1131                         ToolHandler(ToolMouseDown, point);
1132                         return;
1133                 }
1134
1135                 // Clear the selection only if CTRL isn't being held on click
1136                 if (!ctrlDown)
1137                         ClearSelected(document.objects);
1138 //                      ClearSelection();
1139
1140                 // If any objects are being hovered on click, add them to the selection
1141                 // & return
1142                 if (numHovered > 0)
1143                 {
1144                         AddHoveredToSelection();
1145                         update();       // needed??
1146                         GetHovered(hover);      // prolly needed
1147
1148                         // Needed for grab & moving objects
1149                         // We do it *after*... why? (doesn't seem to confer any advantage...)
1150                         if (Global::snapToGrid)
1151                                 oldPoint = SnapPointToGrid(point);
1152
1153                         return;
1154                 }
1155
1156                 // Didn't hit any object and not using a tool, so do a selection rectangle
1157                 Global::selectionInProgress = true;
1158                 Global::selection.setTopLeft(QPointF(point.x, point.y));
1159                 Global::selection.setBottomRight(QPointF(point.x, point.y));
1160         }
1161         else if (event->button() == Qt::MiddleButton)
1162         {
1163                 scrollDrag = true;
1164                 oldPoint = Vector(event->x(), event->y());
1165                 // Should also change the mouse pointer as well...
1166                 setCursor(Qt::SizeAllCursor);
1167         }
1168 }
1169
1170
1171 void DrawingView::mouseMoveEvent(QMouseEvent * event)
1172 {
1173         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1174         Global::selection.setBottomRight(QPointF(point.x, point.y));
1175         // Only needs to be done here, as mouse down is always preceded by movement
1176         Global::snapPointIsValid = false;
1177
1178         // Scrolling...
1179         if (event->buttons() & Qt::MiddleButton)
1180         {
1181                 point = Vector(event->x(), event->y());
1182                 // Since we're using Qt coords for scrolling, we have to adjust them here to
1183                 // conform to Cartesian coords, since the origin is using Cartesian. :-)
1184                 Vector delta(oldPoint, point);
1185                 delta /= Global::zoom;
1186                 delta.y = -delta.y;
1187                 Global::origin -= delta;
1188
1189                 UpdateGridBackground();
1190                 update();
1191                 oldPoint = point;
1192                 return;
1193         }
1194
1195         // If we're doing a selection rect, see if any objects are engulfed by it
1196         // (implies left mouse button held down)
1197         if (Global::selectionInProgress)
1198         {
1199                 CheckObjectBounds();
1200                 update();
1201                 return;
1202         }
1203
1204         // Handle object movement (left button down & over an object)
1205         if ((event->buttons() & Qt::LeftButton) && numHovered && !Global::tool)
1206         {
1207                 if (Global::snapToGrid)
1208                         point = SnapPointToGrid(point);
1209
1210                 HandleObjectMovement(point);
1211                 update();
1212                 oldPoint = point;
1213                 return;
1214         }
1215
1216         // Do object hit testing...
1217         bool needUpdate = HitTestObjects(point);
1218
1219         // Check for multi-hover...
1220         if (numHovered > 1)
1221         {
1222                 GetHovered(hover);
1223
1224                 double t, u;
1225                 int numIntersecting = Geometry::Intersects((Object *)hover[0], (Object *)hover[1], &t, &u);
1226
1227                 if (numIntersecting > 0)
1228                 {
1229                         QString text = tr("Intersection t=%1, u=%2");
1230                         informativeText = text.arg(t).arg(u);
1231                 }
1232         }
1233
1234         // Do tool handling, if any are active...
1235         if (Global::tool)
1236         {
1237                 if (Global::snapToGrid)
1238                         point = SnapPointToGrid(point);
1239
1240                 ToolHandler(ToolMouseMove, point);
1241         }
1242
1243         // This is used to draw the tool crosshair...
1244         oldPoint = point;
1245
1246         if (needUpdate || Global::tool)
1247                 update();
1248 }
1249
1250
1251 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
1252 {
1253         if (event->button() == Qt::LeftButton)
1254         {
1255 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
1256 //could set it up to use the document's update function (assumes that all object updates
1257 //are being reported correctly:
1258 //              if (document.NeedsUpdate())
1259                 // Do an update if collided with at least *one* object in the document
1260 //              if (collided)
1261                         update();
1262
1263                 if (Global::tool)
1264                 {
1265                         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1266                         ToolHandler(ToolMouseUp, point);
1267                         return;
1268                 }
1269
1270                 if (Global::selectionInProgress)
1271                         Global::selectionInProgress = false;
1272
1273                 informativeText.clear();
1274 // Should we be doing this automagically? Hmm...
1275                 // Clear our vectors
1276                 select.clear();
1277                 hover.clear();
1278
1279                 // Scoop 'em up
1280                 std::vector<void *>::iterator i;
1281
1282                 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1283                 {
1284                         if (((Object *)(*i))->selected)
1285                                 select.push_back(*i);
1286
1287 //hmm, this is no good, too late to do any good :-P
1288 //                      if ((*i)->hovered)
1289 //                              hover.push_back(*i);
1290                 }
1291         }
1292         else if (event->button() == Qt::MiddleButton)
1293         {
1294                 scrollDrag = false;
1295                 setCursor(Qt::ArrowCursor);
1296         }
1297 }
1298
1299
1300 void DrawingView::wheelEvent(QWheelEvent * event)
1301 {
1302         double zoomFactor = 1.25;
1303         QSize sizeWin = size();
1304         Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
1305         center = Painter::QtToCartesianCoords(center);
1306
1307         // This is not centering for some reason. Need to figure out why. :-/
1308         if (event->delta() > 0)
1309         {
1310                 Vector newOrigin = center - ((center - Global::origin) / zoomFactor);
1311                 Global::origin = newOrigin;
1312                 Global::zoom *= zoomFactor;
1313         }
1314         else
1315         {
1316                 Vector newOrigin = center + ((-center + Global::origin) * zoomFactor);
1317                 Global::origin = newOrigin;
1318                 Global::zoom /= zoomFactor;
1319         }
1320
1321 //      Global::gridSpacing = gridPixels / Painter::zoom;
1322 //      UpdateGridBackground();
1323         SetGridSize(Global::gridSpacing * Global::zoom);
1324         update();
1325 //      zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Global::gridSpacing));
1326 }
1327
1328
1329 void DrawingView::keyPressEvent(QKeyEvent * event)
1330 {
1331         bool oldShift = shiftDown;
1332         bool oldCtrl = ctrlDown;
1333
1334         if (event->key() == Qt::Key_Shift)
1335                 shiftDown = true;
1336         else if (event->key() == Qt::Key_Control)
1337                 ctrlDown = true;
1338
1339         if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1340         {
1341                 if (Global::tool)
1342                         ToolHandler(ToolKeyDown, Point(0, 0));
1343
1344                 update();
1345         }
1346 }
1347
1348
1349 void DrawingView::keyReleaseEvent(QKeyEvent * event)
1350 {
1351         bool oldShift = shiftDown;
1352         bool oldCtrl = ctrlDown;
1353
1354         if (event->key() == Qt::Key_Shift)
1355                 shiftDown = false;
1356         else if (event->key() == Qt::Key_Control)
1357                 ctrlDown = false;
1358
1359         if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1360         {
1361                 if (Global::tool)
1362                         ToolHandler(ToolKeyUp, Point(0, 0));
1363
1364                 update();
1365         }
1366 }
1367
1368
1369 //
1370 // This looks strange, but it's really quite simple: We want a point that's
1371 // more than half-way to the next grid point to snap there while conversely we
1372 // want a point that's less than half-way to to the next grid point then snap
1373 // to the one before it. So we add half of the grid spacing to the point, then
1374 // divide by it so that we can remove the fractional part, then multiply it
1375 // back to get back to the correct answer.
1376 //
1377 Point DrawingView::SnapPointToGrid(Point point)
1378 {
1379         point += Global::gridSpacing / 2.0;             // *This* adds to Z!!!
1380         point /= Global::gridSpacing;
1381         point.x = floor(point.x);//need to fix this for negative numbers...
1382         point.y = floor(point.y);
1383         point.z = 0;                                    // Make *sure* Z doesn't go anywhere!!!
1384         point *= Global::gridSpacing;
1385         return point;
1386 }
1387
1388
1389 void DrawingView::CheckObjectBounds(void)
1390 {
1391         std::vector<void *>::iterator i;
1392         numSelected = 0;
1393
1394         for(i=document.objects.begin(); i!=document.objects.end(); i++)
1395         {
1396                 Object * obj = (Object *)(*i);
1397                 obj->selected = false;
1398
1399                 switch (obj->type)
1400                 {
1401                 case OTLine:
1402                 {
1403                         Line * l = (Line *)obj;
1404
1405                         if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
1406                                 l->selected = true;
1407
1408                         break;
1409                 }
1410                 case OTCircle:
1411                 {
1412                         Circle * c = (Circle *)obj;
1413
1414                         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]))
1415                                 c->selected = true;
1416
1417                         break;
1418                 }
1419                 case OTArc:
1420                 {
1421                         Arc * a = (Arc *)obj;
1422
1423                         double start = a->angle[0];
1424                         double end = start + a->angle[1];
1425                         QPointF p1(cos(start), sin(start));
1426                         QPointF p2(cos(end), sin(end));
1427                         QRectF bounds(p1, p2);
1428
1429 #if 1
1430                         // Swap X/Y coordinates if they're backwards...
1431                         if (bounds.left() > bounds.right())
1432                         {
1433                                 double temp = bounds.left();
1434                                 bounds.setLeft(bounds.right());
1435                                 bounds.setRight(temp);
1436                         }
1437
1438                         if (bounds.bottom() > bounds.top())
1439                         {
1440                                 double temp = bounds.bottom();
1441                                 bounds.setBottom(bounds.top());
1442                                 bounds.setTop(temp);
1443                         }
1444 #else
1445                         // Doesn't work as advertised! For shame!
1446                         bounds = bounds.normalized();
1447 #endif
1448
1449                         // If the end of the arc is before the beginning, add 360 degrees to it
1450                         if (end < start)
1451                                 end += 2.0 * PI;
1452
1453                         // Adjust the bounds depending on which axes are crossed
1454                         if ((start < PI_OVER_2) && (end > PI_OVER_2))
1455                                 bounds.setTop(1.0);
1456
1457                         if ((start < PI) && (end > PI))
1458                                 bounds.setLeft(-1.0);
1459
1460                         if ((start < (PI + PI_OVER_2)) && (end > (PI + PI_OVER_2)))
1461                                 bounds.setBottom(-1.0);
1462
1463                         if ((start < (2.0 * PI)) && (end > (2.0 * PI)))
1464                                 bounds.setRight(1.0);
1465
1466                         if ((start < ((2.0 * PI) + PI_OVER_2)) && (end > ((2.0 * PI) + PI_OVER_2)))
1467                                 bounds.setTop(1.0);
1468
1469                         if ((start < (3.0 * PI)) && (end > (3.0 * PI)))
1470                                 bounds.setLeft(-1.0);
1471
1472                         if ((start < ((3.0 * PI) + PI_OVER_2)) && (end > ((3.0 * PI) + PI_OVER_2)))
1473                                 bounds.setBottom(-1.0);
1474
1475                         bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
1476                         bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
1477                         bounds.translate(a->p[0].x, a->p[0].y);
1478
1479                         if (Global::selection.contains(bounds))
1480                                 a->selected = true;
1481
1482                         break;
1483                 }
1484                 default:
1485                         break;
1486                 }
1487
1488                 if (obj->selected)
1489                         numSelected++;
1490         }
1491 }
1492
1493
1494 bool DrawingView::HitTestObjects(Point point)
1495 {
1496         std::vector<void *>::iterator i;
1497         numHovered = 0;
1498         bool needUpdate = false;
1499
1500         for(i=document.objects.begin(); i!=document.objects.end(); i++)
1501         {
1502                 Object * obj = (Object *)(*i);
1503
1504                 switch (obj->type)
1505                 {
1506                 case OTLine:
1507                 {
1508                         bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
1509                         obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
1510                         Vector lineSegment = obj->p[1] - obj->p[0];
1511                         Vector v1 = point - obj->p[0];
1512                         Vector v2 = point - obj->p[1];
1513                         double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
1514                         double distance;
1515
1516                         if (t < 0.0)
1517                                 distance = v1.Magnitude();
1518                         else if (t > 1.0)
1519                                 distance = v2.Magnitude();
1520                         else
1521                                 // distance = ?Det?(ls, v1) / |ls|
1522                                 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
1523                                         / lineSegment.Magnitude());
1524
1525                         if ((v1.Magnitude() * Global::zoom) < 8.0)
1526                                 obj->hitPoint[0] = true;
1527                         else if ((v2.Magnitude() * Global::zoom) < 8.0)
1528                                 obj->hitPoint[1] = true;
1529                         else if ((distance * Global::zoom) < 5.0)
1530                                 obj->hitObject = true;
1531
1532                         obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
1533
1534                         if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
1535                                 needUpdate = true;
1536
1537                         break;
1538                 }
1539                 case OTCircle:
1540                 {
1541                         bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
1542                         obj->hitPoint[0] = obj->hitObject = false;
1543                         double length = Vector::Magnitude(obj->p[0], point);
1544
1545                         if ((length * Global::zoom) < 8.0)
1546                                 obj->hitPoint[0] = true;
1547                         else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
1548                                 obj->hitObject = true;
1549
1550                         obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
1551
1552                         if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
1553                                 needUpdate = true;
1554
1555                         break;
1556                 }
1557                 case OTArc:
1558                 {
1559                         bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
1560                         obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
1561                         double length = Vector::Magnitude(obj->p[0], point);
1562                         double angle = Vector::Angle(obj->p[0], point);
1563
1564                         // Make sure we get the angle in the correct spot
1565                         if (angle < obj->angle[0])
1566                                 angle += PI_TIMES_2;
1567
1568                         // Get the span that we're pointing at...
1569                         double span = angle - obj->angle[0];
1570
1571                         // N.B.: Still need to hit test the arc start & arc span handles...
1572                         double spanAngle = obj->angle[0] + obj->angle[1];
1573                         Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
1574                         Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
1575                         double length2 = Vector::Magnitude(point, handle1);
1576                         double length3 = Vector::Magnitude(point, handle2);
1577
1578                         if ((length * Global::zoom) < 8.0)
1579                                 obj->hitPoint[0] = true;
1580                         else if ((length2 * Global::zoom) < 8.0)
1581                                 obj->hitPoint[1] = true;
1582                         else if ((length3 * Global::zoom) < 8.0)
1583                                 obj->hitPoint[2] = true;
1584                         else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
1585                                 obj->hitObject = true;
1586
1587                         obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
1588
1589                         if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
1590                                 needUpdate = true;
1591
1592                         break;
1593                 }
1594                 default:
1595                         break;
1596                 }
1597
1598                 if (obj->hovered)
1599 //              {
1600                         numHovered++;
1601 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
1602 //              }
1603         }
1604
1605         return needUpdate;
1606 }
1607
1608
1609 void DrawingView::HandleObjectMovement(Point point)
1610 {
1611         Point delta = point - oldPoint;
1612 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
1613         Object * obj = (Object *)hover[0];
1614 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
1615 //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"));
1616
1617         switch (obj->type)
1618         {
1619         case OTLine:
1620                 if (obj->hitPoint[0])
1621                         obj->p[0] = point;
1622                 else if (obj->hitPoint[1])
1623                         obj->p[1] = point;
1624                 else if (obj->hitObject)
1625                 {
1626                         obj->p[0] += delta;
1627                         obj->p[1] += delta;
1628                 }
1629
1630                 break;
1631         case OTCircle:
1632                 if (obj->hitPoint[0])
1633                         obj->p[0] = point;
1634                 else if (obj->hitObject)
1635                 {
1636 //this doesn't work. we need to save this on mouse down for this to work correctly!
1637 //                      double oldRadius = obj->radius[0];
1638                         obj->radius[0] = Vector::Magnitude(obj->p[0], point);
1639
1640                         QString text = QObject::tr("Radius: %1");//\nScale: %2%");
1641                         informativeText = text.arg(obj->radius[0], 0, 'd', 4);//.arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0);
1642                 }
1643
1644                 break;
1645         case OTArc:
1646                 if (obj->hitPoint[0])
1647                         obj->p[0] = point;
1648                 else if (obj->hitPoint[1])
1649                 {
1650                         // Change the Arc's span (handle #1)
1651                         if (shiftDown)
1652                         {
1653                                 double angle = Vector::Angle(obj->p[0], point);
1654                                 double delta = angle - obj->angle[0];
1655
1656                                 if (delta < 0)
1657                                         delta += PI_TIMES_2;
1658
1659                                 obj->angle[1] -= delta;
1660                                 obj->angle[0] = angle;
1661
1662                                 if (obj->angle[1] < 0)
1663                                         obj->angle[1] += PI_TIMES_2;
1664
1665                                 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
1666                                 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);
1667                                 return;
1668                         }
1669
1670                         double angle = Vector::Angle(obj->p[0], point);
1671                         obj->angle[0] = angle;
1672                         QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
1673                         informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 4);
1674                 }
1675                 else if (obj->hitPoint[2])
1676                 {
1677                         // Change the Arc's span (handle #2)
1678                         if (shiftDown)
1679                         {
1680                                 double angle = Vector::Angle(obj->p[0], point);
1681                                 obj->angle[1] = angle - obj->angle[0];
1682
1683                                 if (obj->angle[1] < 0)
1684                                         obj->angle[1] += PI_TIMES_2;
1685
1686                                 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
1687                                 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);
1688                                 return;
1689                         }
1690
1691                         double angle = Vector::Angle(obj->p[0], point);
1692                         obj->angle[0] = angle - obj->angle[1];
1693
1694                         if (obj->angle[0] < 0)
1695                                 obj->angle[0] += PI_TIMES_2;
1696
1697                         QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
1698                         informativeText = text.arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 4);
1699                 }
1700                 else if (obj->hitObject)
1701                 {
1702                         if (shiftDown)
1703                         {
1704                                 return;
1705                         }
1706
1707                         obj->radius[0] = Vector::Magnitude(obj->p[0], point);
1708                         QString text = QObject::tr("Radius: %1");
1709                         informativeText = text.arg(obj->radius[0], 0, 'd', 4);
1710                 }
1711
1712                 break;
1713         default:
1714                 break;
1715         }
1716 }
1717
1718
1719
1720 #if 0
1721         // This returns true if we've moved over an object...
1722         if (document.PointerMoved(point)) // <-- This
1723         // This is where the object would do automagic dragging & shit. Since we don't
1724         // do that anymore, we need a strategy to handle it.
1725         {
1726
1727 /*
1728 Now objects handle mouse move snapping as well. The code below mainly works only
1729 for tools; we need to fix it so that objects work as well...
1730
1731 There's a problem with the object point snapping in that it's dependent on the
1732 order of the objects in the document. Most likely this is because it counts the
1733 selected object last and thus fucks up the algorithm. Need to fix this...
1734
1735
1736 */
1737                 // Do object snapping here. Grid snapping on mouse down is done in the
1738                 // objects themselves, only because we have to hit test the raw point,
1739                 // not the snapped point. There has to be a better way...!
1740                 if (document.penultimateObjectHovered)
1741                 {
1742                         // Two objects are hovered, see if we have an intersection point
1743                         if ((document.lastObjectHovered->type == OTLine) && (document.penultimateObjectHovered->type == OTLine))
1744                         {
1745                                 double t;
1746                                 int n = Geometry::Intersects((Line *)document.lastObjectHovered, (Line *)document.penultimateObjectHovered, &t);
1747
1748                                 if (n == 1)
1749                                 {
1750                                         Global::snapPoint = document.lastObjectHovered->GetPointAtParameter(t);
1751                                         Global::snapPointIsValid = true;
1752                                 }
1753                         }
1754                         else if ((document.lastObjectHovered->type == OTCircle) && (document.penultimateObjectHovered->type == OTCircle))
1755                         {
1756                                 Point p1, p2;
1757                                 int n = Geometry::Intersects((Circle *)document.lastObjectHovered, (Circle *)document.penultimateObjectHovered, 0, 0, 0, 0, &p1, &p2);
1758
1759                                 if (n == 1)
1760                                 {
1761                                         Global::snapPoint = p1;
1762                                         Global::snapPointIsValid = true;
1763                                 }
1764                                 else if (n == 2)
1765                                 {
1766                                         double d1 = Vector(point, p1).Magnitude();
1767                                         double d2 = Vector(point, p2).Magnitude();
1768
1769                                         if (d1 < d2)
1770                                                 Global::snapPoint = p1;
1771                                         else
1772                                                 Global::snapPoint = p2;
1773
1774                                         Global::snapPointIsValid = true;
1775                                 }
1776                         }
1777                 }
1778 //              else
1779 //              {
1780                         // Otherwise, it was a single object hovered...
1781 //              }
1782         }
1783
1784         if (toolAction)
1785         {
1786                 if (Global::snapToGrid)
1787                         point = Global::SnapPointToGrid(point);
1788
1789                 // We always snap to object points, and they take precendence over
1790                 // grid points...
1791                 if (Global::snapPointIsValid)
1792                         point = Global::snapPoint;
1793
1794                 toolAction->MouseMoved(point);
1795         }
1796 #else
1797 #endif
1798