2 // TTEDIT.CPP - The TrueType Editor
4 // (C) 2004 Underground Software
6 // JLH = James L. Hammons <jlhamm@acm.org>
9 // --- ---------- -------------------------------------------------------------
10 // JLH 08/28/2008 Created this file
11 // JLH 09/02/2008 Separated scrolling from dedicated tool to MMB drag
12 // JLH 03/13/2009 Converted from wxWidgets to Qt
21 // - Fix bug in Glyphpoints when dragging on an empty canvas or loading a font
22 // - Fix zooming, settings (ini)
23 // - Fix point adding bug 1: should be able to add points to empty canvas
24 // - Fix point adding bug 2: should be able to add point successfully to single
26 // - Add poly multi-select
27 // - Add point multi-select
31 // Uncomment this for debugging...
33 #define DEBUGFOO // Various tool debugging...
34 #define DEBUGTP // Toolpalette debugging...
36 #include "editwindow.h"
38 #include "graphicprimitives.h"
41 #include "charwindow.h"
44 EditWindow::EditWindow(QWidget * parent/*= NULL*/): QWidget(parent),
45 scale(1.0), offsetX(-10), offsetY(-10), tool(TOOLSelect),
46 ptHighlight(-1), oldPtHighlight(-1), ptNextHighlight(-1), oldPtNextHighlight(-1),
49 setBackgroundRole(QPalette::Base);
50 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
52 toolPalette = new ToolWindow();
54 setCursor(cur[TOOLSelect]);
55 setMouseTracking(true);
58 QSize EditWindow::minimumSizeHint() const
63 QSize EditWindow::sizeHint() const
65 return QSize(400, 400);
68 void EditWindow::CreateCursors(void)
70 int hotx[8] = { 1, 1, 11, 15, 1, 1, 1, 1 };
71 int hoty[8] = { 1, 1, 11, 13, 1, 1, 1, 1 };
73 for(int i=0; i<8; i++)
76 s.sprintf(":/res/cursor%u.png", i+1);
78 cur[i] = QCursor(pmTmp, hotx[i], hoty[i]);
82 QPoint EditWindow::GetAdjustedMousePosition(QMouseEvent * event)
84 QSize winSize = size();
85 // This is undoing the transform, e.g. going from client coords to local coords.
86 // In essence, the height - y is height + (y * -1), the (y * -1) term doing the
87 // conversion of the y-axis from increasing bottom to top.
88 return QPoint(offsetX + event->x(), offsetY + (winSize.height() - event->y()));
91 QPoint EditWindow::GetAdjustedClientPosition(int x, int y)
93 QSize winSize = size();
95 // VOODOO ALERT (ON Y COMPONENT!!!!)
96 return QPoint(-offsetX + x, (winSize.height() - (-offsetY + y)) * +1.0);
101 o Different colors for polys on selected points
102 o Different colors for handles on non-selected polys
103 o Line of sight (dashed, dotted) for off-curve points
104 o Repaints for press/release of CTRL/SHIFT during point creation
106 void EditWindow::paintEvent(QPaintEvent * /*event*/)
109 //hm, causes lockup (or does it???)
110 p.setRenderHint(QPainter::Antialiasing);
112 //dc.SetBackground(*wxWHITE_BRUSH);
114 // Due to the screwiness of wxWidgets coord system, the origin is ALWAYS
115 // the upper left corner--regardless of axis orientation, etc...
116 // int width, height;
117 // dc.GetSize(&width, &height);
118 QSize winSize = size();
120 // dc.SetDeviceOrigin(-offsetX, height - (-offsetY));
121 // dc.SetAxisOrientation(true, true);
122 p.translate(QPoint(-offsetX, winSize.height() - (-offsetY)));
125 // Scrolling can be done by using OffsetViewportOrgEx
126 // Scaling can be done by adjusting SetWindowExtEx (it's denominator of txform)
127 // you'd use: % = ViewportExt / WindowExt
128 // But it makes the window look like crap: fuggetuboutit.
129 // Instead, we have to scale EVERYTHING by hand. Crap!
130 // It's not *that* bad, but not as convenient either...
132 // dc.SetPen(*(wxThePenList->FindOrCreatePen(wxColour(0x00, 0x00, 0xFF), 1, wxDOT)));
133 //// dc.DrawLine(0, 0, 10, 10);
134 p.setPen(QPen(Qt::blue, 1.0, Qt::DotLine));
136 // Draw coordinate axes
138 // dc.CrossHair(0, 0);
139 p.drawLine(0, -16384, 0, 16384);
140 p.drawLine(-16384, 0, 16384, 0);
144 for(int i=0; i<pts.GetNumPoints(); i++)
146 if (i == ptHighlight)
148 // dc.SetPen(*(wxThePenList->FindOrCreatePen(wxColour(0xFF, 0x00, 0x00), 1, wxSOLID)));
149 //// SelectObject(hdc, hRedPen1);
150 p.setPen(QPen(Qt::red, 1.0, Qt::SolidLine));
152 if (pts.GetOnCurve(i))
154 DrawSquareDotN(p, pts.GetX(i), pts.GetY(i), 7);
155 DrawSquareDotN(p, pts.GetX(i), pts.GetY(i), 9);
159 DrawRoundDotN(p, pts.GetX(i), pts.GetY(i), 7);
160 DrawRoundDotN(p, pts.GetX(i), pts.GetY(i), 9);
163 else if ((i == ptHighlight || i == ptNextHighlight) && tool == TOOLAddPt)
165 // dc.SetPen(*(wxThePenList->FindOrCreatePen(wxColour(0x00, 0xAF, 0x00), 1, wxSOLID)));
166 //// SelectObject(hdc, hGreenPen1);
167 p.setPen(QPen(Qt::green, 1.0, Qt::SolidLine));
169 if (pts.GetOnCurve(i))
171 DrawSquareDotN(p, pts.GetX(i), pts.GetY(i), 7);
172 DrawSquareDotN(p, pts.GetX(i), pts.GetY(i), 9);
176 DrawRoundDotN(p, pts.GetX(i), pts.GetY(i), 7);
177 DrawRoundDotN(p, pts.GetX(i), pts.GetY(i), 9);
182 // dc.SetPen(*(wxThePenList->FindOrCreatePen(wxColour(0x00, 0x00, 0x00), 1, wxSOLID)));
183 //// SelectObject(hdc, hBlackPen1);
184 p.setPen(QPen(Qt::black, 1.0, Qt::SolidLine));
186 if (pts.GetOnCurve(i))
187 DrawSquareDot(p, pts.GetX(i), pts.GetY(i));
189 DrawRoundDot(p, pts.GetX(i), pts.GetY(i));
192 if (tool == TOOLDelPt && i == ptHighlight)
195 dc.SetPen(*(wxThePenList->FindOrCreatePen(wxColour(0xFF, 0x00, 0x00), 1, wxSOLID)));
196 // SelectObject(hdc, hRedPen1);
197 // MoveToEx(hdc, pts.GetX(i) - 5, pts.GetY(i) - 5, NULL);
198 // LineTo(hdc, pts.GetX(i) + 5, pts.GetY(i) + 5);
199 // LineTo(hdc, pts.GetX(i) - 5, pts.GetY(i) - 5);//Lameness!
200 // MoveToEx(hdc, pts.GetX(i) - 5, pts.GetY(i) + 5, NULL);
201 // LineTo(hdc, pts.GetX(i) + 5, pts.GetY(i) - 5);
202 // LineTo(hdc, pts.GetX(i) - 5, pts.GetY(i) + 5);//More lameness!!
204 p.setPen(QPen(Qt::red, 1.0, Qt::SolidLine));
205 p.drawLine(pts.GetX(i) - 5, pts.GetY(i) - 5, pts.GetX(i) + 5, pts.GetY(i) + 5);
206 p.drawLine(pts.GetX(i) + 5, pts.GetY(i) - 5, pts.GetX(i) - 5, pts.GetY(i) + 5);
210 //// SelectObject(hdc, hBlackPen1);
211 // dc.SetPen(*(wxThePenList->FindOrCreatePen(wxColour(0x00, 0x00, 0x00), 1, wxSOLID)));
212 p.setPen(QPen(Qt::black, 1.0, Qt::SolidLine));
214 // Draw curve formed by points
216 for(int poly=0; poly<pts.GetNumPolys(); poly++)
218 if (pts.GetNumPoints(poly) > 2)
221 // If it's not on curve, then move to it, otherwise move to last point...
225 if (pts.GetOnCurve(poly, pts.GetNumPoints(poly) - 1))
226 x = (int)pts.GetX(poly, pts.GetNumPoints(poly) - 1), y = (int)pts.GetY(poly, pts.GetNumPoints(poly) - 1);
228 x = (int)pts.GetX(poly, 0), y = (int)pts.GetY(poly, 0);
230 for(int i=0; i<pts.GetNumPoints(poly); i++)
232 if (pts.GetOnCurve(poly, i))
233 // LineTo(hdc, pts.GetX(poly, i), pts.GetY(poly, i));
235 p.drawLine(x, y, pts.GetX(poly, i), pts.GetY(poly, i));
236 x = (int)pts.GetX(poly, i), y = (int)pts.GetY(poly, i);
240 uint32 prev = pts.GetPrev(poly, i), next = pts.GetNext(poly, i);
241 float px = pts.GetX(poly, prev), py = pts.GetY(poly, prev),
242 nx = pts.GetX(poly, next), ny = pts.GetY(poly, next);
244 if (!pts.GetOnCurve(poly, prev))
245 px = (px + pts.GetX(poly, i)) / 2.0f,
246 py = (py + pts.GetY(poly, i)) / 2.0f;
248 if (!pts.GetOnCurve(poly, next))
249 nx = (nx + pts.GetX(poly, i)) / 2.0f,
250 ny = (ny + pts.GetY(poly, i)) / 2.0f;
252 Bezier(p, point(px, py), point(pts.GetX(poly, i), pts.GetY(poly, i)), point(nx, ny));
253 x = (int)nx, y = (int)ny;
255 if (pts.GetOnCurve(poly, next))
256 i++; // Following point is on curve, so move past it
263 void EditWindow::mousePressEvent(QMouseEvent * event)
265 if (event->button() == Qt::RightButton)
267 toolPalette->move(event->globalPos());
268 toolPalette->setVisible(true);
269 setCursor(cur[TOOLSelect]);
270 toolPalette->prevTool = TOOLSelect;
272 else if (event->button() == Qt::MidButton)
274 setCursor(cur[2]); // Scrolling cursor
276 else if (event->button() == Qt::LeftButton)
278 if (tool == TOOLScroll || tool == TOOLZoom)
279 ;//meh CaptureMouse(); // Make sure we capture the mouse when in scroll/zoom mode
280 else if (tool == TOOLAddPt) // "Add Point" tool
282 if (pts.GetNumPoints() > 0)
284 QPoint pt = GetAdjustedMousePosition(event);
285 pts.InsertPoint(pts.GetNext(ptHighlight), pt.x(), pt.y(), ((event->modifiers() == Qt::ShiftModifier || event->modifiers() == Qt::ControlModifier) ? false : true));
286 ptHighlight = ptNextHighlight;
290 else if (tool == TOOLAddPoly) // "Add Poly" tool
293 WriteLogMsg("Adding point... # polys: %u, # points: %u", pts.GetNumPolys(), pts.GetNumPoints());
297 polyFirstPoint = false;
298 pts.AddNewPolyAtEnd();
301 QPoint pt = GetAdjustedMousePosition(event);
302 //printf("GetAdjustedMousePosition = %i, %i\n", pt.x(), pt.y());
303 // Append a point to the end of the structure
304 pts += IPoint(pt.x(), pt.y(), ((event->modifiers() == Qt::ShiftModifier || event->modifiers() == Qt::ControlModifier) ? false : true));
305 ptHighlight = pts.GetNumPoints() - 1;
308 WriteLogMsg(" --> [# polys: %u, # points: %u]\n", pts.GetNumPolys(), pts.GetNumPoints());
311 else if (tool == TOOLSelect || tool == TOOLPolySelect)
313 if (pts.GetNumPoints() > 0)
315 pt = GetAdjustedClientPosition(pts.GetX(ptHighlight), pts.GetY(ptHighlight));
316 //printf("GetAdjustedClientPosition = %i, %i\n", pt.x(), pt.y());
317 // WarpPointer(pt.x, pt.y);
318 QCursor::setPos(mapToGlobal(pt));
320 if (event->modifiers() == Qt::ShiftModifier || event->modifiers() == Qt::ControlModifier)
322 pts.SetOnCurve(ptHighlight, !pts.GetOnCurve(ptHighlight));
327 else if (tool == TOOLDelPt)
329 if (pts.GetNumPoints() > 0)
331 // if (ptHighlight != -1)
333 //This assumes that WM_MOUSEMOVE happens before this!
334 //The above commented out line should take care of this contingency... !!! FIX !!!
335 pts.DeletePoint(ptHighlight);
344 void EditWindow::mouseMoveEvent(QMouseEvent * event)
346 if (event->buttons() == Qt::RightButton)
348 ToolType newTool = toolPalette->FindSelectedTool();
350 if (newTool != toolPalette->prevTool)
352 toolPalette->prevTool = newTool;
353 toolPalette->repaint();
356 else if (event->buttons() == Qt::MidButton)
358 // Calc offset from previous point
360 ptOffset = QPoint(pt.x() - ptPrevious.x(), pt.y() - ptPrevious.y());
362 // Then multiply it by the scaling factor. Whee!
363 // This looks wacky because we're using screen coords for the offset...
364 // Otherwise, we would subtract both offsets!
365 offsetX -= ptOffset.x(), offsetY += ptOffset.y();
369 else if (event->buttons() == Qt::LeftButton)
372 if (tool == TOOLScroll)
374 // Extract current point from lParam/calc offset from previous point
376 pt = e.GetPosition();
377 ptOffset.x = pt.x - ptPrevious.x,
378 ptOffset.y = pt.y - ptPrevious.y;
380 // NOTE: OffsetViewportOrg operates in DEVICE UNITS...
382 //Seems there's no equivalent for this in wxWidgets...!
384 // hdc = GetDC(hWnd);
385 // OffsetViewportOrgEx(hdc, ptOffset.x, ptOffset.y, NULL);
386 // ReleaseDC(hWnd, hdc);
388 // this shows that it works, so the logic above must be faulty...
389 // And it is. It should convert the coords first, then do the subtraction to figure the offset...
391 // Then multiply it by the scaling factor. Whee!
392 // This looks wacky because we're using screen coords for the offset...
393 // Otherwise, we would subtract both offsets!
394 offsetX -= ptOffset.x, offsetY += ptOffset.y;
399 if (tool == TOOLAddPt || tool == TOOLAddPoly || tool == TOOLSelect)
401 if (tool != TOOLAddPt || pts.GetNumPoints() > 0)//yecch.
403 //temporary, for testing. BTW, Select drag bug is here...!
405 QPoint pt2 = GetAdjustedMousePosition(event);
406 pts.SetXY(ptHighlight, pt2.x(), pt2.y());
411 else if (tool == TOOLPolySelect)
413 if (pts.GetNumPoints() > 0)
415 QPoint pt2 = GetAdjustedMousePosition(event);
416 // Should also set onCurve here as well, depending on keystate
418 pts.OffsetPoly(pts.GetPoly(ptHighlight), pt2.x() - pts.GetX(ptHighlight), pt2.y() - pts.GetY(ptHighlight));
423 else if (event->buttons() == Qt::NoButton)
425 // Moving, not dragging...
426 if (tool == TOOLSelect || tool == TOOLDelPt || tool == TOOLAddPt
427 || tool == TOOLPolySelect)// || tool == TOOLAddPoly)
429 QPoint pt2 = GetAdjustedMousePosition(event);
430 double closest = 1.0e+99;
432 for(int i=0; i<pts.GetNumPoints(); i++)
434 double dist = ((pt2.x() - pts.GetX(i)) * (pt2.x() - pts.GetX(i)))
435 + ((pt2.y() - pts.GetY(i)) * (pt2.y() - pts.GetY(i)));
438 closest = dist, ptHighlight = i;
441 if (ptHighlight != oldPtHighlight)
443 oldPtHighlight = ptHighlight;
447 // What follows here looks like voodoo, but is really simple. What we do is
448 // check to see if the mouse point has a perpendicular intersection with any of
449 // the line segments. If it does, calculate the length of the perpendicular
450 // and choose the smallest length. If there is no perpendicular, then choose the
451 // length of line connecting the closer of either the first endpoint or the
452 // second and choose the smallest of those.
454 // There is one bit of math that looks like voodoo to me ATM--will explain once
455 // I understand it better (the calculation of the length of the perpendicular).
457 if (pts.GetNumPoints() > 1 && tool == TOOLAddPt)
459 double smallest = 1.0e+99;
461 for(int i=0; i<pts.GetNumPoints(); i++)
463 int32 p1x = pts.GetX(i), p1y = pts.GetY(i),
464 p2x = pts.GetX(pts.GetNext(i)), p2y = pts.GetY(pts.GetNext(i));
466 vector ls(p2x, p2y, 0, p1x, p1y, 0), v1(pt2.x(), pt2.y(), 0, p1x, p1y, 0),
467 v2(pt2.x(), pt2.y(), 0, p2x, p2y, 0);
468 double pp = ls.dot(v1) / ls.length(), dist;
469 // Geometric interpretation:
470 // pp is the paremeterized point on the vector ls where the perpendicular intersects ls.
471 // If pp < 0, then the perpendicular lies beyond the 1st endpoint. If pp > length of ls,
472 // then the perpendicular lies beyond the 2nd endpoint.
476 else if (pp > ls.length())
478 else // distance = ?Det?(ls, v1) / |ls|
479 dist = fabs((ls.x * v1.y - v1.x * ls.y) / ls.length());
481 //The answer to the above looks like it might be found here:
483 //If the segment endpoints are s and e, and the point is p, then the test for the perpendicular
484 //intercepting the segment is equivalent to insisting that the two dot products {s-e}.{s-p} and
485 //{e-s}.{e-p} are both non-negative. Perpendicular distance from the point to the segment is
486 //computed by first computing the area of the triangle the three points form, then dividing by the
487 //length of the segment. Distances are done just by the Pythagorean theorem. Twice the area of the
488 //triangle formed by three points is the determinant of the following matrix:
494 //By translating the start point to the origin, this can be rewritten as:
495 //By subtracting row 1 from all rows, you get the following:
496 //[because sx = sy = 0. you could leave out the -sx/y terms below. because we subtracted
497 // row 1 from all rows (including row 1) row 1 turns out to be zero. duh!]
500 //(ex - sx) (ey - sy) 0
501 //(px - sx) (py - sy) 0
503 //which greatly simplifies the calculation of the determinant.
506 smallest = dist, ptNextHighlight = pts.GetNext(i), ptHighlight = i;
509 if (ptNextHighlight != oldPtNextHighlight)
511 oldPtNextHighlight = ptNextHighlight;
517 ptPrevious = event->pos();
523 void EditWindow::mouseReleaseEvent(QMouseEvent * event)
525 if (event->button() == Qt::RightButton)
527 ToolType newTool = toolPalette->FindSelectedTool();
529 // We only change the tool if a new one was actually selected. Otherwise, we do nothing.
530 if (newTool != TOOLNone)
534 if (tool == TOOLScroll || tool == TOOLZoom || tool == TOOLAddPoly
535 || tool == TOOLDelPoly)
538 if (tool == TOOLAddPoly)
539 polyFirstPoint = true;
542 toolPalette->setVisible(false);
543 setCursor(cur[tool]);
544 // Just in case we changed highlighting style with the new tool...
547 else if (event->button() == Qt::MidButton)
549 setCursor(cur[tool]); // Restore previous cursor
551 else if (event->button() == Qt::LeftButton)
553 // if (tool == TOOLScroll || tool == TOOLZoom)
555 //this is prolly too much
556 ((TTEdit *)qApp)->charWnd->MakePathFromPoints(&pts);
557 ((TTEdit *)qApp)->charWnd->update();