]> Shamusworld >> Repos - architektonas/blob - src/drawingview.cpp
Added object pane, grouping, load/save functionality.
[architektonas] / src / drawingview.cpp
1 // drawingview.cpp
2 //
3 // Part of the Architektonas Project
4 // (C) 2011 Underground Software
5 // See the README and GPLv3 files for licensing and warranty information
6 //
7 // JLH = James Hammons <jlhamm@acm.org>
8 //
9 // Who  When        What
10 // ---  ----------  ------------------------------------------------------------
11 // JLH  03/22/2011  Created this file
12 // JLH  09/29/2011  Added middle mouse button panning
13 //
14
15 // FIXED:
16 //
17 // - Redo rendering code to *not* use Qt's transform functions, as they are tied
18 //   to a left-handed system and we need a right-handed one. [DONE]
19 //
20 // STILL TO BE DONE:
21 //
22 // - Lots of stuff
23 // - Layer locking (hiding works)
24 //
25
26 // Uncomment this for debugging...
27 //#define DEBUG
28 //#define DEBUGFOO                              // Various tool debugging...
29 //#define DEBUGTP                               // Toolpalette debugging...
30
31 #include "drawingview.h"
32
33 #include <stdint.h>
34 #include "geometry.h"
35 #include "global.h"
36 #include "mathconstants.h"
37 #include "painter.h"
38 #include "structs.h"
39 #include "utils.h"
40
41
42 #define BACKGROUND_MAX_SIZE     512
43
44
45 DrawingView::DrawingView(QWidget * parent/*= NULL*/): QWidget(parent),
46         // The value in the settings file will override this.
47         useAntialiasing(true), /*numSelected(0),*/ numHovered(0), shiftDown(false),
48         ctrlDown(false),
49         gridBackground(BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE),
50         scale(1.0), offsetX(-10), offsetY(-10), document(true),
51         gridPixels(0), collided(false), hoveringIntersection(false)
52 {
53 //wtf? doesn't work except in c++11???  document = { 0 };
54         setBackgroundRole(QPalette::Base);
55         setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
56
57         Global::gridSpacing = 12.0;             // In base units (inch is default)
58
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 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
315 {
316         // This is undoing the transform, e.g. going from client coords to local
317         // coords. In essence, the height - y is height + (y * -1), the (y * -1)
318         // term doing the conversion of the y-axis from increasing bottom to top.
319         return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
320 }
321
322
323 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
324 {
325         // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
326         // No voodoo here, it's just grouped wrong to see it. It should be:
327         // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive [why? we use -offsetX after all]
328         return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
329 }
330
331
332 void DrawingView::paintEvent(QPaintEvent * /*event*/)
333 {
334         QPainter qtPainter(this);
335         Painter painter(&qtPainter);
336
337         if (useAntialiasing)
338                 qtPainter.setRenderHint(QPainter::Antialiasing);
339
340         Global::viewportHeight = size().height();
341
342         // Draw coordinate axes
343         painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
344         painter.DrawLine(0, -16384, 0, 16384);
345         painter.DrawLine(-16384, 0, 16384, 0);
346
347         // Do object rendering...
348         for(int i=0; i<Global::numLayers; i++)
349         {
350                 if (Global::layerHidden[i] == false)
351                         RenderObjects(&painter, document.objects, i);
352         }
353
354         // Do tool rendering, if any...
355         if (Global::tool)
356         {
357                 painter.SetPen(QPen(QColor(200, 100, 0, 255), 1.0, Qt::DashLine));
358                 painter.DrawCrosshair(oldPoint);
359                 ToolDraw(&painter);
360         }
361
362         // Do selection rectangle rendering, if any
363         if (Global::selectionInProgress)
364         {
365                 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
366                 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
367                 painter.DrawRect(Global::selection);
368         }
369
370         if (hoveringIntersection)
371                 painter.DrawHandle(intersectionPoint);
372
373         if (!informativeText.isEmpty())
374                 painter.DrawInformativeText(informativeText);
375 }
376
377
378 //
379 // Renders objects in the passed in vector
380 //
381 void DrawingView::RenderObjects(Painter * painter, std::vector<void *> & v, int layer, bool ignoreLayer/*= false*/)
382 {
383         std::vector<void *>::iterator i;
384
385         for(i=v.begin(); i!=v.end(); i++)
386         {
387                 Object * obj = (Object *)(*i);
388                 float scaledThickness = Global::scale * obj->thickness;
389
390                 // If the object isn't on the current layer being drawn, skip it
391                 if (!ignoreLayer && (obj->layer != layer))
392                         continue;
393
394                 if ((Global::tool == TTRotate) && ctrlDown && obj->selected)
395                 {
396                         painter->SetPen(0x00FF00, 2.0, LSSolid);
397                 }
398                 else
399                 {
400                         painter->SetPen(obj->color, Global::zoom * scaledThickness, obj->style);
401                         painter->SetBrush(obj->color);
402
403                         if (obj->selected || obj->hitObject)
404                                 painter->SetPen(0xFF0000, Global::zoom * scaledThickness, LSDash);
405                 }
406
407                 switch (obj->type)
408                 {
409                 case OTLine:
410                         painter->DrawLine(obj->p[0], obj->p[1]);
411
412                         if (obj->hitPoint[0])
413                                 painter->DrawHandle(obj->p[0]);
414
415                         if (obj->hitPoint[1])
416                                 painter->DrawHandle(obj->p[1]);
417
418                         break;
419                 case OTCircle:
420                         painter->SetBrush(QBrush(Qt::NoBrush));
421                         painter->DrawEllipse(obj->p[0], obj->radius[0], obj->radius[0]);
422
423                         if (obj->hitPoint[0])
424                                 painter->DrawHandle(obj->p[0]);
425
426                         break;
427                 case OTArc:
428                         painter->DrawArc(obj->p[0], obj->radius[0], obj->angle[0], obj->angle[1]);
429
430                         if (obj->hitPoint[0])
431                                 painter->DrawHandle(obj->p[0]);
432
433                         if (obj->hitPoint[1])
434                                 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]));
435
436                         if (obj->hitPoint[2])
437                                 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0] + obj->angle[1]), sin(obj->angle[0] + obj->angle[1])) * obj->radius[0]));
438
439                         break;
440                 case OTDimension:
441                 {
442                         Dimension * d = (Dimension *)obj;
443
444                         Vector v(d->p[0], d->p[1]);
445                         double angle = v.Angle();
446                         Vector unit = v.Unit();
447                         Vector linePt1 = d->p[0], linePt2 = d->p[1];
448                         Vector ortho;
449                         double x1, y1, length;
450
451                         if (d->subtype == DTLinearVert)
452                         {
453                                 if ((angle < 0) || (angle > HALF_TAU))
454                                 {
455                                         x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
456                                         y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
457                                         ortho = Vector(1.0, 0);
458                                         angle = THREE_QTR_TAU;
459                                 }
460                                 else
461                                 {
462                                         x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
463                                         y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
464                                         ortho = Vector(-1.0, 0);
465                                         angle = QTR_TAU;
466                                 }
467
468                                 linePt1.x = linePt2.x = x1;
469                                 length = fabs(d->p[0].y - d->p[1].y);
470                         }
471                         else if (d->subtype == DTLinearHorz)
472                         {
473                                 if ((angle < QTR_TAU) || (angle > THREE_QTR_TAU))
474                                 {
475                                         x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
476                                         y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
477                                         ortho = Vector(0, 1.0);
478                                         angle = 0;
479                                 }
480                                 else
481                                 {
482                                         x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
483                                         y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
484                                         ortho = Vector(0, -1.0);
485                                         angle = HALF_TAU;
486                                 }
487
488                                 linePt1.y = linePt2.y = y1;
489                                 length = fabs(d->p[0].x - d->p[1].x);
490                         }
491                         else if (d->subtype == DTLinear)
492                         {
493                                 angle = Vector(linePt1, linePt2).Angle();
494                                 ortho = Vector::Normal(linePt1, linePt2);
495                                 length = v.Magnitude();
496                         }
497
498                         unit = Vector(linePt1, linePt2).Unit();
499
500                         Point p1 = linePt1 + (ortho * 10.0 * scaledThickness);
501                         Point p2 = linePt2 + (ortho * 10.0 * scaledThickness);
502                         Point p3 = linePt1 + (ortho * 16.0 * scaledThickness);
503                         Point p4 = linePt2 + (ortho * 16.0 * scaledThickness);
504                         Point p5 = d->p[0] + (ortho * 4.0 * scaledThickness);
505                         Point p6 = d->p[1] + (ortho * 4.0 * scaledThickness);
506
507                 /*
508                 The numbers hardcoded into here, what are they?
509                 I believe they are pixels.
510                 */
511                         // Draw extension lines (if certain type)
512                         painter->DrawLine(p3, p5);
513                         painter->DrawLine(p4, p6);
514
515                         // Calculate whether or not the arrowheads are too crowded to put
516                         // inside the extension lines. 9.0 is the length of the arrowhead.
517                         double t = Geometry::ParameterOfLineAndPoint(linePt1, linePt2, linePt2 - (unit * 9.0 * scaledThickness));
518
519                         // On the screen, it's acting like this is actually 58%...
520                         // This is correct, we want it to happen at > 50%
521                         if (t > 0.58)
522                         {
523                                 // Draw main dimension line + arrowheads
524                                 painter->DrawLine(p1, p2);
525                                 painter->DrawArrowhead(p1, p2, scaledThickness);
526                                 painter->DrawArrowhead(p2, p1, scaledThickness);
527                         }
528                         else
529                         {
530                                 // Draw outside arrowheads
531                                 Point p7 = p1 - (unit * 9.0 * scaledThickness);
532                                 Point p8 = p2 + (unit * 9.0 * scaledThickness);
533                                 painter->DrawArrowhead(p1, p7, scaledThickness);
534                                 painter->DrawArrowhead(p2, p8, scaledThickness);
535                                 painter->DrawLine(p1, p1 - (unit * 14.0 * scaledThickness));
536                                 painter->DrawLine(p2, p2 + (unit * 14.0 * scaledThickness));
537                         }
538
539                         // Draw length of dimension line...
540                         painter->SetFont(QFont("Arial", 8.0 * Global::zoom * scaledThickness));
541                         Point ctr = p2 + (Vector(p2, p1) / 2.0);
542
543                         QString dimText;
544
545                         if (length < 12.0)
546                                 dimText = QString("%1\"").arg(length);
547                         else
548                         {
549                                 double feet = (double)((int)length / 12);
550                                 double inches = length - (feet * 12.0);
551
552                                 if (inches == 0)
553                                         dimText = QString("%1'").arg(feet);
554                                 else
555                                         dimText = QString("%1' %2\"").arg(feet).arg(inches);
556                         }
557
558                         painter->DrawAngledText(ctr, angle, dimText, scaledThickness);
559
560                         break;
561                 }
562                 case OTText:
563                 {
564                         Text * t = (Text *)obj;
565                         painter->DrawTextObject(t->p[0], t->s.c_str(), scaledThickness);
566                         break;
567                 }
568                 case OTSpline:
569                 {
570                         break;
571                 }
572                 case OTPolygon:
573                 {
574                         break;
575                 }
576                 case OTContainer:
577                 {
578                         // Containers require recursive rendering...
579                         Container * c = (Container *)obj;
580                         RenderObjects(painter, (*c).objects, layer);
581
582 //printf("Container extents: <%lf, %lf>, <%lf, %lf>\nsize: %i\n", r.l, r.t, r.r, r.b, c->objects.size());
583                         // Containers also have special indicators showing they are selected
584                         if (c->selected || c->hitObject)
585                         {
586                                 Rect r = GetObjectExtents(obj);
587                                 painter->DrawRectCorners(r);
588                         }
589
590                         break;
591                 }
592                 default:
593                         break;
594                 }
595         }
596 }
597
598
599 void DrawingView::AddHoveredToSelection(void)
600 {
601         std::vector<void *>::iterator i;
602
603         for(i=document.objects.begin(); i!=document.objects.end(); i++)
604         {
605                 if (((Object *)(*i))->hovered)
606                         ((Object *)(*i))->selected = true;
607         }
608 }
609
610
611 void DrawingView::GetSelection(std::vector<void *> & v)
612 {
613         v.clear();
614         std::vector<void *>::iterator i;
615
616         for(i=document.objects.begin(); i!=document.objects.end(); i++)
617         {
618                 if (((Object *)(*i))->selected)
619                         v.push_back(*i);
620         }
621 }
622
623
624 void DrawingView::GetHovered(std::vector<void *> & v)
625 {
626         v.clear();
627         std::vector<void *>::iterator i;
628
629         for(i=document.objects.begin(); i!=document.objects.end(); i++)
630         {
631                 if (((Object *)(*i))->hovered)
632 //              {
633 //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"));
634                         v.push_back(*i);
635 //              }
636         }
637 }
638
639
640 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
641 {
642         Global::screenSize = Vector(size().width(), size().height());
643         UpdateGridBackground();
644 }
645
646
647 void DrawingView::ToolHandler(int mode, Point p)
648 {
649         if (Global::tool == TTLine)
650                 LineHandler(mode, p);
651         else if (Global::tool == TTCircle)
652                 CircleHandler(mode, p);
653         else if (Global::tool == TTArc)
654                 ArcHandler(mode, p);
655         else if (Global::tool == TTRotate)
656                 RotateHandler(mode, p);
657         else if (Global::tool == TTMirror)
658                 MirrorHandler(mode, p);
659 }
660
661
662 void DrawingView::ToolDraw(Painter * painter)
663 {
664         if (Global::tool == TTLine)
665         {
666                 if (Global::toolState == TSNone)
667                 {
668                         painter->DrawHandle(toolPoint[0]);
669                 }
670                 else if ((Global::toolState == TSPoint2) && shiftDown)
671                 {
672                         painter->DrawHandle(toolPoint[1]);
673                 }
674                 else
675                 {
676                         painter->DrawLine(toolPoint[0], toolPoint[1]);
677                         painter->DrawHandle(toolPoint[1]);
678
679                         Vector v(toolPoint[0], toolPoint[1]);
680                         double absAngle = v.Angle() * RADIANS_TO_DEGREES;
681                         double absLength = v.Magnitude();
682                         QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
683                         informativeText = text.arg(absLength).arg(absAngle);
684                 }
685         }
686         else if (Global::tool == TTCircle)
687         {
688                 if (Global::toolState == TSNone)
689                 {
690                         painter->DrawHandle(toolPoint[0]);
691                 }
692                 else if ((Global::toolState == TSPoint2) && shiftDown)
693                 {
694                         painter->DrawHandle(toolPoint[1]);
695                 }
696                 else
697                 {
698                         painter->DrawCross(toolPoint[0]);
699                         double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
700                         painter->SetBrush(QBrush(Qt::NoBrush));
701                         painter->DrawEllipse(toolPoint[0], length, length);
702                         QString text = tr("Radius: %1 in.");
703                         informativeText = text.arg(length);
704                 }
705         }
706         else if (Global::tool == TTArc)
707         {
708                 if (Global::toolState == TSNone)
709                 {
710                         painter->DrawHandle(toolPoint[0]);
711                 }
712                 else if (Global::toolState == TSPoint2)
713                 {
714                         double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
715                         painter->SetBrush(QBrush(Qt::NoBrush));
716                         painter->DrawEllipse(toolPoint[0], length, length);
717                         painter->DrawLine(toolPoint[0], toolPoint[1]);
718                         painter->DrawHandle(toolPoint[1]);
719                         QString text = tr("Radius: %1 in.");
720                         informativeText = text.arg(length);
721                 }
722                 else if (Global::toolState == TSPoint3)
723                 {
724                         double angle = Vector::Angle(toolPoint[0], toolPoint[2]);
725                         painter->DrawLine(toolPoint[0], toolPoint[2]);
726                         painter->SetBrush(QBrush(Qt::NoBrush));
727                         painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
728                         painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
729                         QString text = tr("Angle start: %1") + QChar(0x00B0);
730                         informativeText = text.arg(RADIANS_TO_DEGREES * angle);
731                 }
732                 else
733                 {
734                         double angle = Vector::Angle(toolPoint[0], toolPoint[3]);
735                         double span = angle - toolPoint[2].x;
736
737                         if (span < 0)
738                                 span += TAU;
739
740                         painter->DrawLine(toolPoint[0], toolPoint[3]);
741                         painter->SetBrush(QBrush(Qt::NoBrush));
742                         painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
743                         painter->SetPen(0xFF00FF, 2.0, LSSolid);
744                         painter->DrawArc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
745                         painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
746                         QString text = tr("Arc span: %1") + QChar(0x00B0);
747                         informativeText = text.arg(RADIANS_TO_DEGREES * span);
748                 }
749         }
750         else if (Global::tool == TTRotate)
751         {
752                 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
753                         painter->DrawHandle(toolPoint[0]);
754                 else if ((Global::toolState == TSPoint2) && shiftDown)
755                         painter->DrawHandle(toolPoint[1]);
756                 else
757                 {
758                         if (toolPoint[0] == toolPoint[1])
759                                 return;
760
761                         painter->DrawLine(toolPoint[0], toolPoint[1]);
762
763                         double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
764                         QString text = QChar(0x2221) + QObject::tr(": %1");
765                         informativeText = text.arg(absAngle);
766
767                         if (ctrlDown)
768                                 informativeText += " (Copy)";
769                 }
770         }
771         else if (Global::tool == TTMirror)
772         {
773                 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
774                         painter->DrawHandle(toolPoint[0]);
775                 else if ((Global::toolState == TSPoint2) && shiftDown)
776                         painter->DrawHandle(toolPoint[1]);
777                 else
778                 {
779                         if (toolPoint[0] == toolPoint[1])
780                                 return;
781                         
782                         Point mirrorPoint = toolPoint[0] + Vector(toolPoint[1], toolPoint[0]);
783                         painter->DrawLine(mirrorPoint, toolPoint[1]);
784
785                         double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
786
787                         if (absAngle > 180.0)
788                                 absAngle -= 180.0;
789
790                         QString text = QChar(0x2221) + QObject::tr(": %1");
791                         informativeText = text.arg(absAngle);
792
793                         if (ctrlDown)
794                                 informativeText += " (Copy)";
795                 }
796         }
797 }
798
799
800 void DrawingView::LineHandler(int mode, Point p)
801 {
802         switch (mode)
803         {
804         case ToolMouseDown:
805                 if (Global::toolState == TSNone)
806                         toolPoint[0] = p;
807                 else
808                         toolPoint[1] = p;
809
810                 break;
811         case ToolMouseMove:
812                 if (Global::toolState == TSNone)
813                         toolPoint[0] = p;
814                 else
815                         toolPoint[1] = p;
816
817                 break;
818         case ToolMouseUp:
819                 if (Global::toolState == TSNone)
820                 {
821                         Global::toolState = TSPoint2;
822                         // Prevent spurious line from drawing...
823                         toolPoint[1] = toolPoint[0];
824                 }
825                 else if ((Global::toolState == TSPoint2) && shiftDown)
826                 {
827                         // Key override is telling us to make a new line, not continue the
828                         // previous one.
829                         toolPoint[0] = toolPoint[1];
830                 }
831                 else
832                 {
833                         Line * l = new Line(toolPoint[0], toolPoint[1]);
834                         l->layer = Global::activeLayer;
835                         document.objects.push_back(l);
836                         toolPoint[0] = toolPoint[1];
837                 }
838         }
839 }
840
841
842 void DrawingView::CircleHandler(int mode, Point p)
843 {
844         switch (mode)
845         {
846         case ToolMouseDown:
847                 if (Global::toolState == TSNone)
848                         toolPoint[0] = p;
849                 else
850                         toolPoint[1] = p;
851
852                 break;
853         case ToolMouseMove:
854                 if (Global::toolState == TSNone)
855                         toolPoint[0] = p;
856                 else
857                         toolPoint[1] = p;
858
859                 break;
860         case ToolMouseUp:
861                 if (Global::toolState == TSNone)
862                 {
863                         Global::toolState = TSPoint2;
864                         // Prevent spurious line from drawing...
865                         toolPoint[1] = toolPoint[0];
866                 }
867                 else if ((Global::toolState == TSPoint2) && shiftDown)
868                 {
869                         // Key override is telling us to make a new line, not continue the
870                         // previous one.
871                         toolPoint[0] = toolPoint[1];
872                 }
873                 else
874                 {
875                         double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
876                         Circle * c = new Circle(toolPoint[0], length);
877                         c->layer = Global::activeLayer;
878                         document.objects.push_back(c);
879                         toolPoint[0] = toolPoint[1];
880                         Global::toolState = TSNone;
881                 }
882         }
883 }
884
885
886 void DrawingView::ArcHandler(int mode, Point p)
887 {
888         switch (mode)
889         {
890         case ToolMouseDown:
891                 if (Global::toolState == TSNone)
892                         toolPoint[0] = p;
893                 else if (Global::toolState == TSPoint2)
894                         toolPoint[1] = p;
895                 else if (Global::toolState == TSPoint3)
896                         toolPoint[2] = p;
897                 else
898                         toolPoint[3] = p;
899
900                 break;
901         case ToolMouseMove:
902                 if (Global::toolState == TSNone)
903                         toolPoint[0] = p;
904                 else if (Global::toolState == TSPoint2)
905                         toolPoint[1] = p;
906                 else if (Global::toolState == TSPoint3)
907                         toolPoint[2] = p;
908                 else
909                         toolPoint[3] = p;
910
911                 break;
912         case ToolMouseUp:
913                 if (Global::toolState == TSNone)
914                 {
915                         // Prevent spurious line from drawing...
916                         toolPoint[1] = toolPoint[0];
917                         Global::toolState = TSPoint2;
918                 }
919                 else if (Global::toolState == TSPoint2)
920                 {
921                         if (shiftDown)
922                         {
923                                 // Key override is telling us to start circle at new center, not
924                                 // continue the current one.
925                                 toolPoint[0] = toolPoint[1];
926                                 return;
927                         }
928
929                         // Set the radius in toolPoint[1].x
930                         toolPoint[1].x = Vector::Magnitude(toolPoint[0], toolPoint[1]);
931                         Global::toolState = TSPoint3;
932                 }
933                 else if (Global::toolState == TSPoint3)
934                 {
935                         // Set the angle in toolPoint[2].x
936                         toolPoint[2].x = Vector::Angle(toolPoint[0], toolPoint[2]);
937                         Global::toolState = TSPoint4;
938                 }
939                 else
940                 {
941                         double endAngle = Vector::Angle(toolPoint[0], toolPoint[3]);
942                         double span = endAngle - toolPoint[2].x;
943
944                         if (span < 0)
945                                 span += TAU;
946
947                         Arc * arc = new Arc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
948                         arc->layer = Global::activeLayer;
949                         document.objects.push_back(arc);
950                         Global::toolState = TSNone;
951                 }
952         }
953 }
954
955
956 void DrawingView::RotateHandler(int mode, Point p)
957 {
958         switch (mode)
959         {
960         case ToolMouseDown:
961                 if (Global::toolState == TSNone)
962                 {
963                         toolPoint[0] = p;
964                         SavePointsFrom(select, toolScratch);
965                         Global::toolState = TSPoint1;
966                 }
967                 else if (Global::toolState == TSPoint1)
968                         toolPoint[0] = p;
969                 else
970                         toolPoint[1] = p;
971
972                 break;
973         case ToolMouseMove:
974                 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
975                         toolPoint[0] = p;
976                 else if (Global::toolState == TSPoint2)
977                 {
978                         toolPoint[1] = p;
979
980                         if (shiftDown)
981                                 return;
982
983                         double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
984                         std::vector<void *>::iterator j = select.begin();
985                         std::vector<Object>::iterator i = toolScratch.begin();
986
987                         for(; i!=toolScratch.end(); i++, j++)
988                         {
989                                 Object obj = *i;
990                                 Point p1 = Geometry::RotatePointAroundPoint(obj.p[0], toolPoint[0], angle);
991                                 Point p2 = Geometry::RotatePointAroundPoint(obj.p[1], toolPoint[0], angle);
992                                 Object * obj2 = (Object *)(*j);
993                                 obj2->p[0] = p1;
994                                 obj2->p[1] = p2;
995
996                                 if (obj.type == OTArc)
997                                 {
998                                         obj2->angle[0] = obj.angle[0] + angle;
999
1000                                         if (obj2->angle[0] > TAU)
1001                                                 obj2->angle[0] -= TAU;
1002                                 }
1003                         }
1004                 }
1005
1006                 break;
1007         case ToolMouseUp:
1008                 if (Global::toolState == TSPoint1)
1009                 {
1010                         Global::toolState = TSPoint2;
1011                         // Prevent spurious line from drawing...
1012                         toolPoint[1] = toolPoint[0];
1013                 }
1014                 else if ((Global::toolState == TSPoint2) && shiftDown)
1015                 {
1016                         // Key override is telling us to make a new line, not continue the
1017                         // previous one.
1018                         toolPoint[0] = toolPoint[1];
1019                 }
1020                 else
1021                 {
1022                         // Either we're finished with our rotate, or we're stamping a copy.
1023                         if (ctrlDown)
1024                         {
1025                                 // Stamp a copy of the selection at the current rotation & bail
1026                                 std::vector<void *> temp;
1027                                 CopyObjects(select, temp);
1028                                 ClearSelected(temp);
1029                                 AddObjectsTo(document.objects, temp);
1030                                 RestorePointsTo(select, toolScratch);
1031                                 return;
1032                         }
1033
1034                         toolPoint[0] = p;
1035                         Global::toolState = TSPoint1;
1036                         SavePointsFrom(select, toolScratch);
1037                 }
1038
1039                 break;
1040         case ToolKeyDown:
1041                 // Reset the selection if shift held down...
1042                 if (shiftDown)
1043                         RestorePointsTo(select, toolScratch);
1044
1045                 break;
1046         case ToolKeyUp:
1047                 // Reset selection when key is let up
1048                 if (!shiftDown)
1049                         RotateHandler(ToolMouseMove, toolPoint[1]);
1050
1051                 break;
1052         case ToolCleanup:
1053                 RestorePointsTo(select, toolScratch);
1054         }
1055 }
1056
1057
1058 void DrawingView::MirrorHandler(int mode, Point p)
1059 {
1060         switch (mode)
1061         {
1062         case ToolMouseDown:
1063                 if (Global::toolState == TSNone)
1064                 {
1065                         toolPoint[0] = p;
1066                         SavePointsFrom(select, toolScratch);
1067                         Global::toolState = TSPoint1;
1068                 }
1069                 else if (Global::toolState == TSPoint1)
1070                         toolPoint[0] = p;
1071                 else
1072                         toolPoint[1] = p;
1073
1074                 break;
1075         case ToolMouseMove:
1076                 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1077                         toolPoint[0] = p;
1078                 else if (Global::toolState == TSPoint2)
1079                 {
1080                         toolPoint[1] = p;
1081
1082                         if (shiftDown)
1083                                 return;
1084
1085                         double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1086                         std::vector<void *>::iterator j = select.begin();
1087                         std::vector<Object>::iterator i = toolScratch.begin();
1088
1089                         for(; i!=toolScratch.end(); i++, j++)
1090                         {
1091                                 Object obj = *i;
1092                                 Point p1 = Geometry::MirrorPointAroundLine(obj.p[0], toolPoint[0], toolPoint[1]);
1093                                 Point p2 = Geometry::MirrorPointAroundLine(obj.p[1], toolPoint[0], toolPoint[1]);
1094                                 Object * obj2 = (Object *)(*j);
1095                                 obj2->p[0] = p1;
1096                                 obj2->p[1] = p2;
1097
1098                                 if (obj.type == OTArc)
1099                                 {
1100                                         // This is 2*mirror angle - obj angle - obj span
1101                                         obj2->angle[0] = (2.0 * angle) - obj.angle[0] - obj.angle[1];
1102
1103                                         if (obj2->angle[0] > TAU)
1104                                                 obj2->angle[0] -= TAU;
1105                                 }
1106                         }
1107                 }
1108
1109                 break;
1110         case ToolMouseUp:
1111                 if (Global::toolState == TSPoint1)
1112                 {
1113                         Global::toolState = TSPoint2;
1114                         // Prevent spurious line from drawing...
1115                         toolPoint[1] = toolPoint[0];
1116                 }
1117                 else if ((Global::toolState == TSPoint2) && shiftDown)
1118                 {
1119                         // Key override is telling us to make a new line, not continue the
1120                         // previous one.
1121                         toolPoint[0] = toolPoint[1];
1122                 }
1123                 else
1124                 {
1125                         // Either we're finished with our rotate, or we're stamping a copy.
1126                         if (ctrlDown)
1127                         {
1128                                 // Stamp a copy of the selection at the current rotation & bail
1129                                 std::vector<void *> temp;
1130                                 CopyObjects(select, temp);
1131                                 ClearSelected(temp);
1132                                 AddObjectsTo(document.objects, temp);
1133                                 RestorePointsTo(select, toolScratch);
1134                                 return;
1135                         }
1136
1137                         toolPoint[0] = p;
1138                         Global::toolState = TSPoint1;
1139                         SavePointsFrom(select, toolScratch);
1140                 }
1141
1142                 break;
1143         case ToolKeyDown:
1144                 // Reset the selection if shift held down...
1145                 if (shiftDown)
1146                         RestorePointsTo(select, toolScratch);
1147
1148                 break;
1149         case ToolKeyUp:
1150                 // Reset selection when key is let up
1151                 if (!shiftDown)
1152                         MirrorHandler(ToolMouseMove, toolPoint[1]);
1153
1154                 break;
1155         case ToolCleanup:
1156                 RestorePointsTo(select, toolScratch);
1157         }
1158 }
1159
1160
1161 void DrawingView::mousePressEvent(QMouseEvent * event)
1162 {
1163         if (event->button() == Qt::LeftButton)
1164         {
1165                 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1166
1167                 // Handle tool processing, if any
1168                 if (Global::tool)
1169                 {
1170                         if (hoveringIntersection)
1171                                 point = intersectionPoint;
1172                         else if (Global::snapToGrid)
1173                                 point = SnapPointToGrid(point);
1174
1175                         //Also, may want to figure out if hovering over a snap point on an object,
1176                         //snap to grid if not.
1177                         // Snap to object point if valid...
1178 //                      if (Global::snapPointIsValid)
1179 //                              point = Global::snapPoint;
1180                         
1181                         ToolHandler(ToolMouseDown, point);
1182                         return;
1183                 }
1184
1185                 // Clear the selection only if CTRL isn't being held on click
1186                 if (!ctrlDown)
1187                         ClearSelected(document.objects);
1188 //                      ClearSelection();
1189
1190                 // If any objects are being hovered on click, add them to the selection
1191                 // & return
1192                 if (numHovered > 0)
1193                 {
1194                         AddHoveredToSelection();
1195                         update();       // needed??
1196                         GetHovered(hover);      // prolly needed
1197
1198                         // Needed for grab & moving objects
1199                         // We do it *after*... why? (doesn't seem to confer any advantage...)
1200                         if (hoveringIntersection)
1201                                 oldPoint = intersectionPoint;
1202                         else if (Global::snapToGrid)
1203                                 oldPoint = SnapPointToGrid(point);
1204
1205                         return;
1206                 }
1207
1208                 // Didn't hit any object and not using a tool, so do a selection rectangle
1209                 Global::selectionInProgress = true;
1210                 Global::selection.setTopLeft(QPointF(point.x, point.y));
1211                 Global::selection.setBottomRight(QPointF(point.x, point.y));
1212         }
1213         else if (event->button() == Qt::MiddleButton)
1214         {
1215                 scrollDrag = true;
1216                 oldPoint = Vector(event->x(), event->y());
1217                 // Should also change the mouse pointer as well...
1218                 setCursor(Qt::SizeAllCursor);
1219         }
1220 }
1221
1222
1223 void DrawingView::mouseMoveEvent(QMouseEvent * event)
1224 {
1225         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1226         Global::selection.setBottomRight(QPointF(point.x, point.y));
1227         // Only needs to be done here, as mouse down is always preceded by movement
1228         Global::snapPointIsValid = false;
1229         hoveringIntersection = false;
1230
1231         // Scrolling...
1232         if (event->buttons() & Qt::MiddleButton)
1233         {
1234                 point = Vector(event->x(), event->y());
1235                 // Since we're using Qt coords for scrolling, we have to adjust them
1236                 // here to conform to Cartesian coords, since the origin is using
1237                 // Cartesian. :-)
1238                 Vector delta(oldPoint, point);
1239                 delta /= Global::zoom;
1240                 delta.y = -delta.y;
1241                 Global::origin -= delta;
1242
1243                 UpdateGridBackground();
1244                 update();
1245                 oldPoint = point;
1246                 return;
1247         }
1248
1249         // If we're doing a selection rect, see if any objects are engulfed by it
1250         // (implies left mouse button held down)
1251         if (Global::selectionInProgress)
1252         {
1253                 CheckObjectBounds();
1254                 update();
1255                 return;
1256         }
1257
1258         // Handle object movement (left button down & over an object)
1259         if ((event->buttons() & Qt::LeftButton) && numHovered && !Global::tool)
1260         {
1261                 if (hoveringIntersection)
1262                         point = intersectionPoint;
1263                 else if (Global::snapToGrid)
1264                         point = SnapPointToGrid(point);
1265
1266                 HandleObjectMovement(point);
1267                 update();
1268                 oldPoint = point;
1269                 return;
1270         }
1271
1272         // Do object hit testing...
1273         bool needUpdate = HitTestObjects(point);
1274
1275         // Check for multi-hover...
1276         if (numHovered > 1)
1277         {
1278                 GetHovered(hover);
1279                 Geometry::Intersects((Object *)hover[0], (Object *)hover[1]);
1280                 int numIntersecting = Global::numIntersectParams;
1281                 double t = Global::intersectParam[0];
1282                 double u = Global::intersectParam[1];
1283
1284                 if (numIntersecting > 0)
1285                 {
1286                         Vector v1 = Geometry::GetPointForParameter((Object *)hover[0], t);
1287                         Vector v2 = Geometry::GetPointForParameter((Object *)hover[1], u);
1288                         QString text = tr("Intersection t=%1 (%3, %4), u=%2 (%5, %6)");
1289                         informativeText = text.arg(t).arg(u).arg(v1.x).arg(v1.y).arg(v2.x).arg(v2.y);
1290
1291                         hoveringIntersection = true;
1292                         intersectionPoint = v1;
1293                 }
1294
1295                 numIntersecting = Global::numIntersectPoints;
1296
1297                 if (numIntersecting > 0)
1298                 {
1299                         Vector v1 = Global::intersectPoint[0];
1300
1301                         if (numIntersecting == 2)
1302                         {
1303                                 Vector v2 = Global::intersectPoint[1];
1304
1305                                 if (Vector::Magnitude(v2, point) < Vector::Magnitude(v1, point))
1306                                         v1 = v2;
1307                         }
1308
1309                         QString text = tr("Intersection <%1, %2>");
1310                         informativeText = text.arg(v1.x).arg(v1.y);
1311                         hoveringIntersection = true;
1312                         intersectionPoint = v1;
1313                 }
1314         }
1315
1316 //this doesn't work down here for some reason... :-P
1317 //could be because the object being moved is part of the intersection, and this is screwing things up. In which case, we need to exclude the moving object somehow from the hit test function...
1318 #if 0
1319         // Handle object movement (left button down & over an object)
1320         if ((event->buttons() & Qt::LeftButton) && numHovered && !Global::tool)
1321         {
1322                 if (hoveringIntersection)
1323                         point = intersectionPoint;
1324                 else if (Global::snapToGrid)
1325                         point = SnapPointToGrid(point);
1326
1327                 HandleObjectMovement(point);
1328                 update();
1329                 oldPoint = point;
1330                 return;
1331         }
1332 #endif
1333
1334         // Do tool handling, if any are active...
1335         if (Global::tool)
1336         {
1337                 if (hoveringIntersection)
1338                         point = intersectionPoint;
1339                 else if (Global::snapToGrid)
1340                         point = SnapPointToGrid(point);
1341
1342                 ToolHandler(ToolMouseMove, point);
1343         }
1344
1345         // This is used to draw the tool crosshair...
1346         oldPoint = point;
1347
1348         if (needUpdate || Global::tool)
1349                 update();
1350 }
1351
1352
1353 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
1354 {
1355         if (event->button() == Qt::LeftButton)
1356         {
1357 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
1358 //could set it up to use the document's update function (assumes that all object
1359 //updates are being reported correctly:
1360 //              if (document.NeedsUpdate())
1361                 // Do an update if collided with at least *one* object in the document
1362 //              if (collided)
1363                         update();
1364
1365                 if (Global::tool)
1366                 {
1367                         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1368                         ToolHandler(ToolMouseUp, point);
1369                         return;
1370                 }
1371
1372                 if (Global::selectionInProgress)
1373                         Global::selectionInProgress = false;
1374
1375                 informativeText.clear();
1376 // Should we be doing this automagically? Hmm...
1377                 // Clear our vectors
1378                 select.clear();
1379                 hover.clear();
1380
1381                 // Scoop 'em up
1382                 std::vector<void *>::iterator i;
1383
1384                 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1385                 {
1386                         if (((Object *)(*i))->selected)
1387                                 select.push_back(*i);
1388
1389 //hmm, this is no good, too late to do any good :-P
1390 //                      if ((*i)->hovered)
1391 //                              hover.push_back(*i);
1392                 }
1393         }
1394         else if (event->button() == Qt::MiddleButton)
1395         {
1396                 scrollDrag = false;
1397                 setCursor(Qt::ArrowCursor);
1398         }
1399 }
1400
1401
1402 void DrawingView::wheelEvent(QWheelEvent * event)
1403 {
1404         double zoomFactor = 1.25;
1405         QSize sizeWin = size();
1406         Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
1407         center = Painter::QtToCartesianCoords(center);
1408
1409         // This is not centering for some reason. Need to figure out why. :-/
1410         if (event->delta() > 0)
1411         {
1412                 Vector newOrigin = center - ((center - Global::origin) / zoomFactor);
1413                 Global::origin = newOrigin;
1414                 Global::zoom *= zoomFactor;
1415         }
1416         else
1417         {
1418                 Vector newOrigin = center + ((-center + Global::origin) * zoomFactor);
1419                 Global::origin = newOrigin;
1420                 Global::zoom /= zoomFactor;
1421         }
1422
1423 //      Global::gridSpacing = gridPixels / Painter::zoom;
1424 //      UpdateGridBackground();
1425         SetGridSize(Global::gridSpacing * Global::zoom);
1426         update();
1427 //      zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Global::gridSpacing));
1428 }
1429
1430
1431 void DrawingView::keyPressEvent(QKeyEvent * event)
1432 {
1433         bool oldShift = shiftDown;
1434         bool oldCtrl = ctrlDown;
1435
1436         if (event->key() == Qt::Key_Shift)
1437                 shiftDown = true;
1438         else if (event->key() == Qt::Key_Control)
1439                 ctrlDown = true;
1440
1441         if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1442         {
1443                 if (Global::tool)
1444                         ToolHandler(ToolKeyDown, Point(0, 0));
1445
1446                 update();
1447         }
1448 }
1449
1450
1451 void DrawingView::keyReleaseEvent(QKeyEvent * event)
1452 {
1453         bool oldShift = shiftDown;
1454         bool oldCtrl = ctrlDown;
1455
1456         if (event->key() == Qt::Key_Shift)
1457                 shiftDown = false;
1458         else if (event->key() == Qt::Key_Control)
1459                 ctrlDown = false;
1460
1461         if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1462         {
1463                 if (Global::tool)
1464                         ToolHandler(ToolKeyUp, Point(0, 0));
1465
1466                 update();
1467         }
1468 }
1469
1470
1471 //
1472 // This looks strange, but it's really quite simple: We want a point that's
1473 // more than half-way to the next grid point to snap there while conversely we
1474 // want a point that's less than half-way to to the next grid point then snap
1475 // to the one before it. So we add half of the grid spacing to the point, then
1476 // divide by it so that we can remove the fractional part, then multiply it
1477 // back to get back to the correct answer.
1478 //
1479 Point DrawingView::SnapPointToGrid(Point point)
1480 {
1481         point += Global::gridSpacing / 2.0;             // *This* adds to Z!!!
1482         point /= Global::gridSpacing;
1483         point.x = floor(point.x);//need to fix this for negative numbers...
1484         point.y = floor(point.y);
1485         point.z = 0;                                    // Make *sure* Z doesn't go anywhere!!!
1486         point *= Global::gridSpacing;
1487         return point;
1488 }
1489
1490
1491 Rect DrawingView::GetObjectExtents(Object * obj)
1492 {
1493         // Default to empty rect, if object checks below fail for some reason
1494         Rect rect;
1495
1496         switch (obj->type)
1497         {
1498         case OTLine:
1499         {
1500                 rect = Rect(obj->p[0], obj->p[1]);
1501                 break;
1502         }
1503         case OTCircle:
1504         {
1505                 rect = Rect(obj->p[0], obj->p[0]);
1506                 rect.Expand(obj->radius[0]);
1507                 break;
1508         }
1509         case OTArc:
1510         {
1511                 Arc * a = (Arc *)obj;
1512
1513                 double start = a->angle[0];
1514                 double end = start + a->angle[1];
1515                 rect = Rect(Point(cos(start), sin(start)), Point(cos(end), sin(end)));
1516
1517                 // If the end of the arc is before the beginning, add 360 degrees to it
1518                 if (end < start)
1519                         end += TAU;
1520
1521                 // Adjust the bounds depending on which axes are crossed
1522                 if ((start < QTR_TAU) && (end > QTR_TAU))
1523                         rect.t = 1.0;
1524
1525                 if ((start < HALF_TAU) && (end > HALF_TAU))
1526                         rect.l = -1.0;
1527
1528                 if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
1529                         rect.b = -1.0;
1530
1531                 if ((start < TAU) && (end > TAU))
1532                         rect.r = 1.0;
1533
1534                 if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
1535                         rect.t = 1.0;
1536
1537                 if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
1538                         rect.l = -1.0;
1539
1540                 if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
1541                         rect.b = -1.0;
1542
1543                 rect *= a->radius[0];
1544                 rect.Translate(a->p[0]);
1545
1546                 break;
1547         }
1548         case OTContainer:
1549         {
1550                 Container * c = (Container *)obj;
1551                 std::vector<void *>::iterator i = c->objects.begin();
1552                 rect = GetObjectExtents((Object *)*i);
1553                 i++;
1554
1555                 for(; i!=c->objects.end(); i++)
1556                         rect |= GetObjectExtents((Object *)*i);
1557         }
1558         default:
1559                 break;
1560         }
1561
1562         return rect;
1563 }
1564
1565
1566 void DrawingView::CheckObjectBounds(void)
1567 {
1568         std::vector<void *>::iterator i;
1569
1570         for(i=document.objects.begin(); i!=document.objects.end(); i++)
1571         {
1572                 Object * obj = (Object *)(*i);
1573                 obj->selected = false;
1574
1575                 switch (obj->type)
1576                 {
1577                 case OTLine:
1578                 {
1579                         Line * l = (Line *)obj;
1580
1581                         if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
1582                                 l->selected = true;
1583
1584                         break;
1585                 }
1586                 case OTCircle:
1587                 {
1588                         Circle * c = (Circle *)obj;
1589
1590                         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]))
1591                                 c->selected = true;
1592
1593                         break;
1594                 }
1595                 case OTArc:
1596                 {
1597                         Arc * a = (Arc *)obj;
1598
1599                         double start = a->angle[0];
1600                         double end = start + a->angle[1];
1601                         QPointF p1(cos(start), sin(start));
1602                         QPointF p2(cos(end), sin(end));
1603                         QRectF bounds(p1, p2);
1604
1605 #if 1
1606                         // Swap X/Y coordinates if they're backwards...
1607                         if (bounds.left() > bounds.right())
1608                         {
1609                                 double temp = bounds.left();
1610                                 bounds.setLeft(bounds.right());
1611                                 bounds.setRight(temp);
1612                         }
1613
1614                         if (bounds.bottom() > bounds.top())
1615                         {
1616                                 double temp = bounds.bottom();
1617                                 bounds.setBottom(bounds.top());
1618                                 bounds.setTop(temp);
1619                         }
1620 #else
1621                         // Doesn't work as advertised! For shame!
1622                         bounds = bounds.normalized();
1623 #endif
1624
1625                         // If the end of the arc is before the beginning, add 360 degrees to it
1626                         if (end < start)
1627                                 end += TAU;
1628
1629                         // Adjust the bounds depending on which axes are crossed
1630                         if ((start < QTR_TAU) && (end > QTR_TAU))
1631                                 bounds.setTop(1.0);
1632
1633                         if ((start < HALF_TAU) && (end > HALF_TAU))
1634                                 bounds.setLeft(-1.0);
1635
1636                         if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU))
1637                                 bounds.setBottom(-1.0);
1638
1639                         if ((start < TAU) && (end > TAU))
1640                                 bounds.setRight(1.0);
1641
1642                         if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU)))
1643                                 bounds.setTop(1.0);
1644
1645                         if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU)))
1646                                 bounds.setLeft(-1.0);
1647
1648                         if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU)))
1649                                 bounds.setBottom(-1.0);
1650
1651                         bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
1652                         bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
1653                         bounds.translate(a->p[0].x, a->p[0].y);
1654
1655                         if (Global::selection.contains(bounds))
1656                                 a->selected = true;
1657
1658                         break;
1659                 }
1660                 default:
1661                         break;
1662                 }
1663         }
1664 }
1665
1666
1667 bool DrawingView::HitTestObjects(Point point)
1668 {
1669         std::vector<void *>::iterator i;
1670         numHovered = 0;
1671         bool needUpdate = false;
1672
1673         for(i=document.objects.begin(); i!=document.objects.end(); i++)
1674         {
1675                 Object * obj = (Object *)(*i);
1676
1677                 if (HitTest(obj, point))
1678                         needUpdate = true;
1679 #if 0
1680                 switch (obj->type)
1681                 {
1682                 case OTLine:
1683                 {
1684                         bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
1685                         obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
1686                         Vector lineSegment = obj->p[1] - obj->p[0];
1687                         Vector v1 = point - obj->p[0];
1688                         Vector v2 = point - obj->p[1];
1689                         double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
1690                         double distance;
1691
1692                         if (t < 0.0)
1693                                 distance = v1.Magnitude();
1694                         else if (t > 1.0)
1695                                 distance = v2.Magnitude();
1696                         else
1697                                 // distance = ?Det?(ls, v1) / |ls|
1698                                 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
1699                                         / lineSegment.Magnitude());
1700
1701                         if ((v1.Magnitude() * Global::zoom) < 8.0)
1702                                 obj->hitPoint[0] = true;
1703                         else if ((v2.Magnitude() * Global::zoom) < 8.0)
1704                                 obj->hitPoint[1] = true;
1705                         else if ((distance * Global::zoom) < 5.0)
1706                                 obj->hitObject = true;
1707
1708                         obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
1709
1710                         if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
1711                                 needUpdate = true;
1712
1713                         break;
1714                 }
1715                 case OTCircle:
1716                 {
1717                         bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
1718                         obj->hitPoint[0] = obj->hitObject = false;
1719                         double length = Vector::Magnitude(obj->p[0], point);
1720
1721                         if ((length * Global::zoom) < 8.0)
1722                                 obj->hitPoint[0] = true;
1723                         else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
1724                                 obj->hitObject = true;
1725
1726                         obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
1727
1728                         if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
1729                                 needUpdate = true;
1730
1731                         break;
1732                 }
1733                 case OTArc:
1734                 {
1735                         bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
1736                         obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
1737                         double length = Vector::Magnitude(obj->p[0], point);
1738                         double angle = Vector::Angle(obj->p[0], point);
1739
1740                         // Make sure we get the angle in the correct spot
1741                         if (angle < obj->angle[0])
1742                                 angle += TAU;
1743
1744                         // Get the span that we're pointing at...
1745                         double span = angle - obj->angle[0];
1746
1747                         // N.B.: Still need to hit test the arc start & arc span handles...
1748                         double spanAngle = obj->angle[0] + obj->angle[1];
1749                         Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
1750                         Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
1751                         double length2 = Vector::Magnitude(point, handle1);
1752                         double length3 = Vector::Magnitude(point, handle2);
1753
1754                         if ((length * Global::zoom) < 8.0)
1755                                 obj->hitPoint[0] = true;
1756                         else if ((length2 * Global::zoom) < 8.0)
1757                                 obj->hitPoint[1] = true;
1758                         else if ((length3 * Global::zoom) < 8.0)
1759                                 obj->hitPoint[2] = true;
1760                         else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
1761                                 obj->hitObject = true;
1762
1763                         obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
1764
1765                         if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
1766                                 needUpdate = true;
1767
1768                         break;
1769                 }
1770                 case OTContainer:
1771                 {
1772                         // Containers must be recursively tested...
1773                         Container * c = (Container *)obj;
1774                         std::vector<void *>::iterator i;
1775
1776                         for(i=c->objects.begin(); i!=c->objects.end(); i++)
1777                         {
1778                                 
1779                         }
1780                 }
1781                 default:
1782                         break;
1783                 }
1784 #endif
1785
1786                 if (obj->hovered)
1787                 {
1788                         numHovered++;
1789 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
1790                         emit(ObjectHovered(obj));
1791                 }
1792         }
1793
1794         return needUpdate;
1795 }
1796
1797
1798 bool DrawingView::HitTest(Object * obj, Point point)
1799 {
1800         bool needUpdate = false;
1801
1802         switch (obj->type)
1803         {
1804         case OTLine:
1805         {
1806                 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
1807                 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
1808                 Vector lineSegment = obj->p[1] - obj->p[0];
1809                 Vector v1 = point - obj->p[0];
1810                 Vector v2 = point - obj->p[1];
1811                 double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
1812                 double distance;
1813
1814                 if (t < 0.0)
1815                         distance = v1.Magnitude();
1816                 else if (t > 1.0)
1817                         distance = v2.Magnitude();
1818                 else
1819                         // distance = ?Det?(ls, v1) / |ls|
1820                         distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
1821                                 / lineSegment.Magnitude());
1822
1823                 if ((v1.Magnitude() * Global::zoom) < 8.0)
1824                         obj->hitPoint[0] = true;
1825                 else if ((v2.Magnitude() * Global::zoom) < 8.0)
1826                         obj->hitPoint[1] = true;
1827                 else if ((distance * Global::zoom) < 5.0)
1828                         obj->hitObject = true;
1829
1830                 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
1831
1832                 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
1833                         needUpdate = true;
1834
1835                 break;
1836         }
1837         case OTCircle:
1838         {
1839                 bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
1840                 obj->hitPoint[0] = obj->hitObject = false;
1841                 double length = Vector::Magnitude(obj->p[0], point);
1842
1843                 if ((length * Global::zoom) < 8.0)
1844                         obj->hitPoint[0] = true;
1845                 else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
1846                         obj->hitObject = true;
1847
1848                 obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
1849
1850                 if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
1851                         needUpdate = true;
1852
1853                 break;
1854         }
1855         case OTArc:
1856         {
1857                 bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
1858                 obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
1859                 double length = Vector::Magnitude(obj->p[0], point);
1860                 double angle = Vector::Angle(obj->p[0], point);
1861
1862                 // Make sure we get the angle in the correct spot
1863                 if (angle < obj->angle[0])
1864                         angle += TAU;
1865
1866                 // Get the span that we're pointing at...
1867                 double span = angle - obj->angle[0];
1868
1869                 // N.B.: Still need to hit test the arc start & arc span handles...
1870                 double spanAngle = obj->angle[0] + obj->angle[1];
1871                 Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
1872                 Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
1873                 double length2 = Vector::Magnitude(point, handle1);
1874                 double length3 = Vector::Magnitude(point, handle2);
1875
1876                 if ((length * Global::zoom) < 8.0)
1877                         obj->hitPoint[0] = true;
1878                 else if ((length2 * Global::zoom) < 8.0)
1879                         obj->hitPoint[1] = true;
1880                 else if ((length3 * Global::zoom) < 8.0)
1881                         obj->hitPoint[2] = true;
1882                 else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
1883                         obj->hitObject = true;
1884
1885                 obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
1886
1887                 if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
1888                         needUpdate = true;
1889
1890                 break;
1891         }
1892         case OTContainer:
1893         {
1894                 // Containers must be recursively tested...
1895                 Container * c = (Container *)obj;
1896                 c->hitObject = false;
1897                 c->hovered = false;
1898                 std::vector<void *>::iterator i;
1899
1900                 for(i=c->objects.begin(); i!=c->objects.end(); i++)
1901                 {
1902                         Object * cObj = (Object *)*i;
1903
1904                         if (HitTest(cObj, point))
1905                                 needUpdate = true;
1906
1907                         if (cObj->hitObject == true)
1908                                 c->hitObject = true;
1909
1910                         if (cObj->hovered == true)
1911                                 c->hovered = true;
1912                 }
1913
1914                 break;
1915         }
1916         default:
1917                 break;
1918         }
1919
1920         return needUpdate;
1921 }
1922
1923
1924 void DrawingView::HandleObjectMovement(Point point)
1925 {
1926         Point delta = point - oldPoint;
1927 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
1928         Object * obj = (Object *)hover[0];
1929 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
1930 //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"));
1931
1932         switch (obj->type)
1933         {
1934         case OTLine:
1935                 if (obj->hitPoint[0])
1936                         obj->p[0] = point;
1937                 else if (obj->hitPoint[1])
1938                         obj->p[1] = point;
1939                 else if (obj->hitObject)
1940                 {
1941                         obj->p[0] += delta;
1942                         obj->p[1] += delta;
1943                 }
1944
1945                 break;
1946         case OTCircle:
1947                 if (obj->hitPoint[0])
1948                         obj->p[0] = point;
1949                 else if (obj->hitObject)
1950                 {
1951 //this doesn't work. we need to save this on mouse down for this to work correctly!
1952 //                      double oldRadius = obj->radius[0];
1953                         obj->radius[0] = Vector::Magnitude(obj->p[0], point);
1954
1955                         QString text = QObject::tr("Radius: %1");//\nScale: %2%");
1956                         informativeText = text.arg(obj->radius[0], 0, 'd', 4);//.arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0);
1957                 }
1958
1959                 break;
1960         case OTArc:
1961                 if (obj->hitPoint[0])
1962                         obj->p[0] = point;
1963                 else if (obj->hitPoint[1])
1964                 {
1965                         // Change the Arc's span (handle #1)
1966                         if (shiftDown)
1967                         {
1968                                 double angle = Vector::Angle(obj->p[0], point);
1969                                 double delta = angle - obj->angle[0];
1970
1971                                 if (delta < 0)
1972                                         delta += TAU;
1973
1974                                 obj->angle[1] -= delta;
1975                                 obj->angle[0] = angle;
1976
1977                                 if (obj->angle[1] < 0)
1978                                         obj->angle[1] += TAU;
1979
1980                                 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
1981                                 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);
1982                                 return;
1983                         }
1984
1985                         double angle = Vector::Angle(obj->p[0], point);
1986                         obj->angle[0] = angle;
1987                         QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
1988                         informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 4);
1989                 }
1990                 else if (obj->hitPoint[2])
1991                 {
1992                         // Change the Arc's span (handle #2)
1993                         if (shiftDown)
1994                         {
1995                                 double angle = Vector::Angle(obj->p[0], point);
1996                                 obj->angle[1] = angle - obj->angle[0];
1997
1998                                 if (obj->angle[1] < 0)
1999                                         obj->angle[1] += TAU;
2000
2001                                 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
2002                                 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);
2003                                 return;
2004                         }
2005
2006                         double angle = Vector::Angle(obj->p[0], point);
2007                         obj->angle[0] = angle - obj->angle[1];
2008
2009                         if (obj->angle[0] < 0)
2010                                 obj->angle[0] += TAU;
2011
2012                         QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
2013                         informativeText = text.arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 4);
2014                 }
2015                 else if (obj->hitObject)
2016                 {
2017                         if (shiftDown)
2018                         {
2019                                 return;
2020                         }
2021
2022                         obj->radius[0] = Vector::Magnitude(obj->p[0], point);
2023                         QString text = QObject::tr("Radius: %1");
2024                         informativeText = text.arg(obj->radius[0], 0, 'd', 4);
2025                 }
2026
2027                 break;
2028         case OTContainer:
2029                 // This is shitty, but works for now until I can code up something
2030                 // nicer :-)
2031                 TranslateObject(obj, delta);
2032
2033                 break;
2034         default:
2035                 break;
2036         }
2037 }
2038