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