]> Shamusworld >> Repos - architektonas/blob - src/drawingview.cpp
aeb764cc5781ff38364f7a99eb154c4898c360bd
[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                 {
358                         Line * l = (Line *)obj;
359                         painter->DrawLine(l->p[0], l->p[1]);
360
361                         if (l->hitPoint[0])
362                                 painter->DrawHandle(l->p[0]);
363
364                         if (l->hitPoint[1])
365                                 painter->DrawHandle(l->p[1]);
366
367                         break;
368                 }
369                 case OTCircle:
370                 {
371                         Circle * ci = (Circle *)obj;
372                         painter->SetBrush(QBrush(Qt::NoBrush));
373                         painter->DrawEllipse(ci->p[0], ci->radius, ci->radius);
374                         break;
375                 }
376                 case OTArc:
377                 {
378                         Arc * a = (Arc *)obj;
379                         painter->DrawArc(a->p[0], a->radius, a->angle1, a->angle2);
380                         break;
381                 }
382                 case OTDimension:
383                 {
384                         Dimension * d = (Dimension *)obj;
385
386                         Vector v(d->p[0], d->p[1]);
387                         double angle = v.Angle();
388                         Vector unit = v.Unit();
389                         Vector linePt1 = d->p[0], linePt2 = d->p[1];
390                         Vector ortho;
391                         double x1, y1, length;
392
393                         if (d->subtype == DTLinearVert)
394                         {
395                                 if ((angle < 0) || (angle > PI))
396                                 {
397                                         x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
398                                         y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
399                                         ortho = Vector(1.0, 0);
400                                         angle = PI3_OVER_2;
401                                 }
402                                 else
403                                 {
404                                         x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
405                                         y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
406                                         ortho = Vector(-1.0, 0);
407                                         angle = PI_OVER_2;
408                                 }
409
410                                 linePt1.x = linePt2.x = x1;
411                                 length = fabs(d->p[0].y - d->p[1].y);
412                         }
413                         else if (d->subtype == DTLinearHorz)
414                         {
415                                 if ((angle < PI_OVER_2) || (angle > PI3_OVER_2))
416                                 {
417                                         x1 = (d->p[0].x > d->p[1].x ? d->p[0].x : d->p[1].x);
418                                         y1 = (d->p[0].y > d->p[1].y ? d->p[0].y : d->p[1].y);
419                                         ortho = Vector(0, 1.0);
420                                         angle = 0;
421                                 }
422                                 else
423                                 {
424                                         x1 = (d->p[0].x > d->p[1].x ? d->p[1].x : d->p[0].x);
425                                         y1 = (d->p[0].y > d->p[1].y ? d->p[1].y : d->p[0].y);
426                                         ortho = Vector(0, -1.0);
427                                         angle = PI;
428                                 }
429
430                                 linePt1.y = linePt2.y = y1;
431                                 length = fabs(d->p[0].x - d->p[1].x);
432                         }
433                         else if (d->subtype == DTLinear)
434                         {
435                                 angle = Vector(linePt1, linePt2).Angle();
436                                 ortho = Vector::Normal(linePt1, linePt2);
437                                 length = v.Magnitude();
438                         }
439
440                         unit = Vector(linePt1, linePt2).Unit();
441
442                         Point p1 = linePt1 + (ortho * 10.0 * scaledThickness);
443                         Point p2 = linePt2 + (ortho * 10.0 * scaledThickness);
444                         Point p3 = linePt1 + (ortho * 16.0 * scaledThickness);
445                         Point p4 = linePt2 + (ortho * 16.0 * scaledThickness);
446                         Point p5 = d->p[0] + (ortho * 4.0 * scaledThickness);
447                         Point p6 = d->p[1] + (ortho * 4.0 * scaledThickness);
448
449                 /*
450                 The numbers hardcoded into here, what are they?
451                 I believe they are pixels.
452                 */
453                         // Draw extension lines (if certain type)
454                         painter->DrawLine(p3, p5);
455                         painter->DrawLine(p4, p6);
456
457                         // Calculate whether or not the arrowheads are too crowded to put inside
458                         // the extension lines. 9.0 is the length of the arrowhead.
459                         double t = Geometry::ParameterOfLineAndPoint(linePt1, linePt2, linePt2 - (unit * 9.0 * scaledThickness));
460                 //printf("Dimension::Draw(): t = %lf\n", t);
461
462                         // On the screen, it's acting like this is actually 58%...
463                         // This is correct, we want it to happen at > 50%
464                         if (t > 0.58)
465                         {
466                                 // Draw main dimension line + arrowheads
467                                 painter->DrawLine(p1, p2);
468                                 painter->DrawArrowhead(p1, p2, scaledThickness);
469                                 painter->DrawArrowhead(p2, p1, scaledThickness);
470                         }
471                         else
472                         {
473                                 // Draw outside arrowheads
474                                 Point p7 = p1 - (unit * 9.0 * scaledThickness);
475                                 Point p8 = p2 + (unit * 9.0 * scaledThickness);
476                                 painter->DrawArrowhead(p1, p7, scaledThickness);
477                                 painter->DrawArrowhead(p2, p8, scaledThickness);
478                                 painter->DrawLine(p1, p1 - (unit * 14.0 * scaledThickness));
479                                 painter->DrawLine(p2, p2 + (unit * 14.0 * scaledThickness));
480                         }
481
482                         // Draw length of dimension line...
483                         painter->SetFont(QFont("Arial", 8.0 * Global::zoom * scaledThickness));
484                         Point ctr = p2 + (Vector(p2, p1) / 2.0);
485
486                 #if 0
487                         QString dimText = QString("%1\"").arg(Vector(endpoint - position).Magnitude());
488                 #else
489                         QString dimText;
490
491                         if (length < 12.0)
492                                 dimText = QString("%1\"").arg(length);
493                         else
494                         {
495                                 double feet = (double)((int)length / 12);
496                                 double inches = length - (feet * 12.0);
497
498                                 if (inches == 0)
499                                         dimText = QString("%1'").arg(feet);
500                                 else
501                                         dimText = QString("%1' %2\"").arg(feet).arg(inches);
502                         }
503                 #endif
504
505                         painter->DrawAngledText(ctr, angle, dimText, scaledThickness);
506
507                         break;
508                 }
509                 case OTText:
510                 {
511                         Text * t = (Text *)obj;
512                         painter->DrawTextObject(t->p[0], t->s.c_str(), scaledThickness);
513                         break;
514                 }
515                 default:
516                         break;
517                 }
518         }
519 }
520
521
522 void DrawingView::AddHoveredToSelection(void)
523 {
524         std::vector<void *>::iterator i;
525
526         for(i=document.objects.begin(); i!=document.objects.end(); i++)
527         {
528                 if (((Object *)(*i))->hovered)
529                         ((Object *)(*i))->selected = true;
530         }
531 }
532
533
534 void DrawingView::GetSelection(std::vector<void *> & v)
535 {
536         v.clear();
537         std::vector<void *>::iterator i;
538
539         for(i=document.objects.begin(); i!=document.objects.end(); i++)
540         {
541                 if (((Object *)(*i))->selected)
542                         v.push_back(*i);
543         }
544 }
545
546
547 void DrawingView::GetHovered(std::vector<void *> & v)
548 {
549         v.clear();
550         std::vector<void *>::iterator i;
551
552         for(i=document.objects.begin(); i!=document.objects.end(); i++)
553         {
554                 if (((Object *)(*i))->hovered)
555 //              {
556 //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"));
557                         v.push_back(*i);
558 //              }
559         }
560 }
561
562
563 void DrawingView::resizeEvent(QResizeEvent * /*event*/)
564 {
565         Global::screenSize = Vector(size().width(), size().height());
566         UpdateGridBackground();
567 }
568
569
570 void DrawingView::ToolHandler(int mode, Point p)
571 {
572         if (Global::tool == TTLine)
573                 LineHandler(mode, p);
574         else if (Global::tool == TTRotate)
575                 RotateHandler(mode, p);
576 }
577
578
579 void DrawingView::ToolDraw(Painter * painter)
580 {
581         if (Global::tool == TTLine)
582         {
583                 if (Global::toolState == TSNone)
584                 {
585                         painter->DrawHandle(toolPoint[0]);
586                 }
587                 else if ((Global::toolState == TSPoint2) && shiftDown)
588                 {
589                         painter->DrawHandle(toolPoint[1]);
590                 }
591                 else
592                 {
593                         painter->DrawLine(toolPoint[0], toolPoint[1]);
594                         painter->DrawHandle(toolPoint[1]);
595
596                         Vector v(toolPoint[0], toolPoint[1]);
597                         double absAngle = v.Angle() * RADIANS_TO_DEGREES;
598                         double absLength = v.Magnitude();
599                         QString text = tr("Length: %1 in.\n") + QChar(0x2221) + tr(": %2");
600                         text = text.arg(absLength).arg(absAngle);
601                         painter->DrawInformativeText(text);
602                 }
603         }
604         else if (Global::tool == TTRotate)
605         {
606                 if ((Global::toolState == TSNone) || (Global::toolState == TSPoint1))
607                         painter->DrawHandle(toolPoint[0]);
608                 else if ((Global::toolState == TSPoint2) && shiftDown)
609                         painter->DrawHandle(toolPoint[1]);
610                 else
611                 {
612                         if (toolPoint[0] == toolPoint[1])
613                                 return;
614                         
615                         painter->DrawLine(toolPoint[0], toolPoint[1]);
616                         // Likely we need a tool container for this... (now we do!)
617 #if 0
618                         if (ctrlDown)
619                         {
620                                 painter->SetPen(0x00FF00, 2.0, LSSolid);
621                                 overrideColor = true;
622                         }
623
624                         RenderObjects(painter, toolObjects);
625                         overrideColor = false;
626 #endif
627
628                         double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES;
629
630                         QString text = QChar(0x2221) + QObject::tr(": %1");
631                         text = text.arg(absAngle);
632
633                         if (ctrlDown)
634                                 text += " (Copy)";
635
636                         painter->DrawInformativeText(text);
637                 }
638         }
639 }
640
641
642 void DrawingView::LineHandler(int mode, Point p)
643 {
644         switch (mode)
645         {
646         case ToolMouseDown:
647                 if (Global::toolState == TSNone)
648                         toolPoint[0] = p;
649                 else
650                         toolPoint[1] = p;
651
652                 break;
653         case ToolMouseMove:
654                 if (Global::toolState == TSNone)
655                         toolPoint[0] = p;
656                 else
657                         toolPoint[1] = p;
658
659                 break;
660         case ToolMouseUp:
661                 if (Global::toolState == TSNone)
662                 {
663                         Global::toolState = TSPoint2;
664                         // Prevent spurious line from drawing...
665                         toolPoint[1] = toolPoint[0];
666                 }
667                 else if ((Global::toolState == TSPoint2) && shiftDown)
668                 {
669                         // Key override is telling us to make a new line, not continue the
670                         // previous one.
671                         toolPoint[0] = toolPoint[1];
672                 }
673                 else
674                 {
675                         Line * l = new Line(toolPoint[0], toolPoint[1]);
676                         document.objects.push_back(l);
677                         toolPoint[0] = toolPoint[1];
678                 }
679         }
680 }
681
682
683 void DrawingView::RotateHandler(int mode, Point p)
684 {
685         switch (mode)
686         {
687         case ToolMouseDown:
688                 if (Global::toolState == TSNone)
689                 {
690                         toolPoint[0] = p;
691                         SavePointsFrom(select, toolScratch);
692                         Global::toolState = TSPoint1;
693                 }
694                 else if (Global::toolState == TSPoint1)
695                         toolPoint[0] = p;
696                 else
697                         toolPoint[1] = p;
698
699                 break;
700         case ToolMouseMove:
701                 if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone))
702                         toolPoint[0] = p;
703                 else if (Global::toolState == TSPoint2)
704                 {
705                         toolPoint[1] = p;
706
707                         if (shiftDown)
708                                 return;
709
710                         double angle = Vector(toolPoint[1], toolPoint[0]).Angle();
711                         std::vector<void *>::iterator j = select.begin();
712                         std::vector<Object>::iterator i = toolScratch.begin();
713
714                         for(; i!=toolScratch.end(); i++, j++)
715                         {
716                                 Object obj = *i;
717                                 Point p1 = Geometry::RotatePointAroundPoint(obj.p[0], toolPoint[0], angle);
718                                 Point p2 = Geometry::RotatePointAroundPoint(obj.p[1], toolPoint[0], angle);
719                                 Object * obj2 = (Object *)(*j);
720                                 obj2->p[0] = p1;
721                                 obj2->p[1] = p2;
722                         }
723                 }
724
725                 break;
726         case ToolMouseUp:
727                 if (Global::toolState == TSPoint1)
728                 {
729                         Global::toolState = TSPoint2;
730                         // Prevent spurious line from drawing...
731                         toolPoint[1] = toolPoint[0];
732                 }
733                 else if ((Global::toolState == TSPoint2) && shiftDown)
734                 {
735                         // Key override is telling us to make a new line, not continue the
736                         // previous one.
737                         toolPoint[0] = toolPoint[1];
738                 }
739                 else
740                 {
741                         // Either we're finished with our rotate, or we're stamping a copy.
742                         if (ctrlDown)
743                         {
744                                 // Stamp a copy of the selection at the current rotation & bail
745                                 std::vector<void *> temp;
746                                 CopyObjects(select, temp);
747                                 ClearSelected(temp);
748                                 AddObjectsTo(document.objects, temp);
749                                 RestorePointsTo(select, toolScratch);
750                                 return;
751                         }
752
753                         toolPoint[0] = p;
754                         Global::toolState = TSPoint1;
755                         SavePointsFrom(select, toolScratch);
756                 }
757
758                 break;
759         case ToolKeyDown:
760                 // Reset the selection if shift held down...
761                 if (shiftDown)
762                         RestorePointsTo(select, toolScratch);
763
764                 break;
765         case ToolKeyUp:
766                 // Reset selection when key is let up
767                 if (!shiftDown)
768                         RotateHandler(ToolMouseMove, toolPoint[1]);
769
770                 break;
771         case ToolCleanup:
772                 RestorePointsTo(select, toolScratch);
773         }
774 }
775
776
777 void DrawingView::mousePressEvent(QMouseEvent * event)
778 {
779         if (event->button() == Qt::LeftButton)
780         {
781                 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
782
783                 // Handle tool processing, if any
784                 if (Global::tool)
785                 {
786                         if (Global::snapToGrid)
787                                 point = SnapPointToGrid(point);
788
789                         //Also, may want to figure out if hovering over a snap point on an object,
790                         //snap to grid if not.
791                         // Snap to object point if valid...
792 //                      if (Global::snapPointIsValid)
793 //                              point = Global::snapPoint;
794                         
795                         ToolHandler(ToolMouseDown, point);
796                         return;
797                 }
798
799                 // Clear the selection only if CTRL isn't being held on click
800                 if (!ctrlDown)
801                         ClearSelected(document.objects);
802 //                      ClearSelection();
803
804                 // If any objects are being hovered on click, add them to the selection
805                 // & return
806                 if (numHovered > 0)
807                 {
808                         AddHoveredToSelection();
809                         update();       // needed??
810                         GetHovered(hover);      // prolly needed
811
812                         // Needed for grab & moving objects
813                         if (Global::snapToGrid)
814                                 oldPoint = SnapPointToGrid(point);
815
816                         return;
817                 }
818
819                 // Didn't hit any object and not using a tool, so do a selection rectangle
820                 Global::selectionInProgress = true;
821                 Global::selection.setTopLeft(QPointF(point.x, point.y));
822                 Global::selection.setBottomRight(QPointF(point.x, point.y));
823         }
824         else if (event->button() == Qt::MiddleButton)
825         {
826                 scrollDrag = true;
827                 oldPoint = Vector(event->x(), event->y());
828                 // Should also change the mouse pointer as well...
829                 setCursor(Qt::SizeAllCursor);
830         }
831 }
832
833
834 void DrawingView::mouseMoveEvent(QMouseEvent * event)
835 {
836         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
837         Global::selection.setBottomRight(QPointF(point.x, point.y));
838         // Only needs to be done here, as mouse down is always preceded by movement
839         Global::snapPointIsValid = false;
840
841         // Scrolling...
842         if (event->buttons() & Qt::MiddleButton)
843         {
844                 point = Vector(event->x(), event->y());
845                 // Since we're using Qt coords for scrolling, we have to adjust them here to
846                 // conform to Cartesian coords, since the origin is using Cartesian. :-)
847                 Vector delta(oldPoint, point);
848                 delta /= Global::zoom;
849                 delta.y = -delta.y;
850                 Global::origin -= delta;
851
852                 UpdateGridBackground();
853                 update();
854                 oldPoint = point;
855                 return;
856         }
857
858         // If we're doing a selection rect, see if any objects are engulfed by it
859         // (implies left mouse button held down)
860         if (Global::selectionInProgress)
861         {
862                 CheckObjectBounds();
863                 update();
864                 return;
865         }
866
867         // Handle object movement (left button down & over an object)
868         if ((event->buttons() & Qt::LeftButton) && numHovered && !Global::tool)
869         {
870                 HandleObjectMovement(point);
871                 update();
872                 oldPoint = point;
873                 return;
874         }
875
876         // Do object hit testing...
877         bool needUpdate = HitTestObjects(point);
878
879         // Do tool handling, if any are active...
880         if (Global::tool)
881         {
882                 if (Global::snapToGrid)
883                         point = SnapPointToGrid(point);
884
885                 ToolHandler(ToolMouseMove, point);
886         }
887
888         // This is used to draw the tool crosshair...
889         oldPoint = point;
890
891         if (needUpdate || Global::tool)
892                 update();
893 }
894
895
896 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
897 {
898         if (event->button() == Qt::LeftButton)
899         {
900 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
901 //could set it up to use the document's update function (assumes that all object updates
902 //are being reported correctly:
903 //              if (document.NeedsUpdate())
904                 // Do an update if collided with at least *one* object in the document
905 //              if (collided)
906                         update();
907
908                 if (Global::tool)
909                 {
910                         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
911                         ToolHandler(ToolMouseUp, point);
912                         return;
913                 }
914
915                 if (Global::selectionInProgress)
916                 {
917                         // Select all the stuff inside of selection
918                         Global::selectionInProgress = false;
919
920                         // Clear our vectors
921                         select.clear();
922                         hover.clear();
923
924                         // Scoop 'em up
925                         std::vector<void *>::iterator i;
926
927                         for(i=document.objects.begin(); i!=document.objects.end(); i++)
928                         {
929                                 if (((Object *)(*i))->selected)
930                                         select.push_back(*i);
931
932 //hmm, this is no good, too late to do any good :-P
933 //                              if ((*i)->hovered)
934 //                                      hover.push_back(*i);
935                         }
936                 }
937         }
938         else if (event->button() == Qt::MiddleButton)
939         {
940                 scrollDrag = false;
941                 setCursor(Qt::ArrowCursor);
942         }
943 }
944
945
946 void DrawingView::wheelEvent(QWheelEvent * event)
947 {
948         double zoomFactor = 1.25;
949         QSize sizeWin = size();
950         Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
951         center = Painter::QtToCartesianCoords(center);
952
953         // This is not centering for some reason. Need to figure out why. :-/
954         if (event->delta() > 0)
955         {
956                 Vector newOrigin = center - ((center - Global::origin) / zoomFactor);
957                 Global::origin = newOrigin;
958                 Global::zoom *= zoomFactor;
959         }
960         else
961         {
962                 Vector newOrigin = center + ((-center + Global::origin) * zoomFactor);
963                 Global::origin = newOrigin;
964                 Global::zoom /= zoomFactor;
965         }
966
967 #if 1
968 //      Global::gridSpacing = gridPixels / Painter::zoom;
969 //      UpdateGridBackground();
970         SetGridSize(Global::gridSpacing * Global::zoom);
971         update();
972 //      zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Global::gridSpacing));
973 #endif
974 }
975
976
977 void DrawingView::keyPressEvent(QKeyEvent * event)
978 {
979         bool oldShift = shiftDown;
980         bool oldCtrl = ctrlDown;
981
982         if (event->key() == Qt::Key_Shift)
983                 shiftDown = true;
984         else if (event->key() == Qt::Key_Control)
985                 ctrlDown = true;
986
987         if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
988         {
989                 if (Global::tool)
990                         ToolHandler(ToolKeyDown, Point(0, 0));
991
992                 update();
993         }
994 }
995
996
997 void DrawingView::keyReleaseEvent(QKeyEvent * event)
998 {
999         bool oldShift = shiftDown;
1000         bool oldCtrl = ctrlDown;
1001
1002         if (event->key() == Qt::Key_Shift)
1003                 shiftDown = false;
1004         else if (event->key() == Qt::Key_Control)
1005                 ctrlDown = false;
1006
1007         if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1008         {
1009                 if (Global::tool)
1010                         ToolHandler(ToolKeyUp, Point(0, 0));
1011
1012                 update();
1013         }
1014 }
1015
1016 //
1017 // This looks strange, but it's really quite simple: We want a point that's
1018 // more than half-way to the next grid point to snap there while conversely we
1019 // want a point that's less than half-way to to the next grid point then snap
1020 // to the one before it. So we add half of the grid spacing to the point, then
1021 // divide by it so that we can remove the fractional part, then multiply it
1022 // back to get back to the correct answer.
1023 //
1024 Point DrawingView::SnapPointToGrid(Point point)
1025 {
1026         point += Global::gridSpacing / 2.0;             // *This* adds to Z!!!
1027         point /= Global::gridSpacing;
1028         point.x = floor(point.x);//need to fix this for negative numbers...
1029         point.y = floor(point.y);
1030         point.z = 0;                                    // Make *sure* Z doesn't go anywhere!!!
1031         point *= Global::gridSpacing;
1032         return point;
1033 }
1034
1035
1036 void DrawingView::CheckObjectBounds(void)
1037 {
1038         std::vector<void *>::iterator i;
1039         numSelected = 0;
1040
1041         for(i=document.objects.begin(); i!=document.objects.end(); i++)
1042         {
1043                 Object * obj = (Object *)(*i);
1044                 obj->selected = false;
1045
1046                 switch (obj->type)
1047                 {
1048                 case OTLine:
1049                 {
1050                         Line * l = (Line *)obj;
1051
1052                         if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
1053                                 l->selected = true;
1054
1055                         break;
1056                 }
1057                 case OTCircle:
1058                 {
1059                         Circle * c = (Circle *)obj;
1060
1061                         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))
1062                                 c->selected = true;
1063
1064                         break;
1065                 }
1066                 case OTArc:
1067                 {
1068                         Arc * a = (Arc *)obj;
1069
1070                         double start = a->angle1;
1071                         double end = start + a->angle2;
1072                         QPointF p1(cos(start), sin(start));
1073                         QPointF p2(cos(end), sin(end));
1074                         QRectF bounds(p1, p2);
1075
1076 #if 1
1077                         // Swap X/Y coordinates if they're backwards...
1078                         if (bounds.left() > bounds.right())
1079                         {
1080                                 double temp = bounds.left();
1081                                 bounds.setLeft(bounds.right());
1082                                 bounds.setRight(temp);
1083                         }
1084
1085                         if (bounds.bottom() > bounds.top())
1086                         {
1087                                 double temp = bounds.bottom();
1088                                 bounds.setBottom(bounds.top());
1089                                 bounds.setTop(temp);
1090                         }
1091 #else
1092                         // Doesn't work as advertised! For shame!
1093                         bounds = bounds.normalized();
1094 #endif
1095
1096                         // If the end of the arc is before the beginning, add 360 degrees to it
1097                         if (end < start)
1098                                 end += 2.0 * PI;
1099
1100                         // Adjust the bounds depending on which axes are crossed
1101                         if ((start < PI_OVER_2) && (end > PI_OVER_2))
1102                                 bounds.setTop(1.0);
1103
1104                         if ((start < PI) && (end > PI))
1105                                 bounds.setLeft(-1.0);
1106
1107                         if ((start < (PI + PI_OVER_2)) && (end > (PI + PI_OVER_2)))
1108                                 bounds.setBottom(-1.0);
1109
1110                         if ((start < (2.0 * PI)) && (end > (2.0 * PI)))
1111                                 bounds.setRight(1.0);
1112
1113                         if ((start < ((2.0 * PI) + PI_OVER_2)) && (end > ((2.0 * PI) + PI_OVER_2)))
1114                                 bounds.setTop(1.0);
1115
1116                         if ((start < (3.0 * PI)) && (end > (3.0 * PI)))
1117                                 bounds.setLeft(-1.0);
1118
1119                         if ((start < ((3.0 * PI) + PI_OVER_2)) && (end > ((3.0 * PI) + PI_OVER_2)))
1120                                 bounds.setBottom(-1.0);
1121
1122                         bounds.setTopLeft(QPointF(bounds.left() * a->radius, bounds.top() * a->radius));
1123                         bounds.setBottomRight(QPointF(bounds.right() * a->radius, bounds.bottom() * a->radius));
1124                         bounds.translate(a->p[0].x, a->p[0].y);
1125
1126                         if (Global::selection.contains(bounds))
1127                                 a->selected = true;
1128
1129                         break;
1130                 }
1131                 default:
1132                         break;
1133                 }
1134
1135                 if (obj->selected)
1136                         numSelected++;
1137         }
1138 }
1139
1140
1141 bool DrawingView::HitTestObjects(Point point)
1142 {
1143         std::vector<void *>::iterator i;
1144         numHovered = 0;
1145         bool needUpdate = false;
1146
1147         for(i=document.objects.begin(); i!=document.objects.end(); i++)
1148         {
1149                 Object * obj = (Object *)(*i);
1150
1151                 switch (obj->type)
1152                 {
1153                 case OTLine:
1154                 {
1155                         Line * l = (Line *)obj;
1156                         bool oldHP0 = l->hitPoint[0], oldHP1 = l->hitPoint[1], oldHO = l->hitObject;
1157                         l->hitPoint[0] = l->hitPoint[1] = l->hitObject = false;
1158                         Vector lineSegment = l->p[1] - l->p[0];
1159                         Vector v1 = point - l->p[0];
1160                         Vector v2 = point - l->p[1];
1161                         double t = Geometry::ParameterOfLineAndPoint(l->p[0], l->p[1], point);
1162                         double distance;
1163
1164                         if (t < 0.0)
1165                                 distance = v1.Magnitude();
1166                         else if (t > 1.0)
1167                                 distance = v2.Magnitude();
1168                         else
1169                                 // distance = ?Det?(ls, v1) / |ls|
1170                                 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
1171                                         / lineSegment.Magnitude());
1172
1173                         if ((v1.Magnitude() * Global::zoom) < 8.0)
1174                         {
1175                                 l->hitPoint[0] = true;
1176 //              snapPoint = l->p1;
1177 //              snapPointIsValid = true;
1178                         }
1179                         else if ((v2.Magnitude() * Global::zoom) < 8.0)
1180                         {
1181                                 l->hitPoint[1] = true;
1182 //              snapPoint = l->p2;
1183 //              snapPointIsValid = true;
1184                         }
1185                         else if ((distance * Global::zoom) < 5.0)
1186                                 l->hitObject = true;
1187
1188 //                      bool oldHovered = l->hovered;
1189                         l->hovered = (l->hitPoint[0] || l->hitPoint[1] || l->hitObject ? true : false);
1190 //                      l->hovered = l->hitObject;
1191
1192 //                      if (oldHovered != l->hovered)
1193                         if ((oldHP0 != l->hitPoint[0]) || (oldHP1 != l->hitPoint[1]) || (oldHO != l->hitObject))
1194                                 needUpdate = true;
1195
1196                         break;
1197                 }
1198                 case OTCircle:
1199                 {
1200                         Circle * c = (Circle *)obj;
1201
1202
1203                         break;
1204                 }
1205                 default:
1206                         break;
1207                 }
1208
1209                 if (obj->hovered)
1210 //              {
1211                         numHovered++;
1212 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
1213 //              }
1214         }
1215
1216         return needUpdate;
1217 }
1218
1219
1220 void DrawingView::HandleObjectMovement(Point point)
1221 {
1222         if (Global::snapToGrid)
1223                 point = SnapPointToGrid(point);
1224
1225         Point delta = point - oldPoint;
1226         Object * obj = (Object *)hover[0];
1227 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
1228 //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"));
1229
1230         switch (obj->type)
1231         {
1232         case OTLine:
1233         {
1234                 Line * l = (Line *)obj;
1235
1236                 if (l->hitPoint[0])
1237                         l->p[0] = point;
1238                 else if (l->hitPoint[1])
1239                         l->p[1] = point;
1240                 else if (l->hitObject)
1241                 {
1242                         l->p[0] += delta;
1243                         l->p[1] += delta;
1244                 }
1245
1246                 break;
1247         }
1248         default:
1249                 break;
1250         }
1251 }
1252
1253
1254
1255 #if 0
1256         // This returns true if we've moved over an object...
1257         if (document.PointerMoved(point)) // <-- This
1258         // This is where the object would do automagic dragging & shit. Since we don't
1259         // do that anymore, we need a strategy to handle it.
1260         {
1261
1262 /*
1263 Now objects handle mouse move snapping as well. The code below mainly works only
1264 for tools; we need to fix it so that objects work as well...
1265
1266 There's a problem with the object point snapping in that it's dependent on the
1267 order of the objects in the document. Most likely this is because it counts the
1268 selected object last and thus fucks up the algorithm. Need to fix this...
1269
1270
1271 */
1272                 // Do object snapping here. Grid snapping on mouse down is done in the
1273                 // objects themselves, only because we have to hit test the raw point,
1274                 // not the snapped point. There has to be a better way...!
1275                 if (document.penultimateObjectHovered)
1276                 {
1277                         // Two objects are hovered, see if we have an intersection point
1278                         if ((document.lastObjectHovered->type == OTLine) && (document.penultimateObjectHovered->type == OTLine))
1279                         {
1280                                 double t;
1281                                 int n = Geometry::Intersects((Line *)document.lastObjectHovered, (Line *)document.penultimateObjectHovered, &t);
1282
1283                                 if (n == 1)
1284                                 {
1285                                         Global::snapPoint = document.lastObjectHovered->GetPointAtParameter(t);
1286                                         Global::snapPointIsValid = true;
1287                                 }
1288                         }
1289                         else if ((document.lastObjectHovered->type == OTCircle) && (document.penultimateObjectHovered->type == OTCircle))
1290                         {
1291                                 Point p1, p2;
1292                                 int n = Geometry::Intersects((Circle *)document.lastObjectHovered, (Circle *)document.penultimateObjectHovered, 0, 0, 0, 0, &p1, &p2);
1293
1294                                 if (n == 1)
1295                                 {
1296                                         Global::snapPoint = p1;
1297                                         Global::snapPointIsValid = true;
1298                                 }
1299                                 else if (n == 2)
1300                                 {
1301                                         double d1 = Vector(point, p1).Magnitude();
1302                                         double d2 = Vector(point, p2).Magnitude();
1303
1304                                         if (d1 < d2)
1305                                                 Global::snapPoint = p1;
1306                                         else
1307                                                 Global::snapPoint = p2;
1308
1309                                         Global::snapPointIsValid = true;
1310                                 }
1311                         }
1312                 }
1313 //              else
1314 //              {
1315                         // Otherwise, it was a single object hovered...
1316 //              }
1317         }
1318
1319         if (toolAction)
1320         {
1321                 if (Global::snapToGrid)
1322                         point = Global::SnapPointToGrid(point);
1323
1324                 // We always snap to object points, and they take precendence over
1325                 // grid points...
1326                 if (Global::snapPointIsValid)
1327                         point = Global::snapPoint;
1328
1329                 toolAction->MouseMoved(point);
1330         }
1331 #else
1332 #endif
1333