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