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