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