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