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         selectionModel.m_nSelectionBrush = 0; // Selection Brush is OFF
272         if (bLeftButton)
273         {
274             CanvasToImage(pt);
275             if (::GetKeyState(VK_CONTROL) < 0) // Ctrl+Click is Selection Clone
276             {
277                 imageModel.SelectionClone();
278             }
279             else if (::GetKeyState(VK_SHIFT) < 0) // Shift+Dragging is Selection Brush
280             {
281                 selectionModel.m_nSelectionBrush = 1; // Selection Brush is ON
282             }
283             StartSelectionDrag(hitSelection, pt);
284         }
285         else
286         {
287             canvasWindow.ClientToScreen(&pt);
288             mainWindow.TrackPopupMenu(pt, 0);
289         }
290         return 0;
291     }
292 
293     HITTEST hit = CanvasHitTest(pt);
294     if (hit == HIT_NONE || hit == HIT_BORDER)
295     {
296         switch (toolsModel.GetActiveTool())
297         {
298             case TOOL_BEZIER:
299             case TOOL_SHAPE:
300                 toolsModel.OnCancelDraw();
301                 canvasWindow.Invalidate();
302                 break;
303 
304             case TOOL_FREESEL:
305             case TOOL_RECTSEL:
306                 toolsModel.OnFinishDraw();
307                 canvasWindow.Invalidate();
308                 break;
309 
310             default:
311                 break;
312         }
313 
314         toolsModel.resetTool(); // resets the point-buffer of the polygon and bezier functions
315         return 0;
316     }
317 
318     CanvasToImage(pt, TRUE);
319 
320     if (hit == HIT_INNER)
321     {
322         m_drawing = TRUE;
323         UnZoomed(pt);
324         SetCapture();
325         toolsModel.OnButtonDown(bLeftButton, pt.x, pt.y, FALSE);
326         Invalidate(FALSE);
327         return 0;
328     }
329 
330     if (bLeftButton)
331     {
332         m_hitCanvasSizeBox = hit;
333         UnZoomed(pt);
334         m_ptOrig = pt;
335         SetCapture();
336     }
337     return 0;
338 }
339 
340 LRESULT CCanvasWindow::OnLButtonDown(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
341 {
342     return OnLRButtonDown(TRUE, nMsg, wParam, lParam, bHandled);
343 }
344 
345 LRESULT CCanvasWindow::OnRButtonDown(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
346 {
347     return OnLRButtonDown(FALSE, nMsg, wParam, lParam, bHandled);
348 }
349 
350 LRESULT CCanvasWindow::OnLRButtonDblClk(BOOL bLeftButton, UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
351 {
352     POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
353     CanvasToImage(pt);
354 
355     m_drawing = FALSE;
356     ReleaseCapture();
357 
358     toolsModel.OnButtonDown(bLeftButton, pt.x, pt.y, TRUE);
359     toolsModel.resetTool();
360     Invalidate(FALSE);
361     return 0;
362 }
363 
364 LRESULT CCanvasWindow::OnLButtonDblClk(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
365 {
366     return OnLRButtonDblClk(TRUE, nMsg, wParam, lParam, bHandled);
367 }
368 
369 LRESULT CCanvasWindow::OnRButtonDblClk(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
370 {
371     return OnLRButtonDblClk(FALSE, nMsg, wParam, lParam, bHandled);
372 }
373 
374 LRESULT CCanvasWindow::OnMouseMove(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
375 {
376     POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
377     CanvasToImage(pt);
378 
379     if (m_hitSelection != HIT_NONE)
380     {
381         SelectionDragging(pt);
382         return 0;
383     }
384 
385     if (!m_drawing || toolsModel.GetActiveTool() <= TOOL_AIRBRUSH)
386     {
387         if (toolsModel.GetActiveTool() == TOOL_ZOOM)
388         {
389             Invalidate(FALSE);
390             UpdateWindow();
391             CanvasToImage(pt);
392             drawZoomFrame(pt.x, pt.y);
393         }
394 
395         TRACKMOUSEEVENT tme = { sizeof(tme) };
396         tme.dwFlags = TME_LEAVE;
397         tme.hwndTrack = m_hWnd;
398         tme.dwHoverTime = 0;
399         ::TrackMouseEvent(&tme);
400 
401         if (!m_drawing)
402         {
403             CString strCoord;
404             strCoord.Format(_T("%ld, %ld"), pt.x, pt.y);
405             ::SendMessage(g_hStatusBar, SB_SETTEXT, 1, (LPARAM) (LPCTSTR) strCoord);
406         }
407     }
408 
409     if (m_drawing)
410     {
411         // values displayed in statusbar
412         LONG xRel = pt.x - g_ptStart.x;
413         LONG yRel = pt.y - g_ptStart.y;
414 
415         switch (toolsModel.GetActiveTool())
416         {
417             // freesel, rectsel and text tools always show numbers limited to fit into image area
418             case TOOL_FREESEL:
419             case TOOL_RECTSEL:
420             case TOOL_TEXT:
421                 if (xRel < 0)
422                     xRel = (pt.x < 0) ? -g_ptStart.x : xRel;
423                 else if (pt.x > imageModel.GetWidth())
424                     xRel = imageModel.GetWidth() - g_ptStart.x;
425                 if (yRel < 0)
426                     yRel = (pt.y < 0) ? -g_ptStart.y : yRel;
427                 else if (pt.y > imageModel.GetHeight())
428                     yRel = imageModel.GetHeight() - g_ptStart.y;
429                 break;
430 
431             // while drawing, update cursor coordinates only for tools 3, 7, 8, 9, 14
432             case TOOL_RUBBER:
433             case TOOL_PEN:
434             case TOOL_BRUSH:
435             case TOOL_AIRBRUSH:
436             case TOOL_SHAPE:
437             {
438                 CString strCoord;
439                 strCoord.Format(_T("%ld, %ld"), pt.x, pt.y);
440                 ::SendMessage(g_hStatusBar, SB_SETTEXT, 1, (LPARAM) (LPCTSTR) strCoord);
441                 break;
442             }
443             default:
444                 break;
445         }
446 
447         // rectsel and shape tools always show non-negative numbers when drawing
448         if (toolsModel.GetActiveTool() == TOOL_RECTSEL || toolsModel.GetActiveTool() == TOOL_SHAPE)
449         {
450             if (xRel < 0)
451                 xRel = -xRel;
452             if (yRel < 0)
453                 yRel =  -yRel;
454         }
455 
456         if (wParam & MK_LBUTTON)
457         {
458             toolsModel.OnMouseMove(TRUE, pt.x, pt.y);
459             Invalidate(FALSE);
460             if ((toolsModel.GetActiveTool() >= TOOL_TEXT) || toolsModel.IsSelection())
461             {
462                 CString strSize;
463                 if ((toolsModel.GetActiveTool() >= TOOL_LINE) && (GetAsyncKeyState(VK_SHIFT) < 0))
464                     yRel = xRel;
465                 strSize.Format(_T("%ld x %ld"), xRel, yRel);
466                 ::SendMessage(g_hStatusBar, SB_SETTEXT, 2, (LPARAM) (LPCTSTR) strSize);
467             }
468         }
469 
470         if (wParam & MK_RBUTTON)
471         {
472             toolsModel.OnMouseMove(FALSE, pt.x, pt.y);
473             Invalidate(FALSE);
474             if (toolsModel.GetActiveTool() >= TOOL_TEXT)
475             {
476                 CString strSize;
477                 if ((toolsModel.GetActiveTool() >= TOOL_LINE) && (GetAsyncKeyState(VK_SHIFT) < 0))
478                     yRel = xRel;
479                 strSize.Format(_T("%ld x %ld"), xRel, yRel);
480                 ::SendMessage(g_hStatusBar, SB_SETTEXT, 2, (LPARAM) (LPCTSTR) strSize);
481             }
482         }
483         return 0;
484     }
485 
486     if (m_hitCanvasSizeBox == HIT_NONE || ::GetCapture() != m_hWnd)
487         return 0;
488 
489     // Dragging now... Calculate the new size
490     INT cxImage = imageModel.GetWidth(), cyImage = imageModel.GetHeight();
491     INT cxDelta = pt.x - m_ptOrig.x;
492     INT cyDelta = pt.y - m_ptOrig.y;
493     switch (m_hitCanvasSizeBox)
494     {
495         case HIT_UPPER_LEFT:
496             cxImage -= cxDelta;
497             cyImage -= cyDelta;
498             break;
499         case HIT_UPPER_CENTER:
500             cyImage -= cyDelta;
501             break;
502         case HIT_UPPER_RIGHT:
503             cxImage += cxDelta;
504             cyImage -= cyDelta;
505             break;
506         case HIT_MIDDLE_LEFT:
507             cxImage -= cxDelta;
508             break;
509         case HIT_MIDDLE_RIGHT:
510             cxImage += cxDelta;
511             break;
512         case HIT_LOWER_LEFT:
513             cxImage -= cxDelta;
514             cyImage += cyDelta;
515             break;
516         case HIT_LOWER_CENTER:
517             cyImage += cyDelta;
518             break;
519         case HIT_LOWER_RIGHT:
520             cxImage += cxDelta;
521             cyImage += cyDelta;
522             break;
523         default:
524             return 0;
525     }
526 
527     // Limit bitmap size
528     cxImage = max(1, cxImage);
529     cyImage = max(1, cyImage);
530     cxImage = min(MAXWORD, cxImage);
531     cyImage = min(MAXWORD, cyImage);
532 
533     // Display new size
534     CString strSize;
535     strSize.Format(_T("%d x %d"), cxImage, cyImage);
536     ::SendMessage(g_hStatusBar, SB_SETTEXT, 2, (LPARAM) (LPCTSTR) strSize);
537 
538     // Dragging now... Fix the position...
539     CRect rcResizing = { 0, 0, cxImage, cyImage };
540     switch (m_hitCanvasSizeBox)
541     {
542         case HIT_UPPER_LEFT:
543             ::OffsetRect(&rcResizing, cxDelta, cyDelta);
544             break;
545         case HIT_UPPER_CENTER:
546             ::OffsetRect(&rcResizing, 0, cyDelta);
547             break;
548         case HIT_UPPER_RIGHT:
549             ::OffsetRect(&rcResizing, 0, cyDelta);
550             break;
551         case HIT_MIDDLE_LEFT:
552             ::OffsetRect(&rcResizing, cxDelta, 0);
553             break;
554         case HIT_LOWER_LEFT:
555             ::OffsetRect(&rcResizing, cxDelta, 0);
556             break;
557         default:
558             break;
559     }
560     ImageToCanvas(rcResizing);
561     m_rcResizing = rcResizing;
562     Invalidate(TRUE);
563 
564     return 0;
565 }
566 
567 LRESULT CCanvasWindow::OnLRButtonUp(BOOL bLeftButton, UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
568 {
569     POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
570     CanvasToImage(pt);
571 
572     ::ReleaseCapture();
573 
574     if (m_drawing)
575     {
576         m_drawing = FALSE;
577         toolsModel.OnButtonUp(bLeftButton, pt.x, pt.y);
578         Invalidate(FALSE);
579         ::SendMessage(g_hStatusBar, SB_SETTEXT, 2, (LPARAM)_T(""));
580         return 0;
581     }
582     else if (m_hitSelection != HIT_NONE && bLeftButton)
583     {
584         EndSelectionDrag(pt);
585         return 0;
586     }
587 
588     if (m_hitCanvasSizeBox == HIT_NONE || !bLeftButton)
589         return 0;
590 
591     // Resize the image
592     INT cxImage = imageModel.GetWidth(), cyImage = imageModel.GetHeight();
593     INT cxDelta = pt.x - m_ptOrig.x;
594     INT cyDelta = pt.y - m_ptOrig.y;
595     switch (m_hitCanvasSizeBox)
596     {
597         case HIT_UPPER_LEFT:
598             imageModel.Crop(cxImage - cxDelta, cyImage - cyDelta, cxDelta, cyDelta);
599             break;
600         case HIT_UPPER_CENTER:
601             imageModel.Crop(cxImage, cyImage - cyDelta, 0, cyDelta);
602             break;
603         case HIT_UPPER_RIGHT:
604             imageModel.Crop(cxImage + cxDelta, cyImage - cyDelta, 0, cyDelta);
605             break;
606         case HIT_MIDDLE_LEFT:
607             imageModel.Crop(cxImage - cxDelta, cyImage, cxDelta, 0);
608             break;
609         case HIT_MIDDLE_RIGHT:
610             imageModel.Crop(cxImage + cxDelta, cyImage, 0, 0);
611             break;
612         case HIT_LOWER_LEFT:
613             imageModel.Crop(cxImage - cxDelta, cyImage + cyDelta, cxDelta, 0);
614             break;
615         case HIT_LOWER_CENTER:
616             imageModel.Crop(cxImage, cyImage + cyDelta, 0, 0);
617             break;
618         case HIT_LOWER_RIGHT:
619             imageModel.Crop(cxImage + cxDelta, cyImage + cyDelta, 0, 0);
620             break;
621         default:
622             break;
623     }
624     ::SetRectEmpty(&m_rcResizing);
625 
626     g_imageSaved = FALSE;
627 
628     m_hitCanvasSizeBox = HIT_NONE;
629     toolsModel.resetTool(); // resets the point-buffer of the polygon and bezier functions
630     Update(NULL);
631     Invalidate(TRUE);
632     return 0;
633 }
634 
635 LRESULT CCanvasWindow::OnLButtonUp(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
636 {
637     return OnLRButtonUp(TRUE, nMsg, wParam, lParam, bHandled);
638 }
639 
640 LRESULT CCanvasWindow::OnRButtonUp(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
641 {
642     return OnLRButtonUp(FALSE, nMsg, wParam, lParam, bHandled);
643 }
644 
645 LRESULT CCanvasWindow::OnSetCursor(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
646 {
647     if (CWaitCursor::IsWaiting())
648     {
649         bHandled = FALSE;
650         return 0;
651     }
652 
653     POINT pt;
654     ::GetCursorPos(&pt);
655     ScreenToClient(&pt);
656 
657     CRect rcClient;
658     GetClientRect(&rcClient);
659 
660     if (!::PtInRect(&rcClient, pt))
661     {
662         bHandled = FALSE;
663         return 0;
664     }
665 
666     HITTEST hitSelection = SelectionHitTest(pt);
667     if (hitSelection != HIT_NONE)
668     {
669         if (!setCursorOnSizeBox(hitSelection))
670             ::SetCursor(::LoadCursor(NULL, IDC_SIZEALL));
671         return 0;
672     }
673 
674     CRect rcImage;
675     GetImageRect(rcImage);
676     ImageToCanvas(rcImage);
677 
678     if (::PtInRect(&rcImage, pt))
679     {
680         switch (toolsModel.GetActiveTool())
681         {
682             case TOOL_FILL:
683                 ::SetCursor(::LoadIcon(g_hinstExe, MAKEINTRESOURCE(IDC_FILL)));
684                 break;
685             case TOOL_COLOR:
686                 ::SetCursor(::LoadIcon(g_hinstExe, MAKEINTRESOURCE(IDC_COLOR)));
687                 break;
688             case TOOL_ZOOM:
689                 ::SetCursor(::LoadIcon(g_hinstExe, MAKEINTRESOURCE(IDC_ZOOM)));
690                 break;
691             case TOOL_PEN:
692                 ::SetCursor(::LoadIcon(g_hinstExe, MAKEINTRESOURCE(IDC_PEN)));
693                 break;
694             case TOOL_AIRBRUSH:
695                 ::SetCursor(::LoadIcon(g_hinstExe, MAKEINTRESOURCE(IDC_AIRBRUSH)));
696                 break;
697             default:
698                 ::SetCursor(::LoadCursor(NULL, IDC_CROSS));
699         }
700         return 0;
701     }
702 
703     if (selectionModel.m_bShow || !setCursorOnSizeBox(CanvasHitTest(pt)))
704         bHandled = FALSE;
705 
706     return 0;
707 }
708 
709 LRESULT CCanvasWindow::OnKeyDown(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
710 {
711     if (wParam == VK_ESCAPE && ::GetCapture() == m_hWnd)
712     {
713         // Cancel dragging
714         ::ReleaseCapture();
715         m_hitCanvasSizeBox = HIT_NONE;
716         ::SetRectEmpty(&m_rcResizing);
717         Invalidate(TRUE);
718     }
719 
720     return 0;
721 }
722 
723 LRESULT CCanvasWindow::OnCancelMode(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
724 {
725     // Cancel dragging
726     m_hitCanvasSizeBox = HIT_NONE;
727     ::SetRectEmpty(&m_rcResizing);
728     Invalidate(TRUE);
729     return 0;
730 }
731 
732 LRESULT CCanvasWindow::OnMouseWheel(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
733 {
734     return ::SendMessage(GetParent(), nMsg, wParam, lParam);
735 }
736 
737 LRESULT CCanvasWindow::OnCaptureChanged(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
738 {
739     ::SendMessage(g_hStatusBar, SB_SETTEXT, 2, (LPARAM)_T(""));
740     return 0;
741 }
742 
743 LRESULT CCanvasWindow::OnEraseBkgnd(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
744 {
745     return TRUE; // do nothing => transparent background
746 }
747 
748 LRESULT CCanvasWindow::OnPaint(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
749 {
750     RECT rcClient;
751     GetClientRect(&rcClient);
752 
753     PAINTSTRUCT ps;
754     HDC hDC = BeginPaint(&ps);
755     DoDraw(hDC, rcClient, ps.rcPaint);
756     EndPaint(&ps);
757     return 0;
758 }
759 
760 VOID CCanvasWindow::cancelDrawing()
761 {
762     selectionModel.ClearColorImage();
763     selectionModel.ClearMaskImage();
764     m_hitSelection = HIT_NONE;
765     m_drawing = FALSE;
766     toolsModel.OnCancelDraw();
767     Invalidate(FALSE);
768 }
769 
770 VOID CCanvasWindow::finishDrawing()
771 {
772     toolsModel.OnFinishDraw();
773     m_drawing = FALSE;
774     Invalidate(FALSE);
775 }
776 
777 HITTEST CCanvasWindow::SelectionHitTest(POINT ptImage)
778 {
779     if (!selectionModel.m_bShow)
780         return HIT_NONE;
781 
782     RECT rcSelection = selectionModel.m_rc;
783     Zoomed(rcSelection);
784     ::OffsetRect(&rcSelection, GRIP_SIZE - GetScrollPos(SB_HORZ), GRIP_SIZE - GetScrollPos(SB_VERT));
785     ::InflateRect(&rcSelection, GRIP_SIZE, GRIP_SIZE);
786 
787     return getSizeBoxHitTest(ptImage, &rcSelection);
788 }
789 
790 VOID CCanvasWindow::StartSelectionDrag(HITTEST hit, POINT ptImage)
791 {
792     m_hitSelection = hit;
793     selectionModel.m_ptHit = ptImage;
794     selectionModel.TakeOff();
795 
796     SetCapture();
797     Invalidate(FALSE);
798 }
799 
800 VOID CCanvasWindow::SelectionDragging(POINT ptImage)
801 {
802     if (selectionModel.m_nSelectionBrush)
803     {
804         imageModel.SelectionClone(selectionModel.m_nSelectionBrush == 1);
805         selectionModel.m_nSelectionBrush = 2; // Selection Brush is ON and drawn
806     }
807 
808     selectionModel.Dragging(m_hitSelection, ptImage);
809     Invalidate(FALSE);
810 }
811 
812 VOID CCanvasWindow::EndSelectionDrag(POINT ptImage)
813 {
814     selectionModel.Dragging(m_hitSelection, ptImage);
815     m_hitSelection = HIT_NONE;
816     Invalidate(FALSE);
817 }
818 
819 VOID CCanvasWindow::MoveSelection(INT xDelta, INT yDelta)
820 {
821     if (!selectionModel.m_bShow)
822         return;
823 
824     selectionModel.TakeOff();
825     ::OffsetRect(&selectionModel.m_rc, xDelta, yDelta);
826     Invalidate(FALSE);
827 }
828 
829 LRESULT CCanvasWindow::OnCtlColorEdit(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
830 {
831     SetTextColor((HDC)wParam, paletteModel.GetFgColor());
832     SetBkMode((HDC)wParam, TRANSPARENT);
833     return (LRESULT)GetStockObject(NULL_BRUSH);
834 }
835 
836 LRESULT CCanvasWindow::OnPaletteModelColorChanged(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
837 {
838     imageModel.NotifyImageChanged();
839     return 0;
840 }
841