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