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