]> Shamusworld >> Repos - architektonas/blob - src/drawingview.cpp
Added ability to manipulate Lines by click & drag.
[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
39
40 #define BACKGROUND_MAX_SIZE     512
41
42 enum { ToolMouseDown, ToolMouseMove, ToolMouseUp };
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),// document(Vector(0, 0)),
54         gridPixels(0), collided(false)//, toolAction(NULL)
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->p1 = Vector(5, 5);
83         line->p2 = Vector(50, 40);
84         line->type = OTLine;
85         line->thickness = 2.0;
86         line->style = LSDash;
87         line->color = 0xFF7F00;
88         document.objects.push_back(line);
89         document.objects.push_back(new Line(Vector(50, 40), Vector(10, 83)));
90         document.objects.push_back(new Line(Vector(10, 83), Vector(17, 2)));
91         document.objects.push_back(new Circle(Vector(100, 100), 36));
92         document.objects.push_back(new Circle(Vector(50, 150), 49));
93         document.objects.push_back(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3)),
94         document.objects.push_back(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5));
95         document.objects.push_back(new Dimension(Vector(50, 40), Vector(5, 5)));
96         document.objects.push_back(new Text(Vector(10, 83), "Here is some awesome text!"));
97 #endif
98
99 /*
100 Here we set the grid size in pixels--12 in this case. Initially, we have our
101 zoom set to make this represent 12 inches at a zoom factor of 25%. (This is
102 arbitrary.) So, to be able to decouple the grid size from the zoom, we need
103 to be able to set the size of the background grid (which we do here at an
104 arbitrary 12 pixels) to anything we want (within reason, of course :-).
105
106 The drawing enforces the grid spacing through the drawing->gridSpacing variable.
107
108         drawing->gridSpacing = 12.0 / Global::zoom;
109
110 Global::zoom is the zoom factor for the drawing, and all mouse clicks are
111 translated to Cartesian coordinates through this. (Initially, Global::zoom is
112 set to 1.0. SCREEN_ZOOM is set to 1.0/4.0.)
113
114 Really, the 100% zoom level can be set at *any* zoom level, it's more of a
115 convenience function than any measure of absolutes. Doing things that way we
116 could rid ourselves of the whole SCREEN_ZOOM parameter and all the attendant
117 shittiness that comes with it.
118
119 However, it seems that SCREEN_ZOOM is used to make text and arrow sizes show up
120 a certain way, which means we should probably create something else in those
121 objects to take its place--like some kind of scale factor. This would seem to
122 imply that certain point sizes actually *do* tie things like fonts to absolute
123 sizes on the screen, but not necessarily because you could have an inch scale
124 with text that is quite small relative to other objects on the screen, which
125 currently you have to zoom in to see (and which blows up the text). Point sizes
126 in an application like this are a bit meaningless; even though an inch is an
127 inch regardless of the zoom level a piece of text can be larger or smaller than
128 this. Maybe this is the case for having a base unit and basing point sizes off
129 of that.
130
131 Here's what's been figured out. Global::zoom is simply the ratio of pixels to
132 base units. What that means is that if you have a 12px grid with a 6" grid size
133 (& base unit of "inches"), Global::zoom becomes 12px / 6" = 2.0 px/in.
134
135 Dimensions now have a "size" parameter to set their absolute size in relation
136 to the base unit. ATM, the arrows are drawn in pixels, but also scaled by
137 Global::zoom *and* size. Same with the dimension text; it's drawn at 10pt and
138 scaled the same way as the arrowheads.
139
140 Need a way to scale line widths as well. :-/ Shouldn't be too difficult, just
141 need a thickness parameter similar to the "size" param for dimensions. (And now
142 we do! :-)
143
144 */
145         SetGridSize(12);        // This is in pixels
146 }
147
148
149 #if 0
150 void DrawingView::SetToolActive(Action * action)
151 {
152         if (action != NULL)
153         {
154                 toolAction = action;
155                 connect(toolAction, SIGNAL(ObjectReady(Object *)), this,
156                         SLOT(AddNewObjectToDocument(Object *)));
157                 connect(toolAction, SIGNAL(NeedRefresh()), this, SLOT(HandleActionUpdate()));
158         }
159 }
160 #endif
161
162
163 void DrawingView::SetGridSize(uint32_t size)
164 {
165         // Sanity check
166         if (size == gridPixels)
167                 return;
168
169         // Recreate the background bitmap
170         gridPixels = size;
171         QPainter pmp(&gridBackground);
172         pmp.fillRect(0, 0, BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE, QColor(240, 240, 240));
173         pmp.setPen(QPen(QColor(210, 210, 255), 2.0, Qt::SolidLine));
174
175         for(int i=0; i<(BACKGROUND_MAX_SIZE-1); i+=gridPixels)
176         {
177                 pmp.drawLine(i, 0, i, BACKGROUND_MAX_SIZE - 1);
178                 pmp.drawLine(0, i, BACKGROUND_MAX_SIZE - 1, i);
179         }
180
181         pmp.end();
182
183         // Set up new BG brush & zoom level (pixels per base unit)
184 //      Painter::zoom = gridPixels / gridSpacing;
185         Global::zoom = gridPixels / Global::gridSpacing;
186         UpdateGridBackground();
187 }
188
189
190 void DrawingView::UpdateGridBackground(void)
191 {
192         // Transform the origin to Qt coordinates
193         Vector pixmapOrigin = Painter::CartesianToQtCoords(Vector());
194         int x = (int)pixmapOrigin.x;
195         int y = (int)pixmapOrigin.y;
196         // Use mod arithmetic to grab the correct swatch of background
197 /*
198 Negative numbers still screw it up... Need to think about what we're
199 trying to do here. The fact that it worked with 72 seems to have been pure luck.
200 It seems the problem is negative numbers: We can't let that happen.
201 When taking away the zero, it pops over 1 px at zero, then goes about 1/2 a
202 grid at x<0.
203
204 The bitmap looks like this:
205
206 +---+---+---+---+---
207 |   |   |   |   |
208 |   |   |   |   |
209 +---+---+---+---+---
210 |   |   |   |   |
211 |   |   |   |   |
212 |   |   |   |   |
213
214 @ x = 1, we want it to look like:
215
216 -+---+---+---+---+---
217  |   |   |   |   |
218  |   |   |   |   |
219 -+---+---+---+---+---
220  |   |   |   |   |
221  |   |   |   |   |
222  |   |   |   |   |
223
224 Which means we need to grab the sample from x = 3. @ x = -1:
225
226 ---+---+---+---+---
227    |   |   |   |
228    |   |   |   |
229 ---+---+---+---+---
230    |   |   |   |
231    |   |   |   |
232    |   |   |   |
233
234 Which means we need to grab the sample from x = 1. Which means we have to take
235 the mirror of the modulus of gridPixels.
236
237 Doing a mod of a negative number is problematic: 1st, the compiler converts the
238 negative number to an unsigned int, then it does the mod. Gets you wrong answers
239 most of the time, unless you use a power of 2. :-P So what we do here is just
240 take the modulus of the negation, which means we don't have to worry about
241 mirroring it later.
242
243 The positive case looks gruesome (and it is) but it boils down to this: We take
244 the modulus of the X coordinate, then mirror it by subtraction from the
245 maximum (in this case, gridPixels). This gives us a number in the range of 1 to
246 gridPixels. But we need the case where the result equalling gridPixels to be
247 zero; so we do another modulus operation on the result to achieve this.
248 */
249         if (x < 0)
250                 x = -x % gridPixels;
251         else
252                 x = (gridPixels - (x % gridPixels)) % gridPixels;
253
254         if (y < 0)
255                 y = -y % gridPixels;
256         else
257                 y = (gridPixels - (y % gridPixels)) % gridPixels;
258
259         // Here we grab a section of the bigger pixmap, so that the background
260         // *looks* like it's scrolling...
261         QPixmap pm = gridBackground.copy(x, y, gridPixels, gridPixels);
262         QPalette pal = palette();
263         pal.setBrush(backgroundRole(), QBrush(pm));
264         setAutoFillBackground(true);
265         setPalette(pal);
266 }
267
268
269 void DrawingView::AddNewObjectToDocument(Object * object)
270 {
271         if (object)
272         {
273 //              object->Reparent(&document);
274 //              document.Add(object);
275                 update();
276         }
277 //printf("DrawingView::AddNewObjectToDocument(). object=%08X\n", object);
278 }
279
280
281 void DrawingView::HandleActionUpdate(void)
282 {
283         update();
284 }
285
286
287 void DrawingView::SetCurrentLayer(int layer)
288 {
289         Global::currentLayer = layer;
290 //printf("DrawingView::CurrentLayer = %i\n", layer);
291 }
292
293
294 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
295 {
296         // This is undoing the transform, e.g. going from client coords to local coords.
297         // In essence, the height - y is height + (y * -1), the (y * -1) term doing the
298         // conversion of the y-axis from increasing bottom to top.
299         return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
300 }
301
302
303 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
304 {
305         // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
306         // No voodoo here, it's just grouped wrong to see it. It should be:
307         // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive
308         return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
309 }
310
311
312 void DrawingView::paintEvent(QPaintEvent * /*event*/)
313 {
314         QPainter qtPainter(this);
315         Painter painter(&qtPainter);
316
317         if (useAntialiasing)
318                 qtPainter.setRenderHint(QPainter::Antialiasing);
319
320         Global::viewportHeight = size().height();
321
322         // Draw coordinate axes
323         painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
324         painter.DrawLine(0, -16384, 0, 16384);
325         painter.DrawLine(-16384, 0, 16384, 0);
326
327         // The top level document takes care of rendering for us...
328 //      document.Draw(&painter);
329         // Not any more it doesn't...
330         RenderObjects(&painter, &document);
331
332 #if 0
333         if (toolAction)
334         {
335                 painter.SetPen(QPen(QColor(200, 100, 0, 255), 1.0, Qt::DashLine));
336                 painter.DrawCrosshair(oldPoint);
337                 toolAction->Draw(&painter);
338         }
339 #else
340         if (Global::tool)
341         {
342                 painter.SetPen(QPen(QColor(200, 100, 0, 255), 1.0, Qt::DashLine));
343                 painter.DrawCrosshair(oldPoint);
344                 ToolDraw(&painter);
345         }
346 #endif
347
348 #if 1
349         if (Global::selectionInProgress)
350         {
351                 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
352                 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
353                 painter.DrawRect(Global::selection);
354         }
355 #endif
356 }
357
358
359 void DrawingView::RenderObjects(Painter * painter, Container * c)
360 {
361         std::vector<void *>::iterator i;
362
363         for(i=c->objects.begin(); i!=c->objects.end(); i++)
364         {
365                 Object * obj = (Object *)(*i);
366                 float scaledThickness = Global::scale * obj->thickness;
367                 painter->SetPen(obj->color, Global::zoom * scaledThickness, obj->style);
368                 painter->SetBrush(obj->color);
369
370                 if (obj->selected || obj->hitObject)
371                         painter->SetPen(0xFF0000, Global::zoom * scaledThickness, LSDash);
372
373                 switch (obj->type)
374                 {
375                 case OTLine:
376                 {
377                         Line * l = (Line *)obj;
378                         painter->DrawLine(l->p1, l->p2);
379
380                         if (l->hitPoint[0])
381                                 painter->DrawHandle(l->p1);
382
383                         if (l->hitPoint[1])
384                                 painter->DrawHandle(l->p2);
385
386                         break;
387                 }
388                 case OTCircle:
389                 {
390                         Circle * ci = (Circle *)obj;
391                         painter->SetBrush(QBrush(Qt::NoBrush));
392                         painter->DrawEllipse(ci->p1, ci->radius, ci->radius);
393                         break;
394                 }
395                 case OTArc:
396                 {
397                         Arc * a = (Arc *)obj;
398                         painter->DrawArc(a->p1, a->radius, a->angle1, a->angle2);
399                         break;
400                 }
401                 case OTDimension:
402                 {
403                         Dimension * d = (Dimension *)obj;
404
405                         Vector v(d->p1, d->p2);
406                         double angle = v.Angle();
407                         Vector unit = v.Unit();
408                         Vector linePt1 = d->p1, linePt2 = d->p2;
409                         Vector ortho;
410                         double x1, y1, length;
411
412                         if (d->subtype == DTLinearVert)
413                         {
414                                 if ((angle < 0) || (angle > PI))
415                                 {
416                                         x1 = (d->p1.x > d->p2.x ? d->p1.x : d->p2.x);
417                                         y1 = (d->p1.y > d->p2.y ? d->p1.y : d->p2.y);
418                                         ortho = Vector(1.0, 0);
419                                         angle = PI3_OVER_2;
420                                 }
421                                 else
422                                 {
423                                         x1 = (d->p1.x > d->p2.x ? d->p2.x : d->p1.x);
424                                         y1 = (d->p1.y > d->p2.y ? d->p2.y : d->p1.y);
425                                         ortho = Vector(-1.0, 0);
426                                         angle = PI_OVER_2;
427                                 }
428
429                                 linePt1.x = linePt2.x = x1;
430                                 length = fabs(d->p1.y - d->p2.y);
431                         }
432                         else if (d->subtype == DTLinearHorz)
433                         {
434                                 if ((angle < PI_OVER_2) || (angle > PI3_OVER_2))
435                                 {
436                                         x1 = (d->p1.x > d->p2.x ? d->p1.x : d->p2.x);
437                                         y1 = (d->p1.y > d->p2.y ? d->p1.y : d->p2.y);
438                                         ortho = Vector(0, 1.0);
439                                         angle = 0;
440                                 }
441                                 else
442                                 {
443                                         x1 = (d->p1.x > d->p2.x ? d->p2.x : d->p1.x);
444                                         y1 = (d->p1.y > d->p2.y ? d->p2.y : d->p1.y);
445                                         ortho = Vector(0, -1.0);
446                                         angle = PI;
447                                 }
448
449                                 linePt1.y = linePt2.y = y1;
450                                 length = fabs(d->p1.x - d->p2.x);
451                         }
452                         else if (d->subtype == DTLinear)
453                         {
454                                 angle = Vector(linePt1, linePt2).Angle();
455                                 ortho = Vector::Normal(linePt1, linePt2);
456                                 length = v.Magnitude();
457                         }
458
459                         unit = Vector(linePt1, linePt2).Unit();
460
461                         Point p1 = linePt1 + (ortho * 10.0 * scaledThickness);
462                         Point p2 = linePt2 + (ortho * 10.0 * scaledThickness);
463                         Point p3 = linePt1 + (ortho * 16.0 * scaledThickness);
464                         Point p4 = linePt2 + (ortho * 16.0 * scaledThickness);
465                         Point p5 = d->p1 + (ortho * 4.0 * scaledThickness);
466                         Point p6 = d->p2 + (ortho * 4.0 * scaledThickness);
467
468                 /*
469                 The numbers hardcoded into here, what are they?
470                 I believe they are pixels.
471                 */
472                         // Draw extension lines (if certain type)
473                         painter->DrawLine(p3, p5);
474                         painter->DrawLine(p4, p6);
475
476                         // Calculate whether or not the arrowheads are too crowded to put inside
477                         // the extension lines. 9.0 is the length of the arrowhead.
478                         double t = Geometry::ParameterOfLineAndPoint(linePt1, linePt2, linePt2 - (unit * 9.0 * scaledThickness));
479                 //printf("Dimension::Draw(): t = %lf\n", t);
480
481                 // On the screen, it's acting like this is actually 58%...
482                 // This is correct, we want it to happen at > 50%
483                         if (t > 0.58)
484                         {
485                                 // Draw main dimension line + arrowheads
486                                 painter->DrawLine(p1, p2);
487                                 painter->DrawArrowhead(p1, p2, scaledThickness);
488                                 painter->DrawArrowhead(p2, p1, scaledThickness);
489                         }
490                         else
491                         {
492                                 // Draw outside arrowheads
493                                 Point p7 = p1 - (unit * 9.0 * scaledThickness);
494                                 Point p8 = p2 + (unit * 9.0 * scaledThickness);
495                                 painter->DrawArrowhead(p1, p7, scaledThickness);
496                                 painter->DrawArrowhead(p2, p8, scaledThickness);
497                                 painter->DrawLine(p1, p1 - (unit * 14.0 * scaledThickness));
498                                 painter->DrawLine(p2, p2 + (unit * 14.0 * scaledThickness));
499                         }
500
501                         // Draw length of dimension line...
502                         painter->SetFont(QFont("Arial", 8.0 * Global::zoom * scaledThickness));
503                         Point ctr = p2 + (Vector(p2, p1) / 2.0);
504
505                 #if 0
506                         QString dimText = QString("%1\"").arg(Vector(endpoint - position).Magnitude());
507                 #else
508                         QString dimText;
509
510                         if (length < 12.0)
511                                 dimText = QString("%1\"").arg(length);
512                         else
513                         {
514                                 double feet = (double)((int)length / 12);
515                                 double inches = length - (feet * 12.0);
516
517                                 if (inches == 0)
518                                         dimText = QString("%1'").arg(feet);
519                                 else
520                                         dimText = QString("%1' %2\"").arg(feet).arg(inches);
521                         }
522                 #endif
523
524                         painter->DrawAngledText(ctr, angle, dimText, scaledThickness);
525
526                         break;
527                 }
528                 case OTText:
529                 {
530                         Text * t = (Text *)obj;
531                         painter->DrawTextObject(t->p1, t->s.c_str(), scaledThickness);
532                         break;
533                 }
534                 default:
535                         break;
536                 }
537         }
538 }
539
540
541 void DrawingView::DeleteSelectedItems(void)
542 {
543         std::vector<void *>::iterator i = document.objects.begin();
544
545         while (i != document.objects.end())
546         {
547                 Object * obj = (Object *)(*i);
548
549                 if (obj->selected)
550                 {
551                         delete obj;
552                         document.objects.erase(i);
553                 }
554                 else
555                         i++;
556         }
557 }
558
559
560 void DrawingView::ClearSelection(void)
561 {
562         std::vector<void *>::iterator i;
563
564         for(i=document.objects.begin(); i!=document.objects.end(); i++)
565                 ((Object *)(*i))->selected = false;
566 }
567
568
569 void DrawingView::AddHoveredToSelection(void)
570 {
571         std::vector<void *>::iterator i;
572
573         for(i=document.objects.begin(); i!=document.objects.end(); i++)
574         {
575                 if (((Object *)(*i))->hovered)
576                         ((Object *)(*i))->selected = true;
577         }
578 }
579
580
581 void DrawingView::GetSelection(std::vector<void *> & v)
582 {
583         v.clear();
584         std::vector<void *>::iterator i;
585
586         for(i=document.objects.begin(); i!=document.objects.end(); i++)
587         {
588                 if (((Object *)(*i))->selected)
589                         v.push_back(*i);
590         }
591 }
592
593
594 void DrawingView::GetHovered(std::vector<void *> & v)
595 {
596         v.clear();
597         std::vector<void *>::iterator i;
598
599         for(i=document.objects.begin(); i!=document.objects.end(); i++)
600         {
601                 if (((Object *)(*i))->hovered)
602 //              {
603 //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"));
604                         v.push_back(*i);
605 //              }
606         }
607 }
608
609
610 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
611 {
612         Global::screenSize = Vector(size().width(), size().height());
613         UpdateGridBackground();
614 }
615
616
617 void DrawingView::ToolMouse(int mode, Point p)
618 {
619         if (Global::tool == TTLine)
620                 LineHandler(mode, p);
621 }
622
623
624 void DrawingView::ToolDraw(Painter * painter)
625 {
626         if (Global::tool == TTLine)
627         {
628                 if (Global::toolState == TSNone)
629                 {
630                         painter->DrawHandle(toolPoint[0]);
631                 }
632                 else if ((Global::toolState == TSPoint2) && shiftDown)
633                 {
634                         painter->DrawHandle(toolPoint[1]);
635                 }
636                 else
637                 {
638                         painter->DrawLine(toolPoint[0], toolPoint[1]);
639                         painter->DrawHandle(toolPoint[1]);
640
641                         Vector v(toolPoint[0], toolPoint[1]);
642                         double absAngle = v.Angle() * RADIANS_TO_DEGREES;
643                         double absLength = v.Magnitude();
644                         QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
645                         text = text.arg(absLength).arg(absAngle);
646                         painter->DrawInformativeText(text);
647                 }
648         }
649 }
650
651
652 void DrawingView::LineHandler(int mode, Point p)
653 {
654         switch (mode)
655         {
656         case ToolMouseDown:
657                 if (Global::toolState == TSNone)
658                         toolPoint[0] = p;
659                 else
660                         toolPoint[1] = p;
661
662                 break;
663         case ToolMouseMove:
664                 if (Global::toolState == TSNone)
665                         toolPoint[0] = p;
666                 else
667                         toolPoint[1] = p;
668
669                 break;
670         case ToolMouseUp:
671                 if (Global::toolState == TSNone)
672                 {
673                         Global::toolState = TSPoint2;
674                         // Prevent spurious line from drawing...
675                         toolPoint[1] = toolPoint[0];
676                 }
677                 else if ((Global::toolState == TSPoint2) && shiftDown)
678                 {
679                         // Key override is telling us to make a new line, not continue the
680                         // previous one.
681                         toolPoint[0] = toolPoint[1];
682                 }
683                 else
684                 {
685                         Line * l = new Line(toolPoint[0], toolPoint[1]);
686                         document.objects.push_back(l);
687                         toolPoint[0] = toolPoint[1];
688                 }
689         }
690 }
691
692
693 void DrawingView::mousePressEvent(QMouseEvent * event)
694 {
695         if (event->button() == Qt::LeftButton)
696         {
697                 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
698
699                 // Handle tool processing, if any
700                 if (Global::tool)
701                 {
702                         if (Global::snapToGrid)
703                                 point = SnapPointToGrid(point);
704
705                         //Also, may want to figure out if hovering over a snap point on an object,
706                         //snap to grid if not.
707                         // Snap to object point if valid...
708 //                      if (Global::snapPointIsValid)
709 //                              point = Global::snapPoint;
710                         
711                         ToolMouse(ToolMouseDown, point);
712                         return;
713                 }
714
715                 // Clear the selection only if CTRL isn't being held on click
716                 if (!ctrlDown)
717                         ClearSelection();
718
719                 // If any objects are being hovered on click, add them to the selection
720                 // & return
721                 if (numHovered > 0)
722                 {
723                         AddHoveredToSelection();
724                         update();       // needed??
725                         GetHovered(hover);      // prolly needed
726
727                         // Needed for grab & moving objects
728                         if (Global::snapToGrid)
729                                 oldPoint = SnapPointToGrid(point);
730
731                         return;
732                 }
733
734                 // Didn't hit any object and not using a tool, so do a selection rectangle
735                 Global::selectionInProgress = true;
736                 Global::selection.setTopLeft(QPointF(point.x, point.y));
737                 Global::selection.setBottomRight(QPointF(point.x, point.y));
738         }
739         else if (event->button() == Qt::MiddleButton)
740         {
741                 scrollDrag = true;
742                 oldPoint = Vector(event->x(), event->y());
743                 // Should also change the mouse pointer as well...
744                 setCursor(Qt::SizeAllCursor);
745         }
746 }
747
748
749 void DrawingView::mouseMoveEvent(QMouseEvent * event)
750 {
751         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
752         Global::selection.setBottomRight(QPointF(point.x, point.y));
753         // Only needs to be done here, as mouse down is always preceded by movement
754         Global::snapPointIsValid = false;
755
756         // Scrolling...
757         if (event->buttons() & Qt::MiddleButton)
758         {
759                 point = Vector(event->x(), event->y());
760                 // Since we're using Qt coords for scrolling, we have to adjust them here to
761                 // conform to Cartesian coords, since the origin is using Cartesian. :-)
762                 Vector delta(oldPoint, point);
763                 delta /= Global::zoom;
764                 delta.y = -delta.y;
765                 Global::origin -= delta;
766
767                 UpdateGridBackground();
768                 update();
769                 oldPoint = point;
770                 return;
771         }
772
773         // If we're doing a selection rect, see if any objects are engulfed by it
774         // (implies left mouse button held down)
775         if (Global::selectionInProgress)
776         {
777                 CheckObjectBounds();
778                 update();
779                 return;
780         }
781
782         // Handle object movement (left button down & over an object)
783         if ((event->buttons() & Qt::LeftButton) && numHovered)
784         {
785                 if (Global::snapToGrid)
786                         point = SnapPointToGrid(point);
787
788                 Point delta = point - oldPoint;
789                 Object * obj = (Object *)hover[0];
790 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
791 //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"));
792
793                 switch (obj->type)
794                 {
795                 case OTLine:
796                 {
797                         Line * l = (Line *)obj;
798
799                         if (l->hitPoint[0])
800                                 l->p1 = point;
801                         else if (l->hitPoint[1])
802                                 l->p2 = point;
803                         else if (l->hitObject)
804                         {
805                                 l->p1 += delta;
806                                 l->p2 += delta;
807                         }
808
809                         break;
810                 }
811                 default:
812                         break;
813                 }
814
815                 update();
816                 oldPoint = point;
817                 return;
818         }
819
820         // Do object hit testing...
821         bool needUpdate = HitTestObjects(point);
822
823         // Do tool handling, if any are active...
824         if (Global::tool)
825         {
826                 if (Global::snapToGrid)
827                         point = SnapPointToGrid(point);
828
829                 ToolMouse(ToolMouseMove, point);
830         }
831
832         // This is used to draw the tool crosshair...
833         oldPoint = point;
834
835         if (needUpdate || Global::tool)
836                 update();
837 }
838
839
840 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
841 {
842         if (event->button() == Qt::LeftButton)
843         {
844 #if 0
845                 document.PointerReleased();
846 #endif
847
848 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
849 //could set it up to use the document's update function (assumes that all object updates
850 //are being reported correctly:
851 //              if (document.NeedsUpdate())
852 //              if (collided)
853                         update();       // Do an update if collided with at least *one* object in the document
854
855 #if 0
856                 if (toolAction)
857                         toolAction->MouseReleased();
858 #else
859                 if (Global::tool)
860                 {
861                         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
862 //                      ToolMouseUp(point);
863                         ToolMouse(ToolMouseUp, point);
864                         return;
865                 }
866 #endif
867
868                 if (Global::selectionInProgress)
869                 {
870                         // Select all the stuff inside of selection
871                         Global::selectionInProgress = false;
872
873                         // Clear our vectors
874                         select.clear();
875                         hover.clear();
876
877                         // Scoop 'em up
878                         std::vector<void *>::iterator i;
879
880                         for(i=document.objects.begin(); i!=document.objects.end(); i++)
881                         {
882                                 if (((Object *)(*i))->selected)
883                                         select.push_back(*i);
884
885 //hmm, this is no good, too late to do any good :-P
886 //                              if ((*i)->hovered)
887 //                                      hover.push_back(*i);
888                         }
889                 }
890         }
891         else if (event->button() == Qt::MiddleButton)
892         {
893                 scrollDrag = false;
894                 setCursor(Qt::ArrowCursor);
895         }
896 }
897
898
899 void DrawingView::wheelEvent(QWheelEvent * event)
900 {
901         double zoomFactor = 1.25;
902         QSize sizeWin = size();
903         Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
904         center = Painter::QtToCartesianCoords(center);
905
906         // This is not centering for some reason. Need to figure out why. :-/
907         if (event->delta() > 0)
908         {
909                 Vector newOrigin = center - ((center - Global::origin) / zoomFactor);
910                 Global::origin = newOrigin;
911                 Global::zoom *= zoomFactor;
912         }
913         else
914         {
915                 Vector newOrigin = center + ((-center + Global::origin) * zoomFactor);
916                 Global::origin = newOrigin;
917                 Global::zoom /= zoomFactor;
918         }
919
920 #if 1
921 //      Global::gridSpacing = gridPixels / Painter::zoom;
922 //      UpdateGridBackground();
923         SetGridSize(Global::gridSpacing * Global::zoom);
924         update();
925 //      zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Global::gridSpacing));
926 #endif
927 }
928
929
930 void DrawingView::keyPressEvent(QKeyEvent * event)
931 {
932 #if 0
933         if (toolAction)
934                 toolAction->KeyDown(event->key());
935 #endif
936         bool oldShift = shiftDown;
937         bool oldCtrl = ctrlDown;
938
939         if (event->key() == Qt::Key_Shift)
940                 shiftDown = true;
941         else if (event->key() == Qt::Key_Control)
942                 ctrlDown = true;
943
944         if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
945                 update();
946 }
947
948
949 void DrawingView::keyReleaseEvent(QKeyEvent * event)
950 {
951 #if 0
952         if (toolAction)
953                 toolAction->KeyReleased(event->key());
954 #endif
955         bool oldShift = shiftDown;
956         bool oldCtrl = ctrlDown;
957
958         if (event->key() == Qt::Key_Shift)
959                 shiftDown = false;
960         else if (event->key() == Qt::Key_Control)
961                 ctrlDown = false;
962
963         if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
964                 update();
965 }
966
967 //
968 // This looks strange, but it's really quite simple: We want a point that's
969 // more than half-way to the next grid point to snap there while conversely we
970 // want a point that's less than half-way to to the next grid point then snap
971 // to the one before it. So we add half of the grid spacing to the point, then
972 // divide by it so that we can remove the fractional part, then multiply it
973 // back to get back to the correct answer.
974 //
975 Point DrawingView::SnapPointToGrid(Point point)
976 {
977         point += Global::gridSpacing / 2.0;             // *This* adds to Z!!!
978         point /= Global::gridSpacing;
979         point.x = floor(point.x);//need to fix this for negative numbers...
980         point.y = floor(point.y);
981         point.z = 0;                                    // Make *sure* Z doesn't go anywhere!!!
982         point *= Global::gridSpacing;
983         return point;
984 }
985
986
987 void DrawingView::CheckObjectBounds(void)
988 {
989         std::vector<void *>::iterator i;
990         numSelected = 0;
991
992         for(i=document.objects.begin(); i!=document.objects.end(); i++)
993         {
994                 Object * obj = (Object *)(*i);
995                 obj->selected = false;
996
997                 switch (obj->type)
998                 {
999                 case OTLine:
1000                 {
1001                         Line * l = (Line *)obj;
1002
1003                         if (Global::selection.contains(l->p1.x, l->p1.y) && Global::selection.contains(l->p2.x, l->p2.y))
1004                                 l->selected = true;
1005
1006                         break;
1007                 }
1008                 case OTCircle:
1009                 {
1010                         Circle * c = (Circle *)obj;
1011
1012                         if (Global::selection.contains(c->p1.x - c->radius, c->p1.y - c->radius) && Global::selection.contains(c->p1.x + c->radius, c->p1.y + c->radius))
1013                                 c->selected = true;
1014
1015                         break;
1016                 }
1017                 case OTArc:
1018                 {
1019                         Arc * a = (Arc *)obj;
1020
1021                         double start = a->angle1;
1022                         double end = start + a->angle2;
1023                         QPointF p1(cos(start), sin(start));
1024                         QPointF p2(cos(end), sin(end));
1025                         QRectF bounds(p1, p2);
1026
1027 #if 1
1028                         // Swap X/Y coordinates if they're backwards...
1029                         if (bounds.left() > bounds.right())
1030                         {
1031                                 double temp = bounds.left();
1032                                 bounds.setLeft(bounds.right());
1033                                 bounds.setRight(temp);
1034                         }
1035
1036                         if (bounds.bottom() > bounds.top())
1037                         {
1038                                 double temp = bounds.bottom();
1039                                 bounds.setBottom(bounds.top());
1040                                 bounds.setTop(temp);
1041                         }
1042 #else
1043                         // Doesn't work as advertised! For shame!
1044                         bounds = bounds.normalized();
1045 #endif
1046
1047                         // If the end of the arc is before the beginning, add 360 degrees to it
1048                         if (end < start)
1049                                 end += 2.0 * PI;
1050
1051                         // Adjust the bounds depending on which axes are crossed
1052                         if ((start < PI_OVER_2) && (end > PI_OVER_2))
1053                                 bounds.setTop(1.0);
1054
1055                         if ((start < PI) && (end > PI))
1056                                 bounds.setLeft(-1.0);
1057
1058                         if ((start < (PI + PI_OVER_2)) && (end > (PI + PI_OVER_2)))
1059                                 bounds.setBottom(-1.0);
1060
1061                         if ((start < (2.0 * PI)) && (end > (2.0 * PI)))
1062                                 bounds.setRight(1.0);
1063
1064                         if ((start < ((2.0 * PI) + PI_OVER_2)) && (end > ((2.0 * PI) + PI_OVER_2)))
1065                                 bounds.setTop(1.0);
1066
1067                         if ((start < (3.0 * PI)) && (end > (3.0 * PI)))
1068                                 bounds.setLeft(-1.0);
1069
1070                         if ((start < ((3.0 * PI) + PI_OVER_2)) && (end > ((3.0 * PI) + PI_OVER_2)))
1071                                 bounds.setBottom(-1.0);
1072
1073                         bounds.setTopLeft(QPointF(bounds.left() * a->radius, bounds.top() * a->radius));
1074                         bounds.setBottomRight(QPointF(bounds.right() * a->radius, bounds.bottom() * a->radius));
1075                         bounds.translate(a->p1.x, a->p1.y);
1076
1077                         if (Global::selection.contains(bounds))
1078                                 a->selected = true;
1079
1080                         break;
1081                 }
1082                 default:
1083                         break;
1084                 }
1085
1086                 if (obj->selected)
1087                         numSelected++;
1088         }
1089 }
1090
1091
1092 bool DrawingView::HitTestObjects(Point point)
1093 {
1094         std::vector<void *>::iterator i;
1095         numHovered = 0;
1096         bool needUpdate = false;
1097
1098         for(i=document.objects.begin(); i!=document.objects.end(); i++)
1099         {
1100                 Object * obj = (Object *)(*i);
1101
1102                 switch (obj->type)
1103                 {
1104                 case OTLine:
1105                 {
1106                         Line * l = (Line *)obj;
1107                         bool oldHP0 = l->hitPoint[0], oldHP1 = l->hitPoint[1], oldHO = l->hitObject;
1108                         l->hitPoint[0] = l->hitPoint[1] = l->hitObject = false;
1109                         Vector lineSegment = l->p2 - l->p1;
1110                         Vector v1 = point - l->p1;
1111                         Vector v2 = point - l->p2;
1112                         double t = Geometry::ParameterOfLineAndPoint(l->p1, l->p2, point);
1113                         double distance;
1114
1115                         if (t < 0.0)
1116                                 distance = v1.Magnitude();
1117                         else if (t > 1.0)
1118                                 distance = v2.Magnitude();
1119                         else
1120                                 // distance = ?Det?(ls, v1) / |ls|
1121                                 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
1122                                         / lineSegment.Magnitude());
1123
1124                         if ((v1.Magnitude() * Global::zoom) < 8.0)
1125                         {
1126                                 l->hitPoint[0] = true;
1127 //              snapPoint = l->p1;
1128 //              snapPointIsValid = true;
1129                         }
1130                         else if ((v2.Magnitude() * Global::zoom) < 8.0)
1131                         {
1132                                 l->hitPoint[1] = true;
1133 //              snapPoint = l->p2;
1134 //              snapPointIsValid = true;
1135                         }
1136                         else if ((distance * Global::zoom) < 5.0)
1137                                 l->hitObject = true;
1138
1139 //                      bool oldHovered = l->hovered;
1140                         l->hovered = (l->hitPoint[0] || l->hitPoint[1] || l->hitObject ? true : false);
1141 //                      l->hovered = l->hitObject;
1142
1143 //                      if (oldHovered != l->hovered)
1144                         if ((oldHP0 != l->hitPoint[0]) || (oldHP1 != l->hitPoint[1]) || (oldHO != l->hitObject))
1145                                 needUpdate = true;
1146
1147                         break;
1148                 }
1149                 case OTCircle:
1150                 {
1151                         Circle * c = (Circle *)obj;
1152
1153
1154                         break;
1155                 }
1156                 default:
1157                         break;
1158                 }
1159
1160                 if (obj->hovered)
1161 //              {
1162                         numHovered++;
1163 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
1164 //              }
1165         }
1166
1167         return needUpdate;
1168 }
1169
1170
1171 #if 0
1172         // This returns true if we've moved over an object...
1173         if (document.PointerMoved(point)) // <-- This
1174         // This is where the object would do automagic dragging & shit. Since we don't
1175         // do that anymore, we need a strategy to handle it.
1176         {
1177
1178 /*
1179 Now objects handle mouse move snapping as well. The code below mainly works only
1180 for tools; we need to fix it so that objects work as well...
1181
1182 There's a problem with the object point snapping in that it's dependent on the
1183 order of the objects in the document. Most likely this is because it counts the
1184 selected object last and thus fucks up the algorithm. Need to fix this...
1185
1186
1187 */
1188                 // Do object snapping here. Grid snapping on mouse down is done in the
1189                 // objects themselves, only because we have to hit test the raw point,
1190                 // not the snapped point. There has to be a better way...!
1191                 if (document.penultimateObjectHovered)
1192                 {
1193                         // Two objects are hovered, see if we have an intersection point
1194                         if ((document.lastObjectHovered->type == OTLine) && (document.penultimateObjectHovered->type == OTLine))
1195                         {
1196                                 double t;
1197                                 int n = Geometry::Intersects((Line *)document.lastObjectHovered, (Line *)document.penultimateObjectHovered, &t);
1198
1199                                 if (n == 1)
1200                                 {
1201                                         Global::snapPoint = document.lastObjectHovered->GetPointAtParameter(t);
1202                                         Global::snapPointIsValid = true;
1203                                 }
1204                         }
1205                         else if ((document.lastObjectHovered->type == OTCircle) && (document.penultimateObjectHovered->type == OTCircle))
1206                         {
1207                                 Point p1, p2;
1208                                 int n = Geometry::Intersects((Circle *)document.lastObjectHovered, (Circle *)document.penultimateObjectHovered, 0, 0, 0, 0, &p1, &p2);
1209
1210                                 if (n == 1)
1211                                 {
1212                                         Global::snapPoint = p1;
1213                                         Global::snapPointIsValid = true;
1214                                 }
1215                                 else if (n == 2)
1216                                 {
1217                                         double d1 = Vector(point, p1).Magnitude();
1218                                         double d2 = Vector(point, p2).Magnitude();
1219
1220                                         if (d1 < d2)
1221                                                 Global::snapPoint = p1;
1222                                         else
1223                                                 Global::snapPoint = p2;
1224
1225                                         Global::snapPointIsValid = true;
1226                                 }
1227                         }
1228                 }
1229 //              else
1230 //              {
1231                         // Otherwise, it was a single object hovered...
1232 //              }
1233         }
1234
1235         if (toolAction)
1236         {
1237                 if (Global::snapToGrid)
1238                         point = Global::SnapPointToGrid(point);
1239
1240                 // We always snap to object points, and they take precendence over
1241                 // grid points...
1242                 if (Global::snapPointIsValid)
1243                         point = Global::snapPoint;
1244
1245                 toolAction->MouseMoved(point);
1246         }
1247 #else
1248 #endif
1249