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