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