1 /*
2  * PROJECT:    PAINT for ReactOS
3  * LICENSE:    LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later)
4  * PURPOSE:    Providing the canvas window class
5  * COPYRIGHT:  Copyright 2015 Benedikt Freisen <b.freisen@gmx.net>
6  */
7 
8 #include "precomp.h"
9 
10 CCanvasWindow canvasWindow;
11 
12 /* FUNCTIONS ********************************************************/
13 
14 CCanvasWindow::CCanvasWindow()
15     : m_drawing(FALSE)
16     , m_hitSelection(HIT_NONE)
17     , m_hitCanvasSizeBox(HIT_NONE)
18     , m_ptOrig { -1, -1 }
19 {
20     m_ahbmCached[0] = m_ahbmCached[1] = NULL;
21     m_rcResizing.SetRectEmpty();
22 }
23 
24 CCanvasWindow::~CCanvasWindow()
25 {
26     if (m_ahbmCached[0])
27         ::DeleteObject(m_ahbmCached[0]);
28     if (m_ahbmCached[1])
29         ::DeleteObject(m_ahbmCached[1]);
30 }
31 
32 RECT CCanvasWindow::GetBaseRect()
33 {
34     CRect rcBase;
35     GetImageRect(rcBase);
36     ImageToCanvas(rcBase);
37     rcBase.InflateRect(GRIP_SIZE, GRIP_SIZE);
38     return rcBase;
39 }
40 
41 VOID CCanvasWindow::ImageToCanvas(POINT& pt)
42 {
43     Zoomed(pt);
44     pt.x += GRIP_SIZE - GetScrollPos(SB_HORZ);
45     pt.y += GRIP_SIZE - GetScrollPos(SB_VERT);
46 }
47 
48 VOID CCanvasWindow::ImageToCanvas(RECT& rc)
49 {
50     Zoomed(rc);
51     ::OffsetRect(&rc, GRIP_SIZE - GetScrollPos(SB_HORZ), GRIP_SIZE - GetScrollPos(SB_VERT));
52 }
53 
54 VOID CCanvasWindow::CanvasToImage(POINT& pt)
55 {
56     pt.x -= GRIP_SIZE - GetScrollPos(SB_HORZ);
57     pt.y -= GRIP_SIZE - GetScrollPos(SB_VERT);
58     UnZoomed(pt);
59 }
60 
61 VOID CCanvasWindow::CanvasToImage(RECT& rc)
62 {
63     ::OffsetRect(&rc, GetScrollPos(SB_HORZ) - GRIP_SIZE, GetScrollPos(SB_VERT) - GRIP_SIZE);
64     UnZoomed(rc);
65 }
66 
67 VOID CCanvasWindow::GetImageRect(RECT& rc)
68 {
69     rc = { 0, 0, imageModel.GetWidth(), imageModel.GetHeight() };
70 }
71 
72 HITTEST CCanvasWindow::CanvasHitTest(POINT pt)
73 {
74     if (selectionModel.m_bShow || ::IsWindowVisible(textEditWindow))
75         return HIT_INNER;
76     RECT rcBase = GetBaseRect();
77     return getSizeBoxHitTest(pt, &rcBase);
78 }
79 
80 VOID CCanvasWindow::getNewZoomRect(CRect& rcView, INT newZoom, CPoint ptTarget)
81 {
82     CRect rcImage;
83     GetImageRect(rcImage);
84     ImageToCanvas(rcImage);
85 
86     // Calculate the zoom rectangle
87     INT oldZoom = toolsModel.GetZoom();
88     GetClientRect(rcView);
89     LONG cxView = rcView.right * oldZoom / newZoom, cyView = rcView.bottom * oldZoom / newZoom;
90     rcView.SetRect(ptTarget.x - cxView / 2, ptTarget.y - cyView / 2,
91                    ptTarget.x + cxView / 2, ptTarget.y + cyView / 2);
92 
93     // Shift the rectangle if necessary
94     INT dx = 0, dy = 0;
95     if (rcView.left < rcImage.left)
96         dx = rcImage.left - rcView.left;
97     else if (rcImage.right < rcView.right)
98         dx = rcImage.right - rcView.right;
99     if (rcView.top < rcImage.top)
100         dy = rcImage.top - rcView.top;
101     else if (rcImage.bottom < rcView.bottom)
102         dy = rcImage.bottom - rcView.bottom;
103     rcView.OffsetRect(dx, dy);
104 
105     rcView.IntersectRect(&rcView, &rcImage);
106 }
107 
108 VOID CCanvasWindow::zoomTo(INT newZoom, LONG left, LONG top)
109 {
110     POINT pt = { left, top };
111     CanvasToImage(pt);
112 
113     toolsModel.SetZoom(newZoom);
114     ImageToCanvas(pt);
115     pt.x += GetScrollPos(SB_HORZ);
116     pt.y += GetScrollPos(SB_VERT);
117 
118     updateScrollRange();
119     updateScrollPos(pt.x, pt.y);
120     Invalidate(TRUE);
121 }
122 
123 VOID CCanvasWindow::DoDraw(HDC hDC, RECT& rcClient, RECT& rcPaint)
124 {
125     // This is the target area we have to draw on
126     CRect rcCanvasDraw;
127     rcCanvasDraw.IntersectRect(&rcClient, &rcPaint);
128 
129     // We use a memory bitmap to reduce flickering
130     HDC hdcMem0 = ::CreateCompatibleDC(hDC);
131     m_ahbmCached[0] = CachedBufferDIB(m_ahbmCached[0], rcClient.right, rcClient.bottom);
132     HGDIOBJ hbm0Old = ::SelectObject(hdcMem0, m_ahbmCached[0]);
133 
134     // Fill the background on hdcMem0
135     ::FillRect(hdcMem0, &rcCanvasDraw, (HBRUSH)(COLOR_APPWORKSPACE + 1));
136 
137     // Draw the sizeboxes if necessary
138     RECT rcBase = GetBaseRect();
139     if (!selectionModel.m_bShow && !::IsWindowVisible(textEditWindow))
140         drawSizeBoxes(hdcMem0, &rcBase, FALSE, &rcCanvasDraw);
141 
142     // Calculate image size
143     CRect rcImage;
144     GetImageRect(rcImage);
145     SIZE sizeImage = { imageModel.GetWidth(), imageModel.GetHeight() };
146 
147     // Calculate the target area on the image
148     CRect rcImageDraw = rcCanvasDraw;
149     CanvasToImage(rcImageDraw);
150     rcImageDraw.IntersectRect(&rcImageDraw, &rcImage);
151 
152     // Consider rounding down by zooming
153     rcImageDraw.right += 1;
154     rcImageDraw.bottom += 1;
155 
156     // hdcMem1 <-- imageModel
157     HDC hdcMem1 = ::CreateCompatibleDC(hDC);
158     m_ahbmCached[1] = CachedBufferDIB(m_ahbmCached[1], sizeImage.cx, sizeImage.cy);
159     HGDIOBJ hbm1Old = ::SelectObject(hdcMem1, m_ahbmCached[1]);
160     ::BitBlt(hdcMem1, rcImageDraw.left, rcImageDraw.top, rcImageDraw.Width(), rcImageDraw.Height(),
161              imageModel.GetDC(), rcImageDraw.left, rcImageDraw.top, SRCCOPY);
162 
163     // Draw overlay #1 on hdcMem1
164     toolsModel.OnDrawOverlayOnImage(hdcMem1);
165 
166     // Transfer the bits with stretch (hdcMem0 <-- hdcMem1)
167     ImageToCanvas(rcImage);
168     ::StretchBlt(hdcMem0, rcImage.left, rcImage.top, rcImage.Width(), rcImage.Height(),
169                  hdcMem1, 0, 0, sizeImage.cx, sizeImage.cy, SRCCOPY);
170 
171     // Clean up hdcMem1
172     ::SelectObject(hdcMem1, hbm1Old);
173     ::DeleteDC(hdcMem1);
174 
175     // Draw the grid on hdcMem0
176     if (g_showGrid && toolsModel.GetZoom() >= 4000)
177     {
178         HPEN oldPen = (HPEN) ::SelectObject(hdcMem0, ::CreatePen(PS_SOLID, 1, RGB(160, 160, 160)));
179         for (INT counter = 0; counter < sizeImage.cy; counter++)
180         {
181             POINT pt0 = { 0, counter }, pt1 = { sizeImage.cx, counter };
182             ImageToCanvas(pt0);
183             ImageToCanvas(pt1);
184             ::MoveToEx(hdcMem0, pt0.x, pt0.y, NULL);
185             ::LineTo(hdcMem0, pt1.x, pt1.y);
186         }
187         for (INT counter = 0; counter < sizeImage.cx; counter++)
188         {
189             POINT pt0 = { counter, 0 }, pt1 = { counter, sizeImage.cy };
190             ImageToCanvas(pt0);
191             ImageToCanvas(pt1);
192             ::MoveToEx(hdcMem0, pt0.x, pt0.y, NULL);
193             ::LineTo(hdcMem0, pt1.x, pt1.y);
194         }
195         ::DeleteObject(::SelectObject(hdcMem0, oldPen));
196     }
197 
198     // Draw overlay #2 on hdcMem0
199     toolsModel.OnDrawOverlayOnCanvas(hdcMem0);
200 
201     // Draw new frame on hdcMem0 if any
202     if (m_hitCanvasSizeBox != HIT_NONE && !m_rcResizing.IsRectEmpty())
203         DrawXorRect(hdcMem0, &m_rcResizing);
204 
205     // Transfer the bits (hDC <-- hdcMem0)
206     ::BitBlt(hDC, rcCanvasDraw.left, rcCanvasDraw.top, rcCanvasDraw.Width(), rcCanvasDraw.Height(),
207              hdcMem0, rcCanvasDraw.left, rcCanvasDraw.top, SRCCOPY);
208 
209     // Clean up hdcMem0
210     ::SelectObject(hdcMem0, hbm0Old);
211     ::DeleteDC(hdcMem0);
212 }
213 
214 VOID CCanvasWindow::updateScrollRange()
215 {
216     CRect rcClient;
217     GetClientRect(&rcClient);
218 
219     CSize sizePage(rcClient.right, rcClient.bottom);
220     CSize sizeZoomed = { Zoomed(imageModel.GetWidth()), Zoomed(imageModel.GetHeight()) };
221     CSize sizeWhole = { sizeZoomed.cx + (GRIP_SIZE * 2), sizeZoomed.cy + (GRIP_SIZE * 2) };
222 
223     // show/hide the scrollbars
224     ShowScrollBar(SB_HORZ, sizePage.cx < sizeWhole.cx);
225     ShowScrollBar(SB_VERT, sizePage.cy < sizeWhole.cy);
226 
227     if (sizePage.cx < sizeWhole.cx || sizePage.cy < sizeWhole.cy)
228     {
229         GetClientRect(&rcClient); // Scrollbars might change, get client rectangle again
230         sizePage = CSize(rcClient.right, rcClient.bottom);
231     }
232 
233     SCROLLINFO si = { sizeof(si), SIF_PAGE | SIF_RANGE };
234     si.nMin   = 0;
235 
236     si.nMax   = sizeWhole.cx;
237     si.nPage  = sizePage.cx;
238     SetScrollInfo(SB_HORZ, &si);
239 
240     si.nMax   = sizeWhole.cy;
241     si.nPage  = sizePage.cy;
242     SetScrollInfo(SB_VERT, &si);
243 }
244 
245 VOID CCanvasWindow::updateScrollPos(INT x, INT y)
246 {
247     SetScrollPos(SB_HORZ, x);
248     SetScrollPos(SB_VERT, y);
249 }
250 
251 LRESULT CCanvasWindow::OnSize(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
252 {
253     if (m_hWnd)
254         updateScrollRange();
255 
256     return 0;
257 }
258 
259 VOID CCanvasWindow::OnHVScroll(WPARAM wParam, INT fnBar)
260 {
261     SCROLLINFO si;
262     si.cbSize = sizeof(SCROLLINFO);
263     si.fMask = SIF_ALL;
264     GetScrollInfo(fnBar, &si);
265     switch (LOWORD(wParam))
266     {
267         case SB_THUMBTRACK:
268         case SB_THUMBPOSITION:
269             si.nPos = (SHORT)HIWORD(wParam);
270             break;
271         case SB_LINELEFT:
272             si.nPos -= 5;
273             break;
274         case SB_LINERIGHT:
275             si.nPos += 5;
276             break;
277         case SB_PAGELEFT:
278             si.nPos -= si.nPage;
279             break;
280         case SB_PAGERIGHT:
281             si.nPos += si.nPage;
282             break;
283     }
284     si.nPos = max(min(si.nPos, si.nMax), si.nMin);
285     SetScrollInfo(fnBar, &si);
286     Invalidate();
287 }
288 
289 LRESULT CCanvasWindow::OnHScroll(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
290 {
291     OnHVScroll(wParam, SB_HORZ);
292     return 0;
293 }
294 
295 LRESULT CCanvasWindow::OnVScroll(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
296 {
297     OnHVScroll(wParam, SB_VERT);
298     return 0;
299 }
300 
301 LRESULT CCanvasWindow::OnButtonDown(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
302 {
303     POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
304 
305     m_nMouseDownMsg = nMsg;
306     BOOL bLeftButton = (nMsg == WM_LBUTTONDOWN);
307 
308     if (nMsg == WM_MBUTTONDOWN)
309     {
310         m_ptOrig = pt;
311         SetCapture();
312         ::SetCursor(::LoadCursorW(g_hinstExe, MAKEINTRESOURCEW(IDC_HANDDRAG)));
313         return 0;
314     }
315 
316     HITTEST hitSelection = selectionModel.hitTest(pt);
317     if (hitSelection != HIT_NONE)
318     {
319         selectionModel.m_nSelectionBrush = 0; // Selection Brush is OFF
320         if (bLeftButton)
321         {
322             CanvasToImage(pt);
323             if (::GetKeyState(VK_CONTROL) < 0) // Ctrl+Click is Selection Clone
324             {
325                 imageModel.SelectionClone();
326             }
327             else if (::GetKeyState(VK_SHIFT) < 0) // Shift+Dragging is Selection Brush
328             {
329                 selectionModel.m_nSelectionBrush = 1; // Selection Brush is ON
330             }
331             StartSelectionDrag(hitSelection, pt);
332         }
333         else
334         {
335             ClientToScreen(&pt);
336             mainWindow.TrackPopupMenu(pt, 0);
337         }
338         return 0;
339     }
340 
341     HITTEST hit = CanvasHitTest(pt);
342     if (hit == HIT_NONE || hit == HIT_BORDER)
343     {
344         switch (toolsModel.GetActiveTool())
345         {
346             case TOOL_BEZIER:
347             case TOOL_SHAPE:
348                 toolsModel.OnEndDraw(TRUE);
349                 Invalidate();
350                 break;
351 
352             case TOOL_FREESEL:
353             case TOOL_RECTSEL:
354                 toolsModel.OnEndDraw(FALSE);
355                 Invalidate();
356                 break;
357 
358             default:
359                 break;
360         }
361 
362         toolsModel.resetTool(); // resets the point-buffer of the polygon and bezier functions
363         return 0;
364     }
365 
366     CanvasToImage(pt);
367 
368     if (hit == HIT_INNER)
369     {
370         m_drawing = TRUE;
371         SetCapture();
372         toolsModel.OnButtonDown(bLeftButton, pt.x, pt.y, FALSE);
373         Invalidate(FALSE);
374         return 0;
375     }
376 
377     if (bLeftButton)
378     {
379         m_hitCanvasSizeBox = hit;
380         m_ptOrig = pt;
381         SetCapture();
382     }
383 
384     return 0;
385 }
386 
387 LRESULT CCanvasWindow::OnButtonDblClk(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
388 {
389     POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
390     CanvasToImage(pt);
391 
392     m_drawing = FALSE;
393     ::ReleaseCapture();
394     m_nMouseDownMsg = 0;
395 
396     toolsModel.OnButtonDown(nMsg == WM_LBUTTONDBLCLK, pt.x, pt.y, TRUE);
397     toolsModel.resetTool();
398     Invalidate(FALSE);
399     return 0;
400 }
401 
402 LRESULT CCanvasWindow::OnMouseMove(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
403 {
404     POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
405 
406     if (m_nMouseDownMsg == WM_MBUTTONDOWN)
407     {
408         INT x = GetScrollPos(SB_HORZ) - (pt.x - m_ptOrig.x);
409         INT y = GetScrollPos(SB_VERT) - (pt.y - m_ptOrig.y);
410         SendMessage(WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, x), 0);
411         SendMessage(WM_VSCROLL, MAKEWPARAM(SB_THUMBPOSITION, y), 0);
412         m_ptOrig = pt;
413         return 0;
414     }
415 
416     CanvasToImage(pt);
417 
418     if (toolsModel.GetActiveTool() == TOOL_ZOOM)
419         Invalidate();
420 
421     if (m_hitSelection != HIT_NONE)
422     {
423         SelectionDragging(pt);
424         return 0;
425     }
426 
427     if (!m_drawing || toolsModel.GetActiveTool() <= TOOL_AIRBRUSH)
428     {
429         TRACKMOUSEEVENT tme = { sizeof(tme) };
430         tme.dwFlags = TME_LEAVE;
431         tme.hwndTrack = m_hWnd;
432         tme.dwHoverTime = 0;
433         ::TrackMouseEvent(&tme);
434 
435         if (!m_drawing)
436         {
437             CRect rcImage;
438             GetImageRect(rcImage);
439 
440             CStringW strCoord;
441             if (rcImage.PtInRect(pt))
442                 strCoord.Format(L"%ld, %ld", pt.x, pt.y);
443             ::SendMessageW(g_hStatusBar, SB_SETTEXT, 1, (LPARAM)(LPCWSTR)strCoord);
444         }
445     }
446 
447     if (m_drawing)
448     {
449         toolsModel.DrawWithMouseTool(pt, wParam);
450         return 0;
451     }
452 
453     if (m_hitCanvasSizeBox == HIT_NONE || ::GetCapture() != m_hWnd)
454         return 0;
455 
456     // Dragging now... Calculate the new size
457     INT cxImage = imageModel.GetWidth(), cyImage = imageModel.GetHeight();
458     INT cxDelta = pt.x - m_ptOrig.x;
459     INT cyDelta = pt.y - m_ptOrig.y;
460     switch (m_hitCanvasSizeBox)
461     {
462         case HIT_UPPER_LEFT:
463             cxImage -= cxDelta;
464             cyImage -= cyDelta;
465             break;
466         case HIT_UPPER_CENTER:
467             cyImage -= cyDelta;
468             break;
469         case HIT_UPPER_RIGHT:
470             cxImage += cxDelta;
471             cyImage -= cyDelta;
472             break;
473         case HIT_MIDDLE_LEFT:
474             cxImage -= cxDelta;
475             break;
476         case HIT_MIDDLE_RIGHT:
477             cxImage += cxDelta;
478             break;
479         case HIT_LOWER_LEFT:
480             cxImage -= cxDelta;
481             cyImage += cyDelta;
482             break;
483         case HIT_LOWER_CENTER:
484             cyImage += cyDelta;
485             break;
486         case HIT_LOWER_RIGHT:
487             cxImage += cxDelta;
488             cyImage += cyDelta;
489             break;
490         default:
491             return 0;
492     }
493 
494     // Limit bitmap size
495     cxImage = max(1, cxImage);
496     cyImage = max(1, cyImage);
497     cxImage = min(MAXWORD, cxImage);
498     cyImage = min(MAXWORD, cyImage);
499 
500     // Display new size
501     CStringW strSize;
502     strSize.Format(L"%d x %d", cxImage, cyImage);
503     ::SendMessageW(g_hStatusBar, SB_SETTEXT, 2, (LPARAM)(LPCWSTR)strSize);
504 
505     // Dragging now... Fix the position...
506     CRect rcResizing = { 0, 0, cxImage, cyImage };
507     switch (m_hitCanvasSizeBox)
508     {
509         case HIT_UPPER_LEFT:
510             rcResizing.OffsetRect(cxDelta, cyDelta);
511             break;
512         case HIT_UPPER_CENTER:
513             rcResizing.OffsetRect(0, cyDelta);
514             break;
515         case HIT_UPPER_RIGHT:
516             rcResizing.OffsetRect(0, cyDelta);
517             break;
518         case HIT_MIDDLE_LEFT:
519             rcResizing.OffsetRect(cxDelta, 0);
520             break;
521         case HIT_LOWER_LEFT:
522             rcResizing.OffsetRect(cxDelta, 0);
523             break;
524         default:
525             break;
526     }
527     ImageToCanvas(rcResizing);
528     m_rcResizing = rcResizing;
529     Invalidate(TRUE);
530 
531     return 0;
532 }
533 
534 LRESULT CCanvasWindow::OnButtonUp(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
535 {
536     POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
537     CanvasToImage(pt);
538 
539     ::ReleaseCapture();
540 
541     BOOL bLeftButton = (m_nMouseDownMsg == WM_LBUTTONDOWN);
542     m_nMouseDownMsg = 0;
543 
544     if (m_drawing)
545     {
546         m_drawing = FALSE;
547         toolsModel.OnButtonUp(bLeftButton, pt.x, pt.y);
548         Invalidate(FALSE);
549         ::SendMessageW(g_hStatusBar, SB_SETTEXT, 2, (LPARAM)L"");
550         return 0;
551     }
552     else if (m_hitSelection != HIT_NONE && bLeftButton)
553     {
554         EndSelectionDrag(pt);
555         return 0;
556     }
557 
558     if (m_hitCanvasSizeBox == HIT_NONE || !bLeftButton)
559         return 0;
560 
561     // Resize the image
562     INT cxImage = imageModel.GetWidth(), cyImage = imageModel.GetHeight();
563     INT cxDelta = pt.x - m_ptOrig.x;
564     INT cyDelta = pt.y - m_ptOrig.y;
565     switch (m_hitCanvasSizeBox)
566     {
567         case HIT_UPPER_LEFT:
568             imageModel.Crop(cxImage - cxDelta, cyImage - cyDelta, cxDelta, cyDelta);
569             break;
570         case HIT_UPPER_CENTER:
571             imageModel.Crop(cxImage, cyImage - cyDelta, 0, cyDelta);
572             break;
573         case HIT_UPPER_RIGHT:
574             imageModel.Crop(cxImage + cxDelta, cyImage - cyDelta, 0, cyDelta);
575             break;
576         case HIT_MIDDLE_LEFT:
577             imageModel.Crop(cxImage - cxDelta, cyImage, cxDelta, 0);
578             break;
579         case HIT_MIDDLE_RIGHT:
580             imageModel.Crop(cxImage + cxDelta, cyImage, 0, 0);
581             break;
582         case HIT_LOWER_LEFT:
583             imageModel.Crop(cxImage - cxDelta, cyImage + cyDelta, cxDelta, 0);
584             break;
585         case HIT_LOWER_CENTER:
586             imageModel.Crop(cxImage, cyImage + cyDelta, 0, 0);
587             break;
588         case HIT_LOWER_RIGHT:
589             imageModel.Crop(cxImage + cxDelta, cyImage + cyDelta, 0, 0);
590             break;
591         default:
592             break;
593     }
594     m_rcResizing.SetRectEmpty();
595 
596     g_imageSaved = FALSE;
597 
598     m_hitCanvasSizeBox = HIT_NONE;
599     toolsModel.resetTool(); // resets the point-buffer of the polygon and bezier functions
600     updateScrollRange();
601     Invalidate(TRUE);
602     return 0;
603 }
604 
605 LRESULT CCanvasWindow::OnSetCursor(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
606 {
607     if (CWaitCursor::IsWaiting())
608     {
609         bHandled = FALSE;
610         return 0;
611     }
612 
613     if (m_nMouseDownMsg == WM_MBUTTONDOWN)
614     {
615         ::SetCursor(::LoadCursorW(g_hinstExe, MAKEINTRESOURCEW(IDC_HANDDRAG)));
616         return 0;
617     }
618 
619     POINT pt;
620     ::GetCursorPos(&pt);
621     ScreenToClient(&pt);
622 
623     CRect rcClient;
624     GetClientRect(&rcClient);
625 
626     if (!rcClient.PtInRect(pt))
627     {
628         bHandled = FALSE;
629         return 0;
630     }
631 
632     HITTEST hitSelection = selectionModel.hitTest(pt);
633     if (hitSelection != HIT_NONE)
634     {
635         if (!setCursorOnSizeBox(hitSelection))
636             ::SetCursor(::LoadCursorW(NULL, (LPCWSTR)IDC_SIZEALL));
637         return 0;
638     }
639 
640     CRect rcImage;
641     GetImageRect(rcImage);
642     ImageToCanvas(rcImage);
643 
644     if (rcImage.PtInRect(pt))
645     {
646         switch (toolsModel.GetActiveTool())
647         {
648             case TOOL_FILL:
649                 ::SetCursor(::LoadCursorW(g_hinstExe, MAKEINTRESOURCEW(IDC_FILL)));
650                 break;
651             case TOOL_COLOR:
652                 ::SetCursor(::LoadCursorW(g_hinstExe, MAKEINTRESOURCEW(IDC_COLOR)));
653                 break;
654             case TOOL_ZOOM:
655                 ::SetCursor(::LoadCursorW(g_hinstExe, MAKEINTRESOURCEW(IDC_ZOOM)));
656                 break;
657             case TOOL_PEN:
658                 ::SetCursor(::LoadCursorW(g_hinstExe, MAKEINTRESOURCEW(IDC_PEN)));
659                 break;
660             case TOOL_AIRBRUSH:
661                 ::SetCursor(::LoadCursorW(g_hinstExe, MAKEINTRESOURCEW(IDC_AIRBRUSH)));
662                 break;
663             default:
664                 ::SetCursor(::LoadCursorW(NULL, (LPCWSTR)IDC_CROSS));
665         }
666         return 0;
667     }
668 
669     if (selectionModel.m_bShow || !setCursorOnSizeBox(CanvasHitTest(pt)))
670         bHandled = FALSE;
671 
672     return 0;
673 }
674 
675 LRESULT CCanvasWindow::OnKeyDown(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
676 {
677     if (wParam == VK_ESCAPE && ::GetCapture() == m_hWnd)
678     {
679         cancelDrawing();
680         ::ReleaseCapture();
681         m_nMouseDownMsg = 0;
682         m_hitCanvasSizeBox = HIT_NONE;
683         m_rcResizing.SetRectEmpty();
684         Invalidate(TRUE);
685     }
686 
687     return 0;
688 }
689 
690 LRESULT CCanvasWindow::OnCancelMode(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
691 {
692     // Cancel dragging
693     m_hitCanvasSizeBox = HIT_NONE;
694     m_rcResizing.SetRectEmpty();
695     Invalidate(TRUE);
696     return 0;
697 }
698 
699 LRESULT CCanvasWindow::OnMouseWheel(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
700 {
701     return ::SendMessageW(GetParent(), nMsg, wParam, lParam);
702 }
703 
704 LRESULT CCanvasWindow::OnCaptureChanged(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
705 {
706     ::SendMessageW(g_hStatusBar, SB_SETTEXT, 2, (LPARAM)L"");
707     return 0;
708 }
709 
710 LRESULT CCanvasWindow::OnEraseBkgnd(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
711 {
712     return TRUE; // do nothing => transparent background
713 }
714 
715 LRESULT CCanvasWindow::OnPaint(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
716 {
717     RECT rcClient;
718     GetClientRect(&rcClient);
719 
720     PAINTSTRUCT ps;
721     HDC hDC = BeginPaint(&ps);
722     DoDraw(hDC, rcClient, ps.rcPaint);
723     EndPaint(&ps);
724     return 0;
725 }
726 
727 VOID CCanvasWindow::cancelDrawing()
728 {
729     selectionModel.ClearColorImage();
730     selectionModel.ClearMaskImage();
731     m_hitSelection = HIT_NONE;
732     m_drawing = FALSE;
733     toolsModel.OnEndDraw(TRUE);
734     Invalidate(FALSE);
735 }
736 
737 VOID CCanvasWindow::finishDrawing()
738 {
739     toolsModel.OnEndDraw(FALSE);
740     m_drawing = FALSE;
741     Invalidate(FALSE);
742 }
743 
744 VOID CCanvasWindow::StartSelectionDrag(HITTEST hit, POINT ptImage)
745 {
746     m_hitSelection = hit;
747     selectionModel.m_ptHit = ptImage;
748     selectionModel.TakeOff();
749 
750     SetCapture();
751     Invalidate(FALSE);
752 }
753 
754 VOID CCanvasWindow::SelectionDragging(POINT ptImage)
755 {
756     if (selectionModel.m_nSelectionBrush)
757     {
758         imageModel.SelectionClone(selectionModel.m_nSelectionBrush == 1);
759         selectionModel.m_nSelectionBrush = 2; // Selection Brush is ON and drawn
760     }
761 
762     selectionModel.Dragging(m_hitSelection, ptImage);
763     Invalidate(FALSE);
764 }
765 
766 VOID CCanvasWindow::EndSelectionDrag(POINT ptImage)
767 {
768     selectionModel.Dragging(m_hitSelection, ptImage);
769     m_hitSelection = HIT_NONE;
770     Invalidate(FALSE);
771 }
772 
773 LRESULT CCanvasWindow::OnCtlColorEdit(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
774 {
775     SetTextColor((HDC)wParam, paletteModel.GetFgColor());
776     SetBkMode((HDC)wParam, TRANSPARENT);
777     return (LRESULT)GetStockObject(NULL_BRUSH);
778 }
779 
780 LRESULT CCanvasWindow::OnPaletteModelColorChanged(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
781 {
782     imageModel.NotifyImageChanged();
783     return 0;
784 }
785