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