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