mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1548 lines
42 KiB
1548 lines
42 KiB
#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;
|
|
}
|