/* * PROJECT: PAINT for ReactOS * LICENSE: LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later) * PURPOSE: Providing the canvas window class * COPYRIGHT: Copyright 2015 Benedikt Freisen * Copyright 2026 Katayama Hirofumi MZ */ #include "precomp.h" CCanvasWindow canvasWindow; /* FUNCTIONS ********************************************************/ HCURSOR CStyledCursor::CreateStyledCursor(BrushStyle style, INT zoom, INT radius, COLORREF color, BOOL is_rubber) { const INT diameter = 2 * radius; if (diameter * zoom / DEFAULT_ZOOM <= 2) { HCURSOR hCursor = ::LoadCursor(NULL, IDC_CROSS); return hCursor ? CopyCursor(hCursor) : NULL; } const INT crosshair1 = 6, crosshair2 = crosshair1 - 2; const INT width = diameter + 2 * crosshair1, height = diameter + 2 * crosshair1; DWORD hotX = width / 2, hotY = height / 2; HDC hdcScreen = ::GetDC(NULL); if (!hdcScreen) return NULL; HDC hdcMem = ::CreateCompatibleDC(hdcScreen); if (!hdcMem) { ::ReleaseDC(NULL, hdcScreen); return NULL; } RECT rc = { 0, 0, width, height }; // Create the AND mask bitmap. This must be monochrome (1bpp): // white bits are transparent, black bits are opaque. HBITMAP hbmMask = ::CreateBitmap(width, height, 1, 1, NULL); if (hbmMask) { HBITMAP hbmOld = (HBITMAP)::SelectObject(hdcMem, hbmMask); // Fill with white brush ::FillRect(hdcMem, &rc, (HBRUSH)::GetStockObject(WHITE_BRUSH)); if (!is_rubber) { // Draw crosshair with white pen ::SelectObject(hdcMem, (HPEN)::GetStockObject(WHITE_PEN)); ::MoveToEx(hdcMem, 0, hotY, NULL); ::LineTo(hdcMem, crosshair2, hotY); ::MoveToEx(hdcMem, width - crosshair2, hotY, NULL); ::LineTo(hdcMem, width, hotY); ::MoveToEx(hdcMem, hotX, 0, NULL); ::LineTo(hdcMem, hotX, crosshair2); ::MoveToEx(hdcMem, hotX, height - crosshair2, NULL); ::LineTo(hdcMem, hotX, height); } // Draw brush or erase with black color if (is_rubber) Erase(hdcMem, hotX, hotY, hotX, hotY, RGB(0, 0, 0), radius + 1); else Brush(hdcMem, hotX, hotY, hotX, hotY, RGB(0, 0, 0), style, diameter); if (is_rubber) InflateRect(&rc, -1, -1); ::SelectObject(hdcMem, hbmOld); } // Create the color (XOR) bitmap HBITMAP hbmColor = ::CreateCompatibleBitmap(hdcScreen, width, height); if (hbmColor) { HBITMAP hbmOld = (HBITMAP)::SelectObject(hdcMem, hbmColor); // Fill with black brush ::FillRect(hdcMem, &rc, (HBRUSH)::GetStockObject(BLACK_BRUSH)); if (is_rubber) { // Draw for rubber border INT avg = (GetRValue(color) + GetGValue(color) + GetBValue(color)) / 3; COLORREF color2 = (avg > 255 / 2) ? RGB(0, 0, 0) : RGB(255, 255, 255); Erase(hdcMem, hotX, hotY, hotX, hotY, color2, radius + 1); } else { // Draw crosshair with white pen ::SelectObject(hdcMem, (HPEN)::GetStockObject(WHITE_PEN)); ::MoveToEx(hdcMem, 0, hotY, NULL); ::LineTo(hdcMem, crosshair2, hotY); ::MoveToEx(hdcMem, width - crosshair2, hotY, NULL); ::LineTo(hdcMem, width, hotY); ::MoveToEx(hdcMem, hotX, 0, NULL); ::LineTo(hdcMem, hotX, crosshair2); ::MoveToEx(hdcMem, hotX, height - crosshair2, NULL); ::LineTo(hdcMem, hotX, height); } // Draw brush or erase with color if (is_rubber) Erase(hdcMem, hotX, hotY, hotX, hotY, color, radius); else Brush(hdcMem, hotX, hotY, hotX, hotY, color, style, diameter); ::SelectObject(hdcMem, hbmOld); } ::ReleaseDC(NULL, hdcScreen); ::DeleteDC(hdcMem); if (zoom != DEFAULT_ZOOM) { INT newWidth = width * zoom / DEFAULT_ZOOM; INT newHeight = height * zoom / DEFAULT_ZOOM; HBITMAP hbmMaskNew = CopyMonoImage(hbmMask, newWidth, newHeight, STRETCH_DELETESCANS); HBITMAP hbmColorNew = CopyDIBImage(hbmColor, newWidth, newHeight, STRETCH_DELETESCANS); ::DeleteObject(hbmMask); ::DeleteObject(hbmColor); hbmMask = hbmMaskNew; hbmColor = hbmColorNew; hotX = width * zoom / (2 * DEFAULT_ZOOM); hotY = height * zoom / (2 * DEFAULT_ZOOM); } ICONINFO ii = { FALSE, hotX, hotY, hbmMask, hbmColor }; HCURSOR hCursor = (HCURSOR)::CreateIconIndirect(&ii); ::DeleteObject(hbmMask); ::DeleteObject(hbmColor); return hCursor; } void CStyledCursor::SetStyle(BrushStyle style, INT zoom, INT radius, COLORREF color, BOOL is_rubber) { if (m_hCursor && m_style == style && m_zoom == zoom && m_radius == radius && m_color == color && m_is_rubber == is_rubber) { return; } if (m_hCursor) DestroyCursor(m_hCursor); m_hCursor = CreateStyledCursor(style, zoom, radius, color, is_rubber); m_style = style; m_zoom = zoom; m_radius = radius; m_color = color; m_is_rubber = is_rubber; } CCanvasWindow::CCanvasWindow() : m_drawing(FALSE) , m_hitCanvasSizeBox(HIT_NONE) , m_ptOrig { -1, -1 } { m_rcResizing.SetRectEmpty(); } CCanvasWindow::~CCanvasWindow() { } RECT CCanvasWindow::GetBaseRect() { CRect rcBase; GetImageRect(rcBase); ImageToCanvas(rcBase); rcBase.InflateRect(GRIP_SIZE, GRIP_SIZE); return rcBase; } VOID CCanvasWindow::ImageToCanvas(POINT& pt) { Zoomed(pt); pt.x += GRIP_SIZE - GetScrollPos(SB_HORZ); pt.y += GRIP_SIZE - GetScrollPos(SB_VERT); } VOID CCanvasWindow::ImageToCanvas(RECT& rc) { Zoomed(rc); ::OffsetRect(&rc, GRIP_SIZE - GetScrollPos(SB_HORZ), GRIP_SIZE - GetScrollPos(SB_VERT)); } VOID CCanvasWindow::CanvasToImage(POINT& pt) { pt.x -= GRIP_SIZE - GetScrollPos(SB_HORZ); pt.y -= GRIP_SIZE - GetScrollPos(SB_VERT); UnZoomed(pt); } VOID CCanvasWindow::CanvasToImage(RECT& rc) { ::OffsetRect(&rc, GetScrollPos(SB_HORZ) - GRIP_SIZE, GetScrollPos(SB_VERT) - GRIP_SIZE); UnZoomed(rc); } VOID CCanvasWindow::GetImageRect(RECT& rc) { rc = { 0, 0, imageModel.GetWidth(), imageModel.GetHeight() }; } HITTEST CCanvasWindow::CanvasHitTest(POINT pt) { if (selectionModel.m_bShow || ::IsWindowVisible(textEditWindow)) return HIT_INNER; RECT rcBase = GetBaseRect(); return getSizeBoxHitTest(pt, &rcBase); } VOID CCanvasWindow::getNewZoomRect(CRect& rcView, INT newZoom, CPoint ptTarget) { CRect rcImage; GetImageRect(rcImage); ImageToCanvas(rcImage); // Calculate the zoom rectangle INT oldZoom = toolsModel.GetZoom(); GetClientRect(rcView); LONG cxView = rcView.right * oldZoom / newZoom, cyView = rcView.bottom * oldZoom / newZoom; rcView.SetRect(ptTarget.x - cxView / 2, ptTarget.y - cyView / 2, ptTarget.x + cxView / 2, ptTarget.y + cyView / 2); // Shift the rectangle if necessary INT dx = 0, dy = 0; if (rcView.left < rcImage.left) dx = rcImage.left - rcView.left; else if (rcImage.right < rcView.right) dx = rcImage.right - rcView.right; if (rcView.top < rcImage.top) dy = rcImage.top - rcView.top; else if (rcImage.bottom < rcView.bottom) dy = rcImage.bottom - rcView.bottom; rcView.OffsetRect(dx, dy); rcView.IntersectRect(&rcView, &rcImage); } VOID CCanvasWindow::zoomTo(INT newZoom, LONG left, LONG top) { POINT pt = { left, top }; CanvasToImage(pt); toolsModel.SetZoom(newZoom); ImageToCanvas(pt); pt.x += GetScrollPos(SB_HORZ); pt.y += GetScrollPos(SB_VERT); updateScrollRange(); updateScrollPos(pt.x, pt.y); Invalidate(TRUE); } BOOL CCanvasWindow::DoDraw(HDC hDC, RECT& rcClient, RECT& rcPaint) { // This is the target area we have to draw on CRect rcCanvasDraw; rcCanvasDraw.IntersectRect(&rcClient, &rcPaint); // Calculate image size CRect rcImage; GetImageRect(rcImage); SIZE sizeImage = { imageModel.GetWidth(), imageModel.GetHeight() }; // We use a memory bitmap to reduce flickering HBITMAP hbmCache1 = CreateDIBWithProperties(rcClient.right, rcClient.bottom); if (!hbmCache1) return FALSE; // Out of memory HBITMAP hbmCache2 = CreateDIBWithProperties(sizeImage.cx, sizeImage.cy); if (!hbmCache2) { ::DeleteObject(hbmCache1); return FALSE; // Out of memory } HDC hdcMem0 = ::CreateCompatibleDC(hDC); HGDIOBJ hbm0Old = ::SelectObject(hdcMem0, hbmCache1); // Fill the background on hdcMem0 ::FillRect(hdcMem0, &rcCanvasDraw, (HBRUSH)(COLOR_APPWORKSPACE + 1)); // Draw the sizeboxes if necessary RECT rcBase = GetBaseRect(); if (!selectionModel.m_bShow && !::IsWindowVisible(textEditWindow)) drawSizeBoxes(hdcMem0, &rcBase, FALSE, &rcCanvasDraw); // Calculate the target area on the image CRect rcImageDraw = rcCanvasDraw; CanvasToImage(rcImageDraw); rcImageDraw.IntersectRect(&rcImageDraw, &rcImage); // Consider rounding down by zooming rcImageDraw.right += 1; rcImageDraw.bottom += 1; // hdcMem1 <-- imageModel HDC hdcMem1 = ::CreateCompatibleDC(hDC); HGDIOBJ hbm1Old = ::SelectObject(hdcMem1, hbmCache2); ::BitBlt(hdcMem1, rcImageDraw.left, rcImageDraw.top, rcImageDraw.Width(), rcImageDraw.Height(), imageModel.GetDC(), rcImageDraw.left, rcImageDraw.top, SRCCOPY); // Draw overlay #1 on hdcMem1 toolsModel.OnDrawOverlayOnImage(hdcMem1); // Transfer the bits with stretch (hdcMem0 <-- hdcMem1) ImageToCanvas(rcImage); ::StretchBlt(hdcMem0, rcImage.left, rcImage.top, rcImage.Width(), rcImage.Height(), hdcMem1, 0, 0, sizeImage.cx, sizeImage.cy, SRCCOPY); // Clean up hdcMem1 ::SelectObject(hdcMem1, hbm1Old); ::DeleteDC(hdcMem1); // Draw the grid on hdcMem0 if (g_showGrid && toolsModel.GetZoom() >= 4000) { HPEN oldPen = (HPEN) ::SelectObject(hdcMem0, ::CreatePen(PS_SOLID, 1, RGB(160, 160, 160))); for (INT counter = 0; counter < sizeImage.cy; counter++) { POINT pt0 = { 0, counter }, pt1 = { sizeImage.cx, counter }; ImageToCanvas(pt0); ImageToCanvas(pt1); ::MoveToEx(hdcMem0, pt0.x, pt0.y, NULL); ::LineTo(hdcMem0, pt1.x, pt1.y); } for (INT counter = 0; counter < sizeImage.cx; counter++) { POINT pt0 = { counter, 0 }, pt1 = { counter, sizeImage.cy }; ImageToCanvas(pt0); ImageToCanvas(pt1); ::MoveToEx(hdcMem0, pt0.x, pt0.y, NULL); ::LineTo(hdcMem0, pt1.x, pt1.y); } ::DeleteObject(::SelectObject(hdcMem0, oldPen)); } // Draw overlay #2 on hdcMem0 toolsModel.OnDrawOverlayOnCanvas(hdcMem0); // Draw new frame on hdcMem0 if any if (m_hitCanvasSizeBox != HIT_NONE && !m_rcResizing.IsRectEmpty()) DrawXorRect(hdcMem0, &m_rcResizing); // Transfer the bits (hDC <-- hdcMem0) ::BitBlt(hDC, rcCanvasDraw.left, rcCanvasDraw.top, rcCanvasDraw.Width(), rcCanvasDraw.Height(), hdcMem0, rcCanvasDraw.left, rcCanvasDraw.top, SRCCOPY); // Clean up hdcMem0 ::SelectObject(hdcMem0, hbm0Old); ::DeleteDC(hdcMem0); ::DeleteObject(hbmCache2); ::DeleteObject(hbmCache1); return TRUE; } VOID CCanvasWindow::updateScrollRange() { CRect rcClient; GetClientRect(&rcClient); CSize sizePage(rcClient.right, rcClient.bottom); CSize sizeZoomed = { Zoomed(imageModel.GetWidth()), Zoomed(imageModel.GetHeight()) }; CSize sizeWhole = { sizeZoomed.cx + (GRIP_SIZE * 2), sizeZoomed.cy + (GRIP_SIZE * 2) }; // show/hide the scrollbars ShowScrollBar(SB_HORZ, sizePage.cx < sizeWhole.cx); ShowScrollBar(SB_VERT, sizePage.cy < sizeWhole.cy); if (sizePage.cx < sizeWhole.cx || sizePage.cy < sizeWhole.cy) { GetClientRect(&rcClient); // Scrollbars might change, get client rectangle again sizePage = CSize(rcClient.right, rcClient.bottom); } SCROLLINFO si = { sizeof(si), SIF_PAGE | SIF_RANGE }; si.nMin = 0; si.nMax = sizeWhole.cx; si.nPage = sizePage.cx; SetScrollInfo(SB_HORZ, &si); si.nMax = sizeWhole.cy; si.nPage = sizePage.cy; SetScrollInfo(SB_VERT, &si); } VOID CCanvasWindow::updateScrollPos(INT x, INT y) { SetScrollPos(SB_HORZ, x); SetScrollPos(SB_VERT, y); } LRESULT CCanvasWindow::OnSize(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { if (m_hWnd) updateScrollRange(); return 0; } VOID CCanvasWindow::OnHVScroll(WPARAM wParam, INT fnBar) { SCROLLINFO si; si.cbSize = sizeof(si); si.fMask = SIF_ALL; GetScrollInfo(fnBar, &si); switch (LOWORD(wParam)) { case SB_THUMBTRACK: case SB_THUMBPOSITION: si.nPos = (SHORT)HIWORD(wParam); break; case SB_LINELEFT: // SB_LINEUP si.nPos -= 15; break; case SB_LINERIGHT: // SB_LINEDOWN si.nPos += 15; break; case SB_PAGELEFT: // SB_PAGEUP si.nPos -= si.nPage; break; case SB_PAGERIGHT: // SB_PAGEDOWN si.nPos += si.nPage; break; } si.nPos = max(min(si.nPos, si.nMax), si.nMin); SetScrollInfo(fnBar, &si); Invalidate(); } LRESULT CCanvasWindow::OnHScroll(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { OnHVScroll(wParam, SB_HORZ); return 0; } LRESULT CCanvasWindow::OnVScroll(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { OnHVScroll(wParam, SB_VERT); return 0; } LRESULT CCanvasWindow::OnButtonDown(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; m_nMouseDownMsg = nMsg; BOOL bLeftButton = (nMsg == WM_LBUTTONDOWN); if (nMsg == WM_MBUTTONDOWN) { m_ptOrig = pt; SetCapture(); ::SetCursor(::LoadCursorW(g_hinstExe, MAKEINTRESOURCEW(IDC_HANDDRAG))); return 0; } HITTEST hitSelection = selectionModel.hitTest(pt); if (hitSelection != HIT_NONE) { m_drawing = TRUE; CanvasToImage(pt); SetCapture(); toolsModel.OnButtonDown(bLeftButton, pt.x, pt.y, FALSE); Invalidate(); return 0; } HITTEST hit = CanvasHitTest(pt); if (hit == HIT_NONE || hit == HIT_BORDER) { switch (toolsModel.GetActiveTool()) { case TOOL_BEZIER: case TOOL_SHAPE: toolsModel.OnEndDraw(TRUE); Invalidate(); break; case TOOL_FREESEL: case TOOL_RECTSEL: toolsModel.OnEndDraw(FALSE); Invalidate(); break; default: break; } toolsModel.resetTool(); // resets the point-buffer of the polygon and bezier functions return 0; } CanvasToImage(pt); if (hit == HIT_INNER) { m_drawing = TRUE; SetCapture(); toolsModel.OnButtonDown(bLeftButton, pt.x, pt.y, FALSE); Invalidate(); return 0; } if (bLeftButton) { m_hitCanvasSizeBox = hit; m_ptOrig = pt; SetCapture(); } return 0; } LRESULT CCanvasWindow::OnButtonDblClk(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; CanvasToImage(pt); m_drawing = FALSE; ::ReleaseCapture(); m_nMouseDownMsg = 0; toolsModel.OnButtonDown(nMsg == WM_LBUTTONDBLCLK, pt.x, pt.y, TRUE); toolsModel.resetTool(); Invalidate(); return 0; } LRESULT CCanvasWindow::OnMouseMove(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; if (m_nMouseDownMsg == WM_MBUTTONDOWN) { INT x = GetScrollPos(SB_HORZ) - (pt.x - m_ptOrig.x); INT y = GetScrollPos(SB_VERT) - (pt.y - m_ptOrig.y); SendMessage(WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, x), 0); SendMessage(WM_VSCROLL, MAKEWPARAM(SB_THUMBPOSITION, y), 0); m_ptOrig = pt; return 0; } CanvasToImage(pt); if (toolsModel.GetActiveTool() == TOOL_ZOOM) Invalidate(); if (!m_drawing || toolsModel.GetActiveTool() <= TOOL_AIRBRUSH) { TRACKMOUSEEVENT tme = { sizeof(tme) }; tme.dwFlags = TME_LEAVE; tme.hwndTrack = m_hWnd; tme.dwHoverTime = 0; ::TrackMouseEvent(&tme); if (!m_drawing) { CRect rcImage; GetImageRect(rcImage); CStringW strCoord; if (rcImage.PtInRect(pt)) strCoord.Format(L"%ld, %ld", pt.x, pt.y); ::SendMessageW(g_hStatusBar, SB_SETTEXT, 1, (LPARAM)(LPCWSTR)strCoord); } } if (m_drawing || toolsModel.IsSelection()) { toolsModel.DrawWithMouseTool(pt, wParam); return 0; } if (m_hitCanvasSizeBox == HIT_NONE || ::GetCapture() != m_hWnd) return 0; // Dragging now... Calculate the new size INT cxImage = imageModel.GetWidth(), cyImage = imageModel.GetHeight(); INT cxDelta = pt.x - m_ptOrig.x; INT cyDelta = pt.y - m_ptOrig.y; switch (m_hitCanvasSizeBox) { case HIT_UPPER_LEFT: cxImage -= cxDelta; cyImage -= cyDelta; break; case HIT_UPPER_CENTER: cyImage -= cyDelta; break; case HIT_UPPER_RIGHT: cxImage += cxDelta; cyImage -= cyDelta; break; case HIT_MIDDLE_LEFT: cxImage -= cxDelta; break; case HIT_MIDDLE_RIGHT: cxImage += cxDelta; break; case HIT_LOWER_LEFT: cxImage -= cxDelta; cyImage += cyDelta; break; case HIT_LOWER_CENTER: cyImage += cyDelta; break; case HIT_LOWER_RIGHT: cxImage += cxDelta; cyImage += cyDelta; break; default: return 0; } // Limit bitmap size cxImage = max(1, cxImage); cyImage = max(1, cyImage); cxImage = min(MAXWORD, cxImage); cyImage = min(MAXWORD, cyImage); // Display new size CStringW strSize; strSize.Format(L"%d x %d", cxImage, cyImage); ::SendMessageW(g_hStatusBar, SB_SETTEXT, 2, (LPARAM)(LPCWSTR)strSize); // Dragging now... Fix the position... CRect rcResizing = { 0, 0, cxImage, cyImage }; switch (m_hitCanvasSizeBox) { case HIT_UPPER_LEFT: rcResizing.OffsetRect(cxDelta, cyDelta); break; case HIT_UPPER_CENTER: rcResizing.OffsetRect(0, cyDelta); break; case HIT_UPPER_RIGHT: rcResizing.OffsetRect(0, cyDelta); break; case HIT_MIDDLE_LEFT: rcResizing.OffsetRect(cxDelta, 0); break; case HIT_LOWER_LEFT: rcResizing.OffsetRect(cxDelta, 0); break; default: break; } ImageToCanvas(rcResizing); m_rcResizing = rcResizing; Invalidate(TRUE); return 0; } LRESULT CCanvasWindow::OnButtonUp(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; CanvasToImage(pt); ::ReleaseCapture(); BOOL bLeftButton = (m_nMouseDownMsg == WM_LBUTTONDOWN); m_nMouseDownMsg = 0; if (m_drawing) { m_drawing = FALSE; toolsModel.OnButtonUp(bLeftButton, pt.x, pt.y); Invalidate(FALSE); ::SendMessageW(g_hStatusBar, SB_SETTEXT, 2, (LPARAM)L""); return 0; } if (m_hitCanvasSizeBox == HIT_NONE || !bLeftButton) return 0; // Resize the image INT cxImage = imageModel.GetWidth(), cyImage = imageModel.GetHeight(); INT cxDelta = pt.x - m_ptOrig.x; INT cyDelta = pt.y - m_ptOrig.y; switch (m_hitCanvasSizeBox) { case HIT_UPPER_LEFT: imageModel.Crop(cxImage - cxDelta, cyImage - cyDelta, cxDelta, cyDelta); break; case HIT_UPPER_CENTER: imageModel.Crop(cxImage, cyImage - cyDelta, 0, cyDelta); break; case HIT_UPPER_RIGHT: imageModel.Crop(cxImage + cxDelta, cyImage - cyDelta, 0, cyDelta); break; case HIT_MIDDLE_LEFT: imageModel.Crop(cxImage - cxDelta, cyImage, cxDelta, 0); break; case HIT_MIDDLE_RIGHT: imageModel.Crop(cxImage + cxDelta, cyImage, 0, 0); break; case HIT_LOWER_LEFT: imageModel.Crop(cxImage - cxDelta, cyImage + cyDelta, cxDelta, 0); break; case HIT_LOWER_CENTER: imageModel.Crop(cxImage, cyImage + cyDelta, 0, 0); break; case HIT_LOWER_RIGHT: imageModel.Crop(cxImage + cxDelta, cyImage + cyDelta, 0, 0); break; default: break; } m_rcResizing.SetRectEmpty(); g_imageSaved = FALSE; m_hitCanvasSizeBox = HIT_NONE; toolsModel.resetTool(); // resets the point-buffer of the polygon and bezier functions updateScrollRange(); Invalidate(TRUE); return 0; } LRESULT CCanvasWindow::OnSetCursor(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { if (CWaitCursor::IsWaiting()) { bHandled = FALSE; return 0; } if (m_nMouseDownMsg == WM_MBUTTONDOWN) { ::SetCursor(::LoadCursorW(g_hinstExe, MAKEINTRESOURCEW(IDC_HANDDRAG))); return 0; } POINT pt; ::GetCursorPos(&pt); ScreenToClient(&pt); CRect rcClient; GetClientRect(&rcClient); if (!rcClient.PtInRect(pt)) { bHandled = FALSE; return 0; } HITTEST hitSelection = selectionModel.hitTest(pt); if (hitSelection != HIT_NONE) { if (!setCursorOnSizeBox(hitSelection)) ::SetCursor(::LoadCursorW(NULL, (LPCWSTR)IDC_SIZEALL)); return 0; } CRect rcImage; GetImageRect(rcImage); ImageToCanvas(rcImage); if (rcImage.PtInRect(pt)) { switch (toolsModel.GetActiveTool()) { case TOOL_FILL: ::SetCursor(::LoadCursorW(g_hinstExe, MAKEINTRESOURCEW(IDC_FILL))); break; case TOOL_COLOR: ::SetCursor(::LoadCursorW(g_hinstExe, MAKEINTRESOURCEW(IDC_COLOR))); break; case TOOL_ZOOM: ::SetCursor(::LoadCursorW(g_hinstExe, MAKEINTRESOURCEW(IDC_ZOOM))); break; case TOOL_PEN: ::SetCursor(::LoadCursorW(g_hinstExe, MAKEINTRESOURCEW(IDC_PEN))); break; case TOOL_AIRBRUSH: ::SetCursor(::LoadCursorW(g_hinstExe, MAKEINTRESOURCEW(IDC_AIRBRUSH))); break; case TOOL_RUBBER: { m_hRubberCursor.SetStyle(BrushStyleSquare, toolsModel.GetZoom(), toolsModel.GetRubberRadius(), paletteModel.GetBgColor(), TRUE); m_hRubberCursor.SetCursor(); break; } case TOOL_BRUSH: { m_hBrushCursor.SetStyle(toolsModel.GetBrushStyle(), toolsModel.GetZoom(), toolsModel.GetBrushWidth() / 2, paletteModel.GetFgColor(), FALSE); m_hBrushCursor.SetCursor(); break; } default: ::SetCursor(::LoadCursorW(NULL, (LPCWSTR)IDC_CROSS)); } return 0; } if (selectionModel.m_bShow || !setCursorOnSizeBox(CanvasHitTest(pt))) bHandled = FALSE; return 0; } LRESULT CCanvasWindow::OnKeyDown(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { if (wParam == VK_ESCAPE) { OnEndDraw(TRUE); ::ReleaseCapture(); m_nMouseDownMsg = 0; m_hitCanvasSizeBox = HIT_NONE; m_rcResizing.SetRectEmpty(); Invalidate(TRUE); } return 0; } LRESULT CCanvasWindow::OnCancelMode(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { // Cancel dragging m_hitCanvasSizeBox = HIT_NONE; m_rcResizing.SetRectEmpty(); Invalidate(TRUE); return 0; } LRESULT CCanvasWindow::OnMouseWheel(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { return ::SendMessageW(GetParent(), nMsg, wParam, lParam); } LRESULT CCanvasWindow::OnCaptureChanged(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { ::SendMessageW(g_hStatusBar, SB_SETTEXT, 2, (LPARAM)L""); return 0; } LRESULT CCanvasWindow::OnEraseBkgnd(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { return TRUE; // do nothing => transparent background } LRESULT CCanvasWindow::OnPaint(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { RECT rcClient; GetClientRect(&rcClient); static BOOL s_bShowedOutOfMemory = FALSE; // Don't show "Out Of Memory" message multiple time PAINTSTRUCT ps; HDC hDC = BeginPaint(&ps); if (DoDraw(hDC, rcClient, ps.rcPaint)) { s_bShowedOutOfMemory = FALSE; } else if (!s_bShowedOutOfMemory) { ShowOutOfMemory(); s_bShowedOutOfMemory = TRUE; imageModel.ClearHistory(); // Reduce memory usage } EndPaint(&ps); return 0; } VOID CCanvasWindow::OnEndDraw(BOOL bCancel) { m_drawing = FALSE; toolsModel.OnEndDraw(bCancel); Invalidate(FALSE); } LRESULT CCanvasWindow::OnCtlColorEdit(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { SetTextColor((HDC)wParam, paletteModel.GetFgColor()); SetBkMode((HDC)wParam, TRANSPARENT); return (LRESULT)GetStockObject(NULL_BRUSH); } LRESULT CCanvasWindow::OnPaletteModelColorChanged(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { imageModel.NotifyImageChanged(); return 0; }