windows-nt/Source/XPSP1/NT/shell/ext/shimgvw/zoomwnd.cpp
2020-09-26 16:20:57 +08:00

1549 lines
42 KiB
C++

#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;
}