]> Shamusworld >> Repos - ttedit/blob - src/editwindow.cpp
Fix for missing ampersand in QApplication.
[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 <QtGui>
38 #include "graphicprimitives.h"
39 #include "debug.h"
40 #include "vector.h"
41 #include "charwindow.h"
42 #include "ttedit.h"
43
44 EditWindow::EditWindow(QWidget * parent/*= NULL*/): QWidget(parent),
45         scale(1.0), offsetX(-10), offsetY(-10), tool(TOOLSelect),
46         ptHighlight(-1), oldPtHighlight(-1), ptNextHighlight(-1), oldPtNextHighlight(-1),
47         polyFirstPoint(true)
48 {
49         setBackgroundRole(QPalette::Base);
50         setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
51
52         toolPalette = new ToolWindow();
53         CreateCursors();
54         setCursor(cur[TOOLSelect]);
55         setMouseTracking(true);
56 }
57
58 QSize EditWindow::minimumSizeHint() const
59 {
60         return QSize(50, 50);
61 }
62
63 QSize EditWindow::sizeHint() const
64 {
65         return QSize(400, 400);
66 }
67
68 void EditWindow::CreateCursors(void)
69 {
70         int hotx[8] = {  1,  1, 11, 15,  1,  1,  1,  1 };
71         int hoty[8] = {  1,  1, 11, 13,  1,  1,  1,  1 };
72
73         for(int i=0; i<8; i++)
74         {
75                 QString s;
76                 s.sprintf(":/res/cursor%u.png", i+1);
77                 QPixmap pmTmp(s);
78                 cur[i] = QCursor(pmTmp, hotx[i], hoty[i]);
79         }
80 }
81
82 QPoint EditWindow::GetAdjustedMousePosition(QMouseEvent * event)
83 {
84         QSize winSize = size();
85         // This is undoing the transform, e.g. going from client coords to local coords.
86         // In essence, the height - y is height + (y * -1), the (y * -1) term doing the
87         // conversion of the y-axis from increasing bottom to top.
88         return QPoint(offsetX + event->x(), offsetY + (winSize.height() - event->y()));
89 }
90
91 QPoint EditWindow::GetAdjustedClientPosition(int x, int y)
92 {
93         QSize winSize = size();
94
95         // VOODOO ALERT (ON Y COMPONENT!!!!)
96         return QPoint(-offsetX + x, (winSize.height() - (-offsetY + y)) * +1.0);
97 }
98
99 /*
100 TODO:
101  o  Different colors for polys on selected points
102  o  Different colors for handles on non-selected polys
103  o  Line of sight (dashed, dotted) for off-curve points
104  o  Repaints for press/release of CTRL/SHIFT during point creation
105 */
106 void EditWindow::paintEvent(QPaintEvent * /*event*/)
107 {
108         QPainter p(this);
109 //hm, causes lockup (or does it???)
110         p.setRenderHint(QPainter::Antialiasing);
111 //Doesn't do crap!
112 //dc.SetBackground(*wxWHITE_BRUSH);
113
114 // Due to the screwiness of wxWidgets coord system, the origin is ALWAYS
115 // the upper left corner--regardless of axis orientation, etc...
116 //      int width, height;
117 //      dc.GetSize(&width, &height);
118         QSize winSize = size();
119
120 //      dc.SetDeviceOrigin(-offsetX, height - (-offsetY));
121 //      dc.SetAxisOrientation(true, true);
122         p.translate(QPoint(-offsetX, winSize.height() - (-offsetY)));
123         p.scale(1.0, -1.0);
124
125 // Scrolling can be done by using OffsetViewportOrgEx
126 // Scaling can be done by adjusting SetWindowExtEx (it's denominator of txform)
127 // you'd use: % = ViewportExt / WindowExt
128 // But it makes the window look like crap: fuggetuboutit.
129 // Instead, we have to scale EVERYTHING by hand. Crap!
130 // It's not *that* bad, but not as convenient either...
131
132 //      dc.SetPen(*(wxThePenList->FindOrCreatePen(wxColour(0x00, 0x00, 0xFF), 1, wxDOT)));
133 ////    dc.DrawLine(0, 0, 10, 10);
134         p.setPen(QPen(Qt::blue, 1.0, Qt::DotLine));
135
136     // Draw coordinate axes
137
138 //      dc.CrossHair(0, 0);
139         p.drawLine(0, -16384, 0, 16384);
140         p.drawLine(-16384, 0, 16384, 0);
141
142     // Draw points
143
144         for(int i=0; i<pts.GetNumPoints(); i++)
145         {
146                 if (i == ptHighlight)
147                 {
148 //                      dc.SetPen(*(wxThePenList->FindOrCreatePen(wxColour(0xFF, 0x00, 0x00), 1, wxSOLID)));
149 ////                    SelectObject(hdc, hRedPen1);
150                         p.setPen(QPen(Qt::red, 1.0, Qt::SolidLine));
151
152                         if (pts.GetOnCurve(i))
153                         {
154                                 DrawSquareDotN(p, pts.GetX(i), pts.GetY(i), 7);
155                                 DrawSquareDotN(p, pts.GetX(i), pts.GetY(i), 9);
156                         }
157                         else
158                         {
159                                 DrawRoundDotN(p, pts.GetX(i), pts.GetY(i), 7);
160                                 DrawRoundDotN(p, pts.GetX(i), pts.GetY(i), 9);
161                         }
162                 }
163                 else if ((i == ptHighlight || i == ptNextHighlight) && tool == TOOLAddPt)
164                 {
165 //                      dc.SetPen(*(wxThePenList->FindOrCreatePen(wxColour(0x00, 0xAF, 0x00), 1, wxSOLID)));
166 ////                    SelectObject(hdc, hGreenPen1);
167                         p.setPen(QPen(Qt::green, 1.0, Qt::SolidLine));
168
169                         if (pts.GetOnCurve(i))
170                         {
171                                 DrawSquareDotN(p, pts.GetX(i), pts.GetY(i), 7);
172                                 DrawSquareDotN(p, pts.GetX(i), pts.GetY(i), 9);
173                         }
174                         else
175                         {
176                                 DrawRoundDotN(p, pts.GetX(i), pts.GetY(i), 7);
177                                 DrawRoundDotN(p, pts.GetX(i), pts.GetY(i), 9);
178                         }
179                 }
180                 else
181                 {
182 //                      dc.SetPen(*(wxThePenList->FindOrCreatePen(wxColour(0x00, 0x00, 0x00), 1, wxSOLID)));
183 ////                    SelectObject(hdc, hBlackPen1);
184                         p.setPen(QPen(Qt::black, 1.0, Qt::SolidLine));
185
186                         if (pts.GetOnCurve(i))
187                                 DrawSquareDot(p, pts.GetX(i), pts.GetY(i));
188                         else
189                                 DrawRoundDot(p, pts.GetX(i), pts.GetY(i));
190                 }
191
192                 if (tool == TOOLDelPt && i == ptHighlight)
193                 {
194 #if 0
195                         dc.SetPen(*(wxThePenList->FindOrCreatePen(wxColour(0xFF, 0x00, 0x00), 1, wxSOLID)));
196 //                      SelectObject(hdc, hRedPen1);
197 //                      MoveToEx(hdc, pts.GetX(i) - 5, pts.GetY(i) - 5, NULL);
198 //                      LineTo(hdc, pts.GetX(i) + 5, pts.GetY(i) + 5);
199 //                      LineTo(hdc, pts.GetX(i) - 5, pts.GetY(i) - 5);//Lameness!
200 //                      MoveToEx(hdc, pts.GetX(i) - 5, pts.GetY(i) + 5, NULL);
201 //                      LineTo(hdc, pts.GetX(i) + 5, pts.GetY(i) - 5);
202 //                      LineTo(hdc, pts.GetX(i) - 5, pts.GetY(i) + 5);//More lameness!!
203 #endif
204                         p.setPen(QPen(Qt::red, 1.0, Qt::SolidLine));
205                         p.drawLine(pts.GetX(i) - 5, pts.GetY(i) - 5, pts.GetX(i) + 5, pts.GetY(i) + 5);
206                         p.drawLine(pts.GetX(i) + 5, pts.GetY(i) - 5, pts.GetX(i) - 5, pts.GetY(i) + 5);
207                 }
208         }
209
210 ////            SelectObject(hdc, hBlackPen1);
211 //      dc.SetPen(*(wxThePenList->FindOrCreatePen(wxColour(0x00, 0x00, 0x00), 1, wxSOLID)));
212         p.setPen(QPen(Qt::black, 1.0, Qt::SolidLine));
213
214         // Draw curve formed by points
215
216         for(int poly=0; poly<pts.GetNumPolys(); poly++)
217         {
218                 if (pts.GetNumPoints(poly) > 2)
219                 {
220                         // Initial move...
221                         // If it's not on curve, then move to it, otherwise move to last point...
222
223                         int x, y;
224
225                         if (pts.GetOnCurve(poly, pts.GetNumPoints(poly) - 1))
226                                 x = (int)pts.GetX(poly, pts.GetNumPoints(poly) - 1), y = (int)pts.GetY(poly, pts.GetNumPoints(poly) - 1);
227                         else
228                                 x = (int)pts.GetX(poly, 0), y = (int)pts.GetY(poly, 0);
229
230                         for(int i=0; i<pts.GetNumPoints(poly); i++)
231                         {
232                                 if (pts.GetOnCurve(poly, i))
233 //                                      LineTo(hdc, pts.GetX(poly, i), pts.GetY(poly, i));
234                                 {
235                                         p.drawLine(x, y, pts.GetX(poly, i), pts.GetY(poly, i));
236                                         x = (int)pts.GetX(poly, i), y = (int)pts.GetY(poly, i);
237                                 }
238                                 else
239                                 {
240                                         uint32 prev = pts.GetPrev(poly, i), next = pts.GetNext(poly, i);
241                                         float px = pts.GetX(poly, prev), py = pts.GetY(poly, prev),
242                                                 nx = pts.GetX(poly, next), ny = pts.GetY(poly, next);
243
244                                         if (!pts.GetOnCurve(poly, prev))
245                                                 px = (px + pts.GetX(poly, i)) / 2.0f,
246                                                 py = (py + pts.GetY(poly, i)) / 2.0f;
247
248                                         if (!pts.GetOnCurve(poly, next))
249                                                 nx = (nx + pts.GetX(poly, i)) / 2.0f,
250                                                 ny = (ny + pts.GetY(poly, i)) / 2.0f;
251
252                                         Bezier(p, point(px, py), point(pts.GetX(poly, i), pts.GetY(poly, i)), point(nx, ny));
253                                         x = (int)nx, y = (int)ny;
254
255                                         if (pts.GetOnCurve(poly, next))
256                                                 i++;                                    // Following point is on curve, so move past it
257                                 }
258                         }
259                 }
260         }
261 }
262
263 void EditWindow::mousePressEvent(QMouseEvent * event)
264 {
265         if (event->button() == Qt::RightButton)
266         {
267                 toolPalette->move(event->globalPos());
268                 toolPalette->setVisible(true);
269                 setCursor(cur[TOOLSelect]);
270                 toolPalette->prevTool = TOOLSelect;
271         }
272         else if (event->button() == Qt::MidButton)
273         {
274                 setCursor(cur[2]);                                                      // Scrolling cursor
275         }
276         else if (event->button() == Qt::LeftButton)
277         {
278                 if (tool == TOOLScroll || tool == TOOLZoom)
279 ;//meh                  CaptureMouse();                                         // Make sure we capture the mouse when in scroll/zoom mode
280                 else if (tool == TOOLAddPt)             // "Add Point" tool
281                 {
282                         if (pts.GetNumPoints() > 0)
283                         {
284                                 QPoint pt = GetAdjustedMousePosition(event);
285                                 pts.InsertPoint(pts.GetNext(ptHighlight), pt.x(), pt.y(), ((event->modifiers() == Qt::ShiftModifier || event->modifiers() == Qt::ControlModifier) ? false : true));
286                                 ptHighlight = ptNextHighlight;
287                                 update();
288                         }
289                 }
290                 else if (tool == TOOLAddPoly)   // "Add Poly" tool
291                 {
292 #ifdef DEBUGFOO
293 WriteLogMsg("Adding point... # polys: %u, # points: %u", pts.GetNumPolys(), pts.GetNumPoints());
294 #endif
295                         if (polyFirstPoint)
296                         {
297                                 polyFirstPoint = false;
298                                 pts.AddNewPolyAtEnd();
299                         }
300
301                         QPoint pt = GetAdjustedMousePosition(event);
302 //printf("GetAdjustedMousePosition = %i, %i\n", pt.x(), pt.y());
303                         // Append a point to the end of the structure
304                         pts += IPoint(pt.x(), pt.y(), ((event->modifiers() == Qt::ShiftModifier || event->modifiers() == Qt::ControlModifier) ? false : true));
305                         ptHighlight = pts.GetNumPoints() - 1;
306                         update();
307 #ifdef DEBUGFOO
308 WriteLogMsg(" --> [# polys: %u, # points: %u]\n", pts.GetNumPolys(), pts.GetNumPoints());
309 #endif
310                 }
311                 else if (tool == TOOLSelect || tool == TOOLPolySelect)
312                 {
313                         if (pts.GetNumPoints() > 0)
314                         {
315                                 pt = GetAdjustedClientPosition(pts.GetX(ptHighlight), pts.GetY(ptHighlight));
316 //printf("GetAdjustedClientPosition = %i, %i\n", pt.x(), pt.y());
317 //                              WarpPointer(pt.x, pt.y);
318                                 QCursor::setPos(mapToGlobal(pt));
319
320                                 if (event->modifiers() == Qt::ShiftModifier || event->modifiers() == Qt::ControlModifier)
321                                 {
322                                         pts.SetOnCurve(ptHighlight, !pts.GetOnCurve(ptHighlight));
323                                         update();
324                                 }
325                         }
326                 }
327                 else if (tool == TOOLDelPt)
328                 {
329                         if (pts.GetNumPoints() > 0)
330 //Or could use:
331 //                      if (ptHighlight != -1)
332                         {
333 //This assumes that WM_MOUSEMOVE happens before this!
334 //The above commented out line should take care of this contingency... !!! FIX !!!
335                                 pts.DeletePoint(ptHighlight);
336                                 update();
337                         }
338                 }
339         }
340
341         event->accept();
342 }
343
344 void EditWindow::mouseMoveEvent(QMouseEvent * event)
345 {
346         if (event->buttons() == Qt::RightButton)
347         {
348                 ToolType newTool = toolPalette->FindSelectedTool();
349
350                 if (newTool != toolPalette->prevTool)
351                 {
352                         toolPalette->prevTool = newTool;
353                         toolPalette->repaint();
354                 }
355         }
356         else if (event->buttons() == Qt::MidButton)
357         {
358                 // Calc offset from previous point
359                 pt = event->pos();
360                 ptOffset = QPoint(pt.x() - ptPrevious.x(), pt.y() - ptPrevious.y());
361
362 // Then multiply it by the scaling factor. Whee!
363                 // This looks wacky because we're using screen coords for the offset...
364                 // Otherwise, we would subtract both offsets!
365                 offsetX -= ptOffset.x(), offsetY += ptOffset.y();
366                 update();
367                 ptPrevious = pt;
368         }
369         else if (event->buttons() == Qt::LeftButton)
370         {
371 #if 0
372                         if (tool == TOOLScroll)
373                         {
374                             // Extract current point from lParam/calc offset from previous point
375
376                                 pt = e.GetPosition();
377                                 ptOffset.x = pt.x - ptPrevious.x,
378                                 ptOffset.y = pt.y - ptPrevious.y;
379
380                                 // NOTE: OffsetViewportOrg operates in DEVICE UNITS...
381
382 //Seems there's no equivalent for this in wxWidgets...!
383 //!!! FIX !!!
384 //                              hdc = GetDC(hWnd);
385 //                              OffsetViewportOrgEx(hdc, ptOffset.x, ptOffset.y, NULL);
386 //                              ReleaseDC(hWnd, hdc);
387
388 // this shows that it works, so the logic above must be faulty...
389 // And it is. It should convert the coords first, then do the subtraction to figure the offset...
390 // Above: DONE
391 // Then multiply it by the scaling factor. Whee!
392                                 // This looks wacky because we're using screen coords for the offset...
393                                 // Otherwise, we would subtract both offsets!
394                                 offsetX -= ptOffset.x, offsetY += ptOffset.y;
395                                 Refresh();
396                         }
397                         else
398 #endif
399                         if (tool == TOOLAddPt || tool == TOOLAddPoly || tool == TOOLSelect)
400                         {
401                                 if (tool != TOOLAddPt || pts.GetNumPoints() > 0)//yecch.
402                                 {
403 //temporary, for testing. BTW, Select drag bug is here...!
404 #if 1
405                                         QPoint pt2 = GetAdjustedMousePosition(event);
406                                         pts.SetXY(ptHighlight, pt2.x(), pt2.y());
407                                         update();
408 #endif
409                                 }
410                         }
411                         else if (tool == TOOLPolySelect)
412                         {
413                                 if (pts.GetNumPoints() > 0)
414                                 {
415                                         QPoint pt2 = GetAdjustedMousePosition(event);
416                                         // Should also set onCurve here as well, depending on keystate
417 //Or should we?
418                                         pts.OffsetPoly(pts.GetPoly(ptHighlight), pt2.x() - pts.GetX(ptHighlight), pt2.y() - pts.GetY(ptHighlight));
419                                         update();
420                                 }
421                         }
422         }
423         else if (event->buttons() == Qt::NoButton)
424         {
425                 // Moving, not dragging...
426                 if (tool == TOOLSelect || tool == TOOLDelPt || tool == TOOLAddPt
427                         || tool == TOOLPolySelect)// || tool == TOOLAddPoly)
428                 {
429                         QPoint pt2 = GetAdjustedMousePosition(event);
430                         double closest = 1.0e+99;
431
432                         for(int i=0; i<pts.GetNumPoints(); i++)
433                         {
434                                 double dist = ((pt2.x() - pts.GetX(i)) * (pt2.x() - pts.GetX(i)))
435                                         + ((pt2.y() - pts.GetY(i)) * (pt2.y() - pts.GetY(i)));
436
437                                 if (dist < closest)
438                                         closest = dist, ptHighlight = i;
439                         }
440
441                         if (ptHighlight != oldPtHighlight)
442                         {
443                                 oldPtHighlight = ptHighlight;
444                                 update();
445                         }
446
447                         // What follows here looks like voodoo, but is really simple. What we do is
448                         // check to see if the mouse point has a perpendicular intersection with any of
449                         // the line segments. If it does, calculate the length of the perpendicular
450                         // and choose the smallest length. If there is no perpendicular, then choose the
451                         // length of line connecting the closer of either the first endpoint or the
452                         // second and choose the smallest of those.
453
454                         // There is one bit of math that looks like voodoo to me ATM--will explain once
455                         // I understand it better (the calculation of the length of the perpendicular).
456
457                         if (pts.GetNumPoints() > 1 && tool == TOOLAddPt)
458                         {
459                                 double smallest = 1.0e+99;
460
461                                 for(int i=0; i<pts.GetNumPoints(); i++)
462                                 {
463                                         int32 p1x = pts.GetX(i), p1y = pts.GetY(i),
464                                                 p2x = pts.GetX(pts.GetNext(i)), p2y = pts.GetY(pts.GetNext(i));
465
466                                         vector ls(p2x, p2y, 0, p1x, p1y, 0), v1(pt2.x(), pt2.y(), 0, p1x, p1y, 0),
467                                                 v2(pt2.x(), pt2.y(), 0, p2x, p2y, 0);
468                                         double pp = ls.dot(v1) / ls.length(), dist;
469 // Geometric interpretation:
470 // pp is the paremeterized point on the vector ls where the perpendicular intersects ls.
471 // If pp < 0, then the perpendicular lies beyond the 1st endpoint. If pp > length of ls,
472 // then the perpendicular lies beyond the 2nd endpoint.
473
474                                         if (pp < 0.0)
475                                                 dist = v1.length();
476                                         else if (pp > ls.length())
477                                                 dist = v2.length();
478                                         else                                    // distance = ?Det?(ls, v1) / |ls|
479                                                 dist = fabs((ls.x * v1.y - v1.x * ls.y) / ls.length());
480
481 //The answer to the above looks like it might be found here:
482 //
483 //If the segment endpoints are s and e, and the point is p, then the test for the perpendicular
484 //intercepting the segment is equivalent to insisting that the two dot products {s-e}.{s-p} and
485 //{e-s}.{e-p} are both non-negative.  Perpendicular distance from the point to the segment is
486 //computed by first computing the area of the triangle the three points form, then dividing by the
487 //length of the segment.  Distances are done just by the Pythagorean theorem.  Twice the area of the
488 //triangle formed by three points is the determinant of the following matrix:
489 //
490 //sx sy 1
491 //ex ey 1
492 //px py 1
493 //
494 //By translating the start point to the origin, this can be rewritten as:
495 //By subtracting row 1 from all rows, you get the following:
496 //[because sx = sy = 0. you could leave out the -sx/y terms below. because we subtracted
497 // row 1 from all rows (including row 1) row 1 turns out to be zero. duh!]
498 //
499 //0         0         0
500 //(ex - sx) (ey - sy) 0
501 //(px - sx) (py - sy) 0
502 //
503 //which greatly simplifies the calculation of the determinant.
504
505                                         if (dist < smallest)
506                                                 smallest = dist, ptNextHighlight = pts.GetNext(i), ptHighlight = i;
507                                 }
508
509                                 if (ptNextHighlight != oldPtNextHighlight)
510                                 {
511                                         oldPtNextHighlight = ptNextHighlight;
512                                         update();
513                                 }
514                         }
515                 }
516
517                 ptPrevious = event->pos();
518         }
519
520         event->accept();
521 }
522
523 void EditWindow::mouseReleaseEvent(QMouseEvent * event)
524 {
525         if (event->button() == Qt::RightButton)
526         {
527                 ToolType newTool = toolPalette->FindSelectedTool();
528
529                 // We only change the tool if a new one was actually selected. Otherwise, we do nothing.
530                 if (newTool != TOOLNone)
531                 {
532                         tool = newTool;
533
534                         if (tool == TOOLScroll || tool == TOOLZoom || tool == TOOLAddPoly
535                                 || tool == TOOLDelPoly)
536                                 ptHighlight = -1;
537
538                         if (tool == TOOLAddPoly)
539                                 polyFirstPoint = true;
540                 }
541
542                 toolPalette->setVisible(false);
543                 setCursor(cur[tool]);
544                 // Just in case we changed highlighting style with the new tool...
545                 update();
546         }
547         else if (event->button() == Qt::MidButton)
548         {
549                 setCursor(cur[tool]);                                           // Restore previous cursor
550         }
551         else if (event->button() == Qt::LeftButton)
552         {
553 //              if (tool == TOOLScroll || tool == TOOLZoom)
554 //                      ReleaseMouse();
555 //this is prolly too much
556                 ((TTEdit *)qApp)->charWnd->MakePathFromPoints(&pts);
557                 ((TTEdit *)qApp)->charWnd->update();
558         }
559
560         event->accept();
561 }