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