xref: /reactos/base/applications/mspaint/mouse.cpp (revision 0c2cdcae)
1 /*
2  * PROJECT:    PAINT for ReactOS
3  * LICENSE:    LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later)
4  * PURPOSE:    Things which should not be in the mouse event handler itself
5  * COPYRIGHT:  Copyright 2015 Benedikt Freisen <b.freisen@gmx.net>
6  *             Copyright 2021-2023 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
7  */
8 
9 #include "precomp.h"
10 #include <atlalloc.h>
11 
12 static SIZE_T s_cPoints = 0;
13 static CHeapPtr<POINT, CLocalAllocator> s_dynamicPoints;
14 static POINT s_staticPoints[512]; // 512 is enough
15 static SIZE_T s_maxPoints = _countof(s_staticPoints);
16 static LPPOINT s_pPoints = s_staticPoints;
17 static POINT g_ptStart, g_ptEnd;
18 
19 /* FUNCTIONS ********************************************************/
20 
21 void
22 regularize(LONG x0, LONG y0, LONG& x1, LONG& y1)
23 {
24     if (labs(x1 - x0) >= labs(y1 - y0))
25         y1 = y0 + (y1 > y0 ? labs(x1 - x0) : -labs(x1 - x0));
26     else
27         x1 = x0 + (x1 > x0 ? labs(y1 - y0) : -labs(y1 - y0));
28 }
29 
30 void
31 roundTo8Directions(LONG x0, LONG y0, LONG& x1, LONG& y1)
32 {
33     if (labs(x1 - x0) >= labs(y1 - y0))
34     {
35         if (labs(y1 - y0) * 5 < labs(x1 - x0) * 2)
36             y1 = y0;
37         else
38             y1 = y0 + (y1 > y0 ? labs(x1 - x0) : -labs(x1 - x0));
39     }
40     else
41     {
42         if (labs(x1 - x0) * 5 < labs(y1 - y0) * 2)
43             x1 = x0;
44         else
45             x1 = x0 + (x1 > x0 ? labs(y1 - y0) : -labs(y1 - y0));
46     }
47 }
48 
49 BOOL nearlyEqualPoints(INT x0, INT y0, INT x1, INT y1)
50 {
51     INT cxThreshold = toolsModel.GetLineWidth() + UnZoomed(GetSystemMetrics(SM_CXDRAG));
52     INT cyThreshold = toolsModel.GetLineWidth() + UnZoomed(GetSystemMetrics(SM_CYDRAG));
53     return (abs(x1 - x0) <= cxThreshold) && (abs(y1 - y0) <= cyThreshold);
54 }
55 
56 void getBoundaryOfPoints(RECT& rcBoundary, SIZE_T cPoints, const POINT *pPoints)
57 {
58     POINT ptMin = { MAXLONG, MAXLONG }, ptMax = { (LONG)MINLONG, (LONG)MINLONG };
59     while (cPoints-- > 0)
60     {
61         LONG x = pPoints->x, y = pPoints->y;
62         ptMin = { min(x, ptMin.x), min(y, ptMin.y) };
63         ptMax = { max(x, ptMax.x), max(y, ptMax.y) };
64         ++pPoints;
65     }
66 
67     ptMax.x += 1;
68     ptMax.y += 1;
69 
70     CRect rc(ptMin, ptMax);
71     rcBoundary = rc;
72 }
73 
74 void ShiftPoints(INT dx, INT dy)
75 {
76     for (SIZE_T i = 0; i < s_cPoints; ++i)
77     {
78         POINT& pt = s_pPoints[i];
79         pt.x += dx;
80         pt.y += dy;
81     }
82 }
83 
84 void BuildMaskFromPoints()
85 {
86     CRect rc;
87     getBoundaryOfPoints(rc, s_cPoints, s_pPoints);
88 
89     ShiftPoints(-rc.left, -rc.top);
90 
91     HDC hdcMem = ::CreateCompatibleDC(NULL);
92     HBITMAP hbmMask = ::CreateBitmap(rc.Width(), rc.Height(), 1, 1, NULL);
93     HGDIOBJ hbmOld = ::SelectObject(hdcMem, hbmMask);
94     ::FillRect(hdcMem, &rc, (HBRUSH)::GetStockObject(BLACK_BRUSH));
95     HGDIOBJ hPenOld = ::SelectObject(hdcMem, GetStockObject(NULL_PEN));
96     HGDIOBJ hbrOld = ::SelectObject(hdcMem, GetStockObject(WHITE_BRUSH));
97     ::Polygon(hdcMem, s_pPoints, (INT)s_cPoints);
98     ::SelectObject(hdcMem, hbrOld);
99     ::SelectObject(hdcMem, hPenOld);
100     ::SelectObject(hdcMem, hbmOld);
101     ::DeleteDC(hdcMem);
102 
103     selectionModel.setMask(rc, hbmMask);
104 }
105 
106 void ToolBase::reset()
107 {
108     if (s_pPoints != s_staticPoints)
109     {
110         s_dynamicPoints.Free();
111         s_pPoints = s_staticPoints;
112         s_maxPoints = _countof(s_staticPoints);
113     }
114 
115     s_cPoints = 0;
116     g_ptEnd = g_ptStart = { -1, -1 };
117 
118     if (selectionModel.m_bShow)
119     {
120         selectionModel.Landing();
121         selectionModel.HideSelection();
122     }
123 }
124 
125 void ToolBase::OnEndDraw(BOOL bCancel)
126 {
127     reset();
128     imageModel.NotifyImageChanged();
129 }
130 
131 void ToolBase::beginEvent()
132 {
133     m_hdc = imageModel.GetDC();
134     m_fg = paletteModel.GetFgColor();
135     m_bg = paletteModel.GetBgColor();
136 }
137 
138 void ToolBase::endEvent()
139 {
140     m_hdc = NULL;
141 }
142 
143 static void pushToPoints(LONG x, LONG y)
144 {
145     if (s_cPoints + 1 >= s_maxPoints)
146     {
147         SIZE_T newMax = s_maxPoints + 512;
148         SIZE_T cbNew = newMax * sizeof(POINT);
149         if (!s_dynamicPoints.ReallocateBytes(cbNew))
150         {
151             ATLTRACE("%d, %d, %d\n", (INT)s_cPoints, (INT)s_maxPoints, (INT)cbNew);
152             return;
153         }
154 
155         if (s_pPoints == s_staticPoints)
156             CopyMemory(s_dynamicPoints, s_staticPoints, s_cPoints * sizeof(POINT));
157 
158         s_pPoints = s_dynamicPoints;
159         s_maxPoints = newMax;
160     }
161 
162     s_pPoints[s_cPoints++] = { x, y };
163 }
164 
165 /* TOOLS ********************************************************/
166 
167 struct TwoPointDrawTool : ToolBase
168 {
169     BOOL m_bLeftButton = FALSE;
170     BOOL m_bDrawing = FALSE;
171 
172     void OnButtonDown(BOOL bLeftButton, LONG x, LONG y, BOOL bDoubleClick) override
173     {
174         m_bLeftButton = bLeftButton;
175         m_bDrawing = TRUE;
176         imageModel.NotifyImageChanged();
177     }
178 
179     BOOL OnMouseMove(BOOL bLeftButton, LONG& x, LONG& y) override
180     {
181         imageModel.NotifyImageChanged();
182         return TRUE;
183     }
184 
185     BOOL OnButtonUp(BOOL bLeftButton, LONG& x, LONG& y) override
186     {
187         CRect rcPartial(g_ptStart, g_ptEnd);
188         rcPartial.NormalizeRect();
189         SIZE size = toolsModel.GetToolSize();
190         rcPartial.InflateRect((size.cx + 1) / 2, (size.cy + 1) / 2);
191         imageModel.PushImageForUndo(rcPartial);
192 
193         OnDrawOverlayOnImage(m_hdc);
194         m_bDrawing = FALSE;
195         imageModel.NotifyImageChanged();
196         return TRUE;
197     }
198 
199     void OnEndDraw(BOOL bCancel) override
200     {
201         m_bDrawing = FALSE;
202         ToolBase::OnEndDraw(bCancel);
203     }
204 
205     void OnSpecialTweak(BOOL bMinus) override
206     {
207         toolsModel.MakeLineThickerOrThinner(bMinus);
208     }
209 };
210 
211 typedef enum DIRECTION
212 {
213     NO_DIRECTION = -1,
214     DIRECTION_HORIZONTAL,
215     DIRECTION_VERTICAL,
216     DIRECTION_DIAGONAL_RIGHT_DOWN,
217     DIRECTION_DIAGONAL_RIGHT_UP,
218 } DIRECTION;
219 
220 #define THRESHOULD_DEG 15
221 
222 static DIRECTION
223 GetDirection(LONG x0, LONG y0, LONG x1, LONG y1)
224 {
225     LONG dx = x1 - x0, dy = y1 - y0;
226 
227     if (labs(dx) <= 8 && labs(dy) <= 8)
228         return NO_DIRECTION;
229 
230     double radian = atan2((double)dy, (double)dx);
231     if (radian < DEG2RAD(-180 + THRESHOULD_DEG))
232     {
233         ATLTRACE("DIRECTION_HORIZONTAL: %ld\n", RAD2DEG(radian));
234         return DIRECTION_HORIZONTAL;
235     }
236     if (radian < DEG2RAD(-90 - THRESHOULD_DEG))
237     {
238         ATLTRACE("DIRECTION_DIAGONAL_RIGHT_DOWN: %ld\n", RAD2DEG(radian));
239         return DIRECTION_DIAGONAL_RIGHT_DOWN;
240     }
241     if (radian < DEG2RAD(-90 + THRESHOULD_DEG))
242     {
243         ATLTRACE("DIRECTION_VERTICAL: %ld\n", RAD2DEG(radian));
244         return DIRECTION_VERTICAL;
245     }
246     if (radian < DEG2RAD(-THRESHOULD_DEG))
247     {
248         ATLTRACE("DIRECTION_DIAGONAL_RIGHT_UP: %ld\n", RAD2DEG(radian));
249         return DIRECTION_DIAGONAL_RIGHT_UP;
250     }
251     if (radian < DEG2RAD(+THRESHOULD_DEG))
252     {
253         ATLTRACE("DIRECTION_HORIZONTAL: %ld\n", RAD2DEG(radian));
254         return DIRECTION_HORIZONTAL;
255     }
256     if (radian < DEG2RAD(+90 - THRESHOULD_DEG))
257     {
258         ATLTRACE("DIRECTION_DIAGONAL_RIGHT_DOWN: %ld\n", RAD2DEG(radian));
259         return DIRECTION_DIAGONAL_RIGHT_DOWN;
260     }
261     if (radian < DEG2RAD(+90 + THRESHOULD_DEG))
262     {
263         ATLTRACE("DIRECTION_VERTICAL: %ld\n", RAD2DEG(radian));
264         return DIRECTION_VERTICAL;
265     }
266     if (radian < DEG2RAD(+180 - THRESHOULD_DEG))
267     {
268         ATLTRACE("DIRECTION_DIAGONAL_RIGHT_UP: %ld\n", RAD2DEG(radian));
269         return DIRECTION_DIAGONAL_RIGHT_UP;
270     }
271     ATLTRACE("DIRECTION_HORIZONTAL: %ld\n", RAD2DEG(radian));
272     return DIRECTION_HORIZONTAL;
273 }
274 
275 static void
276 RestrictDrawDirection(DIRECTION dir, LONG x0, LONG y0, LONG& x1, LONG& y1)
277 {
278     switch (dir)
279     {
280         case NO_DIRECTION:
281         default:
282             return;
283 
284         case DIRECTION_HORIZONTAL:
285             y1 = y0;
286             break;
287 
288         case DIRECTION_VERTICAL:
289             x1 = x0;
290             break;
291 
292         case DIRECTION_DIAGONAL_RIGHT_DOWN:
293             y1 = y0 + (x1 - x0);
294             break;
295 
296         case DIRECTION_DIAGONAL_RIGHT_UP:
297             x1 = x0 - (y1 - y0);
298             break;
299     }
300 }
301 
302 struct SmoothDrawTool : ToolBase
303 {
304     DIRECTION m_direction = NO_DIRECTION;
305     BOOL m_bShiftDown = FALSE;
306     BOOL m_bLeftButton = FALSE;
307 
308     virtual void OnDraw(HDC hdc, BOOL bLeftButton, POINT pt0, POINT pt1) = 0;
309 
310     void OnButtonDown(BOOL bLeftButton, LONG x, LONG y, BOOL bDoubleClick) override
311     {
312         m_direction = NO_DIRECTION;
313         m_bShiftDown = (::GetKeyState(VK_SHIFT) & 0x8000); // Is Shift key pressed?
314         m_bLeftButton = bLeftButton;
315         s_cPoints = 0;
316         pushToPoints(x, y);
317         pushToPoints(x, y); // We have to draw the first point
318         imageModel.NotifyImageChanged();
319     }
320 
321     BOOL OnMouseMove(BOOL bLeftButton, LONG& x, LONG& y) override
322     {
323         if (!m_bShiftDown)
324         {
325             pushToPoints(x, y);
326             imageModel.NotifyImageChanged();
327             return TRUE;
328         }
329 
330         if (m_direction == NO_DIRECTION)
331         {
332             m_direction = GetDirection(g_ptStart.x, g_ptStart.y, x, y);
333             if (m_direction == NO_DIRECTION)
334                 return FALSE;
335         }
336 
337         RestrictDrawDirection(m_direction, g_ptStart.x, g_ptStart.y, x, y);
338         pushToPoints(x, y);
339         imageModel.NotifyImageChanged();
340         return TRUE;
341     }
342 
343     BOOL OnButtonUp(BOOL bLeftButton, LONG& x, LONG& y) override
344     {
345         if (m_bShiftDown && m_direction != NO_DIRECTION)
346             RestrictDrawDirection(m_direction, g_ptStart.x, g_ptStart.y, x, y);
347 
348         pushToPoints(x, y);
349 
350         CRect rcPartial;
351         getBoundaryOfPoints(rcPartial, s_cPoints, s_pPoints);
352 
353         SIZE size = toolsModel.GetToolSize();
354         rcPartial.InflateRect((size.cx + 1) / 2, (size.cy + 1) / 2);
355 
356         imageModel.PushImageForUndo(rcPartial);
357 
358         OnDrawOverlayOnImage(m_hdc);
359         imageModel.NotifyImageChanged();
360         OnEndDraw(FALSE);
361         return TRUE;
362     }
363 
364     void OnDrawOverlayOnImage(HDC hdc) override
365     {
366         for (SIZE_T i = 1; i < s_cPoints; ++i)
367         {
368             OnDraw(hdc, m_bLeftButton, s_pPoints[i - 1], s_pPoints[i]);
369         }
370     }
371 };
372 
373 struct SelectionBaseTool : ToolBase
374 {
375     BOOL m_bLeftButton = FALSE;
376     BOOL m_bCtrlKey = FALSE;
377     BOOL m_bShiftKey = FALSE;
378     BOOL m_bDrawing = FALSE;
379     BOOL m_bNoDrawBack = FALSE;
380     HITTEST m_hitSelection = HIT_NONE;
381 
382     BOOL isRectSelect() const
383     {
384         return (toolsModel.GetActiveTool() == TOOL_RECTSEL);
385     }
386 
387     void OnDrawOverlayOnImage(HDC hdc) override
388     {
389         if (selectionModel.IsLanded() || !selectionModel.m_bShow)
390             return;
391 
392         if (!m_bNoDrawBack)
393             selectionModel.DrawBackground(hdc, selectionModel.m_rgbBack);
394 
395         selectionModel.DrawSelection(hdc, paletteModel.GetBgColor(), toolsModel.IsBackgroundTransparent());
396     }
397 
398     void OnDrawOverlayOnCanvas(HDC hdc) override
399     {
400         if (m_bDrawing || selectionModel.m_bShow)
401             selectionModel.drawFrameOnCanvas(hdc);
402     }
403 
404     void OnButtonDown(BOOL bLeftButton, LONG x, LONG y, BOOL bDoubleClick) override
405     {
406         m_bLeftButton = bLeftButton;
407         m_bCtrlKey = (::GetKeyState(VK_CONTROL) < 0);
408         m_bShiftKey = (::GetKeyState(VK_SHIFT) < 0);
409         m_bDrawing = FALSE;
410         m_hitSelection = HIT_NONE;
411 
412         POINT pt = { x, y };
413         if (!m_bLeftButton) // Show context menu on Right-click
414         {
415             canvasWindow.ImageToCanvas(pt);
416             canvasWindow.ClientToScreen(&pt);
417             mainWindow.TrackPopupMenu(pt, 0);
418             return;
419         }
420 
421         POINT ptCanvas = pt;
422         canvasWindow.ImageToCanvas(ptCanvas);
423         HITTEST hit = selectionModel.hitTest(ptCanvas);
424         if (hit != HIT_NONE) // Dragging of selection started?
425         {
426             if (m_bCtrlKey || m_bShiftKey)
427             {
428                 imageModel.PushImageForUndo();
429                 toolsModel.OnDrawOverlayOnImage(imageModel.GetDC());
430             }
431             m_hitSelection = hit;
432             selectionModel.m_ptHit = pt;
433             selectionModel.TakeOff();
434             m_bNoDrawBack |= (m_bCtrlKey || m_bShiftKey);
435             imageModel.NotifyImageChanged();
436             return;
437         }
438 
439         selectionModel.Landing();
440         m_bDrawing = TRUE;
441 
442         imageModel.Clamp(pt);
443         if (isRectSelect())
444         {
445             selectionModel.SetRectFromPoints(g_ptStart, pt);
446         }
447         else
448         {
449             s_cPoints = 0;
450             pushToPoints(pt.x, pt.y);
451         }
452 
453         imageModel.NotifyImageChanged();
454     }
455 
456     BOOL OnMouseMove(BOOL bLeftButton, LONG& x, LONG& y) override
457     {
458         POINT pt = { x, y };
459 
460         if (!m_bLeftButton)
461             return TRUE;
462 
463         if (m_hitSelection != HIT_NONE) // Now dragging selection?
464         {
465             if (m_bShiftKey)
466                 toolsModel.OnDrawOverlayOnImage(imageModel.GetDC());
467 
468             selectionModel.Dragging(m_hitSelection, pt);
469             imageModel.NotifyImageChanged();
470             return TRUE;
471         }
472 
473         if (isRectSelect() && ::GetKeyState(VK_SHIFT) < 0)
474             regularize(g_ptStart.x, g_ptStart.y, pt.x, pt.y);
475 
476         imageModel.Clamp(pt);
477 
478         if (isRectSelect())
479             selectionModel.SetRectFromPoints(g_ptStart, pt);
480         else
481             pushToPoints(pt.x, pt.y);
482 
483         imageModel.NotifyImageChanged();
484         return TRUE;
485     }
486 
487     BOOL OnButtonUp(BOOL bLeftButton, LONG& x, LONG& y) override
488     {
489         POINT pt = { x, y };
490         m_bDrawing = FALSE;
491 
492         if (!m_bLeftButton)
493             return TRUE;
494 
495         if (m_hitSelection != HIT_NONE) // Dragging of selection ended?
496         {
497             if (m_bShiftKey)
498                 toolsModel.OnDrawOverlayOnImage(imageModel.GetDC());
499 
500             selectionModel.Dragging(m_hitSelection, pt);
501             m_hitSelection = HIT_NONE;
502             imageModel.NotifyImageChanged();
503             return TRUE;
504         }
505 
506         if (isRectSelect() && ::GetKeyState(VK_SHIFT) < 0)
507             regularize(g_ptStart.x, g_ptStart.y, pt.x, pt.y);
508 
509         imageModel.Clamp(pt);
510 
511         if (isRectSelect())
512         {
513             selectionModel.SetRectFromPoints(g_ptStart, pt);
514             selectionModel.m_bShow = !selectionModel.m_rc.IsRectEmpty();
515         }
516         else
517         {
518             if (s_cPoints > 2)
519             {
520                 BuildMaskFromPoints();
521                 selectionModel.m_bShow = TRUE;
522             }
523             else
524             {
525                 s_cPoints = 0;
526                 selectionModel.m_bShow = FALSE;
527             }
528         }
529 
530         m_bNoDrawBack = FALSE;
531         imageModel.NotifyImageChanged();
532         return TRUE;
533     }
534 
535     void OnEndDraw(BOOL bCancel) override
536     {
537         if (bCancel)
538             selectionModel.HideSelection();
539         else
540             selectionModel.Landing();
541 
542         m_bDrawing = FALSE;
543         m_hitSelection = HIT_NONE;
544         ToolBase::OnEndDraw(bCancel);
545     }
546 
547     void OnSpecialTweak(BOOL bMinus) override
548     {
549         selectionModel.StretchSelection(bMinus);
550     }
551 };
552 
553 // TOOL_FREESEL
554 struct FreeSelTool : SelectionBaseTool
555 {
556     void OnDrawOverlayOnImage(HDC hdc) override
557     {
558         SelectionBaseTool::OnDrawOverlayOnImage(hdc);
559 
560         if (!selectionModel.m_bShow && m_bDrawing)
561         {
562             /* Draw the freehand selection inverted/xored */
563             Poly(hdc, s_pPoints, (INT)s_cPoints, 0, 0, 2, 0, FALSE, TRUE);
564         }
565     }
566 };
567 
568 // TOOL_RECTSEL
569 struct RectSelTool : SelectionBaseTool
570 {
571     void OnDrawOverlayOnImage(HDC hdc) override
572     {
573         SelectionBaseTool::OnDrawOverlayOnImage(hdc);
574 
575         if (!selectionModel.m_bShow && m_bDrawing)
576         {
577             CRect& rc = selectionModel.m_rc;
578             if (!rc.IsRectEmpty())
579                 RectSel(hdc, rc.left, rc.top, rc.right, rc.bottom);
580         }
581     }
582 };
583 
584 // TOOL_RUBBER
585 struct RubberTool : SmoothDrawTool
586 {
587     void OnDraw(HDC hdc, BOOL bLeftButton, POINT pt0, POINT pt1) override
588     {
589         if (bLeftButton)
590             Erase(hdc, pt0.x, pt0.y, pt1.x, pt1.y, m_bg, toolsModel.GetRubberRadius());
591         else
592             Replace(hdc, pt0.x, pt0.y, pt1.x, pt1.y, m_fg, m_bg, toolsModel.GetRubberRadius());
593     }
594 
595     void OnSpecialTweak(BOOL bMinus) override
596     {
597         toolsModel.MakeRubberThickerOrThinner(bMinus);
598     }
599 };
600 
601 // TOOL_FILL
602 struct FillTool : ToolBase
603 {
604     void OnButtonDown(BOOL bLeftButton, LONG x, LONG y, BOOL bDoubleClick) override
605     {
606         imageModel.PushImageForUndo();
607         Fill(m_hdc, x, y, bLeftButton ? m_fg : m_bg);
608     }
609 };
610 
611 // TOOL_COLOR
612 struct ColorTool : ToolBase
613 {
614     void fetchColor(BOOL bLeftButton, LONG x, LONG y)
615     {
616         COLORREF rgbColor;
617 
618         if (0 <= x && x < imageModel.GetWidth() && 0 <= y && y < imageModel.GetHeight())
619             rgbColor = GetPixel(m_hdc, x, y);
620         else
621             rgbColor = RGB(255, 255, 255); // Outside is white
622 
623         if (bLeftButton)
624             paletteModel.SetFgColor(rgbColor);
625         else
626             paletteModel.SetBgColor(rgbColor);
627     }
628 
629     BOOL OnMouseMove(BOOL bLeftButton, LONG& x, LONG& y) override
630     {
631         fetchColor(bLeftButton, x, y);
632         return TRUE;
633     }
634 
635     BOOL OnButtonUp(BOOL bLeftButton, LONG& x, LONG& y) override
636     {
637         fetchColor(bLeftButton, x, y);
638         toolsModel.SetActiveTool(toolsModel.GetOldActiveTool());
639         return TRUE;
640     }
641 };
642 
643 // TOOL_ZOOM
644 struct ZoomTool : ToolBase
645 {
646     BOOL m_bZoomed = FALSE;
647 
648     BOOL getNewZoomRect(CRect& rcView, INT newZoom);
649 
650     void OnDrawOverlayOnCanvas(HDC hdc) override
651     {
652         CRect rcView;
653         INT oldZoom = toolsModel.GetZoom();
654         if (oldZoom < MAX_ZOOM && getNewZoomRect(rcView, oldZoom * 2))
655             DrawXorRect(hdc, &rcView);
656     }
657 
658     void OnButtonDown(BOOL bLeftButton, LONG x, LONG y, BOOL bDoubleClick) override
659     {
660         INT newZoom, oldZoom = toolsModel.GetZoom();
661         if (bLeftButton)
662             newZoom = (oldZoom < MAX_ZOOM) ? (oldZoom * 2) : MIN_ZOOM;
663         else
664             newZoom = (oldZoom > MIN_ZOOM) ? (oldZoom / 2) : MAX_ZOOM;
665 
666         m_bZoomed = FALSE;
667 
668         if (oldZoom != newZoom)
669         {
670             CRect rcView;
671             if (getNewZoomRect(rcView, newZoom))
672             {
673                 canvasWindow.zoomTo(newZoom, rcView.left, rcView.top);
674                 m_bZoomed = TRUE;
675             }
676         }
677     }
678 
679     BOOL OnButtonUp(BOOL bLeftButton, LONG& x, LONG& y) override
680     {
681         if (m_bZoomed)
682             toolsModel.SetActiveTool(toolsModel.GetOldActiveTool());
683 
684         return TRUE;
685     }
686 };
687 
688 BOOL ZoomTool::getNewZoomRect(CRect& rcView, INT newZoom)
689 {
690     CPoint pt;
691     ::GetCursorPos(&pt);
692     canvasWindow.ScreenToClient(&pt);
693 
694     canvasWindow.getNewZoomRect(rcView, newZoom, pt);
695 
696     CRect rc;
697     canvasWindow.GetImageRect(rc);
698     canvasWindow.ImageToCanvas(rc);
699 
700     return rc.PtInRect(pt);
701 }
702 
703 // TOOL_PEN
704 struct PenTool : SmoothDrawTool
705 {
706     void OnDraw(HDC hdc, BOOL bLeftButton, POINT pt0, POINT pt1) override
707     {
708         COLORREF rgb = bLeftButton ? m_fg : m_bg;
709         Line(hdc, pt0.x, pt0.y, pt1.x, pt1.y, rgb, toolsModel.GetPenWidth());
710     }
711 
712     void OnSpecialTweak(BOOL bMinus) override
713     {
714         toolsModel.MakePenThickerOrThinner(bMinus);
715     }
716 };
717 
718 // TOOL_BRUSH
719 struct BrushTool : SmoothDrawTool
720 {
721     void OnDraw(HDC hdc, BOOL bLeftButton, POINT pt0, POINT pt1) override
722     {
723         COLORREF rgb = bLeftButton ? m_fg : m_bg;
724         Brush(hdc, pt0.x, pt0.y, pt1.x, pt1.y, rgb, toolsModel.GetBrushStyle(),
725               toolsModel.GetBrushWidth());
726     }
727 
728     void OnSpecialTweak(BOOL bMinus) override
729     {
730         toolsModel.MakeBrushThickerOrThinner(bMinus);
731     }
732 };
733 
734 // TOOL_AIRBRUSH
735 struct AirBrushTool : SmoothDrawTool
736 {
737     DWORD m_dwTick = 0;
738 
739     void OnButtonDown(BOOL bLeftButton, LONG x, LONG y, BOOL bDoubleClick) override
740     {
741         m_dwTick = GetTickCount();
742         SmoothDrawTool::OnButtonDown(bLeftButton, x, y, bDoubleClick);
743     }
744 
745     void OnDrawOverlayOnImage(HDC hdc) override
746     {
747         srand(m_dwTick);
748         SmoothDrawTool::OnDrawOverlayOnImage(hdc);
749     }
750 
751     void OnDraw(HDC hdc, BOOL bLeftButton, POINT pt0, POINT pt1) override
752     {
753         COLORREF rgb = bLeftButton ? m_fg : m_bg;
754         Airbrush(hdc, pt1.x, pt1.y, rgb, toolsModel.GetAirBrushRadius());
755     }
756 
757     void OnSpecialTweak(BOOL bMinus) override
758     {
759         toolsModel.MakeAirBrushThickerOrThinner(bMinus);
760     }
761 };
762 
763 // TOOL_TEXT
764 struct TextTool : ToolBase
765 {
766     void OnDrawOverlayOnImage(HDC hdc) override
767     {
768         if (canvasWindow.m_drawing)
769         {
770             CRect& rc = selectionModel.m_rc;
771             if (!rc.IsRectEmpty())
772                 RectSel(hdc, rc.left, rc.top, rc.right, rc.bottom);
773         }
774     }
775 
776     void UpdatePoint(LONG x, LONG y)
777     {
778         POINT pt = { x, y };
779         imageModel.Clamp(pt);
780         selectionModel.SetRectFromPoints(g_ptStart, pt);
781         imageModel.NotifyImageChanged();
782     }
783 
784     void OnButtonDown(BOOL bLeftButton, LONG x, LONG y, BOOL bDoubleClick) override
785     {
786         if (!textEditWindow.IsWindow())
787             textEditWindow.Create(canvasWindow);
788 
789         UpdatePoint(x, y);
790     }
791 
792     BOOL OnMouseMove(BOOL bLeftButton, LONG& x, LONG& y) override
793     {
794         UpdatePoint(x, y);
795         return TRUE;
796     }
797 
798     void draw(HDC hdc)
799     {
800         CStringW szText;
801         textEditWindow.GetWindowText(szText);
802 
803         CRect rc;
804         textEditWindow.InvalidateEditRect();
805         textEditWindow.GetEditRect(&rc);
806         rc.InflateRect(-GRIP_SIZE / 2, -GRIP_SIZE / 2);
807 
808         // Draw the text
809         INT style = (toolsModel.IsBackgroundTransparent() ? 0 : 1);
810         Text(hdc, rc.left, rc.top, rc.right, rc.bottom, m_fg, m_bg, szText,
811              textEditWindow.GetFont(), style);
812     }
813 
814     void quit()
815     {
816         if (textEditWindow.IsWindow())
817             textEditWindow.ShowWindow(SW_HIDE);
818         selectionModel.HideSelection();
819     }
820 
821     BOOL OnButtonUp(BOOL bLeftButton, LONG& x, LONG& y) override
822     {
823         POINT pt = { x, y };
824         imageModel.Clamp(pt);
825         selectionModel.SetRectFromPoints(g_ptStart, pt);
826 
827         BOOL bTextBoxShown = ::IsWindowVisible(textEditWindow);
828         if (bTextBoxShown)
829         {
830             if (textEditWindow.GetWindowTextLength() > 0)
831             {
832                 imageModel.PushImageForUndo();
833                 draw(m_hdc);
834             }
835             if (selectionModel.m_rc.IsRectEmpty())
836             {
837                 quit();
838                 return TRUE;
839             }
840         }
841 
842         if (registrySettings.ShowTextTool)
843         {
844             if (!fontsDialog.IsWindow())
845                 fontsDialog.Create(mainWindow);
846 
847             fontsDialog.ShowWindow(SW_SHOWNOACTIVATE);
848         }
849 
850         CRect rc = selectionModel.m_rc;
851 
852         // Enlarge if tool small
853         INT cxMin = CX_MINTEXTEDIT, cyMin = CY_MINTEXTEDIT;
854         if (selectionModel.m_rc.IsRectEmpty())
855         {
856             rc.SetRect(x, y, x + cxMin, y + cyMin);
857         }
858         else
859         {
860             if (rc.right - rc.left < cxMin)
861                 rc.right = rc.left + cxMin;
862             if (rc.bottom - rc.top < cyMin)
863                 rc.bottom = rc.top + cyMin;
864         }
865 
866         if (!textEditWindow.IsWindow())
867             textEditWindow.Create(canvasWindow);
868 
869         textEditWindow.SetWindowText(NULL);
870         textEditWindow.ValidateEditRect(&rc);
871         textEditWindow.ShowWindow(SW_SHOWNOACTIVATE);
872         textEditWindow.SetFocus();
873         return TRUE;
874     }
875 
876     void OnEndDraw(BOOL bCancel) override
877     {
878         if (!bCancel)
879         {
880             if (::IsWindowVisible(textEditWindow) &&
881                 textEditWindow.GetWindowTextLength() > 0)
882             {
883                 imageModel.PushImageForUndo();
884                 draw(m_hdc);
885             }
886         }
887         quit();
888         ToolBase::OnEndDraw(bCancel);
889     }
890 };
891 
892 // TOOL_LINE
893 struct LineTool : TwoPointDrawTool
894 {
895     void OnDrawOverlayOnImage(HDC hdc) override
896     {
897         if (!m_bDrawing)
898             return;
899         if (GetAsyncKeyState(VK_SHIFT) < 0)
900             roundTo8Directions(g_ptStart.x, g_ptStart.y, g_ptEnd.x, g_ptEnd.y);
901         COLORREF rgb = m_bLeftButton ? m_fg : m_bg;
902         Line(hdc, g_ptStart.x, g_ptStart.y, g_ptEnd.x, g_ptEnd.y, rgb, toolsModel.GetLineWidth());
903     }
904 };
905 
906 // TOOL_BEZIER
907 struct BezierTool : ToolBase
908 {
909     BOOL m_bLeftButton = FALSE;
910 
911     void OnDrawOverlayOnImage(HDC hdc)
912     {
913         COLORREF rgb = (m_bLeftButton ? m_fg : m_bg);
914         switch (s_cPoints)
915         {
916             case 2:
917                 Line(hdc, s_pPoints[0].x, s_pPoints[0].y, s_pPoints[1].x, s_pPoints[1].y, rgb,
918                      toolsModel.GetLineWidth());
919                 break;
920             case 3:
921                 Bezier(hdc, s_pPoints[0], s_pPoints[2], s_pPoints[2], s_pPoints[1], rgb, toolsModel.GetLineWidth());
922                 break;
923             case 4:
924                 Bezier(hdc, s_pPoints[0], s_pPoints[2], s_pPoints[3], s_pPoints[1], rgb, toolsModel.GetLineWidth());
925                 break;
926         }
927     }
928 
929     void OnButtonDown(BOOL bLeftButton, LONG x, LONG y, BOOL bDoubleClick) override
930     {
931         m_bLeftButton = bLeftButton;
932 
933         if (s_cPoints == 0)
934         {
935             pushToPoints(x, y);
936             pushToPoints(x, y);
937         }
938         else
939         {
940             s_pPoints[s_cPoints - 1] = { x, y };
941         }
942 
943         imageModel.NotifyImageChanged();
944     }
945 
946     BOOL OnMouseMove(BOOL bLeftButton, LONG& x, LONG& y) override
947     {
948         if (s_cPoints > 0)
949             s_pPoints[s_cPoints - 1] = { x, y };
950         imageModel.NotifyImageChanged();
951         return TRUE;
952     }
953 
954     BOOL OnButtonUp(BOOL bLeftButton, LONG& x, LONG& y) override
955     {
956         if (s_cPoints >= 4)
957         {
958             OnEndDraw(FALSE);
959             return TRUE;
960         }
961         pushToPoints(x, y);
962         imageModel.NotifyImageChanged();
963         return TRUE;
964     }
965 
966     void OnEndDraw(BOOL bCancel) override
967     {
968         if (!bCancel && s_cPoints > 1)
969         {
970             // FIXME: I couldn't calculate boundary rectangle from Bezier curve
971             imageModel.PushImageForUndo();
972             OnDrawOverlayOnImage(m_hdc);
973         }
974         ToolBase::OnEndDraw(bCancel);
975     }
976 
977     void OnSpecialTweak(BOOL bMinus) override
978     {
979         toolsModel.MakeLineThickerOrThinner(bMinus);
980     }
981 };
982 
983 // TOOL_RECT
984 struct RectTool : TwoPointDrawTool
985 {
986     void OnDrawOverlayOnImage(HDC hdc) override
987     {
988         if (!m_bDrawing)
989             return;
990         if (GetAsyncKeyState(VK_SHIFT) < 0)
991             regularize(g_ptStart.x, g_ptStart.y, g_ptEnd.x, g_ptEnd.y);
992         if (m_bLeftButton)
993             Rect(hdc, g_ptStart.x, g_ptStart.y, g_ptEnd.x, g_ptEnd.y, m_fg, m_bg, toolsModel.GetLineWidth(), toolsModel.GetShapeStyle());
994         else
995             Rect(hdc, g_ptStart.x, g_ptStart.y, g_ptEnd.x, g_ptEnd.y, m_bg, m_fg, toolsModel.GetLineWidth(), toolsModel.GetShapeStyle());
996     }
997 };
998 
999 // TOOL_SHAPE
1000 struct ShapeTool : ToolBase
1001 {
1002     BOOL m_bLeftButton = FALSE;
1003     BOOL m_bClosed = FALSE;
1004 
1005     void OnDrawOverlayOnImage(HDC hdc)
1006     {
1007         if (s_cPoints <= 0)
1008             return;
1009 
1010         if (m_bLeftButton)
1011             Poly(hdc, s_pPoints, (INT)s_cPoints, m_fg, m_bg, toolsModel.GetLineWidth(), toolsModel.GetShapeStyle(), m_bClosed, FALSE);
1012         else
1013             Poly(hdc, s_pPoints, (INT)s_cPoints, m_bg, m_fg, toolsModel.GetLineWidth(), toolsModel.GetShapeStyle(), m_bClosed, FALSE);
1014     }
1015 
1016     void OnButtonDown(BOOL bLeftButton, LONG x, LONG y, BOOL bDoubleClick) override
1017     {
1018         m_bLeftButton = bLeftButton;
1019         m_bClosed = FALSE;
1020 
1021         if ((s_cPoints > 0) && (GetAsyncKeyState(VK_SHIFT) < 0))
1022             roundTo8Directions(s_pPoints[s_cPoints - 1].x, s_pPoints[s_cPoints - 1].y, x, y);
1023 
1024         pushToPoints(x, y);
1025 
1026         if (s_cPoints > 1 && bDoubleClick)
1027         {
1028             OnEndDraw(FALSE);
1029             return;
1030         }
1031 
1032         if (s_cPoints == 1)
1033             pushToPoints(x, y); // We have to draw the first point
1034 
1035         imageModel.NotifyImageChanged();
1036     }
1037 
1038     BOOL OnMouseMove(BOOL bLeftButton, LONG& x, LONG& y) override
1039     {
1040         if (s_cPoints > 1)
1041         {
1042             if (GetAsyncKeyState(VK_SHIFT) < 0)
1043                 roundTo8Directions(s_pPoints[s_cPoints - 2].x, s_pPoints[s_cPoints - 2].y, x, y);
1044 
1045             s_pPoints[s_cPoints - 1] = { x, y };
1046         }
1047 
1048         imageModel.NotifyImageChanged();
1049         return TRUE;
1050     }
1051 
1052     BOOL OnButtonUp(BOOL bLeftButton, LONG& x, LONG& y) override
1053     {
1054         if ((s_cPoints > 1) && (GetAsyncKeyState(VK_SHIFT) < 0))
1055             roundTo8Directions(s_pPoints[s_cPoints - 2].x, s_pPoints[s_cPoints - 2].y, x, y);
1056 
1057         m_bClosed = FALSE;
1058         if (nearlyEqualPoints(x, y, s_pPoints[0].x, s_pPoints[0].y))
1059         {
1060             OnEndDraw(FALSE);
1061             return TRUE;
1062         }
1063 
1064         pushToPoints(x, y);
1065         imageModel.NotifyImageChanged();
1066         return TRUE;
1067     }
1068 
1069     void OnEndDraw(BOOL bCancel) override
1070     {
1071         if (!bCancel && s_cPoints > 1)
1072         {
1073             CRect rcPartial;
1074             getBoundaryOfPoints(rcPartial, s_cPoints, s_pPoints);
1075 
1076             SIZE size = toolsModel.GetToolSize();
1077             rcPartial.InflateRect((size.cx + 1) / 2, (size.cy + 1) / 2);
1078 
1079             imageModel.PushImageForUndo(rcPartial);
1080 
1081             m_bClosed = TRUE;
1082             OnDrawOverlayOnImage(m_hdc);
1083         }
1084         m_bClosed = FALSE;
1085         ToolBase::OnEndDraw(bCancel);
1086     }
1087 
1088     void OnSpecialTweak(BOOL bMinus) override
1089     {
1090         toolsModel.MakeLineThickerOrThinner(bMinus);
1091     }
1092 };
1093 
1094 // TOOL_ELLIPSE
1095 struct EllipseTool : TwoPointDrawTool
1096 {
1097     void OnDrawOverlayOnImage(HDC hdc) override
1098     {
1099         if (!m_bDrawing)
1100             return;
1101         if (GetAsyncKeyState(VK_SHIFT) < 0)
1102             regularize(g_ptStart.x, g_ptStart.y, g_ptEnd.x, g_ptEnd.y);
1103         if (m_bLeftButton)
1104             Ellp(hdc, g_ptStart.x, g_ptStart.y, g_ptEnd.x, g_ptEnd.y, m_fg, m_bg, toolsModel.GetLineWidth(), toolsModel.GetShapeStyle());
1105         else
1106             Ellp(hdc, g_ptStart.x, g_ptStart.y, g_ptEnd.x, g_ptEnd.y, m_bg, m_fg, toolsModel.GetLineWidth(), toolsModel.GetShapeStyle());
1107     }
1108 };
1109 
1110 // TOOL_RRECT
1111 struct RRectTool : TwoPointDrawTool
1112 {
1113     void OnDrawOverlayOnImage(HDC hdc) override
1114     {
1115         if (!m_bDrawing)
1116             return;
1117         if (GetAsyncKeyState(VK_SHIFT) < 0)
1118             regularize(g_ptStart.x, g_ptStart.y, g_ptEnd.x, g_ptEnd.y);
1119         if (m_bLeftButton)
1120             RRect(hdc, g_ptStart.x, g_ptStart.y, g_ptEnd.x, g_ptEnd.y, m_fg, m_bg, toolsModel.GetLineWidth(), toolsModel.GetShapeStyle());
1121         else
1122             RRect(hdc, g_ptStart.x, g_ptStart.y, g_ptEnd.x, g_ptEnd.y, m_bg, m_fg, toolsModel.GetLineWidth(), toolsModel.GetShapeStyle());
1123     }
1124 };
1125 
1126 /*static*/ ToolBase*
1127 ToolBase::createToolObject(TOOLTYPE type)
1128 {
1129     switch (type)
1130     {
1131         case TOOL_FREESEL:  return new FreeSelTool();
1132         case TOOL_RECTSEL:  return new RectSelTool();
1133         case TOOL_RUBBER:   return new RubberTool();
1134         case TOOL_FILL:     return new FillTool();
1135         case TOOL_COLOR:    return new ColorTool();
1136         case TOOL_ZOOM:     return new ZoomTool();
1137         case TOOL_PEN:      return new PenTool();
1138         case TOOL_BRUSH:    return new BrushTool();
1139         case TOOL_AIRBRUSH: return new AirBrushTool();
1140         case TOOL_TEXT:     return new TextTool();
1141         case TOOL_LINE:     return new LineTool();
1142         case TOOL_BEZIER:   return new BezierTool();
1143         case TOOL_RECT:     return new RectTool();
1144         case TOOL_SHAPE:    return new ShapeTool();
1145         case TOOL_ELLIPSE:  return new EllipseTool();
1146         case TOOL_RRECT:    return new RRectTool();
1147     }
1148     UNREACHABLE;
1149     return NULL;
1150 }
1151 
1152 void ToolsModel::OnButtonDown(BOOL bLeftButton, LONG x, LONG y, BOOL bDoubleClick)
1153 {
1154     m_pToolObject->beginEvent();
1155     g_ptEnd = g_ptStart = { x, y };
1156     m_pToolObject->OnButtonDown(bLeftButton, x, y, bDoubleClick);
1157     m_pToolObject->endEvent();
1158 }
1159 
1160 void ToolsModel::OnMouseMove(BOOL bLeftButton, LONG x, LONG y)
1161 {
1162     m_pToolObject->beginEvent();
1163     if (m_pToolObject->OnMouseMove(bLeftButton, x, y))
1164         g_ptEnd = { x, y };
1165 
1166     m_pToolObject->endEvent();
1167 }
1168 
1169 void ToolsModel::OnButtonUp(BOOL bLeftButton, LONG x, LONG y)
1170 {
1171     m_pToolObject->beginEvent();
1172     if (m_pToolObject->OnButtonUp(bLeftButton, x, y))
1173         g_ptEnd = { x, y };
1174 
1175     m_pToolObject->endEvent();
1176 }
1177 
1178 void ToolsModel::OnEndDraw(BOOL bCancel)
1179 {
1180     ATLTRACE("ToolsModel::OnEndDraw(%d)\n", bCancel);
1181     m_pToolObject->beginEvent();
1182     m_pToolObject->OnEndDraw(bCancel);
1183     m_pToolObject->endEvent();
1184 }
1185 
1186 void ToolsModel::OnDrawOverlayOnImage(HDC hdc)
1187 {
1188     m_pToolObject->OnDrawOverlayOnImage(hdc);
1189 }
1190 
1191 void ToolsModel::OnDrawOverlayOnCanvas(HDC hdc)
1192 {
1193     m_pToolObject->OnDrawOverlayOnCanvas(hdc);
1194 }
1195 
1196 void ToolsModel::SpecialTweak(BOOL bMinus)
1197 {
1198     m_pToolObject->OnSpecialTweak(bMinus);
1199 }
1200 
1201 void ToolsModel::DrawWithMouseTool(POINT pt, WPARAM wParam)
1202 {
1203     LONG xRel = pt.x - g_ptStart.x, yRel = pt.y - g_ptStart.y;
1204 
1205     switch (m_activeTool)
1206     {
1207         // freesel, rectsel and text tools always show numbers limited to fit into image area
1208         case TOOL_FREESEL:
1209         case TOOL_RECTSEL:
1210         case TOOL_TEXT:
1211             if (xRel < 0)
1212                 xRel = (pt.x < 0) ? -g_ptStart.x : xRel;
1213             else if (pt.x > imageModel.GetWidth())
1214                 xRel = imageModel.GetWidth() - g_ptStart.x;
1215             if (yRel < 0)
1216                 yRel = (pt.y < 0) ? -g_ptStart.y : yRel;
1217             else if (pt.y > imageModel.GetHeight())
1218                 yRel = imageModel.GetHeight() - g_ptStart.y;
1219             break;
1220 
1221         // while drawing, update cursor coordinates only for tools 3, 7, 8, 9, 14
1222         case TOOL_RUBBER:
1223         case TOOL_PEN:
1224         case TOOL_BRUSH:
1225         case TOOL_AIRBRUSH:
1226         case TOOL_SHAPE:
1227         {
1228             CStringW strCoord;
1229             strCoord.Format(L"%ld, %ld", pt.x, pt.y);
1230             ::SendMessageW(g_hStatusBar, SB_SETTEXT, 1, (LPARAM)(LPCWSTR)strCoord);
1231             break;
1232         }
1233         default:
1234             break;
1235     }
1236 
1237     // rectsel and shape tools always show non-negative numbers when drawing
1238     if (m_activeTool == TOOL_RECTSEL || m_activeTool == TOOL_SHAPE)
1239     {
1240         xRel = labs(xRel);
1241         yRel = labs(yRel);
1242     }
1243 
1244     if (wParam & MK_LBUTTON)
1245     {
1246         OnMouseMove(TRUE, pt.x, pt.y);
1247         canvasWindow.Invalidate(FALSE);
1248         if ((m_activeTool >= TOOL_TEXT) || IsSelection())
1249         {
1250             CStringW strSize;
1251             if ((m_activeTool >= TOOL_LINE) && (GetAsyncKeyState(VK_SHIFT) < 0))
1252                 yRel = xRel;
1253             strSize.Format(L"%ld x %ld", xRel, yRel);
1254             ::SendMessageW(g_hStatusBar, SB_SETTEXT, 2, (LPARAM)(LPCWSTR)strSize);
1255         }
1256     }
1257 
1258     if (wParam & MK_RBUTTON)
1259     {
1260         OnMouseMove(FALSE, pt.x, pt.y);
1261         canvasWindow.Invalidate(FALSE);
1262         if (m_activeTool >= TOOL_TEXT)
1263         {
1264             CStringW strSize;
1265             if ((m_activeTool >= TOOL_LINE) && (GetAsyncKeyState(VK_SHIFT) < 0))
1266                 yRel = xRel;
1267             strSize.Format(L"%ld x %ld", xRel, yRel);
1268             ::SendMessageW(g_hStatusBar, SB_SETTEXT, 2, (LPARAM)(LPCWSTR)strSize);
1269         }
1270     }
1271 }
1272