]> Shamusworld >> Repos - architektonas/blob - src/drawingview.cpp
Fixed Rotate tool to work with Arcs.
[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[0], ci->radius[0]);
374                         break;
375                 }
376                 case OTArc:
377                 {
378                         Arc * a = (Arc *)obj;
379                         painter->DrawArc(a->p[0], a->radius[0], a->angle[0], a->angle[1]);
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[0], toolPoint[1]).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                                 if (obj.type == OTArc)
724                                 {
725 //printf("Obj2->angle[0] = %f, obj.angle[0] = %f, angle = %f\n", obj2->angle[0], obj.angle[0], angle);
726                                         obj2->angle[0] = obj.angle[0] + angle;
727
728                                         if (obj2->angle[0] > PI_TIMES_2)
729                                                 obj2->angle[0] -= PI_TIMES_2;
730                                 }
731                         }
732                 }
733
734                 break;
735         case ToolMouseUp:
736                 if (Global::toolState == TSPoint1)
737                 {
738                         Global::toolState = TSPoint2;
739                         // Prevent spurious line from drawing...
740                         toolPoint[1] = toolPoint[0];
741                 }
742                 else if ((Global::toolState == TSPoint2) && shiftDown)
743                 {
744                         // Key override is telling us to make a new line, not continue the
745                         // previous one.
746                         toolPoint[0] = toolPoint[1];
747                 }
748                 else
749                 {
750                         // Either we're finished with our rotate, or we're stamping a copy.
751                         if (ctrlDown)
752                         {
753                                 // Stamp a copy of the selection at the current rotation & bail
754                                 std::vector<void *> temp;
755                                 CopyObjects(select, temp);
756                                 ClearSelected(temp);
757                                 AddObjectsTo(document.objects, temp);
758                                 RestorePointsTo(select, toolScratch);
759                                 return;
760                         }
761
762                         toolPoint[0] = p;
763                         Global::toolState = TSPoint1;
764                         SavePointsFrom(select, toolScratch);
765                 }
766
767                 break;
768         case ToolKeyDown:
769                 // Reset the selection if shift held down...
770                 if (shiftDown)
771                         RestorePointsTo(select, toolScratch);
772
773                 break;
774         case ToolKeyUp:
775                 // Reset selection when key is let up
776                 if (!shiftDown)
777                         RotateHandler(ToolMouseMove, toolPoint[1]);
778
779                 break;
780         case ToolCleanup:
781                 RestorePointsTo(select, toolScratch);
782         }
783 }
784
785
786 void DrawingView::mousePressEvent(QMouseEvent * event)
787 {
788         if (event->button() == Qt::LeftButton)
789         {
790                 Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
791
792                 // Handle tool processing, if any
793                 if (Global::tool)
794                 {
795                         if (Global::snapToGrid)
796                                 point = SnapPointToGrid(point);
797
798                         //Also, may want to figure out if hovering over a snap point on an object,
799                         //snap to grid if not.
800                         // Snap to object point if valid...
801 //                      if (Global::snapPointIsValid)
802 //                              point = Global::snapPoint;
803                         
804                         ToolHandler(ToolMouseDown, point);
805                         return;
806                 }
807
808                 // Clear the selection only if CTRL isn't being held on click
809                 if (!ctrlDown)
810                         ClearSelected(document.objects);
811 //                      ClearSelection();
812
813                 // If any objects are being hovered on click, add them to the selection
814                 // & return
815                 if (numHovered > 0)
816                 {
817                         AddHoveredToSelection();
818                         update();       // needed??
819                         GetHovered(hover);      // prolly needed
820
821                         // Needed for grab & moving objects
822                         if (Global::snapToGrid)
823                                 oldPoint = SnapPointToGrid(point);
824
825                         return;
826                 }
827
828                 // Didn't hit any object and not using a tool, so do a selection rectangle
829                 Global::selectionInProgress = true;
830                 Global::selection.setTopLeft(QPointF(point.x, point.y));
831                 Global::selection.setBottomRight(QPointF(point.x, point.y));
832         }
833         else if (event->button() == Qt::MiddleButton)
834         {
835                 scrollDrag = true;
836                 oldPoint = Vector(event->x(), event->y());
837                 // Should also change the mouse pointer as well...
838                 setCursor(Qt::SizeAllCursor);
839         }
840 }
841
842
843 void DrawingView::mouseMoveEvent(QMouseEvent * event)
844 {
845         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
846         Global::selection.setBottomRight(QPointF(point.x, point.y));
847         // Only needs to be done here, as mouse down is always preceded by movement
848         Global::snapPointIsValid = false;
849
850         // Scrolling...
851         if (event->buttons() & Qt::MiddleButton)
852         {
853                 point = Vector(event->x(), event->y());
854                 // Since we're using Qt coords for scrolling, we have to adjust them here to
855                 // conform to Cartesian coords, since the origin is using Cartesian. :-)
856                 Vector delta(oldPoint, point);
857                 delta /= Global::zoom;
858                 delta.y = -delta.y;
859                 Global::origin -= delta;
860
861                 UpdateGridBackground();
862                 update();
863                 oldPoint = point;
864                 return;
865         }
866
867         // If we're doing a selection rect, see if any objects are engulfed by it
868         // (implies left mouse button held down)
869         if (Global::selectionInProgress)
870         {
871                 CheckObjectBounds();
872                 update();
873                 return;
874         }
875
876         // Handle object movement (left button down & over an object)
877         if ((event->buttons() & Qt::LeftButton) && numHovered && !Global::tool)
878         {
879                 HandleObjectMovement(point);
880                 update();
881                 oldPoint = point;
882                 return;
883         }
884
885         // Do object hit testing...
886         bool needUpdate = HitTestObjects(point);
887
888         // Do tool handling, if any are active...
889         if (Global::tool)
890         {
891                 if (Global::snapToGrid)
892                         point = SnapPointToGrid(point);
893
894                 ToolHandler(ToolMouseMove, point);
895         }
896
897         // This is used to draw the tool crosshair...
898         oldPoint = point;
899
900         if (needUpdate || Global::tool)
901                 update();
902 }
903
904
905 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
906 {
907         if (event->button() == Qt::LeftButton)
908         {
909 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
910 //could set it up to use the document's update function (assumes that all object updates
911 //are being reported correctly:
912 //              if (document.NeedsUpdate())
913                 // Do an update if collided with at least *one* object in the document
914 //              if (collided)
915                         update();
916
917                 if (Global::tool)
918                 {
919                         Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
920                         ToolHandler(ToolMouseUp, point);
921                         return;
922                 }
923
924                 if (Global::selectionInProgress)
925                         Global::selectionInProgress = false;
926
927 // Should be we do this automagically? Hmm...
928                 // Clear our vectors
929                 select.clear();
930                 hover.clear();
931
932                 // Scoop 'em up
933                 std::vector<void *>::iterator i;
934
935                 for(i=document.objects.begin(); i!=document.objects.end(); i++)
936                 {
937                         if (((Object *)(*i))->selected)
938                                 select.push_back(*i);
939
940 //hmm, this is no good, too late to do any good :-P
941 //                              if ((*i)->hovered)
942 //                                      hover.push_back(*i);
943 //                      }
944                 }
945         }
946         else if (event->button() == Qt::MiddleButton)
947         {
948                 scrollDrag = false;
949                 setCursor(Qt::ArrowCursor);
950         }
951 }
952
953
954 void DrawingView::wheelEvent(QWheelEvent * event)
955 {
956         double zoomFactor = 1.25;
957         QSize sizeWin = size();
958         Vector center(sizeWin.width() / 2.0, sizeWin.height() / 2.0);
959         center = Painter::QtToCartesianCoords(center);
960
961         // This is not centering for some reason. Need to figure out why. :-/
962         if (event->delta() > 0)
963         {
964                 Vector newOrigin = center - ((center - Global::origin) / zoomFactor);
965                 Global::origin = newOrigin;
966                 Global::zoom *= zoomFactor;
967         }
968         else
969         {
970                 Vector newOrigin = center + ((-center + Global::origin) * zoomFactor);
971                 Global::origin = newOrigin;
972                 Global::zoom /= zoomFactor;
973         }
974
975 #if 1
976 //      Global::gridSpacing = gridPixels / Painter::zoom;
977 //      UpdateGridBackground();
978         SetGridSize(Global::gridSpacing * Global::zoom);
979         update();
980 //      zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Global::gridSpacing));
981 #endif
982 }
983
984
985 void DrawingView::keyPressEvent(QKeyEvent * event)
986 {
987         bool oldShift = shiftDown;
988         bool oldCtrl = ctrlDown;
989
990         if (event->key() == Qt::Key_Shift)
991                 shiftDown = true;
992         else if (event->key() == Qt::Key_Control)
993                 ctrlDown = true;
994
995         if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
996         {
997                 if (Global::tool)
998                         ToolHandler(ToolKeyDown, Point(0, 0));
999
1000                 update();
1001         }
1002 }
1003
1004
1005 void DrawingView::keyReleaseEvent(QKeyEvent * event)
1006 {
1007         bool oldShift = shiftDown;
1008         bool oldCtrl = ctrlDown;
1009
1010         if (event->key() == Qt::Key_Shift)
1011                 shiftDown = false;
1012         else if (event->key() == Qt::Key_Control)
1013                 ctrlDown = false;
1014
1015         if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
1016         {
1017                 if (Global::tool)
1018                         ToolHandler(ToolKeyUp, Point(0, 0));
1019
1020                 update();
1021         }
1022 }
1023
1024 //
1025 // This looks strange, but it's really quite simple: We want a point that's
1026 // more than half-way to the next grid point to snap there while conversely we
1027 // want a point that's less than half-way to to the next grid point then snap
1028 // to the one before it. So we add half of the grid spacing to the point, then
1029 // divide by it so that we can remove the fractional part, then multiply it
1030 // back to get back to the correct answer.
1031 //
1032 Point DrawingView::SnapPointToGrid(Point point)
1033 {
1034         point += Global::gridSpacing / 2.0;             // *This* adds to Z!!!
1035         point /= Global::gridSpacing;
1036         point.x = floor(point.x);//need to fix this for negative numbers...
1037         point.y = floor(point.y);
1038         point.z = 0;                                    // Make *sure* Z doesn't go anywhere!!!
1039         point *= Global::gridSpacing;
1040         return point;
1041 }
1042
1043
1044 void DrawingView::CheckObjectBounds(void)
1045 {
1046         std::vector<void *>::iterator i;
1047         numSelected = 0;
1048
1049         for(i=document.objects.begin(); i!=document.objects.end(); i++)
1050         {
1051                 Object * obj = (Object *)(*i);
1052                 obj->selected = false;
1053
1054                 switch (obj->type)
1055                 {
1056                 case OTLine:
1057                 {
1058                         Line * l = (Line *)obj;
1059
1060                         if (Global::selection.contains(l->p[0].x, l->p[0].y) && Global::selection.contains(l->p[1].x, l->p[1].y))
1061                                 l->selected = true;
1062
1063                         break;
1064                 }
1065                 case OTCircle:
1066                 {
1067                         Circle * c = (Circle *)obj;
1068
1069                         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]))
1070                                 c->selected = true;
1071
1072                         break;
1073                 }
1074                 case OTArc:
1075                 {
1076                         Arc * a = (Arc *)obj;
1077
1078                         double start = a->angle[0];
1079                         double end = start + a->angle[1];
1080                         QPointF p1(cos(start), sin(start));
1081                         QPointF p2(cos(end), sin(end));
1082                         QRectF bounds(p1, p2);
1083
1084 #if 1
1085                         // Swap X/Y coordinates if they're backwards...
1086                         if (bounds.left() > bounds.right())
1087                         {
1088                                 double temp = bounds.left();
1089                                 bounds.setLeft(bounds.right());
1090                                 bounds.setRight(temp);
1091                         }
1092
1093                         if (bounds.bottom() > bounds.top())
1094                         {
1095                                 double temp = bounds.bottom();
1096                                 bounds.setBottom(bounds.top());
1097                                 bounds.setTop(temp);
1098                         }
1099 #else
1100                         // Doesn't work as advertised! For shame!
1101                         bounds = bounds.normalized();
1102 #endif
1103
1104                         // If the end of the arc is before the beginning, add 360 degrees to it
1105                         if (end < start)
1106                                 end += 2.0 * PI;
1107
1108                         // Adjust the bounds depending on which axes are crossed
1109                         if ((start < PI_OVER_2) && (end > PI_OVER_2))
1110                                 bounds.setTop(1.0);
1111
1112                         if ((start < PI) && (end > PI))
1113                                 bounds.setLeft(-1.0);
1114
1115                         if ((start < (PI + PI_OVER_2)) && (end > (PI + PI_OVER_2)))
1116                                 bounds.setBottom(-1.0);
1117
1118                         if ((start < (2.0 * PI)) && (end > (2.0 * PI)))
1119                                 bounds.setRight(1.0);
1120
1121                         if ((start < ((2.0 * PI) + PI_OVER_2)) && (end > ((2.0 * PI) + PI_OVER_2)))
1122                                 bounds.setTop(1.0);
1123
1124                         if ((start < (3.0 * PI)) && (end > (3.0 * PI)))
1125                                 bounds.setLeft(-1.0);
1126
1127                         if ((start < ((3.0 * PI) + PI_OVER_2)) && (end > ((3.0 * PI) + PI_OVER_2)))
1128                                 bounds.setBottom(-1.0);
1129
1130                         bounds.setTopLeft(QPointF(bounds.left() * a->radius[0], bounds.top() * a->radius[0]));
1131                         bounds.setBottomRight(QPointF(bounds.right() * a->radius[0], bounds.bottom() * a->radius[0]));
1132                         bounds.translate(a->p[0].x, a->p[0].y);
1133
1134                         if (Global::selection.contains(bounds))
1135                                 a->selected = true;
1136
1137                         break;
1138                 }
1139                 default:
1140                         break;
1141                 }
1142
1143                 if (obj->selected)
1144                         numSelected++;
1145         }
1146 }
1147
1148
1149 bool DrawingView::HitTestObjects(Point point)
1150 {
1151         std::vector<void *>::iterator i;
1152         numHovered = 0;
1153         bool needUpdate = false;
1154
1155         for(i=document.objects.begin(); i!=document.objects.end(); i++)
1156         {
1157                 Object * obj = (Object *)(*i);
1158
1159                 switch (obj->type)
1160                 {
1161                 case OTLine:
1162                 {
1163                         Line * l = (Line *)obj;
1164                         bool oldHP0 = l->hitPoint[0], oldHP1 = l->hitPoint[1], oldHO = l->hitObject;
1165                         l->hitPoint[0] = l->hitPoint[1] = l->hitObject = false;
1166                         Vector lineSegment = l->p[1] - l->p[0];
1167                         Vector v1 = point - l->p[0];
1168                         Vector v2 = point - l->p[1];
1169                         double t = Geometry::ParameterOfLineAndPoint(l->p[0], l->p[1], point);
1170                         double distance;
1171
1172                         if (t < 0.0)
1173                                 distance = v1.Magnitude();
1174                         else if (t > 1.0)
1175                                 distance = v2.Magnitude();
1176                         else
1177                                 // distance = ?Det?(ls, v1) / |ls|
1178                                 distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
1179                                         / lineSegment.Magnitude());
1180
1181                         if ((v1.Magnitude() * Global::zoom) < 8.0)
1182                         {
1183                                 l->hitPoint[0] = true;
1184 //              snapPoint = l->p1;
1185 //              snapPointIsValid = true;
1186                         }
1187                         else if ((v2.Magnitude() * Global::zoom) < 8.0)
1188                         {
1189                                 l->hitPoint[1] = true;
1190 //              snapPoint = l->p2;
1191 //              snapPointIsValid = true;
1192                         }
1193                         else if ((distance * Global::zoom) < 5.0)
1194                                 l->hitObject = true;
1195
1196 //                      bool oldHovered = l->hovered;
1197                         l->hovered = (l->hitPoint[0] || l->hitPoint[1] || l->hitObject ? true : false);
1198 //                      l->hovered = l->hitObject;
1199
1200 //                      if (oldHovered != l->hovered)
1201                         if ((oldHP0 != l->hitPoint[0]) || (oldHP1 != l->hitPoint[1]) || (oldHO != l->hitObject))
1202                                 needUpdate = true;
1203
1204                         break;
1205                 }
1206                 case OTCircle:
1207                 {
1208                         Circle * c = (Circle *)obj;
1209
1210
1211                         break;
1212                 }
1213                 default:
1214                         break;
1215                 }
1216
1217                 if (obj->hovered)
1218 //              {
1219                         numHovered++;
1220 //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered);
1221 //              }
1222         }
1223
1224         return needUpdate;
1225 }
1226
1227
1228 void DrawingView::HandleObjectMovement(Point point)
1229 {
1230         if (Global::snapToGrid)
1231                 point = SnapPointToGrid(point);
1232
1233         Point delta = point - oldPoint;
1234         Object * obj = (Object *)hover[0];
1235 //printf("Object type = %i (size=%i), ", obj->type, hover.size());
1236 //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"));
1237
1238         switch (obj->type)
1239         {
1240         case OTLine:
1241         {
1242                 Line * l = (Line *)obj;
1243
1244                 if (l->hitPoint[0])
1245                         l->p[0] = point;
1246                 else if (l->hitPoint[1])
1247                         l->p[1] = point;
1248                 else if (l->hitObject)
1249                 {
1250                         l->p[0] += delta;
1251                         l->p[1] += delta;
1252                 }
1253
1254                 break;
1255         }
1256         default:
1257                 break;
1258         }
1259 }
1260
1261
1262
1263 #if 0
1264         // This returns true if we've moved over an object...
1265         if (document.PointerMoved(point)) // <-- This
1266         // This is where the object would do automagic dragging & shit. Since we don't
1267         // do that anymore, we need a strategy to handle it.
1268         {
1269
1270 /*
1271 Now objects handle mouse move snapping as well. The code below mainly works only
1272 for tools; we need to fix it so that objects work as well...
1273
1274 There's a problem with the object point snapping in that it's dependent on the
1275 order of the objects in the document. Most likely this is because it counts the
1276 selected object last and thus fucks up the algorithm. Need to fix this...
1277
1278
1279 */
1280                 // Do object snapping here. Grid snapping on mouse down is done in the
1281                 // objects themselves, only because we have to hit test the raw point,
1282                 // not the snapped point. There has to be a better way...!
1283                 if (document.penultimateObjectHovered)
1284                 {
1285                         // Two objects are hovered, see if we have an intersection point
1286                         if ((document.lastObjectHovered->type == OTLine) && (document.penultimateObjectHovered->type == OTLine))
1287                         {
1288                                 double t;
1289                                 int n = Geometry::Intersects((Line *)document.lastObjectHovered, (Line *)document.penultimateObjectHovered, &t);
1290
1291                                 if (n == 1)
1292                                 {
1293                                         Global::snapPoint = document.lastObjectHovered->GetPointAtParameter(t);
1294                                         Global::snapPointIsValid = true;
1295                                 }
1296                         }
1297                         else if ((document.lastObjectHovered->type == OTCircle) && (document.penultimateObjectHovered->type == OTCircle))
1298                         {
1299                                 Point p1, p2;
1300                                 int n = Geometry::Intersects((Circle *)document.lastObjectHovered, (Circle *)document.penultimateObjectHovered, 0, 0, 0, 0, &p1, &p2);
1301
1302                                 if (n == 1)
1303                                 {
1304                                         Global::snapPoint = p1;
1305                                         Global::snapPointIsValid = true;
1306                                 }
1307                                 else if (n == 2)
1308                                 {
1309                                         double d1 = Vector(point, p1).Magnitude();
1310                                         double d2 = Vector(point, p2).Magnitude();
1311
1312                                         if (d1 < d2)
1313                                                 Global::snapPoint = p1;
1314                                         else
1315                                                 Global::snapPoint = p2;
1316
1317                                         Global::snapPointIsValid = true;
1318                                 }
1319                         }
1320                 }
1321 //              else
1322 //              {
1323                         // Otherwise, it was a single object hovered...
1324 //              }
1325         }
1326
1327         if (toolAction)
1328         {
1329                 if (Global::snapToGrid)
1330                         point = Global::SnapPointToGrid(point);
1331
1332                 // We always snap to object points, and they take precendence over
1333                 // grid points...
1334                 if (Global::snapPointIsValid)
1335                         point = Global::snapPoint;
1336
1337                 toolAction->MouseMoved(point);
1338         }
1339 #else
1340 #endif
1341