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