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