3 // Part of the Architektonas Project
4 // (C) 2011 Underground Software
5 // See the README and GPLv3 files for licensing and warranty information
7 // JLH = James L. Hammons <jlhamm@acm.org>
10 // --- ---------- -------------------------------------------------------------
11 // JLH 03/22/2011 Created this file
21 // Uncomment this for debugging...
23 //#define DEBUGFOO // Various tool debugging...
24 //#define DEBUGTP // Toolpalette debugging...
26 #include "drawingview.h"
29 #include "mathconstants.h"
33 #include "dimension.h"
37 DrawingView::DrawingView(QWidget * parent/*= NULL*/): QWidget(parent),
38 // scale(1.0), offsetX(-10), offsetY(-10), tool(TOOLSelect),
39 // ptHighlight(-1), oldPtHighlight(-1), ptNextHighlight(-1), oldPtNextHighlight(-1),
40 // polyFirstPoint(true)
41 scale(1.0), offsetX(-10), offsetY(-10),
42 document(Vector(0, 0)),
43 gridSpacing(32.0), collided(false)
45 setBackgroundRole(QPalette::Base);
46 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
48 // toolPalette = new ToolWindow();
50 // setCursor(cur[TOOLSelect]);
51 // setMouseTracking(true);
53 Line * line = new Line(Vector(5, 5), Vector(50, 40), &document);
55 document.Add(new Line(Vector(50, 40), Vector(10, 83), &document));
56 document.Add(new Line(Vector(10, 83), Vector(17, 2), &document));
57 document.Add(new Circle(Vector(100, 100), 36, &document));
58 document.Add(new Circle(Vector(50, 150), 49, &document));
59 document.Add(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3, &document)),
60 document.Add(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5, &document));
62 Dimension * dimension = new Dimension(Vector(0, 0), Vector(0, 0), &document);
63 line->SetDimensionOnLine(dimension);
64 // line->SetDimensionOnPoint2(dimension);
65 document.Add(dimension);
67 // Alternate way to do the above...
68 line->SetDimensionOnLine();
72 QPoint DrawingView::GetAdjustedMousePosition(QMouseEvent * event)
74 // This is undoing the transform, e.g. going from client coords to local coords.
75 // In essence, the height - y is height + (y * -1), the (y * -1) term doing the
76 // conversion of the y-axis from increasing bottom to top.
77 return QPoint(offsetX + event->x(), offsetY + (size().height() - event->y()));
80 QPoint DrawingView::GetAdjustedClientPosition(int x, int y)
82 // VOODOO ALERT (ON Y COMPONENT!!!!) (eh?)
83 // No voodoo here, it's just grouped wrong to see it. It should be:
84 // -offsetY + (size.height() + (y * -1.0)) <-- this is wrong, offsetY should be positive
85 return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
88 void DrawingView::paintEvent(QPaintEvent * /*event*/)
90 QPainter painter(this);
91 painter.setRenderHint(QPainter::Antialiasing);
94 painter.translate(QPoint(-offsetX, size.height() - (-offsetY)));
95 painter.scale(1.0, -1.0);
98 //order of operations is important! N.B.: Can't use scaling other than 1.0, it
99 //causes lines to look strange (i.e., it scales the pen strokes too)
100 // transform.translate(-offsetX, size().height() - (-offsetY));
101 transform.scale(1.0, -1.0);
102 transform.translate(-offsetX, -size().height() - offsetY);
103 // transform.scale(0.25, 0.25);
104 painter.setTransform(transform);
106 Object::SetViewportHeight(size().height());
108 // Draw coordinate axes
110 painter.setPen(QPen(Qt::blue, 1.0, Qt::DotLine));
111 painter.drawLine(0, -16384, 0, 16384);
112 painter.drawLine(-16384, 0, 16384, 0);
114 // Maybe we can make the grid into a background brush instead, and let Qt deal
119 painter.setPen(QPen(QColor(90, 90, 90), 1.0, Qt::DotLine));
121 //these two loops kill performance!
122 for(double x=0; x<size().width(); x+=gridSpacing*10.0)
123 p.drawLine((int)x, -16384, (int)x, 16384);
125 for(double y=0; y<size().height(); y+=gridSpacing*10.0)
126 p.drawLine(-16384, (int)y, 16384, (int)y);
129 painter.setPen(QPen(Qt::black, 1.0, Qt::SolidLine));
131 for(double x=0; x<size().width(); x+=gridSpacing)
132 for(double y=0; y<size().height(); y+=gridSpacing)
133 painter.drawPoint((int)x, (int)y);
135 // The top level document takes care of rendering for us...
136 document.Draw(&painter);
139 void DrawingView::mousePressEvent(QMouseEvent * event)
141 if (event->button() == Qt::LeftButton)
143 QPoint pt = GetAdjustedMousePosition(event);
144 Vector point(pt.x(), pt.y());
146 collided = document.Collided(point);
149 update(); // Do an update if collided with at least *one* object in the document
153 void DrawingView::mouseMoveEvent(QMouseEvent * event)
155 QPoint pt = GetAdjustedMousePosition(event);
156 Vector point(pt.x(), pt.y());
158 // Grid processing...
160 // This looks strange, but it's really quite simple: We want a point that's
161 // more than half-way to the next grid point to snap there while conversely
162 // we want a point that's less than half-way to to the next grid point then
163 // snap to the one before it. So we add half of the grid spacing to the
164 // point, then divide by it so that we can remove the fractional part, then
165 // multiply it back to get back to the correct answer.
166 if (event->buttons() & Qt::LeftButton)
168 point += gridSpacing / 2.0; // *This* adds to Z!!!
169 point /= gridSpacing;
170 point.x = floor(point.x);//need to fix this for negative numbers...
171 point.y = floor(point.y);
172 point.z = 0; // Make *sure* Z doesn't go anywhere!!!
173 point *= gridSpacing;
176 //we should keep track of the last point here and only pass this down *if* the point
178 document.PointerMoved(point);
180 if (document.NeedsUpdate())
184 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
186 if (event->button() == Qt::LeftButton)
188 document.PointerReleased();
190 //We need to update especially if nothing collided and the state needs to change. !!! FIX !!!
191 //could set it up to use the document's update function (assumes that all object updates
192 //are being reported correctly:
193 // if (document.NeedsUpdate())
195 update(); // Do an update if collided with at least *one* object in the document
201 QSize DrawingView::minimumSizeHint() const
203 return QSize(50, 50);
206 QSize DrawingView::sizeHint() const
208 return QSize(400, 400);
211 void DrawingView::CreateCursors(void)
213 int hotx[8] = { 1, 1, 11, 15, 1, 1, 1, 1 };
214 int hoty[8] = { 1, 1, 11, 13, 1, 1, 1, 1 };
216 for(int i=0; i<8; i++)
219 s.sprintf(":/res/cursor%u.png", i+1);
221 cur[i] = QCursor(pmTmp, hotx[i], hoty[i]);
227 o Different colors for polys on selected points
228 o Different colors for handles on non-selected polys
229 o Line of sight (dashed, dotted) for off-curve points
230 o Repaints for press/release of CTRL/SHIFT during point creation
232 void DrawingView::paintEvent(QPaintEvent * /*event*/)
236 // p.setRenderHint(QPainter::Antialiasing);
238 //dc.SetBackground(*wxWHITE_BRUSH);
240 // Due to the screwiness of wxWidgets coord system, the origin is ALWAYS
241 // the upper left corner--regardless of axis orientation, etc...
242 // int width, height;
243 // dc.GetSize(&width, &height);
244 QSize winSize = size();
246 // dc.SetDeviceOrigin(-offsetX, height - (-offsetY));
247 // dc.SetAxisOrientation(true, true);
248 p.translate(QPoint(-offsetX, winSize.height() - (-offsetY)));
251 // Scrolling can be done by using OffsetViewportOrgEx
252 // Scaling can be done by adjusting SetWindowExtEx (it's denominator of txform)
253 // you'd use: % = ViewportExt / WindowExt
254 // But it makes the window look like crap: fuggetuboutit.
255 // Instead, we have to scale EVERYTHING by hand. Crap!
256 // It's not *that* bad, but not as convenient either...
258 // dc.SetPen(*(wxThePenList->FindOrCreatePen(wxColour(0x00, 0x00, 0xFF), 1, wxDOT)));
259 //// dc.DrawLine(0, 0, 10, 10);
260 p.setPen(QPen(Qt::blue, 1.0, Qt::DotLine));
262 // Draw coordinate axes
264 // dc.CrossHair(0, 0);
265 p.drawLine(0, -16384, 0, 16384);
266 p.drawLine(-16384, 0, 16384, 0);
270 for(int i=0; i<pts.GetNumPoints(); i++)
272 if (i == ptHighlight)
274 // dc.SetPen(*(wxThePenList->FindOrCreatePen(wxColour(0xFF, 0x00, 0x00), 1, wxSOLID)));
275 //// SelectObject(hdc, hRedPen1);
276 p.setPen(QPen(Qt::red, 1.0, Qt::SolidLine));
278 if (pts.GetOnCurve(i))
280 DrawSquareDotN(p, pts.GetX(i), pts.GetY(i), 7);
281 DrawSquareDotN(p, pts.GetX(i), pts.GetY(i), 9);
285 DrawRoundDotN(p, pts.GetX(i), pts.GetY(i), 7);
286 DrawRoundDotN(p, pts.GetX(i), pts.GetY(i), 9);
289 else if ((i == ptHighlight || i == ptNextHighlight) && tool == TOOLAddPt)
291 // dc.SetPen(*(wxThePenList->FindOrCreatePen(wxColour(0x00, 0xAF, 0x00), 1, wxSOLID)));
292 //// SelectObject(hdc, hGreenPen1);
293 p.setPen(QPen(Qt::green, 1.0, Qt::SolidLine));
295 if (pts.GetOnCurve(i))
297 DrawSquareDotN(p, pts.GetX(i), pts.GetY(i), 7);
298 DrawSquareDotN(p, pts.GetX(i), pts.GetY(i), 9);
302 DrawRoundDotN(p, pts.GetX(i), pts.GetY(i), 7);
303 DrawRoundDotN(p, pts.GetX(i), pts.GetY(i), 9);
308 // dc.SetPen(*(wxThePenList->FindOrCreatePen(wxColour(0x00, 0x00, 0x00), 1, wxSOLID)));
309 //// SelectObject(hdc, hBlackPen1);
310 p.setPen(QPen(Qt::black, 1.0, Qt::SolidLine));
312 if (pts.GetOnCurve(i))
313 DrawSquareDot(p, pts.GetX(i), pts.GetY(i));
315 DrawRoundDot(p, pts.GetX(i), pts.GetY(i));
318 if (tool == TOOLDelPt && i == ptHighlight)
321 dc.SetPen(*(wxThePenList->FindOrCreatePen(wxColour(0xFF, 0x00, 0x00), 1, wxSOLID)));
322 // SelectObject(hdc, hRedPen1);
323 // MoveToEx(hdc, pts.GetX(i) - 5, pts.GetY(i) - 5, NULL);
324 // LineTo(hdc, pts.GetX(i) + 5, pts.GetY(i) + 5);
325 // LineTo(hdc, pts.GetX(i) - 5, pts.GetY(i) - 5);//Lameness!
326 // MoveToEx(hdc, pts.GetX(i) - 5, pts.GetY(i) + 5, NULL);
327 // LineTo(hdc, pts.GetX(i) + 5, pts.GetY(i) - 5);
328 // LineTo(hdc, pts.GetX(i) - 5, pts.GetY(i) + 5);//More lameness!!
330 p.setPen(QPen(Qt::red, 1.0, Qt::SolidLine));
331 p.drawLine(pts.GetX(i) - 5, pts.GetY(i) - 5, pts.GetX(i) + 5, pts.GetY(i) + 5);
332 p.drawLine(pts.GetX(i) + 5, pts.GetY(i) - 5, pts.GetX(i) - 5, pts.GetY(i) + 5);
336 //// SelectObject(hdc, hBlackPen1);
337 // dc.SetPen(*(wxThePenList->FindOrCreatePen(wxColour(0x00, 0x00, 0x00), 1, wxSOLID)));
338 p.setPen(QPen(Qt::black, 1.0, Qt::SolidLine));
340 // Draw curve formed by points
342 for(int poly=0; poly<pts.GetNumPolys(); poly++)
344 if (pts.GetNumPoints(poly) > 2)
347 // If it's not on curve, then move to it, otherwise move to last point...
351 if (pts.GetOnCurve(poly, pts.GetNumPoints(poly) - 1))
352 x = (int)pts.GetX(poly, pts.GetNumPoints(poly) - 1), y = (int)pts.GetY(poly, pts.GetNumPoints(poly) - 1);
354 x = (int)pts.GetX(poly, 0), y = (int)pts.GetY(poly, 0);
356 for(int i=0; i<pts.GetNumPoints(poly); i++)
358 if (pts.GetOnCurve(poly, i))
359 // LineTo(hdc, pts.GetX(poly, i), pts.GetY(poly, i));
361 p.drawLine(x, y, pts.GetX(poly, i), pts.GetY(poly, i));
362 x = (int)pts.GetX(poly, i), y = (int)pts.GetY(poly, i);
366 uint32 prev = pts.GetPrev(poly, i), next = pts.GetNext(poly, i);
367 float px = pts.GetX(poly, prev), py = pts.GetY(poly, prev),
368 nx = pts.GetX(poly, next), ny = pts.GetY(poly, next);
370 if (!pts.GetOnCurve(poly, prev))
371 px = (px + pts.GetX(poly, i)) / 2.0f,
372 py = (py + pts.GetY(poly, i)) / 2.0f;
374 if (!pts.GetOnCurve(poly, next))
375 nx = (nx + pts.GetX(poly, i)) / 2.0f,
376 ny = (ny + pts.GetY(poly, i)) / 2.0f;
378 Bezier(p, point(px, py), point(pts.GetX(poly, i), pts.GetY(poly, i)), point(nx, ny));
379 x = (int)nx, y = (int)ny;
381 if (pts.GetOnCurve(poly, next))
382 i++; // Following point is on curve, so move past it
389 void DrawingView::mousePressEvent(QMouseEvent * event)
391 if (event->button() == Qt::RightButton)
393 toolPalette->move(event->globalPos());
394 toolPalette->setVisible(true);
395 setCursor(cur[TOOLSelect]);
396 toolPalette->prevTool = TOOLSelect;
398 else if (event->button() == Qt::MidButton)
400 setCursor(cur[2]); // Scrolling cursor
402 else if (event->button() == Qt::LeftButton)
404 if (tool == TOOLScroll || tool == TOOLZoom)
405 ;//meh CaptureMouse(); // Make sure we capture the mouse when in scroll/zoom mode
406 else if (tool == TOOLAddPt) // "Add Point" tool
408 if (pts.GetNumPoints() > 0)
410 QPoint pt = GetAdjustedMousePosition(event);
411 pts.InsertPoint(pts.GetNext(ptHighlight), pt.x(), pt.y(), ((event->modifiers() == Qt::ShiftModifier || event->modifiers() == Qt::ControlModifier) ? false : true));
412 ptHighlight = ptNextHighlight;
416 else if (tool == TOOLAddPoly) // "Add Poly" tool
419 WriteLogMsg("Adding point... # polys: %u, # points: %u", pts.GetNumPolys(), pts.GetNumPoints());
423 polyFirstPoint = false;
424 pts.AddNewPolyAtEnd();
427 QPoint pt = GetAdjustedMousePosition(event);
428 //printf("GetAdjustedMousePosition = %i, %i\n", pt.x(), pt.y());
429 // Append a point to the end of the structure
430 pts += IPoint(pt.x(), pt.y(), ((event->modifiers() == Qt::ShiftModifier || event->modifiers() == Qt::ControlModifier) ? false : true));
431 ptHighlight = pts.GetNumPoints() - 1;
434 WriteLogMsg(" --> [# polys: %u, # points: %u]\n", pts.GetNumPolys(), pts.GetNumPoints());
437 else if (tool == TOOLSelect || tool == TOOLPolySelect)
439 if (pts.GetNumPoints() > 0)
441 pt = GetAdjustedClientPosition(pts.GetX(ptHighlight), pts.GetY(ptHighlight));
442 //printf("GetAdjustedClientPosition = %i, %i\n", pt.x(), pt.y());
443 // WarpPointer(pt.x, pt.y);
444 QCursor::setPos(mapToGlobal(pt));
446 if (event->modifiers() == Qt::ShiftModifier || event->modifiers() == Qt::ControlModifier)
448 pts.SetOnCurve(ptHighlight, !pts.GetOnCurve(ptHighlight));
453 else if (tool == TOOLDelPt)
455 if (pts.GetNumPoints() > 0)
457 // if (ptHighlight != -1)
459 //This assumes that WM_MOUSEMOVE happens before this!
460 //The above commented out line should take care of this contingency... !!! FIX !!!
461 pts.DeletePoint(ptHighlight);
470 void DrawingView::mouseMoveEvent(QMouseEvent * event)
472 if (event->buttons() == Qt::RightButton)
474 ToolType newTool = toolPalette->FindSelectedTool();
476 if (newTool != toolPalette->prevTool)
478 toolPalette->prevTool = newTool;
479 toolPalette->repaint();
482 else if (event->buttons() == Qt::MidButton)
484 // Calc offset from previous point
486 ptOffset = QPoint(pt.x() - ptPrevious.x(), pt.y() - ptPrevious.y());
488 // Then multiply it by the scaling factor. Whee!
489 // This looks wacky because we're using screen coords for the offset...
490 // Otherwise, we would subtract both offsets!
491 offsetX -= ptOffset.x(), offsetY += ptOffset.y();
495 else if (event->buttons() == Qt::LeftButton)
498 if (tool == TOOLScroll)
500 // Extract current point from lParam/calc offset from previous point
502 pt = e.GetPosition();
503 ptOffset.x = pt.x - ptPrevious.x,
504 ptOffset.y = pt.y - ptPrevious.y;
506 // NOTE: OffsetViewportOrg operates in DEVICE UNITS...
508 //Seems there's no equivalent for this in wxWidgets...!
510 // hdc = GetDC(hWnd);
511 // OffsetViewportOrgEx(hdc, ptOffset.x, ptOffset.y, NULL);
512 // ReleaseDC(hWnd, hdc);
514 // this shows that it works, so the logic above must be faulty...
515 // And it is. It should convert the coords first, then do the subtraction to figure the offset...
517 // Then multiply it by the scaling factor. Whee!
518 // This looks wacky because we're using screen coords for the offset...
519 // Otherwise, we would subtract both offsets!
520 offsetX -= ptOffset.x, offsetY += ptOffset.y;
525 if (tool == TOOLAddPt || tool == TOOLAddPoly || tool == TOOLSelect)
527 if (tool != TOOLAddPt || pts.GetNumPoints() > 0)//yecch.
529 //temporary, for testing. BTW, Select drag bug is here...!
531 QPoint pt2 = GetAdjustedMousePosition(event);
532 pts.SetXY(ptHighlight, pt2.x(), pt2.y());
537 else if (tool == TOOLPolySelect)
539 if (pts.GetNumPoints() > 0)
541 QPoint pt2 = GetAdjustedMousePosition(event);
542 // Should also set onCurve here as well, depending on keystate
544 pts.OffsetPoly(pts.GetPoly(ptHighlight), pt2.x() - pts.GetX(ptHighlight), pt2.y() - pts.GetY(ptHighlight));
549 else if (event->buttons() == Qt::NoButton)
551 // Moving, not dragging...
552 if (tool == TOOLSelect || tool == TOOLDelPt || tool == TOOLAddPt
553 || tool == TOOLPolySelect)// || tool == TOOLAddPoly)
555 QPoint pt2 = GetAdjustedMousePosition(event);
556 double closest = 1.0e+99;
558 for(int i=0; i<pts.GetNumPoints(); i++)
560 double dist = ((pt2.x() - pts.GetX(i)) * (pt2.x() - pts.GetX(i)))
561 + ((pt2.y() - pts.GetY(i)) * (pt2.y() - pts.GetY(i)));
564 closest = dist, ptHighlight = i;
567 if (ptHighlight != oldPtHighlight)
569 oldPtHighlight = ptHighlight;
573 // What follows here looks like voodoo, but is really simple. What we do is
574 // check to see if the mouse point has a perpendicular intersection with any of
575 // the line segments. If it does, calculate the length of the perpendicular
576 // and choose the smallest length. If there is no perpendicular, then choose the
577 // length of line connecting the closer of either the first endpoint or the
578 // second and choose the smallest of those.
580 // There is one bit of math that looks like voodoo to me ATM--will explain once
581 // I understand it better (the calculation of the length of the perpendicular).
583 if (pts.GetNumPoints() > 1 && tool == TOOLAddPt)
585 double smallest = 1.0e+99;
587 for(int i=0; i<pts.GetNumPoints(); i++)
589 int32 p1x = pts.GetX(i), p1y = pts.GetY(i),
590 p2x = pts.GetX(pts.GetNext(i)), p2y = pts.GetY(pts.GetNext(i));
592 vector ls(p2x, p2y, 0, p1x, p1y, 0), v1(pt2.x(), pt2.y(), 0, p1x, p1y, 0),
593 v2(pt2.x(), pt2.y(), 0, p2x, p2y, 0);
594 double pp = ls.dot(v1) / ls.length(), dist;
595 // Geometric interpretation:
596 // pp is the paremeterized point on the vector ls where the perpendicular intersects ls.
597 // If pp < 0, then the perpendicular lies beyond the 1st endpoint. If pp > length of ls,
598 // then the perpendicular lies beyond the 2nd endpoint.
602 else if (pp > ls.length())
604 else // distance = ?Det?(ls, v1) / |ls|
605 dist = fabs((ls.x * v1.y - v1.x * ls.y) / ls.length());
607 //The answer to the above looks like it might be found here:
609 //If the segment endpoints are s and e, and the point is p, then the test for the perpendicular
610 //intercepting the segment is equivalent to insisting that the two dot products {s-e}.{s-p} and
611 //{e-s}.{e-p} are both non-negative. Perpendicular distance from the point to the segment is
612 //computed by first computing the area of the triangle the three points form, then dividing by the
613 //length of the segment. Distances are done just by the Pythagorean theorem. Twice the area of the
614 //triangle formed by three points is the determinant of the following matrix:
620 //By translating the start point to the origin, this can be rewritten as:
621 //By subtracting row 1 from all rows, you get the following:
622 //[because sx = sy = 0. you could leave out the -sx/y terms below. because we subtracted
623 // row 1 from all rows (including row 1) row 1 turns out to be zero. duh!]
626 //(ex - sx) (ey - sy) 0
627 //(px - sx) (py - sy) 0
629 //which greatly simplifies the calculation of the determinant.
632 smallest = dist, ptNextHighlight = pts.GetNext(i), ptHighlight = i;
635 if (ptNextHighlight != oldPtNextHighlight)
637 oldPtNextHighlight = ptNextHighlight;
643 ptPrevious = event->pos();
649 void DrawingView::mouseReleaseEvent(QMouseEvent * event)
651 if (event->button() == Qt::RightButton)
653 ToolType newTool = toolPalette->FindSelectedTool();
655 // We only change the tool if a new one was actually selected. Otherwise, we do nothing.
656 if (newTool != TOOLNone)
660 if (tool == TOOLScroll || tool == TOOLZoom || tool == TOOLAddPoly
661 || tool == TOOLDelPoly)
664 if (tool == TOOLAddPoly)
665 polyFirstPoint = true;
668 toolPalette->setVisible(false);
669 setCursor(cur[tool]);
670 // Just in case we changed highlighting style with the new tool...
673 else if (event->button() == Qt::MidButton)
675 setCursor(cur[tool]); // Restore previous cursor
677 else if (event->button() == Qt::LeftButton)
679 // if (tool == TOOLScroll || tool == TOOLZoom)
681 //this is prolly too much
682 ((TTEdit *)qApp)->charWnd->MakePathFromPoints(&pts);
683 ((TTEdit *)qApp)->charWnd->update();