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