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