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