]> Shamusworld >> Repos - ttedit/blob - src/editwindow.cpp
4a3d43f50fc8def35436ee526b222036a837cda0
[ttedit] / src / editwindow.cpp
1 //
2 // TTEDIT.CPP - The TrueType Editor
3 // by James L. Hammons
4 // (C) 2004 Underground Software
5 //
6 // JLH = James L. Hammons <jlhamm@acm.org>
7 //
8 // Who  When        What
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
13 //
14
15 // FIXED:
16 //
17 // - Fixed scrolling
18 //
19 // STILL TO BE DONE:
20 //
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
25 //   point on screen
26 // - Add poly multi-select
27 // - Add point multi-select
28 // - Undo system
29 //
30
31 // Uncomment this for debugging...
32 #define DEBUG
33 #define DEBUGFOO            // Various tool debugging...
34 #define DEBUGTP                         // Toolpalette debugging...
35
36 #include "editwindow.h"
37 #include "charwindow.h"
38 #include "debug.h"
39 #include "global.h"
40 #include "mainwindow.h"
41 #include "ttedit.h"
42 #include "vector.h"
43
44
45 EditWindow::EditWindow(QWidget * parent/*= NULL*/): QWidget(parent),
46         tool(TOOLSelect), ptHighlight(-1), oldPtHighlight(-1), ptNextHighlight(-1),
47         oldPtNextHighlight(-1), polyFirstPoint(true), showRotationCenter(false),
48         haveZeroPoint(false), selectionInProgress(false)
49 {
50         setBackgroundRole(QPalette::Base);
51         setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
52
53         toolPalette = new ToolWindow();
54         CreateCursors();
55         setCursor(cur[TOOLSelect]);
56         setMouseTracking(true);
57         ClearSelection();
58 }
59
60
61 QSize EditWindow::minimumSizeHint() const
62 {
63         return QSize(50, 50);
64 }
65
66
67 QSize EditWindow::sizeHint() const
68 {
69         return QSize(400, 400);
70 }
71
72
73 void EditWindow::CreateCursors(void)
74 {
75         int hotx[12] = {  1,  1, 1, 15,  1,  1,  1,  1,  1,  1,  1, 11 };
76         int hoty[12] = {  1,  1, 1, 13,  1,  1,  1,  1,  1,  1,  1, 11 };
77         char cursorName[12][48] = { "select", "select-poly", "select-multi", "zoom",
78                 "add-point", "add-poly", "del-point", "del-poly", "rotate", "rotate",
79                 "select", "scroll" };
80
81         for(int i=0; i<12; i++)
82         {
83                 QPixmap pmTmp(QString(":/res/cursor-%1.png").arg(cursorName[i]));
84                 cur[i] = QCursor(pmTmp, hotx[i], hoty[i]);
85         }
86 }
87
88
89 /*
90 TODO:
91  o  Different colors for polys on selected points
92  o  Different colors for handles on non-selected polys
93  o  Line of sight (dashed, dotted) for off-curve points
94  o  Repaints for press/release of CTRL/SHIFT during point creation
95 */
96 void EditWindow::paintEvent(QPaintEvent * /*event*/)
97 {
98         QPainter qtp(this);
99         Painter painter(&qtp);
100         painter.SetRenderHint(QPainter::Antialiasing);
101
102         Global::viewportHeight = size().height();
103         Global::screenSize = Vector(size().width(), size().height());
104
105         // Draw coordinate axes
106
107         painter.SetPen(QPen(Qt::blue, 1.0, Qt::DotLine));
108         painter.DrawLine(0, -16384, 0, 16384);
109         painter.DrawLine(-16384, 0, 16384, 0);
110
111         // Draw rotation center (if active)
112
113         if (showRotationCenter)
114         {
115                 painter.SetPen(QPen(Qt::red, 2.0, Qt::SolidLine));
116                 painter.DrawLine(rotationCenter.x + 7, rotationCenter.y, rotationCenter.x - 7, rotationCenter.y);
117                 painter.DrawLine(rotationCenter.x, rotationCenter.y + 7, rotationCenter.x, rotationCenter.y - 7);
118         }
119
120         // Draw points
121
122         for(int i=0; i<pts.GetNumPoints(); i++)
123         {
124                 /*if (tool == TOOLMultiSelect)
125                 {
126                         if (selectedPoints[i])
127                         {
128                                 qtp.setPen(QPen(Qt::red, 1.0, Qt::SolidLine));
129                         }
130                         else
131                         {
132                                 qtp.setPen(QPen(Qt::black, 1.0, Qt::SolidLine));
133                         }
134                 }
135                 else*/
136                 if (((i == ptHighlight) && (tool != TOOLMultiSelect)) || ((tool == TOOLMultiSelect) && selectedPoints[i]))
137                 {
138                         painter.SetPen(QPen(Qt::red, 1.0, Qt::SolidLine));
139
140                         if (pts.GetOnCurve(i))
141                         {
142                                 painter.DrawSquareDotN(pts.GetXY(i), 7);
143                                 painter.DrawSquareDotN(pts.GetXY(i), 9);
144                         }
145                         else
146                         {
147                                 painter.DrawRoundDotN(pts.GetXY(i), 7);
148                                 painter.DrawRoundDotN(pts.GetXY(i), 9);
149                         }
150                 }
151                 else if ((i == ptHighlight || i == ptNextHighlight) && tool == TOOLAddPt)
152                 {
153                         painter.SetPen(QPen(Qt::green, 1.0, Qt::SolidLine));
154
155                         if (pts.GetOnCurve(i))
156                         {
157                                 painter.DrawSquareDotN(pts.GetXY(i), 7);
158                                 painter.DrawSquareDotN(pts.GetXY(i), 9);
159                         }
160                         else
161                         {
162                                 painter.DrawRoundDotN(pts.GetXY(i), 7);
163                                 painter.DrawRoundDotN(pts.GetXY(i), 9);
164                         }
165                 }
166                 else
167                 {
168                         painter.SetPen(QPen(Qt::black, 1.0, Qt::SolidLine));
169
170 #if 1
171                         if (pts.GetOnCurve(i))
172                                 painter.DrawSquareDot(pts.GetXY(i));
173                         else
174                                 painter.DrawRoundDot(pts.GetXY(i));
175 #else
176                         (pts.GetOnCurve(i) ? DrawSquareDot(p, pts.GetX(i), pts.GetY(i))
177                                 : DrawRoundDot(p, pts.GetX(i), pts.GetY(i)));
178 #endif
179                 }
180
181                 if (tool == TOOLDelPt && i == ptHighlight)
182                 {
183                         painter.SetPen(QPen(Qt::red, 1.0, Qt::SolidLine));
184                         painter.DrawLine(pts.GetX(i) - 5, pts.GetY(i) - 5, pts.GetX(i) + 5, pts.GetY(i) + 5);
185                         painter.DrawLine(pts.GetX(i) + 5, pts.GetY(i) - 5, pts.GetX(i) - 5, pts.GetY(i) + 5);
186                 }
187         }
188
189         // Draw curve formed by points
190
191         painter.SetPen(QPen(Qt::black, 1.0, Qt::SolidLine));
192         DrawGlyph(painter, pts);
193
194         if (haveZeroPoint)
195         {
196                 // Rotation code
197                 GlyphPoints rotated = pts;
198
199                 if (tool == TOOLRotate)
200                         rotated.RotatePoints(rotationAngle, IPoint(rotationCenter.x, rotationCenter.y));
201                 else if (tool == TOOLRotatePoly)
202                 {
203                         uint16_t poly = rotated.GetPolyForPointNumber(ptHighlight);
204                         rotated.RotatePolyAroundCentroid(poly, rotationAngle);
205                 }
206
207                 painter.SetPen(QPen(QColor(255, 0, 255), 1.0, Qt::SolidLine));
208                 DrawGlyph(painter, rotated);
209         }
210
211         if (selectionInProgress)
212         {
213                 painter.SetPen(QPen(QColor(255, 127, 0, 255)));
214                 painter.SetBrush(QBrush(QColor(255, 127, 0, 100)));
215                 painter.DrawRect(selection);
216         }
217 }
218
219
220 void EditWindow::DrawGlyph(Painter & p, GlyphPoints & glyph)
221 {
222         for(int poly=0; poly<glyph.GetNumPolys(); poly++)
223         {
224 #if 0
225                 if (glyph.GetNumPoints(poly) < 3)
226                         continue;
227
228                 // Initial move: If our start point is on curve, then go to it. Otherwise,
229                 // check previous point. If it's on curve, go to it otherwise go the
230                 // midpoint between start point and previous (since it's between two curve
231                 // control points).
232                 IPoint pt = (glyph.GetOnCurve(poly, 0)
233                         ? glyph.GetPoint(poly, 0) : (glyph.GetPrevOnCurve(poly, 0)
234                                 ? glyph.GetPrevPoint(poly, 0) : glyph.GetMidpointToPrev(poly, 0)));
235
236 // Need to add separate color handling here for polys that are being manipulated...
237
238                 for(int i=0; i<glyph.GetNumPoints(poly); i++)
239                 {
240                         // If this point and then next are both on curve, we have a line...
241                         if (glyph.GetOnCurve(poly, i) && glyph.GetNextOnCurve(poly, i))
242                         {
243                                 IPoint pt2 = glyph.GetNextPoint(poly, i);
244                                 p.drawLine(pt.x, pt.y, pt2.x, pt2.y);
245                                 pt = pt2;
246                         }
247                         else
248                         {
249                                 // Skip point if it's on curve (start of curve--it's already
250                                 // been plotted so we don't need to handle it...)
251                                 if (glyph.GetOnCurve(poly, i))
252                                         continue;
253
254                                 // We are now guaranteed that we are sitting on a curve control point
255                                 // (off curve). Figure the extent of the curve: If the following is a
256                                 // curve control point, then use the midpoint to it otherwise go to
257                                 // the next point since it's on curve.
258                                 IPoint pt2 = (glyph.GetNextOnCurve(poly, i)
259                                         ? glyph.GetNextPoint(poly, i) : glyph.GetMidpointToNext(poly, i));
260
261                                 Bezier(p, pt, glyph.GetPoint(poly, i), pt2);
262                                 pt = pt2;
263                         }
264                 }
265 #else
266                 DrawGlyphPoly(p, glyph, poly);
267 #endif
268         }
269 }
270
271
272 void EditWindow::DrawGlyphPoly(Painter & p, GlyphPoints & glyph, uint16_t poly)
273 {
274         // Sanity check
275         if (glyph.GetNumPoints(poly) < 3)
276                 return;
277
278         // Initial move: If our start point is on curve, then go to it. Otherwise,
279         // check previous point. If it's on curve, go to it otherwise go the
280         // midpoint between start point and previous (since it's between two curve
281         // control points).
282         IPoint pt = (glyph.GetOnCurve(poly, 0)
283                 ? glyph.GetPoint(poly, 0) : (glyph.GetPrevOnCurve(poly, 0)
284                         ? glyph.GetPrevPoint(poly, 0) : glyph.GetMidpointToPrev(poly, 0)));
285
286         for(int i=0; i<glyph.GetNumPoints(poly); i++)
287         {
288                 // If this point and then next are both on curve, we have a line...
289                 if (glyph.GetOnCurve(poly, i) && glyph.GetNextOnCurve(poly, i))
290                 {
291                         IPoint pt2 = glyph.GetNextPoint(poly, i);
292                         p.DrawLine(pt.x, pt.y, pt2.x, pt2.y);
293                         pt = pt2;
294                 }
295                 else
296                 {
297                         // Skip point if it's on curve (start of curve--it's already
298                         // been plotted so we don't need to handle it...)
299                         if (glyph.GetOnCurve(poly, i))
300                                 continue;
301
302                         // We are now guaranteed that we are sitting on a curve control
303                         // point (off curve). Figure the extent of the curve: If the
304                         // following is a curve control point, then use the midpoint to it
305                         // otherwise go to the next point since it's on curve.
306                         IPoint pt2 = (glyph.GetNextOnCurve(poly, i)
307                                 ? glyph.GetNextPoint(poly, i) : glyph.GetMidpointToNext(poly, i));
308
309                         p.DrawBezier(pt, glyph.GetPoint(poly, i), pt2);
310                         pt = pt2;
311                 }
312         }
313 }
314
315
316 void EditWindow::ClearSelection(void)
317 {
318         for(int i=0; i<65536; i++)
319                 selectedPoints[i] = false;
320 }
321
322
323 void EditWindow::mousePressEvent(QMouseEvent * event)
324 {
325         if (event->button() == Qt::RightButton)
326         {
327                 toolPalette->move(event->globalPos());
328                 toolPalette->setVisible(true);
329                 setCursor(cur[TOOLSelect]);
330                 toolPalette->prevTool = TOOLSelect;
331         }
332         else if (event->button() == Qt::MidButton)
333         {
334                 // Scrolling cursor
335                 setCursor(cur[11]);
336                 ptPrevious = Vector(event->x(), event->y());
337                 ptPrevious /= Global::zoom;
338         }
339         else if (event->button() == Qt::LeftButton)
340         {
341                 if (tool == TOOLScroll || tool == TOOLZoom)
342 ;//meh                  CaptureMouse();                                         // Make sure we capture the mouse when in scroll/zoom mode
343                 else if (tool == TOOLMultiSelect)
344                 {
345 //                      QPoint pt = GetAdjustedMousePosition(event);
346                         Vector pt = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
347                         selectionInProgress = true;
348                         selection.setTopLeft(QPoint(pt.x, pt.y));
349                         selection.setBottomRight(QPoint(pt.x, pt.y));
350                 }
351                 else if (tool == TOOLAddPt)             // "Add Point" tool
352                 {
353 //                      QPoint pt = GetAdjustedMousePosition(event);
354                         Vector pt = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
355                         IPoint pointToAdd(pt.x, pt.y, ((event->modifiers() == Qt::ShiftModifier || event->modifiers() == Qt::ControlModifier) ? false : true));
356
357                         if (pts.GetNumPoints() < 2)
358                         {
359 //                              pts += IPoint(pt.x(), pt.y(), ((event->modifiers() == Qt::ShiftModifier || event->modifiers() == Qt::ControlModifier) ? false : true));
360                                 pts += pointToAdd;
361                                 ptHighlight = pts.GetNumPoints() - 1;
362                         }
363                         else
364                         {
365 //                              QPoint pt = GetAdjustedMousePosition(event);
366 //                              pts.InsertPoint(pts.GetNext(ptHighlight), pt.x(), pt.y(), ((event->modifiers() == Qt::ShiftModifier || event->modifiers() == Qt::ControlModifier) ? false : true));
367                                 pts.InsertPoint(pts.GetNext(ptHighlight), pointToAdd);
368                                 ptHighlight = ptNextHighlight;
369 //                              update();
370                         }
371
372                         update();
373                 }
374                 else if (tool == TOOLAddPoly)   // "Add Poly" tool
375                 {
376 #ifdef DEBUGFOO
377 WriteLogMsg("Adding point... # polys: %u, # points: %u", pts.GetNumPolys(), pts.GetNumPoints());
378 #endif
379                         if (polyFirstPoint)
380                         {
381                                 polyFirstPoint = false;
382                                 pts.AddNewPolyAtEnd();
383                         }
384
385 //                      QPoint pt = GetAdjustedMousePosition(event);
386                         Vector pt = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
387 //printf("GetAdjustedMousePosition = %i, %i\n", pt.x(), pt.y());
388                         // Append a point to the end of the structure
389                         pts += IPoint(pt.x, pt.y, ((event->modifiers() == Qt::ShiftModifier || event->modifiers() == Qt::ControlModifier) ? false : true));
390                         ptHighlight = pts.GetNumPoints() - 1;
391                         update();
392 #ifdef DEBUGFOO
393 WriteLogMsg(" --> [# polys: %u, # points: %u]\n", pts.GetNumPolys(), pts.GetNumPoints());
394 #endif
395                 }
396                 else if (tool == TOOLSelect || tool == TOOLPolySelect)
397                 {
398                         if (pts.GetNumPoints() > 0)
399                         {
400 //                              pt = GetAdjustedClientPosition(pts.GetX(ptHighlight), pts.GetY(ptHighlight));
401                                 Vector pt = Painter::CartesianToQtCoords(Vector(pts.GetX(ptHighlight), pts.GetY(ptHighlight)));
402 //printf("GetAdjustedClientPosition = %lf, %lf\n", pt.x, pt.y);
403                                 QPoint warp(pt.x, pt.y);
404                                 QCursor::setPos(mapToGlobal(warp));
405
406                                 if (event->modifiers() == Qt::ShiftModifier || event->modifiers() == Qt::ControlModifier)
407                                 {
408                                         pts.SetOnCurve(ptHighlight, !pts.GetOnCurve(ptHighlight));
409                                         update();
410                                 }
411                         }
412                 }
413                 else if (tool == TOOLDelPt)
414                 {
415                         if (pts.GetNumPoints() > 0)
416 //Or could use:
417 //                      if (ptHighlight != -1)
418                         {
419 //This assumes that WM_MOUSEMOVE happens before this!
420 //The above commented out line should take care of this contingency... !!! FIX !!!
421                                 pts.DeletePoint(ptHighlight);
422                                 update();
423                         }
424                 }
425                 else if (tool == TOOLRotate)
426                 {
427                         // I think what's needed here is to keep the initial mouse click,
428                         // paint the rotation center, then use the 1st mouse move event to
429                         // establish the rotation "zero line", which becomes the line of
430                         // reference to all subsequent mouse moves.
431 //                      rotationCenter = GetAdjustedMousePosition(event);
432                         rotationCenter = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
433                         showRotationCenter = true;
434                         haveZeroPoint = false;
435                         rotationAngle = 0;
436                         update();
437                 }
438                 else if (tool == TOOLRotatePoly)
439                 {
440                         IPoint centroid = pts.GetPolyCentroid(pts.GetPolyForPointNumber(ptHighlight));
441                         rotationCenter = Vector(centroid.x, centroid.y);
442                         showRotationCenter = true;
443 //                      pt = GetAdjustedClientPosition(pts.GetX(ptHighlight), pts.GetY(ptHighlight));
444                         Vector pt = Painter::CartesianToQtCoords(Vector(pts.GetX(ptHighlight), pts.GetY(ptHighlight)));
445                         QCursor::setPos(mapToGlobal(QPoint(pt.x, pt.y)));
446                         rotationZeroPoint = Vector(pts.GetX(ptHighlight), pts.GetY(ptHighlight));
447                         haveZeroPoint = true;
448                         rotationAngle = 0;
449                         update();
450                 }
451                 else if (tool == TOOLFlipWinding)
452                 {
453                         pts.InvertPolyDrawSequence(pts.GetPolyForPointNumber(ptHighlight));
454 //                      pt = GetAdjustedClientPosition(pts.GetX(ptHighlight), pts.GetY(ptHighlight));
455                         Vector pt = Painter::CartesianToQtCoords(Vector(pts.GetX(ptHighlight), pts.GetY(ptHighlight)));
456                         QCursor::setPos(mapToGlobal(QPoint(pt.x, pt.y)));
457                         update();
458                 }
459         }
460
461         event->accept();
462 }
463
464
465 void EditWindow::mouseMoveEvent(QMouseEvent * event)
466 {
467         if (event->buttons() == Qt::RightButton)
468         {
469                 ToolType newTool = toolPalette->FindSelectedTool();
470
471                 if (newTool != toolPalette->prevTool)
472                 {
473                         toolPalette->prevTool = newTool;
474                         toolPalette->repaint();
475                 }
476         }
477         else if (event->buttons() == Qt::MidButton)
478         {
479                 // Calc offset from previous point
480                 Vector pt(event->x(), event->y());
481                 pt /= Global::zoom;
482                 ptOffset = Vector(pt.x - ptPrevious.x, -(pt.y - ptPrevious.y));
483
484 // Then multiply it by the scaling factor. Whee!
485                 // This looks wacky because we're using screen coords for the offset...
486                 // Otherwise, we would subtract both offsets!
487 //              offsetX -= ptOffset.x, offsetY += ptOffset.y;
488                 Global::origin -= ptOffset;
489                 ptPrevious = pt;
490                 update();
491         }
492         else if (event->buttons() == Qt::LeftButton)
493         {
494                 if (tool == TOOLAddPt || tool == TOOLAddPoly || tool == TOOLSelect)
495                 {
496                         // Bail out if we have the select tool and no points yet...
497                         if (tool == TOOLSelect && pts.GetNumPoints() == 0)
498                                 return;
499
500 //                      QPoint pt2 = GetAdjustedMousePosition(event);
501                         Vector pt2 = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
502                         pts.SetXY(ptHighlight, pt2.x, pt2.y);
503                         update();
504                 }
505                 else if (tool == TOOLPolySelect)
506                 {
507                         if (pts.GetNumPoints() > 0)
508                         {
509 //                              QPoint pt2 = GetAdjustedMousePosition(event);
510                                 Vector pt2 = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
511                                 // Should also set onCurve here as well, depending on keystate
512 //Or should we?
513 //Would be nice, but we'd need to trap the keyPressEvent() as well, otherwise pressing/releasing
514 //the hotkey would show no change until the user moved their mouse.
515                                 pts.OffsetPoly(pts.GetPoly(ptHighlight), pt2.x - pts.GetX(ptHighlight), pt2.y - pts.GetY(ptHighlight));
516                                 update();
517                         }
518                 }
519                 else if (tool == TOOLRotate || tool == TOOLRotatePoly)
520                 {
521                         if (pts.GetNumPoints() > 0)
522                         {
523                                 if (!haveZeroPoint)
524                                 {
525 //                                      rotationZeroPoint = GetAdjustedMousePosition(event);
526                                         rotationZeroPoint = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
527                                         haveZeroPoint = true;
528                                 }
529                                 else
530                                 {
531                                         // Figure out the angle between the "zero" vector and the current one,
532                                         // then rotate all points relative to the "zero" vector (done by paint())
533 //                                      QPoint currentPoint = GetAdjustedMousePosition(event);
534                                         Vector currentPoint = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
535                                         Vector v1(rotationZeroPoint.x, rotationZeroPoint.y, 0,
536                                                 rotationCenter.x, rotationCenter.y, 0);
537                                         Vector v2(currentPoint.x, currentPoint.y, 0,
538                                                 rotationCenter.x, rotationCenter.y, 0);
539 //                                      rotationAngle = v1.Angle(v2);
540                                         rotationAngle = v2.Angle(v1);
541
542                                         QString s;
543                                         s.sprintf("%.3f degrees", rotationAngle * 180.0 / 3.14159265358979323);
544                                         ((TTEdit *)qApp)->mainWindow->statusBar()->showMessage(s);
545                                 }
546
547                                 update();
548                         }
549                 }
550                 else if (tool == TOOLMultiSelect)
551                 {
552 //                      QPoint pt = GetAdjustedMousePosition(event);
553                         Vector pt = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
554                         selection.setBottomRight(QPoint(pt.x, pt.y));
555
556                         for(int i=0; i<pts.GetNumPoints(); i++)
557                         {
558                                 if (selection.contains(QPoint(pts.GetX(i), pts.GetY(i))))
559                                         selectedPoints[i] = true;
560                                 else
561                                         selectedPoints[i] = false;
562                         }
563
564                         update();
565                 }
566         }
567         else if (event->buttons() == Qt::NoButton)
568         {
569                 // Moving, not dragging...
570                 if (tool == TOOLSelect || tool == TOOLDelPt || tool == TOOLAddPt
571                         || tool == TOOLPolySelect || tool == TOOLRotatePoly || tool == TOOLFlipWinding)
572                 {
573 //                      QPoint pt2 = GetAdjustedMousePosition(event);
574                         Vector pt2 = Painter::QtToCartesianCoords(Vector(event->x(), event->y()));
575                         double closest = 1.0e+99;
576
577                         for(int i=0; i<pts.GetNumPoints(); i++)
578                         {
579                                 double dist = ((pt2.x - pts.GetX(i)) * (pt2.x - pts.GetX(i)))
580                                         + ((pt2.y - pts.GetY(i)) * (pt2.y - pts.GetY(i)));
581
582                                 if (dist < closest)
583                                         closest = dist, ptHighlight = i;
584                         }
585
586                         if (ptHighlight != oldPtHighlight)
587                         {
588                                 oldPtHighlight = ptHighlight;
589                                 update();
590                         }
591
592                         // What follows here looks like voodoo, but is really simple. What
593                         // we do is check to see if the mouse point has a perpendicular
594                         // intersection with any of the line segments. If it does,
595                         // calculate the length of the perpendicular and choose the
596                         // smallest length. If there is no perpendicular, then choose the
597                         // length of line connecting the closer of either the first
598                         // endpoint or the second and choose the smallest of those.
599
600                         // There is one bit of math that looks like voodoo to me ATM--will
601                         // explain once I understand it better (the calculation of the
602                         // length of the perpendicular).
603
604                         if (pts.GetNumPoints() > 1 && tool == TOOLAddPt)
605                         {
606                                 double smallest = 1.0e+99;
607
608                                 for(int i=0; i<pts.GetNumPoints(); i++)
609                                 {
610                                         int32_t p1x = pts.GetX(i), p1y = pts.GetY(i),
611                                                 p2x = pts.GetX(pts.GetNext(i)), p2y = pts.GetY(pts.GetNext(i));
612
613                                         Vector ls(p2x, p2y, 0, p1x, p1y, 0),
614                                                 v1(pt2.x, pt2.y, 0, p1x, p1y, 0),
615                                                 v2(pt2.x, pt2.y, 0, p2x, p2y, 0);
616                                         double pp = ls.Dot(v1) / ls.Magnitude(), dist;
617 // Geometric interpretation:
618 // pp is the paremeterized point on the vector ls where the perpendicular
619 // intersects ls. If pp < 0, then the perpendicular lies beyond the 1st
620 // endpoint. If pp > length of ls, then the perpendicular lies beyond the 2nd
621 // endpoint.
622
623                                         if (pp < 0.0)
624                                                 dist = v1.Magnitude();
625                                         else if (pp > ls.Magnitude())
626                                                 dist = v2.Magnitude();
627                                         else                                    // distance = ?Det?(ls, v1) / |ls|
628                                                 dist = fabs((ls.x * v1.y - v1.x * ls.y) / ls.Magnitude());
629
630 //The answer to the above looks like it might be found here:
631 //
632 //If the segment endpoints are s and e, and the point is p, then the test for
633 //the perpendicular intercepting the segment is equivalent to insisting that
634 //the two dot products {s-e}.{s-p} and {e-s}.{e-p} are both non-negative.
635 //Perpendicular distance from the point to the segment is computed by first
636 //computing the area of the triangle the three points form, then dividing by
637 //the length of the segment.  Distances are done just by the Pythagorean
638 //theorem. Twice the area of the triangle formed by three points is the
639 //determinant of the following matrix:
640 //
641 //sx sy 1
642 //ex ey 1
643 //px py 1
644 //
645 //By translating the start point to the origin, this can be rewritten as:
646 //By subtracting row 1 from all rows, you get the following:
647 //[because sx = sy = 0. you could leave out the -sx/y terms below. because we
648 //subtracted row 1 from all rows (including row 1) row 1 turns out to be zero.
649 //duh!]
650 //
651 //0         0         0
652 //(ex - sx) (ey - sy) 0
653 //(px - sx) (py - sy) 0
654 //
655 //which greatly simplifies the calculation of the determinant.
656
657                                         if (dist < smallest)
658                                                 smallest = dist, ptNextHighlight = pts.GetNext(i), ptHighlight = i;
659                                 }
660
661                                 if (ptNextHighlight != oldPtNextHighlight)
662                                 {
663                                         oldPtNextHighlight = ptNextHighlight;
664                                         update();
665                                 }
666                         }
667                 }
668
669                 ptPrevious = Vector(event->x(), event->y());
670         }
671
672         event->accept();
673 }
674
675
676 void EditWindow::mouseReleaseEvent(QMouseEvent * event)
677 {
678         if (event->button() == Qt::RightButton)
679         {
680                 ToolType newTool = toolPalette->FindSelectedTool();
681
682                 // We only change the tool if a new one was actually selected. Otherwise, we do nothing.
683                 if (newTool != TOOLNone)
684                 {
685                         tool = newTool;
686
687                         if (tool == TOOLScroll || tool == TOOLZoom || tool == TOOLAddPoly
688                                 || tool == TOOLDelPoly)
689                                 ptHighlight = -1;
690
691                         if (tool == TOOLAddPoly)
692                                 polyFirstPoint = true;
693                 }
694
695                 toolPalette->setVisible(false);
696                 setCursor(cur[tool]);
697                 // Just in case we changed highlighting style with the new tool...
698                 update();
699         }
700         else if (event->button() == Qt::MidButton)
701         {
702                 setCursor(cur[tool]);                                           // Restore previous cursor
703         }
704         else if (event->button() == Qt::LeftButton)
705         {
706                 if (showRotationCenter)
707                 {
708                         showRotationCenter = false;
709                         haveZeroPoint = false;
710
711                         if (tool == TOOLRotate)
712                                 pts.RotatePoints(rotationAngle, IPoint(rotationCenter.x, rotationCenter.y));
713                         else
714                         {
715                                 uint16_t poly = pts.GetPolyForPointNumber(ptHighlight);
716                                 pts.RotatePolyAroundCentroid(poly, rotationAngle);
717                         }
718
719                         update();
720                         ((TTEdit *)qApp)->mainWindow->statusBar()->showMessage("");
721                 }
722
723 //              if (tool == TOOLScroll || tool == TOOLZoom)
724 //                      ReleaseMouse();
725 //this is prolly too much
726                 ((TTEdit *)qApp)->charWnd->MakePathFromPoints(&pts);
727                 ((TTEdit *)qApp)->charWnd->update();
728
729                 if (tool == TOOLMultiSelect)
730                 {
731                         selectionInProgress = false;
732                         update();
733                 }
734         }
735
736         event->accept();
737 }
738
739
740 void EditWindow::keyPressEvent(QKeyEvent * event)
741 {
742         // Sanity checking...
743         if (ptHighlight == -1)
744                 return;
745
746         if (event->key() == Qt::Key_Up)
747         {
748                 pts.SetXY(ptHighlight, pts.GetX(ptHighlight), pts.GetY(ptHighlight) + 1);
749         }
750         else if (event->key() == Qt::Key_Down)
751                 pts.SetXY(ptHighlight, pts.GetX(ptHighlight), pts.GetY(ptHighlight) - 1);
752         else if (event->key() == Qt::Key_Right)
753                 pts.SetXY(ptHighlight, pts.GetX(ptHighlight) + 1, pts.GetY(ptHighlight));
754         else if (event->key() == Qt::Key_Left)
755                 pts.SetXY(ptHighlight, pts.GetX(ptHighlight) - 1, pts.GetY(ptHighlight));
756         else
757                 return;
758
759         event->accept();
760         update();
761         ((TTEdit *)qApp)->charWnd->MakePathFromPoints(&pts);
762         ((TTEdit *)qApp)->charWnd->update();
763 }
764
765
766 void EditWindow::keyReleaseEvent(QKeyEvent * /*event*/)
767 {
768 }
769