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