|
|
#include "precomp.h"
#include <uxtheme.h>
#include <shstyle.h>
#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;i<cSize;i++) { ppoint[i].x -= m_ptszDest.x; ppoint[i].y -= m_ptszDest.y; ppoint[i].x = MulDiv(ppoint[i].x, m_cxImage, m_ptszDest.cx); ppoint[i].y = MulDiv(ppoint[i].y, m_cyImage, m_ptszDest.cy); } }
void CZoomWnd::GetWindowFromImage(LPPOINT ppoint, int cSize) { for(int i=0;i<cSize;i++) { ppoint[i].x = MulDiv(ppoint[i].x, m_ptszDest.cx, m_cxImage); ppoint[i].y = MulDiv(ppoint[i].y, m_ptszDest.cy, m_cyImage); ppoint[i].x += m_ptszDest.x; ppoint[i].y += m_ptszDest.y; } }
// StatusUpdate
//
// Sent when the image generation status has changed, once when the image is first
// being created and again if there is an error of any kind.
void CZoomWnd::StatusUpdate(int iStatus) { if (m_pImageData) { m_pImageData->Release(); 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; }
|