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