#include "precomp.h" #include #include #include "prevwnd.h" #include "guids.h" #include "resource.h" #define COLOR_PREVIEWBKGND COLOR_WINDOW #define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp)) #define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp)) ///////////////////////////////////////////////////////////////////////////// // CZoomWnd CZoomWnd::CZoomWnd(CPreviewWnd *pPreview) { m_modeDefault = MODE_NOACTION; m_fPanning = FALSE; m_fCtrlDown = FALSE; m_fShiftDown = FALSE; m_fBestFit = TRUE; m_cxImage = 1; m_cyImage = 1; m_cxCenter = 1; m_cyCenter = 1; m_pImageData = NULL; m_cyHScroll = GetSystemMetrics(SM_CYHSCROLL); m_cxVScroll = GetSystemMetrics(SM_CXVSCROLL); m_iStrID = IDS_NOPREVIEW; m_hpal = NULL; m_pPreview = pPreview; m_pFront = NULL; m_pBack = NULL; m_pTaskScheduler = NULL; m_fTimerReady = FALSE; m_fFoundBackgroundColor = FALSE; m_iIndex = -1; } CZoomWnd::~CZoomWnd() { if (m_pImageData) m_pImageData->Release(); if (m_pTaskScheduler) { // wait for any pending draw tasks since we're about to delete the buffers DWORD dwMode; m_pPreview->GetMode(&dwMode); TASKOWNERID toid; GetTaskIDFromMode(GTIDFM_DRAW, dwMode, &toid); m_pTaskScheduler->RemoveTasks(toid, ITSAT_DEFAULT_LPARAM, TRUE); m_pTaskScheduler->Release(); } if (m_pBack) { DeleteBuffer(m_pBack); m_pBack = NULL; } // DeleteBuffer is going to check for NULL anyway DeleteBuffer(m_pFront); } LRESULT CZoomWnd::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL&) { // turn off The RTL layout extended style in GWL_EXSTYLE SHSetWindowBits(m_hWnd, GWL_EXSTYLE, WS_EX_LAYOUTRTL, 0); HDC hdc = GetDC(); m_winDPIx = (float)(GetDeviceCaps(hdc,LOGPIXELSX)); m_winDPIy = (float)(GetDeviceCaps(hdc,LOGPIXELSY)); ReleaseDC(hdc); return 0; } DWORD CZoomWnd::GetBackgroundColor() { if (!m_fFoundBackgroundColor) { // First try the theme file HINSTANCE hinstTheme = SHGetShellStyleHInstance(); if (hinstTheme) { WCHAR sz[20]; if (LoadString(hinstTheme, IDS_PREVIEW_BACKGROUND_COLOR, sz, ARRAYSIZE(sz))) { int nColor; if (StrToIntEx(sz, STIF_SUPPORT_HEX, &nColor)) { m_dwBackgroundColor = (DWORD)nColor; m_fFoundBackgroundColor = TRUE; } } FreeLibrary(hinstTheme); } if (!m_fFoundBackgroundColor) { m_dwBackgroundColor = GetSysColor(COLOR_PREVIEWBKGND); m_fFoundBackgroundColor = TRUE; } } return m_dwBackgroundColor; } LRESULT CZoomWnd::OnEraseBkgnd(UINT , WPARAM wParam, LPARAM , BOOL&) { RECT rcFill; // rect to fill with background color HDC hdc = (HDC)wParam; if (!m_pPreview->OnSetColor(hdc)) SetBkColor(hdc, GetBackgroundColor()); // There are four possible regions that might need to be erased: // +-----------------------+ // | Erase Top | // +-------+-------+-------+ // | | | | // | Erase | Image | Erase | // | Left | | Right | // +-------+-------+-------+ // | Erase Bottom | // +-----------------------+ if (m_pFront && m_pFront->hdc) { RECT rcImage = m_pFront->rc; HPALETTE hPalOld = NULL; if (m_pFront->hPal) { hPalOld = SelectPalette(hdc, m_pFront->hPal, FALSE); RealizePalette(hdc); } BitBlt(hdc, rcImage.left, rcImage.top, RECTWIDTH(rcImage), RECTHEIGHT(rcImage), m_pFront->hdc, 0,0, SRCCOPY); // erase the left region rcFill.left = 0; rcFill.top = rcImage.top; rcFill.right = rcImage.left; rcFill.bottom = rcImage.bottom; if (rcFill.right > rcFill.left) { ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rcFill, NULL, 0, NULL); } // erase the right region rcFill.left = rcImage.right; rcFill.right = m_cxWindow; if (rcFill.right > rcFill.left) { ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rcFill, NULL, 0, NULL); } // erase the top region rcFill.left = 0; rcFill.top = 0; rcFill.right = m_cxWindow; rcFill.bottom = rcImage.top; if (rcFill.bottom > rcFill.top) { ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rcFill, NULL, 0, NULL); } // erase the bottom region rcFill.top = rcImage.bottom; rcFill.bottom = m_cyWindow; if (rcFill.bottom > rcFill.top) { ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rcFill, NULL, 0, NULL); } HBRUSH hbr = GetSysColorBrush(COLOR_WINDOWTEXT); FrameRect(hdc, &rcImage, hbr); if (hPalOld) { SelectPalette(hdc, hPalOld, FALSE); } } return TRUE; } void CZoomWnd::FlushDrawMessages() { // first, remove any pending draw tasks DWORD dwMode; m_pPreview->GetMode(&dwMode); TASKOWNERID toid; GetTaskIDFromMode(GTIDFM_DRAW, dwMode, &toid); m_pTaskScheduler->RemoveTasks(toid, ITSAT_DEFAULT_LPARAM, TRUE); // make sure any posted messages get flushed and we free the associated data MSG msg; while (PeekMessage(&msg, m_hWnd, ZW_DRAWCOMPLETE, ZW_DRAWCOMPLETE, PM_REMOVE)) { // NTRAID#NTBUG9-359356-2001/04/05-seank // If the queue is empty when PeekMessage is called and we have already // Posted a quit message then PeekMessage will return a WM_QUIT message // regardless of the filter min and max and subsequent calls to // GetMessage will hang indefinitely see SEANK or JASONSCH for more // info. if (msg.message == WM_QUIT) { PostQuitMessage(0); return; } Buffer * pBuf = (Buffer *)msg.wParam; DeleteBuffer(pBuf); } } HRESULT CZoomWnd::PrepareDraw() { // first, remove any pending draw tasks FlushDrawMessages(); // we are now waiting for the "next task", even if we don't create a task with this ID. HRESULT hr = S_OK; BOOL bInvalidate = FALSE; if (m_pImageData) { if (m_pImageData->_iItem == m_iIndex) { SwitchBuffers(m_iIndex); bInvalidate = TRUE; } else { COLORREF clr; if (!m_pPreview->GetColor(&clr)) clr = GetBackgroundColor(); m_iStrID = IDS_DRAWFAILED; IRunnableTask * pTask; hr = CDrawTask::Create(m_pImageData, clr, m_rcCut, m_rcBleed, m_hWnd, ZW_DRAWCOMPLETE, &pTask); if (SUCCEEDED(hr)) { DWORD dwMode; m_pPreview->GetMode(&dwMode); TASKOWNERID toid; GetTaskIDFromMode(GTIDFM_DRAW, dwMode, &toid); hr = m_pTaskScheduler->AddTask(pTask, toid, ITSAT_DEFAULT_LPARAM, ITSAT_DEFAULT_PRIORITY); if (SUCCEEDED(hr)) { m_iStrID = IDS_DRAWING; } pTask->Release(); } else { bInvalidate = TRUE; } } } else { bInvalidate = TRUE; } if (m_hWnd && bInvalidate) InvalidateRect(NULL); return hr; } HRESULT CZoomWnd::PrepareImageData(CDecodeTask *pImageData) { HRESULT hr = E_FAIL; if (pImageData) { SIZE sz; ULONG dpiX, dpiY; int cxImgPix, cyImgPix; float cxImgPhys, cyImgPhys; PTSZ ptszDest; pImageData->GetSize(&sz); pImageData->GetResolution(&dpiX, &dpiY); cxImgPhys = sz.cx/(float)dpiX; cyImgPhys = sz.cy/(float)dpiY; cxImgPix = (int)(cxImgPhys*m_winDPIx); cyImgPix = (int)(cyImgPhys*m_winDPIy); GetPTSZForBestFit(cxImgPix, cyImgPix, cxImgPhys, cyImgPhys, ptszDest); RECT rcCut, rcBleed; CalcCut(ptszDest, sz.cx, sz.cy, rcCut, rcBleed); COLORREF clr; if (!m_pPreview->GetColor(&clr)) clr = GetBackgroundColor(); IRunnableTask * pTask; hr = CDrawTask::Create(pImageData, clr, rcCut, rcBleed, m_hWnd, ZW_BACKDRAWCOMPLETE, &pTask); if (SUCCEEDED(hr)) { DWORD dwMode; m_pPreview->GetMode(&dwMode); TASKOWNERID toid; GetTaskIDFromMode(GTIDFM_DRAW, dwMode, &toid); hr = m_pTaskScheduler->AddTask(pTask, toid, ITSAT_DEFAULT_LPARAM, ITSAT_DEFAULT_PRIORITY); pTask->Release(); } } return hr; } BOOL CZoomWnd::SwitchBuffers(UINT iIndex) { BOOL fRet = FALSE; if (m_pBack && m_iIndex == iIndex) { // DeleteBuffer is going to check for NULL anyway DeleteBuffer(m_pFront); m_pFront = m_pBack; m_pBack = NULL; m_iIndex = -1; InvalidateRect(NULL); UpdateWindow(); if (m_fTimerReady) { m_pPreview->OnDrawComplete(); m_fTimerReady = FALSE; } fRet = TRUE; } return fRet; } LRESULT CZoomWnd::OnBackDrawComplete(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { Buffer * pBuf = (Buffer *)wParam; if (m_pBack) { DeleteBuffer(m_pBack); m_pBack = NULL; } if (pBuf) { m_pBack = pBuf; } m_iIndex = PtrToInt((void *)lParam); return 0; } LRESULT CZoomWnd::OnDrawComplete(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { Buffer * pBuf = (Buffer *)wParam; if (m_pFront) { DeleteBuffer(m_pFront); m_pFront = NULL; } if (pBuf) { m_pFront = pBuf; } else { m_iStrID = IDS_DRAWFAILED; } InvalidateRect(NULL); UpdateWindow(); if (m_fTimerReady) { m_pPreview->OnDrawComplete(); m_fTimerReady = FALSE; } return 0; } // OnPaint // // Handles WM_PAINT messages sent to the window LRESULT CZoomWnd::OnPaint(UINT , WPARAM , LPARAM , BOOL&) { PAINTSTRUCT ps; HDC hdcDraw = BeginPaint(&ps); // setup the destination DC: SetMapMode(hdcDraw, MM_TEXT); SetStretchBltMode(hdcDraw, COLORONCOLOR); if (m_hpal) { SelectPalette(hdcDraw, m_hpal, TRUE); RealizePalette(hdcDraw); } if (m_pFront) { if (m_Annotations.GetCount() > 0) { CPoint ptDeviceOrigin; ptDeviceOrigin.x = m_rcBleed.left - MulDiv(m_rcCut.left, RECTWIDTH(m_rcBleed), RECTWIDTH(m_rcCut)); ptDeviceOrigin.y = m_rcBleed.top - MulDiv(m_rcCut.top, RECTHEIGHT(m_rcBleed), RECTHEIGHT(m_rcCut)); SetMapMode(hdcDraw, MM_ANISOTROPIC); SetWindowOrgEx(hdcDraw, 0, 0, NULL); SetWindowExtEx(hdcDraw, RECTWIDTH(m_rcCut), RECTHEIGHT(m_rcCut), NULL); SetViewportOrgEx(hdcDraw, ptDeviceOrigin.x, ptDeviceOrigin.y, NULL); SetViewportExtEx(hdcDraw, RECTWIDTH(m_rcBleed), RECTHEIGHT(m_rcBleed), NULL); HRGN hrgn = CreateRectRgnIndirect(&m_rcBleed); if (hrgn != NULL) SelectClipRgn(hdcDraw, hrgn); m_Annotations.RenderAllMarks(hdcDraw); SelectClipRgn(hdcDraw, NULL); if (hrgn != NULL) DeleteObject(hrgn); SetMapMode(hdcDraw, MM_TEXT); SetViewportOrgEx(hdcDraw, 0, 0, NULL); SetWindowOrgEx(hdcDraw, 0, 0, NULL); } m_pPreview->OnDraw(hdcDraw); } else { TCHAR szBuf[80]; LoadString(_Module.GetModuleInstance(), m_iStrID, szBuf, ARRAYSIZE(szBuf) ); LOGFONT lf; SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, 0); HFONT hFont = CreateFontIndirect(&lf); HFONT hFontOld; if (hFont) hFontOld = (HFONT)SelectObject(hdcDraw, hFont); if (!m_pPreview->OnSetColor(hdcDraw)) { SetTextColor(hdcDraw, GetSysColor(COLOR_WINDOWTEXT)); SetBkColor(hdcDraw, GetBackgroundColor()); } RECT rc = { 0,0,m_cxWindow,m_cyWindow }; ExtTextOut(hdcDraw, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL); DrawText(hdcDraw, szBuf, -1, &rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE); if (hFont) { SelectObject(hdcDraw, hFontOld); DeleteObject(hFont); } } EndPaint(&ps); return 0; } // OnSetCursor // // Handles WM_SETCURSOR messages sent to the window. // // This function is a total HackMaster job. I have overloaded its functionality to the point // of absurdity. Here's what the parameters mean: // // uMsg == WM_SETCURSOR // wParam Standard value sent during a WM_SETCURSOR messge. // lParam Standard value sent during a WM_SETCURSOR messge. // // uMsg == 0 // wParam 0 // lParam If this value is non-zero then it is a packed x,y cursor location. // If it's zero then we need to query the cursor location LRESULT CZoomWnd::OnSetCursor(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { // if this is a legitimate message but isn't intended for the client area, we ignore it. // we also ignore set cursor when we have no valid bitmap if (((WM_SETCURSOR == uMsg) && (HTCLIENT != LOWORD(lParam))) || (m_iStrID != IDS_LOADING && !m_pImageData)) { bHandled = FALSE; return 0; } else if (0 == uMsg) { // Since this is one of our fake messages we need to do our own check to test for HTCLIENT. // we need to find the cursor location POINT pt; GetCursorPos(&pt); lParam = MAKELONG(pt.x, pt.y); if (HTCLIENT != SendMessage(WM_NCHITTEST, 0, lParam)) { bHandled = FALSE; return 0; } } if (m_pPreview->OnSetCursor(uMsg, wParam, lParam)) { bHandled = TRUE; return TRUE; } HINSTANCE hinst = _Module.GetModuleInstance(); LPTSTR idCur; if (m_iStrID == IDS_LOADING && !m_pImageData) { idCur = IDC_WAIT; hinst = NULL; } else if (m_fPanning) { idCur = MAKEINTRESOURCE(IDC_CLOSEDHAND); } else if (m_fCtrlDown) { idCur = MAKEINTRESOURCE(IDC_OPENHAND); } else if (m_modeDefault == MODE_NOACTION) { hinst = NULL; idCur = IDC_ARROW; } else if ((m_modeDefault == MODE_ZOOMIN && m_fShiftDown == FALSE) || (m_modeDefault == MODE_ZOOMOUT && m_fShiftDown == TRUE)) { idCur = MAKEINTRESOURCE(IDC_ZOOMIN); } else { idCur = MAKEINTRESOURCE(IDC_ZOOMOUT); } SetCursor(LoadCursor(hinst, idCur)); return TRUE; } // OnKeyUp // // Handles WM_KEYUP messages sent to the window LRESULT CZoomWnd::OnKeyUp(UINT , WPARAM wParam, LPARAM , BOOL& bHandled) { if (VK_CONTROL == wParam) { m_fCtrlDown = FALSE; OnSetCursor(0,0,0, bHandled); } else if (VK_SHIFT == wParam) { m_fShiftDown = FALSE; OnSetCursor(0,0,0, bHandled); } bHandled = FALSE; return 0; } // OnKeyDown // // Handles WM_KEYDOWN messages sent to the window LRESULT CZoomWnd::OnKeyDown(UINT , WPARAM wParam, LPARAM , BOOL& bHandled) { // when we return, we want to call the DefWindowProc bHandled = FALSE; switch (wParam) { case VK_PRIOR: OnScroll(WM_VSCROLL, m_fCtrlDown?SB_TOP:SB_PAGEUP, 0, bHandled); break; case VK_NEXT: OnScroll(WM_VSCROLL, m_fCtrlDown?SB_BOTTOM:SB_PAGEDOWN, 0, bHandled); break; case VK_END: OnScroll(WM_HSCROLL, m_fCtrlDown?SB_BOTTOM:SB_PAGEDOWN, 0, bHandled); break; case VK_HOME: OnScroll(WM_HSCROLL, m_fCtrlDown?SB_TOP:SB_PAGEUP, 0, bHandled); break; case VK_CONTROL: case VK_SHIFT: // if m_fPanning is TRUE then we are already in the middle of an operation so we // should maintain the cursor for that operation if (!m_fPanning) { if (VK_CONTROL == wParam) { m_fCtrlDown = TRUE; } if (VK_SHIFT == wParam) { m_fShiftDown = TRUE; } // Update the cursor based on the key states set above only if we are over our window OnSetCursor(0,0,0, bHandled); } break; default: // if in run screen preview mode any key other than Shift and Control will dismiss the window if (NULL == GetParent()) { DestroyWindow(); } return 1; // return non-zero to indicate unprocessed message } return 0; } // OnMouseUp // // Handles WM_LBUTTONUP messages sent to the window LRESULT CZoomWnd::OnMouseUp(UINT , WPARAM , LPARAM , BOOL& bHandled) { if (m_fPanning) ReleaseCapture(); m_fPanning = FALSE; bHandled = FALSE; return 0; } // OnMouseDown // // Handles WM_LBUTTONDOWN and WM_MBUTTONDOWN messages sent to the window LRESULT CZoomWnd::OnMouseDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { if (m_pPreview->OnMouseDown(uMsg, wParam, lParam)) return 0; // This stuff should be avoided if m_pImage is NULL. if (!m_pImageData) return 0; m_xPosMouse = GET_X_LPARAM(lParam); m_yPosMouse = GET_Y_LPARAM(lParam); ASSERT(m_fPanning == FALSE); // Holding the CTRL key makes a pan into a zoom and vise-versa. // The middle mouse button always pans regardless of default mode and key state. if ((wParam & MK_CONTROL) || (uMsg == WM_MBUTTONDOWN)) { // REVIEW: check for pan being valid here? Should be more efficient than all the checks // I have to do in OnMouseMove. m_fPanning = TRUE; OnSetCursor(0,0,0,bHandled); SetCapture(); } else if (m_modeDefault != MODE_NOACTION) { // Holding down the shift key turns a zoomin into a zoomout and vise-versa. // The "default" zoom mode is zoom in (if mode = pan and ctrl key is down we zoom in). BOOL bZoomIn = (m_modeDefault != MODE_ZOOMOUT) ^ ((wParam & MK_SHIFT)?1:0); // Find the point we want to stay centered on: m_cxCenter = MulDiv(m_xPosMouse-m_ptszDest.x, m_cxImgPix, m_ptszDest.cx); m_cyCenter = MulDiv(m_yPosMouse-m_ptszDest.y, m_cyImgPix, m_ptszDest.cy); bZoomIn?ZoomIn():ZoomOut(); } bHandled = FALSE; return 0; } void CZoomWnd::Zoom(WPARAM wParam, LPARAM lParam) { switch (wParam&0xFF) { case IVZ_CENTER: break; case IVZ_POINT: { int x = GET_X_LPARAM(lParam); int y = GET_Y_LPARAM(lParam); if (x<0) x=0; else if (x>=m_cxImgPix) x = m_cxImgPix-1; if (y<0) y=0; else if (y>=m_cyImgPix) y = m_cyImgPix-1; m_cxCenter = x; m_cyCenter = y; } break; case IVZ_RECT: { LPRECT prc = (LPRECT)lParam; int x = (prc->left+prc->right)/2; int y = (prc->top+prc->bottom)/2; if (x<0) x=0; else if (x>=m_cxImgPix) x = m_cxImgPix-1; if (y<0) y=0; else if (y>=m_cyImgPix) y = m_cyImgPix-1; m_cxCenter = x; m_cyCenter = y; // TODO: This should really completely adjust the dest rect but I have to // check for any assumptions about aspect ratio before I allow this absolute // aspect ignoring zoom mode. } break; } if (wParam&IVZ_ZOOMOUT) { ZoomOut(); SetMode(MODE_ZOOMOUT); } else { ZoomIn(); SetMode(MODE_ZOOMIN); } } void CZoomWnd::ZoomIn() { DWORD dwMode; m_pPreview->GetMode(&dwMode); if (m_pImageData && (SLIDESHOW_MODE != dwMode)) { m_fBestFit = FALSE; // first, the height is adjusted by the amount the mouse cursor moved. m_ptszDest.cy = (LONG)/*ceil*/(m_ptszDest.cy*1.200); // ceil is required in order to zoom in // on 4px high or less image // FEATURE: allow zooming beyond 16x the full size of the image // The use of the Cut and Bleed rectangles should eliminate the need for this // arbitrary zoom limit. The limit was originally added because GDI on win9x isn't ver good and // can't handle large images. Even on NT you would eventually zoom to the point where // it would take many seconds to blt the bitmap. Now we only blt the minimum required // area. if (m_ptszDest.cy >= m_cyImgPix*16) { m_ptszDest.cy = m_cyImgPix*16; } // next, a new width is calculated based on the original image dimensions and the new height m_ptszDest.cx = (LONG)(m_ptszDest.cy* (m_cxImgPhys*m_winDPIx)/(m_cyImgPhys*m_winDPIy)); AdjustRectPlacement(); } } void CZoomWnd::ZoomOut() { DWORD dwMode; m_pPreview->GetMode(&dwMode); if (m_pImageData && (SLIDESHOW_MODE != dwMode)) { // if the destination rect already fits within the window, don't allow a zoom out. // This check is to prevent a redraw that would occur otherwise if ((m_ptszDest.cx <= MIN(m_cxWindow,m_cxImgPix)) && (m_ptszDest.cy <= MIN(m_cyWindow,m_cyImgPix))) { m_fBestFit = TRUE; return; } // first, the height is adjusted by the amount the mouse cursor moved. m_ptszDest.cy = (LONG)/*floor*/(m_ptszDest.cy*0.833); // floor is default behavior // next, a new width is calculated based on the original image dimensions and the new height m_ptszDest.cx = (LONG)(m_ptszDest.cy* (m_cxImgPhys*m_winDPIx)/(m_cyImgPhys*m_winDPIy)); AdjustRectPlacement(); } } // OnMouseMove // // Handles WM_MOUSEMOVE messages sent to the control LRESULT CZoomWnd::OnMouseMove(UINT , WPARAM wParam, LPARAM lParam, BOOL& bHandled) { // This is something of a hack since I never recieve the keyboard focus m_fCtrlDown = (BOOL)(wParam & MK_CONTROL); m_fShiftDown = (BOOL)(wParam & MK_SHIFT); // we only care about mouse move when the middle or left button is down // and we have a valid bitmap handle and we are panning if (!(wParam & (MK_LBUTTON|MK_MBUTTON)) || !m_fPanning || !m_pImageData) { m_pPreview->OnMouseMove(WM_MOUSEMOVE, wParam, lParam); bHandled = FALSE; return TRUE; } // we know we are panning when we reach this point ASSERT(m_fPanning); POINTS pt = MAKEPOINTS(lParam); PTSZ ptszDest; ptszDest.cx = m_ptszDest.cx; ptszDest.cy = m_ptszDest.cy; // only allow side-to-side panning if it's needed if (m_ptszDest.cx > m_cxWindow) { ptszDest.x = m_ptszDest.x + pt.x - m_xPosMouse; } else { ptszDest.x = m_ptszDest.x; } // only allow up-and-down panning if it's needed if (m_ptszDest.cy > m_cyWindow) { ptszDest.y = m_ptszDest.y + pt.y - m_yPosMouse; } else { ptszDest.y = m_ptszDest.y; } // if the image is now smaller than the window, center it // if the image is now panned when it shouldn't be, adjust the possition if (ptszDest.cx < m_cxWindow) ptszDest.x = (m_cxWindow-ptszDest.cx)/2; else { if (ptszDest.x < (m_cxWindow - ptszDest.cx)) ptszDest.x = m_cxWindow - ptszDest.cx; if (ptszDest.x > 0) ptszDest.x = 0; } if (ptszDest.cy < m_cyWindow) ptszDest.y = (m_cyWindow-ptszDest.cy)/2; else { if (ptszDest.y < (m_cyWindow - ptszDest.cy)) ptszDest.y = m_cyWindow - ptszDest.cy; if (ptszDest.y > 0) ptszDest.y = 0; } m_xPosMouse = pt.x; m_yPosMouse = pt.y; // ensure the scroll bars are correct SetScrollBars(); // if anything has changed, we must invalidate the window to force a repaint if ((ptszDest.x != m_ptszDest.x) || (ptszDest.y != m_ptszDest.y) || (ptszDest.cx != m_ptszDest.cx) || (ptszDest.y != m_ptszDest.y)) { m_ptszDest = ptszDest; CalcCut(); PrepareDraw(); } // Update m_cxCenter and m_cyCenter so that a zoom after a pan will zoom in // on the correct area. This is majorly annoying otherwise. We want the // new center to be whatever is in the center of the window after we pan. m_cxCenter = MulDiv(m_cxWindow/2-m_ptszDest.x, m_cxImgPix, m_ptszDest.cx); m_cyCenter = MulDiv(m_cyWindow/2-m_ptszDest.y, m_cyImgPix, m_ptszDest.cy); return TRUE; } // OnSize // // Handles WM_SIZE messages set to the window LRESULT CZoomWnd::OnSize(UINT , WPARAM , LPARAM lParam, BOOL&) { m_cxWindow = GET_X_LPARAM(lParam); m_cyWindow = GET_Y_LPARAM(lParam); _UpdatePhysicalSize(); if (m_fBestFit) { BestFit(); } else { // The size of the rect doesn't change in this case, so just reposition AdjustRectPlacement(); } return TRUE; } BOOL CZoomWnd::SetScheduler(IShellTaskScheduler * pTaskScheduler) { if (!m_pTaskScheduler) { m_pTaskScheduler = pTaskScheduler; m_pTaskScheduler->AddRef(); return TRUE; } return FALSE; } // SetMode // // Sets the current mouse mode to one of the values specified in the MODE enumeration. // Currently there are two important modes, pan and zoom. The mode effects the default mouse // cursor when moving over the zoom window and the behavior of a click-and-drag with the // left mouse button. Holding the shift key effects the result of a click-and-drag but // does not effect m_mode, which is the default when the shift key isn't down. BOOL CZoomWnd::SetMode(MODE modeNew) { if (m_modeDefault == modeNew) return FALSE; m_modeDefault = modeNew; BOOL bDummy; OnSetCursor(0,0,0, bDummy); return TRUE; } // ActualSize // // Displays image zoomed to its full size void CZoomWnd::ActualSize() { m_fBestFit = FALSE; if (m_pImageData) { // actual size means same size as the image m_ptszDest.cx = (LONG)(m_cxImgPix); m_ptszDest.cy = (LONG)(m_cyImgPix); // we center the image in the window m_ptszDest.x = (LONG)((m_cxWinPhys-m_cxImgPhys)*m_winDPIx/2.0); m_ptszDest.y = (LONG)((m_cyWinPhys-m_cyImgPhys)*m_winDPIy/2.0); CalcCut(); // Setting actual size is a zoom operation. Whenever we zoom we update our centerpoint. m_cxCenter = m_cxImgPix/2; m_cyCenter = m_cyImgPix/2; // turn scoll bars on/off as needed SetScrollBars(); PrepareDraw(); } } void CZoomWnd::GetPTSZForBestFit(int cxImgPix, int cyImgPix, float cxImgPhys, float cyImgPhys, PTSZ &ptszDest) { // Determine the limiting axis, if any. if (cxImgPhys <= m_cxWinPhys && cyImgPhys <= m_cyWinPhys) { // item fits centered within window ptszDest.x = (LONG)((m_cxWinPhys-cxImgPhys)*m_winDPIx/2.0); ptszDest.y = (LONG)((m_cyWinPhys-cyImgPhys)*m_winDPIy/2.0); ptszDest.cx = (LONG)(cxImgPix); ptszDest.cy = (LONG)(cyImgPix); } else if (cxImgPhys * m_cyWinPhys < m_cxWinPhys * cyImgPhys) { // height is the limiting factor int iNewWidth = (int)((m_cyWinPhys*cxImgPhys/cyImgPhys) * m_winDPIx); ptszDest.x = (m_cxWindow-iNewWidth)/2; ptszDest.y = 0; ptszDest.cx = iNewWidth; ptszDest.cy = m_cyWindow; } else { // width is the limiting factor int iNewHeight = (int)((m_cxWinPhys*cyImgPhys/cxImgPhys) * m_winDPIy); ptszDest.x = 0; ptszDest.y = (m_cyWindow-iNewHeight)/2; ptszDest.cx = m_cxWindow; ptszDest.cy = iNewHeight; } } // BestFit // // Computes the default location for the destination rectangle. This rectangle is a // best fit while maintaining aspect ratio within a window of the given width and height. // If the window is larger than the image, the image is centered, otherwise it is scaled // to fit within the window. The destination rectange is computed in the client coordinates // of the window whose width and height are passed as arguments (ie we assume the point 0,0 // is the upper left corner of the window). // void CZoomWnd::BestFit() { m_fBestFit = TRUE; if (m_pImageData) { // if scroll bars are on, adjust the client size to what it will be once they are off DWORD dwStyle = GetWindowLong(GWL_STYLE); if (dwStyle & (WS_VSCROLL|WS_HSCROLL)) { m_cxWindow += (dwStyle&WS_VSCROLL)?m_cxVScroll:0; m_cyWindow += (dwStyle&WS_HSCROLL)?m_cyHScroll:0; _UpdatePhysicalSize(); } GetPTSZForBestFit(m_cxImgPix, m_cyImgPix, m_cxImgPhys, m_cyImgPhys, m_ptszDest); // this should turn off the scroll bars if they are on if (dwStyle & (WS_VSCROLL|WS_HSCROLL)) { SetScrollBars(); } CalcCut(); // ensure the scroll bars are now off ASSERT(0 == (GetWindowLong(GWL_STYLE)&(WS_VSCROLL|WS_HSCROLL))); PrepareDraw(); } } // AdjustRectPlacement // // This function determines the optimal placement of the destination rectangle. This may // include resizing the destination rectangle if it is smaller than the "best fit" rectangle // but it is primarily intended for repositioning the rectange due to a change in the window // size or destination rectangle size. The window is repositioned so that the centered point // remains in the center of the window. // void CZoomWnd::AdjustRectPlacement() { // if we have scroll bars ... DWORD dwStyle = GetWindowLong(GWL_STYLE); if (dwStyle&(WS_VSCROLL|WS_HSCROLL)) { // .. and if removing scroll bars would allow the image to fit ... if ((m_ptszDest.cx < (m_cxWindow + ((dwStyle&WS_VSCROLL)?m_cxVScroll:0))) && (m_ptszDest.cy < (m_cyWindow + ((dwStyle&WS_HSCROLL)?m_cyHScroll:0)))) { // ... remove the scroll bars m_cxWindow += (dwStyle&WS_VSCROLL)?m_cxVScroll:0; m_cyWindow += (dwStyle&WS_HSCROLL)?m_cyHScroll:0; SetScrollBars(); _UpdatePhysicalSize(); } } // If the dest rect is smaller than the window ... if ((m_ptszDest.cx < m_cxWindow) && (m_ptszDest.cy < m_cyWindow)) { // ... then it must be larger than the image. Otherwise we switch // to "best fit" mode. if ((m_ptszDest.cx < (LONG)m_cxImgPix) && (m_ptszDest.cy < (LONG)m_cyImgPix)) { BestFit(); return; } } // given the window size, client area size, and dest rect size calculate the // dest rect position. This position is then restrained by the limits below. m_ptszDest.x = (m_cxWindow/2) - MulDiv(m_cxCenter, m_ptszDest.cx, m_cxImgPix); m_ptszDest.y = (m_cyWindow/2) - MulDiv(m_cyCenter, m_ptszDest.cy, m_cyImgPix); // if the image is now narrower than the window ... if (m_ptszDest.cx < m_cxWindow) { // ... center the image m_ptszDest.x = (m_cxWindow-m_ptszDest.cx)/2; } else { // if the image is now panned when it shouldn't be, adjust the position if (m_ptszDest.x < (m_cxWindow - m_ptszDest.cx)) m_ptszDest.x = m_cxWindow - m_ptszDest.cx; if (m_ptszDest.x > 0) m_ptszDest.x = 0; } // if the image is now shorter than the window ... if (m_ptszDest.cy < m_cyWindow) { // ... center the image m_ptszDest.y = (m_cyWindow-m_ptszDest.cy)/2; } else { // if the image is now panned when it shouldn't be, adjust the position if (m_ptszDest.y < (m_cyWindow - m_ptszDest.cy)) m_ptszDest.y = m_cyWindow - m_ptszDest.cy; if (m_ptszDest.y > 0) m_ptszDest.y = 0; } CalcCut(); SetScrollBars(); PrepareDraw(); } // CalcCut // // This function should be called anytime the Destination rectangle changes. // Based on the destination rectangle it determines what part of the image // will be visible, ptszCut, and where on the window to place the stretched // cut rectangle, ptszBleed. // void CZoomWnd::CalcCut() { if (m_pImageData) { CalcCut(m_ptszDest, m_cxImage, m_cyImage, m_rcCut, m_rcBleed); } } void CZoomWnd::CalcCut(PTSZ ptszDest, int cxImage, int cyImage, RECT &rcCut, RECT &rcBleed) { // If the expanded image doesn't occupy the entire window ... if ((ptszDest.cy <= m_cyWindow) || (ptszDest.cx <= m_cxWindow)) { // draw the entire destination rectangle rcBleed.left = ptszDest.x; rcBleed.top = ptszDest.y; rcBleed.right = ptszDest.x + ptszDest.cx; rcBleed.bottom = ptszDest.y + ptszDest.cy; // cut the entire image rcCut.left = 0; rcCut.top = 0; rcCut.right = cxImage; rcCut.bottom = cyImage; } else { // NOTE: These calculations are written to retain as much // precision as possible. Loss of precision will result in // undesirable drawing artifacts in the destination window. // MulDiv is not used because it rounds the result when we // really want the result to be floored. // Given destination rectangle calculate the rectangle // of the original image that will be visible. // To do this we need to convert 2 points from window coordinates to image // coordinates, those two points are (0,0) and (cxWindow, cyWindow). The // (0,0) point needs to be floored and the (cxWindow, cyWindow) point needs // to be ceilinged to handle partially visible pixels. Since we don't have // a good way to do ceiling we just always add one. rcCut.left = LONG(Int32x32To64(-ptszDest.x, cxImage) / ptszDest.cx); rcCut.top = LONG(Int32x32To64(-ptszDest.y, cyImage) / ptszDest.cy); rcCut.right = LONG(Int32x32To64(m_cxWindow-ptszDest.x, cxImage) / ptszDest.cx) + 1; rcCut.bottom = LONG(Int32x32To64(m_cyWindow-ptszDest.y, cyImage) / ptszDest.cy) + 1; // Make sure the +1 does extend past the image border or GDI+ will choke. // If we were doing a TRUE "ceiling" this wouldn't be needed. if (rcCut.right > cxImage) rcCut.right = cxImage; if (rcCut.bottom > cyImage) rcCut.bottom = cyImage; // Calculate where on the window to place the cut rectangle. // Only a fraction of a zoomed pixel may be visible, hence the bleed factor. // Basically we converted from window coordinates to image coordinates to find // the Cut rectangle (what we need to draw), now we convert that Cut rectangle // back to window coordinates so that we know exactly where we need to draw it. rcBleed.left = ptszDest.x + LONG(Int32x32To64(rcCut.left, ptszDest.cx) / cxImage); rcBleed.top = ptszDest.y + LONG(Int32x32To64(rcCut.top, ptszDest.cy) / cyImage); rcBleed.right = ptszDest.x + LONG(Int32x32To64(rcCut.right, ptszDest.cx) / cxImage); rcBleed.bottom = ptszDest.y + LONG(Int32x32To64(rcCut.bottom, ptszDest.cy) / cyImage); } } void CZoomWnd::GetVisibleImageWindowRect(LPRECT prectImage) { CopyRect(prectImage, &m_rcBleed); } void CZoomWnd::GetImageFromWindow(LPPOINT ppoint, int cSize) { for(int i=0;iRelease(); m_pImageData = 0; } if (m_pFront) { DWORD dwMode; m_pPreview->GetMode(&dwMode); if (SLIDESHOW_MODE != dwMode) { DeleteBuffer(m_pFront); m_pFront = NULL; } } m_iStrID = iStatus; // m_cxImage and m_cyImage should be reset to their initial values so that we don't // accidentally draw scroll bars or something like that m_cxImage = 1; m_cyImage = 1; // The dest rect should be reset too so that we don't allow some zoom // or pan that isn't actually valid. m_ptszDest.y = 0; m_ptszDest.x = 0; m_ptszDest.cx = m_cxWindow; m_ptszDest.cy = m_cyWindow; SetScrollBars(); if (m_hWnd) { PrepareDraw(); } } // SetImageData // // Called to pass in the pointer to the IShellImageData we draw. We hold a reference to this // object so that we can use it to paint. // void CZoomWnd::SetImageData(CDecodeTask * pImageData, BOOL bUpdate) { if (bUpdate) { m_fTimerReady = TRUE; if (m_pFront) { DWORD dwMode; m_pPreview->GetMode(&dwMode); if (SLIDESHOW_MODE != dwMode) { DeleteBuffer(m_pFront); m_pFront = NULL; } } } if (m_pImageData) { m_pImageData->Release(); } m_pImageData = pImageData; if (m_pImageData) { m_pImageData->AddRef(); m_pImageData->ChangePage(m_Annotations); SIZE sz; ULONG dpiX; ULONG dpiY; pImageData->GetSize(&sz); pImageData->GetResolution(&dpiX, &dpiY); if (m_cxImage != sz.cx || m_cyImage != sz.cy || dpiX != m_imgDPIx || dpiY != m_imgDPIy) { bUpdate = TRUE; } if (bUpdate) { // cache the image dimensions to avoid checking m_pImageData against NULL all over the place m_cxImage = sz.cx; m_cyImage = sz.cy; m_imgDPIx = (float)dpiX; m_imgDPIy = (float)dpiY; m_cxImgPhys = m_cxImage/m_imgDPIx; m_cyImgPhys = m_cyImage/m_imgDPIy; m_cxImgPix = (int)(m_cxImgPhys*m_winDPIx); m_cyImgPix = (int)(m_cyImgPhys*m_winDPIy); m_cxCenter = m_cxImgPix/2; m_cyCenter = m_cyImgPix/2; } if (m_hWnd) { // REVIEW: should we keep the previous Actual Size/Best Fit setting? if (bUpdate) { BestFit(); } else { PrepareDraw(); } } return; } m_iStrID = IDS_LOADFAILED; } void CZoomWnd::SetPalette(HPALETTE hpal) { m_hpal = hpal; } void CZoomWnd::SetScrollBars() { SCROLLINFO si; si.cbSize = sizeof(si); si.fMask = SIF_ALL; si.nMin = 0; si.nMax = m_ptszDest.cx; si.nPage = m_cxWindow+1; si.nPos = 0-m_ptszDest.x; si.nTrackPos = 0; SetScrollInfo(SB_HORZ, &si, TRUE); si.nMax = m_ptszDest.cy; si.nPage = m_cyWindow+1; si.nPos = 0-m_ptszDest.y; SetScrollInfo(SB_VERT, &si, TRUE); } LRESULT CZoomWnd::OnScroll(UINT uMsg, WPARAM wParam, LPARAM , BOOL&) { int iScrollBar; int iWindow; // width or height of the window LONG * piTL; // pointer to top or left point LONG iWH; // the width or height of the dest rect if (!m_pImageData) return 0; // handle both which direction we're scrolling if (WM_HSCROLL==uMsg) { iScrollBar = SB_HORZ; iWindow = m_cxWindow; piTL = &m_ptszDest.x; iWH = m_ptszDest.cx; } else { iScrollBar = SB_VERT; iWindow = m_cyWindow; piTL = &m_ptszDest.y; iWH = m_ptszDest.cy; } // Using the keyboard we can get scroll messages when we don't have scroll bars. // Ignore these messages. if (iWindow >= iWH) { // window is larger than the image, don't allow scrolling return 0; } // handle all possible scroll cases switch (LOWORD(wParam)) { case SB_TOP: *piTL = 0; break; case SB_PAGEUP: *piTL += iWindow; break; case SB_LINEUP: (*piTL)++; break; case SB_LINEDOWN: (*piTL)--; break; case SB_PAGEDOWN: *piTL -= iWindow; break; case SB_BOTTOM: *piTL = iWindow-iWH; break; case SB_THUMBPOSITION: case SB_THUMBTRACK: *piTL = -HIWORD(wParam); break; case SB_ENDSCROLL: return 0; } // apply limits if (0 < *piTL) *piTL = 0; else if ((iWindow-iWH) > *piTL) *piTL = iWindow-iWH; CalcCut(); // adjust scrollbars SetScrollPos(iScrollBar, -(*piTL), TRUE); // calculate new center point relative to image if (WM_HSCROLL==uMsg) { m_cxCenter = MulDiv((m_cxWindow/2)-m_ptszDest.x, m_cxImage, m_ptszDest.cx); } else { m_cyCenter = MulDiv((m_cyWindow/2)-m_ptszDest.y, m_cyImage, m_ptszDest.cy); } PrepareDraw(); return 0; } // OnWheelTurn // // Respondes to WM_MOUSEWHEEL messages sent to the parent window (then redirected here) LRESULT CZoomWnd::OnWheelTurn(UINT , WPARAM wParam, LPARAM , BOOL&) { BOOL bZoomIn = ((short)HIWORD(wParam) > 0); bZoomIn?ZoomIn():ZoomOut(); return TRUE; } LRESULT CZoomWnd::OnSetFocus(UINT , WPARAM , LPARAM , BOOL&) { HWND hwndParent = GetParent(); ::SetFocus(hwndParent); return 0; } void CZoomWnd::CommitAnnotations() { if (m_pImageData) { IShellImageData * pSID; if (SUCCEEDED(m_pImageData->Lock(&pSID))) { m_Annotations.CommitAnnotations(pSID); m_pImageData->Unlock(); } } } BOOL CZoomWnd::ScrollBarsPresent() { SCROLLINFO si = {0}; si.cbSize = sizeof(si); si.fMask = SIF_ALL; if ((GetScrollInfo(SB_HORZ, &si) && si.nPos) || (GetScrollInfo(SB_VERT, &si) && si.nPos) ) { return TRUE; } return FALSE; } void CZoomWnd::_UpdatePhysicalSize() { m_cxWinPhys = (float)(m_cxWindow)/m_winDPIx; m_cyWinPhys = (float)(m_cyWindow)/m_winDPIy; } LRESULT CZoomWnd::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { FlushDrawMessages(); if (m_pFront) { DeleteBuffer(m_pFront); m_pFront = NULL; } return 0; }