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