]> Shamusworld >> Repos - architektonas/blob - src/drawingview.cpp
048eb721b94a18166411bcedb60ae322ca013499
[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                 // Need to convert this, since it's in Qt coordinates (for wheelEvent)
2130                 oldPoint = Painter::QtToCartesianCoords(oldPoint);
2131         }
2132 }
2133
2134
2135 void DrawingView::wheelEvent(QWheelEvent * event)
2136 {
2137         double zoomFactor = 1.20;
2138         scrollWheelSeen = true;
2139
2140         if (event->angleDelta().y() < 0)
2141         {
2142                 if (Global::zoom > 400.0)
2143                         return;
2144
2145                 Global::zoom *= zoomFactor;
2146         }
2147         else
2148         {
2149                 if (Global::zoom < 0.125)
2150                         return;
2151
2152                 Global::zoom /= zoomFactor;
2153         }
2154
2155         Point np = Painter::QtToCartesianCoords(oldScrollPoint);
2156         Global::origin += (oldPoint - np);
2157
2158         emit(NeedZoomUpdate());
2159 }
2160
2161
2162 void DrawingView::keyPressEvent(QKeyEvent * event)
2163 {
2164         bool oldShift = shiftDown;
2165         bool oldCtrl = ctrlDown;
2166         bool oldAlt = altDown;
2167
2168         if (event->key() == Qt::Key_Shift)
2169                 shiftDown = true;
2170         else if (event->key() == Qt::Key_Control)
2171                 ctrlDown = true;
2172         else if (event->key() == Qt::Key_Alt)
2173                 altDown = true;
2174
2175         // If there's a change in any of the modifier key states, pass it on to
2176         // the current tool's handler
2177         if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
2178         {
2179                 if (Global::tool)
2180                         ToolHandler(ToolKeyDown, Point(0, 0));
2181
2182                 update();
2183         }
2184
2185         if (oldAlt != altDown)
2186         {
2187                 scrollDrag = true;
2188                 setCursor(Qt::SizeAllCursor);
2189                 oldPoint = oldScrollPoint;
2190         }
2191
2192         if (select.size() > 0)
2193         {
2194                 if (event->key() == Qt::Key_Up)
2195                 {
2196                         TranslateObjects(select, Point(0, +1.0));
2197                         update();
2198                 }
2199                 else if (event->key() == Qt::Key_Down)
2200                 {
2201                         TranslateObjects(select, Point(0, -1.0));
2202                         update();
2203                 }
2204                 else if (event->key() == Qt::Key_Right)
2205                 {
2206                         TranslateObjects(select, Point(+1.0, 0));
2207                         update();
2208                 }
2209                 else if (event->key() == Qt::Key_Left)
2210                 {
2211                         TranslateObjects(select, Point(-1.0, 0));
2212                         update();
2213                 }
2214         }
2215 }
2216
2217
2218 void DrawingView::keyReleaseEvent(QKeyEvent * event)
2219 {
2220         bool oldShift = shiftDown;
2221         bool oldCtrl = ctrlDown;
2222         bool oldAlt = altDown;
2223
2224         if (event->key() == Qt::Key_Shift)
2225                 shiftDown = false;
2226         else if (event->key() == Qt::Key_Control)
2227                 ctrlDown = false;
2228         else if (event->key() == Qt::Key_Alt)
2229                 altDown = false;
2230
2231         if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
2232         {
2233                 if (Global::tool)
2234                         ToolHandler(ToolKeyUp, Point(0, 0));
2235
2236                 update();
2237         }
2238
2239         if (oldAlt != altDown)
2240         {
2241                 scrollDrag = false;
2242                 setCursor(Qt::ArrowCursor);
2243         }
2244 }
2245
2246
2247 //
2248 // This looks strange, but it's really quite simple: We want a point that's
2249 // more than half-way to the next grid point to snap there while conversely we
2250 // want a point that's less than half-way to to the next grid point then snap
2251 // to the one before it. So we add half of the grid spacing to the point, then
2252 // divide by it so that we can remove the fractional part, then multiply it
2253 // back to get back to the correct answer.
2254 //
2255 Point DrawingView::SnapPointToGrid(Point point)
2256 {
2257         point += Global::gridSpacing / 2.0;             // *This* adds to Z!!!
2258         point /= Global::gridSpacing;
2259         point.x = floor(point.x);//need to fix this for negative numbers...
2260         point.y = floor(point.y);
2261         point.z = 0;                                    // Make *sure* Z doesn't go anywhere!!!
2262         point *= Global::gridSpacing;
2263         return point;
2264 }
2265
2266
2267 Point DrawingView::SnapPointToAngle(Point point)
2268 {
2269         // Snap to a single digit angle (using toolpoint #1 as the center)
2270         double angle = Vector::Angle(toolPoint[0], point);
2271         double length = Vector::Magnitude(toolPoint[0], point);
2272
2273         // Convert from radians to degrees
2274         double degAngle = angle * RADIANS_TO_DEGREES;
2275         double snapAngle = (double)((int)(degAngle + 0.5));
2276
2277         Vector v;
2278         v.SetAngleAndLength(snapAngle * DEGREES_TO_RADIANS, length);
2279         point = toolPoint[0] + v;
2280
2281         return point;
2282 }
2283
2284
2285 Rect DrawingView::GetObjectExtents(Object * obj)
2286 {
2287         // Default to empty rect, if object checks below fail for some reason
2288         Rect rect;
2289
2290         switch (obj->type)
2291         {
2292         case OTLine:
2293         case OTDimension:
2294         {
2295                 rect = Rect(obj->p[0], obj->p[1]);
2296                 break;
2297         }
2298
2299         case OTCircle:
2300         {
2301                 rect = Rect(obj->p[0], obj->p[0]);
2302                 rect.Expand(obj->radius[0]);
2303                 break;
2304         }
2305
2306         case OTArc:
2307         {
2308                 Arc * a = (Arc *)obj;
2309
2310                 double start = a->angle[0];
2311                 double end = start + a->angle[1];
2312                 rect = Rect(Point(cos(start), sin(start)), Point(cos(end), sin(end)));
2313
2314                 // If the end of the arc is before the beginning, add 360 degrees to it
2315                 if (end < start)
2316                         end += TAU;
2317
2318                 // Adjust the bounds depending on which axes are crossed
2319                 if ((start < QTR_TAU) && (end > QTR_TAU))
2320                         rect.t = 1.0;
2321
2322                 if ((start < HALF_TAU) && (end > HALF_TAU))
2323                         rect.l = -1.0;
2324
2325                 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
2326                         rect.b = -1.0;
2327
2328                 if ((start < TAU) && (end > TAU))
2329                         rect.r = 1.0;
2330
2331                 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
2332                         rect.t = 1.0;
2333
2334                 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
2335                         rect.l = -1.0;
2336
2337                 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
2338                         rect.b = -1.0;
2339
2340                 rect *= a->radius[0];
2341                 rect.Translate(a->p[0]);
2342                 break;
2343         }
2344
2345         case OTText:
2346         {
2347                 Text * t = (Text *)obj;
2348                 rect = Rect(t->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
2349                 break;
2350         }
2351
2352         case OTContainer:
2353         {
2354                 Container * c = (Container *)obj;
2355                 VPVectorIter i = c->objects.begin();
2356                 rect = GetObjectExtents((Object *)*i);
2357                 i++;
2358
2359                 for(; i!=c->objects.end(); i++)
2360                         rect |= GetObjectExtents((Object *)*i);
2361         }
2362
2363         default:
2364                 break;
2365         }
2366
2367         return rect;
2368 }
2369
2370
2371 void DrawingView::CheckObjectBounds(void)
2372 {
2373         VPVectorIter i;
2374
2375         for(i=document.objects.begin(); i!=document.objects.end(); i++)
2376         {
2377                 Object * obj = (Object *)(*i);
2378                 obj->selected = false;
2379
2380                 switch (obj->type)
2381                 {
2382                 case OTLine:
2383                 case OTDimension:
2384                 {
2385                         Line * l = (Line *)obj;
2386
2387                         if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
2388                                 l->selected = true;
2389
2390                         break;
2391                 }
2392
2393                 case OTCircle:
2394                 {
2395                         Circle * c = (Circle *)obj;
2396
2397                         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]))
2398                                 c->selected = true;
2399
2400                         break;
2401                 }
2402
2403                 case OTArc:
2404                 {
2405                         Arc * a = (Arc *)obj;
2406
2407                         double start = a->angle[0];
2408                         double end = start + a->angle[1];
2409                         QPointF p1(cos(start), sin(start));
2410                         QPointF p2(cos(end), sin(end));
2411                         QRectF bounds(p1, p2);
2412
2413 #if 1
2414                         // Swap X/Y coordinates if they're backwards...
2415                         if (bounds.left() > bounds.right())
2416                         {
2417                                 double temp = bounds.left();
2418                                 bounds.setLeft(bounds.right());
2419                                 bounds.setRight(temp);
2420                         }
2421
2422                         if (bounds.bottom() > bounds.top())
2423                         {
2424                                 double temp = bounds.bottom();
2425                                 bounds.setBottom(bounds.top());
2426                                 bounds.setTop(temp);
2427                         }
2428 #else
2429                         // Doesn't work as advertised! For shame!
2430                         bounds = bounds.normalized();
2431 #endif
2432
2433                         // If the end of the arc is before the beginning, add 360 degrees
2434                         // to it
2435                         if (end < start)
2436                                 end += TAU;
2437
2438                         // Adjust the bounds depending on which axes are crossed
2439                         if ((start < QTR_TAU) && (end > QTR_TAU))
2440                                 bounds.setTop(1.0);
2441
2442                         if ((start < HALF_TAU) && (end > HALF_TAU))
2443                                 bounds.setLeft(-1.0);
2444
2445                         if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
2446                                 bounds.setBottom(-1.0);
2447
2448                         if ((start < TAU) && (end > TAU))
2449                                 bounds.setRight(1.0);
2450
2451                         if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
2452                                 bounds.setTop(1.0);
2453
2454                         if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
2455                                 bounds.setLeft(-1.0);
2456
2457                         if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
2458                                 bounds.setBottom(-1.0);
2459
2460                         bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
2461                         bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
2462                         bounds.translate(a->p[0].x, a->p[0].y);
2463
2464                         if (Global::selection.contains(bounds))
2465                                 a->selected = true;
2466
2467                         break;
2468                 }
2469
2470                 case OTText:
2471                 {
2472                         Text * t = (Text *)obj;
2473                         Rect r(obj->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
2474
2475                         if (Global::selection.contains(r.l, r.t) && Global::selection.contains(r.r, r.b))
2476                                 t->selected = true;
2477
2478                         break;
2479                 }
2480
2481                 case OTContainer:
2482                 {
2483                         Container * c = (Container *)obj;
2484
2485                         if (Global::selection.contains(c->p[0].x, c->p[0].y) && Global::selection.contains(c->p[1].x, c->p[1].y))
2486                                 c->selected = true;
2487
2488                         break;
2489                 }
2490
2491 //              default:
2492 //                      break;
2493                 }
2494         }
2495 }
2496
2497
2498 bool DrawingView::HitTestObjects(Point point)
2499 {
2500         VPVectorIter i;
2501         numHovered = 0;
2502         bool needUpdate = false;
2503         hoverPointValid = false;
2504
2505         for(i=document.objects.begin(); i!=document.objects.end(); i++)
2506         {
2507                 Object * obj = (Object *)(*i);
2508
2509                 // If we're seeing the object we're dragging, skip it
2510                 if (draggingObject && (obj == dragged))
2511                         continue;
2512
2513                 if (HitTest(obj, point) == true)
2514                         needUpdate = true;
2515
2516                 if (obj->hovered)
2517                 {
2518                         numHovered++;
2519 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
2520                         emit ObjectHovered(obj);
2521                 }
2522         }
2523
2524         return needUpdate;
2525 }
2526
2527
2528 bool DrawingView::HitTest(Object * obj, Point point)
2529 {
2530         bool needUpdate = false;
2531
2532         switch (obj->type)
2533         {
2534         case OTLine:
2535         {
2536                 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
2537                 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
2538                 Vector lineSegment = obj->p[1] - obj->p[0];
2539                 Vector v1 = point - obj->p[0];
2540                 Vector v2 = point - obj->p[1];
2541                 double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
2542                 double distance;
2543
2544                 if (t < 0.0)
2545                         distance = v1.Magnitude();
2546                 else if (t > 1.0)
2547                         distance = v2.Magnitude();
2548                 else
2549                         // distance = ?Det?(ls, v1) / |ls|
2550                         distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
2551                                 / lineSegment.Magnitude());
2552
2553                 if ((v1.Magnitude() * Global::zoom) < 8.0)
2554                 {
2555                         obj->hitPoint[0] = true;
2556                         hoverPoint = obj->p[0];
2557                         hoverPointValid = true;
2558                 }
2559                 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2560                 {
2561                         obj->hitPoint[1] = true;
2562                         hoverPoint = obj->p[1];
2563                         hoverPointValid = true;
2564                 }
2565                 else if ((distance * Global::zoom) < 5.0)
2566                         obj->hitObject = true;
2567
2568                 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
2569
2570                 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
2571                         needUpdate = true;
2572
2573                 break;
2574         }
2575
2576         case OTCircle:
2577         {
2578                 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
2579                 obj->hitPoint[0] = obj->hitObject = false;
2580                 double length = Vector::Magnitude(obj->p[0], point);
2581
2582                 if ((length * Global::zoom) < 8.0)
2583                 {
2584                         obj->hitPoint[0] = true;
2585                         hoverPoint = obj->p[0];
2586                         hoverPointValid = true;
2587                 }
2588                 else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
2589                         obj->hitObject = true;
2590
2591                 obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
2592
2593                 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
2594                         needUpdate = true;
2595
2596                 break;
2597         }
2598
2599         case OTArc:
2600         {
2601                 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
2602                 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
2603                 double length = Vector::Magnitude(obj->p[0], point);
2604                 double angle = Vector::Angle(obj->p[0], point);
2605
2606                 // Make sure we get the angle in the correct spot
2607                 if (angle < obj->angle[0])
2608                         angle += TAU;
2609
2610                 // Get the span that we're pointing at...
2611                 double span = angle - obj->angle[0];
2612
2613                 // N.B.: Still need to hit test the arc start & arc span handles...
2614                 double spanAngle = obj->angle[0] + obj->angle[1];
2615                 Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
2616                 Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
2617                 double length2 = Vector::Magnitude(point, handle1);
2618                 double length3 = Vector::Magnitude(point, handle2);
2619
2620                 if ((length * Global::zoom) < 8.0)
2621                 {
2622                         obj->hitPoint[0] = true;
2623                         hoverPoint = obj->p[0];
2624                         hoverPointValid = true;
2625                 }
2626                 else if ((length2 * Global::zoom) < 8.0)
2627                 {
2628                         obj->hitPoint[1] = true;
2629                         hoverPoint = handle1;
2630                         hoverPointValid = true;
2631                 }
2632                 else if ((length3 * Global::zoom) < 8.0)
2633                 {
2634                         obj->hitPoint[2] = true;
2635                         hoverPoint = handle2;
2636                         hoverPointValid = true;
2637                 }
2638                 else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
2639                         obj->hitObject = true;
2640
2641                 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
2642
2643                 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
2644                         needUpdate = true;
2645
2646                 break;
2647         }
2648
2649         case OTDimension:
2650         {
2651                 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHP3 = obj->hitPoint[3], oldHP4 = obj->hitPoint[4], oldHO = obj->hitObject;
2652                 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitPoint[3] = obj->hitPoint[4] = obj->hitObject = false;
2653
2654                 Dimension * d = (Dimension *)obj;
2655
2656                 Vector orthogonal = Vector::Normal(d->lp[0], d->lp[1]);
2657                 // Get our line parallel to our points
2658                 float scaledThickness = Global::scale * obj->thickness;
2659 #if 1
2660                 Point p1 = d->lp[0] + (orthogonal * (d->offset + (10.0 * scaledThickness)));
2661                 Point p2 = d->lp[1] + (orthogonal * (d->offset + (10.0 * scaledThickness)));
2662 #else
2663                 Point p1 = d->lp[0] + (orthogonal * (10.0 + d->offset) * scaledThickness);
2664                 Point p2 = d->lp[1] + (orthogonal * (10.0 + d->offset) * scaledThickness);
2665 #endif
2666                 Point p3(p1, point);
2667
2668                 Vector v1(d->p[0], point);
2669                 Vector v2(d->p[1], point);
2670                 Vector lineSegment(p1, p2);
2671                 double t = Geometry::ParameterOfLineAndPoint(p1, p2, point);
2672                 double distance;
2673                 Point midpoint = (p1 + p2) / 2.0;
2674                 Point hFSPoint = Point(midpoint, point);
2675                 Point hCS1Point = Point((p1 + midpoint) / 2.0, point);
2676                 Point hCS2Point = Point((midpoint + p2) / 2.0, point);
2677
2678                 if (t < 0.0)
2679                         distance = v1.Magnitude();
2680                 else if (t > 1.0)
2681                         distance = v2.Magnitude();
2682                 else
2683                         // distance = ?Det?(ls, v1) / |ls|
2684                         distance = fabs((lineSegment.x * p3.y - p3.x * lineSegment.y)
2685                                 / lineSegment.Magnitude());
2686
2687                 if ((v1.Magnitude() * Global::zoom) < 8.0)
2688                         obj->hitPoint[0] = true;
2689                 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2690                         obj->hitPoint[1] = true;
2691                 else if ((distance * Global::zoom) < 5.0)
2692                         obj->hitObject = true;
2693
2694                 if ((hFSPoint.Magnitude() * Global::zoom) < 8.0)
2695                         obj->hitPoint[2] = true;
2696                 else if ((hCS1Point.Magnitude() * Global::zoom) < 8.0)
2697                         obj->hitPoint[3] = true;
2698                 else if ((hCS2Point.Magnitude() * Global::zoom) < 8.0)
2699                         obj->hitPoint[4] = true;
2700
2701                 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitPoint[3] || obj->hitPoint[4] || obj->hitObject ? true : false);
2702
2703                 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHP3 != obj->hitPoint[3]) || (oldHP4 != obj->hitPoint[4]) || (oldHO != obj->hitObject))
2704                         needUpdate = true;
2705
2706                 break;
2707         }
2708
2709         case OTText:
2710         {
2711                 Text * t = (Text *)obj;
2712                 bool oldHO = obj->hitObject;
2713                 obj->hitObject = false;
2714
2715                 Rect r(obj->p[0], Point(obj->p[0].x + t->extents.Width(), obj->p[0].y - t->extents.Height()));
2716 //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);
2717
2718                 if (r.Contains(point))
2719                         obj->hitObject = true;
2720
2721                 obj->hovered = (obj->hitObject ? true : false);
2722
2723                 if (oldHO != obj->hitObject)
2724                         needUpdate = true;
2725
2726                 break;
2727         }
2728
2729         case OTContainer:
2730         {
2731                 // Containers must be recursively tested...  Or do they???
2732 /*
2733 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.
2734 */
2735 //              bool oldHitObj = c->hitObject, oldHovered = c->hovered;
2736 //              Object * oldClicked = c->clicked;
2737 /*
2738 still need to compare old state to new state, and set things up based upon that...
2739 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);
2740 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.
2741 */
2742                 Container * c = (Container *)obj;
2743                 c->hitObject = false;
2744                 c->hovered = false;
2745                 c->clicked = NULL;
2746
2747                 VPVector flat = Flatten(c);
2748
2749 //printf("HitTest::OTContainer (size=%li)\n", flat.size());
2750                 for(VPVectorIter i=flat.begin(); i!=flat.end(); i++)
2751                 {
2752                         Object * cObj = (Object *)(*i);
2753
2754                         // Skip the flattened containers (if any)...
2755                         if (cObj->type == OTContainer)
2756                                 continue;
2757
2758                         // We do it this way instead of needUpdate = HitTest() because we
2759                         // are checking more than one object, and that way of doing will
2760                         // not return consistent results.
2761                         if (HitTest(cObj, point) == true)
2762 //                      {
2763 //printf("HitTest::OTContainer, subobj ($%llX) hit!\n", cObj);
2764                                 needUpdate = true;
2765 //                              c->hitObject = true;
2766 //                              c->clicked = cObj;
2767 //                              c->hovered = true;
2768 //                      }
2769
2770                         // Same reasons for doing it this way here apply.
2771                         if (cObj->hitObject == true)
2772                         {
2773 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2774                                 c->hitObject = true;
2775                                 c->clicked = cObj;
2776                         }//*/
2777
2778                         if (cObj->hitPoint[0] == true)
2779                         {
2780 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2781                                 c->hitPoint[0] = true;
2782                                 c->clicked = cObj;
2783                         }//*/
2784
2785                         if (cObj->hitPoint[1] == true)
2786                         {
2787 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2788                                 c->hitPoint[1] = true;
2789                                 c->clicked = cObj;
2790                         }//*/
2791
2792                         if (cObj->hitPoint[2] == true)
2793                         {
2794 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2795                                 c->hitPoint[2] = true;
2796                                 c->clicked = cObj;
2797                         }//*/
2798
2799                         if (cObj->hovered == true)
2800                                 c->hovered = true;//*/
2801                 }
2802
2803                 break;
2804         }
2805
2806         default:
2807                 break;
2808         }
2809
2810         return needUpdate;
2811 }
2812
2813
2814 bool DrawingView::HandleObjectClicked(void)
2815 {
2816         if (dragged->type == OTDimension)
2817         {
2818                 Dimension * d = (Dimension *)dragged;
2819
2820                 if (d->hitPoint[2])
2821                 {
2822                         // Hit the "flip sides" switch, so flip 'em
2823                         Point temp = d->p[0];
2824                         d->p[0] = d->p[1];
2825                         d->p[1] = temp;
2826                         return true;
2827                 }
2828                 else if (d->hitPoint[3])
2829                 {
2830                         // There are three cases here: aligned, horizontal, & vertical.
2831                         // Aligned and horizontal do the same thing, vertical goes back to
2832                         // linear.
2833                         if (d->subtype == DTLinearVert)
2834                                 d->subtype = DTLinear;
2835                         else
2836                                 d->subtype = DTLinearVert;
2837
2838                         return true;
2839                 }
2840                 else if (d->hitPoint[4])
2841                 {
2842                         // There are three cases here: aligned, horizontal, & vertical.
2843                         // Aligned and vertical do the same thing, horizontal goes back to
2844                         // linear.
2845                         if (d->subtype == DTLinearHorz)
2846                                 d->subtype = DTLinear;
2847                         else
2848                                 d->subtype = DTLinearHorz;
2849
2850                         return true;
2851                 }
2852         }
2853
2854         return false;
2855 }
2856
2857
2858 void DrawingView::HandleObjectMovement(Point point)
2859 {
2860         Point delta = point - oldPoint;
2861 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
2862 //      Object * obj = (Object *)hover[0];
2863         Object * obj = dragged;
2864 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
2865 //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"));
2866
2867         switch (obj->type)
2868         {
2869         case OTLine:
2870                 if (obj->hitPoint[0])
2871                 {
2872                         if (Global::fixedLength)
2873                         {
2874                                 Vector line = point - obj->p[1];
2875                                 Vector unit = line.Unit();
2876                                 point = obj->p[1] + (unit * obj->length);
2877                         }
2878
2879                         obj->p[0] = point;
2880                 }
2881                 else if (obj->hitPoint[1])
2882                 {
2883                         if (Global::fixedLength)
2884                         {
2885                                 Vector line = point - obj->p[0];
2886                                 Vector unit = line.Unit();
2887                                 point = obj->p[0] + (unit * obj->length);
2888                         }
2889
2890                         obj->p[1] = point;
2891                 }
2892                 else if (obj->hitObject)
2893                 {
2894                         obj->p[0] += delta;
2895                         obj->p[1] += delta;
2896                 }
2897
2898                 break;
2899
2900         case OTCircle:
2901                 if (obj->hitPoint[0])
2902                         obj->p[0] = point;
2903                 else if (obj->hitObject)
2904                 {
2905                         double oldRadius = obj->length;
2906                         obj->radius[0] = Vector::Magnitude(obj->p[0], point);
2907
2908                         QString text = QObject::tr("Radius: %1\nScale: %2%");
2909                         informativeText = text.arg(obj->radius[0], 0, 'd', 4).arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0);
2910                 }
2911
2912                 break;
2913
2914         case OTArc:
2915                 if (obj->hitPoint[0])
2916                         obj->p[0] = point;
2917                 else if (obj->hitPoint[1])
2918                 {
2919                         // Change the Arc's span (handle #1)
2920                         if (shiftDown)
2921                         {
2922                                 double angle = Vector::Angle(obj->p[0], point);
2923                                 double delta = angle - obj->angle[0];
2924
2925                                 if (delta < 0)
2926                                         delta += TAU;
2927
2928                                 obj->angle[1] -= delta;
2929                                 obj->angle[0] = angle;
2930
2931                                 if (obj->angle[1] < 0)
2932                                         obj->angle[1] += TAU;
2933
2934                                 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
2935                                 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);
2936                                 return;
2937                         }
2938
2939                         double angle = Vector::Angle(obj->p[0], point);
2940                         obj->angle[0] = angle;
2941                         QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
2942                         informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 4);
2943                 }
2944                 else if (obj->hitPoint[2])
2945                 {
2946                         // Change the Arc's span (handle #2)
2947                         if (shiftDown)
2948                         {
2949                                 double angle = Vector::Angle(obj->p[0], point);
2950                                 obj->angle[1] = angle - obj->angle[0];
2951
2952                                 if (obj->angle[1] < 0)
2953                                         obj->angle[1] += TAU;
2954
2955                                 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
2956                                 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);
2957                                 return;
2958                         }
2959
2960                         double angle = Vector::Angle(obj->p[0], point);
2961                         obj->angle[0] = angle - obj->angle[1];
2962
2963                         if (obj->angle[0] < 0)
2964                                 obj->angle[0] += TAU;
2965
2966                         QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
2967                         informativeText = text.arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 4);
2968                 }
2969                 else if (obj->hitObject)
2970                 {
2971                         if (shiftDown)
2972                         {
2973                                 return;
2974                         }
2975
2976                         obj->radius[0] = Vector::Magnitude(obj->p[0], point);
2977                         QString text = QObject::tr("Radius: %1");
2978                         informativeText = text.arg(obj->radius[0], 0, 'd', 4);
2979                 }
2980
2981                 break;
2982
2983         case OTDimension:
2984                 if (obj->hitPoint[0])
2985                         obj->p[0] = point;
2986                 else if (obj->hitPoint[1])
2987                         obj->p[1] = point;
2988                 else if (obj->hitObject)
2989                 {
2990                         // Move measurement lines in/out
2991                         if (shiftDown)
2992                         {
2993                                 Dimension * d = (Dimension *)obj;
2994                                 double dist = Geometry::DistanceToLineFromPoint(d->lp[0], d->lp[1], point);
2995                                 float scaledThickness = Global::scale * obj->thickness;
2996                                 // Looks like offset is 0 to +MAX, but line is at 10.0.  So
2997                                 // anything less than 10.0 should set the offset to 0.
2998                                 d->offset = 0;
2999
3000                                 if (dist > (10.0 * scaledThickness))
3001                                         d->offset = dist - (10.0 * scaledThickness);
3002                         }
3003                         else
3004                         {
3005                                 obj->p[0] += delta;
3006                                 obj->p[1] += delta;
3007                         }
3008                 }
3009
3010                 break;
3011
3012         case OTText:
3013                 if (obj->hitObject)
3014                         obj->p[0] += delta;
3015
3016                 break;
3017
3018         case OTContainer:
3019                 // This is shitty, but works for now until I can code up something
3020                 // nicer :-)
3021 /*
3022 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.
3023 */
3024 //              TranslateObject(obj, delta);
3025                 TranslateContainer((Container *)obj, point, delta);
3026
3027                 break;
3028         default:
3029                 break;
3030         }
3031 }
3032
3033
3034 void DrawingView::AddDimensionTo(void * o)
3035 {
3036         Object * obj = (Object *)o;
3037
3038         switch (obj->type)
3039         {
3040         case OTLine:
3041                 document.Add(new Dimension(obj->p[0], obj->p[1]));
3042                 break;
3043         case OTCircle:
3044                 break;
3045         case OTEllipse:
3046                 break;
3047         case OTArc:
3048                 break;
3049         case OTSpline:
3050                 break;
3051         }
3052 }
3053