]> Shamusworld >> Repos - architektonas/blob - src/drawingview.cpp
Fixed click selection and pen functionality.
[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(255, 127, 0, 255)));
361                 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
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         }
981 }
982
983 void DrawingView::LineHandler(int mode, Point p)
984 {
985         switch (mode)
986         {
987         case ToolMouseDown:
988 /*              toolObj[0] = NULL;
989
990                 // Check to see if we can do a circle tangent snap
991                 if (numHovered == 1)
992                 {
993                         VPVector hover = GetHovered();
994                         Object * obj = (Object *)hover[0];
995
996                         // 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! :-)
997                         if ((obj->type == OTCircle) && (obj->hitPoint[0] == false))
998                                 toolObj[0] = obj;
999                 }*/
1000
1001                 if (Global::toolState == TSNone)
1002                         toolPoint[0] = p;
1003                 else
1004                         toolPoint[1] = p;
1005
1006                 break;
1007
1008         case ToolMouseMove:
1009                 if (Global::toolState == TSNone)
1010                         toolPoint[0] = p;
1011                 else
1012                 {
1013                         toolPoint[1] = p;
1014 /*                      bool isCircle = false;
1015
1016                         if (numHovered == 1)
1017                         {
1018                                 VPVector hover = GetHovered();
1019                                 Object * obj = (Object *)hover[0];
1020
1021                                 if ((obj->type == OTCircle) && (obj->hitPoint[0] == false))
1022                                 {
1023                                         isCircle = true;
1024                                         toolObj[1] = obj;
1025                                 }
1026                         }
1027
1028                         // Adjust initial point if it's on a circle (tangent point)
1029                         if (toolObj[0] != NULL)
1030                         {
1031                                 if (isCircle)
1032                                 {
1033                                         Geometry::FindTangents(toolObj[0], toolObj[1]);
1034
1035                                         if (Global::numIntersectPoints > 0)
1036                                         {
1037                                                 toolPoint[0] = Global::intersectPoint[0];
1038                                                 toolPoint[1] = Global::intersectPoint[1];
1039                                         }
1040                                 }
1041                                 else
1042                                 {
1043                                         Geometry::FindTangents(toolObj[0], p);
1044
1045                                         if (Global::numIntersectPoints > 0)
1046                                                 toolPoint[0] = Global::intersectPoint[0];
1047                                 }
1048                         }
1049                         else
1050                         {
1051                                 if (isCircle)
1052                                 {
1053                                         Geometry::FindTangents(toolObj[1], toolPoint[0]);
1054
1055                                         if (Global::numIntersectPoints > 0)
1056                                                 toolPoint[1] = Global::intersectPoint[0];
1057                                 }
1058                         }*/
1059                 }
1060
1061                 break;
1062
1063         case ToolMouseUp:
1064                 if (Global::toolState == TSNone)
1065                 {
1066                         Global::toolState = TSPoint2;
1067                         // Prevent spurious line from drawing...
1068                         toolPoint[1] = toolPoint[0];
1069                 }
1070                 else if ((Global::toolState == TSPoint2) && shiftDown)
1071                 {
1072                         // Key override is telling us to make a new line, not continue the
1073                         // previous one.
1074                         toolPoint[0] = toolPoint[1];
1075                 }
1076                 else
1077                 {
1078                         Line * l = new Line(toolPoint[0], toolPoint[1], Global::penWidth, Global::penColor, Global::penStyle);
1079                         l->layer = Global::activeLayer;
1080                         document.objects.push_back(l);
1081                         toolPoint[0] = toolPoint[1];
1082                 }
1083         }
1084 }
1085
1086 void DrawingView::CircleHandler(int mode, Point p)
1087 {
1088         switch (mode)
1089         {
1090         case ToolMouseDown:
1091                 if (Global::toolState == TSNone)
1092                         toolPoint[0] = p;
1093                 else
1094                         toolPoint[1] = p;
1095
1096                 break;
1097
1098         case ToolMouseMove:
1099                 if (Global::toolState == TSNone)
1100                         toolPoint[0] = p;
1101                 else
1102                         toolPoint[1] = p;
1103
1104                 break;
1105
1106         case ToolMouseUp:
1107                 if (Global::toolState == TSNone)
1108                 {
1109                         Global::toolState = TSPoint2;
1110                         // Prevent spurious line from drawing...
1111                         toolPoint[1] = toolPoint[0];
1112                 }
1113                 else if ((Global::toolState == TSPoint2) && shiftDown)
1114                 {
1115                         // Key override is telling us to make a new line, not continue the
1116                         // previous one.
1117                         toolPoint[0] = toolPoint[1];
1118                 }
1119                 else
1120                 {
1121                         double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
1122                         Circle * c = new Circle(toolPoint[0], length, Global::penWidth, Global::penColor, Global::penStyle);
1123                         c->layer = Global::activeLayer;
1124                         document.objects.push_back(c);
1125                         toolPoint[0] = toolPoint[1];
1126                         Global::toolState = TSNone;
1127                 }
1128         }
1129 }
1130
1131 void DrawingView::ArcHandler(int mode, Point p)
1132 {
1133         switch (mode)
1134         {
1135         case ToolMouseDown:
1136                 if (Global::toolState == TSNone)
1137                         toolPoint[0] = p;
1138                 else if (Global::toolState == TSPoint2)
1139                         toolPoint[1] = p;
1140                 else if (Global::toolState == TSPoint3)
1141                         toolPoint[2] = p;
1142                 else
1143                         toolPoint[3] = p;
1144
1145                 break;
1146
1147         case ToolMouseMove:
1148                 if (Global::toolState == TSNone)
1149                         toolPoint[0] = p;
1150                 else if (Global::toolState == TSPoint2)
1151                         toolPoint[1] = p;
1152                 else if (Global::toolState == TSPoint3)
1153                 {
1154                         toolPoint[2] = p;
1155                         angleSnap = true;
1156                 }
1157                 else
1158                 {
1159                         toolPoint[3] = p;
1160                         angleSnap = true;
1161                 }
1162
1163                 break;
1164
1165         case ToolMouseUp:
1166                 if (Global::toolState == TSNone)
1167                 {
1168                         // Prevent spurious line from drawing...
1169                         toolPoint[1] = toolPoint[0];
1170                         Global::toolState = TSPoint2;
1171                 }
1172                 else if (Global::toolState == TSPoint2)
1173                 {
1174                         if (shiftDown)
1175                         {
1176                                 // Key override is telling us to start arc at new center, not
1177                                 // continue the current one.
1178                                 toolPoint[0] = toolPoint[1];
1179                                 return;
1180                         }
1181
1182                         // Set the radius in toolPoint[1].x
1183                         toolPoint[1].x = Vector::Magnitude(toolPoint[0], toolPoint[1]);
1184                         Global::toolState = TSPoint3;
1185                 }
1186                 else if (Global::toolState == TSPoint3)
1187                 {
1188                         // Set the angle in toolPoint[2].x
1189                         toolPoint[2].x = Vector::Angle(toolPoint[0], toolPoint[2]);
1190                         Global::toolState = TSPoint4;
1191                 }
1192                 else
1193                 {
1194                         double endAngle = Vector::Angle(toolPoint[0], toolPoint[3]);
1195                         double span = endAngle - toolPoint[2].x;
1196
1197                         if (span < 0)
1198                                 span += TAU;
1199
1200                         Arc * arc = new Arc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span, Global::penWidth, Global::penColor, Global::penStyle);
1201                         arc->layer = Global::activeLayer;
1202                         document.objects.push_back(arc);
1203                         Global::toolState = TSNone;
1204                 }
1205         }
1206 }
1207
1208 void DrawingView::RotateHandler(int mode, Point p)
1209 {
1210         switch (mode)
1211         {
1212         case ToolMouseDown:
1213                 if (Global::toolState == TSNone)
1214                 {
1215                         toolPoint[0] = p;
1216 //                      SavePointsFrom(select, toolScratch);
1217                         CopyObjects(select, toolScratch2);
1218                         Global::toolState = TSPoint1;
1219                 }
1220                 else if (Global::toolState == TSPoint1)
1221                         toolPoint[0] = p;
1222                 else
1223                         toolPoint[1] = p;
1224
1225                 break;
1226
1227         case ToolMouseMove:
1228                 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1229                         toolPoint[0] = p;
1230                 else if (Global::toolState == TSPoint2)
1231                 {
1232                         toolPoint[1] = p;
1233
1234                         if (shiftDown)
1235                                 return;
1236
1237                         angleSnap = true;
1238                         double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1239                         VPVectorIter j = select.begin();
1240 //                      std::vector<Object>::iterator i = toolScratch.begin();
1241                         VPVectorIter i = toolScratch2.begin();
1242
1243 //                      for(; i!=toolScratch.end(); i++, j++)
1244                         for(; i!=toolScratch2.end(); i++, j++)
1245                         {
1246 //                              Object objT = *i;
1247 //                              Point p1 = Geometry::RotatePointAroundPoint(objT.p[0], toolPoint[0], angle);
1248 //                              Point p2 = Geometry::RotatePointAroundPoint(objT.p[1], toolPoint[0], angle);
1249                                 Object * objT = (Object *)(*i);
1250                                 Object * objS = (Object *)(*j);
1251
1252                                 Point p1 = Geometry::RotatePointAroundPoint(objT->p[0], toolPoint[0], angle);
1253                                 Point p2 = Geometry::RotatePointAroundPoint(objT->p[1], toolPoint[0], angle);
1254
1255                                 objS->p[0] = p1;
1256                                 objS->p[1] = p2;
1257
1258 //                              if (objT.type == OTArc || objT.type == OTText)
1259                                 if (objT->type == OTArc || objT->type == OTText)
1260                                 {
1261 //                                      objS->angle[0] = objT.angle[0] + angle;
1262                                         objS->angle[0] = objT->angle[0] + angle;
1263
1264                                         if (objS->angle[0] > TAU)
1265                                                 objS->angle[0] -= TAU;
1266                                 }
1267 //                              else if (objT.type == OTContainer)
1268                                 else if (objT->type == OTContainer)
1269                                 {
1270                                         // 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...]
1271 //                                      Container * c = (Container *)&objT;
1272                                         Container * c = (Container *)objT;
1273                                         Container * c2 = (Container *)objS;
1274                                         VPVectorIter l = c->objects.begin();
1275                                         // TODO: Rotate items in the container
1276                                         // TODO: Make this recursive
1277                                         for(VPVectorIter k=c2->objects.begin(); k!=c2->objects.end(); k++, l++)
1278                                         {
1279                                                 Object * obj3 = (Object *)(*k);
1280                                                 Object * obj4 = (Object *)(*l);
1281
1282                                                 p1 = Geometry::RotatePointAroundPoint(obj4->p[0], toolPoint[0], angle);
1283                                                 p2 = Geometry::RotatePointAroundPoint(obj4->p[1], toolPoint[0], angle);
1284
1285                                                 obj3->p[0] = p1;
1286                                                 obj3->p[1] = p2;
1287 //                                              obj3->angle[0] = objT.angle[0] + angle;
1288                                                 obj3->angle[0] = obj4->angle[0] + angle;
1289
1290                                                 if (obj3->angle[0] > TAU)
1291                                                         obj3->angle[0] -= TAU;
1292                                         }
1293
1294                                         Rect r = GetObjectExtents(objS);
1295                                         c2->p[0] = r.TopLeft();
1296                                         c2->p[1] = r.BottomRight();
1297                                 }
1298                         }
1299                 }
1300
1301                 break;
1302
1303         case ToolMouseUp:
1304                 if (Global::toolState == TSPoint1)
1305                 {
1306                         Global::toolState = TSPoint2;
1307                         // Prevent spurious line from drawing...
1308                         toolPoint[1] = toolPoint[0];
1309                 }
1310                 else if ((Global::toolState == TSPoint2) && shiftDown)
1311                 {
1312                         // Key override is telling us to make a new line, not continue the
1313                         // previous one.
1314                         toolPoint[0] = toolPoint[1];
1315                 }
1316                 else
1317                 {
1318                         // Either we're finished with our rotate, or we're stamping a copy.
1319                         if (ctrlDown)
1320                         {
1321                                 // Stamp a copy of the selection at the current rotation & bail
1322                                 VPVector temp;
1323                                 CopyObjects(select, temp);
1324                                 ClearSelected(temp);
1325                                 AddObjectsTo(document.objects, temp);
1326 //                              RestorePointsTo(select, toolScratch);
1327                                 RestorePointsTo(select, toolScratch2);
1328                                 return;
1329                         }
1330
1331                         toolPoint[0] = p;
1332                         Global::toolState = TSPoint1;
1333 //                      SavePointsFrom(select, toolScratch);
1334                         DeleteContents(toolScratch2);
1335                         CopyObjects(select, toolScratch2);
1336                 }
1337
1338                 break;
1339
1340         case ToolKeyDown:
1341                 // Reset the selection if shift held down...
1342                 if (shiftDown)
1343 //                      RestorePointsTo(select, toolScratch);
1344                         RestorePointsTo(select, toolScratch2);
1345
1346                 break;
1347
1348         case ToolKeyUp:
1349                 // Reset selection when key is let up
1350                 if (!shiftDown)
1351                         RotateHandler(ToolMouseMove, toolPoint[1]);
1352
1353                 break;
1354
1355         case ToolCleanup:
1356 //              RestorePointsTo(select, toolScratch);
1357                 RestorePointsTo(select, toolScratch2);
1358                 DeleteContents(toolScratch2);
1359         }
1360 }
1361
1362 void DrawingView::MirrorHandler(int mode, Point p)
1363 {
1364         switch (mode)
1365         {
1366         case ToolMouseDown:
1367                 if (Global::toolState == TSNone)
1368                 {
1369                         toolPoint[0] = p;
1370                         SavePointsFrom(select, toolScratch);
1371                         Global::toolState = TSPoint1;
1372                 }
1373                 else if (Global::toolState == TSPoint1)
1374                         toolPoint[0] = p;
1375                 else
1376                         toolPoint[1] = p;
1377
1378                 break;
1379
1380         case ToolMouseMove:
1381                 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1382                         toolPoint[0] = p;
1383                 else if (Global::toolState == TSPoint2)
1384                 {
1385                         toolPoint[1] = p;
1386
1387                         if (shiftDown)
1388                                 return;
1389
1390                         angleSnap = true;
1391                         double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1392                         VPVectorIter j = select.begin();
1393                         std::vector<Object>::iterator i = toolScratch.begin();
1394
1395                         for(; i!=toolScratch.end(); i++, j++)
1396                         {
1397                                 Object obj = *i;
1398                                 Point p1 = Geometry::MirrorPointAroundLine(obj.p[0], toolPoint[0], toolPoint[1]);
1399                                 Point p2 = Geometry::MirrorPointAroundLine(obj.p[1], toolPoint[0], toolPoint[1]);
1400                                 Object * obj2 = (Object *)(*j);
1401                                 obj2->p[0] = p1;
1402                                 obj2->p[1] = p2;
1403
1404 /*
1405 N.B.: When mirroring an arc thru a horizontal axis, this causes the arc to have
1406       a negative start angle which makes it impossible to interact with.
1407       !!! FIX !!!
1408 */
1409                                 if (obj.type == OTArc)
1410                                 {
1411                                         // This is 2*mirror angle - obj angle - obj span
1412                                         obj2->angle[0] = (2.0 * angle) - obj.angle[0] - obj.angle[1];
1413
1414                                         if (obj2->angle[0] > TAU)
1415                                                 obj2->angle[0] -= TAU;
1416                                 }
1417                         }
1418                 }
1419
1420                 break;
1421
1422         case ToolMouseUp:
1423                 if (Global::toolState == TSPoint1)
1424                 {
1425                         Global::toolState = TSPoint2;
1426                         // Prevent spurious line from drawing...
1427                         toolPoint[1] = toolPoint[0];
1428                 }
1429                 else if ((Global::toolState == TSPoint2) && shiftDown)
1430                 {
1431                         // Key override is telling us to make a new line, not continue the
1432                         // previous one.
1433                         toolPoint[0] = toolPoint[1];
1434                 }
1435                 else
1436                 {
1437                         // Either we're finished with our rotate, or we're stamping a copy.
1438                         if (ctrlDown)
1439                         {
1440                                 // Stamp a copy of the selection at the current rotation & bail
1441                                 VPVector temp;
1442                                 CopyObjects(select, temp);
1443                                 ClearSelected(temp);
1444                                 AddObjectsTo(document.objects, temp);
1445                                 RestorePointsTo(select, toolScratch);
1446                                 return;
1447                         }
1448
1449                         toolPoint[0] = p;
1450                         Global::toolState = TSPoint1;
1451                         SavePointsFrom(select, toolScratch);
1452                 }
1453
1454                 break;
1455
1456         case ToolKeyDown:
1457                 // Reset the selection if shift held down...
1458                 if (shiftDown)
1459                         RestorePointsTo(select, toolScratch);
1460
1461                 break;
1462
1463         case ToolKeyUp:
1464                 // Reset selection when key is let up
1465                 if (!shiftDown)
1466                         MirrorHandler(ToolMouseMove, toolPoint[1]);
1467
1468                 break;
1469
1470         case ToolCleanup:
1471                 RestorePointsTo(select, toolScratch);
1472         }
1473 }
1474
1475 void DrawingView::DimensionHandler(int mode, Point p)
1476 {
1477         switch (mode)
1478         {
1479         case ToolMouseDown:
1480                 if (Global::toolState == TSNone)
1481                         toolPoint[0] = p;
1482                 else
1483                         toolPoint[1] = p;
1484
1485                 break;
1486
1487         case ToolMouseMove:
1488                 if (Global::toolState == TSNone)
1489                         toolPoint[0] = p;
1490                 else
1491                         toolPoint[1] = p;
1492
1493                 break;
1494
1495         case ToolMouseUp:
1496                 if (Global::toolState == TSNone)
1497                 {
1498                         Global::toolState = TSPoint2;
1499                         // Prevent spurious line from drawing...
1500                         toolPoint[1] = toolPoint[0];
1501                 }
1502                 else if ((Global::toolState == TSPoint2) && shiftDown)
1503                 {
1504                         // Key override is telling us to make a new line, not continue the
1505                         // previous one.
1506                         toolPoint[0] = toolPoint[1];
1507                 }
1508                 else
1509                 {
1510                         Dimension * d = new Dimension(toolPoint[0], toolPoint[1], DTLinear, 0, Global::penWidth);
1511                         d->layer = Global::activeLayer;
1512                         document.objects.push_back(d);
1513                         Global::toolState = TSNone;
1514                 }
1515         }
1516 }
1517
1518 void DrawingView::DeleteHandler(int mode, Point /*p*/)
1519 {
1520         switch (mode)
1521         {
1522         case ToolMouseDown:
1523         {
1524                 VPVector hovered = GetHovered();
1525
1526                 RemoveHoveredObjects(document.objects);
1527                 DeleteContents(hovered);
1528         }
1529                 break;
1530
1531         case ToolMouseMove:
1532                 break;
1533
1534         case ToolMouseUp:
1535                 break;
1536
1537         case ToolKeyDown:
1538                 break;
1539
1540         case ToolKeyUp:
1541                 break;
1542
1543         case ToolCleanup:
1544                 break;
1545         }
1546 }
1547
1548 void DrawingView::TriangulateHandler(int mode, Point /*p*/)
1549 {
1550         switch (mode)
1551         {
1552         case ToolMouseDown:
1553         {
1554                 // Skip if nothing hovered...
1555                 if (numHovered != 1)
1556                         break;
1557
1558                 VPVector hover = GetHovered();
1559                 Object * obj = (Object *)hover[0];
1560
1561                 // Skip if it's not a line...
1562                 if (obj->type != OTLine)
1563                         break;
1564
1565                 if (Global::toolState == TSNone)
1566                         toolObj[0] = obj;
1567                 else if (Global::toolState == TSPoint2)
1568                         toolObj[1] = obj;
1569                 else
1570                         toolObj[2] = obj;
1571
1572                 break;
1573         }
1574 #if 0
1575         case ToolMouseMove:
1576                 if (Global::toolState == TSNone)
1577                         toolPoint[0] = p;
1578                 else if (Global::toolState == TSPoint2)
1579                         toolPoint[1] = p;
1580                 else if (Global::toolState == TSPoint3)
1581                 {
1582                         toolPoint[2] = p;
1583                         angleSnap = true;
1584                 }
1585                 else
1586                 {
1587                         toolPoint[3] = p;
1588                         angleSnap = true;
1589                 }
1590
1591                 break;
1592 #endif
1593         case ToolMouseUp:
1594                 if (Global::toolState == TSNone)
1595                 {
1596                         Global::toolState = TSPoint2;
1597                 }
1598                 else if (Global::toolState == TSPoint2)
1599                 {
1600 /*                      if (shiftDown)
1601                         {
1602                                 // Key override is telling us to start arc at new center, not
1603                                 // continue the current one.
1604                                 toolPoint[0] = toolPoint[1];
1605                                 return;
1606                         }*/
1607
1608                         Global::toolState = TSPoint3;
1609                 }
1610                 else
1611                 {
1612                         double len2 = Vector::Magnitude(toolObj[1]->p[0], toolObj[1]->p[1]);
1613                         double len3 = Vector::Magnitude(toolObj[2]->p[0], toolObj[2]->p[1]);
1614
1615                         Circle c1(toolObj[0]->p[0], len2);
1616                         Circle c2(toolObj[0]->p[1], len3);
1617
1618                         Geometry::CheckCircleToCircleIntersection((Object *)&c1, (Object *)&c2);
1619
1620                         // Only move lines if the triangle formed by them is not degenerate
1621                         if (Global::numIntersectPoints > 0)
1622                         {
1623                                 toolObj[1]->p[0] = toolObj[0]->p[0];
1624                                 toolObj[1]->p[1] = Global::intersectPoint[0];
1625
1626                                 toolObj[2]->p[0] = Global::intersectPoint[0];
1627                                 toolObj[2]->p[1] = toolObj[0]->p[1];
1628                         }
1629
1630                         Global::toolState = TSNone;
1631                 }
1632         }
1633 }
1634
1635 void DrawingView::TrimHandler(int mode, Point p)
1636 {
1637         switch (mode)
1638         {
1639         case ToolMouseDown:
1640         {
1641         }
1642                 break;
1643
1644         case ToolMouseMove:
1645         {
1646                 // Bail out if nothing hovered...
1647                 if (numHovered != 1)
1648                 {
1649                         toolObj[0] = NULL;
1650                         return;
1651                 }
1652
1653                 VPVector hover = GetHovered();
1654                 Object * obj = (Object *)hover[0];
1655
1656                 // Skip if it's not a line...
1657                 if (obj->type != OTLine)
1658                 {
1659                         toolObj[0] = NULL;
1660                         return;
1661                 }
1662
1663                 toolObj[0] = obj;
1664                 double hoveredParam = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], p);
1665                 double t = 0, u = 1.0;
1666
1667                 // Currently only deal with line against line trimming, can expand to
1668                 // others as well (line/circle, circle/circle, line/arc, etc)
1669                 VPVectorIter i;
1670                 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1671                 {
1672                         obj = (Object *)(*i);
1673
1674                         if (obj == toolObj[0])
1675                                 continue;
1676                         else if (obj->type != OTLine)
1677                                 continue;
1678
1679                         Geometry::CheckLineToLineIntersection(toolObj[0], obj);
1680
1681                         if (Global::numIntersectParams > 0)
1682                         {
1683                                 // Skip endpoint-endpoint intersections
1684                                 if ((Global::numIntersectParams == 2)
1685                                         && (Global::intersectParam[0] == 0
1686                                                 || Global::intersectParam[0] == 1.0)
1687                                         && (Global::intersectParam[1] == 0
1688                                                 || Global::intersectParam[1] == 1.0))
1689                                         continue;
1690
1691                                 // Mark the line segment somehow (the side the mouse is on) so that it can be drawn & trimmed when we hit ToolMouseDown.
1692                                 if ((Global::intersectParam[0] > t) && (Global::intersectParam[0] < hoveredParam))
1693                                         t = Global::intersectParam[0];
1694
1695                                 if ((Global::intersectParam[0] < u) && (Global::intersectParam[0] > hoveredParam))
1696                                         u = Global::intersectParam[0];
1697                         }
1698                 }
1699
1700                 toolParam[0] = t;
1701                 toolParam[1] = u;
1702                 toolPoint[0] = Geometry::GetPointForParameter(toolObj[0], t);
1703                 toolPoint[1] = Geometry::GetPointForParameter(toolObj[0], u);
1704         }
1705                 break;
1706
1707         case ToolMouseUp:
1708         {
1709                 // Bail out if there's no object to trim
1710                 if (toolObj[0] == NULL)
1711                         return;
1712
1713                 Vector v(toolObj[0]->p[0], toolObj[0]->p[1]);
1714
1715                 // Check to see which case we have.
1716                 if ((toolParam[0] == 0) && (toolParam[1] == 1.0))
1717                 {
1718                         // There was no intersection, so delete the object
1719                         toolObj[0]->selected = true;
1720                         DeleteSelectedObjects(document.objects);
1721                 }
1722                 else if (toolParam[0] == 0)
1723                 {
1724                         // We delete the end near point #1
1725                         toolObj[0]->p[0] = toolObj[0]->p[0] + (v * toolParam[1]);
1726                 }
1727                 else if (toolParam[1] == 1.0)
1728                 {
1729                         // We delete the end near point #2
1730                         toolObj[0]->p[1] = toolObj[0]->p[0] + (v * toolParam[0]);
1731                 }
1732                 else
1733                 {
1734                         // We delete the segment in between, and create a new line in the process
1735                         Point p1 = toolObj[0]->p[0] + (v * toolParam[0]);
1736                         Point p2 = toolObj[0]->p[0] + (v * toolParam[1]);
1737                         Point p3 = toolObj[0]->p[1];
1738                         toolObj[0]->p[1] = p1;
1739                         Line * l = new Line(p2, p3, toolObj[0]->thickness, toolObj[0]->color, toolObj[0]->style);
1740                         document.objects.push_back(l);
1741 //                      Global::toolState = TSNone;
1742                 }
1743
1744                 toolObj[0]->hitObject = toolObj[0]->hitPoint[0] = toolObj[0]->hitPoint[1] = false;
1745                 toolObj[0] = NULL;
1746         }
1747                 break;
1748
1749         case ToolKeyDown:
1750                 break;
1751
1752         case ToolKeyUp:
1753                 break;
1754
1755         case ToolCleanup:
1756                 break;
1757         }
1758 }
1759
1760 void DrawingView::ParallelHandler(int mode, Point /*p*/)
1761 {
1762         switch (mode)
1763         {
1764         case ToolMouseDown:
1765                 break;
1766
1767         case ToolMouseMove:
1768                 break;
1769
1770         case ToolMouseUp:
1771                 break;
1772
1773         case ToolKeyDown:
1774                 break;
1775
1776         case ToolKeyUp:
1777                 break;
1778
1779         case ToolCleanup:
1780                 break;
1781         }
1782 }
1783
1784 void DrawingView::mousePressEvent(QMouseEvent * event)
1785 {
1786         if (event->button() == Qt::LeftButton)
1787         {
1788                 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1789
1790                 // Handle tool processing, if any
1791                 if (Global::tool)
1792                 {
1793                         if (hoveringIntersection)
1794                                 point = intersectionPoint;
1795                         else if (hoverPointValid)
1796                                 point = hoverPoint;
1797                         else if (Global::snapToGrid)
1798                                 point = SnapPointToGrid(point);
1799
1800                         ToolHandler(ToolMouseDown, point);
1801                         return;
1802                 }
1803
1804                 // Clear the selection only if CTRL isn't being held on click
1805                 if (!ctrlDown)
1806                         ClearSelected(document.objects);
1807
1808                 // If any objects are being hovered on click, deal with 'em
1809                 if (numHovered > 0)
1810                 {
1811                         VPVector hover2 = GetHovered();
1812                         dragged = (Object *)hover2[0];
1813
1814                         // Alert the pen widget
1815                         if (Global::penDropper)
1816                         {
1817                                 Global::penColor = dragged->color;
1818                                 Global::penWidth = dragged->thickness;
1819                                 Global::penStyle = dragged->style;
1820                                 emit ObjectSelected(dragged);
1821                                 return;
1822                         }
1823                         else if (Global::penStamp)
1824                         {
1825                                 dragged->color = Global::penColor;
1826                                 dragged->thickness = Global::penWidth;
1827                                 dragged->style = Global::penStyle;
1828                                 return;
1829                         }
1830                         // See if anything is using just a straight click on a custom
1831                         // object handle (like Dimension objects)
1832                         else if (HandleObjectClicked())
1833                         {
1834                                 update();
1835                                 return;
1836                         }
1837
1838                         draggingObject = true;
1839                         HandleSelectionClick(hover2);
1840                         update();       // needed??
1841
1842                         // Needed for grab & moving objects
1843                         // We do it *after*... why? (doesn't seem to confer any advantage...)
1844                         if (hoveringIntersection)
1845                                 oldPoint = intersectionPoint;
1846                         else if (hoverPointValid)
1847                                 oldPoint = hoverPoint;
1848                         else if (Global::snapToGrid)
1849                                 oldPoint = SnapPointToGrid(point);
1850
1851                         // Needed for fixed length handling
1852                         if (Global::fixedLength)
1853                         {
1854                                 if (dragged->type == OTLine)
1855                                 {
1856                                         dragged->length = Vector::Magnitude(dragged->p[0], dragged->p[1]);
1857                                 }
1858                         }
1859
1860                         if (dragged->type == OTCircle)
1861                         {
1862                                 // Save for informative text, uh, er, informing
1863                                 dragged->length = dragged->radius[0];
1864                         }
1865
1866                         return;
1867                 }
1868
1869                 // Didn't hit any object and not using a tool, so do a selection
1870                 // rectangle
1871                 Global::selectionInProgress = true;
1872                 Global::selection.setTopLeft(QPointF(point.x, point.y));
1873                 Global::selection.setBottomRight(QPointF(point.x, point.y));
1874                 select = GetSelection();
1875         }
1876         else if (event->button() == Qt::MiddleButton)
1877         {
1878                 scrollDrag = true;
1879                 oldPoint = Vector(event->x(), event->y());
1880                 // Should also change the mouse pointer as well...
1881                 setCursor(Qt::SizeAllCursor);
1882         }
1883 }
1884
1885 void DrawingView::mouseMoveEvent(QMouseEvent * event)
1886 {
1887         // It seems that wheelEvent() triggers this for some reason...
1888         if (scrollWheelSeen)
1889         {
1890                 scrollWheelSeen = false;
1891                 return;
1892         }
1893
1894         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1895         Global::selection.setBottomRight(QPointF(point.x, point.y));
1896         // Only needs to be done here, as mouse down is always preceded by movement
1897         Global::snapPointIsValid = false;
1898         hoveringIntersection = false;
1899         oldScrollPoint = Vector(event->x(), event->y());
1900
1901         // Scrolling...
1902         if ((event->buttons() & Qt::MiddleButton) || scrollDrag)
1903         {
1904                 point = Vector(event->x(), event->y());
1905                 // Since we're using Qt coords for scrolling, we have to adjust them
1906                 // here to conform to Cartesian coords, since the origin is using
1907                 // Cartesian. :-)
1908                 Vector delta(oldPoint, point);
1909                 delta /= Global::zoom;
1910                 delta.y = -delta.y;
1911                 Global::origin -= delta;
1912
1913 //              UpdateGridBackground();
1914                 update();
1915                 oldPoint = point;
1916                 return;
1917         }
1918
1919         // If we're doing a selection rect, see if any objects are engulfed by it
1920         // (implies left mouse button held down)
1921         if (Global::selectionInProgress)
1922         {
1923                 CheckObjectBounds();
1924
1925                 // Make sure previously selected objects stay selected (CTRL held)
1926                 for(VPVectorIter i=select.begin(); i!=select.end(); i++)
1927                 {
1928                         // Make sure *not* to select items on hidden layers
1929                         if (Global::layerHidden[((Object *)(*i))->layer] == false)
1930                                 ((Object *)(*i))->selected = true;
1931                 }
1932
1933                 update();
1934                 return;
1935         }
1936
1937         // Do object hit testing...
1938         bool needUpdate = HitTestObjects(point);
1939         VPVector hover2 = GetHovered(true); // Exclude dimension objects and circle centers (probably need to add arc centers too) from hover (also dragged objects...)
1940 #if 0
1941 {
1942 if (needUpdate)
1943 {
1944         printf("mouseMoveEvent:: numHovered=%li, hover2.size()=%li\n", numHovered, hover2.size());
1945
1946         if (hover2.size() > 0)
1947                 printf("                 (hover2[0]=$%llX, type=%s)\n", hover2[0], objName[((Object *)hover2[0])->type]);
1948 }
1949 }
1950 #endif
1951
1952         // Check for multi-hover...
1953         if (hover2.size() > 1)
1954         {
1955 //need to check for case where hover is over 2 circles and a 3rd's center (no longer a problem, I think)...
1956                 Object * obj1 = (Object *)hover2[0], * obj2 = (Object *)hover2[1];
1957
1958                 Geometry::Intersects(obj1, obj2);
1959                 int numIntersecting = Global::numIntersectParams;
1960                 double t = Global::intersectParam[0];
1961                 double u = Global::intersectParam[1];
1962
1963                 if (numIntersecting > 0)
1964                 {
1965                         Vector v1 = Geometry::GetPointForParameter(obj1, t);
1966                         Vector v2 = Geometry::GetPointForParameter(obj2, u);
1967                         QString text = tr("Intersection t=%1 (%3, %4), u=%2 (%5, %6)");
1968                         informativeText = text.arg(t).arg(u).arg(v1.x).arg(v1.y).arg(v2.x).arg(v2.y);
1969
1970                         hoveringIntersection = true;
1971                         intersectionPoint = v1;
1972                 }
1973
1974                 numIntersecting = Global::numIntersectPoints;
1975
1976                 if (numIntersecting > 0)
1977                 {
1978                         Vector v1 = Global::intersectPoint[0];
1979
1980                         if (numIntersecting == 2)
1981                         {
1982                                 Vector v2 = Global::intersectPoint[1];
1983
1984                                 if (Vector::Magnitude(v2, point) < Vector::Magnitude(v1, point))
1985                                         v1 = v2;
1986                         }
1987
1988                         QString text = tr("Intersection <%1, %2>");
1989                         informativeText = text.arg(v1.x).arg(v1.y);
1990                         hoveringIntersection = true;
1991                         intersectionPoint = v1;
1992                 }
1993         }
1994         else if (hover2.size() == 1)
1995         {
1996                 Object * obj = (Object *)hover2[0];
1997
1998                 if (obj->type == OTLine)
1999                 {
2000 /*
2001 Not sure that this is the best way to handle this, but it works(TM)...
2002 */
2003                         Point midpoint = Geometry::Midpoint((Line *)obj);
2004                         Vector v1 = Vector::Magnitude(midpoint, point);
2005
2006                         if ((v1.Magnitude() * Global::zoom) < 8.0)
2007                         {
2008                                 QString text = tr("Midpoint <%1, %2>");
2009                                 informativeText = text.arg(midpoint.x).arg(midpoint.y);
2010                                 hoverPointValid = true;
2011                                 hoverPoint = midpoint;
2012                                 needUpdate = true;
2013                         }
2014                 }
2015                 else if (obj->type == OTCircle)
2016                 {
2017                         if ((draggingObject && (dragged->type == OTLine)) && (dragged->hitPoint[0] || dragged->hitPoint[1]))
2018                         {
2019                                 Point p = (dragged->hitPoint[0] ? dragged->p[1] : dragged->p[0]);
2020                                 Geometry::FindTangents(obj, p);
2021
2022                                 if (Global::numIntersectPoints > 0)
2023                                 {
2024                                         hoveringIntersection = true;
2025                                         intersectionPoint = Geometry::NearestTo(point, Global::intersectPoint[0], Global::intersectPoint[1]);
2026                                 }
2027                         }
2028                         else if ((Global::tool == TTLine) && (Global::toolState == TSPoint2))
2029                         {
2030                                 Geometry::FindTangents(obj, toolPoint[0]);
2031
2032                                 if (Global::numIntersectPoints > 0)
2033                                 {
2034                                         hoveringIntersection = true;
2035                                         intersectionPoint = Geometry::NearestTo(point, Global::intersectPoint[0], Global::intersectPoint[1]);
2036                                 }
2037                         }
2038                 }
2039         }
2040
2041         // Handle object movement (left button down & over an object)
2042         if ((event->buttons() & Qt::LeftButton) && draggingObject && !Global::tool)
2043         {
2044                 if (hoveringIntersection)
2045                         point = intersectionPoint;
2046                 else if (hoverPointValid)
2047                         point = hoverPoint;
2048                 else if (Global::snapToGrid)
2049                         point = SnapPointToGrid(point);
2050
2051                 HandleObjectMovement(point);
2052                 update();
2053                 oldPoint = point;
2054                 return;
2055         }
2056
2057         // Do tool handling, if any are active...
2058         if (Global::tool)
2059         {
2060                 if (hoveringIntersection)
2061                         point = intersectionPoint;
2062                 else if (hoverPointValid)
2063                         point = hoverPoint;
2064                 else if (Global::snapToGrid)
2065                 {
2066                         if (angleSnap)
2067                                 point = SnapPointToAngle(point);
2068                         else
2069                                 point = SnapPointToGrid(point);
2070                 }
2071
2072                 ToolHandler(ToolMouseMove, point);
2073         }
2074
2075         // This is used to draw the tool crosshair...
2076         oldPoint = point;
2077
2078         if (needUpdate || Global::tool)
2079                 update();
2080 }
2081
2082 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
2083 {
2084         if (event->button() == Qt::LeftButton)
2085         {
2086 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
2087 //could set it up to use the document's update function (assumes that all object
2088 //updates are being reported correctly:
2089 //              if (document.NeedsUpdate())
2090                 // Do an update if collided with at least *one* object in the document
2091 //              if (collided)
2092                         update();
2093
2094                 if (Global::tool)
2095                 {
2096                         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
2097                         ToolHandler(ToolMouseUp, point);
2098                         return;
2099                 }
2100
2101                 Global::selectionInProgress = false;
2102                 informativeText.clear();
2103
2104 // Should we be doing this automagically? Hmm...
2105                 // Clear our vectors
2106 //              select.clear();
2107 ////            hover.clear();
2108
2109                 // 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???])
2110                 select = GetSelection();
2111
2112                 draggingObject = false;
2113         }
2114         else if (event->button() == Qt::MiddleButton)
2115         {
2116                 scrollDrag = false;
2117
2118                 if (Global::penStamp)
2119                         setCursor(curMarker);
2120                 else if (Global::penDropper)
2121                         setCursor(curDropper);
2122                 else
2123                         setCursor(Qt::ArrowCursor);
2124
2125                 // Need to convert this, since it's in Qt coordinates (for wheelEvent)
2126                 oldPoint = Painter::QtToCartesianCoords(oldPoint);
2127         }
2128 }
2129
2130 void DrawingView::wheelEvent(QWheelEvent * event)
2131 {
2132         double zoomFactor = 1.20;
2133         scrollWheelSeen = true;
2134
2135         if (event->angleDelta().y() < 0)
2136         {
2137                 if (Global::zoom > 400.0)
2138                         return;
2139
2140                 Global::zoom *= zoomFactor;
2141         }
2142         else
2143         {
2144                 if (Global::zoom < 0.125)
2145                         return;
2146
2147                 Global::zoom /= zoomFactor;
2148         }
2149
2150         Point np = Painter::QtToCartesianCoords(oldScrollPoint);
2151         Global::origin += (oldPoint - np);
2152
2153         emit(NeedZoomUpdate());
2154 }
2155
2156 void DrawingView::keyPressEvent(QKeyEvent * event)
2157 {
2158         bool oldShift = shiftDown;
2159         bool oldCtrl = ctrlDown;
2160         bool oldAlt = altDown;
2161
2162         if (event->key() == Qt::Key_Shift)
2163                 shiftDown = true;
2164         else if (event->key() == Qt::Key_Control)
2165                 ctrlDown = true;
2166         else if (event->key() == Qt::Key_Alt)
2167                 altDown = true;
2168
2169         // If there's a change in any of the modifier key states, pass it on to
2170         // the current tool's handler
2171         if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
2172         {
2173                 if (Global::tool)
2174                         ToolHandler(ToolKeyDown, Point(0, 0));
2175
2176                 update();
2177         }
2178
2179         if (oldAlt != altDown)
2180         {
2181                 scrollDrag = true;
2182                 setCursor(Qt::SizeAllCursor);
2183                 oldPoint = oldScrollPoint;
2184         }
2185
2186         if (select.size() > 0)
2187         {
2188                 if (event->key() == Qt::Key_Up)
2189                 {
2190                         TranslateObjects(select, Point(0, +1.0));
2191                         update();
2192                 }
2193                 else if (event->key() == Qt::Key_Down)
2194                 {
2195                         TranslateObjects(select, Point(0, -1.0));
2196                         update();
2197                 }
2198                 else if (event->key() == Qt::Key_Right)
2199                 {
2200                         TranslateObjects(select, Point(+1.0, 0));
2201                         update();
2202                 }
2203                 else if (event->key() == Qt::Key_Left)
2204                 {
2205                         TranslateObjects(select, Point(-1.0, 0));
2206                         update();
2207                 }
2208         }
2209 }
2210
2211 void DrawingView::keyReleaseEvent(QKeyEvent * event)
2212 {
2213         bool oldShift = shiftDown;
2214         bool oldCtrl = ctrlDown;
2215         bool oldAlt = altDown;
2216
2217         if (event->key() == Qt::Key_Shift)
2218                 shiftDown = false;
2219         else if (event->key() == Qt::Key_Control)
2220                 ctrlDown = false;
2221         else if (event->key() == Qt::Key_Alt)
2222                 altDown = false;
2223
2224         if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
2225         {
2226                 if (Global::tool)
2227                         ToolHandler(ToolKeyUp, Point(0, 0));
2228
2229                 update();
2230         }
2231
2232         if (oldAlt != altDown)
2233         {
2234                 scrollDrag = false;
2235
2236                 if (Global::penStamp)
2237                         setCursor(curMarker);
2238                 else if (Global::penDropper)
2239                         setCursor(curDropper);
2240                 else
2241                         setCursor(Qt::ArrowCursor);
2242         }
2243 }
2244
2245 //
2246 // This looks strange, but it's really quite simple: We want a point that's
2247 // more than half-way to the next grid point to snap there while conversely we
2248 // want a point that's less than half-way to to the next grid point then snap
2249 // to the one before it. So we add half of the grid spacing to the point, then
2250 // divide by it so that we can remove the fractional part, then multiply it
2251 // back to get back to the correct answer.
2252 //
2253 Point DrawingView::SnapPointToGrid(Point point)
2254 {
2255         point += Global::gridSpacing / 2.0;             // *This* adds to Z!!!
2256         point /= Global::gridSpacing;
2257         point.x = floor(point.x);//need to fix this for negative numbers...
2258         point.y = floor(point.y);
2259         point.z = 0;                                    // Make *sure* Z doesn't go anywhere!!!
2260         point *= Global::gridSpacing;
2261         return point;
2262 }
2263
2264 Point DrawingView::SnapPointToAngle(Point point)
2265 {
2266         // Snap to a single digit angle (using toolpoint #1 as the center)
2267         double angle = Vector::Angle(toolPoint[0], point);
2268         double length = Vector::Magnitude(toolPoint[0], point);
2269
2270         // Convert from radians to degrees
2271         double degAngle = angle * RADIANS_TO_DEGREES;
2272         double snapAngle = (double)((int)(degAngle + 0.5));
2273
2274         Vector v;
2275         v.SetAngleAndLength(snapAngle * DEGREES_TO_RADIANS, length);
2276         point = toolPoint[0] + v;
2277
2278         return point;
2279 }
2280
2281 Rect DrawingView::GetObjectExtents(Object * obj)
2282 {
2283         // Default to empty rect, if object checks below fail for some reason
2284         Rect rect;
2285
2286         switch (obj->type)
2287         {
2288         case OTLine:
2289         case OTDimension:
2290         {
2291                 rect = Rect(obj->p[0], obj->p[1]);
2292                 break;
2293         }
2294
2295         case OTCircle:
2296         {
2297                 rect = Rect(obj->p[0], obj->p[0]);
2298                 rect.Expand(obj->radius[0]);
2299                 break;
2300         }
2301
2302         case OTArc:
2303         {
2304                 Arc * a = (Arc *)obj;
2305
2306                 double start = a->angle[0];
2307                 double end = start + a->angle[1];
2308                 rect = Rect(Point(cos(start), sin(start)), Point(cos(end), sin(end)));
2309
2310                 // If the end of the arc is before the beginning, add 360 degrees to it
2311                 if (end < start)
2312                         end += TAU;
2313
2314                 // Adjust the bounds depending on which axes are crossed
2315                 if ((start < QTR_TAU) && (end > QTR_TAU))
2316                         rect.t = 1.0;
2317
2318                 if ((start < HALF_TAU) && (end > HALF_TAU))
2319                         rect.l = -1.0;
2320
2321                 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
2322                         rect.b = -1.0;
2323
2324                 if ((start < TAU) && (end > TAU))
2325                         rect.r = 1.0;
2326
2327                 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
2328                         rect.t = 1.0;
2329
2330                 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
2331                         rect.l = -1.0;
2332
2333                 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
2334                         rect.b = -1.0;
2335
2336                 rect *= a->radius[0];
2337                 rect.Translate(a->p[0]);
2338                 break;
2339         }
2340
2341         case OTText:
2342         {
2343                 Text * t = (Text *)obj;
2344                 rect = Rect(t->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
2345                 break;
2346         }
2347
2348         case OTContainer:
2349         {
2350                 Container * c = (Container *)obj;
2351                 VPVectorIter i = c->objects.begin();
2352                 rect = GetObjectExtents((Object *)*i);
2353                 i++;
2354
2355                 for(; i!=c->objects.end(); i++)
2356                         rect |= GetObjectExtents((Object *)*i);
2357         }
2358
2359         default:
2360                 break;
2361         }
2362
2363         return rect;
2364 }
2365
2366 void DrawingView::CheckObjectBounds(void)
2367 {
2368         VPVectorIter i;
2369
2370         for(i=document.objects.begin(); i!=document.objects.end(); i++)
2371         {
2372                 Object * obj = (Object *)(*i);
2373                 obj->selected = false;
2374
2375                 switch (obj->type)
2376                 {
2377                 case OTLine:
2378                 case OTDimension: // N.B.: We don't check this properly...
2379                 {
2380                         Line * l = (Line *)obj;
2381
2382                         if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
2383                                 l->selected = true;
2384
2385                         break;
2386                 }
2387
2388                 case OTCircle:
2389                 {
2390                         Circle * c = (Circle *)obj;
2391
2392                         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]))
2393                                 c->selected = true;
2394
2395                         break;
2396                 }
2397
2398                 case OTArc:
2399                 {
2400                         Arc * a = (Arc *)obj;
2401
2402                         double start = a->angle[0];
2403                         double end = start + a->angle[1];
2404                         QPointF p1(cos(start), sin(start));
2405                         QPointF p2(cos(end), sin(end));
2406                         QRectF bounds(p1, p2);
2407
2408 #if 1
2409                         // Swap X/Y coordinates if they're backwards...
2410                         if (bounds.left() > bounds.right())
2411                         {
2412                                 double temp = bounds.left();
2413                                 bounds.setLeft(bounds.right());
2414                                 bounds.setRight(temp);
2415                         }
2416
2417                         if (bounds.bottom() > bounds.top())
2418                         {
2419                                 double temp = bounds.bottom();
2420                                 bounds.setBottom(bounds.top());
2421                                 bounds.setTop(temp);
2422                         }
2423 #else
2424                         // Doesn't work as advertised! For shame!
2425                         bounds = bounds.normalized();
2426 #endif
2427
2428                         // If the end of the arc is before the beginning, add 360 degrees
2429                         // to it
2430                         if (end < start)
2431                                 end += TAU;
2432
2433                         // Adjust the bounds depending on which axes are crossed
2434                         if ((start < QTR_TAU) && (end > QTR_TAU))
2435                                 bounds.setTop(1.0);
2436
2437                         if ((start < HALF_TAU) && (end > HALF_TAU))
2438                                 bounds.setLeft(-1.0);
2439
2440                         if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
2441                                 bounds.setBottom(-1.0);
2442
2443                         if ((start < TAU) && (end > TAU))
2444                                 bounds.setRight(1.0);
2445
2446                         if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
2447                                 bounds.setTop(1.0);
2448
2449                         if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
2450                                 bounds.setLeft(-1.0);
2451
2452                         if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
2453                                 bounds.setBottom(-1.0);
2454
2455                         bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
2456                         bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
2457                         bounds.translate(a->p[0].x, a->p[0].y);
2458
2459                         if (Global::selection.contains(bounds))
2460                                 a->selected = true;
2461
2462                         break;
2463                 }
2464
2465                 case OTText:
2466                 {
2467                         Text * t = (Text *)obj;
2468                         Rect r(obj->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
2469
2470                         if (Global::selection.contains(r.l, r.t) && Global::selection.contains(r.r, r.b))
2471                                 t->selected = true;
2472
2473                         break;
2474                 }
2475
2476                 case OTContainer:
2477                 {
2478                         Container * c = (Container *)obj;
2479
2480                         if (Global::selection.contains(c->p[0].x, c->p[0].y) && Global::selection.contains(c->p[1].x, c->p[1].y))
2481                                 c->selected = true;
2482
2483                         break;
2484                 }
2485
2486 //              default:
2487 //                      break;
2488                 }
2489         }
2490 }
2491
2492 bool DrawingView::HitTestObjects(Point point)
2493 {
2494         VPVectorIter i;
2495         numHovered = 0;
2496         bool needUpdate = false;
2497         hoverPointValid = false;
2498
2499         for(i=document.objects.begin(); i!=document.objects.end(); i++)
2500         {
2501                 Object * obj = (Object *)(*i);
2502
2503                 // If we're seeing the object we're dragging, skip it
2504                 if (draggingObject && (obj == dragged))
2505                         continue;
2506
2507                 if (HitTest(obj, point) == true)
2508                         needUpdate = true;
2509
2510                 if (obj->hovered)
2511                 {
2512                         numHovered++;
2513 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
2514                         emit ObjectHovered(obj);
2515                 }
2516         }
2517
2518         return needUpdate;
2519 }
2520
2521 bool DrawingView::HitTest(Object * obj, Point point)
2522 {
2523         bool needUpdate = false;
2524
2525         switch (obj->type)
2526         {
2527         case OTLine:
2528         {
2529                 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
2530                 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
2531                 Vector lineSegment = obj->p[1] - obj->p[0];
2532                 Vector v1 = point - obj->p[0];
2533                 Vector v2 = point - obj->p[1];
2534                 double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
2535                 double distance;
2536
2537                 if (t < 0.0)
2538                         distance = v1.Magnitude();
2539                 else if (t > 1.0)
2540                         distance = v2.Magnitude();
2541                 else
2542                         // distance = ?Det?(ls, v1) / |ls|
2543                         distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
2544                                 / lineSegment.Magnitude());
2545
2546                 if ((v1.Magnitude() * Global::zoom) < 8.0)
2547                 {
2548                         obj->hitPoint[0] = true;
2549                         hoverPoint = obj->p[0];
2550                         hoverPointValid = true;
2551                 }
2552                 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2553                 {
2554                         obj->hitPoint[1] = true;
2555                         hoverPoint = obj->p[1];
2556                         hoverPointValid = true;
2557                 }
2558                 else if ((distance * Global::zoom) < 5.0)
2559                         obj->hitObject = true;
2560
2561                 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
2562
2563                 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
2564                         needUpdate = true;
2565
2566                 break;
2567         }
2568
2569         case OTCircle:
2570         {
2571                 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
2572                 obj->hitPoint[0] = obj->hitObject = false;
2573                 double length = Vector::Magnitude(obj->p[0], point);
2574
2575                 if ((length * Global::zoom) < 8.0)
2576                 {
2577                         obj->hitPoint[0] = true;
2578                         hoverPoint = obj->p[0];
2579                         hoverPointValid = true;
2580                 }
2581                 else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
2582                         obj->hitObject = true;
2583
2584                 obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
2585
2586                 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
2587                         needUpdate = true;
2588
2589                 break;
2590         }
2591
2592         case OTArc:
2593         {
2594                 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
2595                 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
2596                 double length = Vector::Magnitude(obj->p[0], point);
2597                 double angle = Vector::Angle(obj->p[0], point);
2598
2599                 // Make sure we get the angle in the correct spot
2600                 if (angle < obj->angle[0])
2601                         angle += TAU;
2602
2603                 // Get the span that we're pointing at...
2604                 double span = angle - obj->angle[0];
2605
2606                 // N.B.: Still need to hit test the arc start & arc span handles...
2607                 double spanAngle = obj->angle[0] + obj->angle[1];
2608                 Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
2609                 Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
2610                 double length2 = Vector::Magnitude(point, handle1);
2611                 double length3 = Vector::Magnitude(point, handle2);
2612
2613                 if ((length * Global::zoom) < 8.0)
2614                 {
2615                         obj->hitPoint[0] = true;
2616                         hoverPoint = obj->p[0];
2617                         hoverPointValid = true;
2618                 }
2619                 else if ((length2 * Global::zoom) < 8.0)
2620                 {
2621                         obj->hitPoint[1] = true;
2622                         hoverPoint = handle1;
2623                         hoverPointValid = true;
2624                 }
2625                 else if ((length3 * Global::zoom) < 8.0)
2626                 {
2627                         obj->hitPoint[2] = true;
2628                         hoverPoint = handle2;
2629                         hoverPointValid = true;
2630                 }
2631                 else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
2632                         obj->hitObject = true;
2633
2634                 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
2635
2636                 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
2637                         needUpdate = true;
2638
2639                 break;
2640         }
2641
2642         case OTDimension:
2643         {
2644                 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHP3 = obj->hitPoint[3], oldHP4 = obj->hitPoint[4], oldHO = obj->hitObject;
2645                 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitPoint[3] = obj->hitPoint[4] = obj->hitObject = false;
2646
2647                 Dimension * d = (Dimension *)obj;
2648
2649                 Vector orthogonal = Vector::Normal(d->lp[0], d->lp[1]);
2650                 // Get our line parallel to our points
2651                 float scaledThickness = Global::scale * obj->thickness;
2652 #if 1
2653                 Point p1 = d->lp[0] + (orthogonal * (d->offset + (10.0 * scaledThickness)));
2654                 Point p2 = d->lp[1] + (orthogonal * (d->offset + (10.0 * scaledThickness)));
2655 #else
2656                 Point p1 = d->lp[0] + (orthogonal * (10.0 + d->offset) * scaledThickness);
2657                 Point p2 = d->lp[1] + (orthogonal * (10.0 + d->offset) * scaledThickness);
2658 #endif
2659                 Point p3(p1, point);
2660
2661                 Vector v1(d->p[0], point);
2662                 Vector v2(d->p[1], point);
2663                 Vector lineSegment(p1, p2);
2664                 double t = Geometry::ParameterOfLineAndPoint(p1, p2, point);
2665                 double distance;
2666                 Point midpoint = (p1 + p2) / 2.0;
2667                 Point hFSPoint = Point(midpoint, point);
2668                 Point hCS1Point = Point((p1 + midpoint) / 2.0, point);
2669                 Point hCS2Point = Point((midpoint + p2) / 2.0, point);
2670
2671                 if (t < 0.0)
2672                         distance = v1.Magnitude();
2673                 else if (t > 1.0)
2674                         distance = v2.Magnitude();
2675                 else
2676                         // distance = ?Det?(ls, v1) / |ls|
2677                         distance = fabs((lineSegment.x * p3.y - p3.x * lineSegment.y)
2678                                 / lineSegment.Magnitude());
2679
2680                 if ((v1.Magnitude() * Global::zoom) < 8.0)
2681                         obj->hitPoint[0] = true;
2682                 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2683                         obj->hitPoint[1] = true;
2684                 else if ((distance * Global::zoom) < 5.0)
2685                         obj->hitObject = true;
2686
2687                 if ((hFSPoint.Magnitude() * Global::zoom) < 8.0)
2688                         obj->hitPoint[2] = true;
2689                 else if ((hCS1Point.Magnitude() * Global::zoom) < 8.0)
2690                         obj->hitPoint[3] = true;
2691                 else if ((hCS2Point.Magnitude() * Global::zoom) < 8.0)
2692                         obj->hitPoint[4] = true;
2693
2694                 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitPoint[3] || obj->hitPoint[4] || obj->hitObject ? true : false);
2695
2696                 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHP3 != obj->hitPoint[3]) || (oldHP4 != obj->hitPoint[4]) || (oldHO != obj->hitObject))
2697                         needUpdate = true;
2698
2699                 break;
2700         }
2701
2702         case OTText:
2703         {
2704                 Text * t = (Text *)obj;
2705                 bool oldHO = obj->hitObject;
2706                 obj->hitObject = false;
2707
2708                 Rect r(obj->p[0], Point(obj->p[0].x + t->extents.Width(), obj->p[0].y - t->extents.Height()));
2709 //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);
2710
2711                 if (r.Contains(point))
2712                         obj->hitObject = true;
2713
2714                 obj->hovered = (obj->hitObject ? true : false);
2715
2716                 if (oldHO != obj->hitObject)
2717                         needUpdate = true;
2718
2719                 break;
2720         }
2721
2722         case OTContainer:
2723         {
2724                 // Containers must be recursively tested...  Or do they???
2725 /*
2726 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.
2727 */
2728 //              bool oldHitObj = c->hitObject, oldHovered = c->hovered;
2729 //              Object * oldClicked = c->clicked;
2730 /*
2731 still need to compare old state to new state, and set things up based upon that...
2732 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);
2733 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.
2734 */
2735                 Container * c = (Container *)obj;
2736                 c->hitObject = false;
2737                 c->hovered = false;
2738                 c->clicked = NULL;
2739
2740                 VPVector flat = Flatten(c);
2741
2742 //printf("HitTest::OTContainer (size=%li)\n", flat.size());
2743                 for(VPVectorIter i=flat.begin(); i!=flat.end(); i++)
2744                 {
2745                         Object * cObj = (Object *)(*i);
2746
2747                         // Skip the flattened containers (if any)...
2748                         if (cObj->type == OTContainer)
2749                                 continue;
2750
2751                         // We do it this way instead of needUpdate = HitTest() because we
2752                         // are checking more than one object, and that way of doing will
2753                         // not return consistent results.
2754                         if (HitTest(cObj, point) == true)
2755 //                      {
2756 //printf("HitTest::OTContainer, subobj ($%llX) hit!\n", cObj);
2757                                 needUpdate = true;
2758 //                              c->hitObject = true;
2759 //                              c->clicked = cObj;
2760 //                              c->hovered = true;
2761 //                      }
2762
2763                         // Same reasons for doing it this way here apply.
2764                         if (cObj->hitObject == true)
2765                         {
2766 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2767                                 c->hitObject = true;
2768                                 c->clicked = cObj;
2769                         }//*/
2770
2771                         if (cObj->hitPoint[0] == true)
2772                         {
2773 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2774                                 c->hitPoint[0] = true;
2775                                 c->clicked = cObj;
2776                         }//*/
2777
2778                         if (cObj->hitPoint[1] == true)
2779                         {
2780 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2781                                 c->hitPoint[1] = true;
2782                                 c->clicked = cObj;
2783                         }//*/
2784
2785                         if (cObj->hitPoint[2] == true)
2786                         {
2787 //printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj);
2788                                 c->hitPoint[2] = true;
2789                                 c->clicked = cObj;
2790                         }//*/
2791
2792                         if (cObj->hovered == true)
2793                                 c->hovered = true;//*/
2794                 }
2795
2796                 break;
2797         }
2798
2799         default:
2800                 break;
2801         }
2802
2803         return needUpdate;
2804 }
2805
2806 bool DrawingView::HandleObjectClicked(void)
2807 {
2808         if (dragged->type == OTDimension)
2809         {
2810                 Dimension * d = (Dimension *)dragged;
2811
2812                 if (d->hitPoint[2])
2813                 {
2814                         // Hit the "flip sides" switch, so flip 'em
2815                         Point temp = d->p[0];
2816                         d->p[0] = d->p[1];
2817                         d->p[1] = temp;
2818                         return true;
2819                 }
2820                 else if (d->hitPoint[3])
2821                 {
2822                         // There are three cases here: aligned, horizontal, & vertical.
2823                         // Aligned and horizontal do the same thing, vertical goes back to
2824                         // linear.
2825                         if (d->subtype == DTLinearVert)
2826                                 d->subtype = DTLinear;
2827                         else
2828                                 d->subtype = DTLinearVert;
2829
2830                         return true;
2831                 }
2832                 else if (d->hitPoint[4])
2833                 {
2834                         // There are three cases here: aligned, horizontal, & vertical.
2835                         // Aligned and vertical do the same thing, horizontal goes back to
2836                         // linear.
2837                         if (d->subtype == DTLinearHorz)
2838                                 d->subtype = DTLinear;
2839                         else
2840                                 d->subtype = DTLinearHorz;
2841
2842                         return true;
2843                 }
2844         }
2845
2846         return false;
2847 }
2848
2849 void DrawingView::HandleObjectMovement(Point point)
2850 {
2851         Point delta = point - oldPoint;
2852 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
2853 //      Object * obj = (Object *)hover[0];
2854         Object * obj = dragged;
2855 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
2856 //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"));
2857
2858         switch (obj->type)
2859         {
2860         case OTLine:
2861                 if (obj->hitPoint[0])
2862                 {
2863                         if (Global::fixedLength)
2864                         {
2865                                 Vector line = point - obj->p[1];
2866                                 Vector unit = line.Unit();
2867                                 point = obj->p[1] + (unit * obj->length);
2868                         }
2869
2870                         obj->p[0] = point;
2871                 }
2872                 else if (obj->hitPoint[1])
2873                 {
2874                         if (Global::fixedLength)
2875                         {
2876                                 Vector line = point - obj->p[0];
2877                                 Vector unit = line.Unit();
2878                                 point = obj->p[0] + (unit * obj->length);
2879                         }
2880
2881                         obj->p[1] = point;
2882                 }
2883                 else if (obj->hitObject)
2884                 {
2885                         obj->p[0] += delta;
2886                         obj->p[1] += delta;
2887                 }
2888
2889                 break;
2890
2891         case OTCircle:
2892                 if (obj->hitPoint[0])
2893                         obj->p[0] = point;
2894                 else if (obj->hitObject)
2895                 {
2896                         double oldRadius = obj->length;
2897                         obj->radius[0] = Vector::Magnitude(obj->p[0], point);
2898
2899                         QString text = QObject::tr("Radius: %1\nScale: %2%");
2900                         informativeText = text.arg(obj->radius[0], 0, 'd', 4).arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0);
2901                 }
2902
2903                 break;
2904
2905         case OTArc:
2906                 if (obj->hitPoint[0])
2907                         obj->p[0] = point;
2908                 else if (obj->hitPoint[1])
2909                 {
2910                         // Change the Arc's span (handle #1)
2911                         if (shiftDown)
2912                         {
2913                                 double angle = Vector::Angle(obj->p[0], point);
2914                                 double delta = angle - obj->angle[0];
2915
2916                                 if (delta < 0)
2917                                         delta += TAU;
2918
2919                                 obj->angle[1] -= delta;
2920                                 obj->angle[0] = angle;
2921
2922                                 if (obj->angle[1] < 0)
2923                                         obj->angle[1] += TAU;
2924
2925                                 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
2926                                 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);
2927                                 return;
2928                         }
2929
2930                         double angle = Vector::Angle(obj->p[0], point);
2931                         obj->angle[0] = angle;
2932                         QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
2933                         informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 4);
2934                 }
2935                 else if (obj->hitPoint[2])
2936                 {
2937                         // Change the Arc's span (handle #2)
2938                         if (shiftDown)
2939                         {
2940                                 double angle = Vector::Angle(obj->p[0], point);
2941                                 obj->angle[1] = angle - obj->angle[0];
2942
2943                                 if (obj->angle[1] < 0)
2944                                         obj->angle[1] += TAU;
2945
2946                                 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
2947                                 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);
2948                                 return;
2949                         }
2950
2951                         double angle = Vector::Angle(obj->p[0], point);
2952                         obj->angle[0] = angle - obj->angle[1];
2953
2954                         if (obj->angle[0] < 0)
2955                                 obj->angle[0] += TAU;
2956
2957                         QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
2958                         informativeText = text.arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 4);
2959                 }
2960                 else if (obj->hitObject)
2961                 {
2962                         if (shiftDown)
2963                         {
2964                                 return;
2965                         }
2966
2967                         obj->radius[0] = Vector::Magnitude(obj->p[0], point);
2968                         QString text = QObject::tr("Radius: %1");
2969                         informativeText = text.arg(obj->radius[0], 0, 'd', 4);
2970                 }
2971
2972                 break;
2973
2974         case OTDimension:
2975                 if (obj->hitPoint[0])
2976                         obj->p[0] = point;
2977                 else if (obj->hitPoint[1])
2978                         obj->p[1] = point;
2979                 else if (obj->hitObject)
2980                 {
2981                         // Move measurement lines in/out
2982                         if (shiftDown)
2983                         {
2984                                 Dimension * d = (Dimension *)obj;
2985                                 double dist = Geometry::DistanceToLineFromPoint(d->lp[0], d->lp[1], point);
2986                                 float scaledThickness = Global::scale * obj->thickness;
2987                                 // Looks like offset is 0 to +MAX, but line is at 10.0.  So
2988                                 // anything less than 10.0 should set the offset to 0.
2989                                 d->offset = 0;
2990
2991                                 if (dist > (10.0 * scaledThickness))
2992                                         d->offset = dist - (10.0 * scaledThickness);
2993                         }
2994                         else
2995                         {
2996                                 obj->p[0] += delta;
2997                                 obj->p[1] += delta;
2998                         }
2999                 }
3000
3001                 break;
3002
3003         case OTText:
3004                 if (obj->hitObject)
3005                         obj->p[0] += delta;
3006
3007                 break;
3008
3009         case OTContainer:
3010                 // This is shitty, but works for now until I can code up something
3011                 // nicer :-)
3012 /*
3013 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.
3014 */
3015 //              TranslateObject(obj, delta);
3016                 TranslateContainer((Container *)obj, point, delta);
3017
3018                 break;
3019         default:
3020                 break;
3021         }
3022 }
3023
3024 void DrawingView::AddDimensionTo(void * o)
3025 {
3026         Object * obj = (Object *)o;
3027
3028         switch (obj->type)
3029         {
3030         case OTLine:
3031                 document.Add(new Dimension(obj->p[0], obj->p[1]));
3032                 break;
3033         case OTCircle:
3034                 break;
3035         case OTEllipse:
3036                 break;
3037         case OTArc:
3038                 break;
3039         case OTSpline:
3040                 break;
3041         }
3042 }