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