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