]> Shamusworld >> Repos - architektonas/blob - src/drawingview.cpp
Add pen toolbar widget.
[architektonas] / src / drawingview.cpp
1 // drawingview.cpp
2 //
3 // Part of the Architektonas Project
4 // (C) 2011 Underground Software
5 // See the README and GPLv3 files for licensing and warranty information
6 //
7 // JLH = James Hammons <jlhamm@acm.org>
8 //
9 // Who  When        What
10 // ---  ----------  ------------------------------------------------------------
11 // JLH  03/22/2011  Created this file
12 // JLH  09/29/2011  Added middle mouse button panning
13 //
14
15 // FIXED:
16 //
17 // - Redo rendering code to *not* use Qt's transform functions, as they are tied
18 //   to a left-handed system and we need a right-handed one. [DONE]
19 //
20 // STILL TO BE DONE:
21 //
22 // - Lots of stuff
23 // - Layer locking (hiding works)
24 //
25
26 // Uncomment this for debugging...
27 //#define DEBUG
28 //#define DEBUGFOO                              // Various tool debugging...
29 //#define DEBUGTP                               // Toolpalette debugging...
30
31 #include "drawingview.h"
32
33 #include <stdint.h>
34 #include "geometry.h"
35 #include "global.h"
36 #include "mathconstants.h"
37 #include "painter.h"
38 #include "structs.h"
39 #include "utils.h"
40
41
42 #define BACKGROUND_MAX_SIZE     512
43
44
45 DrawingView::DrawingView(QWidget * parent/*= NULL*/): QWidget(parent),
46         // The value in the settings file will override this.
47         useAntialiasing(true), /*numSelected(0),*/ numHovered(0), shiftDown(false),
48         ctrlDown(false),
49         gridBackground(BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE),
50         scale(1.0), offsetX(-10), offsetY(-10), document(true),
51         gridPixels(0), collided(false), hoveringIntersection(false),
52         dragged(NULL), draggingObject(false), angleSnap(false)
53 {
54 //wtf? doesn't work except in c++11???  document = { 0 };
55         setBackgroundRole(QPalette::Base);
56         setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
57
58         Global::gridSpacing = 12.0;             // In base units (inch is default)
59 #if 0
60         Line * line = new Line(Vector(5, 5), Vector(50, 40), &document);
61         document.Add(line);
62         document.Add(new Line(Vector(50, 40), Vector(10, 83), &document));
63         document.Add(new Line(Vector(10, 83), Vector(17, 2), &document));
64         document.Add(new Circle(Vector(100, 100), 36, &document));
65         document.Add(new Circle(Vector(50, 150), 49, &document));
66         document.Add(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3, &document)),
67         document.Add(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5, &document));
68 #if 1
69         Dimension * dimension = new Dimension(Vector(0, 0), Vector(0, 0), DTLinear, &document);
70         line->SetDimensionOnLine(dimension);
71         document.Add(dimension);
72 #else
73         // Alternate way to do the above...
74         line->SetDimensionOnLine();
75 #endif
76 #else
77         Line * line = new Line;//(Vector(5, 5), Vector(50, 40), &document);
78         line->p[0] = Vector(5, 5);
79         line->p[1] = Vector(50, 40);
80         line->type = OTLine;
81         line->thickness = 2.0;
82         line->style = LSDash;
83         line->color = 0xFF7F00;
84         line->layer = 0;
85         document.objects.push_back(line);
86         document.objects.push_back(new Line(Vector(50, 40), Vector(10, 83)));
87         document.objects.push_back(new Line(Vector(10, 83), Vector(17, 2)));
88         document.objects.push_back(new Circle(Vector(100, 100), 36));
89         document.objects.push_back(new Circle(Vector(50, 150), 49));
90         document.objects.push_back(new Arc(Vector(300, 300), 32, TAU / 8.0, TAU * 0.65)),
91         document.objects.push_back(new Arc(Vector(200, 200), 60, TAU / 4.0, TAU * 0.75));
92         document.objects.push_back(new Dimension(Vector(50, 40), Vector(5, 5)));
93         document.objects.push_back(new Text(Vector(10, 83), "Here is some awesome text!"));
94 #endif
95
96 /*
97 Here we set the grid size in pixels--12 in this case. Initially, we have our
98 zoom set to make this represent 12 inches at a zoom factor of 25%. (This is
99 arbitrary.) So, to be able to decouple the grid size from the zoom, we need
100 to be able to set the size of the background grid (which we do here at an
101 arbitrary 12 pixels) to anything we want (within reason, of course :-).
102
103 The drawing enforces the grid spacing through the drawing->gridSpacing variable.
104
105         drawing->gridSpacing = 12.0 / Global::zoom;
106
107 Global::zoom is the zoom factor for the drawing, and all mouse clicks are
108 translated to Cartesian coordinates through this. (Initially, Global::zoom is
109 set to 1.0. SCREEN_ZOOM is set to 1.0/4.0.)
110
111 Really, the 100% zoom level can be set at *any* zoom level, it's more of a
112 convenience function than any measure of absolutes. Doing things that way we
113 could rid ourselves of the whole SCREEN_ZOOM parameter and all the attendant
114 shittiness that comes with it.
115
116 However, it seems that SCREEN_ZOOM is used to make text and arrow sizes show up
117 a certain way, which means we should probably create something else in those
118 objects to take its place--like some kind of scale factor. This would seem to
119 imply that certain point sizes actually *do* tie things like fonts to absolute
120 sizes on the screen, but not necessarily because you could have an inch scale
121 with text that is quite small relative to other objects on the screen, which
122 currently you have to zoom in to see (and which blows up the text). Point sizes
123 in an application like this are a bit meaningless; even though an inch is an
124 inch regardless of the zoom level a piece of text can be larger or smaller than
125 this. Maybe this is the case for having a base unit and basing point sizes off
126 of that.
127
128 Here's what's been figured out. Global::zoom is simply the ratio of pixels to
129 base units. What that means is that if you have a 12px grid with a 6" grid size
130 (& base unit of "inches"), Global::zoom becomes 12px / 6" = 2.0 px/in.
131
132 Dimensions now have a "size" parameter to set their absolute size in relation
133 to the base unit. ATM, the arrows are drawn in pixels, but also scaled by
134 Global::zoom *and* size. Same with the dimension text; it's drawn at 10pt and
135 scaled the same way as the arrowheads.
136
137 Need a way to scale line widths as well. :-/ Shouldn't be too difficult, just
138 need a thickness parameter similar to the "size" param for dimensions. (And now
139 we do! :-)
140
141 */
142         SetGridSize(12);        // This is in pixels
143 }
144
145
146 void DrawingView::SetGridSize(uint32_t size)
147 {
148         // Sanity check
149         if (size == gridPixels)
150                 return;
151
152         // Recreate the background bitmap
153         gridPixels = size;
154         QPainter pmp(&gridBackground);
155         pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
156         pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
157
158         for(int i=0; i<(BACKGROUND_MAX_SIZE-1); i+=gridPixels)
159         {
160                 pmp.drawLine(i, 0, i, BACKGROUND_MAX_SIZE - 1);
161                 pmp.drawLine(0, i, BACKGROUND_MAX_SIZE - 1, i);
162         }
163
164         pmp.end();
165
166         // Set up new BG brush & zoom level (pixels per base unit)
167         Global::zoom = gridPixels / Global::gridSpacing;
168         UpdateGridBackground();
169 }
170
171
172 void DrawingView::UpdateGridBackground(void)
173 {
174         // Transform the origin to Qt coordinates
175         Vector pixmapOrigin = Painter::CartesianToQtCoords(Vector());
176         int x = (int)pixmapOrigin.x;
177         int y = (int)pixmapOrigin.y;
178         // Use mod arithmetic to grab the correct swatch of background
179 /*
180 Negative numbers still screw it up... Need to think about what we're
181 trying to do here. The fact that it worked with 72 seems to have been pure luck.
182 It seems the problem is negative numbers: We can't let that happen.
183 When taking away the zero, it pops over 1 px at zero, then goes about 1/2 a
184 grid at x<0.
185
186 The bitmap looks like this:
187
188 +---+---+---+---+---
189 |   |   |   |   |
190 |   |   |   |   |
191 +---+---+---+---+---
192 |   |   |   |   |
193 |   |   |   |   |
194 |   |   |   |   |
195
196 @ x = 1, we want it to look like:
197
198 -+---+---+---+---+---
199  |   |   |   |   |
200  |   |   |   |   |
201 -+---+---+---+---+---
202  |   |   |   |   |
203  |   |   |   |   |
204  |   |   |   |   |
205
206 Which means we need to grab the sample from x = 3. @ x = -1:
207
208 ---+---+---+---+---
209    |   |   |   |
210    |   |   |   |
211 ---+---+---+---+---
212    |   |   |   |
213    |   |   |   |
214    |   |   |   |
215
216 Which means we need to grab the sample from x = 1. Which means we have to take
217 the mirror of the modulus of gridPixels.
218
219 Doing a mod of a negative number is problematic: 1st, the compiler converts the
220 negative number to an unsigned int, then it does the mod. Gets you wrong answers
221 most of the time, unless you use a power of 2. :-P So what we do here is just
222 take the modulus of the negation, which means we don't have to worry about
223 mirroring it later.
224
225 The positive case looks gruesome (and it is) but it boils down to this: We take
226 the modulus of the X coordinate, then mirror it by subtraction from the
227 maximum (in this case, gridPixels). This gives us a number in the range of 1 to
228 gridPixels. But we need the case where the result equalling gridPixels to be
229 zero; so we do another modulus operation on the result to achieve this.
230 */
231         if (x < 0)
232                 x = -x % gridPixels;
233         else
234                 x = (gridPixels - (x % gridPixels)) % gridPixels;
235
236         if (y < 0)
237                 y = -y % gridPixels;
238         else
239                 y = (gridPixels - (y % gridPixels)) % gridPixels;
240
241         // Here we grab a section of the bigger pixmap, so that the background
242         // *looks* like it's scrolling...
243         QPixmap pm = gridBackground.copy(x, y, gridPixels, gridPixels);
244         QPalette pal = palette();
245         pal.setBrush(backgroundRole(), QBrush(pm));
246         setAutoFillBackground(true);
247         setPalette(pal);
248 }
249
250
251 //
252 // Basically, we just make a single pass through the Container. If the layer #
253 // is less than the layer # being deleted, then do nothing. If the layer # is
254 // equal to the layer # being deleted, then delete the object. If the layer #
255 // is greater than the layer # being deleted, then set the layer # to its layer
256 // # - 1.
257 //
258 void DrawingView::DeleteCurrentLayer(int layer)
259 {
260 //printf("DrawingView::DeleteCurrentLayer(): currentLayer = %i\n", layer);
261         std::vector<void *>::iterator i = document.objects.begin();
262
263         while (i != document.objects.end())
264         {
265                 Object * obj = (Object *)(*i);
266
267                 if (obj->layer < layer)
268                         i++;
269                 else if (obj->layer == layer)
270                 {
271                         document.objects.erase(i);
272                         delete obj;
273                 }
274                 else
275                 {
276                         obj->layer--;
277                         i++;
278                 }
279         }
280
281         // We've just done a destructive action, so update the screen!
282         update();
283 }
284
285
286 void DrawingView::HandleLayerToggle(void)
287 {
288         // A layer's visibility was toggled, so update the screen...
289         update();
290 }
291
292
293 //
294 // A layer was moved up or down in the layer list, so we have to swap the
295 // document's object's layer numbers in the layers that were swapped.
296 //
297 void DrawingView::HandleLayerSwap(int layer1, int layer2)
298 {
299 //printf("DrawingView: Swapping layers %i and %i.\n", layer1, layer2);
300         std::vector<void *>::iterator i;
301
302         for(i=document.objects.begin(); i!=document.objects.end(); i++)
303         {
304                 Object * obj = (Object *)(*i);
305
306                 if (obj->layer == layer1)
307                         obj->layer = layer2;
308                 else if (obj->layer == layer2)
309                         obj->layer = layer1;
310         }
311 }
312
313
314 void DrawingView::HandlePenWidth(float width)
315 {
316         std::vector<void *>::iterator i = select.begin();
317
318         for(; i!=select.end(); i++)
319         {
320                 Object * obj = (Object *)(*i);
321                 obj->thickness = width;
322         }
323 }
324
325
326 void DrawingView::HandlePenStyle(int style)
327 {
328         std::vector<void *>::iterator i = select.begin();
329
330         for(; i!=select.end(); i++)
331         {
332                 Object * obj = (Object *)(*i);
333                 obj->style = style;
334         }
335 }
336
337
338 void DrawingView::HandlePenColor(uint32_t color)
339 {
340         std::vector<void *>::iterator i = select.begin();
341
342         for(; i!=select.end(); i++)
343         {
344                 Object * obj = (Object *)(*i);
345                 obj->color = color;
346         }
347 }
348
349
350 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
351 {
352         // This is undoing the transform, e.g. going from client coords to local
353         // coords. In essence, the height - y is height + (y * -1), the (y * -1)
354         // term doing the conversion of the y-axis from increasing bottom to top.
355         return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
356 }
357
358
359 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
360 {
361         // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
362         // No voodoo here, it's just grouped wrong to see it. It should be:
363         // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive [why? we use -offsetX after all]
364         return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
365 }
366
367
368 void DrawingView::paintEvent(QPaintEvent * /*event*/)
369 {
370         QPainter qtPainter(this);
371         Painter painter(&qtPainter);
372
373         if (useAntialiasing)
374                 qtPainter.setRenderHint(QPainter::Antialiasing);
375
376         Global::viewportHeight = size().height();
377
378         // Draw coordinate axes
379         painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
380         painter.DrawLine(0, -16384, 0, 16384);
381         painter.DrawLine(-16384, 0, 16384, 0);
382
383         // Do object rendering...
384         for(int i=0; i<Global::numLayers; i++)
385         {
386                 if (Global::layerHidden[i] == false)
387                         RenderObjects(&painter, document.objects, i);
388         }
389
390         // Do tool rendering, if any...
391         if (Global::tool)
392         {
393                 painter.SetPen(QPen(QColor(200, 100, 0, 255), 1.0, Qt::DashLine));
394                 painter.DrawCrosshair(oldPoint);
395                 ToolDraw(&painter);
396         }
397
398         // Do selection rectangle rendering, if any
399         if (Global::selectionInProgress)
400         {
401                 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
402                 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
403                 painter.DrawRect(Global::selection);
404         }
405
406         if (hoveringIntersection)
407                 painter.DrawHandle(intersectionPoint);
408
409         if (!informativeText.isEmpty())
410                 painter.DrawInformativeText(informativeText);
411 }
412
413
414 //
415 // Renders objects in the passed in vector
416 //
417 void DrawingView::RenderObjects(Painter * painter, std::vector<void *> & v, int layer, bool ignoreLayer/*= false*/)
418 {
419         std::vector<void *>::iterator i;
420
421         for(i=v.begin(); i!=v.end(); i++)
422         {
423                 Object * obj = (Object *)(*i);
424                 float scaledThickness = Global::scale * obj->thickness;
425
426                 // If the object isn't on the current layer being drawn, skip it
427                 if (!ignoreLayer && (obj->layer != layer))
428                         continue;
429
430                 if ((Global::tool == TTRotate) && ctrlDown && obj->selected)
431                 {
432                         painter->SetPen(0x00FF00, 2.0, LSSolid);
433                 }
434                 else
435                 {
436                         painter->SetPen(obj->color, Global::zoom * scaledThickness, obj->style);
437                         painter->SetBrush(obj->color);
438
439                         if (obj->selected || obj->hitObject)
440                                 painter->SetPen(0xFF0000, Global::zoom * scaledThickness, LSDash);
441                 }
442
443                 switch (obj->type)
444                 {
445                 case OTLine:
446                         painter->DrawLine(obj->p[0], obj->p[1]);
447
448                         if (obj->hitPoint[0])
449                                 painter->DrawHandle(obj->p[0]);
450
451                         if (obj->hitPoint[1])
452                                 painter->DrawHandle(obj->p[1]);
453
454                         break;
455                 case OTCircle:
456                         painter->SetBrush(QBrush(Qt::NoBrush));
457                         painter->DrawEllipse(obj->p[0], obj->radius[0], obj->radius[0]);
458
459                         if (obj->hitPoint[0])
460                                 painter->DrawHandle(obj->p[0]);
461
462                         break;
463                 case OTArc:
464                         painter->DrawArc(obj->p[0], obj->radius[0], obj->angle[0], obj->angle[1]);
465
466                         if (obj->hitPoint[0])
467                                 painter->DrawHandle(obj->p[0]);
468
469                         if (obj->hitPoint[1])
470                                 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]));
471
472                         if (obj->hitPoint[2])
473                                 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0] + obj->angle[1]), sin(obj->angle[0] + obj->angle[1])) * obj->radius[0]));
474
475                         break;
476                 case OTDimension:
477                 {
478                         Dimension * d = (Dimension *)obj;
479
480                         Vector v(d->p[0], d->p[1]);
481                         double angle = v.Angle();
482                         Vector unit = v.Unit();
483                         d->lp[0] = d->p[0], d->lp[1] = d->p[1];
484                         Vector ortho;
485                         double x1, y1, length;
486
487                         if (d->subtype == DTLinearVert)
488                         {
489                                 if ((angle < 0) || (angle > HALF_TAU))
490                                 {
491                                         x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
492                                         y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
493                                         ortho = Vector(1.0, 0);
494                                         angle = THREE_QTR_TAU;
495                                 }
496                                 else
497                                 {
498                                         x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
499                                         y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
500                                         ortho = Vector(-1.0, 0);
501                                         angle = QTR_TAU;
502                                 }
503
504                                 d->lp[0].x = d->lp[1].x = x1;
505                                 length = fabs(d->p[0].y - d->p[1].y);
506                         }
507                         else if (d->subtype == DTLinearHorz)
508                         {
509                                 if ((angle < QTR_TAU) || (angle > THREE_QTR_TAU))
510                                 {
511                                         x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
512                                         y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
513                                         ortho = Vector(0, 1.0);
514                                         angle = 0;
515                                 }
516                                 else
517                                 {
518                                         x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
519                                         y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
520                                         ortho = Vector(0, -1.0);
521                                         angle = HALF_TAU;
522                                 }
523
524                                 d->lp[0].y = d->lp[1].y = y1;
525                                 length = fabs(d->p[0].x - d->p[1].x);
526                         }
527                         else if (d->subtype == DTLinear)
528                         {
529                                 angle = Vector(d->lp[0], d->lp[1]).Angle();
530                                 ortho = Vector::Normal(d->lp[0], d->lp[1]);
531                                 length = v.Magnitude();
532                         }
533
534                         unit = Vector(d->lp[0], d->lp[1]).Unit();
535
536                         Point p1 = d->lp[0] + (ortho * 10.0 * scaledThickness);
537                         Point p2 = d->lp[1] + (ortho * 10.0 * scaledThickness);
538                         Point p3 = d->lp[0] + (ortho * 16.0 * scaledThickness);
539                         Point p4 = d->lp[1] + (ortho * 16.0 * scaledThickness);
540                         Point p5 = d->p[0] + (ortho * 4.0 * scaledThickness);
541                         Point p6 = d->p[1] + (ortho * 4.0 * scaledThickness);
542
543                 /*
544                 The numbers hardcoded into here, what are they?
545                 I believe they are pixels.
546                 */
547                         // Draw extension lines (if certain type)
548                         painter->DrawLine(p3, p5);
549                         painter->DrawLine(p4, p6);
550
551                         // Calculate whether or not the arrowheads are too crowded to put
552                         // inside the extension lines. 9.0 is the length of the arrowhead.
553                         double t = Geometry::ParameterOfLineAndPoint(d->lp[0], d->lp[1], d->lp[1] - (unit * 9.0 * scaledThickness));
554
555                         // On the screen, it's acting like this is actually 58%...
556                         // This is correct, we want it to happen at > 50%
557                         if (t > 0.58)
558                         {
559                                 // Draw main dimension line + arrowheads
560                                 painter->DrawLine(p1, p2);
561                                 painter->DrawArrowhead(p1, p2, scaledThickness);
562                                 painter->DrawArrowhead(p2, p1, scaledThickness);
563                         }
564                         else
565                         {
566                                 // Draw outside arrowheads
567                                 Point p7 = p1 - (unit * 9.0 * scaledThickness);
568                                 Point p8 = p2 + (unit * 9.0 * scaledThickness);
569                                 painter->DrawArrowhead(p1, p7, scaledThickness);
570                                 painter->DrawArrowhead(p2, p8, scaledThickness);
571                                 painter->DrawLine(p1, p1 - (unit * 14.0 * scaledThickness));
572                                 painter->DrawLine(p2, p2 + (unit * 14.0 * scaledThickness));
573                         }
574
575                         // Draw length of dimension line...
576                         painter->SetFont(QFont("Arial", 8.0 * Global::zoom * scaledThickness));
577                         Point ctr = p2 + (Vector(p2, p1) / 2.0);
578
579                         QString dimText;
580
581                         if (length < 12.0)
582                                 dimText = QString("%1\"").arg(length);
583                         else
584                         {
585                                 double feet = (double)((int)length / 12);
586                                 double inches = length - (feet * 12.0);
587
588                                 if (inches == 0)
589                                         dimText = QString("%1'").arg(feet);
590                                 else
591                                         dimText = QString("%1' %2\"").arg(feet).arg(inches);
592                         }
593
594                         painter->DrawAngledText(ctr, angle, dimText, scaledThickness);
595
596                         if (d->hitObject)
597                         {
598                                 Point hp1 = (p1 + p2) / 2.0;
599                                 Point hp2 = (p1 + hp1) / 2.0;
600                                 Point hp3 = (hp1 + p2) / 2.0;
601
602                                 if (d->hitPoint[2])
603                                 {
604                                         painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
605                                         painter->SetBrush(QBrush(QColor(Qt::magenta)));
606                                         painter->DrawArrowHandle(hp1, ortho.Angle() + HALF_TAU);
607                                         painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
608                                 }
609
610                                 painter->DrawHandle(hp1);
611                                 painter->SetPen(QPen(Qt::blue, 1.0 * Global::zoom * scaledThickness, Qt::SolidLine));
612
613                                 if (d->hitPoint[3])
614                                 {
615                                         painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
616                                         painter->SetBrush(QBrush(QColor(Qt::magenta)));
617                                         painter->DrawArrowToLineHandle(hp2, (d->subtype == DTLinearVert ? v.Angle() - QTR_TAU : (v.Angle() < HALF_TAU ? HALF_TAU : 0)));
618                                         painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
619                                 }
620
621                                 painter->DrawHandle(hp2);
622                                 painter->SetPen(QPen(Qt::blue, 1.0 * Global::zoom * scaledThickness, Qt::SolidLine));
623
624                                 if (d->hitPoint[4])
625                                 {
626                                         painter->SetPen(QPen(Qt::magenta, 1.0, Qt::SolidLine));
627                                         painter->SetBrush(QBrush(QColor(Qt::magenta)));
628                                         painter->DrawArrowToLineHandle(hp3, (d->subtype == DTLinearHorz ? v.Angle() - QTR_TAU : (v.Angle() > HALF_TAU && v.Angle() < THREE_QTR_TAU ? THREE_QTR_TAU : QTR_TAU)));
629                                         painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DotLine));
630                                 }
631
632                                 painter->DrawHandle(hp3);
633                         }
634
635                         if (obj->hitPoint[0])
636                                 painter->DrawHandle(obj->p[0]);
637
638                         if (obj->hitPoint[1])
639                                 painter->DrawHandle(obj->p[1]);
640
641                         break;
642                 }
643                 case OTText:
644                 {
645                         Text * t = (Text *)obj;
646
647                         if (t->measured == false)
648                         {
649                                 t->extents = painter->MeasureTextObject(t->s.c_str(), scaledThickness);
650                                 t->measured = true;
651                         }
652
653                         painter->DrawTextObject(t->p[0], t->s.c_str(), scaledThickness, t->angle[0]);
654                         break;
655                 }
656                 case OTSpline:
657                 {
658                         break;
659                 }
660                 case OTPolygon:
661                 {
662                         break;
663                 }
664                 case OTContainer:
665                 {
666                         // Containers require recursive rendering...
667                         Container * c = (Container *)obj;
668                         RenderObjects(painter, (*c).objects, layer);
669
670 //printf("Container extents: <%lf, %lf>, <%lf, %lf>\nsize: %i\n", r.l, r.t, r.r, r.b, c->objects.size());
671                         // Containers also have special indicators showing they are selected
672                         if (c->selected || c->hitObject)
673                         {
674                                 Rect r = GetObjectExtents(obj);
675                                 painter->DrawRectCorners(r);
676                         }
677
678                         break;
679                 }
680                 default:
681                         break;
682                 }
683         }
684 }
685
686
687 void DrawingView::AddHoveredToSelection(void)
688 {
689         std::vector<void *>::iterator i;
690
691         for(i=document.objects.begin(); i!=document.objects.end(); i++)
692         {
693                 if (((Object *)(*i))->hovered)
694                         ((Object *)(*i))->selected = true;
695         }
696 }
697
698
699 void DrawingView::GetSelection(std::vector<void *> & v)
700 {
701         v.clear();
702         std::vector<void *>::iterator i;
703
704         for(i=document.objects.begin(); i!=document.objects.end(); i++)
705         {
706                 if (((Object *)(*i))->selected)
707                         v.push_back(*i);
708         }
709 }
710
711
712 void DrawingView::GetHovered(std::vector<void *> & v)
713 {
714         v.clear();
715         std::vector<void *>::iterator i;
716
717         for(i=document.objects.begin(); i!=document.objects.end(); i++)
718         {
719                 if (((Object *)(*i))->hovered)
720 //              {
721 //printf("GetHovered: adding object (%X) to hover... hp1=%s, hp2=%s, hl=%s\n", (*i), (((Line *)(*i))->hitPoint[0] ? "true" : "false"), (((Line *)(*i))->hitPoint[1] ? "true" : "false"), (((Line *)(*i))->hitObject ? "true" : "false"));
722                         v.push_back(*i);
723 //              }
724         }
725 }
726
727
728 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
729 {
730         Global::screenSize = Vector(size().width(), size().height());
731         UpdateGridBackground();
732 }
733
734
735 void DrawingView::ToolHandler(int mode, Point p)
736 {
737         // Drop angle snap until it's needed
738         angleSnap = false;
739
740         if (Global::tool == TTLine)
741                 LineHandler(mode, p);
742         else if (Global::tool == TTCircle)
743                 CircleHandler(mode, p);
744         else if (Global::tool == TTArc)
745                 ArcHandler(mode, p);
746         else if (Global::tool == TTRotate)
747                 RotateHandler(mode, p);
748         else if (Global::tool == TTMirror)
749                 MirrorHandler(mode, p);
750 }
751
752
753 void DrawingView::ToolDraw(Painter * painter)
754 {
755         if (Global::tool == TTLine)
756         {
757                 if (Global::toolState == TSNone)
758                 {
759                         painter->DrawHandle(toolPoint[0]);
760                 }
761                 else if ((Global::toolState == TSPoint2) && shiftDown)
762                 {
763                         painter->DrawHandle(toolPoint[1]);
764                 }
765                 else
766                 {
767                         painter->DrawLine(toolPoint[0], toolPoint[1]);
768                         painter->DrawHandle(toolPoint[1]);
769
770                         Vector v(toolPoint[0], toolPoint[1]);
771                         double absAngle = v.Angle() * RADIANS_TO_DEGREES;
772                         double absLength = v.Magnitude();
773                         QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
774                         informativeText = text.arg(absLength).arg(absAngle);
775                 }
776         }
777         else if (Global::tool == TTCircle)
778         {
779                 if (Global::toolState == TSNone)
780                 {
781                         painter->DrawHandle(toolPoint[0]);
782                 }
783                 else if ((Global::toolState == TSPoint2) && shiftDown)
784                 {
785                         painter->DrawHandle(toolPoint[1]);
786                 }
787                 else
788                 {
789                         painter->DrawCross(toolPoint[0]);
790                         double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
791                         painter->SetBrush(QBrush(Qt::NoBrush));
792                         painter->DrawEllipse(toolPoint[0], length, length);
793                         QString text = tr("Radius: %1 in.");
794                         informativeText = text.arg(length);
795                 }
796         }
797         else if (Global::tool == TTArc)
798         {
799                 if (Global::toolState == TSNone)
800                 {
801                         painter->DrawHandle(toolPoint[0]);
802                 }
803                 else if (Global::toolState == TSPoint2)
804                 {
805                         double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
806                         painter->SetBrush(QBrush(Qt::NoBrush));
807                         painter->DrawEllipse(toolPoint[0], length, length);
808                         painter->DrawLine(toolPoint[0], toolPoint[1]);
809                         painter->DrawHandle(toolPoint[1]);
810                         QString text = tr("Radius: %1 in.");
811                         informativeText = text.arg(length);
812                 }
813                 else if (Global::toolState == TSPoint3)
814                 {
815                         double angle = Vector::Angle(toolPoint[0], toolPoint[2]);
816                         painter->DrawLine(toolPoint[0], toolPoint[2]);
817                         painter->SetBrush(QBrush(Qt::NoBrush));
818                         painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
819                         painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
820                         QString text = tr("Angle start: %1") + QChar(0x00B0);
821                         informativeText = text.arg(RADIANS_TO_DEGREES * angle);
822                 }
823                 else
824                 {
825                         double angle = Vector::Angle(toolPoint[0], toolPoint[3]);
826                         double span = angle - toolPoint[2].x;
827
828                         if (span < 0)
829                                 span += TAU;
830
831                         painter->DrawLine(toolPoint[0], toolPoint[3]);
832                         painter->SetBrush(QBrush(Qt::NoBrush));
833                         painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
834                         painter->SetPen(0xFF00FF, 2.0, LSSolid);
835                         painter->DrawArc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
836                         painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
837                         QString text = tr("Arc span: %1") + QChar(0x00B0);
838                         informativeText = text.arg(RADIANS_TO_DEGREES * span);
839                 }
840         }
841         else if (Global::tool == TTRotate)
842         {
843                 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
844                         painter->DrawHandle(toolPoint[0]);
845                 else if ((Global::toolState == TSPoint2) && shiftDown)
846                         painter->DrawHandle(toolPoint[1]);
847                 else
848                 {
849                         if (toolPoint[0] == toolPoint[1])
850                                 return;
851
852                         painter->DrawLine(toolPoint[0], toolPoint[1]);
853
854                         double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
855                         QString text = QChar(0x2221) + QObject::tr(": %1");
856                         informativeText = text.arg(absAngle);
857
858                         if (ctrlDown)
859                                 informativeText += " (Copy)";
860                 }
861         }
862         else if (Global::tool == TTMirror)
863         {
864                 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
865                         painter->DrawHandle(toolPoint[0]);
866                 else if ((Global::toolState == TSPoint2) && shiftDown)
867                         painter->DrawHandle(toolPoint[1]);
868                 else
869                 {
870                         if (toolPoint[0] == toolPoint[1])
871                                 return;
872
873                         Point mirrorPoint = toolPoint[0] + Vector(toolPoint[1], toolPoint[0]);
874                         painter->DrawLine(mirrorPoint, toolPoint[1]);
875
876                         double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
877
878                         if (absAngle > 180.0)
879                                 absAngle -= 180.0;
880
881                         QString text = QChar(0x2221) + QObject::tr(": %1");
882                         informativeText = text.arg(absAngle);
883
884                         if (ctrlDown)
885                                 informativeText += " (Copy)";
886                 }
887         }
888 }
889
890
891 void DrawingView::LineHandler(int mode, Point p)
892 {
893         switch (mode)
894         {
895         case ToolMouseDown:
896                 if (Global::toolState == TSNone)
897                         toolPoint[0] = p;
898                 else
899                         toolPoint[1] = p;
900
901                 break;
902         case ToolMouseMove:
903                 if (Global::toolState == TSNone)
904                         toolPoint[0] = p;
905                 else
906                         toolPoint[1] = p;
907
908                 break;
909         case ToolMouseUp:
910                 if (Global::toolState == TSNone)
911                 {
912                         Global::toolState = TSPoint2;
913                         // Prevent spurious line from drawing...
914                         toolPoint[1] = toolPoint[0];
915                 }
916                 else if ((Global::toolState == TSPoint2) && shiftDown)
917                 {
918                         // Key override is telling us to make a new line, not continue the
919                         // previous one.
920                         toolPoint[0] = toolPoint[1];
921                 }
922                 else
923                 {
924                         Line * l = new Line(toolPoint[0], toolPoint[1]);
925                         l->layer = Global::activeLayer;
926                         document.objects.push_back(l);
927                         toolPoint[0] = toolPoint[1];
928                 }
929         }
930 }
931
932
933 void DrawingView::CircleHandler(int mode, Point p)
934 {
935         switch (mode)
936         {
937         case ToolMouseDown:
938                 if (Global::toolState == TSNone)
939                         toolPoint[0] = p;
940                 else
941                         toolPoint[1] = p;
942
943                 break;
944         case ToolMouseMove:
945                 if (Global::toolState == TSNone)
946                         toolPoint[0] = p;
947                 else
948                         toolPoint[1] = p;
949
950                 break;
951         case ToolMouseUp:
952                 if (Global::toolState == TSNone)
953                 {
954                         Global::toolState = TSPoint2;
955                         // Prevent spurious line from drawing...
956                         toolPoint[1] = toolPoint[0];
957                 }
958                 else if ((Global::toolState == TSPoint2) && shiftDown)
959                 {
960                         // Key override is telling us to make a new line, not continue the
961                         // previous one.
962                         toolPoint[0] = toolPoint[1];
963                 }
964                 else
965                 {
966                         double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
967                         Circle * c = new Circle(toolPoint[0], length);
968                         c->layer = Global::activeLayer;
969                         document.objects.push_back(c);
970                         toolPoint[0] = toolPoint[1];
971                         Global::toolState = TSNone;
972                 }
973         }
974 }
975
976
977 void DrawingView::ArcHandler(int mode, Point p)
978 {
979         switch (mode)
980         {
981         case ToolMouseDown:
982                 if (Global::toolState == TSNone)
983                         toolPoint[0] = p;
984                 else if (Global::toolState == TSPoint2)
985                         toolPoint[1] = p;
986                 else if (Global::toolState == TSPoint3)
987                         toolPoint[2] = p;
988                 else
989                         toolPoint[3] = p;
990
991                 break;
992         case ToolMouseMove:
993                 if (Global::toolState == TSNone)
994                         toolPoint[0] = p;
995                 else if (Global::toolState == TSPoint2)
996                         toolPoint[1] = p;
997                 else if (Global::toolState == TSPoint3)
998                 {
999                         toolPoint[2] = p;
1000                         angleSnap = true;
1001                 }
1002                 else
1003                 {
1004                         toolPoint[3] = p;
1005                         angleSnap = true;
1006                 }
1007
1008                 break;
1009         case ToolMouseUp:
1010                 if (Global::toolState == TSNone)
1011                 {
1012                         // Prevent spurious line from drawing...
1013                         toolPoint[1] = toolPoint[0];
1014                         Global::toolState = TSPoint2;
1015                 }
1016                 else if (Global::toolState == TSPoint2)
1017                 {
1018                         if (shiftDown)
1019                         {
1020                                 // Key override is telling us to start arc at new center, not
1021                                 // continue the current one.
1022                                 toolPoint[0] = toolPoint[1];
1023                                 return;
1024                         }
1025
1026                         // Set the radius in toolPoint[1].x
1027                         toolPoint[1].x = Vector::Magnitude(toolPoint[0], toolPoint[1]);
1028                         Global::toolState = TSPoint3;
1029                 }
1030                 else if (Global::toolState == TSPoint3)
1031                 {
1032                         // Set the angle in toolPoint[2].x
1033                         toolPoint[2].x = Vector::Angle(toolPoint[0], toolPoint[2]);
1034                         Global::toolState = TSPoint4;
1035                 }
1036                 else
1037                 {
1038                         double endAngle = Vector::Angle(toolPoint[0], toolPoint[3]);
1039                         double span = endAngle - toolPoint[2].x;
1040
1041                         if (span < 0)
1042                                 span += TAU;
1043
1044                         Arc * arc = new Arc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
1045                         arc->layer = Global::activeLayer;
1046                         document.objects.push_back(arc);
1047                         Global::toolState = TSNone;
1048                 }
1049         }
1050 }
1051
1052
1053 void DrawingView::RotateHandler(int mode, Point p)
1054 {
1055         switch (mode)
1056         {
1057         case ToolMouseDown:
1058                 if (Global::toolState == TSNone)
1059                 {
1060                         toolPoint[0] = p;
1061                         SavePointsFrom(select, toolScratch);
1062                         Global::toolState = TSPoint1;
1063                 }
1064                 else if (Global::toolState == TSPoint1)
1065                         toolPoint[0] = p;
1066                 else
1067                         toolPoint[1] = p;
1068
1069                 break;
1070         case ToolMouseMove:
1071                 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1072                         toolPoint[0] = p;
1073                 else if (Global::toolState == TSPoint2)
1074                 {
1075                         toolPoint[1] = p;
1076
1077                         if (shiftDown)
1078                                 return;
1079
1080                         angleSnap = true;
1081                         double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1082                         std::vector<void *>::iterator j = select.begin();
1083                         std::vector<Object>::iterator i = toolScratch.begin();
1084
1085                         for(; i!=toolScratch.end(); i++, j++)
1086                         {
1087                                 Object obj = *i;
1088                                 Point p1 = Geometry::RotatePointAroundPoint(obj.p[0], toolPoint[0], angle);
1089                                 Point p2 = Geometry::RotatePointAroundPoint(obj.p[1], toolPoint[0], angle);
1090                                 Object * obj2 = (Object *)(*j);
1091                                 obj2->p[0] = p1;
1092                                 obj2->p[1] = p2;
1093
1094                                 if (obj.type == OTArc)
1095                                 {
1096                                         obj2->angle[0] = obj.angle[0] + angle;
1097
1098                                         if (obj2->angle[0] > TAU)
1099                                                 obj2->angle[0] -= TAU;
1100                                 }
1101                         }
1102                 }
1103
1104                 break;
1105         case ToolMouseUp:
1106                 if (Global::toolState == TSPoint1)
1107                 {
1108                         Global::toolState = TSPoint2;
1109                         // Prevent spurious line from drawing...
1110                         toolPoint[1] = toolPoint[0];
1111                 }
1112                 else if ((Global::toolState == TSPoint2) && shiftDown)
1113                 {
1114                         // Key override is telling us to make a new line, not continue the
1115                         // previous one.
1116                         toolPoint[0] = toolPoint[1];
1117                 }
1118                 else
1119                 {
1120                         // Either we're finished with our rotate, or we're stamping a copy.
1121                         if (ctrlDown)
1122                         {
1123                                 // Stamp a copy of the selection at the current rotation & bail
1124                                 std::vector<void *> temp;
1125                                 CopyObjects(select, temp);
1126                                 ClearSelected(temp);
1127                                 AddObjectsTo(document.objects, temp);
1128                                 RestorePointsTo(select, toolScratch);
1129                                 return;
1130                         }
1131
1132                         toolPoint[0] = p;
1133                         Global::toolState = TSPoint1;
1134                         SavePointsFrom(select, toolScratch);
1135                 }
1136
1137                 break;
1138         case ToolKeyDown:
1139                 // Reset the selection if shift held down...
1140                 if (shiftDown)
1141                         RestorePointsTo(select, toolScratch);
1142
1143                 break;
1144         case ToolKeyUp:
1145                 // Reset selection when key is let up
1146                 if (!shiftDown)
1147                         RotateHandler(ToolMouseMove, toolPoint[1]);
1148
1149                 break;
1150         case ToolCleanup:
1151                 RestorePointsTo(select, toolScratch);
1152         }
1153 }
1154
1155
1156 void DrawingView::MirrorHandler(int mode, Point p)
1157 {
1158         switch (mode)
1159         {
1160         case ToolMouseDown:
1161                 if (Global::toolState == TSNone)
1162                 {
1163                         toolPoint[0] = p;
1164                         SavePointsFrom(select, toolScratch);
1165                         Global::toolState = TSPoint1;
1166                 }
1167                 else if (Global::toolState == TSPoint1)
1168                         toolPoint[0] = p;
1169                 else
1170                         toolPoint[1] = p;
1171
1172                 break;
1173         case ToolMouseMove:
1174                 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1175                         toolPoint[0] = p;
1176                 else if (Global::toolState == TSPoint2)
1177                 {
1178                         toolPoint[1] = p;
1179
1180                         if (shiftDown)
1181                                 return;
1182
1183                         angleSnap = true;
1184                         double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1185                         std::vector<void *>::iterator j = select.begin();
1186                         std::vector<Object>::iterator i = toolScratch.begin();
1187
1188                         for(; i!=toolScratch.end(); i++, j++)
1189                         {
1190                                 Object obj = *i;
1191                                 Point p1 = Geometry::MirrorPointAroundLine(obj.p[0], toolPoint[0], toolPoint[1]);
1192                                 Point p2 = Geometry::MirrorPointAroundLine(obj.p[1], toolPoint[0], toolPoint[1]);
1193                                 Object * obj2 = (Object *)(*j);
1194                                 obj2->p[0] = p1;
1195                                 obj2->p[1] = p2;
1196
1197                                 if (obj.type == OTArc)
1198                                 {
1199                                         // This is 2*mirror angle - obj angle - obj span
1200                                         obj2->angle[0] = (2.0 * angle) - obj.angle[0] - obj.angle[1];
1201
1202                                         if (obj2->angle[0] > TAU)
1203                                                 obj2->angle[0] -= TAU;
1204                                 }
1205                         }
1206                 }
1207
1208                 break;
1209         case ToolMouseUp:
1210                 if (Global::toolState == TSPoint1)
1211                 {
1212                         Global::toolState = TSPoint2;
1213                         // Prevent spurious line from drawing...
1214                         toolPoint[1] = toolPoint[0];
1215                 }
1216                 else if ((Global::toolState == TSPoint2) && shiftDown)
1217                 {
1218                         // Key override is telling us to make a new line, not continue the
1219                         // previous one.
1220                         toolPoint[0] = toolPoint[1];
1221                 }
1222                 else
1223                 {
1224                         // Either we're finished with our rotate, or we're stamping a copy.
1225                         if (ctrlDown)
1226                         {
1227                                 // Stamp a copy of the selection at the current rotation & bail
1228                                 std::vector<void *> temp;
1229                                 CopyObjects(select, temp);
1230                                 ClearSelected(temp);
1231                                 AddObjectsTo(document.objects, temp);
1232                                 RestorePointsTo(select, toolScratch);
1233                                 return;
1234                         }
1235
1236                         toolPoint[0] = p;
1237                         Global::toolState = TSPoint1;
1238                         SavePointsFrom(select, toolScratch);
1239                 }
1240
1241                 break;
1242         case ToolKeyDown:
1243                 // Reset the selection if shift held down...
1244                 if (shiftDown)
1245                         RestorePointsTo(select, toolScratch);
1246
1247                 break;
1248         case ToolKeyUp:
1249                 // Reset selection when key is let up
1250                 if (!shiftDown)
1251                         MirrorHandler(ToolMouseMove, toolPoint[1]);
1252
1253                 break;
1254         case ToolCleanup:
1255                 RestorePointsTo(select, toolScratch);
1256         }
1257 }
1258
1259
1260 void DrawingView::mousePressEvent(QMouseEvent * event)
1261 {
1262         if (event->button() == Qt::LeftButton)
1263         {
1264                 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1265
1266                 // Handle tool processing, if any
1267                 if (Global::tool)
1268                 {
1269                         if (hoveringIntersection)
1270                                 point = intersectionPoint;
1271                         else if (Global::snapToGrid)
1272                                 point = SnapPointToGrid(point);
1273
1274                         //Also, may want to figure out if hovering over a snap point on an
1275                         //object, snap to grid if not.
1276                         // Snap to object point if valid...
1277 //                      if (Global::snapPointIsValid)
1278 //                              point = Global::snapPoint;
1279
1280                         ToolHandler(ToolMouseDown, point);
1281                         return;
1282                 }
1283
1284                 // Clear the selection only if CTRL isn't being held on click
1285                 if (!ctrlDown)
1286                         ClearSelected(document.objects);
1287 //                      ClearSelection();
1288
1289                 // If any objects are being hovered on click, add them to the selection
1290                 // & return
1291                 if (numHovered > 0)
1292                 {
1293                         AddHoveredToSelection();
1294                         update();       // needed??
1295                         GetHovered(hover);      // prolly needed
1296                         dragged = (Object *)hover[0];
1297                         draggingObject = true;
1298
1299                         // Alert the pen widget
1300                         emit(ObjectSelected(dragged));
1301
1302                         // See if anything is using just a straight click on a handle
1303                         if (HandleObjectClicked())
1304                         {
1305                                 draggingObject = false;
1306                                 update();
1307                                 return;
1308                         }
1309
1310                         // Needed for grab & moving objects
1311                         // We do it *after*... why? (doesn't seem to confer any advantage...)
1312                         if (hoveringIntersection)
1313                                 oldPoint = intersectionPoint;
1314                         else if (Global::snapToGrid)
1315                                 oldPoint = SnapPointToGrid(point);
1316
1317                         return;
1318                 }
1319
1320                 // Didn't hit any object and not using a tool, so do a selection rectangle
1321                 Global::selectionInProgress = true;
1322                 Global::selection.setTopLeft(QPointF(point.x, point.y));
1323                 Global::selection.setBottomRight(QPointF(point.x, point.y));
1324         }
1325         else if (event->button() == Qt::MiddleButton)
1326         {
1327                 scrollDrag = true;
1328                 oldPoint = Vector(event->x(), event->y());
1329                 // Should also change the mouse pointer as well...
1330                 setCursor(Qt::SizeAllCursor);
1331         }
1332 }
1333
1334
1335 void DrawingView::mouseMoveEvent(QMouseEvent * event)
1336 {
1337         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1338         Global::selection.setBottomRight(QPointF(point.x, point.y));
1339         // Only needs to be done here, as mouse down is always preceded by movement
1340         Global::snapPointIsValid = false;
1341         hoveringIntersection = false;
1342
1343         // Scrolling...
1344         if (event->buttons() & Qt::MiddleButton)
1345         {
1346                 point = Vector(event->x(), event->y());
1347                 // Since we're using Qt coords for scrolling, we have to adjust them
1348                 // here to conform to Cartesian coords, since the origin is using
1349                 // Cartesian. :-)
1350                 Vector delta(oldPoint, point);
1351                 delta /= Global::zoom;
1352                 delta.y = -delta.y;
1353                 Global::origin -= delta;
1354
1355                 UpdateGridBackground();
1356                 update();
1357                 oldPoint = point;
1358                 return;
1359         }
1360
1361         // If we're doing a selection rect, see if any objects are engulfed by it
1362         // (implies left mouse button held down)
1363         if (Global::selectionInProgress)
1364         {
1365                 CheckObjectBounds();
1366                 update();
1367                 return;
1368         }
1369
1370         // Do object hit testing...
1371         bool needUpdate = HitTestObjects(point);
1372         GetHovered(hover);
1373
1374         // Check for multi-hover...
1375         if (numHovered > 1)
1376         {
1377 //need to check for case where hover is over 2 circles and a 3rd's center...
1378                 Object * obj1 = (Object *)hover[0], * obj2 = (Object *)hover[1];
1379
1380                 Geometry::Intersects(obj1, obj2);
1381                 int numIntersecting = Global::numIntersectParams;
1382                 double t = Global::intersectParam[0];
1383                 double u = Global::intersectParam[1];
1384
1385                 if (numIntersecting > 0)
1386                 {
1387                         Vector v1 = Geometry::GetPointForParameter(obj1, t);
1388                         Vector v2 = Geometry::GetPointForParameter(obj2, u);
1389                         QString text = tr("Intersection t=%1 (%3, %4), u=%2 (%5, %6)");
1390                         informativeText = text.arg(t).arg(u).arg(v1.x).arg(v1.y).arg(v2.x).arg(v2.y);
1391
1392                         hoveringIntersection = true;
1393                         intersectionPoint = v1;
1394                 }
1395
1396                 numIntersecting = Global::numIntersectPoints;
1397
1398                 if (numIntersecting > 0)
1399                 {
1400                         Vector v1 = Global::intersectPoint[0];
1401
1402                         if (numIntersecting == 2)
1403                         {
1404                                 Vector v2 = Global::intersectPoint[1];
1405
1406                                 if (Vector::Magnitude(v2, point) < Vector::Magnitude(v1, point))
1407                                         v1 = v2;
1408                         }
1409
1410                         QString text = tr("Intersection <%1, %2>");
1411                         informativeText = text.arg(v1.x).arg(v1.y);
1412                         hoveringIntersection = true;
1413                         intersectionPoint = v1;
1414                 }
1415         }
1416
1417         // Handle object movement (left button down & over an object)
1418         if ((event->buttons() & Qt::LeftButton) && draggingObject && !Global::tool)
1419         {
1420                 if (hoveringIntersection)
1421                         point = intersectionPoint;
1422                 else if (hoverPointValid)
1423                         point = hoverPoint;
1424                 else if (Global::snapToGrid)
1425                         point = SnapPointToGrid(point);
1426
1427                 HandleObjectMovement(point);
1428                 update();
1429                 oldPoint = point;
1430                 return;
1431         }
1432
1433         // Do tool handling, if any are active...
1434         if (Global::tool)
1435         {
1436                 if (hoveringIntersection)
1437                         point = intersectionPoint;
1438                 else if (hoverPointValid)
1439                         point = hoverPoint;
1440                 else if (Global::snapToGrid)
1441                 {
1442                         if (angleSnap)
1443                                 point = SnapPointToAngle(point);
1444                         else
1445                                 point = SnapPointToGrid(point);
1446                 }
1447
1448                 ToolHandler(ToolMouseMove, point);
1449         }
1450
1451         // This is used to draw the tool crosshair...
1452         oldPoint = point;
1453
1454         if (needUpdate || Global::tool)
1455                 update();
1456 }
1457
1458
1459 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
1460 {
1461         if (event->button() == Qt::LeftButton)
1462         {
1463 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
1464 //could set it up to use the document's update function (assumes that all object
1465 //updates are being reported correctly:
1466 //              if (document.NeedsUpdate())
1467                 // Do an update if collided with at least *one* object in the document
1468 //              if (collided)
1469                         update();
1470
1471                 if (Global::tool)
1472                 {
1473                         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1474                         ToolHandler(ToolMouseUp, point);
1475                         return;
1476                 }
1477
1478                 if (Global::selectionInProgress)
1479                         Global::selectionInProgress = false;
1480
1481                 informativeText.clear();
1482 // Should we be doing this automagically? Hmm...
1483                 // Clear our vectors
1484                 select.clear();
1485                 hover.clear();
1486
1487                 // Scoop 'em up
1488                 std::vector<void *>::iterator i;
1489
1490                 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1491                 {
1492                         if (((Object *)(*i))->selected)
1493                                 select.push_back(*i);
1494                 }
1495
1496                 draggingObject = false;
1497         }
1498         else if (event->button() == Qt::MiddleButton)
1499         {
1500                 scrollDrag = false;
1501                 setCursor(Qt::ArrowCursor);
1502         }
1503 }
1504
1505
1506 void DrawingView::wheelEvent(QWheelEvent * event)
1507 {
1508         double zoomFactor = 1.25;
1509         QSize sizeWin = size();
1510         Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
1511         center = Painter::QtToCartesianCoords(center);
1512
1513         // This is not centering for some reason. Need to figure out why. :-/
1514         if (event->delta() > 0)
1515         {
1516                 Vector newOrigin = center - ((center - Global::origin) / zoomFactor);
1517                 Global::origin = newOrigin;
1518                 Global::zoom *= zoomFactor;
1519         }
1520         else
1521         {
1522                 Vector newOrigin = center + ((-center + Global::origin) * zoomFactor);
1523                 Global::origin = newOrigin;
1524                 Global::zoom /= zoomFactor;
1525         }
1526
1527 //      Global::gridSpacing = gridPixels / Painter::zoom;
1528 //      UpdateGridBackground();
1529         SetGridSize(Global::gridSpacing * Global::zoom);
1530         update();
1531 //      zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Global::gridSpacing));
1532 }
1533
1534
1535 void DrawingView::keyPressEvent(QKeyEvent * event)
1536 {
1537         bool oldShift = shiftDown;
1538         bool oldCtrl = ctrlDown;
1539
1540         if (event->key() == Qt::Key_Shift)
1541                 shiftDown = true;
1542         else if (event->key() == Qt::Key_Control)
1543                 ctrlDown = true;
1544
1545         if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1546         {
1547                 if (Global::tool)
1548                         ToolHandler(ToolKeyDown, Point(0, 0));
1549
1550                 update();
1551         }
1552
1553         if (select.size() > 0)
1554         {
1555                 if (event->key() == Qt::Key_Up)
1556                 {
1557                         TranslateObjects(select, Point(0, +1.0));
1558                         update();
1559                 }
1560                 else if (event->key() == Qt::Key_Down)
1561                 {
1562                         TranslateObjects(select, Point(0, -1.0));
1563                         update();
1564                 }
1565                 else if (event->key() == Qt::Key_Right)
1566                 {
1567                         TranslateObjects(select, Point(+1.0, 0));
1568                         update();
1569                 }
1570                 else if (event->key() == Qt::Key_Left)
1571                 {
1572                         TranslateObjects(select, Point(-1.0, 0));
1573                         update();
1574                 }
1575         }
1576 }
1577
1578
1579 void DrawingView::keyReleaseEvent(QKeyEvent * event)
1580 {
1581         bool oldShift = shiftDown;
1582         bool oldCtrl = ctrlDown;
1583
1584         if (event->key() == Qt::Key_Shift)
1585                 shiftDown = false;
1586         else if (event->key() == Qt::Key_Control)
1587                 ctrlDown = false;
1588
1589         if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1590         {
1591                 if (Global::tool)
1592                         ToolHandler(ToolKeyUp, Point(0, 0));
1593
1594                 update();
1595         }
1596 }
1597
1598
1599 //
1600 // This looks strange, but it's really quite simple: We want a point that's
1601 // more than half-way to the next grid point to snap there while conversely we
1602 // want a point that's less than half-way to to the next grid point then snap
1603 // to the one before it. So we add half of the grid spacing to the point, then
1604 // divide by it so that we can remove the fractional part, then multiply it
1605 // back to get back to the correct answer.
1606 //
1607 Point DrawingView::SnapPointToGrid(Point point)
1608 {
1609         point += Global::gridSpacing / 2.0;             // *This* adds to Z!!!
1610         point /= Global::gridSpacing;
1611         point.x = floor(point.x);//need to fix this for negative numbers...
1612         point.y = floor(point.y);
1613         point.z = 0;                                    // Make *sure* Z doesn't go anywhere!!!
1614         point *= Global::gridSpacing;
1615         return point;
1616 }
1617
1618
1619 Point DrawingView::SnapPointToAngle(Point point)
1620 {
1621         // Snap to a single digit angle (using toolpoint #1 as the center)
1622         double angle = Vector::Angle(toolPoint[0], point);
1623         double length = Vector::Magnitude(toolPoint[0], point);
1624
1625         // Convert from radians to degrees
1626         double degAngle = angle * RADIANS_TO_DEGREES;
1627         double snapAngle = (double)((int)(degAngle + 0.5));
1628
1629         Vector v;
1630         v.SetAngleAndLength(snapAngle * DEGREES_TO_RADIANS, length);
1631         point = toolPoint[0] + v;
1632
1633         return point;
1634 }
1635
1636
1637 Rect DrawingView::GetObjectExtents(Object * obj)
1638 {
1639         // Default to empty rect, if object checks below fail for some reason
1640         Rect rect;
1641
1642         switch (obj->type)
1643         {
1644         case OTLine:
1645         case OTDimension:
1646         {
1647                 rect = Rect(obj->p[0], obj->p[1]);
1648                 break;
1649         }
1650
1651         case OTCircle:
1652         {
1653                 rect = Rect(obj->p[0], obj->p[0]);
1654                 rect.Expand(obj->radius[0]);
1655                 break;
1656         }
1657
1658         case OTArc:
1659         {
1660                 Arc * a = (Arc *)obj;
1661
1662                 double start = a->angle[0];
1663                 double end = start + a->angle[1];
1664                 rect = Rect(Point(cos(start), sin(start)), Point(cos(end), sin(end)));
1665
1666                 // If the end of the arc is before the beginning, add 360 degrees to it
1667                 if (end < start)
1668                         end += TAU;
1669
1670                 // Adjust the bounds depending on which axes are crossed
1671                 if ((start < QTR_TAU) && (end > QTR_TAU))
1672                         rect.t = 1.0;
1673
1674                 if ((start < HALF_TAU) && (end > HALF_TAU))
1675                         rect.l = -1.0;
1676
1677                 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
1678                         rect.b = -1.0;
1679
1680                 if ((start < TAU) && (end > TAU))
1681                         rect.r = 1.0;
1682
1683                 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
1684                         rect.t = 1.0;
1685
1686                 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
1687                         rect.l = -1.0;
1688
1689                 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
1690                         rect.b = -1.0;
1691
1692                 rect *= a->radius[0];
1693                 rect.Translate(a->p[0]);
1694                 break;
1695         }
1696
1697         case OTText:
1698         {
1699                 Text * t = (Text *)obj;
1700                 rect = Rect(t->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
1701                 break;
1702         }
1703
1704         case OTContainer:
1705         {
1706                 Container * c = (Container *)obj;
1707                 std::vector<void *>::iterator i = c->objects.begin();
1708                 rect = GetObjectExtents((Object *)*i);
1709                 i++;
1710
1711                 for(; i!=c->objects.end(); i++)
1712                         rect |= GetObjectExtents((Object *)*i);
1713         }
1714
1715         default:
1716                 break;
1717         }
1718
1719         return rect;
1720 }
1721
1722
1723 void DrawingView::CheckObjectBounds(void)
1724 {
1725         std::vector<void *>::iterator i;
1726
1727         for(i=document.objects.begin(); i!=document.objects.end(); i++)
1728         {
1729                 Object * obj = (Object *)(*i);
1730                 obj->selected = false;
1731
1732                 switch (obj->type)
1733                 {
1734                 case OTLine:
1735                 case OTDimension:
1736                 {
1737                         Line * l = (Line *)obj;
1738
1739                         if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
1740                                 l->selected = true;
1741
1742                         break;
1743                 }
1744
1745                 case OTCircle:
1746                 {
1747                         Circle * c = (Circle *)obj;
1748
1749                         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]))
1750                                 c->selected = true;
1751
1752                         break;
1753                 }
1754
1755                 case OTArc:
1756                 {
1757                         Arc * a = (Arc *)obj;
1758
1759                         double start = a->angle[0];
1760                         double end = start + a->angle[1];
1761                         QPointF p1(cos(start), sin(start));
1762                         QPointF p2(cos(end), sin(end));
1763                         QRectF bounds(p1, p2);
1764
1765 #if 1
1766                         // Swap X/Y coordinates if they're backwards...
1767                         if (bounds.left() > bounds.right())
1768                         {
1769                                 double temp = bounds.left();
1770                                 bounds.setLeft(bounds.right());
1771                                 bounds.setRight(temp);
1772                         }
1773
1774                         if (bounds.bottom() > bounds.top())
1775                         {
1776                                 double temp = bounds.bottom();
1777                                 bounds.setBottom(bounds.top());
1778                                 bounds.setTop(temp);
1779                         }
1780 #else
1781                         // Doesn't work as advertised! For shame!
1782                         bounds = bounds.normalized();
1783 #endif
1784
1785                         // If the end of the arc is before the beginning, add 360 degrees to it
1786                         if (end < start)
1787                                 end += TAU;
1788
1789                         // Adjust the bounds depending on which axes are crossed
1790                         if ((start < QTR_TAU) && (end > QTR_TAU))
1791                                 bounds.setTop(1.0);
1792
1793                         if ((start < HALF_TAU) && (end > HALF_TAU))
1794                                 bounds.setLeft(-1.0);
1795
1796                         if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
1797                                 bounds.setBottom(-1.0);
1798
1799                         if ((start < TAU) && (end > TAU))
1800                                 bounds.setRight(1.0);
1801
1802                         if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
1803                                 bounds.setTop(1.0);
1804
1805                         if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
1806                                 bounds.setLeft(-1.0);
1807
1808                         if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
1809                                 bounds.setBottom(-1.0);
1810
1811                         bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
1812                         bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
1813                         bounds.translate(a->p[0].x, a->p[0].y);
1814
1815                         if (Global::selection.contains(bounds))
1816                                 a->selected = true;
1817
1818                         break;
1819                 }
1820
1821                 case OTText:
1822                 {
1823                         Text * t = (Text *)obj;
1824                         Rect r(obj->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
1825
1826                         if (Global::selection.contains(r.l, r.t) && Global::selection.contains(r.r, r.b))
1827                                 t->selected = true;
1828
1829                         break;
1830                 }
1831
1832                 default:
1833                         break;
1834                 }
1835         }
1836 }
1837
1838
1839 bool DrawingView::HitTestObjects(Point point)
1840 {
1841         std::vector<void *>::iterator i;
1842         numHovered = 0;
1843         bool needUpdate = false;
1844         hoverPointValid = false;
1845
1846         for(i=document.objects.begin(); i!=document.objects.end(); i++)
1847         {
1848                 Object * obj = (Object *)(*i);
1849
1850                 // If we're seeing the object we're dragging, skip it
1851                 if (draggingObject && (obj == dragged))
1852                         continue;
1853
1854                 if (HitTest(obj, point))
1855                         needUpdate = true;
1856
1857                 if (obj->hovered)
1858                 {
1859                         numHovered++;
1860 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
1861                         emit(ObjectHovered(obj));
1862                 }
1863         }
1864
1865         return needUpdate;
1866 }
1867
1868
1869 bool DrawingView::HitTest(Object * obj, Point point)
1870 {
1871         bool needUpdate = false;
1872
1873         switch (obj->type)
1874         {
1875         case OTLine:
1876         {
1877                 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
1878                 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
1879                 Vector lineSegment = obj->p[1] - obj->p[0];
1880                 Vector v1 = point - obj->p[0];
1881                 Vector v2 = point - obj->p[1];
1882                 double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
1883                 double distance;
1884
1885                 if (t < 0.0)
1886                         distance = v1.Magnitude();
1887                 else if (t > 1.0)
1888                         distance = v2.Magnitude();
1889                 else
1890                         // distance = ?Det?(ls, v1) / |ls|
1891                         distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
1892                                 / lineSegment.Magnitude());
1893
1894                 if ((v1.Magnitude() * Global::zoom) < 8.0)
1895                 {
1896                         obj->hitPoint[0] = true;
1897                         hoverPoint = obj->p[0];
1898                         hoverPointValid = true;
1899                 }
1900                 else if ((v2.Magnitude() * Global::zoom) < 8.0)
1901                 {
1902                         obj->hitPoint[1] = true;
1903                         hoverPoint = obj->p[1];
1904                         hoverPointValid = true;
1905                 }
1906                 else if ((distance * Global::zoom) < 5.0)
1907                         obj->hitObject = true;
1908
1909                 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
1910
1911                 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
1912                         needUpdate = true;
1913
1914                 break;
1915         }
1916
1917         case OTCircle:
1918         {
1919                 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
1920                 obj->hitPoint[0] = obj->hitObject = false;
1921                 double length = Vector::Magnitude(obj->p[0], point);
1922
1923                 if ((length * Global::zoom) < 8.0)
1924                 {
1925                         obj->hitPoint[0] = true;
1926                         hoverPoint = obj->p[0];
1927                         hoverPointValid = true;
1928                 }
1929                 else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
1930                         obj->hitObject = true;
1931
1932                 obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
1933
1934                 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
1935                         needUpdate = true;
1936
1937                 break;
1938         }
1939
1940         case OTArc:
1941         {
1942                 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
1943                 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
1944                 double length = Vector::Magnitude(obj->p[0], point);
1945                 double angle = Vector::Angle(obj->p[0], point);
1946
1947                 // Make sure we get the angle in the correct spot
1948                 if (angle < obj->angle[0])
1949                         angle += TAU;
1950
1951                 // Get the span that we're pointing at...
1952                 double span = angle - obj->angle[0];
1953
1954                 // N.B.: Still need to hit test the arc start & arc span handles...
1955                 double spanAngle = obj->angle[0] + obj->angle[1];
1956                 Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
1957                 Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
1958                 double length2 = Vector::Magnitude(point, handle1);
1959                 double length3 = Vector::Magnitude(point, handle2);
1960
1961                 if ((length * Global::zoom) < 8.0)
1962                 {
1963                         obj->hitPoint[0] = true;
1964                         hoverPoint = obj->p[0];
1965                         hoverPointValid = true;
1966                 }
1967                 else if ((length2 * Global::zoom) < 8.0)
1968                 {
1969                         obj->hitPoint[1] = true;
1970                         hoverPoint = handle1;
1971                         hoverPointValid = true;
1972                 }
1973                 else if ((length3 * Global::zoom) < 8.0)
1974                 {
1975                         obj->hitPoint[2] = true;
1976                         hoverPoint = handle2;
1977                         hoverPointValid = true;
1978                 }
1979                 else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
1980                         obj->hitObject = true;
1981
1982                 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
1983
1984                 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
1985                         needUpdate = true;
1986
1987                 break;
1988         }
1989
1990         case OTDimension:
1991         {
1992                 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHP3 = obj->hitPoint[3], oldHP4 = obj->hitPoint[4], oldHO = obj->hitObject;
1993                 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitPoint[3] = obj->hitPoint[4] = obj->hitObject = false;
1994
1995                 Dimension * d = (Dimension *)obj;
1996
1997                 Vector orthogonal = Vector::Normal(d->lp[0], d->lp[1]);
1998                 // Get our line parallel to our points
1999                 float scaledThickness = Global::scale * obj->thickness;
2000                 Point p1 = d->lp[0] + (orthogonal * 10.0 * scaledThickness);
2001                 Point p2 = d->lp[1] + (orthogonal * 10.0 * scaledThickness);
2002                 Point p3(p1, point);
2003
2004                 Vector v1(d->p[0], point);
2005                 Vector v2(d->p[1], point);
2006                 Vector lineSegment(p1, p2);
2007                 double t = Geometry::ParameterOfLineAndPoint(p1, p2, point);
2008                 double distance;
2009                 Point midpoint = (p1 + p2) / 2.0;
2010                 Point hFSPoint = Point(midpoint, point);
2011                 Point hCS1Point = Point((p1 + midpoint) / 2.0, point);
2012                 Point hCS2Point = Point((midpoint + p2) / 2.0, point);
2013
2014                 if (t < 0.0)
2015                         distance = v1.Magnitude();
2016                 else if (t > 1.0)
2017                         distance = v2.Magnitude();
2018                 else
2019                         // distance = ?Det?(ls, v1) / |ls|
2020                         distance = fabs((lineSegment.x * p3.y - p3.x * lineSegment.y)
2021                                 / lineSegment.Magnitude());
2022
2023                 if ((v1.Magnitude() * Global::zoom) < 8.0)
2024                         obj->hitPoint[0] = true;
2025                 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2026                         obj->hitPoint[1] = true;
2027                 else if ((distance * Global::zoom) < 5.0)
2028                         obj->hitObject = true;
2029
2030                 if ((hFSPoint.Magnitude() * Global::zoom) < 8.0)
2031                         obj->hitPoint[2] = true;
2032                 else if ((hCS1Point.Magnitude() * Global::zoom) < 8.0)
2033                         obj->hitPoint[3] = true;
2034                 else if ((hCS2Point.Magnitude() * Global::zoom) < 8.0)
2035                         obj->hitPoint[4] = true;
2036
2037 //              return (hitPoint1 || hitPoint2 || hitLine || hitFlipSwitch || hitChangeSwitch1 || hitChangeSwitch2 ? true : false);
2038                 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitPoint[3] || obj->hitPoint[4] || obj->hitObject ? true : false);
2039
2040                 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHP3 != obj->hitPoint[3]) || (oldHP4 != obj->hitPoint[4]) || (oldHO != obj->hitObject))
2041                         needUpdate = true;
2042
2043                 break;
2044         }
2045
2046         case OTText:
2047         {
2048                 Text * t = (Text *)obj;
2049                 bool oldHO = obj->hitObject;
2050                 obj->hitObject = false;
2051
2052                 Rect r(obj->p[0], Point(obj->p[0].x + t->extents.Width(), obj->p[0].y - t->extents.Height()));
2053 //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);
2054
2055                 if (r.Contains(point))
2056                         obj->hitObject = true;
2057
2058                 obj->hovered = (obj->hitObject ? true : false);
2059
2060                 if (oldHO != obj->hitObject)
2061                         needUpdate = true;
2062
2063                 break;
2064         }
2065
2066         case OTContainer:
2067         {
2068                 // Containers must be recursively tested...
2069                 Container * c = (Container *)obj;
2070                 c->hitObject = false;
2071                 c->hovered = false;
2072                 std::vector<void *>::iterator i;
2073
2074                 for(i=c->objects.begin(); i!=c->objects.end(); i++)
2075                 {
2076                         Object * cObj = (Object *)*i;
2077
2078                         if (HitTest(cObj, point))
2079                                 needUpdate = true;
2080
2081                         if (cObj->hitObject == true)
2082                                 c->hitObject = true;
2083
2084                         if (cObj->hovered == true)
2085                                 c->hovered = true;
2086                 }
2087
2088                 break;
2089         }
2090
2091         default:
2092                 break;
2093         }
2094
2095         return needUpdate;
2096 }
2097
2098
2099 bool DrawingView::HandleObjectClicked(void)
2100 {
2101         if (dragged->type == OTDimension)
2102         {
2103                 Dimension * d = (Dimension *)dragged;
2104
2105                 if (d->hitPoint[2])
2106                 {
2107                         // Hit the "flip sides" switch, so flip 'em
2108                         Point temp = d->p[0];
2109                         d->p[0] = d->p[1];
2110                         d->p[1] = temp;
2111                         return true;
2112                 }
2113                 else if (d->hitPoint[3])
2114                 {
2115                         // There are three cases here: aligned, horizontal, & vertical.
2116                         // Aligned and horizontal do the same thing, vertical goes back to
2117                         // linear.
2118                         if (d->subtype == DTLinearVert)
2119                                 d->subtype = DTLinear;
2120                         else
2121                                 d->subtype = DTLinearVert;
2122
2123                         return true;
2124                 }
2125                 else if (d->hitPoint[4])
2126                 {
2127                         // There are three cases here: aligned, horizontal, & vertical.
2128                         // Aligned and vertical do the same thing, horizontal goes back to
2129                         // linear.
2130                         if (d->subtype == DTLinearHorz)
2131                                 d->subtype = DTLinear;
2132                         else
2133                                 d->subtype = DTLinearHorz;
2134
2135                         return true;
2136                 }
2137         }
2138
2139         return false;
2140 }
2141
2142
2143 void DrawingView::HandleObjectMovement(Point point)
2144 {
2145         Point delta = point - oldPoint;
2146 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
2147 //      Object * obj = (Object *)hover[0];
2148         Object * obj = dragged;
2149 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
2150 //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"));
2151
2152         switch (obj->type)
2153         {
2154         case OTLine:
2155                 if (obj->hitPoint[0])
2156                         obj->p[0] = point;
2157                 else if (obj->hitPoint[1])
2158                         obj->p[1] = point;
2159                 else if (obj->hitObject)
2160                 {
2161                         obj->p[0] += delta;
2162                         obj->p[1] += delta;
2163                 }
2164
2165                 break;
2166
2167         case OTCircle:
2168                 if (obj->hitPoint[0])
2169                         obj->p[0] = point;
2170                 else if (obj->hitObject)
2171                 {
2172 //this doesn't work. we need to save this on mouse down for this to work correctly!
2173 //                      double oldRadius = obj->radius[0];
2174                         obj->radius[0] = Vector::Magnitude(obj->p[0], point);
2175
2176                         QString text = QObject::tr("Radius: %1");//\nScale: %2%");
2177                         informativeText = text.arg(obj->radius[0], 0, 'd', 4);//.arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0);
2178                 }
2179
2180                 break;
2181
2182         case OTArc:
2183                 if (obj->hitPoint[0])
2184                         obj->p[0] = point;
2185                 else if (obj->hitPoint[1])
2186                 {
2187                         // Change the Arc's span (handle #1)
2188                         if (shiftDown)
2189                         {
2190                                 double angle = Vector::Angle(obj->p[0], point);
2191                                 double delta = angle - obj->angle[0];
2192
2193                                 if (delta < 0)
2194                                         delta += TAU;
2195
2196                                 obj->angle[1] -= delta;
2197                                 obj->angle[0] = angle;
2198
2199                                 if (obj->angle[1] < 0)
2200                                         obj->angle[1] += TAU;
2201
2202                                 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
2203                                 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);
2204                                 return;
2205                         }
2206
2207                         double angle = Vector::Angle(obj->p[0], point);
2208                         obj->angle[0] = angle;
2209                         QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
2210                         informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 4);
2211                 }
2212                 else if (obj->hitPoint[2])
2213                 {
2214                         // Change the Arc's span (handle #2)
2215                         if (shiftDown)
2216                         {
2217                                 double angle = Vector::Angle(obj->p[0], point);
2218                                 obj->angle[1] = angle - obj->angle[0];
2219
2220                                 if (obj->angle[1] < 0)
2221                                         obj->angle[1] += TAU;
2222
2223                                 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
2224                                 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);
2225                                 return;
2226                         }
2227
2228                         double angle = Vector::Angle(obj->p[0], point);
2229                         obj->angle[0] = angle - obj->angle[1];
2230
2231                         if (obj->angle[0] < 0)
2232                                 obj->angle[0] += TAU;
2233
2234                         QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
2235                         informativeText = text.arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 4);
2236                 }
2237                 else if (obj->hitObject)
2238                 {
2239                         if (shiftDown)
2240                         {
2241                                 return;
2242                         }
2243
2244                         obj->radius[0] = Vector::Magnitude(obj->p[0], point);
2245                         QString text = QObject::tr("Radius: %1");
2246                         informativeText = text.arg(obj->radius[0], 0, 'd', 4);
2247                 }
2248
2249                 break;
2250
2251         case OTDimension:
2252                 if (obj->hitPoint[0])
2253                         obj->p[0] = point;
2254                 else if (obj->hitPoint[1])
2255                         obj->p[1] = point;
2256                 else if (obj->hitObject)
2257                 {
2258                         obj->p[0] += delta;
2259                         obj->p[1] += delta;
2260                 }
2261
2262                 break;
2263
2264         case OTText:
2265                 if (obj->hitObject)
2266                         obj->p[0] += delta;
2267
2268                 break;
2269
2270         case OTContainer:
2271                 // This is shitty, but works for now until I can code up something
2272                 // nicer :-)
2273                 TranslateObject(obj, delta);
2274
2275                 break;
2276         default:
2277                 break;
2278         }
2279 }
2280