]> Shamusworld >> Repos - architektonas/blob - src/drawingview.cpp
fff56da8ef77bbd2e78a0f28a0278d82339fad4e
[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 //
24
25 // Uncomment this for debugging...
26 //#define DEBUG
27 //#define DEBUGFOO                              // Various tool debugging...
28 //#define DEBUGTP                               // Toolpalette debugging...
29
30 #include "drawingview.h"
31
32 #include <stdint.h>
33 #include "geometry.h"
34 #include "global.h"
35 #include "mathconstants.h"
36 #include "painter.h"
37 #include "structs.h"
38 #include "utils.h"
39
40
41 #define BACKGROUND_MAX_SIZE     512
42
43 // Class variable
44 //Container DrawingView::document(Vector(0, 0));
45
46
47 DrawingView::DrawingView(QWidget * parent/*= NULL*/): QWidget(parent),
48         // The value in the settings file will override this.
49         useAntialiasing(true), numSelected(0), numHovered(0), shiftDown(false),
50         ctrlDown(false),
51         gridBackground(BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE),
52         scale(1.0), offsetX(-10), offsetY(-10),// document(Vector(0, 0)),
53         gridPixels(0), collided(false)//, toolAction(NULL)
54 {
55 //      document.isTopLevelContainer = true;
56 //wtf? doesn't work except in c++11???  document = { 0 };
57         setBackgroundRole(QPalette::Base);
58         setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
59
60         Global::gridSpacing = 12.0;             // In base units (inch is default)
61
62 #if 0
63         Line * line = new Line(Vector(5, 5), Vector(50, 40), &document);
64         document.Add(line);
65         document.Add(new Line(Vector(50, 40), Vector(10, 83), &document));
66         document.Add(new Line(Vector(10, 83), Vector(17, 2), &document));
67         document.Add(new Circle(Vector(100, 100), 36, &document));
68         document.Add(new Circle(Vector(50, 150), 49, &document));
69         document.Add(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3, &document)),
70         document.Add(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5, &document));
71 #if 1
72         Dimension * dimension = new Dimension(Vector(0, 0), Vector(0, 0), DTLinear, &document);
73         line->SetDimensionOnLine(dimension);
74         document.Add(dimension);
75 #else
76         // Alternate way to do the above...
77         line->SetDimensionOnLine();
78 #endif
79 #else
80         Line * line = new Line;//(Vector(5, 5), Vector(50, 40), &document);
81         line->p[0] = Vector(5, 5);
82         line->p[1] = Vector(50, 40);
83         line->type = OTLine;
84         line->thickness = 2.0;
85         line->style = LSDash;
86         line->color = 0xFF7F00;
87         document.objects.push_back(line);
88         document.objects.push_back(new Line(Vector(50, 40), Vector(10, 83)));
89         document.objects.push_back(new Line(Vector(10, 83), Vector(17, 2)));
90         document.objects.push_back(new Circle(Vector(100, 100), 36));
91         document.objects.push_back(new Circle(Vector(50, 150), 49));
92         document.objects.push_back(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3)),
93         document.objects.push_back(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5));
94         document.objects.push_back(new Dimension(Vector(50, 40), Vector(5, 5)));
95         document.objects.push_back(new Text(Vector(10, 83), "Here is some awesome text!"));
96 #endif
97
98 /*
99 Here we set the grid size in pixels--12 in this case. Initially, we have our
100 zoom set to make this represent 12 inches at a zoom factor of 25%. (This is
101 arbitrary.) So, to be able to decouple the grid size from the zoom, we need
102 to be able to set the size of the background grid (which we do here at an
103 arbitrary 12 pixels) to anything we want (within reason, of course :-).
104
105 The drawing enforces the grid spacing through the drawing->gridSpacing variable.
106
107         drawing->gridSpacing = 12.0 / Global::zoom;
108
109 Global::zoom is the zoom factor for the drawing, and all mouse clicks are
110 translated to Cartesian coordinates through this. (Initially, Global::zoom is
111 set to 1.0. SCREEN_ZOOM is set to 1.0/4.0.)
112
113 Really, the 100% zoom level can be set at *any* zoom level, it's more of a
114 convenience function than any measure of absolutes. Doing things that way we
115 could rid ourselves of the whole SCREEN_ZOOM parameter and all the attendant
116 shittiness that comes with it.
117
118 However, it seems that SCREEN_ZOOM is used to make text and arrow sizes show up
119 a certain way, which means we should probably create something else in those
120 objects to take its place--like some kind of scale factor. This would seem to
121 imply that certain point sizes actually *do* tie things like fonts to absolute
122 sizes on the screen, but not necessarily because you could have an inch scale
123 with text that is quite small relative to other objects on the screen, which
124 currently you have to zoom in to see (and which blows up the text). Point sizes
125 in an application like this are a bit meaningless; even though an inch is an
126 inch regardless of the zoom level a piece of text can be larger or smaller than
127 this. Maybe this is the case for having a base unit and basing point sizes off
128 of that.
129
130 Here's what's been figured out. Global::zoom is simply the ratio of pixels to
131 base units. What that means is that if you have a 12px grid with a 6" grid size
132 (& base unit of "inches"), Global::zoom becomes 12px / 6" = 2.0 px/in.
133
134 Dimensions now have a "size" parameter to set their absolute size in relation
135 to the base unit. ATM, the arrows are drawn in pixels, but also scaled by
136 Global::zoom *and* size. Same with the dimension text; it's drawn at 10pt and
137 scaled the same way as the arrowheads.
138
139 Need a way to scale line widths as well. :-/ Shouldn't be too difficult, just
140 need a thickness parameter similar to the "size" param for dimensions. (And now
141 we do! :-)
142
143 */
144         SetGridSize(12);        // This is in pixels
145 }
146
147
148 #if 0
149 void DrawingView::SetToolActive(Action * action)
150 {
151         if (action != NULL)
152         {
153                 toolAction = action;
154                 connect(toolAction, SIGNAL(ObjectReady(Object *)), this,
155                         SLOT(AddNewObjectToDocument(Object *)));
156                 connect(toolAction, SIGNAL(NeedRefresh()), this, SLOT(HandleActionUpdate()));
157         }
158 }
159 #endif
160
161
162 void DrawingView::SetGridSize(uint32_t size)
163 {
164         // Sanity check
165         if (size == gridPixels)
166                 return;
167
168         // Recreate the background bitmap
169         gridPixels = size;
170         QPainter pmp(&gridBackground);
171         pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
172         pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
173
174         for(int i=0; i<(BACKGROUND_MAX_SIZE-1); i+=gridPixels)
175         {
176                 pmp.drawLine(i, 0, i, BACKGROUND_MAX_SIZE - 1);
177                 pmp.drawLine(0, i, BACKGROUND_MAX_SIZE - 1, i);
178         }
179
180         pmp.end();
181
182         // Set up new BG brush & zoom level (pixels per base unit)
183 //      Painter::zoom = gridPixels / gridSpacing;
184         Global::zoom = gridPixels / Global::gridSpacing;
185         UpdateGridBackground();
186 }
187
188
189 void DrawingView::UpdateGridBackground(void)
190 {
191         // Transform the origin to Qt coordinates
192         Vector pixmapOrigin = Painter::CartesianToQtCoords(Vector());
193         int x = (int)pixmapOrigin.x;
194         int y = (int)pixmapOrigin.y;
195         // Use mod arithmetic to grab the correct swatch of background
196 /*
197 Negative numbers still screw it up... Need to think about what we're
198 trying to do here. The fact that it worked with 72 seems to have been pure luck.
199 It seems the problem is negative numbers: We can't let that happen.
200 When taking away the zero, it pops over 1 px at zero, then goes about 1/2 a
201 grid at x<0.
202
203 The bitmap looks like this:
204
205 +---+---+---+---+---
206 |   |   |   |   |
207 |   |   |   |   |
208 +---+---+---+---+---
209 |   |   |   |   |
210 |   |   |   |   |
211 |   |   |   |   |
212
213 @ x = 1, we want it to look like:
214
215 -+---+---+---+---+---
216  |   |   |   |   |
217  |   |   |   |   |
218 -+---+---+---+---+---
219  |   |   |   |   |
220  |   |   |   |   |
221  |   |   |   |   |
222
223 Which means we need to grab the sample from x = 3. @ x = -1:
224
225 ---+---+---+---+---
226    |   |   |   |
227    |   |   |   |
228 ---+---+---+---+---
229    |   |   |   |
230    |   |   |   |
231    |   |   |   |
232
233 Which means we need to grab the sample from x = 1. Which means we have to take
234 the mirror of the modulus of gridPixels.
235
236 Doing a mod of a negative number is problematic: 1st, the compiler converts the
237 negative number to an unsigned int, then it does the mod. Gets you wrong answers
238 most of the time, unless you use a power of 2. :-P So what we do here is just
239 take the modulus of the negation, which means we don't have to worry about
240 mirroring it later.
241
242 The positive case looks gruesome (and it is) but it boils down to this: We take
243 the modulus of the X coordinate, then mirror it by subtraction from the
244 maximum (in this case, gridPixels). This gives us a number in the range of 1 to
245 gridPixels. But we need the case where the result equalling gridPixels to be
246 zero; so we do another modulus operation on the result to achieve this.
247 */
248         if (x < 0)
249                 x = -x % gridPixels;
250         else
251                 x = (gridPixels - (x % gridPixels)) % gridPixels;
252
253         if (y < 0)
254                 y = -y % gridPixels;
255         else
256                 y = (gridPixels - (y % gridPixels)) % gridPixels;
257
258         // Here we grab a section of the bigger pixmap, so that the background
259         // *looks* like it's scrolling...
260         QPixmap pm = gridBackground.copy(x, y, gridPixels, gridPixels);
261         QPalette pal = palette();
262         pal.setBrush(backgroundRole(), QBrush(pm));
263         setAutoFillBackground(true);
264         setPalette(pal);
265 }
266
267
268 void DrawingView::SetCurrentLayer(int layer)
269 {
270         Global::currentLayer = layer;
271 //printf("DrawingView::CurrentLayer = %i\n", layer);
272 }
273
274
275 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
276 {
277         // This is undoing the transform, e.g. going from client coords to local coords.
278         // In essence, the height - y is height + (y * -1), the (y * -1) term doing the
279         // conversion of the y-axis from increasing bottom to top.
280         return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
281 }
282
283
284 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
285 {
286         // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
287         // No voodoo here, it's just grouped wrong to see it. It should be:
288         // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive
289         return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
290 }
291
292
293 void DrawingView::paintEvent(QPaintEvent * /*event*/)
294 {
295         QPainter qtPainter(this);
296         Painter painter(&qtPainter);
297
298         if (useAntialiasing)
299                 qtPainter.setRenderHint(QPainter::Antialiasing);
300
301         Global::viewportHeight = size().height();
302
303         // Draw coordinate axes
304         painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
305         painter.DrawLine(0, -16384, 0, 16384);
306         painter.DrawLine(-16384, 0, 16384, 0);
307
308         // Do object rendering...
309         RenderObjects(&painter, document.objects);
310
311         // Do tool rendering, if any...
312         if (Global::tool)
313         {
314                 painter.SetPen(QPen(QColor(200, 100, 0, 255), 1.0, Qt::DashLine));
315                 painter.DrawCrosshair(oldPoint);
316                 ToolDraw(&painter);
317         }
318
319         // Do selection rectangle rendering, if any
320         if (Global::selectionInProgress)
321         {
322                 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
323                 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
324                 painter.DrawRect(Global::selection);
325         }
326
327         if (!informativeText.isEmpty())
328                 painter.DrawInformativeText(informativeText);
329 }
330
331
332 //
333 // Renders objects in the passed in vector
334 //
335 void DrawingView::RenderObjects(Painter * painter, std::vector<void *> & v)
336 {
337         std::vector<void *>::iterator i;
338
339         for(i=v.begin(); i!=v.end(); i++)
340         {
341                 Object * obj = (Object *)(*i);
342                 float scaledThickness = Global::scale * obj->thickness;
343
344                 if ((Global::tool == TTRotate) && ctrlDown && obj->selected)
345                 {
346                         painter->SetPen(0x00FF00, 2.0, LSSolid);
347                 }
348                 else
349                 {
350                         painter->SetPen(obj->color, Global::zoom * scaledThickness, obj->style);
351                         painter->SetBrush(obj->color);
352
353                         if (obj->selected || obj->hitObject)
354                                 painter->SetPen(0xFF0000, Global::zoom * scaledThickness, LSDash);
355                 }
356
357                 switch (obj->type)
358                 {
359                 case OTLine:
360                         painter->DrawLine(obj->p[0], obj->p[1]);
361
362                         if (obj->hitPoint[0])
363                                 painter->DrawHandle(obj->p[0]);
364
365                         if (obj->hitPoint[1])
366                                 painter->DrawHandle(obj->p[1]);
367
368                         break;
369                 case OTCircle:
370                         painter->SetBrush(QBrush(Qt::NoBrush));
371                         painter->DrawEllipse(obj->p[0], obj->radius[0], obj->radius[0]);
372
373                         if (obj->hitPoint[0])
374                                 painter->DrawHandle(obj->p[0]);
375
376                         break;
377                 case OTArc:
378                         painter->DrawArc(obj->p[0], obj->radius[0], obj->angle[0], obj->angle[1]);
379
380                         if (obj->hitPoint[0])
381                                 painter->DrawHandle(obj->p[0]);
382
383                         if (obj->hitPoint[1])
384                                 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]));
385
386                         if (obj->hitPoint[2])
387                                 painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0] + obj->angle[1]), sin(obj->angle[0] + obj->angle[1])) * obj->radius[0]));
388
389                         break;
390                 case OTDimension:
391                 {
392                         Dimension * d = (Dimension *)obj;
393
394                         Vector v(d->p[0], d->p[1]);
395                         double angle = v.Angle();
396                         Vector unit = v.Unit();
397                         Vector linePt1 = d->p[0], linePt2 = d->p[1];
398                         Vector ortho;
399                         double x1, y1, length;
400
401                         if (d->subtype == DTLinearVert)
402                         {
403                                 if ((angle < 0) || (angle > PI))
404                                 {
405                                         x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
406                                         y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
407                                         ortho = Vector(1.0, 0);
408                                         angle = PI3_OVER_2;
409                                 }
410                                 else
411                                 {
412                                         x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
413                                         y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
414                                         ortho = Vector(-1.0, 0);
415                                         angle = PI_OVER_2;
416                                 }
417
418                                 linePt1.x = linePt2.x = x1;
419                                 length = fabs(d->p[0].y - d->p[1].y);
420                         }
421                         else if (d->subtype == DTLinearHorz)
422                         {
423                                 if ((angle < PI_OVER_2) || (angle > PI3_OVER_2))
424                                 {
425                                         x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
426                                         y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
427                                         ortho = Vector(0, 1.0);
428                                         angle = 0;
429                                 }
430                                 else
431                                 {
432                                         x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
433                                         y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
434                                         ortho = Vector(0, -1.0);
435                                         angle = PI;
436                                 }
437
438                                 linePt1.y = linePt2.y = y1;
439                                 length = fabs(d->p[0].x - d->p[1].x);
440                         }
441                         else if (d->subtype == DTLinear)
442                         {
443                                 angle = Vector(linePt1, linePt2).Angle();
444                                 ortho = Vector::Normal(linePt1, linePt2);
445                                 length = v.Magnitude();
446                         }
447
448                         unit = Vector(linePt1, linePt2).Unit();
449
450                         Point p1 = linePt1 + (ortho * 10.0 * scaledThickness);
451                         Point p2 = linePt2 + (ortho * 10.0 * scaledThickness);
452                         Point p3 = linePt1 + (ortho * 16.0 * scaledThickness);
453                         Point p4 = linePt2 + (ortho * 16.0 * scaledThickness);
454                         Point p5 = d->p[0] + (ortho * 4.0 * scaledThickness);
455                         Point p6 = d->p[1] + (ortho * 4.0 * scaledThickness);
456
457                 /*
458                 The numbers hardcoded into here, what are they?
459                 I believe they are pixels.
460                 */
461                         // Draw extension lines (if certain type)
462                         painter->DrawLine(p3, p5);
463                         painter->DrawLine(p4, p6);
464
465                         // Calculate whether or not the arrowheads are too crowded to put inside
466                         // the extension lines. 9.0 is the length of the arrowhead.
467                         double t = Geometry::ParameterOfLineAndPoint(linePt1, linePt2, linePt2 - (unit * 9.0 * scaledThickness));
468                 //printf("Dimension::Draw(): t = %lf\n", t);
469
470                         // On the screen, it's acting like this is actually 58%...
471                         // This is correct, we want it to happen at > 50%
472                         if (t > 0.58)
473                         {
474                                 // Draw main dimension line + arrowheads
475                                 painter->DrawLine(p1, p2);
476                                 painter->DrawArrowhead(p1, p2, scaledThickness);
477                                 painter->DrawArrowhead(p2, p1, scaledThickness);
478                         }
479                         else
480                         {
481                                 // Draw outside arrowheads
482                                 Point p7 = p1 - (unit * 9.0 * scaledThickness);
483                                 Point p8 = p2 + (unit * 9.0 * scaledThickness);
484                                 painter->DrawArrowhead(p1, p7, scaledThickness);
485                                 painter->DrawArrowhead(p2, p8, scaledThickness);
486                                 painter->DrawLine(p1, p1 - (unit * 14.0 * scaledThickness));
487                                 painter->DrawLine(p2, p2 + (unit * 14.0 * scaledThickness));
488                         }
489
490                         // Draw length of dimension line...
491                         painter->SetFont(QFont("Arial", 8.0 * Global::zoom * scaledThickness));
492                         Point ctr = p2 + (Vector(p2, p1) / 2.0);
493
494                 #if 0
495                         QString dimText = QString("%1\"").arg(Vector(endpoint - position).Magnitude());
496                 #else
497                         QString dimText;
498
499                         if (length < 12.0)
500                                 dimText = QString("%1\"").arg(length);
501                         else
502                         {
503                                 double feet = (double)((int)length / 12);
504                                 double inches = length - (feet * 12.0);
505
506                                 if (inches == 0)
507                                         dimText = QString("%1'").arg(feet);
508                                 else
509                                         dimText = QString("%1' %2\"").arg(feet).arg(inches);
510                         }
511                 #endif
512
513                         painter->DrawAngledText(ctr, angle, dimText, scaledThickness);
514
515                         break;
516                 }
517                 case OTText:
518                 {
519                         Text * t = (Text *)obj;
520                         painter->DrawTextObject(t->p[0], t->s.c_str(), scaledThickness);
521                         break;
522                 }
523                 default:
524                         break;
525                 }
526         }
527 }
528
529
530 void DrawingView::AddHoveredToSelection(void)
531 {
532         std::vector<void *>::iterator i;
533
534         for(i=document.objects.begin(); i!=document.objects.end(); i++)
535         {
536                 if (((Object *)(*i))->hovered)
537                         ((Object *)(*i))->selected = true;
538         }
539 }
540
541
542 void DrawingView::GetSelection(std::vector<void *> & v)
543 {
544         v.clear();
545         std::vector<void *>::iterator i;
546
547         for(i=document.objects.begin(); i!=document.objects.end(); i++)
548         {
549                 if (((Object *)(*i))->selected)
550                         v.push_back(*i);
551         }
552 }
553
554
555 void DrawingView::GetHovered(std::vector<void *> & v)
556 {
557         v.clear();
558         std::vector<void *>::iterator i;
559
560         for(i=document.objects.begin(); i!=document.objects.end(); i++)
561         {
562                 if (((Object *)(*i))->hovered)
563 //              {
564 //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"));
565                         v.push_back(*i);
566 //              }
567         }
568 }
569
570
571 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
572 {
573         Global::screenSize = Vector(size().width(), size().height());
574         UpdateGridBackground();
575 }
576
577
578 void DrawingView::ToolHandler(int mode, Point p)
579 {
580         if (Global::tool == TTLine)
581                 LineHandler(mode, p);
582         else if (Global::tool == TTCircle)
583                 CircleHandler(mode, p);
584         else if (Global::tool == TTArc)
585                 ArcHandler(mode, p);
586         else if (Global::tool == TTRotate)
587                 RotateHandler(mode, p);
588         else if (Global::tool == TTMirror)
589                 MirrorHandler(mode, p);
590 }
591
592
593 void DrawingView::ToolDraw(Painter * painter)
594 {
595         if (Global::tool == TTLine)
596         {
597                 if (Global::toolState == TSNone)
598                 {
599                         painter->DrawHandle(toolPoint[0]);
600                 }
601                 else if ((Global::toolState == TSPoint2) && shiftDown)
602                 {
603                         painter->DrawHandle(toolPoint[1]);
604                 }
605                 else
606                 {
607                         painter->DrawLine(toolPoint[0], toolPoint[1]);
608                         painter->DrawHandle(toolPoint[1]);
609
610                         Vector v(toolPoint[0], toolPoint[1]);
611                         double absAngle = v.Angle() * RADIANS_TO_DEGREES;
612                         double absLength = v.Magnitude();
613                         QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
614                         informativeText = text.arg(absLength).arg(absAngle);
615                 }
616         }
617         else if (Global::tool == TTCircle)
618         {
619                 if (Global::toolState == TSNone)
620                 {
621                         painter->DrawHandle(toolPoint[0]);
622                 }
623                 else if ((Global::toolState == TSPoint2) && shiftDown)
624                 {
625                         painter->DrawHandle(toolPoint[1]);
626                 }
627                 else
628                 {
629                         double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
630 //                      painter->DrawLine(toolPoint[0], toolPoint[1]);
631 //                      painter->DrawHandle(toolPoint[1]);
632                         painter->SetBrush(QBrush(Qt::NoBrush));
633                         painter->DrawEllipse(toolPoint[0], length, length);
634                         QString text = tr("Radius: %1 in.");//\n") + QChar(0x2221) + tr(": %2");
635                         informativeText = text.arg(length);//.arg(absAngle);
636                 }
637         }
638         else if (Global::tool == TTArc)
639         {
640                 if (Global::toolState == TSNone)
641                 {
642                         painter->DrawHandle(toolPoint[0]);
643                 }
644                 else if (Global::toolState == TSPoint2)
645                 {
646                         double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
647                         painter->SetBrush(QBrush(Qt::NoBrush));
648                         painter->DrawEllipse(toolPoint[0], length, length);
649                         painter->DrawLine(toolPoint[0], toolPoint[1]);
650                         painter->DrawHandle(toolPoint[1]);
651                         QString text = tr("Radius: %1 in.");
652                         informativeText = text.arg(length);
653                 }
654                 else if (Global::toolState == TSPoint3)
655                 {
656                         double angle = Vector::Angle(toolPoint[0], toolPoint[2]);
657                         painter->DrawLine(toolPoint[0], toolPoint[2]);
658                         painter->SetBrush(QBrush(Qt::NoBrush));
659                         painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
660                         painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
661                         QString text = tr("Angle start: %1") + QChar(0x00B0);
662                         informativeText = text.arg(RADIANS_TO_DEGREES * angle);
663                 }
664                 else
665                 {
666                         double angle = Vector::Angle(toolPoint[0], toolPoint[3]);
667                         double span = angle - toolPoint[2].x;
668
669                         if (span < 0)
670                                 span += PI_TIMES_2;
671
672                         painter->DrawLine(toolPoint[0], toolPoint[3]);
673                         painter->SetBrush(QBrush(Qt::NoBrush));
674                         painter->DrawEllipse(toolPoint[0], toolPoint[1].x, toolPoint[1].x);
675                         painter->SetPen(0xFF00FF, 2.0, LSSolid);
676                         painter->DrawArc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
677                         painter->DrawHandle(toolPoint[0] + (Vector(cos(angle), sin(angle)) * toolPoint[1].x));
678                         QString text = tr("Arc span: %1") + QChar(0x00B0);
679                         informativeText = text.arg(RADIANS_TO_DEGREES * span);
680                 }
681         }
682         else if (Global::tool == TTRotate)
683         {
684                 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
685                         painter->DrawHandle(toolPoint[0]);
686                 else if ((Global::toolState == TSPoint2) && shiftDown)
687                         painter->DrawHandle(toolPoint[1]);
688                 else
689                 {
690                         if (toolPoint[0] == toolPoint[1])
691                                 return;
692                         
693                         painter->DrawLine(toolPoint[0], toolPoint[1]);
694                         // Likely we need a tool container for this... (now we do!)
695 #if 0
696                         if (ctrlDown)
697                         {
698                                 painter->SetPen(0x00FF00, 2.0, LSSolid);
699                                 overrideColor = true;
700                         }
701
702                         RenderObjects(painter, toolObjects);
703                         overrideColor = false;
704 #endif
705
706                         double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
707
708                         QString text = QChar(0x2221) + QObject::tr(": %1");
709                         informativeText = text.arg(absAngle);
710
711                         if (ctrlDown)
712                                 informativeText += " (Copy)";
713                 }
714         }
715         else if (Global::tool == TTMirror)
716         {
717                 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
718                         painter->DrawHandle(toolPoint[0]);
719                 else if ((Global::toolState == TSPoint2) && shiftDown)
720                         painter->DrawHandle(toolPoint[1]);
721                 else
722                 {
723                         if (toolPoint[0] == toolPoint[1])
724                                 return;
725                         
726                         painter->DrawLine(toolPoint[0], toolPoint[1]);
727                         // Likely we need a tool container for this... (now we do!)
728 #if 0
729                         if (ctrlDown)
730                         {
731                                 painter->SetPen(0x00FF00, 2.0, LSSolid);
732                                 overrideColor = true;
733                         }
734
735                         RenderObjects(painter, toolObjects);
736                         overrideColor = false;
737 #endif
738
739                         double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
740
741                         QString text = QChar(0x2221) + QObject::tr(": %1");
742                         informativeText = text.arg(absAngle);
743
744                         if (ctrlDown)
745                                 informativeText += " (Copy)";
746                 }
747         }
748 }
749
750
751 void DrawingView::LineHandler(int mode, Point p)
752 {
753         switch (mode)
754         {
755         case ToolMouseDown:
756                 if (Global::toolState == TSNone)
757                         toolPoint[0] = p;
758                 else
759                         toolPoint[1] = p;
760
761                 break;
762         case ToolMouseMove:
763                 if (Global::toolState == TSNone)
764                         toolPoint[0] = p;
765                 else
766                         toolPoint[1] = p;
767
768                 break;
769         case ToolMouseUp:
770                 if (Global::toolState == TSNone)
771                 {
772                         Global::toolState = TSPoint2;
773                         // Prevent spurious line from drawing...
774                         toolPoint[1] = toolPoint[0];
775                 }
776                 else if ((Global::toolState == TSPoint2) && shiftDown)
777                 {
778                         // Key override is telling us to make a new line, not continue the
779                         // previous one.
780                         toolPoint[0] = toolPoint[1];
781                 }
782                 else
783                 {
784                         Line * l = new Line(toolPoint[0], toolPoint[1]);
785                         document.objects.push_back(l);
786                         toolPoint[0] = toolPoint[1];
787                 }
788         }
789 }
790
791
792 void DrawingView::CircleHandler(int mode, Point p)
793 {
794         switch (mode)
795         {
796         case ToolMouseDown:
797                 if (Global::toolState == TSNone)
798                         toolPoint[0] = p;
799                 else
800                         toolPoint[1] = p;
801
802                 break;
803         case ToolMouseMove:
804                 if (Global::toolState == TSNone)
805                         toolPoint[0] = p;
806                 else
807                         toolPoint[1] = p;
808
809                 break;
810         case ToolMouseUp:
811                 if (Global::toolState == TSNone)
812                 {
813                         Global::toolState = TSPoint2;
814                         // Prevent spurious line from drawing...
815                         toolPoint[1] = toolPoint[0];
816                 }
817                 else if ((Global::toolState == TSPoint2) && shiftDown)
818                 {
819                         // Key override is telling us to make a new line, not continue the
820                         // previous one.
821                         toolPoint[0] = toolPoint[1];
822                 }
823                 else
824                 {
825                         double length = Vector::Magnitude(toolPoint[0], toolPoint[1]);
826                         Circle * c = new Circle(toolPoint[0], length);
827                         document.objects.push_back(c);
828                         toolPoint[0] = toolPoint[1];
829                         Global::toolState = TSNone;
830                 }
831         }
832 }
833
834
835 void DrawingView::ArcHandler(int mode, Point p)
836 {
837         switch (mode)
838         {
839         case ToolMouseDown:
840                 if (Global::toolState == TSNone)
841                         toolPoint[0] = p;
842                 else if (Global::toolState == TSPoint2)
843                         toolPoint[1] = p;
844                 else if (Global::toolState == TSPoint3)
845                         toolPoint[2] = p;
846                 else
847                         toolPoint[3] = p;
848
849                 break;
850         case ToolMouseMove:
851                 if (Global::toolState == TSNone)
852                         toolPoint[0] = p;
853                 else if (Global::toolState == TSPoint2)
854                         toolPoint[1] = p;
855                 else if (Global::toolState == TSPoint3)
856                         toolPoint[2] = p;
857                 else
858                         toolPoint[3] = p;
859
860                 break;
861         case ToolMouseUp:
862                 if (Global::toolState == TSNone)
863                 {
864                         // Prevent spurious line from drawing...
865                         toolPoint[1] = toolPoint[0];
866                         Global::toolState = TSPoint2;
867                 }
868                 else if (Global::toolState == TSPoint2)
869                 {
870                         if (shiftDown)
871                         {
872                                 // Key override is telling us to start circle at new center, not
873                                 // continue the current one.
874                                 toolPoint[0] = toolPoint[1];
875                                 return;
876                         }
877
878                         // Set the radius in toolPoint[1].x
879                         toolPoint[1].x = Vector::Magnitude(toolPoint[0], toolPoint[1]);
880                         Global::toolState = TSPoint3;
881                 }
882                 else if (Global::toolState == TSPoint3)
883                 {
884                         // Set the angle in toolPoint[2].x
885                         toolPoint[2].x = Vector::Angle(toolPoint[0], toolPoint[2]);
886                         Global::toolState = TSPoint4;
887                 }
888                 else
889                 {
890                         double endAngle = Vector::Angle(toolPoint[0], toolPoint[3]);
891                         double span = endAngle - toolPoint[2].x;
892
893                         if (span < 0)
894                                 span += PI_TIMES_2;
895
896                         Arc * arc = new Arc(toolPoint[0], toolPoint[1].x, toolPoint[2].x, span);
897                         document.objects.push_back(arc);
898                         Global::toolState = TSNone;
899                 }
900         }
901 }
902
903
904 void DrawingView::RotateHandler(int mode, Point p)
905 {
906         switch (mode)
907         {
908         case ToolMouseDown:
909                 if (Global::toolState == TSNone)
910                 {
911                         toolPoint[0] = p;
912                         SavePointsFrom(select, toolScratch);
913                         Global::toolState = TSPoint1;
914                 }
915                 else if (Global::toolState == TSPoint1)
916                         toolPoint[0] = p;
917                 else
918                         toolPoint[1] = p;
919
920                 break;
921         case ToolMouseMove:
922                 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
923                         toolPoint[0] = p;
924                 else if (Global::toolState == TSPoint2)
925                 {
926                         toolPoint[1] = p;
927
928                         if (shiftDown)
929                                 return;
930
931                         double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
932                         std::vector<void *>::iterator j = select.begin();
933                         std::vector<Object>::iterator i = toolScratch.begin();
934
935                         for(; i!=toolScratch.end(); i++, j++)
936                         {
937                                 Object obj = *i;
938                                 Point p1 = Geometry::RotatePointAroundPoint(obj.p[0], toolPoint[0], angle);
939                                 Point p2 = Geometry::RotatePointAroundPoint(obj.p[1], toolPoint[0], angle);
940                                 Object * obj2 = (Object *)(*j);
941                                 obj2->p[0] = p1;
942                                 obj2->p[1] = p2;
943
944                                 if (obj.type == OTArc)
945                                 {
946                                         obj2->angle[0] = obj.angle[0] + angle;
947
948                                         if (obj2->angle[0] > PI_TIMES_2)
949                                                 obj2->angle[0] -= PI_TIMES_2;
950                                 }
951                         }
952                 }
953
954                 break;
955         case ToolMouseUp:
956                 if (Global::toolState == TSPoint1)
957                 {
958                         Global::toolState = TSPoint2;
959                         // Prevent spurious line from drawing...
960                         toolPoint[1] = toolPoint[0];
961                 }
962                 else if ((Global::toolState == TSPoint2) && shiftDown)
963                 {
964                         // Key override is telling us to make a new line, not continue the
965                         // previous one.
966                         toolPoint[0] = toolPoint[1];
967                 }
968                 else
969                 {
970                         // Either we're finished with our rotate, or we're stamping a copy.
971                         if (ctrlDown)
972                         {
973                                 // Stamp a copy of the selection at the current rotation & bail
974                                 std::vector<void *> temp;
975                                 CopyObjects(select, temp);
976                                 ClearSelected(temp);
977                                 AddObjectsTo(document.objects, temp);
978                                 RestorePointsTo(select, toolScratch);
979                                 return;
980                         }
981
982                         toolPoint[0] = p;
983                         Global::toolState = TSPoint1;
984                         SavePointsFrom(select, toolScratch);
985                 }
986
987                 break;
988         case ToolKeyDown:
989                 // Reset the selection if shift held down...
990                 if (shiftDown)
991                         RestorePointsTo(select, toolScratch);
992
993                 break;
994         case ToolKeyUp:
995                 // Reset selection when key is let up
996                 if (!shiftDown)
997                         RotateHandler(ToolMouseMove, toolPoint[1]);
998
999                 break;
1000         case ToolCleanup:
1001                 RestorePointsTo(select, toolScratch);
1002         }
1003 }
1004
1005
1006 void DrawingView::MirrorHandler(int mode, Point p)
1007 {
1008         switch (mode)
1009         {
1010         case ToolMouseDown:
1011                 if (Global::toolState == TSNone)
1012                 {
1013                         toolPoint[0] = p;
1014                         SavePointsFrom(select, toolScratch);
1015                         Global::toolState = TSPoint1;
1016                 }
1017                 else if (Global::toolState == TSPoint1)
1018                         toolPoint[0] = p;
1019                 else
1020                         toolPoint[1] = p;
1021
1022                 break;
1023         case ToolMouseMove:
1024                 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
1025                         toolPoint[0] = p;
1026                 else if (Global::toolState == TSPoint2)
1027                 {
1028                         toolPoint[1] = p;
1029
1030                         if (shiftDown)
1031                                 return;
1032
1033                         double angle = Vector(toolPoint[0], toolPoint[1]).Angle();
1034                         std::vector<void *>::iterator j = select.begin();
1035                         std::vector<Object>::iterator i = toolScratch.begin();
1036
1037                         for(; i!=toolScratch.end(); i++, j++)
1038                         {
1039                                 Object obj = *i;
1040                                 Point p1 = Geometry::MirrorPointAroundLine(obj.p[0], toolPoint[0], toolPoint[1]);
1041                                 Point p2 = Geometry::MirrorPointAroundLine(obj.p[1], toolPoint[0], toolPoint[1]);
1042                                 Object * obj2 = (Object *)(*j);
1043                                 obj2->p[0] = p1;
1044                                 obj2->p[1] = p2;
1045
1046                                 if (obj.type == OTArc)
1047                                 {
1048 #if 0
1049                                         // This is WRONG for mirroring...
1050                                         obj2->angle[0] = obj.angle[0] + angle;
1051 #endif
1052                                         obj2->angle[0] = (2.0 * angle) - obj.angle[0] - obj.angle[1];
1053
1054                                         if (obj2->angle[0] > PI_TIMES_2)
1055                                                 obj2->angle[0] -= PI_TIMES_2;
1056                                 }
1057                         }
1058                 }
1059
1060                 break;
1061         case ToolMouseUp:
1062                 if (Global::toolState == TSPoint1)
1063                 {
1064                         Global::toolState = TSPoint2;
1065                         // Prevent spurious line from drawing...
1066                         toolPoint[1] = toolPoint[0];
1067                 }
1068                 else if ((Global::toolState == TSPoint2) && shiftDown)
1069                 {
1070                         // Key override is telling us to make a new line, not continue the
1071                         // previous one.
1072                         toolPoint[0] = toolPoint[1];
1073                 }
1074                 else
1075                 {
1076                         // Either we're finished with our rotate, or we're stamping a copy.
1077                         if (ctrlDown)
1078                         {
1079                                 // Stamp a copy of the selection at the current rotation & bail
1080                                 std::vector<void *> temp;
1081                                 CopyObjects(select, temp);
1082                                 ClearSelected(temp);
1083                                 AddObjectsTo(document.objects, temp);
1084                                 RestorePointsTo(select, toolScratch);
1085                                 return;
1086                         }
1087
1088                         toolPoint[0] = p;
1089                         Global::toolState = TSPoint1;
1090                         SavePointsFrom(select, toolScratch);
1091                 }
1092
1093                 break;
1094         case ToolKeyDown:
1095                 // Reset the selection if shift held down...
1096                 if (shiftDown)
1097                         RestorePointsTo(select, toolScratch);
1098
1099                 break;
1100         case ToolKeyUp:
1101                 // Reset selection when key is let up
1102                 if (!shiftDown)
1103                         MirrorHandler(ToolMouseMove, toolPoint[1]);
1104
1105                 break;
1106         case ToolCleanup:
1107                 RestorePointsTo(select, toolScratch);
1108         }
1109 }
1110
1111
1112 void DrawingView::mousePressEvent(QMouseEvent * event)
1113 {
1114         if (event->button() == Qt::LeftButton)
1115         {
1116                 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1117
1118                 // Handle tool processing, if any
1119                 if (Global::tool)
1120                 {
1121                         if (Global::snapToGrid)
1122                                 point = SnapPointToGrid(point);
1123
1124                         //Also, may want to figure out if hovering over a snap point on an object,
1125                         //snap to grid if not.
1126                         // Snap to object point if valid...
1127 //                      if (Global::snapPointIsValid)
1128 //                              point = Global::snapPoint;
1129                         
1130                         ToolHandler(ToolMouseDown, point);
1131                         return;
1132                 }
1133
1134                 // Clear the selection only if CTRL isn't being held on click
1135                 if (!ctrlDown)
1136                         ClearSelected(document.objects);
1137 //                      ClearSelection();
1138
1139                 // If any objects are being hovered on click, add them to the selection
1140                 // & return
1141                 if (numHovered > 0)
1142                 {
1143                         AddHoveredToSelection();
1144                         update();       // needed??
1145                         GetHovered(hover);      // prolly needed
1146
1147                         // Needed for grab & moving objects
1148                         // We do it *after*... why? (doesn't seem to confer any advantage...)
1149                         if (Global::snapToGrid)
1150                                 oldPoint = SnapPointToGrid(point);
1151
1152                         return;
1153                 }
1154
1155                 // Didn't hit any object and not using a tool, so do a selection rectangle
1156                 Global::selectionInProgress = true;
1157                 Global::selection.setTopLeft(QPointF(point.x, point.y));
1158                 Global::selection.setBottomRight(QPointF(point.x, point.y));
1159         }
1160         else if (event->button() == Qt::MiddleButton)
1161         {
1162                 scrollDrag = true;
1163                 oldPoint = Vector(event->x(), event->y());
1164                 // Should also change the mouse pointer as well...
1165                 setCursor(Qt::SizeAllCursor);
1166         }
1167 }
1168
1169
1170 void DrawingView::mouseMoveEvent(QMouseEvent * event)
1171 {
1172         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1173         Global::selection.setBottomRight(QPointF(point.x, point.y));
1174         // Only needs to be done here, as mouse down is always preceded by movement
1175         Global::snapPointIsValid = false;
1176
1177         // Scrolling...
1178         if (event->buttons() & Qt::MiddleButton)
1179         {
1180                 point = Vector(event->x(), event->y());
1181                 // Since we're using Qt coords for scrolling, we have to adjust them here to
1182                 // conform to Cartesian coords, since the origin is using Cartesian. :-)
1183                 Vector delta(oldPoint, point);
1184                 delta /= Global::zoom;
1185                 delta.y = -delta.y;
1186                 Global::origin -= delta;
1187
1188                 UpdateGridBackground();
1189                 update();
1190                 oldPoint = point;
1191                 return;
1192         }
1193
1194         // If we're doing a selection rect, see if any objects are engulfed by it
1195         // (implies left mouse button held down)
1196         if (Global::selectionInProgress)
1197         {
1198                 CheckObjectBounds();
1199                 update();
1200                 return;
1201         }
1202
1203         // Handle object movement (left button down & over an object)
1204         if ((event->buttons() & Qt::LeftButton) && numHovered && !Global::tool)
1205         {
1206                 if (Global::snapToGrid)
1207                         point = SnapPointToGrid(point);
1208
1209                 HandleObjectMovement(point);
1210                 update();
1211                 oldPoint = point;
1212                 return;
1213         }
1214
1215         // Do object hit testing...
1216         bool needUpdate = HitTestObjects(point);
1217
1218         // Do tool handling, if any are active...
1219         if (Global::tool)
1220         {
1221                 if (Global::snapToGrid)
1222                         point = SnapPointToGrid(point);
1223
1224                 ToolHandler(ToolMouseMove, point);
1225         }
1226
1227         // This is used to draw the tool crosshair...
1228         oldPoint = point;
1229
1230         if (needUpdate || Global::tool)
1231                 update();
1232 }
1233
1234
1235 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
1236 {
1237         if (event->button() == Qt::LeftButton)
1238         {
1239 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
1240 //could set it up to use the document's update function (assumes that all object updates
1241 //are being reported correctly:
1242 //              if (document.NeedsUpdate())
1243                 // Do an update if collided with at least *one* object in the document
1244 //              if (collided)
1245                         update();
1246
1247                 if (Global::tool)
1248                 {
1249                         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
1250                         ToolHandler(ToolMouseUp, point);
1251                         return;
1252                 }
1253
1254                 if (Global::selectionInProgress)
1255                         Global::selectionInProgress = false;
1256
1257                 informativeText.clear();
1258 // Should we be doing this automagically? Hmm...
1259                 // Clear our vectors
1260                 select.clear();
1261                 hover.clear();
1262
1263                 // Scoop 'em up
1264                 std::vector<void *>::iterator i;
1265
1266                 for(i=document.objects.begin(); i!=document.objects.end(); i++)
1267                 {
1268                         if (((Object *)(*i))->selected)
1269                                 select.push_back(*i);
1270
1271 //hmm, this is no good, too late to do any good :-P
1272 //                      if ((*i)->hovered)
1273 //                              hover.push_back(*i);
1274                 }
1275         }
1276         else if (event->button() == Qt::MiddleButton)
1277         {
1278                 scrollDrag = false;
1279                 setCursor(Qt::ArrowCursor);
1280         }
1281 }
1282
1283
1284 void DrawingView::wheelEvent(QWheelEvent * event)
1285 {
1286         double zoomFactor = 1.25;
1287         QSize sizeWin = size();
1288         Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
1289         center = Painter::QtToCartesianCoords(center);
1290
1291         // This is not centering for some reason. Need to figure out why. :-/
1292         if (event->delta() > 0)
1293         {
1294                 Vector newOrigin = center - ((center - Global::origin) / zoomFactor);
1295                 Global::origin = newOrigin;
1296                 Global::zoom *= zoomFactor;
1297         }
1298         else
1299         {
1300                 Vector newOrigin = center + ((-center + Global::origin) * zoomFactor);
1301                 Global::origin = newOrigin;
1302                 Global::zoom /= zoomFactor;
1303         }
1304
1305 //      Global::gridSpacing = gridPixels / Painter::zoom;
1306 //      UpdateGridBackground();
1307         SetGridSize(Global::gridSpacing * Global::zoom);
1308         update();
1309 //      zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Global::gridSpacing));
1310 }
1311
1312
1313 void DrawingView::keyPressEvent(QKeyEvent * event)
1314 {
1315         bool oldShift = shiftDown;
1316         bool oldCtrl = ctrlDown;
1317
1318         if (event->key() == Qt::Key_Shift)
1319                 shiftDown = true;
1320         else if (event->key() == Qt::Key_Control)
1321                 ctrlDown = true;
1322
1323         if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1324         {
1325                 if (Global::tool)
1326                         ToolHandler(ToolKeyDown, Point(0, 0));
1327
1328                 update();
1329         }
1330 }
1331
1332
1333 void DrawingView::keyReleaseEvent(QKeyEvent * event)
1334 {
1335         bool oldShift = shiftDown;
1336         bool oldCtrl = ctrlDown;
1337
1338         if (event->key() == Qt::Key_Shift)
1339                 shiftDown = false;
1340         else if (event->key() == Qt::Key_Control)
1341                 ctrlDown = false;
1342
1343         if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1344         {
1345                 if (Global::tool)
1346                         ToolHandler(ToolKeyUp, Point(0, 0));
1347
1348                 update();
1349         }
1350 }
1351
1352
1353 //
1354 // This looks strange, but it's really quite simple: We want a point that's
1355 // more than half-way to the next grid point to snap there while conversely we
1356 // want a point that's less than half-way to to the next grid point then snap
1357 // to the one before it. So we add half of the grid spacing to the point, then
1358 // divide by it so that we can remove the fractional part, then multiply it
1359 // back to get back to the correct answer.
1360 //
1361 Point DrawingView::SnapPointToGrid(Point point)
1362 {
1363         point += Global::gridSpacing / 2.0;             // *This* adds to Z!!!
1364         point /= Global::gridSpacing;
1365         point.x = floor(point.x);//need to fix this for negative numbers...
1366         point.y = floor(point.y);
1367         point.z = 0;                                    // Make *sure* Z doesn't go anywhere!!!
1368         point *= Global::gridSpacing;
1369         return point;
1370 }
1371
1372
1373 void DrawingView::CheckObjectBounds(void)
1374 {
1375         std::vector<void *>::iterator i;
1376         numSelected = 0;
1377
1378         for(i=document.objects.begin(); i!=document.objects.end(); i++)
1379         {
1380                 Object * obj = (Object *)(*i);
1381                 obj->selected = false;
1382
1383                 switch (obj->type)
1384                 {
1385                 case OTLine:
1386                 {
1387                         Line * l = (Line *)obj;
1388
1389                         if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
1390                                 l->selected = true;
1391
1392                         break;
1393                 }
1394                 case OTCircle:
1395                 {
1396                         Circle * c = (Circle *)obj;
1397
1398                         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]))
1399                                 c->selected = true;
1400
1401                         break;
1402                 }
1403                 case OTArc:
1404                 {
1405                         Arc * a = (Arc *)obj;
1406
1407                         double start = a->angle[0];
1408                         double end = start + a->angle[1];
1409                         QPointF p1(cos(start), sin(start));
1410                         QPointF p2(cos(end), sin(end));
1411                         QRectF bounds(p1, p2);
1412
1413 #if 1
1414                         // Swap X/Y coordinates if they're backwards...
1415                         if (bounds.left() > bounds.right())
1416                         {
1417                                 double temp = bounds.left();
1418                                 bounds.setLeft(bounds.right());
1419                                 bounds.setRight(temp);
1420                         }
1421
1422                         if (bounds.bottom() > bounds.top())
1423                         {
1424                                 double temp = bounds.bottom();
1425                                 bounds.setBottom(bounds.top());
1426                                 bounds.setTop(temp);
1427                         }
1428 #else
1429                         // Doesn't work as advertised! For shame!
1430                         bounds = bounds.normalized();
1431 #endif
1432
1433                         // If the end of the arc is before the beginning, add 360 degrees to it
1434                         if (end < start)
1435                                 end += 2.0 * PI;
1436
1437                         // Adjust the bounds depending on which axes are crossed
1438                         if ((start < PI_OVER_2) && (end > PI_OVER_2))
1439                                 bounds.setTop(1.0);
1440
1441                         if ((start < PI) && (end > PI))
1442                                 bounds.setLeft(-1.0);
1443
1444                         if ((start < (PI + PI_OVER_2)) && (end > (PI + PI_OVER_2)))
1445                                 bounds.setBottom(-1.0);
1446
1447                         if ((start < (2.0 * PI)) && (end > (2.0 * PI)))
1448                                 bounds.setRight(1.0);
1449
1450                         if ((start < ((2.0 * PI) + PI_OVER_2)) && (end > ((2.0 * PI) + PI_OVER_2)))
1451                                 bounds.setTop(1.0);
1452
1453                         if ((start < (3.0 * PI)) && (end > (3.0 * PI)))
1454                                 bounds.setLeft(-1.0);
1455
1456                         if ((start < ((3.0 * PI) + PI_OVER_2)) && (end > ((3.0 * PI) + PI_OVER_2)))
1457                                 bounds.setBottom(-1.0);
1458
1459                         bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
1460                         bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
1461                         bounds.translate(a->p[0].x, a->p[0].y);
1462
1463                         if (Global::selection.contains(bounds))
1464                                 a->selected = true;
1465
1466                         break;
1467                 }
1468                 default:
1469                         break;
1470                 }
1471
1472                 if (obj->selected)
1473                         numSelected++;
1474         }
1475 }
1476
1477
1478 bool DrawingView::HitTestObjects(Point point)
1479 {
1480         std::vector<void *>::iterator i;
1481         numHovered = 0;
1482         bool needUpdate = false;
1483
1484         for(i=document.objects.begin(); i!=document.objects.end(); i++)
1485         {
1486                 Object * obj = (Object *)(*i);
1487
1488                 switch (obj->type)
1489                 {
1490                 case OTLine:
1491                 {
1492                         bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject;
1493                         obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false;
1494                         Vector lineSegment = obj->p[1] - obj->p[0];
1495                         Vector v1 = point - obj->p[0];
1496                         Vector v2 = point - obj->p[1];
1497                         double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point);
1498                         double distance;
1499
1500                         if (t < 0.0)
1501                                 distance = v1.Magnitude();
1502                         else if (t > 1.0)
1503                                 distance = v2.Magnitude();
1504                         else
1505                                 // distance = ?Det?(ls, v1) / |ls|
1506                                 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
1507                                         / lineSegment.Magnitude());
1508
1509                         if ((v1.Magnitude() * Global::zoom) < 8.0)
1510                                 obj->hitPoint[0] = true;
1511                         else if ((v2.Magnitude() * Global::zoom) < 8.0)
1512                                 obj->hitPoint[1] = true;
1513                         else if ((distance * Global::zoom) < 5.0)
1514                                 obj->hitObject = true;
1515
1516                         obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false);
1517
1518                         if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject))
1519                                 needUpdate = true;
1520
1521                         break;
1522                 }
1523                 case OTCircle:
1524                 {
1525                         bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject;
1526                         obj->hitPoint[0] = obj->hitObject = false;
1527                         double length = Vector::Magnitude(obj->p[0], point);
1528
1529                         if ((length * Global::zoom) < 8.0)
1530                                 obj->hitPoint[0] = true;
1531                         else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0)
1532                                 obj->hitObject = true;
1533
1534                         obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false);
1535
1536                         if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject))
1537                                 needUpdate = true;
1538
1539                         break;
1540                 }
1541                 case OTArc:
1542                 {
1543                         bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject;
1544                         obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false;
1545                         double length = Vector::Magnitude(obj->p[0], point);
1546                         double angle = Vector::Angle(obj->p[0], point);
1547
1548                         // Make sure we get the angle in the correct spot
1549                         if (angle < obj->angle[0])
1550                                 angle += PI_TIMES_2;
1551
1552                         // Get the span that we're pointing at...
1553                         double span = angle - obj->angle[0];
1554
1555                         // N.B.: Still need to hit test the arc start & arc span handles...
1556                         double spanAngle = obj->angle[0] + obj->angle[1];
1557                         Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]);
1558                         Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]);
1559                         double length2 = Vector::Magnitude(point, handle1);
1560                         double length3 = Vector::Magnitude(point, handle2);
1561
1562                         if ((length * Global::zoom) < 8.0)
1563                                 obj->hitPoint[0] = true;
1564                         else if ((length2 * Global::zoom) < 8.0)
1565                                 obj->hitPoint[1] = true;
1566                         else if ((length3 * Global::zoom) < 8.0)
1567                                 obj->hitPoint[2] = true;
1568                         else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1]))
1569                                 obj->hitObject = true;
1570
1571                         obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false);
1572
1573                         if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject))
1574                                 needUpdate = true;
1575
1576                         break;
1577                 }
1578                 default:
1579                         break;
1580                 }
1581
1582                 if (obj->hovered)
1583 //              {
1584                         numHovered++;
1585 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
1586 //              }
1587         }
1588
1589         return needUpdate;
1590 }
1591
1592
1593 void DrawingView::HandleObjectMovement(Point point)
1594 {
1595         Point delta = point - oldPoint;
1596 //printf("HOM: old = (%f,%f), new = (%f, %f), delta = (%f, %f)\n", oldPoint.x, oldPoint.y, point.x, point.y, delta.x, delta.y);
1597         Object * obj = (Object *)hover[0];
1598 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
1599 //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"));
1600
1601         switch (obj->type)
1602         {
1603         case OTLine:
1604                 if (obj->hitPoint[0])
1605                         obj->p[0] = point;
1606                 else if (obj->hitPoint[1])
1607                         obj->p[1] = point;
1608                 else if (obj->hitObject)
1609                 {
1610                         obj->p[0] += delta;
1611                         obj->p[1] += delta;
1612                 }
1613
1614                 break;
1615         case OTCircle:
1616                 if (obj->hitPoint[0])
1617                         obj->p[0] = point;
1618                 else if (obj->hitObject)
1619                 {
1620 //this doesn't work. we need to save this on mouse down for this to work correctly!
1621 //                      double oldRadius = obj->radius[0];
1622                         obj->radius[0] = Vector::Magnitude(obj->p[0], point);
1623
1624                         QString text = QObject::tr("Radius: %1");//\nScale: %2%");
1625                         informativeText = text.arg(obj->radius[0], 0, 'd', 4);//.arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0);
1626                 }
1627
1628                 break;
1629         case OTArc:
1630                 if (obj->hitPoint[0])
1631                         obj->p[0] = point;
1632                 else if (obj->hitPoint[1])
1633                 {
1634                         // Change the Arc's span (handle #1)
1635                         if (shiftDown)
1636                         {
1637                                 double angle = Vector::Angle(obj->p[0], point);
1638                                 double delta = angle - obj->angle[0];
1639
1640                                 if (delta < 0)
1641                                         delta += PI_TIMES_2;
1642
1643                                 obj->angle[1] -= delta;
1644                                 obj->angle[0] = angle;
1645
1646                                 if (obj->angle[1] < 0)
1647                                         obj->angle[1] += PI_TIMES_2;
1648
1649                                 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
1650                                 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);
1651                                 return;
1652                         }
1653
1654                         double angle = Vector::Angle(obj->p[0], point);
1655                         obj->angle[0] = angle;
1656                         QString text = QObject::tr("Start angle: %1") + QChar(0x00B0);
1657                         informativeText = text.arg(obj->angle[0] * RADIANS_TO_DEGREES, 0, 'd', 4);
1658                 }
1659                 else if (obj->hitPoint[2])
1660                 {
1661                         // Change the Arc's span (handle #2)
1662                         if (shiftDown)
1663                         {
1664                                 double angle = Vector::Angle(obj->p[0], point);
1665                                 obj->angle[1] = angle - obj->angle[0];
1666
1667                                 if (obj->angle[1] < 0)
1668                                         obj->angle[1] += PI_TIMES_2;
1669
1670                                 QString text = QObject::tr("Span: %1") + QChar(0x00B0) + QObject::tr("\n%2") + QChar(0x00B0) + QObject::tr(" - %3") + QChar(0x00B0);
1671                                 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);
1672                                 return;
1673                         }
1674
1675                         double angle = Vector::Angle(obj->p[0], point);
1676                         obj->angle[0] = angle - obj->angle[1];
1677
1678                         if (obj->angle[0] < 0)
1679                                 obj->angle[0] += PI_TIMES_2;
1680
1681                         QString text = QObject::tr("End angle: %1") + QChar(0x00B0);
1682                         informativeText = text.arg((obj->angle[0] + obj->angle[1]) * RADIANS_TO_DEGREES, 0, 'd', 4);
1683                 }
1684                 else if (obj->hitObject)
1685                 {
1686                         if (shiftDown)
1687                         {
1688                                 return;
1689                         }
1690
1691                         obj->radius[0] = Vector::Magnitude(obj->p[0], point);
1692                         QString text = QObject::tr("Radius: %1");
1693                         informativeText = text.arg(obj->radius[0], 0, 'd', 4);
1694                 }
1695
1696                 break;
1697         default:
1698                 break;
1699         }
1700 }
1701
1702
1703
1704 #if 0
1705         // This returns true if we've moved over an object...
1706         if (document.PointerMoved(point)) // <-- This
1707         // This is where the object would do automagic dragging & shit. Since we don't
1708         // do that anymore, we need a strategy to handle it.
1709         {
1710
1711 /*
1712 Now objects handle mouse move snapping as well. The code below mainly works only
1713 for tools; we need to fix it so that objects work as well...
1714
1715 There's a problem with the object point snapping in that it's dependent on the
1716 order of the objects in the document. Most likely this is because it counts the
1717 selected object last and thus fucks up the algorithm. Need to fix this...
1718
1719
1720 */
1721                 // Do object snapping here. Grid snapping on mouse down is done in the
1722                 // objects themselves, only because we have to hit test the raw point,
1723                 // not the snapped point. There has to be a better way...!
1724                 if (document.penultimateObjectHovered)
1725                 {
1726                         // Two objects are hovered, see if we have an intersection point
1727                         if ((document.lastObjectHovered->type == OTLine) && (document.penultimateObjectHovered->type == OTLine))
1728                         {
1729                                 double t;
1730                                 int n = Geometry::Intersects((Line *)document.lastObjectHovered, (Line *)document.penultimateObjectHovered, &t);
1731
1732                                 if (n == 1)
1733                                 {
1734                                         Global::snapPoint = document.lastObjectHovered->GetPointAtParameter(t);
1735                                         Global::snapPointIsValid = true;
1736                                 }
1737                         }
1738                         else if ((document.lastObjectHovered->type == OTCircle) && (document.penultimateObjectHovered->type == OTCircle))
1739                         {
1740                                 Point p1, p2;
1741                                 int n = Geometry::Intersects((Circle *)document.lastObjectHovered, (Circle *)document.penultimateObjectHovered, 0, 0, 0, 0, &p1, &p2);
1742
1743                                 if (n == 1)
1744                                 {
1745                                         Global::snapPoint = p1;
1746                                         Global::snapPointIsValid = true;
1747                                 }
1748                                 else if (n == 2)
1749                                 {
1750                                         double d1 = Vector(point, p1).Magnitude();
1751                                         double d2 = Vector(point, p2).Magnitude();
1752
1753                                         if (d1 < d2)
1754                                                 Global::snapPoint = p1;
1755                                         else
1756                                                 Global::snapPoint = p2;
1757
1758                                         Global::snapPointIsValid = true;
1759                                 }
1760                         }
1761                 }
1762 //              else
1763 //              {
1764                         // Otherwise, it was a single object hovered...
1765 //              }
1766         }
1767
1768         if (toolAction)
1769         {
1770                 if (Global::snapToGrid)
1771                         point = Global::SnapPointToGrid(point);
1772
1773                 // We always snap to object points, and they take precendence over
1774                 // grid points...
1775                 if (Global::snapPointIsValid)
1776                         point = Global::snapPoint;
1777
1778                 toolAction->MouseMoved(point);
1779         }
1780 #else
1781 #endif
1782