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