]> Shamusworld >> Repos - architektonas/blob - src/drawingview.cpp
ca352c2cb7c8de575b6a59b2e3f250d609f89f27
[architektonas] / src / drawingview.cpp
1 //
2 // drawingview.cpp
3 //
4 // Part of the Architektonas Project
5 // (C) 2011-2020 Underground Software
6 // See the README and GPLv3 files for licensing and warranty information
7 //
8 // JLH = James Hammons <jlhamm@acm.org>
9 //
10 // Who  When        What
11 // ---  ----------  ------------------------------------------------------------
12 // JLH  03/22/2011  Created this file
13 // JLH  09/29/2011  Added middle mouse button panning
14 //
15
16 // FIXED:
17 //
18 // - Redo rendering code to *not* use Qt's transform functions, as they are tied
19 //   to a left-handed system and we need a right-handed one. [DONE]
20 // - Fixed length tool doesn't work on lines [DONE]
21 //
22 // STILL TO BE DONE:
23 //
24 // - Lots of stuff
25 // - Layer locking (hiding works)
26 // - Fixed angle tool doesn't work on lines
27 // - Make it so "dirty" flag reflects drawing state
28 //
29
30 // Uncomment this for debugging...
31 //#define DEBUG
32 //#define DEBUGFOO                              // Various tool debugging...
33 //#define DEBUGTP                               // Toolpalette debugging...
34
35 #include "drawingview.h"
36
37 #include <stdint.h>
38 #include "geometry.h"
39 #include "global.h"
40 #include "mathconstants.h"
41 #include "painter.h"
42 #include "structs.h"
43 #include "utils.h"
44
45
46 #define BACKGROUND_MAX_SIZE     512
47
48
49 DrawingView::DrawingView(QWidget * parent/*= NULL*/): QWidget(parent),
50         // The value in the settings file will override this.
51         useAntialiasing(true), numHovered(0), shiftDown(false),
52         ctrlDown(false), altDown(false),
53         gridBackground(BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE),
54         scale(1.0), offsetX(-10), offsetY(-10), document(true),
55         gridPixels(0), collided(false), scrollDrag(false), hoverPointValid(false),
56         hoveringIntersection(false), dragged(NULL), draggingObject(false),
57         angleSnap(false), dirty(false)
58 {
59 //wtf? doesn't work except in c++11???  document = { 0 };
60         setBackgroundRole(QPalette::Base);
61         setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
62
63         Global::gridSpacing = 12.0;             // In base units (inch is default)
64
65         Line * line = new Line(Vector(5, 5), Vector(50, 40), 2.0, 0xFF7F00, LSDash);
66         document.Add(line);
67         document.Add(new Line(Vector(50, 40), Vector(10, 83)));
68         document.Add(new Line(Vector(10, 83), Vector(17, 2)));
69         document.Add(new Circle(Vector(100, 100), 36));
70         document.Add(new Circle(Vector(50, 150), 49));
71         document.Add(new Arc(Vector(300, 300), 32, TAU / 8.0, TAU * 0.65)),
72         document.Add(new Arc(Vector(200, 200), 60, TAU / 4.0, TAU * 0.75));
73         document.Add(new Text(Vector(10, 83), "Here is some awesome text!"));
74
75         AddDimensionTo(line);
76
77 /*
78 Here we set the grid size in pixels--12 in this case. Initially, we have our
79 zoom set to make this represent 12 inches at a zoom factor of 25%. (This is
80 arbitrary.) So, to be able to decouple the grid size from the zoom, we need
81 to be able to set the size of the background grid (which we do here at an
82 arbitrary 12 pixels) to anything we want (within reason, of course :-).
83
84 The drawing enforces the grid spacing through the drawing->gridSpacing variable.
85
86         drawing->gridSpacing = 12.0 / Global::zoom;
87
88 Global::zoom is the zoom factor for the drawing, and all mouse clicks are
89 translated to Cartesian coordinates through this. (Initially, Global::zoom is
90 set to 1.0. SCREEN_ZOOM is set to 1.0/4.0.)
91
92 Really, the 100% zoom level can be set at *any* zoom level, it's more of a
93 convenience function than any measure of absolutes. Doing things that way we
94 could rid ourselves of the whole SCREEN_ZOOM parameter and all the attendant
95 shittiness that comes with it.
96
97 However, it seems that SCREEN_ZOOM is used to make text and arrow sizes show up
98 a certain way, which means we should probably create something else in those
99 objects to take its place--like some kind of scale factor. This would seem to
100 imply that certain point sizes actually *do* tie things like fonts to absolute
101 sizes on the screen, but not necessarily because you could have an inch scale
102 with text that is quite small relative to other objects on the screen, which
103 currently you have to zoom in to see (and which blows up the text). Point sizes
104 in an application like this are a bit meaningless; even though an inch is an
105 inch regardless of the zoom level a piece of text can be larger or smaller than
106 this. Maybe this is the case for having a base unit and basing point sizes off
107 of that.
108
109 Here's what's been figured out. Global::zoom is simply the ratio of pixels to
110 base units. What that means is that if you have a 12px grid with a 6" grid size
111 (& base unit of "inches"), Global::zoom becomes 12px / 6" = 2.0 px/in.
112
113 Dimensions now have a "size" parameter to set their absolute size in relation
114 to the base unit. ATM, the arrows are drawn in pixels, but also scaled by
115 Global::zoom *and* size. Same with the dimension text; it's drawn at 10pt and
116 scaled the same way as the arrowheads.
117
118 Need a way to scale line widths as well. :-/ Shouldn't be too difficult, just
119 need a thickness parameter similar to the "size" param for dimensions. (And now
120 we do! :-)
121
122 */
123         gridPixels = 12; //tmp
124         SetGridSize(12.0);      // This is in pixels
125 }
126
127
128 void DrawingView::DrawBackground(Painter * painter)
129 {
130         Point ul = Painter::QtToCartesianCoords(Vector(0, 0));
131         Point br = Painter::QtToCartesianCoords(Vector(Global::screenSize.x, Global::screenSize.y));
132
133         painter->SetBrush(0xF0F0F0);
134         painter->SetPen(0xF0F0F0, 1, 1);
135         painter->DrawRect(QRectF(QPointF(ul.x, ul.y), QPointF(br.x, br.y)));
136
137         double spacing = Global::gridSpacing;
138
139         if (spacing < 1.0)
140                 spacing = 1.0;
141
142         double leftx = floor(ul.x / spacing) * spacing;
143         double bottomy = floor(br.y / spacing) * spacing;
144
145         double w = (br.x - ul.x) + Global::gridSpacing + 1.0;
146         double h = (ul.y - br.y) + Global::gridSpacing + 1.0;
147
148         Vector start(leftx, bottomy), size(w, h);
149
150         if (Global::gridSpacing <= 0.015625)
151                 DrawSubGrid(painter, 0xFFD2D2, 0.015625, start, size);
152
153         if (Global::gridSpacing <= 0.03125)
154                 DrawSubGrid(painter, 0xFFD2D2, 0.03125, start, size);
155
156         if (Global::gridSpacing <= 0.0625)
157                 DrawSubGrid(painter, 0xB8ECFF, 0.0625, start, size);
158
159         if (Global::gridSpacing <= 0.125)
160                 DrawSubGrid(painter, 0xB8ECFF, 0.125, start, size);
161
162         if (Global::gridSpacing <= 0.25)
163                 DrawSubGrid(painter, 0xDBDBFF, 0.25, start, size);
164
165         if (Global::gridSpacing <= 0.5)
166                 DrawSubGrid(painter, 0xDBDBFF, 0.5, start, size);
167
168         painter->SetPen(QPen(QColor(0xD2, 0xD2, 0xFF), 2.0, Qt::SolidLine));
169
170         for(double i=0; i<=w; i+=spacing)
171                 painter->DrawVLine(leftx + i);
172
173         for(double i=0; i<=h; i+=spacing)
174                 painter->DrawHLine(bottomy + i);
175 }
176
177
178 void DrawingView::DrawSubGrid(Painter * painter, uint32_t color, double step, Vector start, Vector size)
179 {
180         painter->SetPen(color, 1, 1);
181
182         for(double i=-step; i<=size.x; i+=step*2.0)
183                 painter->DrawVLine(start.x + i);
184
185         for(double i=-step; i<=size.y; i+=step*2.0)
186                 painter->DrawHLine(start.y + i);
187 }
188
189
190 void DrawingView::SetGridSize(uint32_t size)
191 {
192 #if 0
193         // Sanity check
194         if (size == gridPixels)
195                 return;
196
197         // tmp...
198         if (size <= 1)
199                 return;
200
201         // Recreate the background bitmap
202         gridPixels = size;
203         QPainter pmp(&gridBackground);
204         pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
205         pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
206
207         for(int i=0; i<(BACKGROUND_MAX_SIZE-1); i+=gridPixels)
208         {
209                 pmp.drawLine(i, 0, i, BACKGROUND_MAX_SIZE - 1);
210                 pmp.drawLine(0, i, BACKGROUND_MAX_SIZE - 1, i);
211         }
212
213         pmp.end();
214
215         // Set up new BG brush & zoom level (pixels per base unit)
216 // This shouldn't be done here, because it fucks up the scrollwheel zooming...
217 //      Global::zoom = gridPixels / Global::gridSpacing;
218         UpdateGridBackground();
219 #endif
220 }
221
222
223 void DrawingView::UpdateGridBackground(void)
224 {
225 #if 0
226         // Transform the origin to Qt coordinates
227         Vector pixmapOrigin = Painter::CartesianToQtCoords(Vector());
228         int x = (int)pixmapOrigin.x;
229         int y = (int)pixmapOrigin.y;
230         // Use mod arithmetic to grab the correct swatch of background
231 /*
232 Negative numbers still screw it up... Need to think about what we're
233 trying to do here. The fact that it worked with 72 seems to have been pure luck.
234 It seems the problem is negative numbers: We can't let that happen.
235 When taking away the zero, it pops over 1 px at zero, then goes about 1/2 a
236 grid at x<0.
237
238 The bitmap looks like this:
239
240 +---+---+---+---+---
241 |   |   |   |   |
242 |   |   |   |   |
243 +---+---+---+---+---
244 |   |   |   |   |
245 |   |   |   |   |
246 |   |   |   |   |
247
248 @ x = 1, we want it to look like:
249
250 -+---+---+---+---+---
251  |   |   |   |   |
252  |   |   |   |   |
253 -+---+---+---+---+---
254  |   |   |   |   |
255  |   |   |   |   |
256  |   |   |   |   |
257
258 Which means we need to grab the sample from x = 3. @ x = -1:
259
260 ---+---+---+---+---
261    |   |   |   |
262    |   |   |   |
263 ---+---+---+---+---
264    |   |   |   |
265    |   |   |   |
266    |   |   |   |
267
268 Which means we need to grab the sample from x = 1. Which means we have to take
269 the mirror of the modulus of gridPixels.
270
271 Doing a mod of a negative number is problematic: 1st, the compiler converts the
272 negative number to an unsigned int, then it does the mod. Gets you wrong answers
273 most of the time, unless you use a power of 2. :-P So what we do here is just
274 take the modulus of the negation, which means we don't have to worry about
275 mirroring it later.
276
277 The positive case looks gruesome (and it is) but it boils down to this: We take
278 the modulus of the X coordinate, then mirror it by subtraction from the
279 maximum (in this case, gridPixels). This gives us a number in the range of 1 to
280 gridPixels. But we need the case where the result equalling gridPixels to be
281 zero; so we do another modulus operation on the result to achieve this.
282 */
283         if (x < 0)
284                 x = -x % gridPixels;
285         else
286                 x = (gridPixels - (x % gridPixels)) % gridPixels;
287
288         if (y < 0)
289                 y = -y % gridPixels;
290         else
291                 y = (gridPixels - (y % gridPixels)) % gridPixels;
292
293         // Here we grab a section of the bigger pixmap, so that the background
294         // *looks* like it's scrolling...
295         QPixmap pm = gridBackground.copy(x, y, gridPixels, gridPixels);
296         QPalette pal = palette();
297         pal.setBrush(backgroundRole(), QBrush(pm));
298         setAutoFillBackground(true);
299         setPalette(pal);
300 #endif
301 }
302
303
304 void DrawingView::SetGridSize(double size)
305 {
306 #if 0
307         // Sanity check
308         if (size == gridPixelsF)
309                 return;
310
311         // tmp...
312         if (size <= 1)
313                 return;
314
315         // Recreate the background bitmap
316         gridPixelsF = size;
317         QPainter pmp(&gridBackground);
318         pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
319         pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
320
321         for(double i=0; i<(BACKGROUND_MAX_SIZE-1); i+=gridPixelsF)
322         {
323                 pmp.drawLine(i, 0, i, (double)(BACKGROUND_MAX_SIZE - 1));
324                 pmp.drawLine(0, i, (double)(BACKGROUND_MAX_SIZE - 1), i);
325         }
326
327         pmp.end();
328
329         // Set up new BG brush & zoom level (pixels per base unit)
330 // This shouldn't be done here, because it fucks up the scrollwheel zooming...
331 //      Global::zoom = gridPixels / Global::gridSpacing;
332         UpdateGridBackgroundF();
333 #endif
334 }
335
336
337 void DrawingView::UpdateGridBackgroundF(void)
338 {
339 #if 0
340         // Transform the origin to Qt coordinates
341         Vector pixmapOrigin = Painter::CartesianToQtCoords(Vector());
342         int x = 0;// (int)pixmapOrigin.x;
343         int y = 0;// (int)pixmapOrigin.y;
344         // Use mod arithmetic to grab the correct swatch of background
345
346 /*      if (x < 0)
347                 x = -x % gridPixels;
348         else
349                 x = (gridPixels - (x % gridPixels)) % gridPixels;
350
351         if (y < 0)
352                 y = -y % gridPixels;
353         else
354                 y = (gridPixels - (y % gridPixels)) % gridPixels;*/
355
356         // Here we grab a section of the bigger pixmap, so that the background
357         // *looks* like it's scrolling...
358         QPixmap pm = gridBackground.copy(x, y, gridPixelsF, gridPixelsF);
359         QPalette pal = palette();
360         pal.setBrush(backgroundRole(), QBrush(pm));
361         setAutoFillBackground(true);
362         setPalette(pal);
363 #endif
364 }
365
366
367 //
368 // Basically, we just make a single pass through the Container. If the layer #
369 // is less than the layer # being deleted, then do nothing. If the layer # is
370 // equal to the layer # being deleted, then delete the object. If the layer #
371 // is greater than the layer # being deleted, then set the layer # to its layer
372 // # - 1.
373 //
374 void DrawingView::DeleteCurrentLayer(int layer)
375 {
376 //printf("DrawingView::DeleteCurrentLayer(): currentLayer = %i\n", layer);
377         VPVectorIter i = document.objects.begin();
378
379         while (i != document.objects.end())
380         {
381                 Object * obj = (Object *)(*i);
382
383                 if (obj->layer < layer)
384                         i++;
385                 else if (obj->layer == layer)
386                 {
387                         document.objects.erase(i);
388                         delete obj;
389                 }
390                 else
391                 {
392                         obj->layer--;
393                         i++;
394                 }
395         }
396
397         // We've just done a destructive action, so update the screen!
398         update();
399 }
400
401
402 void DrawingView::HandleLayerToggle(void)
403 {
404         // A layer's visibility was toggled, so update the screen...
405         update();
406 }
407
408
409 //
410 // A layer was moved up or down in the layer list, so we have to swap the
411 // document's object's layer numbers in the layers that were swapped.
412 //
413 void DrawingView::HandleLayerSwap(int layer1, int layer2)
414 {
415 // !!! FIX !!! This doesn't properly handle container contents...
416 //printf("DrawingView: Swapping layers %i and %i.\n", layer1, layer2);
417         for(VPVectorIter i=document.objects.begin(); i!=document.objects.end(); i++)
418         {
419                 Object * obj = (Object *)(*i);
420
421                 if (obj->layer == layer1)
422                         obj->layer = layer2;
423                 else if (obj->layer == layer2)
424                         obj->layer = layer1;
425         }
426 }
427
428
429 void DrawingView::HandlePenWidth(float width)
430 {
431         for(VPVectorIter i=select.begin(); i!=select.end(); i++)
432         {
433                 Object * obj = (Object *)(*i);
434                 obj->thickness = width;
435         }
436 }
437
438
439 void DrawingView::HandlePenStyle(int style)
440 {
441         for(VPVectorIter i=select.begin(); i!=select.end(); i++)
442         {
443                 Object * obj = (Object *)(*i);
444                 obj->style = style;
445         }
446 }
447
448
449 void DrawingView::HandlePenColor(uint32_t color)
450 {
451         for(VPVectorIter i=select.begin(); i!=select.end(); i++)
452         {
453                 Object * obj = (Object *)(*i);
454                 obj->color = color;
455         }
456 }
457
458
459 void DrawingView::HandlePenStamp(void)
460 {
461         VPVector flat = Flatten(select);
462
463         for(VPVectorIter i=flat.begin(); i!=flat.end(); i++)
464         {
465                 Object * obj = (Object *)(*i);
466
467                 if (obj->type != OTText)
468                         obj->thickness = Global::penWidth;
469
470                 obj->style = Global::penStyle;
471                 obj->color = Global::penColor;
472         }
473
474         update();
475 }
476
477
478 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
479 {
480         // This is undoing the transform, e.g. going from client coords to local
481         // coords. In essence, the height - y is height + (y * -1), the (y * -1)
482         // term doing the conversion of the y-axis from increasing bottom to top.
483         return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
484 }
485
486
487 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
488 {
489         // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
490         // No voodoo here, it's just grouped wrong to see it. It should be:
491         // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive [why? we use -offsetX after all]
492         return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
493 }
494
495
496 void DrawingView::focusOutEvent(QFocusEvent * /*event*/)
497 {
498 //      printf("DrawingView::focusOutEvent()...\n");
499         // Make sure all modkeys being held are marked as released when the app
500         // loses focus (N.B.: This only works because the app sets the focus policy
501         // of this object to something other than Qt::NoFocus)
502         shiftDown = ctrlDown = altDown = false;
503         scrollDrag = false;
504         setCursor(Qt::ArrowCursor);
505 }
506
507
508 void DrawingView::paintEvent(QPaintEvent * /*event*/)
509 {
510         QPainter qtPainter(this);
511         Painter painter(&qtPainter);
512
513         if (useAntialiasing)
514                 qtPainter.setRenderHint(QPainter::Antialiasing);
515
516         Global::viewportHeight = size().height();
517
518         DrawBackground(&painter);
519
520         // Draw coordinate axes
521         painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
522         painter.DrawLine(0, -16384, 0, 16384);
523         painter.DrawLine(-16384, 0, 16384, 0);
524
525         // Do object rendering...
526         for(int i=0; i<Global::numLayers; i++)
527         {
528                 if (Global::layerHidden[i] == false)
529                         RenderObjects(&painter, document.objects, i);
530         }
531
532         // Do tool rendering, if any...
533         if (Global::tool)
534         {
535                 painter.SetPen(QPen(QColor(200, 100, 0, 255), 1.0, Qt::DashLine));
536                 painter.DrawCrosshair(oldPoint);
537                 ToolDraw(&painter);
538         }
539
540         // Do selection rectangle rendering, if any
541         if (Global::selectionInProgress)
542         {
543                 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
544                 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
545                 painter.DrawRect(Global::selection);
546         }
547
548         if (hoveringIntersection)
549                 painter.DrawHandle(intersectionPoint);
550
551         if (hoverPointValid)
552                 painter.DrawHandle(hoverPoint);
553
554         if (!informativeText.isEmpty())
555                 painter.DrawInformativeText(informativeText);
556 }
557
558
559 //
560 // Renders objects in the passed in vector
561 //
562 /*
563 N.B.: Since we have "hoverPointValid" drawing regular object handles above,
564       we can probably do away with a lot of them that are being done down below.
565       !!! FIX !!!
566       [Well, it seems to work OK *except* when you move one of the points, then you get to see nothing. Is it worth fixing there to get rid of problems here? Have to investigate...]
567 */
568 void DrawingView::RenderObjects(Painter * painter, VPVector & v, int layer, bool ignoreLayer/*= false*/)
569 {
570         for(VPVectorIter i=v.begin(); i!=v.end(); i++)
571         {
572                 Object * obj = (Object *)(*i);
573                 float scaledThickness = Global::scale * obj->thickness;
574
575                 // If the object isn't on the current layer being drawn, skip it
576                 if (!ignoreLayer && (obj->layer != layer))
577                         continue;
578
579                 if ((Global::tool == TTRotate) && ctrlDown && obj->selected)
580                 {
581                         painter->SetPen(0x00FF00, 2.0, LSSolid);
582                 }
583                 else
584                 {
585                         painter->SetPen(obj->color, Global::zoom * scaledThickness, obj->style);
586                         painter->SetBrush(obj->color);
587
588                         if (obj->selected || obj->hitObject)
589                                 painter->SetPen(0xFF0000, Global::zoom * scaledThickness, LSDash);
590                 }
591
592                 switch (obj->type)
593                 {
594                 case OTLine:
595                         painter->DrawLine(obj->p[0], obj->p[1]);
596
597                         if (obj->hitPoint[0])
598                                 painter->DrawHandle(obj->p[0]);
599
600                         if (obj->hitPoint[1])
601                                 painter->DrawHandle(obj->p[1]);
602
603                         if (obj->hitObject)
604                                 painter->DrawSmallHandle(Geometry::Midpoint((Line *)obj));
605
606                         break;
607
608                 case OTCircle:
609                         painter->SetBrush(QBrush(Qt::NoBrush));
610                         painter->DrawEllipse(obj->p[0], obj->radius[0], obj->radius[0]);
611
612                         if (obj->hitPoint[0])
613                                 painter->DrawHandle(obj->p[0]);
614
615                         break;
616
617                 case OTArc:
618                         painter->DrawArc(obj->p[0], obj->radius[0], obj->angle[0], obj->angle[1]);
619
620                         if (obj->hitPoint[0])
621                                 painter->DrawHandle(obj->p[0]);
622
623                         if (obj->hitPoint[1])
624                                 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]));
625
626                         if (obj->hitPoint[2])
627                                 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0] + obj->angle[1]), sin(obj->angle[0] + obj->angle[1])) * obj->radius[0]));
628
629                         break;
630
631                 case OTDimension:
632                 {
633                         Dimension * d = (Dimension *)obj;
634
635                         Vector v(d->p[0], d->p[1]);
636                         double angle = v.Angle();
637                         Vector unit = v.Unit();
638                         d->lp[0] = d->p[0], d->lp[1] = d->p[1];
639                         Vector ortho;
640                         double x1, y1, length;
641
642                         if (d->subtype == DTLinearVert)
643                         {
644                                 if ((angle < 0) || (angle > HALF_TAU))
645                                 {
646                                         x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
647                                         y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
648                                         ortho = Vector(1.0, 0);
649                                         angle = THREE_QTR_TAU;
650                                 }
651                                 else
652                                 {
653                                         x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
654                                         y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
655                                         ortho = Vector(-1.0, 0);
656                                         angle = QTR_TAU;
657                                 }
658
659                                 d->lp[0].x = d->lp[1].x = x1;
660                                 length = fabs(d->p[0].y - d->p[1].y);
661                         }
662                         else if (d->subtype == DTLinearHorz)
663                         {
664                                 if ((angle < QTR_TAU) || (angle > THREE_QTR_TAU))
665                                 {
666                                         x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
667                                         y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
668                                         ortho = Vector(0, 1.0);
669                                         angle = 0;
670                                 }
671                                 else
672                                 {
673                                         x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
674                                         y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
675                                         ortho = Vector(0, -1.0);
676                                         angle = HALF_TAU;
677                                 }
678
679                                 d->lp[0].y = d->lp[1].y = y1;
680                                 length = fabs(d->p[0].x - d->p[1].x);
681                         }
682                         else if (d->subtype == DTLinear)
683                         {
684                                 angle = Vector(d->lp[0], d->lp[1]).Angle();
685                                 ortho = Vector::Normal(d->lp[0], d->lp[1]);
686                                 length = v.Magnitude();
687                         }
688
689                         unit = Vector(d->lp[0], d->lp[1]).Unit();
690
691                         Point p1 = d->lp[0] + (ortho * (d->offset + (10.0 * scaledThickness)));
692                         Point p2 = d->lp[1] + (ortho * (d->offset + (10.0 * scaledThickness)));
693                         Point p3 = d->lp[0] + (ortho * (d->offset + (16.0 * scaledThickness)));
694                         Point p4 = d->lp[1] + (ortho * (d->offset + (16.0 * scaledThickness)));
695                         Point p5 = d->p[0] + (ortho * 4.0 * scaledThickness);
696                         Point p6 = d->p[1] + (ortho * 4.0 * scaledThickness);
697
698                 /*
699                 The numbers hardcoded into here, what are they?
700                 I believe they are pixels.
701                 */
702                         // Draw extension lines (if certain type)
703                         painter->DrawLine(p3, p5);
704                         painter->DrawLine(p4, p6);
705
706                         // Calculate whether or not the arrowheads are too crowded to put
707                         // inside the extension lines. 9.0 is the length of the arrowhead.
708                         double t = Geometry::ParameterOfLineAndPoint(d->lp[0], d->lp[1], d->lp[1] - (unit * 9.0 * scaledThickness));
709
710                         // On the screen, it's acting like this is actually 58%...
711                         // This is correct, we want it to happen at > 50%
712                         if (t > 0.58)
713                         {
714                                 // Draw main dimension line + arrowheads
715                                 painter->DrawLine(p1, p2);
716                                 painter->DrawArrowhead(p1, p2, scaledThickness);
717                                 painter->DrawArrowhead(p2, p1, scaledThickness);
718                         }
719                         else
720                         {
721                                 // Draw outside arrowheads
722                                 Point p7 = p1 - (unit * 9.0 * scaledThickness);
723                                 Point p8 = p2 + (unit * 9.0 * scaledThickness);
724                                 painter->DrawArrowhead(p1, p7, scaledThickness);
725                                 painter->DrawArrowhead(p2, p8, scaledThickness);
726                                 painter->DrawLine(p1, p1 - (unit * 14.0 * scaledThickness));
727                                 painter->DrawLine(p2, p2 + (unit * 14.0 * scaledThickness));
728                         }
729
730                         // Draw length of dimension line...
731                         painter->SetFont(QFont("Arial", 8.0 * Global::zoom * scaledThickness));
732                         Point ctr = p2 + (Vector(p2, p1) / 2.0);
733
734                         QString dimText;
735
736                         if (length < 12.0)
737                                 dimText = QString("%1\"").arg(length);
738                         else
739                         {
740                                 double feet = (double)((int)length / 12);
741                                 double inches = length - (feet * 12.0);
742
743                                 if (inches == 0)
744                                         dimText = QString("%1'").arg(feet);
745                                 else
746                                         dimText = QString("%1' %2\"").arg(feet).arg(inches);
747                         }
748
749 /*
750 Where is the text offset?  It looks like it's drawing in the center, but obviously it isn't.  It isn't here, it's in Painter::DrawAngledText().
751 */
752                         painter->DrawAngledText(ctr, angle, dimText, scaledThickness);
753
754                         if (d->hitObject)
755                         {
756                                 Point hp1 = (p1 + p2) / 2.0;
757                                 Point hp2 = (p1 + hp1) / 2.0;
758                                 Point hp3 = (hp1 + p2) / 2.0;
759
760                                 if (d->hitPoint[2])
761                                 {
762                                         painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
763                                         painter->SetBrush(QBrush(QColor(Qt::magenta)));
764                                         painter->DrawArrowHandle(hp1, ortho.Angle() + HALF_TAU);
765                                         painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
766                                 }
767
768                                 painter->DrawHandle(hp1);
769                                 painter->SetPen(QPen(Qt::blue, 1.0 * Global::zoom * scaledThickness, Qt::SolidLine));
770
771                                 if (d->hitPoint[3])
772                                 {
773                                         painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
774                                         painter->SetBrush(QBrush(QColor(Qt::magenta)));
775                                         painter->DrawArrowToLineHandle(hp2, (d->subtype == DTLinearVert ? v.Angle() - QTR_TAU : (v.Angle() < HALF_TAU ? HALF_TAU : 0)));
776                                         painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
777                                 }
778
779                                 painter->DrawHandle(hp2);
780                                 painter->SetPen(QPen(Qt::blue, 1.0 * Global::zoom * scaledThickness, Qt::SolidLine));
781
782                                 if (d->hitPoint[4])
783                                 {
784                                         painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
785                                         painter->SetBrush(QBrush(QColor(Qt::magenta)));
786                                         painter->DrawArrowToLineHandle(hp3, (d->subtype == DTLinearHorz ? v.Angle() - QTR_TAU : (v.Angle() > HALF_TAU && v.Angle() < THREE_QTR_TAU ? THREE_QTR_TAU : QTR_TAU)));
787                                         painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
788                                 }
789
790                                 painter->DrawHandle(hp3);
791                         }
792
793                         if (obj->hitPoint[0])
794                                 painter->DrawHandle(obj->p[0]);
795
796                         if (obj->hitPoint[1])
797                                 painter->DrawHandle(obj->p[1]);
798
799                         break;
800                 }
801
802                 case OTText:
803                 {
804                         Text * t = (Text *)obj;
805
806                         if (t->measured == false)
807                         {
808                                 t->extents = painter->MeasureTextObject(t->s.c_str(), scaledThickness);
809                                 t->measured = true;
810                         }
811
812                         painter->DrawTextObject(t->p[0], t->s.c_str(), scaledThickness, t->angle[0]);
813                         break;
814                 }
815
816                 case OTSpline:
817                 {
818                         break;
819                 }
820
821                 case OTPolygon:
822                 {
823                         break;
824                 }
825
826                 case OTContainer:
827                 {
828                         // Containers require recursive rendering...
829                         Container * c = (Container *)obj;
830 printf("About to render container: # objs=%i, layer=%i\n", (*c).objects.size(), layer);
831                         RenderObjects(painter, (*c).objects, layer, ignoreLayer);
832
833 //printf("Container extents: <%lf, %lf>, <%lf, %lf>\nsize: %i\n", r.l, r.t, r.r, r.b, c->objects.size());
834                         // Containers also have special indicators showing they are selected
835                         if (c->selected || c->hitObject)
836                         {
837 //                              Rect r = GetObjectExtents(obj);
838 //                              painter->DrawRectCorners(r);
839                                 painter->DrawRectCorners(Rect(c->p[0], c->p[1]));
840                         }
841
842                         break;
843                 }
844
845                 default:
846                         break;
847                 }
848         }
849 }
850
851
852 //
853 // This toggles the selection being hovered (typically, only 1 object)
854 //
855 void DrawingView::AddHoveredToSelection(void)
856 {
857         for(VPVectorIter i=document.objects.begin(); i!=document.objects.end(); i++)
858         {
859                 if (((Object *)(*i))->hovered)
860 //                      ((Object *)(*i))->selected = true;
861                         ((Object *)(*i))->selected = !((Object *)(*i))->selected;
862         }
863 }
864
865
866 VPVector DrawingView::GetSelection(void)
867 {
868         VPVector v;
869
870         for(VPVectorIter i=document.objects.begin(); i!=document.objects.end(); i++)
871         {
872                 if (((Object *)(*i))->selected)
873                         v.push_back(*i);
874         }
875
876         return v;
877 }
878
879
880 VPVector DrawingView::GetHovered(void)
881 {
882         VPVector v;
883
884         for(VPVectorIter i=document.objects.begin(); i!=document.objects.end(); i++)
885         {
886                 if (((Object *)(*i))->hovered)
887                         v.push_back(*i);
888         }
889
890         return v;
891 }
892
893
894 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
895 {
896         Global::screenSize = Vector(size().width(), size().height());
897         UpdateGridBackground();
898 }
899
900
901 void DrawingView::ToolHandler(int mode, Point p)
902 {
903         // Drop angle snap until it's needed
904         angleSnap = false;
905
906         if (Global::tool == TTLine)
907                 LineHandler(mode, p);
908         else if (Global::tool == TTCircle)
909                 CircleHandler(mode, p);
910         else if (Global::tool == TTArc)
911                 ArcHandler(mode, p);
912         else if (Global::tool == TTRotate)
913                 RotateHandler(mode, p);
914         else if (Global::tool == TTMirror)
915                 MirrorHandler(mode, p);
916         else if (Global::tool == TTDimension)
917                 DimensionHandler(mode, p);
918         else if (Global::tool == TTTriangulate)
919                 TriangulateHandler(mode, p);
920         else if (Global::tool == TTTrim)
921                 TrimHandler(mode, p);
922         else if (Global::tool == TTParallel)
923                 ParallelHandler(mode, p);
924 }
925
926
927 void DrawingView::ToolDraw(Painter * painter)
928 {
929         if (Global::tool == TTLine)
930         {
931                 if (Global::toolState == TSNone)
932                 {
933                         painter->DrawHandle(toolPoint[0]);
934                 }
935                 else if ((Global::toolState == TSPoint2) && shiftDown)
936                 {
937                         painter->DrawHandle(toolPoint[1]);
938                 }
939                 else
940                 {
941                         painter->DrawLine(toolPoint[0], toolPoint[1]);
942                         painter->DrawHandle(toolPoint[1]);
943
944                         Vector v(toolPoint[0], toolPoint[1]);
945                         double absAngle = v.Angle() * RADIANS_TO_DEGREES;
946                         double absLength = v.Magnitude();
947                         QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
948                         informativeText = text.arg(absLength).arg(absAngle);
949                 }
950         }
951         else if (Global::tool == TTCircle)
952         {
953                 if (Global::toolState == TSNone)
954                 {
955                         painter->DrawHandle(toolPoint[0]);
956                 }
957                 else if ((Global::toolState == TSPoint2) && shiftDown)
958                 {
959                         painter->DrawHandle(toolPoint[1]);
960                 }
961                 else
962                 {
963                         painter->DrawCross(toolPoint[0]);
964                         double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
965                         painter->SetBrush(QBrush(Qt::NoBrush));
966                         painter->DrawEllipse(toolPoint[0], length, length);
967                         QString text = tr("Radius: %1 in.");
968                         informativeText = text.arg(length);
969                 }
970         }
971         else if (Global::tool == TTArc)
972         {
973                 if (Global::toolState == TSNone)
974                 {
975                         painter->DrawHandle(toolPoint[0]);
976                 }
977                 else if (Global::toolState == TSPoint2)
978                 {
979                         double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
980                         painter->SetBrush(QBrush(Qt::NoBrush));
981                         painter->DrawEllipse(toolPoint[0], length, length);
982                         painter->DrawLine(toolPoint[0], toolPoint[1]);
983                         painter->DrawHandle(toolPoint[1]);
984                         QString text = tr("Radius: %1 in.");
985                         informativeText = text.arg(length);
986                 }
987                 else if (Global::toolState == TSPoint3)
988                 {
989                         double angle = Vector::Angle(toolPoint[0], toolPoint[2]);
990                         painter->DrawLine(toolPoint[0], toolPoint[2]);
991                         painter->SetBrush(QBrush(Qt::NoBrush));
992                         painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
993                         painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
994                         QString text = tr("Angle start: %1") + QChar(0x00B0);
995                         informativeText = text.arg(RADIANS_TO_DEGREES * angle);
996                 }
997                 else
998                 {
999                         double angle = Vector::Angle(toolPoint[0], toolPoint[3]);
1000                         double span = angle - toolPoint[2].x;
1001
1002                         if (span < 0)
1003                                 span += TAU;
1004
1005                         painter->DrawLine(toolPoint[0], toolPoint[3]);
1006                         painter->SetBrush(QBrush(Qt::NoBrush));
1007                         painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
1008                         painter->SetPen(0xFF00FF, 2.0, LSSolid);
1009                         painter->DrawArc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
1010                         painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
1011                         QString text = tr("Arc span: %1") + QChar(0x00B0);
1012                         informativeText = text.arg(RADIANS_TO_DEGREES * span);
1013                 }
1014         }
1015         else if (Global::tool == TTRotate)
1016         {
1017                 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
1018                         painter->DrawHandle(toolPoint[0]);
1019                 else if ((Global::toolState == TSPoint2) && shiftDown)
1020                         painter->DrawHandle(toolPoint[1]);
1021                 else
1022                 {
1023                         if (toolPoint[0] == toolPoint[1])
1024                                 return;
1025
1026                         painter->DrawLine(toolPoint[0], toolPoint[1]);
1027
1028                         double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
1029                         QString text = QChar(0x2221) + QObject::tr(": %1");
1030                         informativeText = text.arg(absAngle);
1031
1032                         if (ctrlDown)
1033                                 informativeText += " (Copy)";
1034                 }
1035         }
1036         else if (Global::tool == TTMirror)
1037         {
1038                 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
1039                         painter->DrawHandle(toolPoint[0]);
1040                 else if ((Global::toolState == TSPoint2) && shiftDown)
1041                         painter->DrawHandle(toolPoint[1]);
1042                 else
1043                 {
1044                         if (toolPoint[0] == toolPoint[1])
1045                                 return;
1046
1047                         Point mirrorPoint = toolPoint[0] + Vector(toolPoint[1], toolPoint[0]);
1048                         painter->DrawLine(mirrorPoint, toolPoint[1]);
1049
1050                         double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
1051
1052                         if (absAngle > 180.0)
1053                                 absAngle -= 180.0;
1054
1055                         QString text = QChar(0x2221) + QObject::tr(": %1");
1056                         informativeText = text.arg(absAngle);
1057
1058                         if (ctrlDown)
1059                                 informativeText += " (Copy)";
1060                 }
1061         }
1062         if (Global::tool == TTDimension)
1063         {
1064                 if (Global::toolState == TSNone)
1065                 {
1066                         painter->DrawHandle(toolPoint[0]);
1067                 }
1068                 else if ((Global::toolState == TSPoint2) && shiftDown)
1069                 {
1070                         painter->DrawHandle(toolPoint[1]);
1071                 }
1072                 else
1073                 {
1074                         painter->DrawLine(toolPoint[0], toolPoint[1]);
1075                         painter->DrawHandle(toolPoint[1]);
1076
1077                         Vector v(toolPoint[0], toolPoint[1]);
1078                         double absAngle = v.Angle() * RADIANS_TO_DEGREES;
1079                         double absLength = v.Magnitude();
1080                         QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
1081                         informativeText = text.arg(absLength).arg(absAngle);
1082                 }
1083         }
1084 }
1085
1086
1087 void DrawingView::LineHandler(int mode, Point p)
1088 {
1089         switch (mode)
1090         {
1091         case ToolMouseDown:
1092                 if (Global::toolState == TSNone)
1093                         toolPoint[0] = p;
1094                 else
1095                         toolPoint[1] = p;
1096
1097                 break;
1098
1099         case ToolMouseMove:
1100                 if (Global::toolState == TSNone)
1101                         toolPoint[0] = p;
1102                 else
1103                         toolPoint[1] = p;
1104
1105                 break;
1106
1107         case ToolMouseUp:
1108                 if (Global::toolState == TSNone)
1109                 {
1110                         Global::toolState = TSPoint2;
1111                         // Prevent spurious line from drawing...
1112                         toolPoint[1] = toolPoint[0];
1113                 }
1114                 else if ((Global::toolState == TSPoint2) && shiftDown)
1115                 {
1116                         // Key override is telling us to make a new line, not continue the
1117                         // previous one.
1118                         toolPoint[0] = toolPoint[1];
1119                 }
1120                 else
1121                 {
1122                         Line * l = new Line(toolPoint[0], toolPoint[1], Global::penWidth, Global::penColor, Global::penStyle);
1123                         l->layer = Global::activeLayer;
1124                         document.objects.push_back(l);
1125                         toolPoint[0] = toolPoint[1];
1126                 }
1127         }
1128 }
1129
1130
1131 void DrawingView::CircleHandler(int mode, Point p)
1132 {
1133         switch (mode)
1134         {
1135         case ToolMouseDown:
1136                 if (Global::toolState == TSNone)
1137                         toolPoint[0] = p;
1138                 else
1139                         toolPoint[1] = p;
1140
1141                 break;
1142
1143         case ToolMouseMove:
1144                 if (Global::toolState == TSNone)
1145                         toolPoint[0] = p;
1146                 else
1147                         toolPoint[1] = p;
1148
1149                 break;
1150
1151         case ToolMouseUp:
1152                 if (Global::toolState == TSNone)
1153                 {
1154                         Global::toolState = TSPoint2;
1155                         // Prevent spurious line from drawing...
1156                         toolPoint[1] = toolPoint[0];
1157                 }
1158                 else if ((Global::toolState == TSPoint2) && shiftDown)
1159                 {
1160                         // Key override is telling us to make a new line, not continue the
1161                         // previous one.
1162                         toolPoint[0] = toolPoint[1];
1163                 }
1164                 else
1165                 {
1166                         double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
1167                         Circle * c = new Circle(toolPoint[0], length, Global::penWidth, Global::penColor, Global::penStyle);
1168                         c->layer = Global::activeLayer;
1169                         document.objects.push_back(c);
1170                         toolPoint[0] = toolPoint[1];
1171                         Global::toolState = TSNone;
1172                 }
1173         }
1174 }
1175
1176
1177 void DrawingView::ArcHandler(int mode, Point p)
1178 {
1179         switch (mode)
1180         {
1181         case ToolMouseDown:
1182                 if (Global::toolState == TSNone)
1183                         toolPoint[0] = p;
1184                 else if (Global::toolState == TSPoint2)
1185                         toolPoint[1] = p;
1186                 else if (Global::toolState == TSPoint3)
1187                         toolPoint[2] = p;
1188                 else
1189                         toolPoint[3] = p;
1190
1191                 break;
1192
1193         case ToolMouseMove:
1194                 if (Global::toolState == TSNone)
1195                         toolPoint[0] = p;
1196                 else if (Global::toolState == TSPoint2)
1197                         toolPoint[1] = p;
1198                 else if (Global::toolState == TSPoint3)
1199                 {
1200                         toolPoint[2] = p;
1201                         angleSnap = true;
1202                 }
1203                 else
1204                 {
1205                         toolPoint[3] = p;
1206                         angleSnap = true;
1207                 }
1208
1209                 break;
1210
1211         case ToolMouseUp:
1212                 if (Global::toolState == TSNone)
1213                 {
1214                         // Prevent spurious line from drawing...
1215                         toolPoint[1] = toolPoint[0];
1216                         Global::toolState = TSPoint2;
1217                 }
1218                 else if (Global::toolState == TSPoint2)
1219                 {
1220                         if (shiftDown)
1221                         {
1222                                 // Key override is telling us to start arc at new center, not
1223                                 // continue the current one.
1224                                 toolPoint[0] = toolPoint[1];
1225                                 return;
1226                         }
1227
1228                         // Set the radius in toolPoint[1].x
1229                         toolPoint[1].x = Vector::Magnitude(toolPoint[0], toolPoint[1]);
1230                         Global::toolState = TSPoint3;
1231                 }
1232                 else if (Global::toolState == TSPoint3)
1233                 {
1234                         // Set the angle in toolPoint[2].x
1235                         toolPoint[2].x = Vector::Angle(toolPoint[0], toolPoint[2]);
1236                         Global::toolState = TSPoint4;
1237                 }
1238                 else
1239                 {
1240                         double endAngle = Vector::Angle(toolPoint[0], toolPoint[3]);
1241                         double span = endAngle - toolPoint[2].x;
1242
1243                         if (span < 0)
1244                                 span += TAU;
1245
1246                         Arc * arc = new Arc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span, Global::penWidth, Global::penColor, Global::penStyle);
1247                         arc->layer = Global::activeLayer;
1248                         document.objects.push_back(arc);
1249                         Global::toolState = TSNone;
1250                 }
1251         }
1252 }
1253
1254
1255 void DrawingView::RotateHandler(int mode, Point p)
1256 {
1257         switch (mode)
1258         {
1259         case ToolMouseDown:
1260                 if (Global::toolState == TSNone)
1261                 {
1262                         toolPoint[0] = p;
1263 //                      SavePointsFrom(select, toolScratch);
1264                         CopyObjects(select, toolScratch2);
1265                         Global::toolState = TSPoint1;
1266                 }
1267                 else if (Global::toolState == TSPoint1)
1268                         toolPoint[0] = p;
1269                 else
1270                         toolPoint[1] = p;
1271
1272                 break;
1273
1274         case ToolMouseMove:
1275                 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1276                         toolPoint[0] = p;
1277                 else if (Global::toolState == TSPoint2)
1278                 {
1279                         toolPoint[1] = p;
1280
1281                         if (shiftDown)
1282                                 return;
1283
1284                         angleSnap = true;
1285                         double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1286                         VPVectorIter j = select.begin();
1287 //                      std::vector<Object>::iterator i = toolScratch.begin();
1288                         VPVectorIter i = toolScratch2.begin();
1289
1290 //                      for(; i!=toolScratch.end(); i++, j++)
1291                         for(; i!=toolScratch2.end(); i++, j++)
1292                         {
1293 //                              Object objT = *i;
1294 //                              Point p1 = Geometry::RotatePointAroundPoint(objT.p[0], toolPoint[0], angle);
1295 //                              Point p2 = Geometry::RotatePointAroundPoint(objT.p[1], toolPoint[0], angle);
1296                                 Object * objT = (Object *)(*i);
1297                                 Object * objS = (Object *)(*j);
1298
1299                                 Point p1 = Geometry::RotatePointAroundPoint(objT->p[0], toolPoint[0], angle);
1300                                 Point p2 = Geometry::RotatePointAroundPoint(objT->p[1], toolPoint[0], angle);
1301
1302                                 objS->p[0] = p1;
1303                                 objS->p[1] = p2;
1304
1305 //                              if (objT.type == OTArc || objT.type == OTText)
1306                                 if (objT->type == OTArc || objT->type == OTText)
1307                                 {
1308 //                                      objS->angle[0] = objT.angle[0] + angle;
1309                                         objS->angle[0] = objT->angle[0] + angle;
1310
1311                                         if (objS->angle[0] > TAU)
1312                                                 objS->angle[0] -= TAU;
1313                                 }
1314 //                              else if (objT.type == OTContainer)
1315                                 else if (objT->type == OTContainer)
1316                                 {
1317                                         // OK, this doesn't work because toolScratch only has points and nothing in the containers... [ACTUALLY... toolScratch is is a vector of type Object... which DOESN'T have an objects vector in it...]
1318 //                                      Container * c = (Container *)&objT;
1319                                         Container * c = (Container *)objT;
1320                                         Container * c2 = (Container *)objS;
1321                                         VPVectorIter l = c->objects.begin();
1322                                         // TODO: Rotate items in the container
1323                                         // TODO: Make this recursive
1324                                         for(VPVectorIter k=c2->objects.begin(); k!=c2->objects.end(); k++, l++)
1325                                         {
1326                                                 Object * obj3 = (Object *)(*k);
1327                                                 Object * obj4 = (Object *)(*l);
1328
1329                                                 p1 = Geometry::RotatePointAroundPoint(obj4->p[0], toolPoint[0], angle);
1330                                                 p2 = Geometry::RotatePointAroundPoint(obj4->p[1], toolPoint[0], angle);
1331
1332                                                 obj3->p[0] = p1;
1333                                                 obj3->p[1] = p2;
1334 //                                              obj3->angle[0] = objT.angle[0] + angle;
1335                                                 obj3->angle[0] = obj4->angle[0] + angle;
1336
1337                                                 if (obj3->angle[0] > TAU)
1338                                                         obj3->angle[0] -= TAU;
1339                                         }
1340
1341                                         Rect r = GetObjectExtents(objS);
1342                                         c2->p[0] = r.TopLeft();
1343                                         c2->p[1] = r.BottomRight();
1344                                 }
1345                         }
1346                 }
1347
1348                 break;
1349
1350         case ToolMouseUp:
1351                 if (Global::toolState == TSPoint1)
1352                 {
1353                         Global::toolState = TSPoint2;
1354                         // Prevent spurious line from drawing...
1355                         toolPoint[1] = toolPoint[0];
1356                 }
1357                 else if ((Global::toolState == TSPoint2) && shiftDown)
1358                 {
1359                         // Key override is telling us to make a new line, not continue the
1360                         // previous one.
1361                         toolPoint[0] = toolPoint[1];
1362                 }
1363                 else
1364                 {
1365                         // Either we're finished with our rotate, or we're stamping a copy.
1366                         if (ctrlDown)
1367                         {
1368                                 // Stamp a copy of the selection at the current rotation & bail
1369                                 VPVector temp;
1370                                 CopyObjects(select, temp);
1371                                 ClearSelected(temp);
1372                                 AddObjectsTo(document.objects, temp);
1373 //                              RestorePointsTo(select, toolScratch);
1374                                 RestorePointsTo(select, toolScratch2);
1375                                 return;
1376                         }
1377
1378                         toolPoint[0] = p;
1379                         Global::toolState = TSPoint1;
1380 //                      SavePointsFrom(select, toolScratch);
1381                         DeleteContents(toolScratch2);
1382                         CopyObjects(select, toolScratch2);
1383                 }
1384
1385                 break;
1386
1387         case ToolKeyDown:
1388                 // Reset the selection if shift held down...
1389                 if (shiftDown)
1390 //                      RestorePointsTo(select, toolScratch);
1391                         RestorePointsTo(select, toolScratch2);
1392
1393                 break;
1394
1395         case ToolKeyUp:
1396                 // Reset selection when key is let up
1397                 if (!shiftDown)
1398                         RotateHandler(ToolMouseMove, toolPoint[1]);
1399
1400                 break;
1401
1402         case ToolCleanup:
1403 //              RestorePointsTo(select, toolScratch);
1404                 RestorePointsTo(select, toolScratch2);
1405                 DeleteContents(toolScratch2);
1406         }
1407 }
1408
1409
1410 void DrawingView::MirrorHandler(int mode, Point p)
1411 {
1412         switch (mode)
1413         {
1414         case ToolMouseDown:
1415                 if (Global::toolState == TSNone)
1416                 {
1417                         toolPoint[0] = p;
1418                         SavePointsFrom(select, toolScratch);
1419                         Global::toolState = TSPoint1;
1420                 }
1421                 else if (Global::toolState == TSPoint1)
1422                         toolPoint[0] = p;
1423                 else
1424                         toolPoint[1] = p;
1425
1426                 break;
1427
1428         case ToolMouseMove:
1429                 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1430                         toolPoint[0] = p;
1431                 else if (Global::toolState == TSPoint2)
1432                 {
1433                         toolPoint[1] = p;
1434
1435                         if (shiftDown)
1436                                 return;
1437
1438                         angleSnap = true;
1439                         double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1440                         VPVectorIter j = select.begin();
1441                         std::vector<Object>::iterator i = toolScratch.begin();
1442
1443                         for(; i!=toolScratch.end(); i++, j++)
1444                         {
1445                                 Object obj = *i;
1446                                 Point p1 = Geometry::MirrorPointAroundLine(obj.p[0], toolPoint[0], toolPoint[1]);
1447                                 Point p2 = Geometry::MirrorPointAroundLine(obj.p[1], toolPoint[0], toolPoint[1]);
1448                                 Object * obj2 = (Object *)(*j);
1449                                 obj2->p[0] = p1;
1450                                 obj2->p[1] = p2;
1451
1452 /*
1453 N.B.: When mirroring an arc thru a horizontal axis, this causes the arc to have
1454       a negative start angle which makes it impossible to interact with.
1455       !!! FIX !!!
1456 */
1457                                 if (obj.type == OTArc)
1458                                 {
1459                                         // This is 2*mirror angle - obj angle - obj span
1460                                         obj2->angle[0] = (2.0 * angle) - obj.angle[0] - obj.angle[1];
1461
1462                                         if (obj2->angle[0] > TAU)
1463                                                 obj2->angle[0] -= TAU;
1464                                 }
1465                         }
1466                 }
1467
1468                 break;
1469
1470         case ToolMouseUp:
1471                 if (Global::toolState == TSPoint1)
1472                 {
1473                         Global::toolState = TSPoint2;
1474                         // Prevent spurious line from drawing...
1475                         toolPoint[1] = toolPoint[0];
1476                 }
1477                 else if ((Global::toolState == TSPoint2) && shiftDown)
1478                 {
1479                         // Key override is telling us to make a new line, not continue the
1480                         // previous one.
1481                         toolPoint[0] = toolPoint[1];
1482                 }
1483                 else
1484                 {
1485                         // Either we're finished with our rotate, or we're stamping a copy.
1486                         if (ctrlDown)
1487                         {
1488                                 // Stamp a copy of the selection at the current rotation & bail
1489                                 VPVector temp;
1490                                 CopyObjects(select, temp);
1491                                 ClearSelected(temp);
1492                                 AddObjectsTo(document.objects, temp);
1493                                 RestorePointsTo(select, toolScratch);
1494                                 return;
1495                         }
1496
1497                         toolPoint[0] = p;
1498                         Global::toolState = TSPoint1;
1499                         SavePointsFrom(select, toolScratch);
1500                 }
1501
1502                 break;
1503
1504         case ToolKeyDown:
1505                 // Reset the selection if shift held down...
1506                 if (shiftDown)
1507                         RestorePointsTo(select, toolScratch);
1508
1509                 break;
1510
1511         case ToolKeyUp:
1512                 // Reset selection when key is let up
1513                 if (!shiftDown)
1514                         MirrorHandler(ToolMouseMove, toolPoint[1]);
1515
1516                 break;
1517
1518         case ToolCleanup:
1519                 RestorePointsTo(select, toolScratch);
1520         }
1521 }
1522
1523
1524 void DrawingView::DimensionHandler(int mode, Point p)
1525 {
1526         switch (mode)
1527         {
1528         case ToolMouseDown:
1529                 if (Global::toolState == TSNone)
1530                         toolPoint[0] = p;
1531                 else
1532                         toolPoint[1] = p;
1533
1534                 break;
1535
1536         case ToolMouseMove:
1537                 if (Global::toolState == TSNone)
1538                         toolPoint[0] = p;
1539                 else
1540                         toolPoint[1] = p;
1541
1542                 break;
1543
1544         case ToolMouseUp:
1545                 if (Global::toolState == TSNone)
1546                 {
1547                         Global::toolState = TSPoint2;
1548                         // Prevent spurious line from drawing...
1549                         toolPoint[1] = toolPoint[0];
1550                 }
1551                 else if ((Global::toolState == TSPoint2) && shiftDown)
1552                 {
1553                         // Key override is telling us to make a new line, not continue the
1554                         // previous one.
1555                         toolPoint[0] = toolPoint[1];
1556                 }
1557                 else
1558                 {
1559                         Dimension * d = new Dimension(toolPoint[0], toolPoint[1], DTLinear);
1560                         d->layer = Global::activeLayer;
1561                         document.objects.push_back(d);
1562                         Global::toolState = TSNone;
1563                 }
1564         }
1565 }
1566
1567
1568 void DrawingView::TriangulateHandler(int mode, Point/*p*/)
1569 {
1570         switch (mode)
1571         {
1572         case ToolMouseDown:
1573         {
1574                 // Skip if nothing hovered...
1575                 if (numHovered != 1)
1576                         break;
1577
1578                 VPVector hover = GetHovered();
1579                 Object * obj = (Object *)hover[0];
1580
1581                 // Skip if it's not a line...
1582                 if (obj->type != OTLine)
1583                         break;
1584
1585                 if (Global::toolState == TSNone)
1586                         toolObj[0] = obj;
1587                 else if (Global::toolState == TSPoint2)
1588                         toolObj[1] = obj;
1589                 else
1590                         toolObj[2] = obj;
1591
1592                 break;
1593         }
1594 #if 0
1595         case ToolMouseMove:
1596                 if (Global::toolState == TSNone)
1597                         toolPoint[0] = p;
1598                 else if (Global::toolState == TSPoint2)
1599                         toolPoint[1] = p;
1600                 else if (Global::toolState == TSPoint3)
1601                 {
1602                         toolPoint[2] = p;
1603                         angleSnap = true;
1604                 }
1605                 else
1606                 {
1607                         toolPoint[3] = p;
1608                         angleSnap = true;
1609                 }
1610
1611                 break;
1612 #endif
1613         case ToolMouseUp:
1614                 if (Global::toolState == TSNone)
1615                 {
1616                         Global::toolState = TSPoint2;
1617                 }
1618                 else if (Global::toolState == TSPoint2)
1619                 {
1620 /*                      if (shiftDown)
1621                         {
1622                                 // Key override is telling us to start arc at new center, not
1623                                 // continue the current one.
1624                                 toolPoint[0] = toolPoint[1];
1625                                 return;
1626                         }*/
1627
1628                         Global::toolState = TSPoint3;
1629                 }
1630                 else
1631                 {
1632                         double len2 = Vector::Magnitude(toolObj[1]->p[0], toolObj[1]->p[1]);
1633                         double len3 = Vector::Magnitude(toolObj[2]->p[0], toolObj[2]->p[1]);
1634
1635                         Circle c1(toolObj[0]->p[0], len2);
1636                         Circle c2(toolObj[0]->p[1], len3);
1637
1638                         Geometry::CheckCircleToCircleIntersection((Object *)&c1, (Object *)&c2);
1639
1640                         // Only move lines if the triangle formed by them is not degenerate
1641                         if (Global::numIntersectPoints > 0)
1642                         {
1643                                 toolObj[1]->p[0] = toolObj[0]->p[0];
1644                                 toolObj[1]->p[1] = Global::intersectPoint[0];
1645
1646                                 toolObj[2]->p[0] = Global::intersectPoint[0];
1647                                 toolObj[2]->p[1] = toolObj[0]->p[1];
1648                         }
1649
1650                         Global::toolState = TSNone;
1651                 }
1652         }
1653 }
1654
1655
1656 void DrawingView::TrimHandler(int mode, Point p)
1657 {
1658 /*
1659 n.b.: this code is lifted straight out of the old oo code.  needs to be updated.
1660 */
1661         switch (mode)
1662         {
1663         case ToolMouseDown:
1664         {
1665 #if 0
1666                 Object * toTrim = doc->lastObjectHovered;
1667
1668                 if (toTrim == NULL)
1669                         return;
1670
1671                 Vector v(((Line *)toTrim)->position, ((Line *)toTrim)->endpoint);
1672
1673                 // Check to see which case we have...
1674                 // We're trimming point #1...
1675                 if (t == 0)
1676                 {
1677                         ((Line *)toTrim)->position = ((Line *)toTrim)->position + (v * u);
1678                 }
1679                 else if (u == 1.0)
1680                 {
1681                         ((Line *)toTrim)->endpoint = ((Line *)toTrim)->position + (v * t);
1682                 }
1683                 else
1684                 {
1685                         Point p1 = ((Line *)toTrim)->position + (v * t);
1686                         Point p2 = ((Line *)toTrim)->position + (v * u);
1687                         Point p3 = ((Line *)toTrim)->endpoint;
1688                         ((Line *)toTrim)->endpoint = p1;
1689                         Line * line = new Line(p2, p3);
1690                         emit ObjectReady(line);
1691                 }
1692
1693                 doc->lastObjectHovered = NULL;
1694 #endif
1695         }
1696                 break;
1697
1698         case ToolMouseMove:
1699         {
1700 #if 0
1701                 Object * toTrim = doc->lastObjectHovered;
1702                 t = 0, u = 1.0;
1703
1704                 if (toTrim == NULL)
1705                         return;
1706
1707                 if (toTrim->type != OTLine)
1708                         return;
1709
1710                 double pointHoveredT = Geometry::ParameterOfLineAndPoint(((Line *)toTrim)->position, ((Line *)toTrim)->endpoint, point);
1711
1712                 std::vector<Object *>::iterator i;
1713
1714                 for(i=doc->objects.begin(); i!=doc->objects.end(); i++)
1715                 {
1716                         // Can't trim against yourself... :-P
1717                         if (*i == toTrim)
1718                                 continue;
1719
1720                         Object * trimAgainst = *i;
1721                         double t1;//, u1;
1722
1723                         if ((toTrim->type != OTLine) || (trimAgainst->type != OTLine))
1724                                 continue;
1725
1726                         int intersects = Geometry::Intersects((Line *)toTrim, (Line *)trimAgainst, &t1);//, &u1);
1727
1728                         if (intersects)
1729                         {
1730                                 // Now what? We don't know which side to trim!
1731                                 // ... now we do, we know which side of the Line we're on!
1732                                 if ((t1 > t) && (t1 < pointHoveredT))
1733                                         t = t1;
1734
1735                                 if ((t1 < u) && (t1 > pointHoveredT))
1736                                         u = t1;
1737                         }
1738                 }
1739 #endif
1740                 // Bail out if nothing hovered...
1741                 if (numHovered != 1)
1742                 {
1743                         toolObj[0] = NULL;
1744                         return;
1745                 }
1746
1747                 VPVector hover = GetHovered();
1748                 Object * obj = (Object *)hover[0];
1749
1750                 // Skip if it's not a line...
1751                 if (obj->type != OTLine)
1752                 {
1753                         toolObj[0] = NULL;
1754                         return;
1755                 }
1756
1757                 toolObj[0] = obj;
1758                 double hoveredParam = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], p);
1759
1760                 // Currently only deal with line against line trimming, can expand to
1761                 // others as well (line/circle, circle/circle, line/arc, etc)
1762                 VPVectorIter i;
1763                 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1764                 {
1765                         obj = (Object *)(*i);
1766
1767                         if (obj == toolObj[0])
1768                                 continue;
1769                         else if (obj->type != OTLine)
1770                                 continue;
1771
1772                         Geometry::CheckLineToLineIntersection(toolObj[0], obj);
1773
1774                         if (Global::numIntersectParams > 0)
1775                         {
1776                                 // Mark the line segment somehow (the side the mouse is on) so that it can be drawn & trimmed when we hit ToolMouseDown.
1777                         }
1778                 }
1779         }
1780                 break;
1781
1782         case ToolMouseUp:
1783                 break;
1784
1785         case ToolKeyDown:
1786                 break;
1787
1788         case ToolKeyUp:
1789                 break;
1790
1791         case ToolCleanup:
1792                 break;
1793         }
1794 }
1795
1796
1797 void DrawingView::ParallelHandler(int mode, Point /*p*/)
1798 {
1799         switch (mode)
1800         {
1801         case ToolMouseDown:
1802                 break;
1803
1804         case ToolMouseMove:
1805                 break;
1806
1807         case ToolMouseUp:
1808                 break;
1809
1810         case ToolKeyDown:
1811                 break;
1812
1813         case ToolKeyUp:
1814                 break;
1815
1816         case ToolCleanup:
1817                 break;
1818         }
1819 }
1820
1821
1822 void DrawingView::mousePressEvent(QMouseEvent * event)
1823 {
1824         if (event->button() == Qt::LeftButton)
1825         {
1826 //printf("mousePressEvent::Qt::LeftButton numHovered=%li\n", numHovered);
1827                 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1828
1829                 // Handle tool processing, if any
1830                 if (Global::tool)
1831                 {
1832                         if (hoveringIntersection)
1833                                 point = intersectionPoint;
1834                         else if (hoverPointValid)
1835                                 point = hoverPoint;
1836                         else if (Global::snapToGrid)
1837                                 point = SnapPointToGrid(point);
1838
1839                         //Also, may want to figure out if hovering over a snap point on an
1840                         //object, snap to grid if not.
1841                         // Snap to object point if valid...
1842 //                      if (Global::snapPointIsValid)
1843 //                              point = Global::snapPoint;
1844
1845                         ToolHandler(ToolMouseDown, point);
1846                         return;
1847                 }
1848
1849                 // Clear the selection only if CTRL isn't being held on click
1850                 if (!ctrlDown)
1851                         ClearSelected(document.objects);
1852 //                      ClearSelection();
1853
1854                 // If any objects are being hovered on click, add them to the selection
1855                 // & return
1856                 if (numHovered > 0)
1857                 {
1858                         AddHoveredToSelection();
1859                         update();       // needed??
1860 //                      GetHovered(hover);      // prolly needed
1861                         VPVector hover2 = GetHovered();
1862                         dragged = (Object *)hover2[0];
1863                         draggingObject = true;
1864 //printf("mousePressEvent::numHovered > 0 (hover2[0]=$%llx, type=%s)\n", dragged, objName[dragged->type]);
1865
1866                         // Alert the pen widget
1867                         emit ObjectSelected(dragged);
1868
1869                         // See if anything is using just a straight click on a handle
1870                         if (HandleObjectClicked())
1871                         {
1872                                 draggingObject = false;
1873                                 update();
1874                                 return;
1875                         }
1876
1877                         // Needed for grab & moving objects
1878                         // We do it *after*... why? (doesn't seem to confer any advantage...)
1879                         if (hoveringIntersection)
1880                                 oldPoint = intersectionPoint;
1881                         else if (hoverPointValid)
1882                                 oldPoint = hoverPoint;
1883                         else if (Global::snapToGrid)
1884                                 oldPoint = SnapPointToGrid(point);
1885
1886                         // Needed for fixed length handling
1887                         if (Global::fixedLength)
1888                         {
1889                                 if (dragged->type == OTLine)
1890                                 {
1891                                         dragged->length = Vector::Magnitude(dragged->p[0], dragged->p[1]);
1892                                 }
1893                         }
1894
1895                         if (dragged->type == OTCircle)
1896                         {
1897                                 // Save for informative text, uh, er, informing
1898                                 dragged->length = dragged->radius[0];
1899                         }
1900
1901                         return;
1902                 }
1903
1904                 // Didn't hit any object and not using a tool, so do a selection
1905                 // rectangle
1906                 Global::selectionInProgress = true;
1907                 Global::selection.setTopLeft(QPointF(point.x, point.y));
1908                 Global::selection.setBottomRight(QPointF(point.x, point.y));
1909                 select = GetSelection();
1910         }
1911         else if (event->button() == Qt::MiddleButton)
1912         {
1913                 scrollDrag = true;
1914                 oldPoint = Vector(event->x(), event->y());
1915                 // Should also change the mouse pointer as well...
1916                 setCursor(Qt::SizeAllCursor);
1917         }
1918 }
1919
1920
1921 void DrawingView::mouseMoveEvent(QMouseEvent * event)
1922 {
1923         // It seems that wheelEvent() triggers this for some reason...
1924         if (scrollWheelSeen)
1925         {
1926                 scrollWheelSeen = false;
1927                 return;
1928         }
1929
1930         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1931         Global::selection.setBottomRight(QPointF(point.x, point.y));
1932         // Only needs to be done here, as mouse down is always preceded by movement
1933         Global::snapPointIsValid = false;
1934         hoveringIntersection = false;
1935         oldScrollPoint = Vector(event->x(), event->y());
1936
1937         // Scrolling...
1938         if ((event->buttons() & Qt::MiddleButton) || scrollDrag)
1939         {
1940                 point = Vector(event->x(), event->y());
1941                 // Since we're using Qt coords for scrolling, we have to adjust them
1942                 // here to conform to Cartesian coords, since the origin is using
1943                 // Cartesian. :-)
1944                 Vector delta(oldPoint, point);
1945                 delta /= Global::zoom;
1946                 delta.y = -delta.y;
1947                 Global::origin -= delta;
1948
1949                 UpdateGridBackground();
1950                 update();
1951                 oldPoint = point;
1952                 return;
1953         }
1954
1955         // If we're doing a selection rect, see if any objects are engulfed by it
1956         // (implies left mouse button held down)
1957         if (Global::selectionInProgress)
1958         {
1959                 CheckObjectBounds();
1960
1961                 // Make sure previously selected objects stay selected (CTRL held)
1962                 for(VPVectorIter i=select.begin(); i!=select.end(); i++)
1963                         ((Object *)(*i))->selected = true;
1964
1965                 update();
1966                 return;
1967         }
1968
1969         // Do object hit testing...
1970         bool needUpdate = HitTestObjects(point);
1971         VPVector hover2 = GetHovered();
1972 #if 0
1973 {
1974 if (needUpdate)
1975 {
1976         printf("mouseMoveEvent:: numHovered=%li, hover2.size()=%li\n", numHovered, hover2.size());
1977
1978         if (hover2.size() > 0)
1979                 printf("                 (hover2[0]=$%llX, type=%s)\n", hover2[0], objName[((Object *)hover2[0])->type]);
1980 }
1981 }
1982 #endif
1983
1984         // Check for multi-hover...
1985         if (numHovered > 1)
1986         {
1987 //need to check for case where hover is over 2 circles and a 3rd's center...
1988                 Object * obj1 = (Object *)hover2[0], * obj2 = (Object *)hover2[1];
1989
1990                 Geometry::Intersects(obj1, obj2);
1991                 int numIntersecting = Global::numIntersectParams;
1992                 double t = Global::intersectParam[0];
1993                 double u = Global::intersectParam[1];
1994
1995                 if (numIntersecting > 0)
1996                 {
1997                         Vector v1 = Geometry::GetPointForParameter(obj1, t);
1998                         Vector v2 = Geometry::GetPointForParameter(obj2, u);
1999                         QString text = tr("Intersection t=%1 (%3, %4), u=%2 (%5, %6)");
2000                         informativeText = text.arg(t).arg(u).arg(v1.x).arg(v1.y).arg(v2.x).arg(v2.y);
2001
2002                         hoveringIntersection = true;
2003                         intersectionPoint = v1;
2004                 }
2005
2006                 numIntersecting = Global::numIntersectPoints;
2007
2008                 if (numIntersecting > 0)
2009                 {
2010                         Vector v1 = Global::intersectPoint[0];
2011
2012                         if (numIntersecting == 2)
2013                         {
2014                                 Vector v2 = Global::intersectPoint[1];
2015
2016                                 if (Vector::Magnitude(v2, point) < Vector::Magnitude(v1, point))
2017                                         v1 = v2;
2018                         }
2019
2020                         QString text = tr("Intersection <%1, %2>");
2021                         informativeText = text.arg(v1.x).arg(v1.y);
2022                         hoveringIntersection = true;
2023                         intersectionPoint = v1;
2024                 }
2025         }
2026         else if (numHovered == 1)
2027         {
2028                 Object * obj = (Object *)hover2[0];
2029
2030                 if (obj->type == OTLine)
2031                 {
2032 /*
2033 Not sure that this is the best way to handle this, but it works(TM)...
2034 Except when lines are overlapping, then it doesn't work... !!! FIX !!!
2035 */
2036                         Point midpoint = Geometry::Midpoint((Line *)obj);
2037                         Vector v1 = Vector::Magnitude(midpoint, point);
2038
2039                         if ((v1.Magnitude() * Global::zoom) < 8.0)
2040                         {
2041                                 QString text = tr("Midpoint <%1, %2>");
2042                                 informativeText = text.arg(midpoint.x).arg(midpoint.y);
2043                                 hoverPointValid = true;
2044                                 hoverPoint = midpoint;
2045                                 needUpdate = true;
2046                         }
2047                 }
2048         }
2049
2050         // Handle object movement (left button down & over an object)
2051         if ((event->buttons() & Qt::LeftButton) && draggingObject && !Global::tool)
2052         {
2053                 if (hoveringIntersection)
2054                         point = intersectionPoint;
2055                 else if (hoverPointValid)
2056                         point = hoverPoint;
2057                 else if (Global::snapToGrid)
2058                         point = SnapPointToGrid(point);
2059
2060                 HandleObjectMovement(point);
2061                 update();
2062                 oldPoint = point;
2063                 return;
2064         }
2065
2066         // Do tool handling, if any are active...
2067         if (Global::tool)
2068         {
2069                 if (hoveringIntersection)
2070                         point = intersectionPoint;
2071                 else if (hoverPointValid)
2072                         point = hoverPoint;
2073                 else if (Global::snapToGrid)
2074                 {
2075                         if (angleSnap)
2076                                 point = SnapPointToAngle(point);
2077                         else
2078                                 point = SnapPointToGrid(point);
2079                 }
2080
2081                 ToolHandler(ToolMouseMove, point);
2082         }
2083
2084         // This is used to draw the tool crosshair...
2085         oldPoint = point;
2086
2087         if (needUpdate || Global::tool)
2088                 update();
2089 }
2090
2091
2092 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
2093 {
2094         if (event->button() == Qt::LeftButton)
2095         {
2096 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
2097 //could set it up to use the document's update function (assumes that all object
2098 //updates are being reported correctly:
2099 //              if (document.NeedsUpdate())
2100                 // Do an update if collided with at least *one* object in the document
2101 //              if (collided)
2102                         update();
2103
2104                 if (Global::tool)
2105                 {
2106                         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
2107                         ToolHandler(ToolMouseUp, point);
2108                         return;
2109                 }
2110
2111 //              if (Global::selectionInProgress)
2112                         Global::selectionInProgress = false;
2113
2114                 informativeText.clear();
2115 // Should we be doing this automagically? Hmm...
2116                 // Clear our vectors
2117 //              select.clear();
2118 ////            hover.clear();
2119
2120                 // Scoop 'em up (do we need to??? Seems we do, because keyboard movement uses it.  Also, tools use it too.  But we can move it out of here)
2121                 select = GetSelection();
2122
2123                 draggingObject = false;
2124         }
2125         else if (event->button() == Qt::MiddleButton)
2126         {
2127                 scrollDrag = false;
2128                 setCursor(Qt::ArrowCursor);
2129         }
2130 }
2131
2132
2133 void DrawingView::wheelEvent(QWheelEvent * event)
2134 {
2135         double zoomFactor = 1.20;
2136         scrollWheelSeen = true;
2137
2138         if (event->angleDelta().y() < 0)
2139         {
2140                 if (Global::zoom > 400.0)
2141                         return;
2142
2143                 Global::zoom *= zoomFactor;
2144         }
2145         else
2146         {
2147                 if (Global::zoom < 0.125)
2148                         return;
2149
2150                 Global::zoom /= zoomFactor;
2151         }
2152
2153         Point np = Painter::QtToCartesianCoords(oldScrollPoint);
2154         Global::origin += (oldPoint - np);
2155
2156         emit(NeedZoomUpdate());
2157 }
2158
2159
2160 void DrawingView::keyPressEvent(QKeyEvent * event)
2161 {
2162         bool oldShift = shiftDown;
2163         bool oldCtrl = ctrlDown;
2164         bool oldAlt = altDown;
2165
2166         if (event->key() == Qt::Key_Shift)
2167                 shiftDown = true;
2168         else if (event->key() == Qt::Key_Control)
2169                 ctrlDown = true;
2170         else if (event->key() == Qt::Key_Alt)
2171                 altDown = true;
2172
2173         // If there's a change in any of the modifier key states, pass it on to
2174         // the current tool's handler
2175         if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
2176         {
2177                 if (Global::tool)
2178                         ToolHandler(ToolKeyDown, Point(0, 0));
2179
2180                 update();
2181         }
2182
2183         if (oldAlt != altDown)
2184         {
2185                 scrollDrag = true;
2186                 setCursor(Qt::SizeAllCursor);
2187                 oldPoint = oldScrollPoint;
2188         }
2189
2190         if (select.size() > 0)
2191         {
2192                 if (event->key() == Qt::Key_Up)
2193                 {
2194                         TranslateObjects(select, Point(0, +1.0));
2195                         update();
2196                 }
2197                 else if (event->key() == Qt::Key_Down)
2198                 {
2199                         TranslateObjects(select, Point(0, -1.0));
2200                         update();
2201                 }
2202                 else if (event->key() == Qt::Key_Right)
2203                 {
2204                         TranslateObjects(select, Point(+1.0, 0));
2205                         update();
2206                 }
2207                 else if (event->key() == Qt::Key_Left)
2208                 {
2209                         TranslateObjects(select, Point(-1.0, 0));
2210                         update();
2211                 }
2212         }
2213 }
2214
2215
2216 void DrawingView::keyReleaseEvent(QKeyEvent * event)
2217 {
2218         bool oldShift = shiftDown;
2219         bool oldCtrl = ctrlDown;
2220         bool oldAlt = altDown;
2221
2222         if (event->key() == Qt::Key_Shift)
2223                 shiftDown = false;
2224         else if (event->key() == Qt::Key_Control)
2225                 ctrlDown = false;
2226         else if (event->key() == Qt::Key_Alt)
2227                 altDown = false;
2228
2229         if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
2230         {
2231                 if (Global::tool)
2232                         ToolHandler(ToolKeyUp, Point(0, 0));
2233
2234                 update();
2235         }
2236
2237         if (oldAlt != altDown)
2238         {
2239                 scrollDrag = false;
2240                 setCursor(Qt::ArrowCursor);
2241         }
2242 }
2243
2244
2245 //
2246 // This looks strange, but it's really quite simple: We want a point that's
2247 // more than half-way to the next grid point to snap there while conversely we
2248 // want a point that's less than half-way to to the next grid point then snap
2249 // to the one before it. So we add half of the grid spacing to the point, then
2250 // divide by it so that we can remove the fractional part, then multiply it
2251 // back to get back to the correct answer.
2252 //
2253 Point DrawingView::SnapPointToGrid(Point point)
2254 {
2255         point += Global::gridSpacing / 2.0;             // *This* adds to Z!!!
2256         point /= Global::gridSpacing;
2257         point.x = floor(point.x);//need to fix this for negative numbers...
2258         point.y = floor(point.y);
2259         point.z = 0;                                    // Make *sure* Z doesn't go anywhere!!!
2260         point *= Global::gridSpacing;
2261         return point;
2262 }
2263
2264
2265 Point DrawingView::SnapPointToAngle(Point point)
2266 {
2267         // Snap to a single digit angle (using toolpoint #1 as the center)
2268         double angle = Vector::Angle(toolPoint[0], point);
2269         double length = Vector::Magnitude(toolPoint[0], point);
2270
2271         // Convert from radians to degrees
2272         double degAngle = angle * RADIANS_TO_DEGREES;
2273         double snapAngle = (double)((int)(degAngle + 0.5));
2274
2275         Vector v;
2276         v.SetAngleAndLength(snapAngle * DEGREES_TO_RADIANS, length);
2277         point = toolPoint[0] + v;
2278
2279         return point;
2280 }
2281
2282
2283 Rect DrawingView::GetObjectExtents(Object * obj)
2284 {
2285         // Default to empty rect, if object checks below fail for some reason
2286         Rect rect;
2287
2288         switch (obj->type)
2289         {
2290         case OTLine:
2291         case OTDimension:
2292         {
2293                 rect = Rect(obj->p[0], obj->p[1]);
2294                 break;
2295         }
2296
2297         case OTCircle:
2298         {
2299                 rect = Rect(obj->p[0], obj->p[0]);
2300                 rect.Expand(obj->radius[0]);
2301                 break;
2302         }
2303
2304         case OTArc:
2305         {
2306                 Arc * a = (Arc *)obj;
2307
2308                 double start = a->angle[0];
2309                 double end = start + a->angle[1];
2310                 rect = Rect(Point(cos(start), sin(start)), Point(cos(end), sin(end)));
2311
2312                 // If the end of the arc is before the beginning, add 360 degrees to it
2313                 if (end < start)
2314                         end += TAU;
2315
2316                 // Adjust the bounds depending on which axes are crossed
2317                 if ((start < QTR_TAU) && (end > QTR_TAU))
2318                         rect.t = 1.0;
2319
2320                 if ((start < HALF_TAU) && (end > HALF_TAU))
2321                         rect.l = -1.0;
2322
2323                 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
2324                         rect.b = -1.0;
2325
2326                 if ((start < TAU) && (end > TAU))
2327                         rect.r = 1.0;
2328
2329                 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
2330                         rect.t = 1.0;
2331
2332                 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
2333                         rect.l = -1.0;
2334
2335                 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
2336                         rect.b = -1.0;
2337
2338                 rect *= a->radius[0];
2339                 rect.Translate(a->p[0]);
2340                 break;
2341         }
2342
2343         case OTText:
2344         {
2345                 Text * t = (Text *)obj;
2346                 rect = Rect(t->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
2347                 break;
2348         }
2349
2350         case OTContainer:
2351         {
2352                 Container * c = (Container *)obj;
2353                 VPVectorIter i = c->objects.begin();
2354                 rect = GetObjectExtents((Object *)*i);
2355                 i++;
2356
2357                 for(; i!=c->objects.end(); i++)
2358                         rect |= GetObjectExtents((Object *)*i);
2359         }
2360
2361         default:
2362                 break;
2363         }
2364
2365         return rect;
2366 }
2367
2368
2369 void DrawingView::CheckObjectBounds(void)
2370 {
2371         VPVectorIter i;
2372
2373         for(i=document.objects.begin(); i!=document.objects.end(); i++)
2374         {
2375                 Object * obj = (Object *)(*i);
2376                 obj->selected = false;
2377
2378                 switch (obj->type)
2379                 {
2380                 case OTLine:
2381                 case OTDimension:
2382                 {
2383                         Line * l = (Line *)obj;
2384
2385                         if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
2386                                 l->selected = true;
2387
2388                         break;
2389                 }
2390
2391                 case OTCircle:
2392                 {
2393                         Circle * c = (Circle *)obj;
2394
2395                         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]))
2396                                 c->selected = true;
2397
2398                         break;
2399                 }
2400
2401                 case OTArc:
2402                 {
2403                         Arc * a = (Arc *)obj;
2404
2405                         double start = a->angle[0];
2406                         double end = start + a->angle[1];
2407                         QPointF p1(cos(start), sin(start));
2408                         QPointF p2(cos(end), sin(end));
2409                         QRectF bounds(p1, p2);
2410
2411 #if 1
2412                         // Swap X/Y coordinates if they're backwards...
2413                         if (bounds.left() > bounds.right())
2414                         {
2415                                 double temp = bounds.left();
2416                                 bounds.setLeft(bounds.right());
2417                                 bounds.setRight(temp);
2418                         }
2419
2420                         if (bounds.bottom() > bounds.top())
2421                         {
2422                                 double temp = bounds.bottom();
2423                                 bounds.setBottom(bounds.top());
2424                                 bounds.setTop(temp);
2425                         }
2426 #else
2427                         // Doesn't work as advertised! For shame!
2428                         bounds = bounds.normalized();
2429 #endif
2430
2431                         // If the end of the arc is before the beginning, add 360 degrees
2432                         // to it
2433                         if (end < start)
2434                                 end += TAU;
2435
2436                         // Adjust the bounds depending on which axes are crossed
2437                         if ((start < QTR_TAU) && (end > QTR_TAU))
2438                                 bounds.setTop(1.0);
2439
2440                         if ((start < HALF_TAU) && (end > HALF_TAU))
2441                                 bounds.setLeft(-1.0);
2442
2443                         if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
2444                                 bounds.setBottom(-1.0);
2445
2446                         if ((start < TAU) && (end > TAU))
2447                                 bounds.setRight(1.0);
2448
2449                         if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
2450                                 bounds.setTop(1.0);
2451
2452                         if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
2453                                 bounds.setLeft(-1.0);
2454
2455                         if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
2456                                 bounds.setBottom(-1.0);
2457
2458                         bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
2459                         bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
2460                         bounds.translate(a->p[0].x, a->p[0].y);
2461
2462                         if (Global::selection.contains(bounds))
2463                                 a->selected = true;
2464
2465                         break;
2466                 }
2467
2468                 case OTText:
2469                 {
2470                         Text * t = (Text *)obj;
2471                         Rect r(obj->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
2472
2473                         if (Global::selection.contains(r.l, r.t) && Global::selection.contains(r.r, r.b))
2474                                 t->selected = true;
2475
2476                         break;
2477                 }
2478
2479                 case OTContainer:
2480                 {
2481                         Container * c = (Container *)obj;
2482
2483                         if (Global::selection.contains(c->p[0].x, c->p[0].y) && Global::selection.contains(c->p[1].x, c->p[1].y))
2484                                 c->selected = true;
2485
2486                         break;
2487                 }
2488
2489 //              default:
2490 //                      break;
2491                 }
2492         }
2493 }
2494
2495
2496 bool DrawingView::HitTestObjects(Point point)
2497 {
2498         VPVectorIter i;
2499         numHovered = 0;
2500         bool needUpdate = false;
2501         hoverPointValid = false;
2502
2503         for(i=document.objects.begin(); i!=document.objects.end(); i++)
2504         {
2505                 Object * obj = (Object *)(*i);
2506
2507                 // If we're seeing the object we're dragging, skip it
2508                 if (draggingObject && (obj == dragged))
2509                         continue;
2510
2511                 if (HitTest(obj, point) == true)
2512                         needUpdate = true;
2513
2514                 if (obj->hovered)
2515                 {
2516                         numHovered++;
2517 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
2518                         emit ObjectHovered(obj);
2519                 }
2520         }
2521
2522         return needUpdate;
2523 }
2524
2525
2526 bool DrawingView::HitTest(Object * obj, Point point)
2527 {
2528         bool needUpdate = false;
2529
2530         switch (obj->type)
2531         {
2532         case OTLine:
2533         {
2534                 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
2535                 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
2536                 Vector lineSegment = obj->p[1] - obj->p[0];
2537                 Vector v1 = point - obj->p[0];
2538                 Vector v2 = point - obj->p[1];
2539                 double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
2540                 double distance;
2541
2542                 if (t < 0.0)
2543                         distance = v1.Magnitude();
2544                 else if (t > 1.0)
2545                         distance = v2.Magnitude();
2546                 else
2547                         // distance = ?Det?(ls, v1) / |ls|
2548                         distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
2549                                 / lineSegment.Magnitude());
2550
2551                 if ((v1.Magnitude() * Global::zoom) < 8.0)
2552                 {
2553                         obj->hitPoint[0] = true;
2554                         hoverPoint = obj->p[0];
2555                         hoverPointValid = true;
2556                 }
2557                 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2558                 {
2559                         obj->hitPoint[1] = true;
2560                         hoverPoint = obj->p[1];
2561                         hoverPointValid = true;
2562                 }
2563                 else if ((distance * Global::zoom) < 5.0)
2564                         obj->hitObject = true;
2565
2566                 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
2567
2568                 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
2569                         needUpdate = true;
2570
2571                 break;
2572         }
2573
2574         case OTCircle:
2575         {
2576                 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
2577                 obj->hitPoint[0] = obj->hitObject = false;
2578                 double length = Vector::Magnitude(obj->p[0], point);
2579
2580                 if ((length * Global::zoom) < 8.0)
2581                 {
2582                         obj->hitPoint[0] = true;
2583                         hoverPoint = obj->p[0];
2584                         hoverPointValid = true;
2585                 }
2586                 else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
2587                         obj->hitObject = true;
2588
2589                 obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
2590
2591                 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
2592                         needUpdate = true;
2593
2594                 break;
2595         }
2596
2597         case OTArc:
2598         {
2599                 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
2600                 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
2601                 double length = Vector::Magnitude(obj->p[0], point);
2602                 double angle = Vector::Angle(obj->p[0], point);
2603
2604                 // Make sure we get the angle in the correct spot
2605                 if (angle < obj->angle[0])
2606                         angle += TAU;
2607
2608                 // Get the span that we're pointing at...
2609                 double span = angle - obj->angle[0];
2610
2611                 // N.B.: Still need to hit test the arc start & arc span handles...
2612                 double spanAngle = obj->angle[0] + obj->angle[1];
2613                 Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
2614                 Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
2615                 double length2 = Vector::Magnitude(point, handle1);
2616                 double length3 = Vector::Magnitude(point, handle2);
2617
2618                 if ((length * Global::zoom) < 8.0)
2619                 {
2620                         obj->hitPoint[0] = true;
2621                         hoverPoint = obj->p[0];
2622                         hoverPointValid = true;
2623                 }
2624                 else if ((length2 * Global::zoom) < 8.0)
2625                 {
2626                         obj->hitPoint[1] = true;
2627                         hoverPoint = handle1;
2628                         hoverPointValid = true;
2629                 }
2630                 else if ((length3 * Global::zoom) < 8.0)
2631                 {
2632                         obj->hitPoint[2] = true;
2633                         hoverPoint = handle2;
2634                         hoverPointValid = true;
2635                 }
2636                 else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
2637                         obj->hitObject = true;
2638
2639                 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
2640
2641                 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
2642                         needUpdate = true;
2643
2644                 break;
2645         }
2646
2647         case OTDimension:
2648         {
2649                 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHP3 = obj->hitPoint[3], oldHP4 = obj->hitPoint[4], oldHO = obj->hitObject;
2650                 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitPoint[3] = obj->hitPoint[4] = obj->hitObject = false;
2651
2652                 Dimension * d = (Dimension *)obj;
2653
2654                 Vector orthogonal = Vector::Normal(d->lp[0], d->lp[1]);
2655                 // Get our line parallel to our points
2656                 float scaledThickness = Global::scale * obj->thickness;
2657 #if 1
2658                 Point p1 = d->lp[0] + (orthogonal * (d->offset + (10.0 * scaledThickness)));
2659                 Point p2 = d->lp[1] + (orthogonal * (d->offset + (10.0 * scaledThickness)));
2660 #else
2661                 Point p1 = d->lp[0] + (orthogonal * (10.0 + d->offset) * scaledThickness);
2662                 Point p2 = d->lp[1] + (orthogonal * (10.0 + d->offset) * scaledThickness);
2663 #endif
2664                 Point p3(p1, point);
2665
2666                 Vector v1(d->p[0], point);
2667                 Vector v2(d->p[1], point);
2668                 Vector lineSegment(p1, p2);
2669                 double t = Geometry::ParameterOfLineAndPoint(p1, p2, point);
2670                 double distance;
2671                 Point midpoint = (p1 + p2) / 2.0;
2672                 Point hFSPoint = Point(midpoint, point);
2673                 Point hCS1Point = Point((p1 + midpoint) / 2.0, point);
2674                 Point hCS2Point = Point((midpoint + p2) / 2.0, point);
2675
2676                 if (t < 0.0)
2677                         distance = v1.Magnitude();
2678                 else if (t > 1.0)
2679                         distance = v2.Magnitude();
2680                 else
2681                         // distance = ?Det?(ls, v1) / |ls|
2682                         distance = fabs((lineSegment.x * p3.y - p3.x * lineSegment.y)
2683                                 / lineSegment.Magnitude());
2684
2685                 if ((v1.Magnitude() * Global::zoom) < 8.0)
2686                         obj->hitPoint[0] = true;
2687                 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2688                         obj->hitPoint[1] = true;
2689                 else if ((distance * Global::zoom) < 5.0)
2690                         obj->hitObject = true;
2691
2692                 if ((hFSPoint.Magnitude() * Global::zoom) < 8.0)
2693                         obj->hitPoint[2] = true;
2694                 else if ((hCS1Point.Magnitude() * Global::zoom) < 8.0)
2695                         obj->hitPoint[3] = true;
2696                 else if ((hCS2Point.Magnitude() * Global::zoom) < 8.0)
2697                         obj->hitPoint[4] = true;
2698
2699                 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitPoint[3] || obj->hitPoint[4] || obj->hitObject ? true : false);
2700
2701                 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHP3 != obj->hitPoint[3]) || (oldHP4 != obj->hitPoint[4]) || (oldHO != obj->hitObject))
2702                         needUpdate = true;
2703
2704                 break;
2705         }
2706
2707         case OTText:
2708         {
2709                 Text * t = (Text *)obj;
2710                 bool oldHO = obj->hitObject;
2711                 obj->hitObject = false;
2712
2713                 Rect r(obj->p[0], Point(obj->p[0].x + t->extents.Width(), obj->p[0].y - t->extents.Height()));
2714 //printf("Text: p=<%lf, %lf>, w/h=%lf, %lf [lrtb=%lf, %lf, %lf, %lf]\n", obj->p[0].x, obj->p[0].y, t->extents.Width(), t->extents.Height(), t->extents.l, t->extents.r, t->extents.t, t->extents.b);
2715
2716                 if (r.Contains(point))
2717                         obj->hitObject = true;
2718
2719                 obj->hovered = (obj->hitObject ? true : false);
2720
2721                 if (oldHO != obj->hitObject)
2722                         needUpdate = true;
2723
2724                 break;
2725         }
2726
2727         case OTContainer:
2728         {
2729                 // Containers must be recursively tested...  Or do they???
2730 /*
2731 So the idea here is to flatten the structure, *then* test the objects within.  Flattening includes the Containers as well; we can do this because it's pointers all the way down.
2732 */
2733 //              bool oldHitObj = c->hitObject, oldHovered = c->hovered;
2734 //              Object * oldClicked = c->clicked;
2735 /*
2736 still need to compare old state to new state, and set things up based upon that...
2737 likely we can just rely on the object itself and steal its state like we have in the commented out portion below; can prolly rewrite the HitTest() portion to be one line: needUpdate = HitTest(cObj, point);
2738 Well, you could if there was only one object in the Container.  But since there isn't, we have to keep the if HitTest() == true then needUpdate = true bit.  Because otherwise, a false result anywhere will kill the needed update elsewhere.
2739 */
2740                 Container * c = (Container *)obj;
2741                 c->hitObject = false;
2742                 c->hovered = false;
2743                 c->clicked = NULL;
2744
2745                 VPVector flat = Flatten(c);
2746
2747 //printf("HitTest::OTContainer (size=%li)\n", flat.size());
2748                 for(VPVectorIter i=flat.begin(); i!=flat.end(); i++)
2749                 {
2750                         Object * cObj = (Object *)(*i);
2751
2752                         // Skip the flattened containers (if any)...
2753                         if (cObj->type == OTContainer)
2754                                 continue;
2755
2756                         // We do it this way instead of needUpdate = HitTest() because we
2757                         // are checking more than one object, and that way of doing will
2758                         // not return consistent results.
2759                         if (HitTest(cObj, point) == true)
2760 //                      {
2761 //printf("HitTest::OTContainer, subobj ($%llX) hit!\n", cObj);
2762                                 needUpdate = true;
2763 //                              c->hitObject = true;
2764 //                              c->clicked = cObj;
2765 //                              c->hovered = true;
2766 //                      }
2767
2768                         // Same reasons for doing it this way here apply.
2769                         if (cObj->hitObject == true)
2770                         {
2771 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2772                                 c->hitObject = true;
2773                                 c->clicked = cObj;
2774                         }//*/
2775
2776                         if (cObj->hitPoint[0] == true)
2777                         {
2778 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2779                                 c->hitPoint[0] = true;
2780                                 c->clicked = cObj;
2781                         }//*/
2782
2783                         if (cObj->hitPoint[1] == true)
2784                         {
2785 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2786                                 c->hitPoint[1] = true;
2787                                 c->clicked = cObj;
2788                         }//*/
2789
2790                         if (cObj->hitPoint[2] == true)
2791                         {
2792 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2793                                 c->hitPoint[2] = true;
2794                                 c->clicked = cObj;
2795                         }//*/
2796
2797                         if (cObj->hovered == true)
2798                                 c->hovered = true;//*/
2799                 }
2800
2801                 break;
2802         }
2803
2804         default:
2805                 break;
2806         }
2807
2808         return needUpdate;
2809 }
2810
2811
2812 bool DrawingView::HandleObjectClicked(void)
2813 {
2814         if (dragged->type == OTDimension)
2815         {
2816                 Dimension * d = (Dimension *)dragged;
2817
2818                 if (d->hitPoint[2])
2819                 {
2820                         // Hit the "flip sides" switch, so flip 'em
2821                         Point temp = d->p[0];
2822                         d->p[0] = d->p[1];
2823                         d->p[1] = temp;
2824                         return true;
2825                 }
2826                 else if (d->hitPoint[3])
2827                 {
2828                         // There are three cases here: aligned, horizontal, & vertical.
2829                         // Aligned and horizontal do the same thing, vertical goes back to
2830                         // linear.
2831                         if (d->subtype == DTLinearVert)
2832                                 d->subtype = DTLinear;
2833                         else
2834                                 d->subtype = DTLinearVert;
2835
2836                         return true;
2837                 }
2838                 else if (d->hitPoint[4])
2839                 {
2840                         // There are three cases here: aligned, horizontal, & vertical.
2841                         // Aligned and vertical do the same thing, horizontal goes back to
2842                         // linear.
2843                         if (d->subtype == DTLinearHorz)
2844                                 d->subtype = DTLinear;
2845                         else
2846                                 d->subtype = DTLinearHorz;
2847
2848                         return true;
2849                 }
2850         }
2851
2852         return false;
2853 }
2854
2855
2856 void DrawingView::HandleObjectMovement(Point point)
2857 {
2858         Point delta = point - oldPoint;
2859 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
2860 //      Object * obj = (Object *)hover[0];
2861         Object * obj = dragged;
2862 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
2863 //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"));
2864
2865         switch (obj->type)
2866         {
2867         case OTLine:
2868                 if (obj->hitPoint[0])
2869                 {
2870                         if (Global::fixedLength)
2871                         {
2872                                 Vector line = point - obj->p[1];
2873                                 Vector unit = line.Unit();
2874                                 point = obj->p[1] + (unit * obj->length);
2875                         }
2876
2877                         obj->p[0] = point;
2878                 }
2879                 else if (obj->hitPoint[1])
2880                 {
2881                         if (Global::fixedLength)
2882                         {
2883                                 Vector line = point - obj->p[0];
2884                                 Vector unit = line.Unit();
2885                                 point = obj->p[0] + (unit * obj->length);
2886                         }
2887
2888                         obj->p[1] = point;
2889                 }
2890                 else if (obj->hitObject)
2891                 {
2892                         obj->p[0] += delta;
2893                         obj->p[1] += delta;
2894                 }
2895
2896                 break;
2897
2898         case OTCircle:
2899                 if (obj->hitPoint[0])
2900                         obj->p[0] = point;
2901                 else if (obj->hitObject)
2902                 {
2903                         double oldRadius = obj->length;
2904                         obj->radius[0] = Vector::Magnitude(obj->p[0], point);
2905
2906                         QString text = QObject::tr("Radius: %1\nScale: %2%");
2907                         informativeText = text.arg(obj->radius[0], 0, 'd', 4).arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0);
2908                 }
2909
2910                 break;
2911
2912         case OTArc:
2913                 if (obj->hitPoint[0])
2914                         obj->p[0] = point;
2915                 else if (obj->hitPoint[1])
2916                 {
2917                         // Change the Arc's span (handle #1)
2918                         if (shiftDown)
2919                         {
2920                                 double angle = Vector::Angle(obj->p[0], point);
2921                                 double delta = angle - obj->angle[0];
2922
2923                                 if (delta < 0)
2924                                         delta += TAU;
2925
2926                                 obj->angle[1] -= delta;
2927                                 obj->angle[0] = angle;
2928
2929                                 if (obj->angle[1] < 0)
2930                                         obj->angle[1] += TAU;
2931
2932                                 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
2933                                 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);
2934                                 return;
2935                         }
2936
2937                         double angle = Vector::Angle(obj->p[0], point);
2938                         obj->angle[0] = angle;
2939                         QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
2940                         informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 4);
2941                 }
2942                 else if (obj->hitPoint[2])
2943                 {
2944                         // Change the Arc's span (handle #2)
2945                         if (shiftDown)
2946                         {
2947                                 double angle = Vector::Angle(obj->p[0], point);
2948                                 obj->angle[1] = angle - obj->angle[0];
2949
2950                                 if (obj->angle[1] < 0)
2951                                         obj->angle[1] += TAU;
2952
2953                                 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
2954                                 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);
2955                                 return;
2956                         }
2957
2958                         double angle = Vector::Angle(obj->p[0], point);
2959                         obj->angle[0] = angle - obj->angle[1];
2960
2961                         if (obj->angle[0] < 0)
2962                                 obj->angle[0] += TAU;
2963
2964                         QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
2965                         informativeText = text.arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 4);
2966                 }
2967                 else if (obj->hitObject)
2968                 {
2969                         if (shiftDown)
2970                         {
2971                                 return;
2972                         }
2973
2974                         obj->radius[0] = Vector::Magnitude(obj->p[0], point);
2975                         QString text = QObject::tr("Radius: %1");
2976                         informativeText = text.arg(obj->radius[0], 0, 'd', 4);
2977                 }
2978
2979                 break;
2980
2981         case OTDimension:
2982                 if (obj->hitPoint[0])
2983                         obj->p[0] = point;
2984                 else if (obj->hitPoint[1])
2985                         obj->p[1] = point;
2986                 else if (obj->hitObject)
2987                 {
2988                         // Move measurement lines in/out
2989                         if (shiftDown)
2990                         {
2991                                 Dimension * d = (Dimension *)obj;
2992                                 double dist = Geometry::DistanceToLineFromPoint(d->lp[0], d->lp[1], point);
2993                                 float scaledThickness = Global::scale * obj->thickness;
2994                                 // Looks like offset is 0 to +MAX, but line is at 10.0.  So
2995                                 // anything less than 10.0 should set the offset to 0.
2996                                 d->offset = 0;
2997
2998                                 if (dist > (10.0 * scaledThickness))
2999                                         d->offset = dist - (10.0 * scaledThickness);
3000                         }
3001                         else
3002                         {
3003                                 obj->p[0] += delta;
3004                                 obj->p[1] += delta;
3005                         }
3006                 }
3007
3008                 break;
3009
3010         case OTText:
3011                 if (obj->hitObject)
3012                         obj->p[0] += delta;
3013
3014                 break;
3015
3016         case OTContainer:
3017                 // This is shitty, but works for now until I can code up something
3018                 // nicer :-)
3019 /*
3020 The idea is to make it so whichever point on the object in question is being dragged becomes the snap point for the container; shouldn't be too difficult to figure out how to do this.
3021 */
3022 //              TranslateObject(obj, delta);
3023                 TranslateContainer((Container *)obj, point, delta);
3024
3025                 break;
3026         default:
3027                 break;
3028         }
3029 }
3030
3031
3032 void DrawingView::AddDimensionTo(void * o)
3033 {
3034         Object * obj = (Object *)o;
3035
3036         switch (obj->type)
3037         {
3038         case OTLine:
3039                 document.Add(new Dimension(obj->p[0], obj->p[1]));
3040                 break;
3041         case OTCircle:
3042                 break;
3043         case OTEllipse:
3044                 break;
3045         case OTArc:
3046                 break;
3047         case OTSpline:
3048                 break;
3049         }
3050 }
3051