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