]> Shamusworld >> Repos - architektonas/blob - src/drawingview.cpp
Added miscellaneous features.
[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), altDown(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         else if (Global::tool == TTDimension)
751                 DimensionHandler(mode, p);
752 }
753
754
755 void DrawingView::ToolDraw(Painter * painter)
756 {
757         if (Global::tool == TTLine)
758         {
759                 if (Global::toolState == TSNone)
760                 {
761                         painter->DrawHandle(toolPoint[0]);
762                 }
763                 else if ((Global::toolState == TSPoint2) && shiftDown)
764                 {
765                         painter->DrawHandle(toolPoint[1]);
766                 }
767                 else
768                 {
769                         painter->DrawLine(toolPoint[0], toolPoint[1]);
770                         painter->DrawHandle(toolPoint[1]);
771
772                         Vector v(toolPoint[0], toolPoint[1]);
773                         double absAngle = v.Angle() * RADIANS_TO_DEGREES;
774                         double absLength = v.Magnitude();
775                         QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
776                         informativeText = text.arg(absLength).arg(absAngle);
777                 }
778         }
779         else if (Global::tool == TTCircle)
780         {
781                 if (Global::toolState == TSNone)
782                 {
783                         painter->DrawHandle(toolPoint[0]);
784                 }
785                 else if ((Global::toolState == TSPoint2) && shiftDown)
786                 {
787                         painter->DrawHandle(toolPoint[1]);
788                 }
789                 else
790                 {
791                         painter->DrawCross(toolPoint[0]);
792                         double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
793                         painter->SetBrush(QBrush(Qt::NoBrush));
794                         painter->DrawEllipse(toolPoint[0], length, length);
795                         QString text = tr("Radius: %1 in.");
796                         informativeText = text.arg(length);
797                 }
798         }
799         else if (Global::tool == TTArc)
800         {
801                 if (Global::toolState == TSNone)
802                 {
803                         painter->DrawHandle(toolPoint[0]);
804                 }
805                 else if (Global::toolState == TSPoint2)
806                 {
807                         double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
808                         painter->SetBrush(QBrush(Qt::NoBrush));
809                         painter->DrawEllipse(toolPoint[0], length, length);
810                         painter->DrawLine(toolPoint[0], toolPoint[1]);
811                         painter->DrawHandle(toolPoint[1]);
812                         QString text = tr("Radius: %1 in.");
813                         informativeText = text.arg(length);
814                 }
815                 else if (Global::toolState == TSPoint3)
816                 {
817                         double angle = Vector::Angle(toolPoint[0], toolPoint[2]);
818                         painter->DrawLine(toolPoint[0], toolPoint[2]);
819                         painter->SetBrush(QBrush(Qt::NoBrush));
820                         painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
821                         painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
822                         QString text = tr("Angle start: %1") + QChar(0x00B0);
823                         informativeText = text.arg(RADIANS_TO_DEGREES * angle);
824                 }
825                 else
826                 {
827                         double angle = Vector::Angle(toolPoint[0], toolPoint[3]);
828                         double span = angle - toolPoint[2].x;
829
830                         if (span < 0)
831                                 span += TAU;
832
833                         painter->DrawLine(toolPoint[0], toolPoint[3]);
834                         painter->SetBrush(QBrush(Qt::NoBrush));
835                         painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
836                         painter->SetPen(0xFF00FF, 2.0, LSSolid);
837                         painter->DrawArc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
838                         painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
839                         QString text = tr("Arc span: %1") + QChar(0x00B0);
840                         informativeText = text.arg(RADIANS_TO_DEGREES * span);
841                 }
842         }
843         else if (Global::tool == TTRotate)
844         {
845                 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
846                         painter->DrawHandle(toolPoint[0]);
847                 else if ((Global::toolState == TSPoint2) && shiftDown)
848                         painter->DrawHandle(toolPoint[1]);
849                 else
850                 {
851                         if (toolPoint[0] == toolPoint[1])
852                                 return;
853
854                         painter->DrawLine(toolPoint[0], toolPoint[1]);
855
856                         double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
857                         QString text = QChar(0x2221) + QObject::tr(": %1");
858                         informativeText = text.arg(absAngle);
859
860                         if (ctrlDown)
861                                 informativeText += " (Copy)";
862                 }
863         }
864         else if (Global::tool == TTMirror)
865         {
866                 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
867                         painter->DrawHandle(toolPoint[0]);
868                 else if ((Global::toolState == TSPoint2) && shiftDown)
869                         painter->DrawHandle(toolPoint[1]);
870                 else
871                 {
872                         if (toolPoint[0] == toolPoint[1])
873                                 return;
874
875                         Point mirrorPoint = toolPoint[0] + Vector(toolPoint[1], toolPoint[0]);
876                         painter->DrawLine(mirrorPoint, toolPoint[1]);
877
878                         double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
879
880                         if (absAngle > 180.0)
881                                 absAngle -= 180.0;
882
883                         QString text = QChar(0x2221) + QObject::tr(": %1");
884                         informativeText = text.arg(absAngle);
885
886                         if (ctrlDown)
887                                 informativeText += " (Copy)";
888                 }
889         }
890         if (Global::tool == TTDimension)
891         {
892                 if (Global::toolState == TSNone)
893                 {
894                         painter->DrawHandle(toolPoint[0]);
895                 }
896                 else if ((Global::toolState == TSPoint2) && shiftDown)
897                 {
898                         painter->DrawHandle(toolPoint[1]);
899                 }
900                 else
901                 {
902                         painter->DrawLine(toolPoint[0], toolPoint[1]);
903                         painter->DrawHandle(toolPoint[1]);
904
905                         Vector v(toolPoint[0], toolPoint[1]);
906                         double absAngle = v.Angle() * RADIANS_TO_DEGREES;
907                         double absLength = v.Magnitude();
908                         QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
909                         informativeText = text.arg(absLength).arg(absAngle);
910                 }
911         }
912 }
913
914
915 void DrawingView::LineHandler(int mode, Point p)
916 {
917         switch (mode)
918         {
919         case ToolMouseDown:
920                 if (Global::toolState == TSNone)
921                         toolPoint[0] = p;
922                 else
923                         toolPoint[1] = p;
924
925                 break;
926         case ToolMouseMove:
927                 if (Global::toolState == TSNone)
928                         toolPoint[0] = p;
929                 else
930                         toolPoint[1] = p;
931
932                 break;
933         case ToolMouseUp:
934                 if (Global::toolState == TSNone)
935                 {
936                         Global::toolState = TSPoint2;
937                         // Prevent spurious line from drawing...
938                         toolPoint[1] = toolPoint[0];
939                 }
940                 else if ((Global::toolState == TSPoint2) && shiftDown)
941                 {
942                         // Key override is telling us to make a new line, not continue the
943                         // previous one.
944                         toolPoint[0] = toolPoint[1];
945                 }
946                 else
947                 {
948                         Line * l = new Line(toolPoint[0], toolPoint[1], Global::penWidth, Global::penColor, Global::penStyle);
949                         l->layer = Global::activeLayer;
950                         document.objects.push_back(l);
951                         toolPoint[0] = toolPoint[1];
952                 }
953         }
954 }
955
956
957 void DrawingView::CircleHandler(int mode, Point p)
958 {
959         switch (mode)
960         {
961         case ToolMouseDown:
962                 if (Global::toolState == TSNone)
963                         toolPoint[0] = p;
964                 else
965                         toolPoint[1] = p;
966
967                 break;
968         case ToolMouseMove:
969                 if (Global::toolState == TSNone)
970                         toolPoint[0] = p;
971                 else
972                         toolPoint[1] = p;
973
974                 break;
975         case ToolMouseUp:
976                 if (Global::toolState == TSNone)
977                 {
978                         Global::toolState = TSPoint2;
979                         // Prevent spurious line from drawing...
980                         toolPoint[1] = toolPoint[0];
981                 }
982                 else if ((Global::toolState == TSPoint2) && shiftDown)
983                 {
984                         // Key override is telling us to make a new line, not continue the
985                         // previous one.
986                         toolPoint[0] = toolPoint[1];
987                 }
988                 else
989                 {
990                         double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
991                         Circle * c = new Circle(toolPoint[0], length, Global::penWidth, Global::penColor, Global::penStyle);
992                         c->layer = Global::activeLayer;
993                         document.objects.push_back(c);
994                         toolPoint[0] = toolPoint[1];
995                         Global::toolState = TSNone;
996                 }
997         }
998 }
999
1000
1001 void DrawingView::ArcHandler(int mode, Point p)
1002 {
1003         switch (mode)
1004         {
1005         case ToolMouseDown:
1006                 if (Global::toolState == TSNone)
1007                         toolPoint[0] = p;
1008                 else if (Global::toolState == TSPoint2)
1009                         toolPoint[1] = p;
1010                 else if (Global::toolState == TSPoint3)
1011                         toolPoint[2] = p;
1012                 else
1013                         toolPoint[3] = p;
1014
1015                 break;
1016         case ToolMouseMove:
1017                 if (Global::toolState == TSNone)
1018                         toolPoint[0] = p;
1019                 else if (Global::toolState == TSPoint2)
1020                         toolPoint[1] = p;
1021                 else if (Global::toolState == TSPoint3)
1022                 {
1023                         toolPoint[2] = p;
1024                         angleSnap = true;
1025                 }
1026                 else
1027                 {
1028                         toolPoint[3] = p;
1029                         angleSnap = true;
1030                 }
1031
1032                 break;
1033         case ToolMouseUp:
1034                 if (Global::toolState == TSNone)
1035                 {
1036                         // Prevent spurious line from drawing...
1037                         toolPoint[1] = toolPoint[0];
1038                         Global::toolState = TSPoint2;
1039                 }
1040                 else if (Global::toolState == TSPoint2)
1041                 {
1042                         if (shiftDown)
1043                         {
1044                                 // Key override is telling us to start arc at new center, not
1045                                 // continue the current one.
1046                                 toolPoint[0] = toolPoint[1];
1047                                 return;
1048                         }
1049
1050                         // Set the radius in toolPoint[1].x
1051                         toolPoint[1].x = Vector::Magnitude(toolPoint[0], toolPoint[1]);
1052                         Global::toolState = TSPoint3;
1053                 }
1054                 else if (Global::toolState == TSPoint3)
1055                 {
1056                         // Set the angle in toolPoint[2].x
1057                         toolPoint[2].x = Vector::Angle(toolPoint[0], toolPoint[2]);
1058                         Global::toolState = TSPoint4;
1059                 }
1060                 else
1061                 {
1062                         double endAngle = Vector::Angle(toolPoint[0], toolPoint[3]);
1063                         double span = endAngle - toolPoint[2].x;
1064
1065                         if (span < 0)
1066                                 span += TAU;
1067
1068                         Arc * arc = new Arc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span, Global::penWidth, Global::penColor, Global::penStyle);
1069                         arc->layer = Global::activeLayer;
1070                         document.objects.push_back(arc);
1071                         Global::toolState = TSNone;
1072                 }
1073         }
1074 }
1075
1076
1077 void DrawingView::RotateHandler(int mode, Point p)
1078 {
1079         switch (mode)
1080         {
1081         case ToolMouseDown:
1082                 if (Global::toolState == TSNone)
1083                 {
1084                         toolPoint[0] = p;
1085                         SavePointsFrom(select, toolScratch);
1086                         Global::toolState = TSPoint1;
1087                 }
1088                 else if (Global::toolState == TSPoint1)
1089                         toolPoint[0] = p;
1090                 else
1091                         toolPoint[1] = p;
1092
1093                 break;
1094         case ToolMouseMove:
1095                 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1096                         toolPoint[0] = p;
1097                 else if (Global::toolState == TSPoint2)
1098                 {
1099                         toolPoint[1] = p;
1100
1101                         if (shiftDown)
1102                                 return;
1103
1104                         angleSnap = true;
1105                         double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1106                         std::vector<void *>::iterator j = select.begin();
1107                         std::vector<Object>::iterator i = toolScratch.begin();
1108
1109                         for(; i!=toolScratch.end(); i++, j++)
1110                         {
1111                                 Object obj = *i;
1112                                 Point p1 = Geometry::RotatePointAroundPoint(obj.p[0], toolPoint[0], angle);
1113                                 Point p2 = Geometry::RotatePointAroundPoint(obj.p[1], toolPoint[0], angle);
1114                                 Object * obj2 = (Object *)(*j);
1115                                 obj2->p[0] = p1;
1116                                 obj2->p[1] = p2;
1117
1118                                 if (obj.type == OTArc)
1119                                 {
1120                                         obj2->angle[0] = obj.angle[0] + angle;
1121
1122                                         if (obj2->angle[0] > TAU)
1123                                                 obj2->angle[0] -= TAU;
1124                                 }
1125                         }
1126                 }
1127
1128                 break;
1129         case ToolMouseUp:
1130                 if (Global::toolState == TSPoint1)
1131                 {
1132                         Global::toolState = TSPoint2;
1133                         // Prevent spurious line from drawing...
1134                         toolPoint[1] = toolPoint[0];
1135                 }
1136                 else if ((Global::toolState == TSPoint2) && shiftDown)
1137                 {
1138                         // Key override is telling us to make a new line, not continue the
1139                         // previous one.
1140                         toolPoint[0] = toolPoint[1];
1141                 }
1142                 else
1143                 {
1144                         // Either we're finished with our rotate, or we're stamping a copy.
1145                         if (ctrlDown)
1146                         {
1147                                 // Stamp a copy of the selection at the current rotation & bail
1148                                 std::vector<void *> temp;
1149                                 CopyObjects(select, temp);
1150                                 ClearSelected(temp);
1151                                 AddObjectsTo(document.objects, temp);
1152                                 RestorePointsTo(select, toolScratch);
1153                                 return;
1154                         }
1155
1156                         toolPoint[0] = p;
1157                         Global::toolState = TSPoint1;
1158                         SavePointsFrom(select, toolScratch);
1159                 }
1160
1161                 break;
1162         case ToolKeyDown:
1163                 // Reset the selection if shift held down...
1164                 if (shiftDown)
1165                         RestorePointsTo(select, toolScratch);
1166
1167                 break;
1168         case ToolKeyUp:
1169                 // Reset selection when key is let up
1170                 if (!shiftDown)
1171                         RotateHandler(ToolMouseMove, toolPoint[1]);
1172
1173                 break;
1174         case ToolCleanup:
1175                 RestorePointsTo(select, toolScratch);
1176         }
1177 }
1178
1179
1180 void DrawingView::MirrorHandler(int mode, Point p)
1181 {
1182         switch (mode)
1183         {
1184         case ToolMouseDown:
1185                 if (Global::toolState == TSNone)
1186                 {
1187                         toolPoint[0] = p;
1188                         SavePointsFrom(select, toolScratch);
1189                         Global::toolState = TSPoint1;
1190                 }
1191                 else if (Global::toolState == TSPoint1)
1192                         toolPoint[0] = p;
1193                 else
1194                         toolPoint[1] = p;
1195
1196                 break;
1197         case ToolMouseMove:
1198                 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1199                         toolPoint[0] = p;
1200                 else if (Global::toolState == TSPoint2)
1201                 {
1202                         toolPoint[1] = p;
1203
1204                         if (shiftDown)
1205                                 return;
1206
1207                         angleSnap = true;
1208                         double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1209                         std::vector<void *>::iterator j = select.begin();
1210                         std::vector<Object>::iterator i = toolScratch.begin();
1211
1212                         for(; i!=toolScratch.end(); i++, j++)
1213                         {
1214                                 Object obj = *i;
1215                                 Point p1 = Geometry::MirrorPointAroundLine(obj.p[0], toolPoint[0], toolPoint[1]);
1216                                 Point p2 = Geometry::MirrorPointAroundLine(obj.p[1], toolPoint[0], toolPoint[1]);
1217                                 Object * obj2 = (Object *)(*j);
1218                                 obj2->p[0] = p1;
1219                                 obj2->p[1] = p2;
1220
1221                                 if (obj.type == OTArc)
1222                                 {
1223                                         // This is 2*mirror angle - obj angle - obj span
1224                                         obj2->angle[0] = (2.0 * angle) - obj.angle[0] - obj.angle[1];
1225
1226                                         if (obj2->angle[0] > TAU)
1227                                                 obj2->angle[0] -= TAU;
1228                                 }
1229                         }
1230                 }
1231
1232                 break;
1233         case ToolMouseUp:
1234                 if (Global::toolState == TSPoint1)
1235                 {
1236                         Global::toolState = TSPoint2;
1237                         // Prevent spurious line from drawing...
1238                         toolPoint[1] = toolPoint[0];
1239                 }
1240                 else if ((Global::toolState == TSPoint2) && shiftDown)
1241                 {
1242                         // Key override is telling us to make a new line, not continue the
1243                         // previous one.
1244                         toolPoint[0] = toolPoint[1];
1245                 }
1246                 else
1247                 {
1248                         // Either we're finished with our rotate, or we're stamping a copy.
1249                         if (ctrlDown)
1250                         {
1251                                 // Stamp a copy of the selection at the current rotation & bail
1252                                 std::vector<void *> temp;
1253                                 CopyObjects(select, temp);
1254                                 ClearSelected(temp);
1255                                 AddObjectsTo(document.objects, temp);
1256                                 RestorePointsTo(select, toolScratch);
1257                                 return;
1258                         }
1259
1260                         toolPoint[0] = p;
1261                         Global::toolState = TSPoint1;
1262                         SavePointsFrom(select, toolScratch);
1263                 }
1264
1265                 break;
1266         case ToolKeyDown:
1267                 // Reset the selection if shift held down...
1268                 if (shiftDown)
1269                         RestorePointsTo(select, toolScratch);
1270
1271                 break;
1272         case ToolKeyUp:
1273                 // Reset selection when key is let up
1274                 if (!shiftDown)
1275                         MirrorHandler(ToolMouseMove, toolPoint[1]);
1276
1277                 break;
1278         case ToolCleanup:
1279                 RestorePointsTo(select, toolScratch);
1280         }
1281 }
1282
1283
1284 void DrawingView::DimensionHandler(int mode, Point p)
1285 {
1286         switch (mode)
1287         {
1288         case ToolMouseDown:
1289                 if (Global::toolState == TSNone)
1290                         toolPoint[0] = p;
1291                 else
1292                         toolPoint[1] = p;
1293
1294                 break;
1295         case ToolMouseMove:
1296                 if (Global::toolState == TSNone)
1297                         toolPoint[0] = p;
1298                 else
1299                         toolPoint[1] = p;
1300
1301                 break;
1302         case ToolMouseUp:
1303                 if (Global::toolState == TSNone)
1304                 {
1305                         Global::toolState = TSPoint2;
1306                         // Prevent spurious line from drawing...
1307                         toolPoint[1] = toolPoint[0];
1308                 }
1309                 else if ((Global::toolState == TSPoint2) && shiftDown)
1310                 {
1311                         // Key override is telling us to make a new line, not continue the
1312                         // previous one.
1313                         toolPoint[0] = toolPoint[1];
1314                 }
1315                 else
1316                 {
1317                         Dimension * d = new Dimension(toolPoint[0], toolPoint[1], DTLinear);
1318                         d->layer = Global::activeLayer;
1319                         document.objects.push_back(d);
1320                         Global::toolState = TSNone;
1321                 }
1322         }
1323 }
1324
1325
1326 void DrawingView::mousePressEvent(QMouseEvent * event)
1327 {
1328         if (event->button() == Qt::LeftButton)
1329         {
1330                 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1331
1332                 // Handle tool processing, if any
1333                 if (Global::tool)
1334                 {
1335                         if (hoveringIntersection)
1336                                 point = intersectionPoint;
1337                         else if (Global::snapToGrid)
1338                                 point = SnapPointToGrid(point);
1339
1340                         //Also, may want to figure out if hovering over a snap point on an
1341                         //object, snap to grid if not.
1342                         // Snap to object point if valid...
1343 //                      if (Global::snapPointIsValid)
1344 //                              point = Global::snapPoint;
1345
1346                         ToolHandler(ToolMouseDown, point);
1347                         return;
1348                 }
1349
1350                 // Clear the selection only if CTRL isn't being held on click
1351                 if (!ctrlDown)
1352                         ClearSelected(document.objects);
1353 //                      ClearSelection();
1354
1355                 // If any objects are being hovered on click, add them to the selection
1356                 // & return
1357                 if (numHovered > 0)
1358                 {
1359                         AddHoveredToSelection();
1360                         update();       // needed??
1361                         GetHovered(hover);      // prolly needed
1362                         dragged = (Object *)hover[0];
1363                         draggingObject = true;
1364
1365                         // Alert the pen widget
1366                         emit(ObjectSelected(dragged));
1367
1368                         // See if anything is using just a straight click on a handle
1369                         if (HandleObjectClicked())
1370                         {
1371                                 draggingObject = false;
1372                                 update();
1373                                 return;
1374                         }
1375
1376                         // Needed for grab & moving objects
1377                         // We do it *after*... why? (doesn't seem to confer any advantage...)
1378                         if (hoveringIntersection)
1379                                 oldPoint = intersectionPoint;
1380                         else if (Global::snapToGrid)
1381                                 oldPoint = SnapPointToGrid(point);
1382
1383                         return;
1384                 }
1385
1386                 // Didn't hit any object and not using a tool, so do a selection rectangle
1387                 Global::selectionInProgress = true;
1388                 Global::selection.setTopLeft(QPointF(point.x, point.y));
1389                 Global::selection.setBottomRight(QPointF(point.x, point.y));
1390         }
1391         else if (event->button() == Qt::MiddleButton)
1392         {
1393                 scrollDrag = true;
1394                 oldPoint = Vector(event->x(), event->y());
1395                 // Should also change the mouse pointer as well...
1396                 setCursor(Qt::SizeAllCursor);
1397         }
1398 }
1399
1400
1401 void DrawingView::mouseMoveEvent(QMouseEvent * event)
1402 {
1403         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1404         Global::selection.setBottomRight(QPointF(point.x, point.y));
1405         // Only needs to be done here, as mouse down is always preceded by movement
1406         Global::snapPointIsValid = false;
1407         hoveringIntersection = false;
1408         oldScrollPoint = Vector(event->x(), event->y());
1409
1410         // Scrolling...
1411         if ((event->buttons() & Qt::MiddleButton) || scrollDrag)
1412         {
1413                 point = Vector(event->x(), event->y());
1414                 // Since we're using Qt coords for scrolling, we have to adjust them
1415                 // here to conform to Cartesian coords, since the origin is using
1416                 // Cartesian. :-)
1417                 Vector delta(oldPoint, point);
1418                 delta /= Global::zoom;
1419                 delta.y = -delta.y;
1420                 Global::origin -= delta;
1421
1422                 UpdateGridBackground();
1423                 update();
1424                 oldPoint = point;
1425                 return;
1426         }
1427
1428         // If we're doing a selection rect, see if any objects are engulfed by it
1429         // (implies left mouse button held down)
1430         if (Global::selectionInProgress)
1431         {
1432                 CheckObjectBounds();
1433                 update();
1434                 return;
1435         }
1436
1437         // Do object hit testing...
1438         bool needUpdate = HitTestObjects(point);
1439         GetHovered(hover);
1440
1441         // Check for multi-hover...
1442         if (numHovered > 1)
1443         {
1444 //need to check for case where hover is over 2 circles and a 3rd's center...
1445                 Object * obj1 = (Object *)hover[0], * obj2 = (Object *)hover[1];
1446
1447                 Geometry::Intersects(obj1, obj2);
1448                 int numIntersecting = Global::numIntersectParams;
1449                 double t = Global::intersectParam[0];
1450                 double u = Global::intersectParam[1];
1451
1452                 if (numIntersecting > 0)
1453                 {
1454                         Vector v1 = Geometry::GetPointForParameter(obj1, t);
1455                         Vector v2 = Geometry::GetPointForParameter(obj2, u);
1456                         QString text = tr("Intersection t=%1 (%3, %4), u=%2 (%5, %6)");
1457                         informativeText = text.arg(t).arg(u).arg(v1.x).arg(v1.y).arg(v2.x).arg(v2.y);
1458
1459                         hoveringIntersection = true;
1460                         intersectionPoint = v1;
1461                 }
1462
1463                 numIntersecting = Global::numIntersectPoints;
1464
1465                 if (numIntersecting > 0)
1466                 {
1467                         Vector v1 = Global::intersectPoint[0];
1468
1469                         if (numIntersecting == 2)
1470                         {
1471                                 Vector v2 = Global::intersectPoint[1];
1472
1473                                 if (Vector::Magnitude(v2, point) < Vector::Magnitude(v1, point))
1474                                         v1 = v2;
1475                         }
1476
1477                         QString text = tr("Intersection <%1, %2>");
1478                         informativeText = text.arg(v1.x).arg(v1.y);
1479                         hoveringIntersection = true;
1480                         intersectionPoint = v1;
1481                 }
1482         }
1483
1484         // Handle object movement (left button down & over an object)
1485         if ((event->buttons() & Qt::LeftButton) && draggingObject && !Global::tool)
1486         {
1487                 if (hoveringIntersection)
1488                         point = intersectionPoint;
1489                 else if (hoverPointValid)
1490                         point = hoverPoint;
1491                 else if (Global::snapToGrid)
1492                         point = SnapPointToGrid(point);
1493
1494                 HandleObjectMovement(point);
1495                 update();
1496                 oldPoint = point;
1497                 return;
1498         }
1499
1500         // Do tool handling, if any are active...
1501         if (Global::tool)
1502         {
1503                 if (hoveringIntersection)
1504                         point = intersectionPoint;
1505                 else if (hoverPointValid)
1506                         point = hoverPoint;
1507                 else if (Global::snapToGrid)
1508                 {
1509                         if (angleSnap)
1510                                 point = SnapPointToAngle(point);
1511                         else
1512                                 point = SnapPointToGrid(point);
1513                 }
1514
1515                 ToolHandler(ToolMouseMove, point);
1516         }
1517
1518         // This is used to draw the tool crosshair...
1519         oldPoint = point;
1520
1521         if (needUpdate || Global::tool)
1522                 update();
1523 }
1524
1525
1526 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
1527 {
1528         if (event->button() == Qt::LeftButton)
1529         {
1530 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
1531 //could set it up to use the document's update function (assumes that all object
1532 //updates are being reported correctly:
1533 //              if (document.NeedsUpdate())
1534                 // Do an update if collided with at least *one* object in the document
1535 //              if (collided)
1536                         update();
1537
1538                 if (Global::tool)
1539                 {
1540                         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1541                         ToolHandler(ToolMouseUp, point);
1542                         return;
1543                 }
1544
1545                 if (Global::selectionInProgress)
1546                         Global::selectionInProgress = false;
1547
1548                 informativeText.clear();
1549 // Should we be doing this automagically? Hmm...
1550                 // Clear our vectors
1551                 select.clear();
1552                 hover.clear();
1553
1554                 // Scoop 'em up
1555                 std::vector<void *>::iterator i;
1556
1557                 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1558                 {
1559                         if (((Object *)(*i))->selected)
1560                                 select.push_back(*i);
1561                 }
1562
1563                 draggingObject = false;
1564         }
1565         else if (event->button() == Qt::MiddleButton)
1566         {
1567                 scrollDrag = false;
1568                 setCursor(Qt::ArrowCursor);
1569         }
1570 }
1571
1572
1573 void DrawingView::wheelEvent(QWheelEvent * event)
1574 {
1575         double zoomFactor = 1.25;
1576         QSize sizeWin = size();
1577         Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
1578         center = Painter::QtToCartesianCoords(center);
1579
1580         // This is not centering for some reason. Need to figure out why. :-/
1581         if (event->delta() > 0)
1582         {
1583                 Vector newOrigin = center - ((center - Global::origin) / zoomFactor);
1584                 Global::origin = newOrigin;
1585                 Global::zoom *= zoomFactor;
1586         }
1587         else
1588         {
1589                 Vector newOrigin = center + ((-center + Global::origin) * zoomFactor);
1590                 Global::origin = newOrigin;
1591                 Global::zoom /= zoomFactor;
1592         }
1593
1594 //      Global::gridSpacing = gridPixels / Painter::zoom;
1595 //      UpdateGridBackground();
1596         SetGridSize(Global::gridSpacing * Global::zoom);
1597         update();
1598 //      zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Global::gridSpacing));
1599 }
1600
1601
1602 void DrawingView::keyPressEvent(QKeyEvent * event)
1603 {
1604         bool oldShift = shiftDown;
1605         bool oldCtrl = ctrlDown;
1606         bool oldAlt = altDown;
1607
1608         if (event->key() == Qt::Key_Shift)
1609                 shiftDown = true;
1610         else if (event->key() == Qt::Key_Control)
1611                 ctrlDown = true;
1612         else if (event->key() == Qt::Key_Alt)
1613                 altDown = true;
1614
1615         if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1616         {
1617                 if (Global::tool)
1618                         ToolHandler(ToolKeyDown, Point(0, 0));
1619
1620                 update();
1621         }
1622
1623         if (oldAlt != altDown)
1624         {
1625                 scrollDrag = true;
1626                 setCursor(Qt::SizeAllCursor);
1627 //              oldPoint = Vector();
1628                 oldPoint = oldScrollPoint;
1629         }
1630
1631         if (select.size() > 0)
1632         {
1633                 if (event->key() == Qt::Key_Up)
1634                 {
1635                         TranslateObjects(select, Point(0, +1.0));
1636                         update();
1637                 }
1638                 else if (event->key() == Qt::Key_Down)
1639                 {
1640                         TranslateObjects(select, Point(0, -1.0));
1641                         update();
1642                 }
1643                 else if (event->key() == Qt::Key_Right)
1644                 {
1645                         TranslateObjects(select, Point(+1.0, 0));
1646                         update();
1647                 }
1648                 else if (event->key() == Qt::Key_Left)
1649                 {
1650                         TranslateObjects(select, Point(-1.0, 0));
1651                         update();
1652                 }
1653         }
1654 }
1655
1656
1657 void DrawingView::keyReleaseEvent(QKeyEvent * event)
1658 {
1659         bool oldShift = shiftDown;
1660         bool oldCtrl = ctrlDown;
1661         bool oldAlt = altDown;
1662
1663         if (event->key() == Qt::Key_Shift)
1664                 shiftDown = false;
1665         else if (event->key() == Qt::Key_Control)
1666                 ctrlDown = false;
1667         else if (event->key() == Qt::Key_Alt)
1668                 altDown = false;
1669
1670         if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1671         {
1672                 if (Global::tool)
1673                         ToolHandler(ToolKeyUp, Point(0, 0));
1674
1675                 update();
1676         }
1677
1678         if (oldAlt != altDown)
1679         {
1680                 scrollDrag = false;
1681                 setCursor(Qt::ArrowCursor);
1682         }
1683 }
1684
1685
1686 //
1687 // This looks strange, but it's really quite simple: We want a point that's
1688 // more than half-way to the next grid point to snap there while conversely we
1689 // want a point that's less than half-way to to the next grid point then snap
1690 // to the one before it. So we add half of the grid spacing to the point, then
1691 // divide by it so that we can remove the fractional part, then multiply it
1692 // back to get back to the correct answer.
1693 //
1694 Point DrawingView::SnapPointToGrid(Point point)
1695 {
1696         point += Global::gridSpacing / 2.0;             // *This* adds to Z!!!
1697         point /= Global::gridSpacing;
1698         point.x = floor(point.x);//need to fix this for negative numbers...
1699         point.y = floor(point.y);
1700         point.z = 0;                                    // Make *sure* Z doesn't go anywhere!!!
1701         point *= Global::gridSpacing;
1702         return point;
1703 }
1704
1705
1706 Point DrawingView::SnapPointToAngle(Point point)
1707 {
1708         // Snap to a single digit angle (using toolpoint #1 as the center)
1709         double angle = Vector::Angle(toolPoint[0], point);
1710         double length = Vector::Magnitude(toolPoint[0], point);
1711
1712         // Convert from radians to degrees
1713         double degAngle = angle * RADIANS_TO_DEGREES;
1714         double snapAngle = (double)((int)(degAngle + 0.5));
1715
1716         Vector v;
1717         v.SetAngleAndLength(snapAngle * DEGREES_TO_RADIANS, length);
1718         point = toolPoint[0] + v;
1719
1720         return point;
1721 }
1722
1723
1724 Rect DrawingView::GetObjectExtents(Object * obj)
1725 {
1726         // Default to empty rect, if object checks below fail for some reason
1727         Rect rect;
1728
1729         switch (obj->type)
1730         {
1731         case OTLine:
1732         case OTDimension:
1733         {
1734                 rect = Rect(obj->p[0], obj->p[1]);
1735                 break;
1736         }
1737
1738         case OTCircle:
1739         {
1740                 rect = Rect(obj->p[0], obj->p[0]);
1741                 rect.Expand(obj->radius[0]);
1742                 break;
1743         }
1744
1745         case OTArc:
1746         {
1747                 Arc * a = (Arc *)obj;
1748
1749                 double start = a->angle[0];
1750                 double end = start + a->angle[1];
1751                 rect = Rect(Point(cos(start), sin(start)), Point(cos(end), sin(end)));
1752
1753                 // If the end of the arc is before the beginning, add 360 degrees to it
1754                 if (end < start)
1755                         end += TAU;
1756
1757                 // Adjust the bounds depending on which axes are crossed
1758                 if ((start < QTR_TAU) && (end > QTR_TAU))
1759                         rect.t = 1.0;
1760
1761                 if ((start < HALF_TAU) && (end > HALF_TAU))
1762                         rect.l = -1.0;
1763
1764                 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
1765                         rect.b = -1.0;
1766
1767                 if ((start < TAU) && (end > TAU))
1768                         rect.r = 1.0;
1769
1770                 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
1771                         rect.t = 1.0;
1772
1773                 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
1774                         rect.l = -1.0;
1775
1776                 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
1777                         rect.b = -1.0;
1778
1779                 rect *= a->radius[0];
1780                 rect.Translate(a->p[0]);
1781                 break;
1782         }
1783
1784         case OTText:
1785         {
1786                 Text * t = (Text *)obj;
1787                 rect = Rect(t->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
1788                 break;
1789         }
1790
1791         case OTContainer:
1792         {
1793                 Container * c = (Container *)obj;
1794                 std::vector<void *>::iterator i = c->objects.begin();
1795                 rect = GetObjectExtents((Object *)*i);
1796                 i++;
1797
1798                 for(; i!=c->objects.end(); i++)
1799                         rect |= GetObjectExtents((Object *)*i);
1800         }
1801
1802         default:
1803                 break;
1804         }
1805
1806         return rect;
1807 }
1808
1809
1810 void DrawingView::CheckObjectBounds(void)
1811 {
1812         std::vector<void *>::iterator i;
1813
1814         for(i=document.objects.begin(); i!=document.objects.end(); i++)
1815         {
1816                 Object * obj = (Object *)(*i);
1817                 obj->selected = false;
1818
1819                 switch (obj->type)
1820                 {
1821                 case OTLine:
1822                 case OTDimension:
1823                 {
1824                         Line * l = (Line *)obj;
1825
1826                         if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
1827                                 l->selected = true;
1828
1829                         break;
1830                 }
1831
1832                 case OTCircle:
1833                 {
1834                         Circle * c = (Circle *)obj;
1835
1836                         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]))
1837                                 c->selected = true;
1838
1839                         break;
1840                 }
1841
1842                 case OTArc:
1843                 {
1844                         Arc * a = (Arc *)obj;
1845
1846                         double start = a->angle[0];
1847                         double end = start + a->angle[1];
1848                         QPointF p1(cos(start), sin(start));
1849                         QPointF p2(cos(end), sin(end));
1850                         QRectF bounds(p1, p2);
1851
1852 #if 1
1853                         // Swap X/Y coordinates if they're backwards...
1854                         if (bounds.left() > bounds.right())
1855                         {
1856                                 double temp = bounds.left();
1857                                 bounds.setLeft(bounds.right());
1858                                 bounds.setRight(temp);
1859                         }
1860
1861                         if (bounds.bottom() > bounds.top())
1862                         {
1863                                 double temp = bounds.bottom();
1864                                 bounds.setBottom(bounds.top());
1865                                 bounds.setTop(temp);
1866                         }
1867 #else
1868                         // Doesn't work as advertised! For shame!
1869                         bounds = bounds.normalized();
1870 #endif
1871
1872                         // If the end of the arc is before the beginning, add 360 degrees to it
1873                         if (end < start)
1874                                 end += TAU;
1875
1876                         // Adjust the bounds depending on which axes are crossed
1877                         if ((start < QTR_TAU) && (end > QTR_TAU))
1878                                 bounds.setTop(1.0);
1879
1880                         if ((start < HALF_TAU) && (end > HALF_TAU))
1881                                 bounds.setLeft(-1.0);
1882
1883                         if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
1884                                 bounds.setBottom(-1.0);
1885
1886                         if ((start < TAU) && (end > TAU))
1887                                 bounds.setRight(1.0);
1888
1889                         if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
1890                                 bounds.setTop(1.0);
1891
1892                         if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
1893                                 bounds.setLeft(-1.0);
1894
1895                         if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
1896                                 bounds.setBottom(-1.0);
1897
1898                         bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
1899                         bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
1900                         bounds.translate(a->p[0].x, a->p[0].y);
1901
1902                         if (Global::selection.contains(bounds))
1903                                 a->selected = true;
1904
1905                         break;
1906                 }
1907
1908                 case OTText:
1909                 {
1910                         Text * t = (Text *)obj;
1911                         Rect r(obj->p[0], Point(t->p[0].x + t->extents.Width(), t->p[0].y - t->extents.Height()));
1912
1913                         if (Global::selection.contains(r.l, r.t) && Global::selection.contains(r.r, r.b))
1914                                 t->selected = true;
1915
1916                         break;
1917                 }
1918
1919                 default:
1920                         break;
1921                 }
1922         }
1923 }
1924
1925
1926 bool DrawingView::HitTestObjects(Point point)
1927 {
1928         std::vector<void *>::iterator i;
1929         numHovered = 0;
1930         bool needUpdate = false;
1931         hoverPointValid = false;
1932
1933         for(i=document.objects.begin(); i!=document.objects.end(); i++)
1934         {
1935                 Object * obj = (Object *)(*i);
1936
1937                 // If we're seeing the object we're dragging, skip it
1938                 if (draggingObject && (obj == dragged))
1939                         continue;
1940
1941                 if (HitTest(obj, point))
1942                         needUpdate = true;
1943
1944                 if (obj->hovered)
1945                 {
1946                         numHovered++;
1947 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
1948                         emit(ObjectHovered(obj));
1949                 }
1950         }
1951
1952         return needUpdate;
1953 }
1954
1955
1956 bool DrawingView::HitTest(Object * obj, Point point)
1957 {
1958         bool needUpdate = false;
1959
1960         switch (obj->type)
1961         {
1962         case OTLine:
1963         {
1964                 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
1965                 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
1966                 Vector lineSegment = obj->p[1] - obj->p[0];
1967                 Vector v1 = point - obj->p[0];
1968                 Vector v2 = point - obj->p[1];
1969                 double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
1970                 double distance;
1971
1972                 if (t < 0.0)
1973                         distance = v1.Magnitude();
1974                 else if (t > 1.0)
1975                         distance = v2.Magnitude();
1976                 else
1977                         // distance = ?Det?(ls, v1) / |ls|
1978                         distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
1979                                 / lineSegment.Magnitude());
1980
1981                 if ((v1.Magnitude() * Global::zoom) < 8.0)
1982                 {
1983                         obj->hitPoint[0] = true;
1984                         hoverPoint = obj->p[0];
1985                         hoverPointValid = true;
1986                 }
1987                 else if ((v2.Magnitude() * Global::zoom) < 8.0)
1988                 {
1989                         obj->hitPoint[1] = true;
1990                         hoverPoint = obj->p[1];
1991                         hoverPointValid = true;
1992                 }
1993                 else if ((distance * Global::zoom) < 5.0)
1994                         obj->hitObject = true;
1995
1996                 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
1997
1998                 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
1999                         needUpdate = true;
2000
2001                 break;
2002         }
2003
2004         case OTCircle:
2005         {
2006                 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
2007                 obj->hitPoint[0] = obj->hitObject = false;
2008                 double length = Vector::Magnitude(obj->p[0], point);
2009
2010                 if ((length * Global::zoom) < 8.0)
2011                 {
2012                         obj->hitPoint[0] = true;
2013                         hoverPoint = obj->p[0];
2014                         hoverPointValid = true;
2015                 }
2016                 else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
2017                         obj->hitObject = true;
2018
2019                 obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
2020
2021                 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
2022                         needUpdate = true;
2023
2024                 break;
2025         }
2026
2027         case OTArc:
2028         {
2029                 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
2030                 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
2031                 double length = Vector::Magnitude(obj->p[0], point);
2032                 double angle = Vector::Angle(obj->p[0], point);
2033
2034                 // Make sure we get the angle in the correct spot
2035                 if (angle < obj->angle[0])
2036                         angle += TAU;
2037
2038                 // Get the span that we're pointing at...
2039                 double span = angle - obj->angle[0];
2040
2041                 // N.B.: Still need to hit test the arc start & arc span handles...
2042                 double spanAngle = obj->angle[0] + obj->angle[1];
2043                 Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
2044                 Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
2045                 double length2 = Vector::Magnitude(point, handle1);
2046                 double length3 = Vector::Magnitude(point, handle2);
2047
2048                 if ((length * Global::zoom) < 8.0)
2049                 {
2050                         obj->hitPoint[0] = true;
2051                         hoverPoint = obj->p[0];
2052                         hoverPointValid = true;
2053                 }
2054                 else if ((length2 * Global::zoom) < 8.0)
2055                 {
2056                         obj->hitPoint[1] = true;
2057                         hoverPoint = handle1;
2058                         hoverPointValid = true;
2059                 }
2060                 else if ((length3 * Global::zoom) < 8.0)
2061                 {
2062                         obj->hitPoint[2] = true;
2063                         hoverPoint = handle2;
2064                         hoverPointValid = true;
2065                 }
2066                 else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
2067                         obj->hitObject = true;
2068
2069                 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
2070
2071                 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
2072                         needUpdate = true;
2073
2074                 break;
2075         }
2076
2077         case OTDimension:
2078         {
2079                 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHP3 = obj->hitPoint[3], oldHP4 = obj->hitPoint[4], oldHO = obj->hitObject;
2080                 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitPoint[3] = obj->hitPoint[4] = obj->hitObject = false;
2081
2082                 Dimension * d = (Dimension *)obj;
2083
2084                 Vector orthogonal = Vector::Normal(d->lp[0], d->lp[1]);
2085                 // Get our line parallel to our points
2086                 float scaledThickness = Global::scale * obj->thickness;
2087                 Point p1 = d->lp[0] + (orthogonal * 10.0 * scaledThickness);
2088                 Point p2 = d->lp[1] + (orthogonal * 10.0 * scaledThickness);
2089                 Point p3(p1, point);
2090
2091                 Vector v1(d->p[0], point);
2092                 Vector v2(d->p[1], point);
2093                 Vector lineSegment(p1, p2);
2094                 double t = Geometry::ParameterOfLineAndPoint(p1, p2, point);
2095                 double distance;
2096                 Point midpoint = (p1 + p2) / 2.0;
2097                 Point hFSPoint = Point(midpoint, point);
2098                 Point hCS1Point = Point((p1 + midpoint) / 2.0, point);
2099                 Point hCS2Point = Point((midpoint + p2) / 2.0, point);
2100
2101                 if (t < 0.0)
2102                         distance = v1.Magnitude();
2103                 else if (t > 1.0)
2104                         distance = v2.Magnitude();
2105                 else
2106                         // distance = ?Det?(ls, v1) / |ls|
2107                         distance = fabs((lineSegment.x * p3.y - p3.x * lineSegment.y)
2108                                 / lineSegment.Magnitude());
2109
2110                 if ((v1.Magnitude() * Global::zoom) < 8.0)
2111                         obj->hitPoint[0] = true;
2112                 else if ((v2.Magnitude() * Global::zoom) < 8.0)
2113                         obj->hitPoint[1] = true;
2114                 else if ((distance * Global::zoom) < 5.0)
2115                         obj->hitObject = true;
2116
2117                 if ((hFSPoint.Magnitude() * Global::zoom) < 8.0)
2118                         obj->hitPoint[2] = true;
2119                 else if ((hCS1Point.Magnitude() * Global::zoom) < 8.0)
2120                         obj->hitPoint[3] = true;
2121                 else if ((hCS2Point.Magnitude() * Global::zoom) < 8.0)
2122                         obj->hitPoint[4] = true;
2123
2124 //              return (hitPoint1 || hitPoint2 || hitLine || hitFlipSwitch || hitChangeSwitch1 || hitChangeSwitch2 ? true : false);
2125                 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitPoint[3] || obj->hitPoint[4] || obj->hitObject ? true : false);
2126
2127                 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHP3 != obj->hitPoint[3]) || (oldHP4 != obj->hitPoint[4]) || (oldHO != obj->hitObject))
2128                         needUpdate = true;
2129
2130                 break;
2131         }
2132
2133         case OTText:
2134         {
2135                 Text * t = (Text *)obj;
2136                 bool oldHO = obj->hitObject;
2137                 obj->hitObject = false;
2138
2139                 Rect r(obj->p[0], Point(obj->p[0].x + t->extents.Width(), obj->p[0].y - t->extents.Height()));
2140 //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);
2141
2142                 if (r.Contains(point))
2143                         obj->hitObject = true;
2144
2145                 obj->hovered = (obj->hitObject ? true : false);
2146
2147                 if (oldHO != obj->hitObject)
2148                         needUpdate = true;
2149
2150                 break;
2151         }
2152
2153         case OTContainer:
2154         {
2155                 // Containers must be recursively tested...
2156                 Container * c = (Container *)obj;
2157                 c->hitObject = false;
2158                 c->hovered = false;
2159                 std::vector<void *>::iterator i;
2160
2161                 for(i=c->objects.begin(); i!=c->objects.end(); i++)
2162                 {
2163                         Object * cObj = (Object *)*i;
2164
2165                         if (HitTest(cObj, point))
2166                                 needUpdate = true;
2167
2168                         if (cObj->hitObject == true)
2169                                 c->hitObject = true;
2170
2171                         if (cObj->hovered == true)
2172                                 c->hovered = true;
2173                 }
2174
2175                 break;
2176         }
2177
2178         default:
2179                 break;
2180         }
2181
2182         return needUpdate;
2183 }
2184
2185
2186 bool DrawingView::HandleObjectClicked(void)
2187 {
2188         if (dragged->type == OTDimension)
2189         {
2190                 Dimension * d = (Dimension *)dragged;
2191
2192                 if (d->hitPoint[2])
2193                 {
2194                         // Hit the "flip sides" switch, so flip 'em
2195                         Point temp = d->p[0];
2196                         d->p[0] = d->p[1];
2197                         d->p[1] = temp;
2198                         return true;
2199                 }
2200                 else if (d->hitPoint[3])
2201                 {
2202                         // There are three cases here: aligned, horizontal, & vertical.
2203                         // Aligned and horizontal do the same thing, vertical goes back to
2204                         // linear.
2205                         if (d->subtype == DTLinearVert)
2206                                 d->subtype = DTLinear;
2207                         else
2208                                 d->subtype = DTLinearVert;
2209
2210                         return true;
2211                 }
2212                 else if (d->hitPoint[4])
2213                 {
2214                         // There are three cases here: aligned, horizontal, & vertical.
2215                         // Aligned and vertical do the same thing, horizontal goes back to
2216                         // linear.
2217                         if (d->subtype == DTLinearHorz)
2218                                 d->subtype = DTLinear;
2219                         else
2220                                 d->subtype = DTLinearHorz;
2221
2222                         return true;
2223                 }
2224         }
2225
2226         return false;
2227 }
2228
2229
2230 void DrawingView::HandleObjectMovement(Point point)
2231 {
2232         Point delta = point - oldPoint;
2233 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
2234 //      Object * obj = (Object *)hover[0];
2235         Object * obj = dragged;
2236 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
2237 //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"));
2238
2239         switch (obj->type)
2240         {
2241         case OTLine:
2242                 if (obj->hitPoint[0])
2243                         obj->p[0] = point;
2244                 else if (obj->hitPoint[1])
2245                         obj->p[1] = point;
2246                 else if (obj->hitObject)
2247                 {
2248                         obj->p[0] += delta;
2249                         obj->p[1] += delta;
2250                 }
2251
2252                 break;
2253
2254         case OTCircle:
2255                 if (obj->hitPoint[0])
2256                         obj->p[0] = point;
2257                 else if (obj->hitObject)
2258                 {
2259 //this doesn't work. we need to save this on mouse down for this to work correctly!
2260 //                      double oldRadius = obj->radius[0];
2261                         obj->radius[0] = Vector::Magnitude(obj->p[0], point);
2262
2263                         QString text = QObject::tr("Radius: %1");//\nScale: %2%");
2264                         informativeText = text.arg(obj->radius[0], 0, 'd', 4);//.arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0);
2265                 }
2266
2267                 break;
2268
2269         case OTArc:
2270                 if (obj->hitPoint[0])
2271                         obj->p[0] = point;
2272                 else if (obj->hitPoint[1])
2273                 {
2274                         // Change the Arc's span (handle #1)
2275                         if (shiftDown)
2276                         {
2277                                 double angle = Vector::Angle(obj->p[0], point);
2278                                 double delta = angle - obj->angle[0];
2279
2280                                 if (delta < 0)
2281                                         delta += TAU;
2282
2283                                 obj->angle[1] -= delta;
2284                                 obj->angle[0] = angle;
2285
2286                                 if (obj->angle[1] < 0)
2287                                         obj->angle[1] += TAU;
2288
2289                                 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
2290                                 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);
2291                                 return;
2292                         }
2293
2294                         double angle = Vector::Angle(obj->p[0], point);
2295                         obj->angle[0] = angle;
2296                         QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
2297                         informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 4);
2298                 }
2299                 else if (obj->hitPoint[2])
2300                 {
2301                         // Change the Arc's span (handle #2)
2302                         if (shiftDown)
2303                         {
2304                                 double angle = Vector::Angle(obj->p[0], point);
2305                                 obj->angle[1] = angle - obj->angle[0];
2306
2307                                 if (obj->angle[1] < 0)
2308                                         obj->angle[1] += TAU;
2309
2310                                 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
2311                                 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);
2312                                 return;
2313                         }
2314
2315                         double angle = Vector::Angle(obj->p[0], point);
2316                         obj->angle[0] = angle - obj->angle[1];
2317
2318                         if (obj->angle[0] < 0)
2319                                 obj->angle[0] += TAU;
2320
2321                         QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
2322                         informativeText = text.arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 4);
2323                 }
2324                 else if (obj->hitObject)
2325                 {
2326                         if (shiftDown)
2327                         {
2328                                 return;
2329                         }
2330
2331                         obj->radius[0] = Vector::Magnitude(obj->p[0], point);
2332                         QString text = QObject::tr("Radius: %1");
2333                         informativeText = text.arg(obj->radius[0], 0, 'd', 4);
2334                 }
2335
2336                 break;
2337
2338         case OTDimension:
2339                 if (obj->hitPoint[0])
2340                         obj->p[0] = point;
2341                 else if (obj->hitPoint[1])
2342                         obj->p[1] = point;
2343                 else if (obj->hitObject)
2344                 {
2345                         obj->p[0] += delta;
2346                         obj->p[1] += delta;
2347                 }
2348
2349                 break;
2350
2351         case OTText:
2352                 if (obj->hitObject)
2353                         obj->p[0] += delta;
2354
2355                 break;
2356
2357         case OTContainer:
2358                 // This is shitty, but works for now until I can code up something
2359                 // nicer :-)
2360                 TranslateObject(obj, delta);
2361
2362                 break;
2363         default:
2364                 break;
2365         }
2366 }
2367