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.
5983 lines
187 KiB
5983 lines
187 KiB
#include "precomp.h"
|
|
#include "PrevWnd.h"
|
|
#include "PrevCtrl.h"
|
|
#include "resource.h"
|
|
#include <shimgdata.h>
|
|
#include "shutil.h"
|
|
#include "tasks.h"
|
|
#include <shellp.h>
|
|
#include <ccstock2.h>
|
|
#include <htmlhelp.h>
|
|
|
|
#include "prwiziid.h"
|
|
#pragma hdrstop
|
|
|
|
#define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp))
|
|
#define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp))
|
|
|
|
#define COPYDATATYPE_DATAOBJECT 1
|
|
#define COPYDATATYPE_FILENAME 2
|
|
|
|
int g_x = 0, g_y = 0; // mouse coordinates
|
|
BOOL g_bMirroredOS = FALSE;
|
|
|
|
|
|
#define HTMLHELP_FILENAME TEXT("ImgPrev.chm")
|
|
|
|
static COLORREF g_crCustomColors[] = {
|
|
RGB(255,255,255),
|
|
RGB(255,255,255),
|
|
RGB(255,255,255),
|
|
RGB(255,255,255),
|
|
RGB(255,255,255),
|
|
RGB(255,255,255),
|
|
RGB(255,255,255),
|
|
RGB(255,255,255),
|
|
RGB(255,255,255),
|
|
RGB(255,255,255),
|
|
RGB(255,255,255),
|
|
RGB(255,255,255),
|
|
RGB(255,255,255),
|
|
RGB(255,255,255),
|
|
RGB(255,255,255),
|
|
RGB(255,255,255)
|
|
};
|
|
|
|
static void _RotateRect(CRect &rect, CAnnotation* pAnnotation)
|
|
{
|
|
UINT uType = pAnnotation->GetType();
|
|
|
|
if (uType != MT_TYPEDTEXT && uType != MT_FILETEXT && uType != MT_STAMP && uType != MT_ATTACHANOTE)
|
|
return;
|
|
|
|
CTextAnnotation* pTextAnnotation = (CTextAnnotation*)pAnnotation;
|
|
|
|
int nOrientation = pTextAnnotation->GetOrientation();
|
|
if (nOrientation == 900 || nOrientation == 2700)
|
|
{
|
|
int nWidth = rect.Width();
|
|
int nHeight = rect.Height();
|
|
rect.right = rect.left + nHeight;
|
|
rect.bottom = rect.top + nWidth;
|
|
}
|
|
}
|
|
|
|
CPreviewWnd::CPreviewWnd() : m_ctlToolbar(NULL, this, 1), m_ctlPreview(this), m_ctlEdit(NULL, this, 2)
|
|
{
|
|
// we are often created on the stack, so we can't be sure that we're zero initialized
|
|
|
|
m_fCanCrop = FALSE;
|
|
m_fCropping = FALSE;
|
|
m_rectCropping.SetRectEmpty();
|
|
|
|
m_fBusy = FALSE;
|
|
m_hCurOld = NULL;
|
|
m_hCurrent = NULL;
|
|
m_fWarnQuietSave = TRUE;
|
|
m_fWarnNoSave = TRUE;
|
|
m_fPromptingUser = FALSE;
|
|
m_fCanAnnotate = FALSE;
|
|
m_fAnnotating = FALSE;
|
|
m_fEditingAnnotation = FALSE;
|
|
m_fDirty = FALSE;
|
|
m_pEvents = 0;
|
|
m_fPaused = FALSE;
|
|
m_fGoBack = FALSE;
|
|
m_fHidePrintBtn = FALSE;
|
|
m_fPrintable = FALSE;
|
|
m_fDisableEdit = FALSE;
|
|
m_fCanSave = TRUE;
|
|
m_fExitApp = FALSE;
|
|
|
|
m_fAllowContextMenu = TRUE; // full screen window always has a context menu
|
|
m_iCurSlide = -1;
|
|
|
|
m_iDecodingNextImage = -1;
|
|
m_pNextImageData = NULL;
|
|
|
|
m_fToolbarHidden = TRUE;
|
|
m_dwMultiPageMode = MPCMD_HIDDEN;
|
|
m_fIgnoreUITimers = FALSE;
|
|
|
|
DWORD cbSize = sizeof(m_uTimeout);
|
|
UINT uDefault = DEFAULT_SHIMGVW_TIMEOUT;
|
|
SHRegGetUSValue(REGSTR_SHIMGVW, REGSTR_TIMEOUT, NULL, (void *)&m_uTimeout, &cbSize, FALSE, (void *)&uDefault, sizeof(uDefault));
|
|
|
|
InitSelectionTracking();
|
|
|
|
g_bMirroredOS = IS_MIRRORING_ENABLED(); // global????
|
|
|
|
m_hdpaSelectedAnnotations = NULL;
|
|
m_haccel = NULL;
|
|
m_hpal = NULL;
|
|
m_dwMode = 0;
|
|
m_fShowToolbar = TRUE;
|
|
m_punkSite = NULL;
|
|
m_pImageFactory = NULL;
|
|
m_pcwndSlideShow = NULL;
|
|
m_hFont = NULL;
|
|
m_pImageData = NULL;
|
|
m_ppidls = NULL;
|
|
m_cItems = 0;
|
|
_pcm3 = NULL;
|
|
|
|
m_pTaskScheduler = NULL;
|
|
m_pici = NULL;
|
|
|
|
_pdtobj = NULL;
|
|
|
|
m_fFirstTime = TRUE;
|
|
m_fFirstItem = FALSE;
|
|
m_dwEffect = DROPEFFECT_NONE;
|
|
m_fIgnoreNextNotify = FALSE;
|
|
m_uRegister = 0;
|
|
m_fNoRestore = FALSE;
|
|
m_bRTLMirrored = FALSE;
|
|
m_cWalkDepth = 0;
|
|
m_fIgnoreAllNotifies = FALSE;
|
|
}
|
|
|
|
HRESULT CPreviewWnd::Initialize(CPreviewWnd* pother, DWORD dwMode, BOOL bExitApp)
|
|
{
|
|
HRESULT hr = E_OUTOFMEMORY;
|
|
|
|
m_hdpaSelectedAnnotations = DPA_Create(16);
|
|
if (m_hdpaSelectedAnnotations)
|
|
{
|
|
hr = S_OK;
|
|
m_dwMode = dwMode;
|
|
m_fExitApp = bExitApp;
|
|
|
|
// Set some defaults based on the mode
|
|
if (CONTROL_MODE == m_dwMode)
|
|
{
|
|
m_fHidePrintBtn = TRUE;
|
|
}
|
|
|
|
if (pother)
|
|
{
|
|
m_fHidePrintBtn = pother->m_fHidePrintBtn;
|
|
m_fPrintable = pother->m_fPrintable;
|
|
m_fDisableEdit = pother->m_fDisableEdit;
|
|
m_fCanSave = pother->m_fCanSave;
|
|
m_haccel = pother->m_haccel;
|
|
m_dwMultiPageMode = pother->m_dwMultiPageMode;
|
|
m_hpal = pother->m_hpal;
|
|
m_iCurSlide = pother->m_iCurSlide;
|
|
|
|
m_uTimeout = pother->m_uTimeout;
|
|
|
|
SetSite(pother->m_punkSite);
|
|
|
|
// we grab a reference to the controlling objects m_pImageFactory
|
|
// becaue it would be odd to create new ones.
|
|
m_pImageFactory = pother->m_pImageFactory;
|
|
if (m_pImageFactory)
|
|
{
|
|
m_pImageFactory->AddRef();
|
|
}
|
|
|
|
m_pTaskScheduler = pother->m_pTaskScheduler;
|
|
if (m_pTaskScheduler)
|
|
{
|
|
m_pTaskScheduler->AddRef();
|
|
}
|
|
|
|
// lets copy the DPA of items also, and the current index
|
|
if (pother->m_ppidls)
|
|
{
|
|
m_ppidls = (LPITEMIDLIST*)CoTaskMemAlloc(sizeof(LPITEMIDLIST)*pother->m_cItems);
|
|
if (m_ppidls)
|
|
{
|
|
for (int iItem = 0; iItem != pother->m_cItems; iItem++)
|
|
{
|
|
if (SUCCEEDED(pother->_GetItem(iItem, &m_ppidls[m_cItems])))
|
|
{
|
|
m_cItems++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
int ClearCB(void *p, void *pData);
|
|
|
|
CPreviewWnd::~CPreviewWnd()
|
|
{
|
|
CleanupSelectionTracking();
|
|
|
|
if (m_hdpaSelectedAnnotations != NULL)
|
|
DPA_Destroy(m_hdpaSelectedAnnotations);
|
|
|
|
::DeleteObject(m_hFont);
|
|
|
|
ATOMICRELEASE(m_pImageData);
|
|
ATOMICRELEASE(m_pNextImageData);
|
|
ATOMICRELEASE(m_pImageFactory);
|
|
SetSite(NULL);
|
|
ATOMICRELEASE(_pdtobj);
|
|
ATOMICRELEASE(m_pTaskScheduler);
|
|
|
|
|
|
_ClearDPA();
|
|
|
|
if (m_pcwndSlideShow)
|
|
{
|
|
if (m_pcwndSlideShow->m_hWnd)
|
|
{
|
|
m_pcwndSlideShow->DestroyWindow();
|
|
}
|
|
delete m_pcwndSlideShow;
|
|
}
|
|
|
|
if (m_pici)
|
|
{
|
|
LocalFree(m_pici);
|
|
m_pici = NULL;
|
|
}
|
|
}
|
|
|
|
BOOL CPreviewWnd::CreateSlideshowWindow(UINT cWalkDepth)
|
|
{
|
|
RECT rc = { CW_USEDEFAULT, CW_USEDEFAULT, 0, 0 };
|
|
|
|
if (!Create(NULL, rc, NULL, WS_VISIBLE | WS_POPUP | WS_CLIPCHILDREN))
|
|
return FALSE;
|
|
|
|
WINDOWPLACEMENT wp = {0};
|
|
|
|
wp.length = sizeof(wp);
|
|
GetWindowPlacement(&wp);
|
|
wp.showCmd = SW_MAXIMIZE;
|
|
SetWindowPlacement(&wp);
|
|
SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_SHOWWINDOW);
|
|
|
|
// when we get called from autoplay to do a slideshow,
|
|
// we need to walk deeply to find everything
|
|
// that the autoplay code may have found
|
|
|
|
m_cWalkDepth = cWalkDepth;
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL CPreviewWnd::_CloseSlideshowWindow()
|
|
{
|
|
if (m_fExitApp)
|
|
PostQuitMessage(0);
|
|
else
|
|
PostMessage(WM_CLOSE, 0, 0);
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL CPreviewWnd::CreateViewerWindow()
|
|
{
|
|
// create the window hidden, that way any sizing etc we perform doesn't reflect
|
|
// until its actually visible.
|
|
|
|
RECT rc = { CW_USEDEFAULT, CW_USEDEFAULT, 0, 0 };
|
|
BOOL bRet = (NULL != Create(NULL, rc, NULL, WS_OVERLAPPEDWINDOW));
|
|
m_haccel = LoadAccelerators(_Module.GetModuleInstance(), MAKEINTRESOURCE(IDA_PREVWND_SINGLEPAGE));
|
|
if (bRet)
|
|
{
|
|
// restore the window size based on the information we store in the registry.
|
|
HKEY hk;
|
|
if (ERROR_SUCCESS == RegOpenKey(HKEY_CURRENT_USER, REGSTR_SHIMGVW, &hk))
|
|
{
|
|
DWORD cbSize, dwType;
|
|
|
|
// set the window placement, passing the restore rectangle for the window.
|
|
|
|
WINDOWPLACEMENT wp = { 0 };
|
|
wp.length = sizeof(wp);
|
|
|
|
GetWindowPlacement(&wp);
|
|
|
|
cbSize = sizeof(wp.rcNormalPosition);
|
|
RegQueryValueEx(hk, REGSTR_BOUNDS, NULL, &dwType, (BYTE*)&wp.rcNormalPosition, &cbSize);
|
|
|
|
BOOL fMaximize = TRUE;
|
|
cbSize = sizeof(fMaximize);
|
|
RegQueryValueEx(hk, REGSTR_MAXIMIZED, NULL, &dwType, (BYTE*)&fMaximize, &cbSize);
|
|
if (fMaximize)
|
|
wp.showCmd = SW_MAXIMIZE;
|
|
|
|
SetWindowPlacement(&wp);
|
|
RegCloseKey(hk);
|
|
}
|
|
// now show the window having set its placement etc.
|
|
SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_SHOWWINDOW);
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
|
|
void ReplaceWindowIcon(HWND hwnd, int id, HICON hicon)
|
|
{
|
|
HICON hiconOld = (HICON)SendMessage(hwnd, WM_SETICON, id, (LPARAM)hicon);
|
|
if (hiconOld)
|
|
DestroyIcon(hiconOld);
|
|
}
|
|
|
|
LRESULT CPreviewWnd::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL&)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if (IS_BIDI_LOCALIZED_SYSTEM())
|
|
{
|
|
SHSetWindowBits(m_hWnd, GWL_EXSTYLE, WS_EX_LAYOUTRTL, WS_EX_LAYOUTRTL);
|
|
m_bRTLMirrored = TRUE;
|
|
}
|
|
if (!m_pImageFactory)
|
|
{
|
|
hr = CoCreateInstance(CLSID_ShellImageDataFactory, NULL, CLSCTX_INPROC,
|
|
IID_PPV_ARG(IShellImageDataFactory, &m_pImageFactory));
|
|
if (FAILED(hr))
|
|
return -1;
|
|
}
|
|
|
|
if (!m_pTaskScheduler)
|
|
{
|
|
hr = IUnknown_QueryService(m_punkSite, SID_ShellTaskScheduler, IID_PPV_ARG(IShellTaskScheduler, &m_pTaskScheduler));
|
|
if (FAILED(hr))
|
|
{
|
|
hr = CoCreateInstance(CLSID_ShellTaskScheduler, NULL, CLSCTX_INPROC,
|
|
IID_PPV_ARG(IShellTaskScheduler, &m_pTaskScheduler));
|
|
|
|
if (FAILED(hr))
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// figure out where to place the zoom window
|
|
RECT rcWnd;
|
|
GetClientRect(&rcWnd);
|
|
|
|
if (m_fShowToolbar)
|
|
{
|
|
// Create a toolbar control and then subclass it
|
|
if (!CreateToolbar())
|
|
return -1;
|
|
|
|
m_iSSToolbarSelect = 0;
|
|
}
|
|
|
|
HICON hicon = LoadIcon(_Module.GetModuleInstance(), MAKEINTRESOURCE(IDI_FULLSCREEN));
|
|
|
|
ReplaceWindowIcon(m_hWnd, ICON_SMALL, hicon);
|
|
ReplaceWindowIcon(m_hWnd, ICON_BIG, hicon);
|
|
|
|
// Create the preview window
|
|
DWORD dwExStyle = 0; // (WINDOW_MODE == m_dwMode) ? WS_EX_CLIENTEDGE : 0 ;
|
|
if (m_ctlPreview.Create(m_hWnd, rcWnd, NULL, WS_CHILD|WS_VISIBLE|WS_CLIPCHILDREN|WS_CLIPSIBLINGS, dwExStyle))
|
|
{
|
|
// When the window is created its default mode should be NOACTION. This call is needed
|
|
// because the object might have a longer life cycle than the window. If a new window
|
|
// is created for the same object we want to reset the state.
|
|
m_ctlPreview.SetMode(CZoomWnd::MODE_NOACTION);
|
|
m_ctlPreview.SetScheduler(m_pTaskScheduler);
|
|
}
|
|
|
|
RegisterDragDrop(m_hWnd, SAFECAST(this, IDropTarget *));
|
|
|
|
return 0;
|
|
}
|
|
|
|
HRESULT CPreviewWnd::_SaveIfDirty(BOOL fCanCancel)
|
|
{
|
|
// Callers assume that _SaveIfDirty will either return S_OK or S_FALSE
|
|
// since this function is designed to sit in a loop until the user gives
|
|
// up saving (cancels) or save successfully.
|
|
HRESULT hr = S_OK;
|
|
if (m_fDirty)
|
|
{
|
|
CComBSTR bstrMsg, bstrTitle;
|
|
|
|
if (bstrMsg.LoadString(IDS_SAVEWARNING_MSGBOX) && bstrTitle.LoadString(IDS_PROJNAME))
|
|
{
|
|
hr = E_FAIL;
|
|
while (FAILED(hr))
|
|
{
|
|
UINT uFlags;
|
|
if (fCanCancel)
|
|
uFlags = MB_YESNOCANCEL;
|
|
else
|
|
uFlags = MB_YESNO;
|
|
|
|
uFlags |= MB_ICONQUESTION | MB_DEFBUTTON1 | MB_APPLMODAL;
|
|
|
|
m_fPromptingUser = TRUE;
|
|
int iResult = MessageBox(bstrMsg, bstrTitle, uFlags);
|
|
m_fPromptingUser = FALSE;
|
|
|
|
if (iResult == IDYES)
|
|
{
|
|
// if this fails we keep looping.
|
|
// if this returns S_OK we succeed
|
|
// if this returns S_FALSE we cancel
|
|
hr = _SaveAsCmd();
|
|
}
|
|
else if (iResult == IDCANCEL)
|
|
{
|
|
hr = S_FALSE;
|
|
}
|
|
else
|
|
{
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
}
|
|
if (S_OK == hr)
|
|
m_fDirty = FALSE;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
LRESULT CPreviewWnd::OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& fHandled)
|
|
{
|
|
// hack alert- if CONTROL is held down while pressing the close button, do the registry stuff.
|
|
// make sure the E accelerator isn't being used (edit verb)!
|
|
if (m_fPromptingUser)
|
|
{
|
|
SetForegroundWindow(m_hWnd);
|
|
fHandled = TRUE;
|
|
return 0;
|
|
}
|
|
if (!m_fNoRestore && GetKeyState(VK_CONTROL) & 0x8000)
|
|
{
|
|
CRegKey Key;
|
|
if (ERROR_SUCCESS == Key.Open(HKEY_CURRENT_USER, REGSTR_SHIMGVW))
|
|
{
|
|
Key.DeleteValue(REGSTR_LOSSYROTATE);
|
|
}
|
|
|
|
if (ERROR_SUCCESS == Key.Open(HKEY_CURRENT_USER, REGSTR_DONTSHOWME))
|
|
{
|
|
Key.DeleteValue(REGSTR_SAVELESS);
|
|
Key.DeleteValue(REGSTR_LOSSYROTATE);
|
|
}
|
|
|
|
CComBSTR bstrMsg, bstrTitle;
|
|
|
|
if (bstrMsg.LoadString(IDS_RESET_MSGBOX) && bstrTitle.LoadString(IDS_PROJNAME))
|
|
{
|
|
m_fPromptingUser = TRUE;
|
|
MessageBox(bstrMsg, bstrTitle, MB_OK | MB_APPLMODAL);
|
|
m_fPromptingUser = FALSE;
|
|
}
|
|
}
|
|
|
|
fHandled = FALSE; // let it close
|
|
HRESULT hr = _SaveIfDirty(TRUE);
|
|
if (hr == S_FALSE) // _SaveIfDirty can only return S_OK and S_FALSE
|
|
{
|
|
m_fNoRestore = FALSE;
|
|
fHandled = TRUE;
|
|
}
|
|
if (!fHandled)
|
|
{
|
|
m_fClosed = TRUE;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// we only have to erase the portion not covered by the zoomwnd
|
|
// change this code if the toolbar is put back at the top of the window
|
|
LRESULT CPreviewWnd::OnEraseBkgnd(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& fHandled)
|
|
{
|
|
RECT rc;
|
|
RECT rcZoomwnd;
|
|
m_ctlPreview.GetClientRect(&rcZoomwnd);
|
|
GetClientRect(&rc);
|
|
rc.top = RECTHEIGHT(rcZoomwnd);
|
|
SetBkColor((HDC)wParam, m_ctlPreview.GetBackgroundColor());
|
|
ExtTextOut((HDC)wParam, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL);
|
|
|
|
fHandled = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
LRESULT CPreviewWnd::OnSize(UINT , WPARAM wParam, LPARAM lParam, BOOL&)
|
|
{
|
|
int x =0, y =0, cx =0, cy =0;
|
|
|
|
if (lParam == 0)
|
|
{
|
|
RECT rcClient;
|
|
GetClientRect(&rcClient);
|
|
cx = RECTWIDTH(rcClient);
|
|
cy = RECTHEIGHT(rcClient);
|
|
}
|
|
else
|
|
{
|
|
cx = GET_X_LPARAM(lParam);
|
|
cy = GET_Y_LPARAM(lParam);
|
|
}
|
|
|
|
if (m_fShowToolbar)
|
|
{
|
|
SIZE sizeToolbar;
|
|
m_ctlToolbar.SendMessage(TB_GETMAXSIZE, 0, (LPARAM)&sizeToolbar);
|
|
if (sizeToolbar.cx > cx)
|
|
sizeToolbar.cx = cx;
|
|
|
|
if (SLIDESHOW_MODE != m_dwMode)
|
|
{
|
|
// Center the toolbar horizontally
|
|
LONG cyBottomMargin = (CONTROL_MODE == m_dwMode) ? NEWTOOLBAR_BOTTOMMARGIN_CTRLMODE : NEWTOOLBAR_BOTTOMMARGIN;
|
|
LONG xNewToolbar = ( cx - sizeToolbar.cx ) / 2;
|
|
LONG yNewToolbar = cy - ( cyBottomMargin + sizeToolbar.cy );
|
|
|
|
::SetWindowPos(m_ctlToolbar.m_hWnd, NULL, xNewToolbar, yNewToolbar, sizeToolbar.cx, sizeToolbar.cy, SWP_NOZORDER);
|
|
|
|
// make the preview window shorter so the toolbar is below it
|
|
cy -= ( NEWTOOLBAR_TOPMARGIN + sizeToolbar.cy + cyBottomMargin);
|
|
}
|
|
else
|
|
{
|
|
// Pin the toolbar to the upper right corner
|
|
UINT uFlags = 0;
|
|
if (m_fToolbarHidden)
|
|
uFlags |= SWP_HIDEWINDOW;
|
|
else
|
|
uFlags |= SWP_SHOWWINDOW;
|
|
|
|
::SetWindowPos(m_ctlToolbar.m_hWnd, HWND_TOP, cx-sizeToolbar.cx, 0, sizeToolbar.cx, sizeToolbar.cy, uFlags);
|
|
}
|
|
}
|
|
|
|
::SetWindowPos(m_ctlPreview.m_hWnd, NULL, x, y, cx, cy, SWP_NOZORDER);
|
|
return 0;
|
|
}
|
|
|
|
|
|
BOOL CPreviewWnd::_VerbExists(LPCTSTR pszVerb)
|
|
{
|
|
// TODO: Create the context menu for the item and check it for the verb
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// given a verb lets invoke it for the current file
|
|
HRESULT CPreviewWnd::_InvokeVerb(LPCTSTR pszVerb, LPCTSTR szParameters)
|
|
{
|
|
SHELLEXECUTEINFO sei = {0};
|
|
|
|
sei.cbSize = sizeof(sei);
|
|
sei.fMask = SEE_MASK_INVOKEIDLIST | SEE_MASK_HMONITOR;
|
|
sei.hwnd = m_hWnd;
|
|
sei.lpVerb = pszVerb;
|
|
sei.nShow = SW_SHOW;
|
|
sei.lpParameters = szParameters;
|
|
sei.hMonitor = MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTONEAREST);
|
|
LPITEMIDLIST pidl = NULL;
|
|
TCHAR szPath[MAX_PATH];
|
|
HRESULT hr = GetCurrentIDList(&pidl);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
sei.lpIDList = pidl;
|
|
}
|
|
else if (SUCCEEDED(hr = PathFromImageData(szPath, ARRAYSIZE(szPath))))
|
|
{
|
|
sei.lpFile = szPath;
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = S_OK;
|
|
if (!ShellExecuteEx(&sei))
|
|
hr = E_FAIL;
|
|
}
|
|
ILFree(pidl);
|
|
|
|
return hr;
|
|
}
|
|
|
|
// show the window and activate it
|
|
// if the window is minimized, restore it
|
|
|
|
void RestoreAndActivate(HWND hwnd)
|
|
{
|
|
if (IsIconic(hwnd))
|
|
{
|
|
ShowWindow(hwnd, SW_RESTORE);
|
|
}
|
|
SetForegroundWindow(hwnd);
|
|
}
|
|
|
|
// Handles WM_COMMAND messages sent from the toolbar control
|
|
|
|
LRESULT CPreviewWnd::OnToolbarCommand(WORD wNotifyCode, WORD wID, HWND hwnd, BOOL& fHandled)
|
|
{
|
|
// don't use early returns in this function
|
|
m_fIgnoreAllNotifies = TRUE;
|
|
switch (wID)
|
|
{
|
|
case ID_ZOOMINCMD:
|
|
ZoomIn();
|
|
break;
|
|
|
|
case ID_ZOOMOUTCMD:
|
|
ZoomOut();
|
|
break;
|
|
|
|
case ID_SELECTCMD:
|
|
if (m_fCanAnnotate)
|
|
{
|
|
_UpdateButtons(wID);
|
|
}
|
|
break;
|
|
|
|
case ID_CROPCMD:
|
|
if (m_fCanCrop)
|
|
{
|
|
m_rectCropping.SetRectEmpty();
|
|
_UpdateButtons(wID);
|
|
}
|
|
break;
|
|
|
|
case ID_ACTUALSIZECMD:
|
|
ActualSize();
|
|
break;
|
|
|
|
case ID_BESTFITCMD:
|
|
BestFit();
|
|
break;
|
|
|
|
case ID_PRINTCMD:
|
|
_RefreshSelection(FALSE);
|
|
_InvokePrintWizard();
|
|
break;
|
|
|
|
case ID_PREVPAGECMD:
|
|
case ID_NEXTPAGECMD:
|
|
_PrevNextPage(ID_NEXTPAGECMD==wID);
|
|
break;
|
|
|
|
case ID_NEXTIMGCMD:
|
|
case ID_PREVIMGCMD:
|
|
_RefreshSelection(FALSE);
|
|
if (WINDOW_MODE == m_dwMode)
|
|
{
|
|
HRESULT hr = _SaveIfDirty(TRUE);
|
|
if (hr != S_FALSE)
|
|
{
|
|
_ShowNextSlide(ID_PREVIMGCMD == wID);
|
|
}
|
|
}
|
|
else if (m_punkSite)
|
|
{
|
|
IFolderView* pfv;
|
|
if (SUCCEEDED(IUnknown_QueryService(m_punkSite, SID_DefView, IID_PPV_ARG(IFolderView, &pfv))))
|
|
{
|
|
int iCurrent, cItems;
|
|
if (SUCCEEDED(pfv->ItemCount(SVGIO_ALLVIEW, &cItems)) && (cItems > 1) &&
|
|
SUCCEEDED(pfv->GetFocusedItem(&iCurrent)))
|
|
{
|
|
int iToSelect = iCurrent + ((ID_PREVIMGCMD == wID) ? -1 : 1);
|
|
if (iToSelect < 0)
|
|
{
|
|
iToSelect = cItems-1;
|
|
}
|
|
else if (iToSelect >= cItems)
|
|
{
|
|
iToSelect = 0;
|
|
}
|
|
|
|
pfv->SelectItem(iToSelect, SVSI_SELECTIONMARK | SVSI_SELECT
|
|
| SVSI_ENSUREVISIBLE | SVSI_DESELECTOTHERS | SVSI_FOCUSED);
|
|
}
|
|
pfv->Release();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ID_FREEHANDCMD:
|
|
case ID_HIGHLIGHTCMD:
|
|
case ID_LINECMD:
|
|
case ID_FRAMECMD:
|
|
case ID_RECTCMD:
|
|
case ID_TEXTCMD:
|
|
case ID_NOTECMD:
|
|
if (m_fCanAnnotate)
|
|
{
|
|
_UpdateButtons(wID);
|
|
}
|
|
break;
|
|
|
|
case ID_PROPERTIESCMD:
|
|
_UpdateButtons(wID);
|
|
_PropertiesCmd();
|
|
break;
|
|
|
|
case ID_SAVEASCMD:
|
|
_UpdateButtons(wID);
|
|
_SaveAsCmd();
|
|
break;
|
|
|
|
case ID_EDITCMD:
|
|
_UpdateButtons(wID);
|
|
if (m_fCanAnnotate && m_fAnnotating)
|
|
{
|
|
_StartEditing();
|
|
}
|
|
break;
|
|
|
|
case ID_HELPCMD:
|
|
_UpdateButtons(wID);
|
|
HtmlHelp(::GetDesktopWindow(), HTMLHELP_FILENAME, HH_DISPLAY_TOPIC, NULL);
|
|
break;
|
|
|
|
case ID_OPENCMD:
|
|
_UpdateButtons(wID);
|
|
_OpenCmd();
|
|
break;
|
|
|
|
case ID_DELETECMD:
|
|
_UpdateButtons(wID);
|
|
if (m_fCanAnnotate && m_fAnnotating && DPA_GetPtrCount(m_hdpaSelectedAnnotations) > 0)
|
|
{
|
|
_RemoveAnnotatingSelection();
|
|
}
|
|
else
|
|
{
|
|
_DeleteCurrentSlide();
|
|
}
|
|
break;
|
|
case ID_SLIDESHOWCMD:
|
|
if (!m_fFirstTime) // don't try to do this while the namespace walk is ongoing
|
|
{
|
|
StartSlideShow(NULL);
|
|
}
|
|
break;
|
|
}
|
|
m_fIgnoreAllNotifies = FALSE;
|
|
return 0;
|
|
}
|
|
|
|
|
|
// OnEditCommand
|
|
//
|
|
// Handles picture editting(rotate/flip/save etc) WM_COMMAND messages
|
|
|
|
LRESULT CPreviewWnd::OnEditCommand(WORD , WORD wID, HWND , BOOL& )
|
|
{
|
|
m_fIgnoreAllNotifies = TRUE;
|
|
switch (wID)
|
|
{
|
|
case ID_ROTATE90CMD:
|
|
Rotate(90);
|
|
break;
|
|
|
|
case ID_ROTATE270CMD:
|
|
Rotate(270);
|
|
break;
|
|
}
|
|
m_fIgnoreAllNotifies = FALSE;
|
|
return 0;
|
|
}
|
|
|
|
LRESULT CPreviewWnd::OnPositionCommand(WORD wNotifyCode, WORD wID, HWND hwnd, BOOL& fHandled)
|
|
{
|
|
if (SLIDESHOW_MODE == m_dwMode)
|
|
{
|
|
switch (wID)
|
|
{
|
|
case ID_NUDGELEFTCMD:
|
|
OnSlideshowCommand(0, ID_PREVCMD, NULL, fHandled);
|
|
break;
|
|
case ID_NUDGERIGHTCMD:
|
|
OnSlideshowCommand(0, ID_NEXTCMD, NULL, fHandled);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (DPA_GetPtrCount(m_hdpaSelectedAnnotations) == 0)
|
|
{
|
|
BOOL bDummy;
|
|
switch (wID)
|
|
{
|
|
case ID_MOVELEFTCMD:
|
|
m_ctlPreview.OnScroll(WM_HSCROLL, SB_PAGEUP, 0, fHandled);
|
|
break;
|
|
case ID_MOVERIGHTCMD:
|
|
m_ctlPreview.OnScroll(WM_HSCROLL, SB_PAGEDOWN, 0, fHandled);
|
|
break;
|
|
case ID_MOVEUPCMD:
|
|
m_ctlPreview.OnScroll(WM_VSCROLL, SB_PAGEUP, 0, fHandled);
|
|
break;
|
|
case ID_MOVEDOWNCMD:
|
|
m_ctlPreview.OnScroll(WM_VSCROLL, SB_PAGEDOWN, 0, fHandled);
|
|
break;
|
|
case ID_NUDGELEFTCMD:
|
|
if (m_ctlPreview.ScrollBarsPresent())
|
|
{
|
|
m_ctlPreview.OnScroll(WM_HSCROLL, SB_LINEUP, 0, fHandled);
|
|
}
|
|
else
|
|
{
|
|
OnToolbarCommand(0, ID_PREVIMGCMD, m_hWnd, bDummy);
|
|
}
|
|
break;
|
|
case ID_NUDGERIGHTCMD:
|
|
if (m_ctlPreview.ScrollBarsPresent())
|
|
{
|
|
m_ctlPreview.OnScroll(WM_HSCROLL, SB_LINEDOWN, 0, fHandled);
|
|
}
|
|
else
|
|
{
|
|
OnToolbarCommand(0, ID_NEXTIMGCMD, m_hWnd, bDummy);
|
|
}
|
|
break;
|
|
case ID_NUDGEUPCMD:
|
|
if (m_ctlPreview.ScrollBarsPresent())
|
|
{
|
|
m_ctlPreview.OnScroll(WM_VSCROLL, SB_LINEUP, 0, fHandled);
|
|
}
|
|
else
|
|
{
|
|
OnToolbarCommand(0, ID_PREVIMGCMD, m_hWnd, bDummy);
|
|
}
|
|
break;
|
|
case ID_NUDGEDOWNCMD:
|
|
if (m_ctlPreview.ScrollBarsPresent())
|
|
{
|
|
m_ctlPreview.OnScroll(WM_VSCROLL, SB_LINEDOWN, 0, fHandled);
|
|
}
|
|
else
|
|
{
|
|
OnToolbarCommand(0, ID_NEXTIMGCMD, m_hWnd, bDummy);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CRect rectImage;
|
|
m_ctlPreview.GetVisibleImageWindowRect(rectImage);
|
|
m_ctlPreview.GetImageFromWindow((LPPOINT)(LPRECT)rectImage, 2);
|
|
rectImage.DeflateRect(5, 5);
|
|
|
|
CSize size(0,0);
|
|
|
|
switch (wID)
|
|
{
|
|
case ID_MOVELEFTCMD:
|
|
size.cx = -25;
|
|
break;
|
|
case ID_MOVERIGHTCMD:
|
|
size.cx = 25;
|
|
break;
|
|
case ID_MOVEUPCMD:
|
|
size.cy = -25;
|
|
break;
|
|
case ID_MOVEDOWNCMD:
|
|
size.cy = 25;
|
|
break;
|
|
case ID_NUDGELEFTCMD:
|
|
size.cx = -1;
|
|
break;
|
|
case ID_NUDGERIGHTCMD:
|
|
size.cx = 1;
|
|
break;
|
|
case ID_NUDGEUPCMD:
|
|
size.cy = -1;
|
|
break;
|
|
case ID_NUDGEDOWNCMD:
|
|
size.cy = 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (size.cx == 0 && size.cy == 0)
|
|
return 0;
|
|
|
|
_UpdateAnnotatingSelection();
|
|
|
|
CRect rect;
|
|
CRect rectNewPos;
|
|
BOOL bValidMove = TRUE;
|
|
for (int i = 0; i < DPA_GetPtrCount(m_hdpaSelectedAnnotations); i++)
|
|
{
|
|
CAnnotation* pAnnotation = (CAnnotation*)DPA_GetPtr(m_hdpaSelectedAnnotations, i);
|
|
pAnnotation->GetRect(rect);
|
|
rect.NormalizeRect();
|
|
rect.OffsetRect(size);
|
|
|
|
if (!rectNewPos.IntersectRect(rectImage, rect))
|
|
bValidMove = FALSE;
|
|
}
|
|
|
|
if (!bValidMove)
|
|
return 0;
|
|
|
|
for (int i = 0; i < DPA_GetPtrCount(m_hdpaSelectedAnnotations); i++)
|
|
{
|
|
CAnnotation* pAnnotation = (CAnnotation*)DPA_GetPtr(m_hdpaSelectedAnnotations, i);
|
|
pAnnotation->Move(size);
|
|
}
|
|
|
|
m_fDirty = TRUE;
|
|
_UpdateAnnotatingSelection();
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
// call SetNewImage when the entire image has changed, call UpdateImage if it's the same image
|
|
// but it needs to be updated
|
|
|
|
void CPreviewWnd::_SetNewImage(CDecodeTask * pData)
|
|
{
|
|
// store the new pointer
|
|
if (m_pImageData)
|
|
{
|
|
_SaveIfDirty();
|
|
// m_pImageData might be NULL after _SaveAsCmd
|
|
ATOMICRELEASE(m_pImageData);
|
|
}
|
|
|
|
m_pImageData = pData;
|
|
|
|
m_fWarnQuietSave = TRUE; // Reset for each image
|
|
m_fWarnNoSave = TRUE;
|
|
|
|
if (!m_pImageData)
|
|
{
|
|
StatusUpdate(IDS_LOADFAILED);
|
|
return;
|
|
}
|
|
|
|
m_pImageData->AddRef();
|
|
|
|
// Even if m_hpal is NULL it is still correct, so we always go ahead and set it.
|
|
m_ctlPreview.SetPalette(m_hpal);
|
|
|
|
if (SLIDESHOW_MODE != m_dwMode)
|
|
{
|
|
// update toolbar state
|
|
_SetMultipageCommands();
|
|
_SetMultiImagesCommands();
|
|
|
|
BOOL fCanAnnotate = _CanAnnotate(m_pImageData);
|
|
_SetAnnotatingCommands(fCanAnnotate);
|
|
|
|
BOOL fCanCrop = _CanCrop(m_pImageData);
|
|
_SetCroppingCommands(fCanCrop);
|
|
|
|
_RefreshSelection(TRUE);
|
|
|
|
_SetEditCommands();
|
|
|
|
m_fPrintable = _VerbExists(TEXT("print"));
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_PRINTCMD, MAKELONG(m_fPrintable, 0));
|
|
// we need to watch non-TIFFs for changes so we can reload.
|
|
// TIFFs are problematic because we allow annotations, and reloading during annotation
|
|
// would suck
|
|
if (CONTROL_MODE != m_dwMode)
|
|
{
|
|
_RegisterForChangeNotify(TRUE);
|
|
}
|
|
}
|
|
|
|
// notify our child
|
|
m_ctlPreview.SetImageData(m_pImageData, TRUE);
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_ACTUALSIZECMD, MAKELONG(TRUE, 0));
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_BESTFITCMD, MAKELONG(FALSE, 0));
|
|
|
|
// update our toolbar
|
|
BOOL fHandled;
|
|
OnSize(0x0, 0, 0, fHandled);
|
|
}
|
|
|
|
// for refreshing the current m_pImageData because it has changed in some
|
|
// way, i.e. it has advanced to the next frame, the next page, or was edited.
|
|
|
|
void CPreviewWnd::_UpdateImage()
|
|
{
|
|
_RefreshSelection(TRUE);
|
|
m_ctlPreview.SetImageData(m_pImageData, FALSE);
|
|
}
|
|
|
|
// Handles slidwshow (pause/resume, next/previous etc) WM_COMMAND messages
|
|
|
|
void CPreviewWnd::TogglePlayState()
|
|
{
|
|
if (!m_fPaused)
|
|
{
|
|
KillTimer(TIMER_SLIDESHOW);
|
|
}
|
|
else
|
|
{
|
|
SetTimer(TIMER_SLIDESHOW, m_uTimeout);
|
|
}
|
|
m_fPaused = !m_fPaused;
|
|
|
|
WPARAM wpCheck, wpUncheck;
|
|
if (m_fPaused)
|
|
{
|
|
wpCheck = ID_PAUSECMD;
|
|
wpUncheck = ID_PLAYCMD;
|
|
}
|
|
else
|
|
{
|
|
wpCheck = ID_PLAYCMD;
|
|
wpUncheck = ID_PAUSECMD;
|
|
}
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, wpCheck, TBSTATE_ENABLED | TBSTATE_CHECKED);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, wpUncheck, TBSTATE_ENABLED);
|
|
}
|
|
|
|
LRESULT CPreviewWnd::OnMenuMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& fHandled)
|
|
{
|
|
if (_pcm3)
|
|
_pcm3->HandleMenuMsg(uMsg, wParam, lParam);
|
|
return 0;
|
|
}
|
|
|
|
LRESULT CPreviewWnd::OnAppCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& fHandled)
|
|
{
|
|
UINT cmd = GET_APPCOMMAND_LPARAM(lParam);
|
|
DWORD dwKeys = GET_KEYSTATE_LPARAM(lParam);
|
|
|
|
switch (cmd)
|
|
{
|
|
case APPCOMMAND_BROWSER_FORWARD:
|
|
if (SLIDESHOW_MODE == m_dwMode)
|
|
OnSlideshowCommand(0, ID_NEXTCMD, NULL, fHandled);
|
|
else
|
|
{
|
|
if ((dwKeys & MK_CONTROL) || (m_pImageData && !m_pImageData->IsMultipage()))
|
|
OnToolbarCommand(0, ID_NEXTIMGCMD, NULL, fHandled);
|
|
else
|
|
NextPage();
|
|
}
|
|
break;
|
|
|
|
case APPCOMMAND_BROWSER_BACKWARD:
|
|
if (SLIDESHOW_MODE == m_dwMode)
|
|
OnSlideshowCommand(0, ID_PREVCMD, NULL, fHandled);
|
|
else
|
|
{
|
|
if ((dwKeys & MK_CONTROL) || (m_pImageData && !m_pImageData->IsMultipage()))
|
|
OnToolbarCommand(0, ID_PREVIMGCMD, NULL, fHandled);
|
|
else
|
|
PreviousPage();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
fHandled = FALSE;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
LRESULT CPreviewWnd::OnSlideshowCommand(WORD wNotifyCode, WORD wID, HWND hwnd, BOOL& fHandled)
|
|
{
|
|
switch (wID)
|
|
{
|
|
case ID_PLAYCMD:
|
|
m_iSSToolbarSelect = 0;
|
|
if (m_fPaused)
|
|
{
|
|
m_fGoBack = FALSE;
|
|
TogglePlayState();
|
|
_ShowNextSlide(m_fGoBack);
|
|
}
|
|
fHandled = TRUE;
|
|
break;
|
|
|
|
case ID_PAUSECMD:
|
|
m_iSSToolbarSelect = 1;
|
|
if (!m_fPaused)
|
|
{
|
|
TogglePlayState();
|
|
}
|
|
fHandled = TRUE;
|
|
break;
|
|
|
|
case ID_NEXTCMD:
|
|
case ID_PREVCMD:
|
|
if (wID == ID_PREVCMD)
|
|
{
|
|
m_iSSToolbarSelect = 3;
|
|
m_fGoBack = TRUE;
|
|
}
|
|
else
|
|
{
|
|
m_iSSToolbarSelect = 4;
|
|
m_fGoBack = FALSE;
|
|
}
|
|
_ShowNextSlide(m_fGoBack);
|
|
fHandled = TRUE;
|
|
break;
|
|
|
|
case ID_CLOSECMD:
|
|
m_iSSToolbarSelect = 6;
|
|
_CloseSlideshowWindow();
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
BOOL CPreviewWnd::CreateToolbar()
|
|
{
|
|
// ensure that the common controls are initialized
|
|
INITCOMMONCONTROLSEX icc;
|
|
icc.dwSize = sizeof(icc);
|
|
icc.dwICC = ICC_BAR_CLASSES;
|
|
InitCommonControlsEx(&icc);
|
|
|
|
return (SLIDESHOW_MODE == m_dwMode) ? _CreateSlideshowToolbar() : _CreateViewerToolbar();
|
|
}
|
|
|
|
static const TBBUTTON c_tbSlideShow[] =
|
|
{
|
|
// override default toolbar width for separators; iBitmap member of
|
|
// TBBUTTON struct is a union of bitmap index & separator width
|
|
|
|
{ 0, ID_PLAYCMD, TBSTATE_ENABLED | TBSTATE_CHECKED, TBSTYLE_CHECKGROUP, {0,0}, 0, 0},
|
|
{ 1, ID_PAUSECMD, TBSTATE_ENABLED, TBSTYLE_CHECKGROUP, {0,0}, 0, 0},
|
|
{ 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, {0,0}, 0, 0},
|
|
{ 2, ID_PREVCMD, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0,0}, 0, 0},
|
|
{ 3, ID_NEXTCMD, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0,0}, 0, 0},
|
|
#if 0
|
|
{ 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, {0,0}, 0, 0},
|
|
{ 5, ID_DELETECMD, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0,0}, 0, 0},
|
|
#endif
|
|
{ 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, {0,0}, 0, 0},
|
|
{ 4, ID_CLOSECMD, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0,0}, 0, 0},
|
|
};
|
|
|
|
BOOL CPreviewWnd::_CreateSlideshowToolbar()
|
|
{
|
|
DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS |
|
|
CCS_NODIVIDER | CCS_NORESIZE |
|
|
TBSTYLE_LIST | TBSTYLE_FLAT | TBSTYLE_TOOLTIPS;
|
|
|
|
HWND hwndTB = CreateWindowEx(WS_EX_TOOLWINDOW, TOOLBARCLASSNAME, NULL, dwStyle, 0, 0, 0, 0,
|
|
m_hWnd, NULL, _Module.GetModuleInstance(), NULL);
|
|
|
|
_InitializeToolbar(hwndTB, IDB_SLIDESHOWTOOLBAR, IDB_SLIDESHOWTOOLBAR_HOT, IDB_SLIDESHOWTOOLBARHIGH, IDB_SLIDESHOWTOOLBARHIGH_HOT);
|
|
|
|
TBBUTTON tbSlideShow[ARRAYSIZE(c_tbSlideShow)];
|
|
memcpy(tbSlideShow, c_tbSlideShow, sizeof(c_tbSlideShow));
|
|
|
|
// Add the buttons, and then set the minimum and maximum button widths.
|
|
::SendMessage(hwndTB, TB_ADDBUTTONS, (UINT)ARRAYSIZE(c_tbSlideShow), (LPARAM)tbSlideShow);
|
|
|
|
LRESULT dwSize = ::SendMessage(hwndTB, TB_GETBUTTONSIZE, 0, 0);
|
|
SIZE size = {0, HIWORD(dwSize)};
|
|
::SendMessage(hwndTB, TB_GETIDEALSIZE, 0, (LPARAM)&size);
|
|
|
|
RECT rcClient;
|
|
RECT rcToolbar = {0, 0, size.cx, size.cy};
|
|
|
|
GetClientRect(&rcClient);
|
|
AdjustWindowRectEx(&rcToolbar, dwStyle, FALSE, WS_EX_TOOLWINDOW);
|
|
::SetWindowPos(hwndTB, HWND_TOP, RECTWIDTH(rcClient)-RECTWIDTH(rcToolbar), 0,
|
|
RECTWIDTH(rcToolbar), RECTHEIGHT(rcToolbar), 0);
|
|
|
|
//> REVIEW This is a feature that CyraR would like to get into Whistler, but it doesn't seem to work. I will investigate more after Beta1
|
|
// LONG lStyle = ::GetWindowLong(hwndTB, GWL_EXSTYLE);
|
|
// ::SetWindowLong(hwndTB, GWL_EXSTYLE, lStyle | WS_EX_LAYERED);
|
|
// if (::SetLayeredWindowAttributes(hwndTB, 0, 0, 0) == 0)
|
|
// {
|
|
// void *lpMsgBuf;
|
|
// ::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
// NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL);
|
|
//
|
|
// MessageBox((LPCTSTR)lpMsgBuf, L"Error", MB_OK | MB_ICONINFORMATION);
|
|
// ::LocalFree(lpMsgBuf);
|
|
// }
|
|
|
|
m_ctlToolbar.SubclassWindow(hwndTB);
|
|
ShowSSToolbar(FALSE, TRUE);
|
|
|
|
return (NULL != hwndTB);
|
|
}
|
|
|
|
enum EViewerToolbarButtons
|
|
{
|
|
PREVIMGPOS = 0,
|
|
NEXTIMGPOS,
|
|
VIEWSEPPOS, // seperator
|
|
|
|
BESTFITPOS,
|
|
ACTUALSIZEPOS,
|
|
SLIDESHOWPOS,
|
|
IMAGECMDSEPPOS, // seperator
|
|
|
|
ZOOMINPOS,
|
|
ZOOMEOUTPOS,
|
|
SELECTPOS,
|
|
CROPPOS,
|
|
|
|
ROTATESEPPOS, // seperator
|
|
ROTATE90POS,
|
|
ROTATE270POS,
|
|
|
|
// these are all TIFF related
|
|
PAGESEPPOS,
|
|
PREVPAGEPOS,
|
|
PAGELISTPOS,
|
|
NEXTPAGEPOS,
|
|
ANNOTATEPOS,
|
|
FREEHANDPOS,
|
|
HIGLIGHTPOS,
|
|
LINEPOS,
|
|
FRAMEPOS,
|
|
RECTPOS,
|
|
TEXTPOS,
|
|
NOTEPOS,
|
|
|
|
PRINTSEPPOS,
|
|
DELETEPOS,
|
|
PRINTPOS,
|
|
PROPERTIESPOS,
|
|
SAVEASPOS,
|
|
OPENPOS,
|
|
|
|
HELPSEPPOS,
|
|
HELPPOS,
|
|
|
|
MAXPOS,
|
|
};
|
|
|
|
static const TBBUTTON c_tbViewer[] =
|
|
{
|
|
// override default toolbar width for separators; iBitmap member of
|
|
// TBBUTTON struct is a union of bitmap index & separator width
|
|
|
|
{ 0, ID_PREVIMGCMD, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0,0}, 0, -1},
|
|
{ 1, ID_NEXTIMGCMD, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0,0}, 0, -1},
|
|
|
|
{ 0, ID_VIEWCMDSEP, TBSTATE_ENABLED, TBSTYLE_SEP, {0,0}, 0, -1},
|
|
{ 5, ID_BESTFITCMD, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0,0}, 0, -1},
|
|
{ 6, ID_ACTUALSIZECMD, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0,0}, 0, -1},
|
|
{ 8, ID_SLIDESHOWCMD, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0,0}, 0, -1},
|
|
|
|
{ 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, {0,0}, 0, -1},
|
|
{ 2, ID_ZOOMINCMD, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0,0}, 0, -1},
|
|
{ 3, ID_ZOOMOUTCMD, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0,0}, 0, -1},
|
|
{ 4, ID_SELECTCMD, TBSTATE_HIDDEN, TBSTYLE_BUTTON, {0,0}, 0, -1},
|
|
{ 23, ID_CROPCMD, TBSTATE_HIDDEN, TBSTYLE_BUTTON, {0,0}, 0, -1},
|
|
|
|
{ 0, ID_ROTATESEP, TBSTATE_ENABLED, TBSTYLE_SEP, {0,0}, 0, -1},
|
|
{ 12, ID_ROTATE90CMD, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0,0}, 0, -1},
|
|
{ 11, ID_ROTATE270CMD, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0,0}, 0, -1},
|
|
|
|
{ 0, ID_PAGECMDSEP, TBSTATE_HIDDEN, TBSTYLE_SEP, {0,0}, 0, -1}, //tiff
|
|
{ 9, ID_PREVPAGECMD, TBSTATE_HIDDEN, TBSTYLE_BUTTON, {0,0}, 0, -1}, //tiff
|
|
{ I_IMAGENONE, ID_PAGELIST, TBSTATE_HIDDEN, BTNS_WHOLEDROPDOWN | BTNS_SHOWTEXT | BTNS_AUTOSIZE, {0,0}, 0, -1},//tiff
|
|
{ 10, ID_NEXTPAGECMD, TBSTATE_HIDDEN, TBSTYLE_BUTTON, {0,0}, 0, -1}, //tiff
|
|
{ 0, ID_ANNOTATESEP, TBSTATE_HIDDEN, TBSTYLE_SEP, {0,0}, 0, -1}, //tiff
|
|
{ 13, ID_FREEHANDCMD, TBSTATE_HIDDEN, TBSTYLE_BUTTON, {0,0}, 0, -1}, //tiff
|
|
{ 14, ID_HIGHLIGHTCMD, TBSTATE_HIDDEN, TBSTYLE_BUTTON, {0,0}, 0, -1}, //tiff
|
|
{ 15, ID_LINECMD, TBSTATE_HIDDEN, TBSTYLE_BUTTON, {0,0}, 0, -1}, //tiff
|
|
{ 16, ID_FRAMECMD, TBSTATE_HIDDEN, TBSTYLE_BUTTON, {0,0}, 0, -1}, //tiff
|
|
{ 17, ID_RECTCMD, TBSTATE_HIDDEN, TBSTYLE_BUTTON, {0,0}, 0, -1}, //tiff
|
|
{ 18, ID_TEXTCMD, TBSTATE_HIDDEN, TBSTYLE_BUTTON, {0,0}, 0, -1}, //tiff
|
|
{ 19, ID_NOTECMD, TBSTATE_HIDDEN, TBSTYLE_BUTTON, {0,0}, 0, -1}, //tiff
|
|
|
|
{ 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, {0,0}, 0, -1},
|
|
{ 25, ID_DELETECMD, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0,0}, 0, -1},
|
|
{ 20, ID_PRINTCMD, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0,0}, 0, -1},
|
|
{ 21, ID_PROPERTIESCMD, TBSTATE_HIDDEN, TBSTYLE_BUTTON, {0,0}, 0, -1},
|
|
{ 22, ID_SAVEASCMD, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0,0}, 0, -1},
|
|
{ 26, ID_OPENCMD, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0,0}, 0, -1},
|
|
|
|
{ 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, {0,0}, 0, -1},
|
|
{ 24, ID_HELPCMD, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0,0}, 0, -1},
|
|
};
|
|
|
|
void CPreviewWnd::_InitializeViewerToolbarButtons(HWND hwndToolbar, const TBBUTTON c_tbbuttons[], size_t c_nButtons, TBBUTTON tbbuttons[], size_t nButtons)
|
|
{
|
|
ASSERT(c_nButtons == nButtons); // Sanity check.
|
|
ASSERT(c_nButtons <= 100); // Sanity check.
|
|
|
|
// Determine if running RTL mirrored and initialize toolbar accordingly.
|
|
if (!m_bRTLMirrored)
|
|
{
|
|
//
|
|
// Init LTR.
|
|
//
|
|
|
|
memcpy(tbbuttons, c_tbbuttons, c_nButtons * sizeof(TBBUTTON));
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Init RTL.
|
|
//
|
|
|
|
// Toolbar window inherits RTL style from parent hwnd, but we don't
|
|
// want full-blown RTL. We do want the icons to have their positions
|
|
// reversed in the toolbar, but we don't want the button bitmaps to
|
|
// get blitted backwards. So we turn of RTL for the toolbar hwnd
|
|
// and do a manual reorder of the buttons in RTL fashion.
|
|
|
|
// Remove RTL style from toolbar hwnd.
|
|
DWORD dwStyle = ::GetWindowLong(hwndToolbar, GWL_EXSTYLE);
|
|
DWORD dwNewStyle = (dwStyle & ~WS_EX_LAYOUTRTL);
|
|
ASSERT(dwStyle != dwNewStyle); // Sanity check.
|
|
::SetWindowLong(hwndToolbar, GWL_EXSTYLE, dwNewStyle);
|
|
|
|
// Reverse toolbar button order.
|
|
size_t iFrom = nButtons - 1;
|
|
size_t iTo = 0;
|
|
while (iTo < iFrom)
|
|
{
|
|
memcpy(&tbbuttons[iTo], &c_tbbuttons[iFrom], sizeof(TBBUTTON));
|
|
memcpy(&tbbuttons[iFrom], &c_tbbuttons[iTo], sizeof(TBBUTTON));
|
|
iFrom--;
|
|
iTo++;
|
|
}
|
|
if (iTo == iFrom)
|
|
{
|
|
memcpy(&tbbuttons[iTo], &c_tbbuttons[iFrom], sizeof(TBBUTTON));
|
|
}
|
|
}
|
|
}
|
|
|
|
inline UINT CPreviewWnd::_IndexOfViewerToolbarButton(EViewerToolbarButtons eButton)
|
|
{
|
|
ASSERT(eButton > 0);
|
|
|
|
if (!m_bRTLMirrored)
|
|
{
|
|
return eButton;
|
|
}
|
|
else
|
|
{
|
|
return MAXPOS - eButton - 1;
|
|
}
|
|
}
|
|
|
|
BOOL CPreviewWnd::_CreateViewerToolbar()
|
|
{
|
|
DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS |
|
|
CCS_NODIVIDER | CCS_NORESIZE |
|
|
TBSTYLE_LIST | TBSTYLE_FLAT | TBSTYLE_TOOLTIPS;
|
|
|
|
HWND hwndTB = CreateWindowEx(0, TOOLBARCLASSNAME, NULL, dwStyle, 0, 0, 0, 0,
|
|
m_hWnd, NULL, _Module.GetModuleInstance(), NULL);
|
|
|
|
_InitializeToolbar(hwndTB, IDB_TOOLBAR, IDB_TOOLBAR_HOT, IDB_TOOLBARHIGH, IDB_TOOLBARHIGH_HOT);
|
|
|
|
TBBUTTON tbbuttons[ARRAYSIZE(c_tbViewer)];
|
|
_InitializeViewerToolbarButtons(hwndTB, c_tbViewer, ARRAYSIZE(c_tbViewer), tbbuttons, ARRAYSIZE(tbbuttons));
|
|
|
|
if (CONTROL_MODE == m_dwMode)
|
|
{
|
|
ASSERT(ID_BESTFITCMD==tbbuttons[_IndexOfViewerToolbarButton(BESTFITPOS)].idCommand);
|
|
tbbuttons[_IndexOfViewerToolbarButton(BESTFITPOS)].fsState = TBSTATE_HIDDEN;
|
|
|
|
ASSERT(ID_ACTUALSIZECMD==tbbuttons[_IndexOfViewerToolbarButton(ACTUALSIZEPOS)].idCommand);
|
|
tbbuttons[_IndexOfViewerToolbarButton(ACTUALSIZEPOS)].fsState = TBSTATE_HIDDEN;
|
|
|
|
ASSERT(ID_SLIDESHOWCMD==tbbuttons[_IndexOfViewerToolbarButton(SLIDESHOWPOS)].idCommand);
|
|
tbbuttons[_IndexOfViewerToolbarButton(SLIDESHOWPOS)].fsState = TBSTATE_HIDDEN;
|
|
|
|
ASSERT(ID_ZOOMINCMD==tbbuttons[_IndexOfViewerToolbarButton(ZOOMINPOS)].idCommand);
|
|
tbbuttons[_IndexOfViewerToolbarButton(ZOOMINPOS)].fsState = TBSTATE_HIDDEN;
|
|
|
|
ASSERT(ID_ZOOMOUTCMD==tbbuttons[_IndexOfViewerToolbarButton(ZOOMEOUTPOS)].idCommand);
|
|
tbbuttons[_IndexOfViewerToolbarButton(ZOOMEOUTPOS)].fsState = TBSTATE_HIDDEN;
|
|
|
|
ASSERT(ID_SAVEASCMD==tbbuttons[_IndexOfViewerToolbarButton(SAVEASPOS)].idCommand);
|
|
tbbuttons[_IndexOfViewerToolbarButton(SAVEASPOS)].fsState = TBSTATE_HIDDEN;
|
|
|
|
ASSERT(ID_DELETECMD==tbbuttons[_IndexOfViewerToolbarButton(DELETEPOS)].idCommand);
|
|
tbbuttons[_IndexOfViewerToolbarButton(DELETEPOS)].fsState = TBSTATE_HIDDEN;
|
|
|
|
ASSERT(ID_OPENCMD==tbbuttons[_IndexOfViewerToolbarButton(OPENPOS)].idCommand);
|
|
tbbuttons[_IndexOfViewerToolbarButton(OPENPOS)].fsState = TBSTATE_HIDDEN;
|
|
|
|
ASSERT(ID_HELPCMD==tbbuttons[_IndexOfViewerToolbarButton(HELPPOS)].idCommand);
|
|
tbbuttons[_IndexOfViewerToolbarButton(HELPPOS)].fsState = TBSTATE_HIDDEN;
|
|
|
|
// remove a few seperators too:
|
|
tbbuttons[_IndexOfViewerToolbarButton(VIEWSEPPOS)].fsState = TBSTATE_HIDDEN;
|
|
tbbuttons[_IndexOfViewerToolbarButton(IMAGECMDSEPPOS)].fsState = TBSTATE_HIDDEN;
|
|
tbbuttons[_IndexOfViewerToolbarButton(PRINTSEPPOS)].fsState = TBSTATE_HIDDEN;
|
|
tbbuttons[_IndexOfViewerToolbarButton(HELPSEPPOS)].fsState = TBSTATE_HIDDEN;
|
|
}
|
|
|
|
if (m_fHidePrintBtn)
|
|
{
|
|
ASSERT(ID_PRINTCMD==tbbuttons[_IndexOfViewerToolbarButton(PRINTPOS)].idCommand);
|
|
tbbuttons[_IndexOfViewerToolbarButton(PRINTPOS)].fsState = TBSTATE_HIDDEN;
|
|
}
|
|
|
|
if (m_fDisableEdit)
|
|
{
|
|
ASSERT(ID_ROTATESEP == tbbuttons[_IndexOfViewerToolbarButton(ROTATESEPPOS)].idCommand);
|
|
tbbuttons[_IndexOfViewerToolbarButton(ROTATESEPPOS)].fsState = TBSTATE_HIDDEN;
|
|
|
|
ASSERT(ID_ROTATE90CMD == tbbuttons[_IndexOfViewerToolbarButton(ROTATE90POS)].idCommand);
|
|
tbbuttons[_IndexOfViewerToolbarButton(ROTATE90POS)].fsState = TBSTATE_HIDDEN;
|
|
|
|
ASSERT(ID_ROTATE270CMD == tbbuttons[_IndexOfViewerToolbarButton(ROTATE270POS)].idCommand);
|
|
tbbuttons[_IndexOfViewerToolbarButton(ROTATE270POS)].fsState = TBSTATE_HIDDEN;
|
|
}
|
|
|
|
if (m_bRTLMirrored)
|
|
{
|
|
UINT uTmp = tbbuttons[_IndexOfViewerToolbarButton(PREVIMGPOS)].iBitmap;
|
|
tbbuttons[_IndexOfViewerToolbarButton(PREVIMGPOS)].iBitmap = tbbuttons[_IndexOfViewerToolbarButton(NEXTIMGPOS)].iBitmap;
|
|
tbbuttons[_IndexOfViewerToolbarButton(NEXTIMGPOS)].iBitmap = uTmp;
|
|
|
|
uTmp = tbbuttons[_IndexOfViewerToolbarButton(PREVPAGEPOS)].iBitmap;
|
|
tbbuttons[_IndexOfViewerToolbarButton(PREVPAGEPOS)].iBitmap = tbbuttons[_IndexOfViewerToolbarButton(NEXTPAGEPOS)].iBitmap;
|
|
tbbuttons[_IndexOfViewerToolbarButton(NEXTPAGEPOS)].iBitmap = uTmp;
|
|
}
|
|
|
|
// Add the buttons, and then set the minimum and maximum button widths.
|
|
::SendMessage(hwndTB, TB_ADDBUTTONS, ARRAYSIZE(tbbuttons), (LPARAM)tbbuttons);
|
|
|
|
// we just created the toolbar so we are now in the hidden state of the multipage buttons.
|
|
m_dwMultiPageMode = MPCMD_HIDDEN;
|
|
m_fCanAnnotate = FALSE;
|
|
m_fCanCrop = FALSE;
|
|
|
|
m_ctlToolbar.SubclassWindow(hwndTB);
|
|
|
|
return (NULL != hwndTB);
|
|
}
|
|
|
|
|
|
void CPreviewWnd::_InitializeToolbar(HWND hwndTB, int idLow, int idLowHot, int idHigh, int idHighHot)
|
|
{
|
|
int cxBitmap = 16, cyBitmap = 16;
|
|
|
|
::SendMessage(hwndTB, CCM_SETVERSION, COMCTL32_VERSION, 0);
|
|
::SendMessage (hwndTB, TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_HIDECLIPPEDBUTTONS | TBSTYLE_EX_MIXEDBUTTONS | TBSTYLE_EX_DOUBLEBUFFER);
|
|
|
|
// Sets the size of the TBBUTTON structure.
|
|
::SendMessage(hwndTB, TB_BUTTONSTRUCTSIZE, sizeof(TBBUTTON), 0);
|
|
|
|
// Set the maximum number of text rows and bitmap size.
|
|
::SendMessage(hwndTB, TB_SETMAXTEXTROWS, 1, 0);
|
|
|
|
int nDepth = SHGetCurColorRes();
|
|
HIMAGELIST himl = ImageList_LoadImage(_Module.GetModuleInstance(),
|
|
(nDepth > 8) ? MAKEINTRESOURCE(idHigh) : MAKEINTRESOURCE(idLow),
|
|
cxBitmap, 0, RGB(0, 255, 0), IMAGE_BITMAP,
|
|
(nDepth > 8) ? LR_CREATEDIBSECTION : LR_DEFAULTCOLOR);
|
|
::SendMessage(hwndTB, TB_SETIMAGELIST, 0, (LPARAM)himl);
|
|
|
|
HIMAGELIST himlHot = ImageList_LoadImage(_Module.GetModuleInstance(),
|
|
(nDepth > 8) ? MAKEINTRESOURCE(idHighHot) : MAKEINTRESOURCE(idLowHot),
|
|
cxBitmap, 0, RGB(0, 255, 0), IMAGE_BITMAP,
|
|
(nDepth > 8) ? LR_CREATEDIBSECTION : LR_DEFAULTCOLOR);
|
|
::SendMessage(hwndTB, TB_SETHOTIMAGELIST, 0, (LPARAM)himlHot);
|
|
|
|
}
|
|
|
|
LRESULT CPreviewWnd::OnPrintClient(UINT , WPARAM wParam, LPARAM lParam, BOOL&)
|
|
{
|
|
COLORREF bgClr = m_ctlPreview.GetBackgroundColor();
|
|
|
|
RECT rcFill;
|
|
GetClientRect(&rcFill);
|
|
SHFillRectClr((HDC)wParam, &rcFill, bgClr);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
LRESULT CPreviewWnd::OnNeedText(int , LPNMHDR pnmh, BOOL&)
|
|
{
|
|
TOOLTIPTEXT *pTTT = (TOOLTIPTEXT *)pnmh;
|
|
|
|
// tooltip text messages have the same string ID as the control ID
|
|
pTTT->lpszText = MAKEINTRESOURCE(pTTT->hdr.idFrom);
|
|
pTTT->hinst = _Module.GetModuleInstance();
|
|
|
|
// Except in Control Mode due to a DUI Web View bug that's too hard to fix for Whistler...
|
|
if (CONTROL_MODE == m_dwMode)
|
|
{
|
|
// keyboard accelerators are broken, so swap IDs around for the few that display them
|
|
static const struct {
|
|
UINT idCommand;
|
|
UINT idsNewName;
|
|
} map[] = {
|
|
{ ID_PRINTCMD, IDS_PRINTCMD },
|
|
{ ID_ROTATE90CMD, IDS_ROTATE90CMD },
|
|
{ ID_ROTATE270CMD, IDS_ROTATE270CMD }};
|
|
|
|
for (int i = 0 ; i < ARRAYSIZE(map) ; i++)
|
|
{
|
|
if (map[i].idCommand == pTTT->hdr.idFrom)
|
|
{
|
|
pTTT->lpszText = MAKEINTRESOURCE(map[i].idsNewName);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
LRESULT CPreviewWnd::OnDropDown(int id, LPNMHDR pnmh, BOOL&)
|
|
{
|
|
LPNMTOOLBAR pnmTB = (LPNMTOOLBAR)pnmh;
|
|
switch (pnmTB->iItem)
|
|
{
|
|
case ID_PAGELIST:
|
|
_DropDownPageList (pnmTB);
|
|
break;
|
|
|
|
default:
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
void CPreviewWnd::_DropDownPageList(LPNMTOOLBAR pnmTB)
|
|
{
|
|
HMENU hmenuPopup = CreatePopupMenu();
|
|
if (hmenuPopup)
|
|
{
|
|
for (DWORD i = 1; i <= m_pImageData->_cImages; i++)
|
|
{
|
|
TCHAR szBuffer[10];
|
|
wnsprintf(szBuffer, ARRAYSIZE(szBuffer), TEXT("%d"), i);
|
|
|
|
MENUITEMINFO mii = {0};
|
|
mii.cbSize = sizeof(mii);
|
|
mii.fMask = MIIM_STRING | MIIM_ID;
|
|
mii.wID = i;
|
|
mii.dwTypeData = szBuffer;
|
|
|
|
InsertMenuItem (hmenuPopup, i-1, TRUE, &mii);
|
|
}
|
|
|
|
RECT rc;
|
|
::SendMessage(pnmTB->hdr.hwndFrom, TB_GETRECT, (WPARAM)pnmTB->iItem, (LPARAM)&rc);
|
|
::MapWindowPoints(pnmTB->hdr.hwndFrom, HWND_DESKTOP, (LPPOINT)&rc, 2);
|
|
|
|
TPMPARAMS tpm = { 0};
|
|
tpm.cbSize = sizeof(TPMPARAMS);
|
|
tpm.rcExclude = rc;
|
|
|
|
BOOL bRet = ::TrackPopupMenuEx(hmenuPopup,
|
|
TPM_RETURNCMD | TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_VERTICAL | TPM_NONOTIFY,
|
|
rc.left, rc.bottom,
|
|
m_hWnd, &tpm);
|
|
if (bRet)
|
|
{
|
|
if (m_fDirty)
|
|
{
|
|
m_ctlPreview.CommitAnnotations();
|
|
}
|
|
m_pImageData->SelectPage((LONG)bRet-1);
|
|
_UpdateImage();
|
|
|
|
_SetMultipageCommands();
|
|
}
|
|
|
|
DestroyMenu(hmenuPopup);
|
|
}
|
|
}
|
|
|
|
void CPreviewWnd::SetNotify(CEvents * pEvents)
|
|
{
|
|
m_pEvents = pEvents;
|
|
}
|
|
|
|
void CPreviewWnd::SetPalette(HPALETTE hpal)
|
|
{
|
|
m_hpal = hpal;
|
|
}
|
|
|
|
BOOL CPreviewWnd::GetPrintable()
|
|
{
|
|
return m_fPrintable;
|
|
}
|
|
|
|
LRESULT CPreviewWnd::OnWheelTurn(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL&)
|
|
{
|
|
// REVIEW: Shouldn't this just be translated into a command?
|
|
|
|
// this message is ALWAYS forwarded to the zoom window
|
|
m_ctlPreview.SendMessage(uMsg, wParam, lParam);
|
|
|
|
// Since we know that the mouse wheel will either ZoomIn or ZoomOut lets update the buttons if we are in Window Mode.
|
|
if (WINDOW_MODE == m_dwMode)
|
|
{
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_ACTUALSIZECMD, MAKELONG(TRUE, 0));
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_BESTFITCMD, MAKELONG(!m_ctlPreview.IsBestFit(), 0));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
BOOL CPreviewWnd::OnNonSlideShowTab()
|
|
{
|
|
BOOL fHandled = FALSE;
|
|
|
|
if ((SLIDESHOW_MODE != m_dwMode) && m_fShowToolbar)
|
|
{
|
|
if (GetFocus() != m_ctlToolbar.m_hWnd)
|
|
{
|
|
m_ctlToolbar.SetFocus();
|
|
m_ctlToolbar.SetActiveWindow();
|
|
m_ctlToolbar.SendMessage(TB_SETHOTITEM, 0, 0);
|
|
m_iSSToolbarSelect = 0;
|
|
fHandled = TRUE;
|
|
}
|
|
}
|
|
|
|
return fHandled;
|
|
}
|
|
|
|
// Forwards WM_KEYUP and WM_KEYDOWN events to the zoom window but only if they are keys
|
|
// that the zoom window cares about.
|
|
// Activates the slideshow toolbar if needed
|
|
LRESULT CPreviewWnd::OnKeyEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& fHandled)
|
|
{
|
|
MSG msg;
|
|
msg.hwnd = m_hWnd;
|
|
msg.message = uMsg;
|
|
msg.wParam = wParam;
|
|
msg.lParam = lParam;
|
|
GetCursorPos (&msg.pt);
|
|
fHandled = FALSE;
|
|
if (SLIDESHOW_MODE == m_dwMode)
|
|
{
|
|
if (WM_KEYDOWN == uMsg)
|
|
{
|
|
switch (wParam)
|
|
{
|
|
case VK_TAB:
|
|
OnSlideshowCommand(0, ID_PAUSECMD, NULL, fHandled);
|
|
ShowSSToolbar(TRUE);
|
|
KillTimer(TIMER_TOOLBAR);
|
|
m_ctlToolbar.SetFocus();
|
|
m_ctlToolbar.SendMessage(TB_SETHOTITEM, 0, 0);
|
|
m_iSSToolbarSelect = 0;
|
|
fHandled = TRUE;
|
|
break;
|
|
case VK_SPACE:
|
|
ShowSSToolbar(!m_fPaused); // if we are unpausing, hide the toolbar if it's shown. if we are pausing, show the toolbar
|
|
OnSlideshowCommand(0, m_fPaused ? ID_PLAYCMD : ID_PAUSECMD, NULL, fHandled);
|
|
break;
|
|
case VK_PRIOR: // PAGEUP
|
|
case VK_UP:
|
|
case VK_LEFT:
|
|
case VK_BACK: // BACKSPACE
|
|
OnSlideshowCommand(0, ID_PREVCMD, NULL, fHandled);
|
|
break;
|
|
case VK_NEXT: // PAGEDOWN
|
|
case VK_RIGHT:
|
|
case VK_DOWN:
|
|
case VK_RETURN: // ENTER
|
|
OnSlideshowCommand(0, ID_NEXTCMD, NULL, fHandled);
|
|
break;
|
|
case VK_DELETE:
|
|
_DeleteCurrentSlide();
|
|
fHandled = TRUE;
|
|
break;
|
|
case 'K':
|
|
if (0x8000 & GetKeyState(VK_CONTROL))
|
|
{
|
|
OnSlideshowCommand(0, ID_PAUSECMD, NULL, fHandled);
|
|
Rotate(90);
|
|
fHandled = TRUE;
|
|
}
|
|
break;
|
|
case 'L':
|
|
if (0x8000 & GetKeyState(VK_CONTROL))
|
|
{
|
|
OnSlideshowCommand(0, ID_PAUSECMD, NULL, fHandled);
|
|
Rotate(270);
|
|
fHandled = TRUE;
|
|
}
|
|
break;
|
|
case VK_ESCAPE:
|
|
PostMessage(m_fExitApp ? WM_QUIT : WM_CLOSE, 0, 0);
|
|
fHandled = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (!TranslateAccelerator(&msg)) // Only translate accelerators in the non-slideshow case
|
|
// Slideshow keys are handled explicitly above
|
|
{
|
|
switch (wParam)
|
|
{
|
|
case VK_SHIFT:
|
|
case VK_CONTROL:
|
|
case VK_PRIOR:
|
|
case VK_NEXT:
|
|
case VK_HOME:
|
|
case VK_END:
|
|
// these are forwarded to the zoom window
|
|
m_ctlPreview.SendMessage(uMsg, wParam, lParam);
|
|
break;
|
|
|
|
case VK_TAB:
|
|
fHandled = OnNonSlideShowTab();
|
|
break;
|
|
|
|
case VK_ESCAPE:
|
|
m_ctlPreview.SetMode(CZoomWnd::MODE_NOACTION);
|
|
_UpdateButtons(NOBUTTON);
|
|
fHandled = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// OnTBKeyEvent
|
|
//
|
|
|
|
LRESULT CPreviewWnd::OnTBKeyEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& fHandled)
|
|
{
|
|
fHandled = FALSE;
|
|
|
|
if (SLIDESHOW_MODE == m_dwMode && m_fToolbarHidden)
|
|
{
|
|
ShowSSToolbar(TRUE);
|
|
m_ctlToolbar.SetFocus();
|
|
m_ctlToolbar.SendMessage(TB_SETHOTITEM, 0, 0);
|
|
m_iSSToolbarSelect = 0;
|
|
fHandled = TRUE;
|
|
}
|
|
else
|
|
{
|
|
switch (wParam)
|
|
{
|
|
case VK_ESCAPE:
|
|
if (WM_KEYDOWN == uMsg)
|
|
{
|
|
if (SLIDESHOW_MODE == m_dwMode)
|
|
{
|
|
PostMessage(m_fExitApp ? WM_QUIT : WM_CLOSE, 0, 0);
|
|
}
|
|
else
|
|
{
|
|
m_ctlPreview.SetMode(CZoomWnd::MODE_NOACTION);
|
|
_UpdateButtons(NOBUTTON);
|
|
SetFocus();
|
|
SetActiveWindow();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case VK_SHIFT:
|
|
case VK_CONTROL:
|
|
case VK_PRIOR:
|
|
case VK_NEXT:
|
|
case VK_HOME:
|
|
case VK_END:
|
|
// these are forwarded to the zoom window
|
|
m_ctlPreview.SendMessage(uMsg, wParam, lParam);
|
|
break;
|
|
|
|
case VK_LEFT:
|
|
case VK_RIGHT:
|
|
if (WM_KEYDOWN == uMsg)
|
|
{
|
|
int iSel = (int)m_ctlToolbar.SendMessage(TB_GETHOTITEM, 0, 0);
|
|
int iSize = (int)m_ctlToolbar.SendMessage(TB_BUTTONCOUNT, 0, 0);
|
|
int iStepSize = (wParam == VK_RIGHT) ? 1 : iSize - 1; // ((pos + (size - 1)) % size) == left by 1
|
|
if (iSel != -1)
|
|
{
|
|
m_iSSToolbarSelect = iSel;
|
|
}
|
|
TBBUTTON tb = {0};
|
|
do
|
|
{
|
|
m_iSSToolbarSelect = (m_iSSToolbarSelect + iStepSize) % iSize;
|
|
m_ctlToolbar.SendMessage(TB_GETBUTTON, m_iSSToolbarSelect, (LPARAM)&tb);
|
|
}
|
|
while ((tb.fsStyle & TBSTYLE_SEP) || (tb.fsState & TBSTATE_HIDDEN) || !(tb.fsState & TBSTATE_ENABLED)); // don't stop on the separators
|
|
m_ctlToolbar.SendMessage(TB_SETHOTITEM, m_iSSToolbarSelect, 0);
|
|
fHandled = TRUE;
|
|
}
|
|
break;
|
|
|
|
case VK_RETURN:
|
|
case VK_SPACE:
|
|
if ((WM_KEYDOWN == uMsg) && (SLIDESHOW_MODE == m_dwMode))
|
|
{
|
|
// to "press" the button, get its command id and sendmessage on it
|
|
// TB_PRESSBUTTON doesn't work here, don't know why.
|
|
// m_ctlToolbar.SendMessage(TB_PRESSBUTTON, m_iSSToolbarSelect, MAKELONG(TRUE, 0));
|
|
TBBUTTON tbbutton;
|
|
if (m_ctlToolbar.SendMessage(TB_GETBUTTON, m_iSSToolbarSelect, (LPARAM)&tbbutton))
|
|
{
|
|
OnSlideshowCommand(0, (WORD)tbbutton.idCommand, NULL, fHandled);
|
|
}
|
|
fHandled = TRUE;
|
|
}
|
|
break;
|
|
|
|
case VK_TAB:
|
|
if ((WM_KEYDOWN == uMsg) && (CONTROL_MODE != m_dwMode))
|
|
{
|
|
// move focus back to the previewwnd
|
|
SetFocus();
|
|
fHandled = TRUE;
|
|
ShowSSToolbar(FALSE);
|
|
SetTimer(TIMER_TOOLBAR, m_uTimeout);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
LRESULT CPreviewWnd::OnSysCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& fHandled)
|
|
{
|
|
fHandled = FALSE;
|
|
|
|
if ((SLIDESHOW_MODE == m_dwMode) &&
|
|
((SC_MONITORPOWER == wParam) || (SC_SCREENSAVE == wParam)))
|
|
{
|
|
fHandled = TRUE;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
HRESULT CPreviewWnd::_PreviewFromStream(IStream *pStream, UINT iItem, BOOL fUpdateCaption)
|
|
{
|
|
IRunnableTask * pTask;
|
|
|
|
if (fUpdateCaption)
|
|
{
|
|
// set the caption here in case decode fails
|
|
STATSTG stat;
|
|
if (SUCCEEDED(pStream->Stat(&stat, 0)))
|
|
{
|
|
SetCaptionInfo(stat.pwcsName);
|
|
CoTaskMemFree(stat.pwcsName);
|
|
}
|
|
else
|
|
{
|
|
SetCaptionInfo(NULL);
|
|
}
|
|
}
|
|
|
|
HRESULT hr = CDecodeTask::Create(pStream, NULL, iItem, m_pImageFactory, m_hWnd, &pTask);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
TASKOWNERID toid;
|
|
GetTaskIDFromMode(GTIDFM_DECODE, m_dwMode, &toid);
|
|
hr = m_pTaskScheduler->AddTask(pTask, toid, 0, ITSAT_DEFAULT_PRIORITY);
|
|
pTask->Release();
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CPreviewWnd::_PreviewFromFile(LPCTSTR pszFile, UINT iItem, BOOL fUpdateCaption)
|
|
{
|
|
IRunnableTask * pTask;
|
|
|
|
if (fUpdateCaption)
|
|
{
|
|
// set the caption here in case decode fails
|
|
SetCaptionInfo(pszFile);
|
|
}
|
|
|
|
HRESULT hr = CDecodeTask::Create(NULL, pszFile, iItem, m_pImageFactory, m_hWnd, &pTask);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
TASKOWNERID toid;
|
|
GetTaskIDFromMode(GTIDFM_DECODE, m_dwMode, &toid);
|
|
hr = m_pTaskScheduler->AddTask(pTask, toid, 0, ITSAT_DEFAULT_PRIORITY);
|
|
pTask->Release();
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
#define SLIDESHOW_CURSOR_NOTBUSY 0x0
|
|
#define SLIDESHOW_CURSOR_BUSY 0x1
|
|
#define SLIDESHOW_CURSOR_HIDDEN 0x2
|
|
#define SLIDESHOW_CURSOR_NORMAL 0x3
|
|
#define SLIDESHOW_CURSOR_CURRENT 0x4
|
|
|
|
void CPreviewWnd::SetCursorState(DWORD dwType)
|
|
{
|
|
switch (dwType)
|
|
{
|
|
case SLIDESHOW_CURSOR_NOTBUSY:
|
|
KillTimer(TIMER_BUSYCURSOR);
|
|
if (m_fBusy) // ignore multiple NOTBUSY, which we receive for the precaching
|
|
{
|
|
m_hCurrent = m_hCurOld;
|
|
SetCursor(m_hCurrent);
|
|
m_fBusy = FALSE;
|
|
}
|
|
break;
|
|
case SLIDESHOW_CURSOR_BUSY:
|
|
if (!m_fBusy)
|
|
{
|
|
m_hCurrent = LoadCursor(NULL, IDC_APPSTARTING);
|
|
m_hCurOld = SetCursor(m_hCurrent);
|
|
m_fBusy = TRUE;
|
|
}
|
|
break;
|
|
case SLIDESHOW_CURSOR_HIDDEN:
|
|
m_hCurOld = NULL;
|
|
if (!m_fBusy)
|
|
{
|
|
m_hCurrent = m_hCurOld;
|
|
SetCursor(m_hCurrent);
|
|
}
|
|
break;
|
|
case SLIDESHOW_CURSOR_NORMAL:
|
|
m_hCurOld = LoadCursor(NULL, IDC_ARROW);
|
|
if (!m_fBusy)
|
|
{
|
|
m_hCurrent = m_hCurOld;
|
|
SetCursor(m_hCurrent);
|
|
}
|
|
break;
|
|
case SLIDESHOW_CURSOR_CURRENT:
|
|
SetCursor(m_hCurrent);
|
|
break;
|
|
}
|
|
}
|
|
|
|
LRESULT CPreviewWnd::OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& fHandled)
|
|
{
|
|
if (TIMER_ANIMATION == wParam)
|
|
{
|
|
KillTimer(TIMER_ANIMATION);
|
|
if (m_pImageData && m_pImageData->IsAnimated() && _ShouldDisplayAnimations()) // might have switched pages between timer calls
|
|
{
|
|
if (m_pImageData->NextFrame())
|
|
{
|
|
SetTimer(TIMER_ANIMATION, m_pImageData->GetDelay());
|
|
|
|
// paint the new image
|
|
_UpdateImage();
|
|
}
|
|
}
|
|
}
|
|
else if (TIMER_DATAOBJECT == wParam)
|
|
{
|
|
KillTimer(TIMER_DATAOBJECT); // on shot timer
|
|
if (_pdtobj)
|
|
{
|
|
PreviewItemsFromUnk(_pdtobj);
|
|
ATOMICRELEASE(_pdtobj);
|
|
}
|
|
}
|
|
else if (TIMER_BUSYCURSOR == wParam)
|
|
{
|
|
SetCursorState(SLIDESHOW_CURSOR_BUSY);
|
|
}
|
|
else if (SLIDESHOW_MODE == m_dwMode)
|
|
{
|
|
if (TIMER_SLIDESHOW == wParam)
|
|
{
|
|
_ShowNextSlide(FALSE); // always go forward?
|
|
}
|
|
else if (TIMER_TOOLBAR == wParam && !m_fIgnoreUITimers)
|
|
{
|
|
KillTimer(TIMER_TOOLBAR);
|
|
ShowSSToolbar(FALSE);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void CPreviewWnd::ShowSSToolbar(BOOL bShow, BOOL fForce /*=FALSE*/)
|
|
{
|
|
if (SLIDESHOW_MODE == m_dwMode)
|
|
{
|
|
if (fForce)
|
|
{
|
|
POINT pt;
|
|
RECT rc;
|
|
|
|
GetCursorPos(&pt);
|
|
GetWindowRect(&rc);
|
|
if (PtInRect(&rc, pt))
|
|
{
|
|
g_x = pt.x;
|
|
g_y = pt.y;
|
|
}
|
|
}
|
|
|
|
if (!bShow)
|
|
{
|
|
if (!m_fToolbarHidden || fForce)
|
|
{
|
|
//AnimateWindow(m_ctlToolbar.m_hWnd, 200, AW_VER_NEGATIVE | AW_SLIDE | AW_HIDE);
|
|
m_ctlToolbar.SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_HIDEWINDOW);
|
|
m_fToolbarHidden = TRUE;
|
|
m_ctlToolbar.SendMessage(TB_SETHOTITEM, -1, 0);
|
|
SetCursorState(SLIDESHOW_CURSOR_HIDDEN);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
KillTimer(TIMER_TOOLBAR);
|
|
if (m_fToolbarHidden || fForce)
|
|
{
|
|
//AnimateWindow(m_ctlToolbar.m_hWnd, 200, AW_VER_POSITIVE | AW_SLIDE | AW_ACTIVATE);
|
|
m_ctlToolbar.SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
|
|
m_fToolbarHidden = FALSE;
|
|
SetCursorState(SLIDESHOW_CURSOR_NORMAL);
|
|
}
|
|
SetTimer(TIMER_TOOLBAR, m_uTimeout);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPreviewWnd::OnDraw(HDC hdc)
|
|
{
|
|
if (m_fCropping)
|
|
{
|
|
CSelectionTracker tracker;
|
|
_SetupCroppingTracker(tracker);
|
|
|
|
CRect rectImage(0, 0, m_ctlPreview.m_cxImage, m_ctlPreview.m_cyImage);
|
|
m_ctlPreview.GetWindowFromImage((LPPOINT)(LPRECT)rectImage, 2);
|
|
|
|
CRect rectCrop = m_rectCropping;
|
|
m_ctlPreview.GetWindowFromImage((LPPOINT)(LPRECT)rectCrop, 2);
|
|
|
|
HRGN hrgn = ::CreateRectRgn(0, 0, 0, 0);
|
|
if (hrgn != NULL)
|
|
{
|
|
HRGN hrgnImage = ::CreateRectRgnIndirect(rectImage);
|
|
if (hrgnImage != NULL)
|
|
{
|
|
HRGN hrgnCrop = ::CreateRectRgnIndirect(rectCrop);
|
|
if (hrgnCrop != NULL)
|
|
{
|
|
if (ERROR != ::CombineRgn(hrgn, hrgnImage, hrgnCrop, RGN_DIFF))
|
|
{
|
|
::InvertRgn(hdc, hrgn);
|
|
}
|
|
::DeleteObject(hrgnCrop);
|
|
}
|
|
::DeleteObject(hrgnImage);
|
|
}
|
|
::DeleteObject(hrgn);
|
|
}
|
|
|
|
tracker.Draw(hdc);
|
|
}
|
|
else
|
|
{
|
|
if (m_fAnnotating)
|
|
{
|
|
if (DPA_GetPtrCount(m_hdpaSelectedAnnotations) > 0)
|
|
{
|
|
CSelectionTracker tracker;
|
|
_SetupAnnotatingTracker(tracker);
|
|
|
|
tracker.Draw(hdc);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPreviewWnd::OnDrawComplete()
|
|
{
|
|
if (SLIDESHOW_MODE == m_dwMode)
|
|
{
|
|
if (!m_fPaused)
|
|
SetTimer(TIMER_SLIDESHOW, m_uTimeout);
|
|
|
|
SetCursorState(SLIDESHOW_CURSOR_NOTBUSY);
|
|
}
|
|
}
|
|
|
|
BOOL CPreviewWnd::OnMouseDown(UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
if (SLIDESHOW_MODE == m_dwMode)
|
|
{
|
|
if (uMsg == WM_LBUTTONDOWN)
|
|
{
|
|
_ShowNextSlide(FALSE); // advance the slide (param is "go back?")
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_fCropping)
|
|
return _OnMouseDownForCropping(uMsg, wParam, lParam);
|
|
else
|
|
return _OnMouseDownForAnnotating(uMsg, wParam, lParam);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL CPreviewWnd::OnMouseMove(UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
if (SLIDESHOW_MODE == m_dwMode)
|
|
{
|
|
int xPos = GET_X_LPARAM(lParam);
|
|
int yPos = GET_Y_LPARAM(lParam);
|
|
int dx = xPos > g_x ? xPos - g_x : g_x - xPos;
|
|
int dy = yPos > g_y ? yPos - g_y : g_y - yPos;
|
|
|
|
if (dx > 10 || dy > 10)
|
|
{
|
|
ShowSSToolbar(TRUE);
|
|
}
|
|
|
|
g_x = xPos;
|
|
g_y = yPos;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
LRESULT CPreviewWnd::OnTBMouseLeave(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& fHandled)
|
|
{
|
|
if (SLIDESHOW_MODE == m_dwMode)
|
|
{
|
|
m_fTBTrack = FALSE;
|
|
ShowSSToolbar(TRUE);
|
|
}
|
|
fHandled = FALSE;
|
|
return 0;
|
|
}
|
|
|
|
LRESULT CPreviewWnd::OnTBMouseMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& fHandled)
|
|
{
|
|
if (SLIDESHOW_MODE == m_dwMode)
|
|
{
|
|
if (!m_fTBTrack)
|
|
{
|
|
TRACKMOUSEEVENT tme;
|
|
|
|
tme.cbSize = sizeof(tme);
|
|
tme.dwFlags = TME_LEAVE;
|
|
tme.hwndTrack = m_ctlToolbar.m_hWnd;
|
|
TrackMouseEvent(&tme);
|
|
|
|
ShowSSToolbar(TRUE);
|
|
KillTimer(TIMER_TOOLBAR); // we keep the toolbar down for as long as mouse is over it
|
|
m_fTBTrack = TRUE;
|
|
}
|
|
}
|
|
fHandled = FALSE;
|
|
return 0;
|
|
}
|
|
|
|
BOOL CPreviewWnd::OnSetCursor(UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
if (SLIDESHOW_MODE == m_dwMode)
|
|
{
|
|
if (m_fToolbarHidden)
|
|
{
|
|
SetCursorState(SLIDESHOW_CURSOR_HIDDEN);
|
|
}
|
|
else
|
|
{
|
|
SetCursorState(SLIDESHOW_CURSOR_NORMAL);
|
|
}
|
|
return TRUE;
|
|
}
|
|
else if (m_fAnnotating)
|
|
{
|
|
if (DPA_GetPtrCount(m_hdpaSelectedAnnotations) > 0)
|
|
{
|
|
CSelectionTracker tracker;
|
|
_SetupAnnotatingTracker(tracker);
|
|
|
|
if (tracker.SetCursor(m_ctlPreview.m_hWnd, lParam))
|
|
return TRUE;
|
|
}
|
|
SetCursor(LoadCursor(NULL, IDC_ARROW));
|
|
return TRUE;
|
|
}
|
|
else if (m_fCropping)
|
|
{
|
|
CSelectionTracker tracker;
|
|
_SetupCroppingTracker(tracker);
|
|
|
|
if (tracker.SetCursor(m_ctlPreview.m_hWnd, lParam))
|
|
return TRUE;
|
|
|
|
SetCursor(LoadCursor(NULL, IDC_ARROW));
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL CPreviewWnd::GetColor(COLORREF * pref)
|
|
{
|
|
*pref = 0; // black
|
|
if (SLIDESHOW_MODE == m_dwMode)
|
|
{
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL CPreviewWnd::OnSetColor(HDC hdc)
|
|
{
|
|
if (SLIDESHOW_MODE == m_dwMode)
|
|
{
|
|
SetBkColor(hdc, 0); // black
|
|
SetTextColor(hdc, 0xffffff); // white
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
// IObjectWithSite
|
|
HRESULT CPreviewWnd::SetSite(IUnknown *punk)
|
|
{
|
|
IUnknown_Set(&m_punkSite, punk);
|
|
if (m_pcwndSlideShow)
|
|
{
|
|
m_pcwndSlideShow->SetSite(punk);
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
// This function take the name of the file being previewed and converts it into a
|
|
// title for the Full Screen Preview window. In converting the title it take into
|
|
// account user preference settings for how to display the filename.
|
|
void CPreviewWnd::SetCaptionInfo(LPCTSTR pszPath)
|
|
{
|
|
TCHAR szTitle[MAX_PATH] = TEXT("");
|
|
TCHAR szDisplayName[MAX_PATH] = TEXT("");
|
|
SHFILEINFO sfi = {0};
|
|
//
|
|
// Default to pszPath for the caption
|
|
// pszPath is non-null before the decode is attempted
|
|
if (pszPath)
|
|
{
|
|
if (SHGetFileInfo(pszPath, 0, &sfi, sizeof(sfi), SHGFI_DISPLAYNAME | SHGFI_USEFILEATTRIBUTES))
|
|
{
|
|
StrCpyN(szTitle, sfi.szDisplayName, ARRAYSIZE(szTitle));
|
|
StrCatBuff(szTitle, TEXT(" - "), ARRAYSIZE(szTitle));
|
|
}
|
|
}
|
|
|
|
TCHAR szApp[64];
|
|
szApp[0] = 0;
|
|
LoadString(_Module.GetModuleInstance(), IDS_PROJNAME, szApp, ARRAYSIZE(szApp));
|
|
StrCatBuff(szTitle, szApp, ARRAYSIZE(szTitle));
|
|
|
|
SetWindowText(szTitle);
|
|
}
|
|
|
|
LRESULT CPreviewWnd::OnDestroy(UINT , WPARAM , LPARAM , BOOL& fHandled)
|
|
{
|
|
RevokeDragDrop(m_hWnd);
|
|
_RegisterForChangeNotify(FALSE);
|
|
FlushBitmapMessages();
|
|
fHandled = FALSE;
|
|
|
|
// Make sure we don't leak icons
|
|
ReplaceWindowIcon(m_hWnd, ICON_SMALL, NULL);
|
|
ReplaceWindowIcon(m_hWnd, ICON_BIG, NULL);
|
|
|
|
// release the image lists used by the toolbar.
|
|
HWND hwndTB = m_ctlToolbar.m_hWnd;
|
|
HIMAGELIST himl = (HIMAGELIST)::SendMessage(hwndTB, TB_GETHOTIMAGELIST, 0, 0);
|
|
::SendMessage(hwndTB, TB_SETHOTIMAGELIST, 0, NULL);
|
|
ImageList_Destroy(himl);
|
|
|
|
himl = (HIMAGELIST)::SendMessage(hwndTB, TB_GETIMAGELIST, 0, 0);
|
|
::SendMessage(hwndTB, TB_SETIMAGELIST, 0, NULL);
|
|
ImageList_Destroy(himl);
|
|
|
|
if (WINDOW_MODE == m_dwMode)
|
|
{
|
|
WINDOWPLACEMENT wp;
|
|
wp.length = sizeof(wp);
|
|
|
|
if (GetWindowPlacement(&wp))
|
|
{
|
|
HKEY hk;
|
|
if (ERROR_SUCCESS == RegCreateKeyEx(HKEY_CURRENT_USER, REGSTR_SHIMGVW, 0, NULL,
|
|
REG_OPTION_NON_VOLATILE, KEY_WRITE,
|
|
NULL, &hk, NULL))
|
|
{
|
|
RegSetValueEx(hk, REGSTR_BOUNDS, NULL, REG_BINARY,
|
|
(BYTE*)&wp.rcNormalPosition, sizeof(wp.rcNormalPosition));
|
|
|
|
BOOL fIsMaximized = (wp.showCmd == SW_SHOWMAXIMIZED);
|
|
RegSetValueEx(hk, REGSTR_MAXIMIZED, NULL, REG_BINARY,
|
|
(BYTE*)&fIsMaximized, sizeof(fIsMaximized));
|
|
|
|
RegCloseKey(hk);
|
|
}
|
|
}
|
|
|
|
PostQuitMessage(0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
HRESULT CPreviewWnd::GetCurrentIDList(LPITEMIDLIST *ppidl)
|
|
{
|
|
HRESULT hr = _GetItem(m_iCurSlide, ppidl);
|
|
if (FAILED(hr))
|
|
{
|
|
TCHAR szPath[MAX_PATH];
|
|
hr = PathFromImageData(szPath, ARRAYSIZE(szPath));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = SHILCreateFromPath(szPath, ppidl, NULL);
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
void CPreviewWnd::MenuPoint(LPARAM lParam, int *px, int *py)
|
|
{
|
|
if (-1 == lParam)
|
|
{
|
|
// message is from the keyboard, figure out where to place the window
|
|
RECT rc;
|
|
::GetWindowRect(m_hWnd, &rc);
|
|
*px = ((rc.left + rc.right) / 2);
|
|
*py = ((rc.top + rc.bottom) / 2);
|
|
}
|
|
else
|
|
{
|
|
*px = GET_X_LPARAM(lParam);
|
|
*py = GET_Y_LPARAM(lParam);
|
|
}
|
|
}
|
|
|
|
#define ID_FIRST 1 // Context Menu ID's
|
|
#define ID_LAST 0x7fff
|
|
|
|
LRESULT CPreviewWnd::OnContextMenu(UINT , WPARAM wParam, LPARAM lParam, BOOL& fHandled)
|
|
{
|
|
if (!m_fAllowContextMenu)
|
|
return 0;
|
|
|
|
if (m_fCanAnnotate && m_fAnnotating && DPA_GetPtrCount(m_hdpaSelectedAnnotations) > 0)
|
|
{
|
|
HMENU hpopup = CreatePopupMenu();
|
|
if (hpopup)
|
|
{
|
|
CComBSTR bstrDelete;
|
|
CComBSTR bstrProperties;
|
|
if (bstrDelete.LoadString(IDS_DELETECMD) &&
|
|
bstrProperties.LoadString(IDS_PROPERTIESCMD))
|
|
{
|
|
if (AppendMenu(hpopup, MF_STRING, ID_DELETECMD, bstrDelete) &&
|
|
AppendMenu(hpopup, MF_STRING, ID_PROPERTIESCMD, bstrProperties))
|
|
{
|
|
int x, y;
|
|
MenuPoint(lParam, &x, &y);
|
|
TrackPopupMenu(hpopup, TPM_RIGHTBUTTON | TPM_LEFTALIGN, x, y, 0, m_hWnd, NULL);
|
|
}
|
|
}
|
|
DestroyMenu(hpopup);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
LPITEMIDLIST pidl;
|
|
HRESULT hr = GetCurrentIDList(&pidl); // gets the dynamically generated title for this window
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
IContextMenu *pcm;
|
|
hr = SHGetUIObjectFromFullPIDL(pidl, NULL, IID_PPV_ARG(IContextMenu, &pcm));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
HMENU hpopup = CreatePopupMenu();
|
|
if (hpopup)
|
|
{
|
|
// SetSite required if you want in place navigation
|
|
IUnknown_SetSite(pcm, SAFECAST(this, IServiceProvider *));
|
|
hr = pcm->QueryContextMenu(hpopup, 0, ID_FIRST, ID_LAST, CMF_NORMAL);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
int x, y;
|
|
MenuPoint(lParam, &x, &y);
|
|
ASSERT(_pcm3 == NULL);
|
|
pcm->QueryInterface(IID_PPV_ARG(IContextMenu3, &_pcm3));
|
|
// if there's a separator after "copy", remove it
|
|
UINT uCopy = GetMenuIndexForCanonicalVerb(hpopup, pcm,ID_FIRST, L"copy");
|
|
if (uCopy != -1)
|
|
{
|
|
UINT uState = GetMenuState(hpopup, uCopy+1, MF_BYPOSITION);
|
|
if (-1 != uState && (uState & MF_SEPARATOR))
|
|
{
|
|
RemoveMenu(hpopup, uCopy+1, MF_BYPOSITION);
|
|
}
|
|
}
|
|
ContextMenu_DeleteCommandByName(pcm, hpopup, ID_FIRST, L"link");
|
|
ContextMenu_DeleteCommandByName(pcm, hpopup, ID_FIRST, L"cut");
|
|
ContextMenu_DeleteCommandByName(pcm, hpopup, ID_FIRST, L"copy");
|
|
// the shell may have added a static Preview verb
|
|
ContextMenu_DeleteCommandByName(pcm, hpopup, ID_FIRST, L"open");
|
|
|
|
|
|
|
|
if (!m_fPaused)
|
|
{
|
|
TogglePlayState();
|
|
}
|
|
|
|
if (SLIDESHOW_MODE == m_dwMode)
|
|
{
|
|
m_fIgnoreUITimers = TRUE;
|
|
}
|
|
|
|
int idCmd = TrackPopupMenu(hpopup, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTALIGN,
|
|
x, y, 0, m_hWnd, NULL);
|
|
|
|
ATOMICRELEASE(_pcm3);
|
|
|
|
if (idCmd > 0)
|
|
{
|
|
CMINVOKECOMMANDINFO cmdInfo =
|
|
{
|
|
sizeof(cmdInfo),
|
|
0,
|
|
m_hWnd,
|
|
(LPSTR)MAKEINTRESOURCE(idCmd - ID_FIRST),
|
|
NULL,
|
|
NULL,
|
|
SW_NORMAL
|
|
};
|
|
TCHAR szCommandString[40] = TEXT("");
|
|
|
|
ContextMenu_GetCommandStringVerb(pcm, idCmd - ID_FIRST, szCommandString, ARRAYSIZE(szCommandString));
|
|
|
|
if (lstrcmpi(szCommandString, TEXT("edit")) == 0)
|
|
{
|
|
hr = _SaveIfDirty(TRUE);
|
|
if (S_OK != hr)
|
|
{
|
|
hr = E_ABORT;
|
|
}
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (lstrcmpi(szCommandString, TEXT("print")) == 0)
|
|
{
|
|
_RefreshSelection(FALSE);
|
|
_InvokePrintWizard();
|
|
}
|
|
else
|
|
{
|
|
hr = pcm->InvokeCommand(&cmdInfo);
|
|
}
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (lstrcmpi(szCommandString, TEXT("delete")) == 0)
|
|
{
|
|
_RemoveFromArray(m_iCurSlide);
|
|
_ShowNextSlide(FALSE);
|
|
}
|
|
else if (lstrcmpi(szCommandString, TEXT("edit")) == 0)
|
|
{
|
|
m_fDirty = FALSE;
|
|
m_fNoRestore = TRUE;
|
|
|
|
// RAID 414238: Image Preview Control
|
|
// Context menu "&Edit" causes image preview window
|
|
// to close, wrecking Explorer when in 'control mode'.
|
|
if (m_dwMode != CONTROL_MODE)
|
|
{
|
|
PostMessage(WM_CLOSE, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SLIDESHOW_MODE == m_dwMode)
|
|
{
|
|
SetTimer(TIMER_TOOLBAR, m_uTimeout);
|
|
m_fIgnoreUITimers = FALSE;
|
|
}
|
|
}
|
|
IUnknown_SetSite(pcm, NULL);
|
|
DestroyMenu(hpopup);
|
|
}
|
|
pcm->Release();
|
|
}
|
|
ILFree(pidl);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int ClearCB(void *p, void *pData)
|
|
{
|
|
SHFree(p);
|
|
return 1;
|
|
}
|
|
|
|
void CPreviewWnd::_ClearDPA()
|
|
{
|
|
if (m_ppidls)
|
|
{
|
|
for (UINT i = 0; i < m_cItems; i++)
|
|
ILFree(m_ppidls[i]);
|
|
|
|
CoTaskMemFree(m_ppidls);
|
|
m_ppidls = NULL;
|
|
}
|
|
m_cItems = 0;
|
|
m_iCurSlide = 0;
|
|
}
|
|
|
|
HRESULT CPreviewWnd::SetWallpaper(BSTR pszPath)
|
|
{
|
|
return SUCCEEDED(SetWallpaperHelper(pszPath)) ? S_OK : S_FALSE;
|
|
}
|
|
|
|
|
|
HRESULT CPreviewWnd::StartSlideShow(IUnknown *punkToView)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
|
|
if (NULL == punkToView)
|
|
punkToView = m_punkSite;
|
|
|
|
if (SLIDESHOW_MODE == m_dwMode)
|
|
{
|
|
// these are required for slideshow
|
|
KillTimer(TIMER_SLIDESHOW);
|
|
SetCursorState(SLIDESHOW_CURSOR_HIDDEN);
|
|
|
|
m_fGoBack = FALSE;
|
|
// if slide show was reopened cancel any previous tracking
|
|
TRACKMOUSEEVENT tme = {0};
|
|
tme.cbSize = sizeof(tme);
|
|
tme.dwFlags = TME_CANCEL | TME_LEAVE;
|
|
tme.hwndTrack = m_ctlToolbar.m_hWnd;
|
|
TrackMouseEvent(&tme);
|
|
|
|
m_fTBTrack = FALSE;
|
|
|
|
if (punkToView)
|
|
hr = PreviewItemsFromUnk(punkToView);
|
|
else
|
|
hr = _PreviewItem(m_iCurSlide);
|
|
|
|
if (SUCCEEDED(hr))
|
|
m_fPaused = FALSE;
|
|
}
|
|
else
|
|
{
|
|
//create the slide show window
|
|
|
|
// Full Screen
|
|
if (m_pcwndSlideShow && m_pcwndSlideShow->m_hWnd)
|
|
{
|
|
RestoreAndActivate(m_pcwndSlideShow->m_hWnd);
|
|
}
|
|
else
|
|
{
|
|
// create the window
|
|
if (!m_pcwndSlideShow)
|
|
{
|
|
m_pcwndSlideShow = new CPreviewWnd();
|
|
if (!m_pcwndSlideShow)
|
|
{
|
|
// out of memory
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
else
|
|
{
|
|
if (FAILED(m_pcwndSlideShow->Initialize(this, SLIDESHOW_MODE, FALSE)))
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_pcwndSlideShow->m_iCurSlide = m_iCurSlide; // so the slide show stays in sync
|
|
|
|
RECT rc = { 0,0,GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)};
|
|
m_pcwndSlideShow->Create(NULL, rc, NULL, WS_VISIBLE | WS_POPUP);
|
|
}
|
|
|
|
hr = m_pcwndSlideShow->StartSlideShow(NULL);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CPreviewWnd::_GetItem(UINT iItem, LPITEMIDLIST *ppidl)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
if (iItem < m_cItems)
|
|
{
|
|
hr = SHILClone(m_ppidls[iItem], ppidl);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
void CPreviewWnd::_RemoveFromArray(UINT iItem)
|
|
{
|
|
if (iItem < m_cItems)
|
|
{
|
|
ILFree(m_ppidls[iItem]); // this one is now gone
|
|
|
|
// slide all other pidls down in the array
|
|
for (UINT i = iItem + 1; i < m_cItems; i++)
|
|
{
|
|
m_ppidls[i - 1] = m_ppidls[i];
|
|
}
|
|
m_cItems--;
|
|
m_ppidls[m_cItems] = NULL; // make sure stale ptr is now NULL
|
|
|
|
// if we deleted an item before m_iCurSlide then we must adjust m_iCurSlide
|
|
if (iItem < m_iCurSlide)
|
|
{
|
|
m_iCurSlide--;
|
|
}
|
|
else if (m_iCurSlide == m_cItems)
|
|
{
|
|
m_iCurSlide = 0;
|
|
}
|
|
// Now prepare for "ShowNextSlide"
|
|
if (!m_iCurSlide)
|
|
{
|
|
m_iCurSlide = m_cItems ? m_cItems-1 : 0;
|
|
}
|
|
else
|
|
{
|
|
m_iCurSlide--;
|
|
}
|
|
// make sure the prefetch task has the right index
|
|
if (m_pNextImageData)
|
|
{
|
|
if (!(m_pNextImageData->_iItem) && iItem && m_cItems)
|
|
{
|
|
m_pNextImageData->_iItem = m_cItems-1;
|
|
}
|
|
else if (m_pNextImageData->_iItem > iItem)
|
|
{
|
|
m_pNextImageData->_iItem--;
|
|
}
|
|
else
|
|
{
|
|
FlushBitmapMessages();
|
|
ATOMICRELEASE(m_pNextImageData);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
HRESULT CPreviewWnd::_DeleteCurrentSlide()
|
|
{
|
|
LPITEMIDLIST pidl;
|
|
HRESULT hr = _GetItem(m_iCurSlide, &pidl);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
TCHAR szPath[MAX_PATH + 1] = {0}; // +1 and zero init for dbl NULL terminate extra terminator
|
|
DWORD dwAttribs = SFGAO_FILESYSTEM | SFGAO_STREAM;
|
|
hr = SHGetNameAndFlags(pidl, SHGDN_FORPARSING, szPath, ARRAYSIZE(szPath)-1, &dwAttribs);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
BOOL fSuccess = TRUE;
|
|
if (dwAttribs & SFGAO_FILESYSTEM)
|
|
{
|
|
SHFILEOPSTRUCT fo = {0};
|
|
fo.hwnd = m_hWnd;
|
|
fo.wFunc = FO_DELETE;
|
|
fo.pFrom = szPath;
|
|
fo.fFlags = FOF_ALLOWUNDO;
|
|
fo.fAnyOperationsAborted = FALSE;
|
|
if (SHFileOperation(&fo) == ERROR_SUCCESS)
|
|
{
|
|
if (fo.fAnyOperationsAborted == TRUE)
|
|
fSuccess = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_InvokeVerb(TEXT("delete"));
|
|
// We have to assume success since there is no way to know if the user
|
|
// cancelled the confirmation dialog without hitting the camera again.
|
|
}
|
|
|
|
if (fSuccess)
|
|
{
|
|
m_fDirty = FALSE;
|
|
_RemoveFromArray(m_iCurSlide);
|
|
_ShowNextSlide(FALSE);
|
|
}
|
|
}
|
|
|
|
ILFree(pidl);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
// index can be either current or next slide so that user can click multiple times on next/prev button
|
|
HRESULT CPreviewWnd::_ShowNextSlide(BOOL bGoBack)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
|
|
if (m_cItems)
|
|
{
|
|
if (bGoBack)
|
|
{
|
|
if (m_iCurSlide)
|
|
m_iCurSlide--;
|
|
else
|
|
m_iCurSlide = m_cItems - 1;
|
|
}
|
|
else
|
|
{
|
|
m_iCurSlide++;
|
|
if (m_iCurSlide >= m_cItems)
|
|
m_iCurSlide = 0;
|
|
}
|
|
|
|
|
|
if (!m_fPaused)
|
|
{
|
|
SetTimer(TIMER_SLIDESHOW, m_uTimeout);
|
|
}
|
|
SetTimer(TIMER_BUSYCURSOR, 500);
|
|
|
|
LPITEMIDLIST pidl;
|
|
// set the caption in case the load fails
|
|
if (SUCCEEDED(_GetItem(m_iCurSlide, &pidl)))
|
|
{
|
|
TCHAR szPath[MAX_PATH];
|
|
if (SUCCEEDED(SHGetNameAndFlags(pidl, SHGDN_FORPARSING | SHGDN_INFOLDER, szPath, ARRAYSIZE(szPath)-1, NULL)))
|
|
{
|
|
SetCaptionInfo(szPath);
|
|
}
|
|
else
|
|
{
|
|
SetCaptionInfo(NULL);
|
|
}
|
|
ILFree(pidl);
|
|
}
|
|
hr = _PreviewItem(m_iCurSlide);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
_PreLoadItem((m_iCurSlide + 1) % m_cItems);
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
HRESULT CPreviewWnd::_StartDecode(UINT iItem, BOOL fUpdateCaption)
|
|
{
|
|
LPITEMIDLIST pidl;
|
|
HRESULT hr = _GetItem(iItem, &pidl);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
TCHAR szPath[MAX_PATH];
|
|
DWORD dwAttribs = SFGAO_FILESYSTEM | SFGAO_STREAM;
|
|
hr = SHGetNameAndFlags(pidl, SHGDN_FORPARSING, szPath, ARRAYSIZE(szPath), &dwAttribs);
|
|
if (SUCCEEDED(hr) && (dwAttribs & SFGAO_FILESYSTEM))
|
|
{
|
|
hr = _PreviewFromFile(szPath, iItem, fUpdateCaption);
|
|
}
|
|
else if (dwAttribs & SFGAO_STREAM)
|
|
{
|
|
// this might not be a file system object, try to bind to it via IStream
|
|
IStream *pstrm;
|
|
|
|
hr = SHBindToObject(NULL, IID_X_PPV_ARG(IStream, pidl, &pstrm));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _PreviewFromStream(pstrm, iItem, fUpdateCaption);
|
|
pstrm->Release();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// funky attributes?
|
|
hr = S_FALSE;
|
|
}
|
|
ILFree(pidl);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CPreviewWnd::_PreLoadItem(UINT iItem)
|
|
{
|
|
HRESULT hr = _StartDecode(iItem, FALSE);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_iDecodingNextImage = iItem;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CPreviewWnd::_PreviewItem(UINT iItem)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if ((SLIDESHOW_MODE == m_dwMode) && (0 == m_cItems)) // if no more items, user just deleted the last one, so quit the slideshow
|
|
{
|
|
_CloseSlideshowWindow();
|
|
}
|
|
else
|
|
{
|
|
if (!_TrySetImage())
|
|
{
|
|
// If we are not currently already decoding this item, let's get cranking!
|
|
if (m_iDecodingNextImage != iItem)
|
|
{
|
|
hr = _StartDecode(iItem, TRUE);
|
|
}
|
|
|
|
StatusUpdate((S_OK == hr) ? IDS_LOADING : IDS_LOADFAILED);
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
int CPreviewWnd::TranslateAccelerator(MSG *pmsg)
|
|
{
|
|
if (IsVK_TABCycler(pmsg))
|
|
{
|
|
if (OnNonSlideShowTab())
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
else if (m_haccel)
|
|
{
|
|
ASSERT(m_hWnd);
|
|
return ::TranslateAccelerator(m_hWnd, m_haccel, pmsg);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
// 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. This should invalidate
|
|
// and free any left over bitmap and the cached copy of the previous m_ImgCtx
|
|
void CPreviewWnd::StatusUpdate(int iStatus)
|
|
{
|
|
if (m_pImageData)
|
|
{
|
|
m_pImageData->Release();
|
|
m_pImageData = NULL;
|
|
}
|
|
|
|
//
|
|
// the caption is set at the first attempt to load an image
|
|
m_ctlPreview.StatusUpdate(iStatus);
|
|
|
|
_SetMultipageCommands();
|
|
_SetMultiImagesCommands();
|
|
_SetAnnotatingCommands(FALSE);
|
|
_SetEditCommands();
|
|
|
|
m_fPrintable = FALSE;
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_PRINTCMD, MAKELONG(m_fPrintable, 0));
|
|
|
|
// update our toolbar
|
|
BOOL fHandled;
|
|
OnSize(0x0, 0, 0, fHandled);
|
|
}
|
|
|
|
// Return:
|
|
// S_OK walk succeeded, image files found to preview: display new images in preview
|
|
// S_FALSE walk cancelled (by user): display existing image in preview (no change)
|
|
// E_XXXX walk failed: display no image in preview
|
|
//
|
|
HRESULT CPreviewWnd::WalkItemsToPreview(IUnknown* punk)
|
|
{
|
|
HRESULT hr = _SaveIfDirty(TRUE);
|
|
if (FAILED(hr) || hr == S_FALSE)
|
|
return hr;
|
|
|
|
|
|
m_fFirstItem = TRUE;
|
|
|
|
|
|
_ClearDPA(); // clean up old stuff
|
|
|
|
INamespaceWalk *pnsw;
|
|
hr = CoCreateInstance(CLSID_NamespaceWalker, NULL, CLSCTX_INPROC, IID_PPV_ARG(INamespaceWalk, &pnsw));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// in control mode we only dislay one item at a time. lets setup our
|
|
// state so this can work just like the other modes
|
|
DWORD dwFlags = (CONTROL_MODE == m_dwMode) ? 0 : NSWF_ONE_IMPLIES_ALL | NSWF_NONE_IMPLIES_ALL;
|
|
m_fClosed = FALSE;
|
|
hr = pnsw->Walk(punk, dwFlags, m_cWalkDepth, SAFECAST(this, INamespaceWalkCB *));
|
|
// the window might have been closed during the namespace walk
|
|
if (WINDOW_MODE == m_dwMode && m_fClosed)
|
|
{
|
|
hr = E_FAIL;
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pnsw->GetIDArrayResult(&m_cItems, &m_ppidls);
|
|
if (SUCCEEDED(hr) && (m_dwMode == WINDOW_MODE) && m_cItems && m_fFirstTime)
|
|
{
|
|
m_fFirstTime = FALSE;
|
|
SHAddToRecentDocs(SHARD_PIDL, m_ppidls[0]);
|
|
}
|
|
}
|
|
pnsw->Release();
|
|
}
|
|
|
|
|
|
// Clarification of INamespaceWalk return values:
|
|
// S_OK walk has succeeded, and image files found to preview
|
|
// S_FALSE walk has succeeded, but no image files found to preview
|
|
// **** convert to E_FAIL to keep in line with return of function
|
|
// E_XXXX walk has failed
|
|
//
|
|
return hr == S_FALSE ? E_FAIL : hr;
|
|
}
|
|
|
|
void CPreviewWnd::PreviewItems()
|
|
{
|
|
if (WINDOW_MODE == m_dwMode)
|
|
{
|
|
RestoreAndActivate(m_hWnd);
|
|
}
|
|
_PreviewItem(0);
|
|
if (SLIDESHOW_MODE == m_dwMode)
|
|
{
|
|
if (m_cItems > 1)
|
|
{
|
|
_PreLoadItem(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// build the _ppidl and m_cItems members and preview the first one
|
|
HRESULT CPreviewWnd::PreviewItemsFromUnk(IUnknown *punk)
|
|
{
|
|
HRESULT hr = WalkItemsToPreview(punk);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (S_FALSE != hr)
|
|
PreviewItems();
|
|
}
|
|
else
|
|
{
|
|
StatusUpdate(IDS_LOADFAILED);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
// If the "new" image is the same as the old image, and the old image was recently edited then we assume that
|
|
// the reason we are getting a new ShowFile request is due to an FSChangeNotify on the file. We also assume
|
|
// that we are the cause of this change notify. Further we assume that we already have the correctly decoded
|
|
// image still ready. These are assumptions which might not be TRUE 100%, in which case we will do really
|
|
// strange things, but they should be TRUE 99.9% of the time which is considered "good enough". The reason we
|
|
// make these dangerous assumptions is to prevent decoding the image again and thus flickering between the
|
|
// old image, the "generating preview..." message, and the new (identical) image.
|
|
|
|
BOOL CPreviewWnd::_ReShowingSameFile(LPCTSTR pszNewFile)
|
|
{
|
|
BOOL bIsSameFile = FALSE;
|
|
if (m_pImageData)
|
|
{
|
|
if (pszNewFile && m_fWasEdited)
|
|
{
|
|
m_fWasEdited = FALSE;
|
|
|
|
TCHAR szOldPath[MAX_PATH];
|
|
if ((S_OK == PathFromImageData(szOldPath, ARRAYSIZE(szOldPath))) &&
|
|
(0 == StrCmpI(szOldPath, pszNewFile)))
|
|
{
|
|
if (m_pEvents)
|
|
m_pEvents->OnPreviewReady();
|
|
|
|
bIsSameFile = TRUE;
|
|
}
|
|
}
|
|
|
|
if (!bIsSameFile)
|
|
{
|
|
m_pImageData->Release(); // need to start clean
|
|
m_pImageData = NULL;
|
|
}
|
|
}
|
|
return bIsSameFile;
|
|
}
|
|
|
|
// pszFile may be NULL. cItems expresses how many are selected so we can
|
|
// display "multiple items selected" and not display anything.
|
|
|
|
LRESULT CPreviewWnd::ShowFile(LPCTSTR pszFile, UINT cItems, BOOL fReshow)
|
|
{
|
|
if (!m_hWnd)
|
|
return S_FALSE;
|
|
|
|
HRESULT hr = S_FALSE;
|
|
|
|
TCHAR szLongName[MAX_PATH]; // short file names are UGLY
|
|
if (GetLongPathName(pszFile, szLongName, ARRAYSIZE(szLongName)))
|
|
{
|
|
pszFile = szLongName;
|
|
}
|
|
|
|
if (!fReshow && _ReShowingSameFile(pszFile))
|
|
return S_FALSE;
|
|
|
|
// It is possible that there is already a bitmap message in our queue from the previous rendering.
|
|
// If this is the case we should remove that message and release its bitmap before we continue.
|
|
// If we do not then that message will get processed and will send the OnPreviewReady event to the
|
|
// obejct container but this event might no longer be valid.
|
|
FlushBitmapMessages();
|
|
|
|
if (pszFile && *pszFile)
|
|
{
|
|
IDataObject *pdtobj;
|
|
hr = GetUIObjectFromPath(pszFile, IID_PPV_ARG(IDataObject, &pdtobj));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = PreviewItemsFromUnk(pdtobj);
|
|
m_fPaused = TRUE;
|
|
pdtobj->Release();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int iRetCode = (cItems > 1) ? IDS_MULTISELECT : IDS_NOPREVIEW;
|
|
|
|
// Set the Return Code into all owned zoom windows. This instructs these windows to disregard
|
|
// their previous images and display the status message instead.
|
|
StatusUpdate(iRetCode);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
LRESULT CPreviewWnd::IV_OnIVScroll(UINT , WPARAM , LPARAM lParam, BOOL&)
|
|
{
|
|
DWORD nHCode = LOWORD(lParam);
|
|
DWORD nVCode = HIWORD(lParam);
|
|
if (nHCode)
|
|
{
|
|
m_ctlPreview.SendMessage(WM_HSCROLL, nHCode, NULL);
|
|
}
|
|
if (nVCode)
|
|
{
|
|
m_ctlPreview.SendMessage(WM_VSCROLL, nVCode, NULL);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
// IV_OnSetOptions
|
|
//
|
|
// This message is sent to turn on or off all the optional features of the image preview control.
|
|
// NOTE: When used as a control this function is called BEFORE the window is created. Don't do
|
|
// anything in this function that will fail without a window unless you check for this condition.
|
|
LRESULT CPreviewWnd::IV_OnSetOptions(UINT , WPARAM wParam, LPARAM lParam, BOOL&)
|
|
{
|
|
BOOL bResult = TRUE;
|
|
|
|
// Boolify lParam just to be safe.
|
|
lParam = lParam ? 1:0;
|
|
|
|
switch (wParam)
|
|
{
|
|
case IVO_TOOLBAR:
|
|
if ((BOOL)lParam != m_fShowToolbar)
|
|
{
|
|
m_fShowToolbar = (BOOL)lParam;
|
|
if (m_hWnd)
|
|
{
|
|
if (m_fShowToolbar)
|
|
{
|
|
if (!m_ctlToolbar)
|
|
{
|
|
bResult = CreateToolbar();
|
|
if (!bResult)
|
|
{
|
|
m_fShowToolbar = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_ctlToolbar)
|
|
{
|
|
m_ctlToolbar.DestroyWindow();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case IVO_PRINTBTN:
|
|
if ((BOOL)lParam != m_fHidePrintBtn)
|
|
{
|
|
m_fHidePrintBtn = (BOOL)lParam;
|
|
if (m_hWnd && m_ctlToolbar)
|
|
{
|
|
m_ctlToolbar.SendMessage(TB_HIDEBUTTON,ID_PRINTCMD,lParam);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case IVO_CONTEXTMENU:
|
|
m_fAllowContextMenu = (BOOL)lParam;
|
|
break;
|
|
|
|
case IVO_PRINTABLE:
|
|
TraceMsg(TF_WARNING, "Obsolete IVO_PRINTABLE option received.");
|
|
break;
|
|
|
|
case IVO_DISABLEEDIT:
|
|
m_fDisableEdit = (BOOL)lParam;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
void CPreviewWnd::_SetEditCommands()
|
|
{
|
|
if (CONTROL_MODE != m_dwMode)
|
|
{
|
|
// We can save if we have a file; the save dialog will show available encoders
|
|
BOOL fCanSave = m_pImageData ? TRUE : FALSE;
|
|
|
|
m_ctlToolbar.SendMessage(TB_HIDEBUTTON, ID_SAVEASCMD, MAKELONG(!fCanSave, 0));
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_SAVEASCMD, MAKELONG(fCanSave, 0));
|
|
}
|
|
|
|
BOOL fCanRotate = m_pImageData != NULL;
|
|
if (CONTROL_MODE != m_dwMode)
|
|
{
|
|
m_ctlToolbar.SendMessage(TB_HIDEBUTTON, ID_ROTATESEP, MAKELONG(!fCanRotate, 0));
|
|
m_ctlToolbar.SendMessage(TB_HIDEBUTTON, ID_ROTATE90CMD, MAKELONG(!fCanRotate, 0));
|
|
m_ctlToolbar.SendMessage(TB_HIDEBUTTON, ID_ROTATE270CMD, MAKELONG(!fCanRotate, 0));
|
|
}
|
|
else
|
|
{
|
|
// we don't rotate multi-page images in control mode
|
|
fCanRotate = fCanRotate && !m_pImageData->IsMultipage();
|
|
}
|
|
|
|
// No matter where we are GDIPlus can't rotate WMF or EMF files. Curiously,
|
|
// we will let you rotate ICO files, but because we don't have an encoder
|
|
// we won't save them :)
|
|
if (fCanRotate)
|
|
{
|
|
fCanRotate = !(IsEqualGUID(ImageFormatWMF, m_pImageData->_guidFormat) ||
|
|
IsEqualGUID(ImageFormatEMF, m_pImageData->_guidFormat) ||
|
|
m_pImageData->IsAnimated());
|
|
}
|
|
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_ROTATESEP, MAKELONG(fCanRotate, 0));
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_ROTATE90CMD, MAKELONG(fCanRotate, 0));
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_ROTATE270CMD, MAKELONG(fCanRotate, 0));
|
|
TCHAR szFile[MAX_PATH];
|
|
BOOL fCanOpen = SUCCEEDED(PathFromImageData(szFile, ARRAYSIZE(szFile)));
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_OPENCMD, MAKELONG(fCanOpen, 0));
|
|
|
|
}
|
|
|
|
void CPreviewWnd::_UpdatePageNumber()
|
|
{
|
|
TCHAR szText[20];
|
|
wnsprintf(szText, ARRAYSIZE(szText), TEXT("%d"), m_pImageData->_iCurrent+1);
|
|
|
|
TBBUTTONINFO bi = {0};
|
|
bi.cbSize = sizeof(bi);
|
|
bi.dwMask = TBIF_TEXT | TBIF_STATE;
|
|
bi.fsState = TBSTATE_ENABLED;
|
|
bi.pszText = szText;
|
|
m_ctlToolbar.SendMessage(TB_SETBUTTONINFO, ID_PAGELIST, (LPARAM)&bi);
|
|
}
|
|
|
|
void CPreviewWnd::_SetMultipageCommands()
|
|
{
|
|
DWORD dwMode;
|
|
|
|
// this code relies on the fact that TIFFs are the only multipage format we view
|
|
if (!m_pImageData || m_pImageData->_guidFormat != ImageFormatTIFF )
|
|
{
|
|
dwMode = MPCMD_HIDDEN;
|
|
}
|
|
else if (!m_pImageData->IsMultipage())
|
|
{
|
|
dwMode = MPCMD_DISABLED;
|
|
}
|
|
else if (m_pImageData->IsFirstPage())
|
|
{
|
|
dwMode = MPCMD_FIRSTPAGE;
|
|
}
|
|
else if (m_pImageData->IsLastPage())
|
|
{
|
|
dwMode = MPCMD_LASTPAGE;
|
|
}
|
|
else
|
|
{
|
|
dwMode = MPCMD_MIDDLEPAGE;
|
|
}
|
|
|
|
// remember which buttons are enabled/hidden so we can quickly create our context menu
|
|
if (dwMode != m_dwMultiPageMode)
|
|
{
|
|
m_dwMultiPageMode = dwMode;
|
|
|
|
if (CONTROL_MODE != m_dwMode)
|
|
{
|
|
// Switch accelerator tables so that Page Up and Page Down work
|
|
if (dwMode == MPCMD_HIDDEN || dwMode == MPCMD_DISABLED)
|
|
{
|
|
m_haccel = LoadAccelerators(_Module.GetModuleInstance(), MAKEINTRESOURCE(IDA_PREVWND_SINGLEPAGE));
|
|
}
|
|
else
|
|
{
|
|
m_haccel = LoadAccelerators(_Module.GetModuleInstance(), MAKEINTRESOURCE(IDA_PREVWND_MULTIPAGE));
|
|
}
|
|
|
|
m_ctlToolbar.SendMessage(TB_HIDEBUTTON, ID_PAGECMDSEP, MAKELONG((MPCMD_HIDDEN==dwMode),0));
|
|
m_ctlToolbar.SendMessage(TB_HIDEBUTTON, ID_PREVPAGECMD, MAKELONG((MPCMD_HIDDEN==dwMode),0));
|
|
m_ctlToolbar.SendMessage(TB_HIDEBUTTON, ID_PAGELIST, MAKELONG((MPCMD_HIDDEN==dwMode),0));
|
|
m_ctlToolbar.SendMessage(TB_HIDEBUTTON, ID_NEXTPAGECMD, MAKELONG((MPCMD_HIDDEN==dwMode),0));
|
|
|
|
if (MPCMD_HIDDEN != dwMode)
|
|
{
|
|
_UpdatePageNumber();
|
|
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_PREVPAGECMD, MAKELONG((MPCMD_FIRSTPAGE!=dwMode && MPCMD_DISABLED!=dwMode),0));
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_NEXTPAGECMD, MAKELONG((MPCMD_LASTPAGE !=dwMode && MPCMD_DISABLED!=dwMode),0));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (CONTROL_MODE != m_dwMode)
|
|
{
|
|
if (dwMode == MPCMD_MIDDLEPAGE)
|
|
{
|
|
_UpdatePageNumber();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPreviewWnd::_SetMultiImagesCommands()
|
|
{
|
|
BOOL bHasFiles = m_cItems;
|
|
if (CONTROL_MODE != m_dwMode)
|
|
{
|
|
m_ctlToolbar.SendMessage(TB_HIDEBUTTON, ID_PREVIMGCMD, MAKELONG(!bHasFiles, 0));
|
|
m_ctlToolbar.SendMessage(TB_HIDEBUTTON, ID_NEXTIMGCMD, MAKELONG(!bHasFiles, 0));
|
|
m_ctlToolbar.SendMessage(TB_HIDEBUTTON, ID_VIEWCMDSEP, MAKELONG(!bHasFiles, 0));
|
|
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_PREVIMGCMD, MAKELONG(bHasFiles, 0));
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_NEXTIMGCMD, MAKELONG(bHasFiles, 0));
|
|
}
|
|
}
|
|
|
|
HRESULT CPreviewWnd::PathFromImageData(LPTSTR pszFile, UINT cch)
|
|
{
|
|
*pszFile = 0;
|
|
|
|
IShellImageData *pSID;
|
|
HRESULT hr = m_pImageData ? m_pImageData->Lock(&pSID) : E_FAIL;
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
IPersistFile *ppf;
|
|
hr = pSID->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
WCHAR *psz;
|
|
hr = ppf->GetCurFile(&psz);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
lstrcpyn(pszFile, psz, cch);
|
|
CoTaskMemFree(psz);
|
|
}
|
|
ppf->Release();
|
|
}
|
|
m_pImageData->Unlock();
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CPreviewWnd::ImageDataSave(LPCTSTR pszFile, BOOL bShowUI)
|
|
{
|
|
IShellImageData * pSID = NULL;
|
|
HRESULT hr = m_pImageData ? m_pImageData->Lock(&pSID) : E_FAIL;
|
|
Image *pimgRestore = NULL;
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
GUID guidFmt = GUID_NULL;
|
|
BOOL bSave = TRUE;
|
|
BOOL bWarnBurn = FALSE;
|
|
BOOL bRestoreParams = FALSE;
|
|
pSID->GetRawDataFormat(&guidFmt);
|
|
// if saving to a jpeg, set the image quality to high
|
|
// if pszFile is NULL, we're saving the same file, so don't promote the image quality
|
|
if (pszFile)
|
|
{
|
|
m_pImageFactory->GetDataFormatFromPath(pszFile, &guidFmt);
|
|
if (guidFmt == ImageFormatJPEG )
|
|
{
|
|
IPropertyBag *ppb;
|
|
if (SUCCEEDED(SHCreatePropertyBagOnMemory(STGM_READWRITE,
|
|
IID_PPV_ARG(IPropertyBag, &ppb))))
|
|
{
|
|
// write the quality value for the recompression into the property bag
|
|
// we have to write the format too...CImageData relies on "all or nothing"
|
|
// from the encoder params property bag
|
|
VARIANT var;
|
|
hr = InitVariantFromGUID(&var, ImageFormatJPEG);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
ppb->Write(SHIMGKEY_RAWFORMAT, &var);
|
|
VariantClear(&var);
|
|
}
|
|
SHPropertyBag_WriteInt(ppb, SHIMGKEY_QUALITY, 100);
|
|
pSID->SetEncoderParams(ppb);
|
|
ppb->Release();
|
|
bRestoreParams = TRUE;
|
|
}
|
|
}
|
|
}
|
|
if (bShowUI && pszFile)
|
|
{
|
|
// warn the user if saving from TIFF to something that will lose annotations
|
|
|
|
BOOL bDestTiff = ImageFormatTIFF == guidFmt;
|
|
BOOL bAnnot = m_ctlPreview.GetAnnotations()->GetCount() > 0;
|
|
bWarnBurn = bAnnot && !bDestTiff;
|
|
|
|
#if 0
|
|
if (!bWarnBurn && S_OK == m_pImageData->IsMultipage() && !bDestTiff)
|
|
{
|
|
GUID guidFmt;
|
|
bWarnBurn = TRUE;
|
|
if (SUCCEEDED(m_pImageFactory->GetDataFormatFromPath(pszFile, &guidFmt)))
|
|
{
|
|
bWarn = !FmtSupportsMultiPage(pSID, &guidFmt);
|
|
}
|
|
}
|
|
#endif // 0 Put the multipage warning back in if needed, and change the wording of IDS_SAVE_WARN_TIFF
|
|
}
|
|
|
|
if (bWarnBurn)
|
|
{
|
|
m_fPromptingUser = TRUE;
|
|
bSave = (IDYES == ShellMessageBox(_Module.GetModuleInstance(), m_hWnd,
|
|
MAKEINTRESOURCE(IDS_SAVE_WARN_TIFF),
|
|
MAKEINTRESOURCE(IDS_PROJNAME),
|
|
MB_YESNO | MB_ICONINFORMATION));
|
|
m_fPromptingUser = FALSE;
|
|
|
|
if (bSave)
|
|
{
|
|
// Save the current image frame to restore after the save to a different file is complete
|
|
pimgRestore = _BurnAnnotations(pSID);
|
|
}
|
|
}
|
|
if (bSave)
|
|
{
|
|
IPersistFile *ppf;
|
|
hr = pSID->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// if saving to the same file name, make sure
|
|
// the changenotify code ignores the notify this will generate
|
|
//
|
|
if (!pszFile)
|
|
{
|
|
m_fIgnoreNextNotify = TRUE;
|
|
}
|
|
hr = ppf->Save(pszFile, FALSE);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_fWasEdited = TRUE;
|
|
}
|
|
else if (!pszFile)
|
|
{
|
|
m_fIgnoreNextNotify = FALSE;
|
|
}
|
|
}
|
|
ppf->Release();
|
|
if (pimgRestore)
|
|
{
|
|
pSID->ReplaceFrame(pimgRestore);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = S_FALSE; // we did nothing
|
|
}
|
|
if (bRestoreParams)
|
|
{
|
|
pSID->SetEncoderParams(NULL);
|
|
}
|
|
m_pImageData->Unlock();
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CPreviewWnd::_SaveAsCmd()
|
|
{
|
|
if (m_pImageData == NULL)
|
|
return S_OK;
|
|
|
|
|
|
OPENFILENAME ofn = {0};
|
|
TCHAR szOrgFile[MAX_PATH];
|
|
TCHAR szExt[MAX_PATH]={0};
|
|
PathFromImageData(szOrgFile, ARRAYSIZE(szOrgFile));
|
|
LPTSTR psz = PathFindExtension(szOrgFile);
|
|
StrCpyN(szExt, psz, ARRAYSIZE(szExt));
|
|
|
|
TCHAR szFile[MAX_PATH];
|
|
if (!m_fDisableEdit && m_fCanSave && m_pImageData->IsEditable())
|
|
{
|
|
// If we haven't explicitly been told not to and the file is writeable then
|
|
// suggest saving on top of the current image
|
|
PathFromImageData(szFile, ARRAYSIZE(szFile));
|
|
}
|
|
else
|
|
{
|
|
// Otherwise suggest New Image.jpg
|
|
LoadString(_Module.GetModuleInstance(), IDS_NEW_FILENAME, szFile, ARRAYSIZE(szFile));
|
|
}
|
|
|
|
CComBSTR bstrTitle;
|
|
bstrTitle.LoadString(IDS_SAVEAS_TITLE);
|
|
|
|
ofn.lStructSize = sizeof(ofn);
|
|
PathRemoveExtension(szFile);
|
|
TCHAR szFilter[MAX_PATH] = TEXT("\0");
|
|
ofn.nFilterIndex = _GetFilterStringForSave(szFilter, ARRAYSIZE(szFilter), szExt);
|
|
ofn.lpstrFilter = szFilter;
|
|
ofn.hwndOwner = m_hWnd;
|
|
ofn.lpstrFile = szFile;
|
|
ofn.lpstrTitle = bstrTitle;
|
|
ofn.nMaxFile = MAX_PATH - lstrlen(szExt);
|
|
ofn.Flags = OFN_EXPLORER | OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST | OFN_ENABLESIZING;
|
|
ofn.lpstrDefExt = *szExt == TEXT('.') ? szExt + 1: szExt;
|
|
|
|
m_fPromptingUser = TRUE;
|
|
BOOL bResult = ::GetSaveFileName(&ofn);
|
|
m_fPromptingUser = FALSE;
|
|
|
|
if (bResult != 0)
|
|
{
|
|
m_ctlPreview.CommitAnnotations();
|
|
HRESULT hr = ImageDataSave(szFile, TRUE);
|
|
if (S_OK == hr)
|
|
{
|
|
if (lstrcmpi(szFile, szOrgFile) == 0)
|
|
{
|
|
_UpdateImage();
|
|
ShowFile(szFile, 1);
|
|
m_fDirty = FALSE;
|
|
}
|
|
}
|
|
else if (FAILED(hr))
|
|
{
|
|
// If we failed to save then we are corrupt and need to be reloaded
|
|
// If we were just copying then only show the message
|
|
if (lstrcmpi(szFile, szOrgFile) == 0)
|
|
{
|
|
_UpdateImage();
|
|
ShowFile(szOrgFile, 1, TRUE);
|
|
m_fDirty = FALSE;
|
|
}
|
|
else
|
|
{
|
|
// delete the failed copy
|
|
DeleteFile(szFile);
|
|
}
|
|
|
|
CComBSTR bstrMsg, bstrTitle;
|
|
|
|
if (bstrMsg.LoadString(IDS_SAVEFAILED_MSGBOX) && bstrTitle.LoadString(IDS_PROJNAME))
|
|
{
|
|
m_fPromptingUser = TRUE;
|
|
MessageBox(bstrMsg, bstrTitle, MB_OK | MB_ICONERROR | MB_APPLMODAL);
|
|
m_fPromptingUser = FALSE;
|
|
return E_FAIL;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DWORD dwResult = CommDlgExtendedError();
|
|
if (dwResult == FNERR_BUFFERTOOSMALL)
|
|
{
|
|
CComBSTR bstrMsg, bstrTitle;
|
|
|
|
if (bstrMsg.LoadString(IDS_NAMETOOLONG_MSGBOX) && bstrTitle.LoadString(IDS_PROJNAME))
|
|
{
|
|
m_fPromptingUser = TRUE;
|
|
MessageBox(bstrMsg, bstrTitle, MB_OK | MB_ICONERROR | MB_APPLMODAL);
|
|
m_fPromptingUser = FALSE;
|
|
}
|
|
}
|
|
return S_FALSE; // User probably cancelled
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
void CPreviewWnd::_PropertiesCmd()
|
|
{
|
|
if (m_fAnnotating && DPA_GetPtrCount(m_hdpaSelectedAnnotations) == 1)
|
|
{
|
|
CAnnotation* pAnnotation = (CAnnotation*)DPA_GetPtr(m_hdpaSelectedAnnotations, 0);
|
|
|
|
if (!pAnnotation->HasWidth() && !pAnnotation->HasTransparent() && !pAnnotation->HasColor() && pAnnotation->HasFont())
|
|
{
|
|
CHOOSEFONT cf = {0};
|
|
|
|
LOGFONT lfFont;
|
|
pAnnotation->GetFont(lfFont);
|
|
COLORREF crFont = pAnnotation->GetFontColor();
|
|
|
|
cf.lStructSize = sizeof(cf);
|
|
cf.hwndOwner = m_hWnd;
|
|
cf.lpLogFont = &lfFont;
|
|
cf.Flags = CF_SCREENFONTS | CF_EFFECTS | CF_INITTOLOGFONTSTRUCT | CF_NOVERTFONTS | CF_NOSCRIPTSEL;
|
|
cf.rgbColors = crFont;
|
|
|
|
m_fPromptingUser = TRUE;
|
|
BOOL bResult = ::ChooseFont(&cf);
|
|
m_fPromptingUser = FALSE;
|
|
|
|
if (bResult)
|
|
{
|
|
crFont = cf.rgbColors;
|
|
lfFont.lfHeight = (lfFont.lfHeight > 0) ? lfFont.lfHeight : -lfFont.lfHeight;
|
|
pAnnotation->SetFont(lfFont);
|
|
pAnnotation->SetFontColor(crFont);
|
|
m_fDirty = TRUE;
|
|
|
|
CRegKey Key;
|
|
if (ERROR_SUCCESS != Key.Open(HKEY_CURRENT_USER, REGSTR_SHIMGVW))
|
|
{
|
|
Key.Create(HKEY_CURRENT_USER, REGSTR_SHIMGVW);
|
|
}
|
|
|
|
if (Key.m_hKey != NULL)
|
|
{
|
|
Key.SetValue(crFont, REGSTR_TEXTCOLOR);
|
|
::RegSetValueEx(Key, REGSTR_FONT, 0, REG_BINARY, (LPBYTE)&lfFont, sizeof(lfFont));
|
|
}
|
|
_RefreshSelection();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_fPromptingUser = TRUE;
|
|
INT_PTR iResult = DialogBoxParam(_Module.GetModuleInstance(),
|
|
MAKEINTRESOURCE(IDD_ANNOPROPS),
|
|
m_hWnd, _AnnoPropsDlgProc, (LPARAM)this);
|
|
m_fPromptingUser = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Under these condition the has pressed Ctrl-I to get File Properties
|
|
// So serve them up.
|
|
CComBSTR bstrSummary;
|
|
bstrSummary.LoadString(IDS_SUMMARY);
|
|
_InvokeVerb(TEXT("properties"), bstrSummary);
|
|
}
|
|
}
|
|
|
|
HRESULT _VerbMatches(LPCWSTR pszFile, LPCWSTR pszVerb, LPCTSTR pszOurs)
|
|
{
|
|
TCHAR szTemp[MAX_PATH];
|
|
DWORD cch = ARRAYSIZE(szTemp);
|
|
HRESULT hr = AssocQueryString(ASSOCF_VERIFY, ASSOCSTR_COMMAND, pszFile, pszVerb, szTemp, &cch);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = (StrStrI(szTemp, pszOurs)) ? S_OK : S_FALSE;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
void CPreviewWnd::_OpenCmd()
|
|
{
|
|
HRESULT hr = _SaveIfDirty(TRUE);
|
|
LPCTSTR pszVerb;
|
|
if (S_OK == hr)
|
|
{
|
|
TCHAR szFile[MAX_PATH];
|
|
hr = PathFromImageData(szFile, ARRAYSIZE(szFile));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
HRESULT hrOpen = _VerbMatches(szFile, L"open", TEXT("shimgvw.dll"));
|
|
HRESULT hrEdit = _VerbMatches(szFile, L"edit", TEXT("mspaint.exe"));
|
|
// if edit is empty, or if edit is mspaint and open is not shimgvw, use the open verb instead
|
|
if (SUCCEEDED(hrEdit))
|
|
{
|
|
if (S_OK == hrEdit && hrOpen == S_FALSE)
|
|
{
|
|
pszVerb = TEXT("open");
|
|
}
|
|
else
|
|
{
|
|
pszVerb = TEXT("edit");
|
|
}
|
|
}
|
|
else if (hrOpen == S_FALSE)
|
|
{
|
|
pszVerb = TEXT("open");
|
|
}
|
|
else
|
|
{
|
|
pszVerb = TEXT("openas");
|
|
}
|
|
hr = _InvokeVerb(pszVerb);
|
|
}
|
|
if (FAILED(hr))
|
|
return;
|
|
|
|
// set m_fNoRestore to avoid the rotation confirmation restoration popup-ation
|
|
m_fNoRestore = TRUE;
|
|
// The user had a chance to save but may have said no. Pretend we're not dirty
|
|
m_fDirty = FALSE;
|
|
PostMessage(WM_CLOSE, 0, 0);
|
|
}
|
|
}
|
|
|
|
BOOL CPreviewWnd::_CanAnnotate(CDecodeTask * pImageData)
|
|
{
|
|
// If we have an image and its encoder and we haven't been explicitly told not to allow editing
|
|
// and the image is writeable
|
|
if (m_pImageData && m_pImageData->IsEditable() && !m_fDisableEdit && m_fCanSave)
|
|
{
|
|
// then if its a TIFF we can annotate it
|
|
return IsEqualGUID(ImageFormatTIFF, pImageData->_guidFormat);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL CPreviewWnd::_CanCrop(CDecodeTask * pImageData)
|
|
{
|
|
if (m_pImageData != NULL)
|
|
{
|
|
// REVIEW I added this for CyraR as a proof of concept. If we decide to support it
|
|
// we still need to catch all the places where we should save the croppped image and
|
|
// call GDIplus to accomplish the crop.
|
|
#ifdef SUPPORT_CROPPING
|
|
if (S_OK != m_pImageData->IsEditable())
|
|
return FALSE;
|
|
|
|
LONG cPages;
|
|
if (S_OK == m_pImageData->GetPageCount(&cPages))
|
|
{
|
|
if (cPages > 1)
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
#endif
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
// Called whenever the image changes to hide or show the annotation buttons.
|
|
void CPreviewWnd::_SetAnnotatingCommands(BOOL fEnableAnnotations)
|
|
{
|
|
if (CONTROL_MODE != m_dwMode)
|
|
{
|
|
if (fEnableAnnotations)
|
|
{
|
|
m_fCanAnnotate = TRUE;
|
|
m_fAnnotating = FALSE;
|
|
}
|
|
else
|
|
{
|
|
if (m_fAnnotating)
|
|
{
|
|
m_ctlPreview.SetMode(CZoomWnd::MODE_NOACTION);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_ZOOMOUTCMD, TBSTATE_ENABLED);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_ZOOMINCMD, TBSTATE_ENABLED);
|
|
}
|
|
|
|
m_fCanAnnotate = FALSE;
|
|
m_fAnnotating = FALSE;
|
|
}
|
|
|
|
m_ctlToolbar.SendMessage(TB_HIDEBUTTON, ID_SELECTCMD, MAKELONG(!m_fCanAnnotate, 0));
|
|
m_ctlToolbar.SendMessage(TB_HIDEBUTTON, ID_ANNOTATESEP, MAKELONG(!m_fCanAnnotate, 0));
|
|
m_ctlToolbar.SendMessage(TB_HIDEBUTTON, ID_FREEHANDCMD, MAKELONG(!m_fCanAnnotate, 0));
|
|
m_ctlToolbar.SendMessage(TB_HIDEBUTTON, ID_HIGHLIGHTCMD, MAKELONG(!m_fCanAnnotate, 0));
|
|
m_ctlToolbar.SendMessage(TB_HIDEBUTTON, ID_LINECMD, MAKELONG(!m_fCanAnnotate, 0));
|
|
m_ctlToolbar.SendMessage(TB_HIDEBUTTON, ID_FRAMECMD, MAKELONG(!m_fCanAnnotate, 0));
|
|
m_ctlToolbar.SendMessage(TB_HIDEBUTTON, ID_RECTCMD, MAKELONG(!m_fCanAnnotate, 0));
|
|
m_ctlToolbar.SendMessage(TB_HIDEBUTTON, ID_TEXTCMD, MAKELONG(!m_fCanAnnotate, 0));
|
|
m_ctlToolbar.SendMessage(TB_HIDEBUTTON, ID_NOTECMD, MAKELONG(!m_fCanAnnotate, 0));
|
|
m_ctlToolbar.SendMessage(TB_HIDEBUTTON, ID_PROPERTIESCMD, MAKELONG(!m_fCanAnnotate, 0));
|
|
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_SELECTCMD, MAKELONG(m_fCanAnnotate, 0));
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_ANNOTATESEP, MAKELONG(m_fCanAnnotate, 0));
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_FREEHANDCMD, MAKELONG(m_fCanAnnotate, 0));
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_HIGHLIGHTCMD, MAKELONG(m_fCanAnnotate, 0));
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_LINECMD, MAKELONG(m_fCanAnnotate, 0));
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_FRAMECMD, MAKELONG(m_fCanAnnotate, 0));
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_RECTCMD, MAKELONG(m_fCanAnnotate, 0));
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_TEXTCMD, MAKELONG(m_fCanAnnotate, 0));
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_NOTECMD, MAKELONG(m_fCanAnnotate, 0));
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_PROPERTIESCMD, MAKELONG(FALSE, 0));
|
|
}
|
|
}
|
|
|
|
void CPreviewWnd::_SetCroppingCommands(BOOL fEnableCropping)
|
|
{
|
|
if (CONTROL_MODE != m_dwMode)
|
|
{
|
|
if (fEnableCropping)
|
|
{
|
|
m_fCanCrop = TRUE;
|
|
m_fCropping = FALSE;
|
|
}
|
|
else
|
|
{
|
|
if (m_fCropping)
|
|
{
|
|
m_ctlPreview.SetMode(CZoomWnd::MODE_NOACTION);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_ZOOMOUTCMD, TBSTATE_ENABLED);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_ZOOMINCMD, TBSTATE_ENABLED);
|
|
}
|
|
|
|
m_fCanCrop = FALSE;
|
|
m_fCropping = FALSE;
|
|
}
|
|
|
|
m_ctlToolbar.SendMessage(TB_HIDEBUTTON, ID_CROPCMD, MAKELONG(!m_fCanCrop, 0));
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_CROPCMD, MAKELONG(m_fCanCrop, 0));
|
|
}
|
|
}
|
|
|
|
// Called on Toolbar command to fix the state of the other buttons.
|
|
void CPreviewWnd::_UpdateButtons(WORD wID)
|
|
{
|
|
if (CONTROL_MODE != m_dwMode)
|
|
{
|
|
switch (wID)
|
|
{
|
|
case NOBUTTON:
|
|
case ID_ZOOMINCMD:
|
|
case ID_ZOOMOUTCMD:
|
|
case ID_SELECTCMD:
|
|
case ID_CROPCMD:
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_ZOOMINCMD, TBSTATE_ENABLED);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_ZOOMOUTCMD, TBSTATE_ENABLED);
|
|
if (m_fCanAnnotate)
|
|
{
|
|
m_wNewAnnotation = 0;
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_SELECTCMD, TBSTATE_ENABLED);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_FREEHANDCMD, TBSTATE_ENABLED);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_HIGHLIGHTCMD, TBSTATE_ENABLED);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_LINECMD, TBSTATE_ENABLED);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_FRAMECMD, TBSTATE_ENABLED);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_RECTCMD, TBSTATE_ENABLED);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_TEXTCMD, TBSTATE_ENABLED);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_NOTECMD, TBSTATE_ENABLED);
|
|
m_fAnnotating = (wID == ID_SELECTCMD);
|
|
}
|
|
if (m_fCanCrop)
|
|
{
|
|
m_fCropping = (wID == ID_CROPCMD);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_CROPCMD, TBSTATE_ENABLED);
|
|
}
|
|
|
|
_RefreshSelection(!m_fAnnotating);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, wID, TBSTATE_ENABLED|TBSTATE_CHECKED);
|
|
break;
|
|
case ID_FREEHANDCMD:
|
|
case ID_LINECMD:
|
|
case ID_FRAMECMD:
|
|
case ID_RECTCMD:
|
|
case ID_TEXTCMD:
|
|
case ID_NOTECMD:
|
|
case ID_HIGHLIGHTCMD:
|
|
if (m_fCanAnnotate)
|
|
{
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_ZOOMINCMD, TBSTATE_ENABLED);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_ZOOMOUTCMD, TBSTATE_ENABLED);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_FREEHANDCMD, TBSTATE_ENABLED);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_HIGHLIGHTCMD, TBSTATE_ENABLED);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_LINECMD, TBSTATE_ENABLED);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_FRAMECMD, TBSTATE_ENABLED);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_RECTCMD, TBSTATE_ENABLED);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_TEXTCMD, TBSTATE_ENABLED);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_NOTECMD, TBSTATE_ENABLED);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_SELECTCMD, TBSTATE_ENABLED|TBSTATE_CHECKED);
|
|
m_fAnnotating = TRUE;
|
|
_RefreshSelection(TRUE);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, wID, TBSTATE_ENABLED|TBSTATE_CHECKED);
|
|
m_wNewAnnotation = wID;
|
|
}
|
|
break;
|
|
default:
|
|
if (m_fCanAnnotate)
|
|
{
|
|
m_wNewAnnotation = 0;
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_SELECTCMD, TBSTATE_ENABLED);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_FREEHANDCMD, TBSTATE_ENABLED);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_HIGHLIGHTCMD, TBSTATE_ENABLED);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_LINECMD, TBSTATE_ENABLED);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_FRAMECMD, TBSTATE_ENABLED);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_RECTCMD, TBSTATE_ENABLED);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_TEXTCMD, TBSTATE_ENABLED);
|
|
m_ctlToolbar.SendMessage(TB_SETSTATE, ID_NOTECMD, TBSTATE_ENABLED);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPreviewWnd::_RefreshSelection(BOOL fDeselect)
|
|
{
|
|
if (m_fCropping)
|
|
_UpdateCroppingSelection();
|
|
_UpdateAnnotatingSelection(fDeselect);
|
|
}
|
|
|
|
BOOL CPreviewWnd::_ShouldDisplayAnimations()
|
|
{
|
|
return !::GetSystemMetrics(SM_REMOTESESSION);
|
|
}
|
|
|
|
void CPreviewWnd::_UpdateAnnotatingSelection(BOOL fDeselect)
|
|
{
|
|
BOOL bEditing = FALSE;
|
|
if (m_ctlEdit.m_hWnd != NULL)
|
|
{
|
|
if (m_ctlEdit.IsWindowVisible())
|
|
{
|
|
_HideEditing();
|
|
bEditing = TRUE;
|
|
}
|
|
}
|
|
|
|
if (DPA_GetPtrCount(m_hdpaSelectedAnnotations) > 0)
|
|
{
|
|
CRect rectUpdate;
|
|
CSelectionTracker tracker;
|
|
_SetupAnnotatingTracker(tracker, bEditing);
|
|
|
|
tracker.GetTrueRect(rectUpdate);
|
|
|
|
// If we were editing or this was a straight line, we
|
|
// need to get the bounding rect as well
|
|
if (DPA_GetPtrCount(m_hdpaSelectedAnnotations) == 1)
|
|
{
|
|
CRect rect;
|
|
CAnnotation* pAnnotation = (CAnnotation*)DPA_GetPtr(m_hdpaSelectedAnnotations, 0);
|
|
|
|
pAnnotation->GetRect(rect);
|
|
m_ctlPreview.GetWindowFromImage((LPPOINT)(LPRECT)rect, 2);
|
|
|
|
rectUpdate.UnionRect(rectUpdate, rect);
|
|
}
|
|
m_ctlPreview.InvalidateRect(&rectUpdate);
|
|
|
|
if (m_fAnnotating && !fDeselect)
|
|
{
|
|
if (bEditing)
|
|
_StartEditing(FALSE);
|
|
}
|
|
else
|
|
{
|
|
_StopEditing();
|
|
DPA_DeleteAllPtrs(m_hdpaSelectedAnnotations);
|
|
}
|
|
}
|
|
|
|
// Disable the properties button if there are 0 or 2 or more annotations selected
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_PROPERTIESCMD, MAKELONG(DPA_GetPtrCount(m_hdpaSelectedAnnotations) == 1, 0));
|
|
}
|
|
|
|
void CPreviewWnd::_UpdateCroppingSelection()
|
|
{
|
|
if (m_fCropping)
|
|
{
|
|
m_ctlPreview.InvalidateRect(NULL);
|
|
}
|
|
}
|
|
|
|
void CPreviewWnd::_RemoveAnnotatingSelection()
|
|
{
|
|
// Invalidate current selection and remove annotations
|
|
_UpdateAnnotatingSelection();
|
|
|
|
CAnnotationSet* pAnnotations = m_ctlPreview.GetAnnotations();
|
|
|
|
for (int i = 0; i < DPA_GetPtrCount(m_hdpaSelectedAnnotations); i++)
|
|
{
|
|
CAnnotation* pAnnotation = (CAnnotation*)DPA_GetPtr(m_hdpaSelectedAnnotations, i);
|
|
pAnnotations->RemoveAnnotation(pAnnotation);
|
|
delete pAnnotation;
|
|
m_fDirty = TRUE;
|
|
}
|
|
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_PROPERTIESCMD, MAKELONG(FALSE, 0));
|
|
DPA_DeleteAllPtrs(m_hdpaSelectedAnnotations);
|
|
}
|
|
|
|
void CPreviewWnd::_SetupAnnotatingTracker(CSelectionTracker& tracker, BOOL bEditing)
|
|
{
|
|
CRect rect;
|
|
rect.SetRectEmpty();
|
|
|
|
if (!bEditing)
|
|
{
|
|
if (m_ctlEdit.m_hWnd != NULL)
|
|
bEditing = m_ctlEdit.IsWindowVisible();
|
|
}
|
|
|
|
if (DPA_GetPtrCount(m_hdpaSelectedAnnotations) > 0)
|
|
{
|
|
CAnnotation* pAnnotation;
|
|
|
|
if (DPA_GetPtrCount(m_hdpaSelectedAnnotations) == 1)
|
|
{
|
|
pAnnotation = (CAnnotation*)DPA_GetPtr(m_hdpaSelectedAnnotations, 0);
|
|
|
|
// If this is a straight-line annotation then we need to get
|
|
// to the actual points rather than the bounding rect
|
|
if (pAnnotation->GetType() == MT_STRAIGHTLINE)
|
|
{
|
|
CLineMark* pLine = (CLineMark*)pAnnotation;
|
|
pLine->GetPointsRect(rect);
|
|
}
|
|
else
|
|
{
|
|
pAnnotation->GetRect(rect);
|
|
}
|
|
|
|
if (bEditing)
|
|
_RotateRect(rect, pAnnotation);
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < DPA_GetPtrCount(m_hdpaSelectedAnnotations); i++)
|
|
{
|
|
CRect rectAnnotation;
|
|
pAnnotation = (CAnnotation*)DPA_GetPtr(m_hdpaSelectedAnnotations, i);
|
|
|
|
pAnnotation->GetRect(rectAnnotation);
|
|
rectAnnotation.NormalizeRect();
|
|
rect.UnionRect(rect, rectAnnotation);
|
|
}
|
|
}
|
|
|
|
m_ctlPreview.GetWindowFromImage((LPPOINT)(LPRECT)rect, 2);
|
|
}
|
|
tracker.m_rect = rect;
|
|
|
|
UINT uStyle = 0;
|
|
|
|
if (DPA_GetPtrCount(m_hdpaSelectedAnnotations) > 1)
|
|
{
|
|
uStyle = CSelectionTracker::hatchedBorder;
|
|
}
|
|
else if (DPA_GetPtrCount(m_hdpaSelectedAnnotations) == 1)
|
|
{
|
|
CAnnotation* pAnnotation = (CAnnotation*)DPA_GetPtr(m_hdpaSelectedAnnotations, 0);
|
|
|
|
if (pAnnotation->CanResize())
|
|
{
|
|
if (pAnnotation->GetType() == MT_STRAIGHTLINE)
|
|
uStyle = CSelectionTracker::resizeOutside | CSelectionTracker::lineSelection;
|
|
else
|
|
uStyle = CSelectionTracker::solidLine | CSelectionTracker::resizeOutside;
|
|
}
|
|
else
|
|
{
|
|
uStyle = CSelectionTracker::hatchedBorder;
|
|
}
|
|
}
|
|
|
|
tracker.m_uStyle = uStyle;
|
|
}
|
|
|
|
void CPreviewWnd::_SetupCroppingTracker(CSelectionTracker& tracker)
|
|
{
|
|
if (m_fCropping)
|
|
{
|
|
CRect rect(0, 0, m_ctlPreview.m_cxImage, m_ctlPreview.m_cyImage);
|
|
if (m_rectCropping.IsRectEmpty())
|
|
m_rectCropping = rect;
|
|
|
|
rect = m_rectCropping;
|
|
|
|
m_ctlPreview.GetWindowFromImage((LPPOINT)(LPRECT)rect, 2);
|
|
|
|
tracker.m_rect = rect;
|
|
tracker.m_uStyle = CSelectionTracker::solidLine | CSelectionTracker::resizeOutside;
|
|
}
|
|
}
|
|
|
|
BOOL CPreviewWnd::_OnMouseDownForCropping(UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
if (!m_fCropping)
|
|
return FALSE;
|
|
|
|
if (uMsg != WM_LBUTTONDOWN)
|
|
return TRUE;
|
|
|
|
CSelectionTracker tracker;
|
|
CPoint point(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
|
|
|
|
_SetupCroppingTracker(tracker);
|
|
_RefreshSelection();
|
|
|
|
if (tracker.HitTest(point) == CSelectionTracker::hitNothing)
|
|
return TRUE;
|
|
|
|
if (tracker.Track(m_ctlPreview.m_hWnd, point))
|
|
{
|
|
CRect rectNewPos;
|
|
tracker.GetTrueRect(rectNewPos);
|
|
|
|
m_ctlPreview.GetImageFromWindow((LPPOINT)(LPRECT)rectNewPos, 2);
|
|
|
|
CRect rectImage(0, 0, m_ctlPreview.m_cxImage, m_ctlPreview.m_cyImage);
|
|
|
|
if (rectNewPos.left < rectImage.left)
|
|
m_rectCropping.left = rectImage.left;
|
|
else
|
|
m_rectCropping.left = rectNewPos.left;
|
|
|
|
if (rectNewPos.top < rectImage.top)
|
|
m_rectCropping.top = rectImage.top;
|
|
else
|
|
m_rectCropping.top = rectNewPos.top;
|
|
|
|
if (rectNewPos.right > rectImage.right)
|
|
m_rectCropping.right = rectImage.right;
|
|
else
|
|
m_rectCropping.right = rectNewPos.right;
|
|
|
|
if (rectNewPos.bottom > rectImage.bottom)
|
|
m_rectCropping.bottom = rectImage.bottom;
|
|
else
|
|
m_rectCropping.bottom = rectNewPos.bottom;
|
|
|
|
m_fDirty = TRUE;
|
|
}
|
|
|
|
_RefreshSelection();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL CPreviewWnd::_OnMouseDownForAnnotating(UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
if (!m_fAnnotating)
|
|
return FALSE;
|
|
|
|
if (uMsg != WM_LBUTTONDOWN)
|
|
return TRUE;
|
|
|
|
CRect rect;
|
|
CRect rectImage;
|
|
CPoint point(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
|
|
CSelectionTracker tracker;
|
|
|
|
m_ctlPreview.GetVisibleImageWindowRect(rectImage);
|
|
|
|
if (DPA_GetPtrCount(m_hdpaSelectedAnnotations) == 0)
|
|
{
|
|
_OnMouseDownForAnnotatingHelper(point, rectImage);
|
|
return TRUE;
|
|
}
|
|
|
|
_SetupAnnotatingTracker(tracker);
|
|
tracker.GetTrueRect(rect);
|
|
|
|
if (tracker.HitTest(point) == CSelectionTracker::hitNothing)
|
|
{
|
|
_RefreshSelection(TRUE);
|
|
_OnMouseDownForAnnotatingHelper(point, rectImage);
|
|
return TRUE;
|
|
}
|
|
|
|
if (!tracker.Track(m_ctlPreview.m_hWnd, point))
|
|
{
|
|
_StartEditing();
|
|
return TRUE;
|
|
}
|
|
|
|
CRect rectNewPos;
|
|
tracker.GetTrueRect(rectNewPos);
|
|
|
|
rect.BottomRight() = rectNewPos.TopLeft();
|
|
m_ctlPreview.GetImageFromWindow((LPPOINT)(LPRECT)rect, 2);
|
|
|
|
CSize size = rect.BottomRight() - rect.TopLeft();
|
|
|
|
_RefreshSelection();
|
|
|
|
if (DPA_GetPtrCount(m_hdpaSelectedAnnotations) > 1)
|
|
{
|
|
if (size.cx == 0 && size.cy == 0)
|
|
return TRUE;
|
|
|
|
m_ctlPreview.GetImageFromWindow((LPPOINT)(LPRECT)rectImage, 2);
|
|
rectImage.DeflateRect(5, 5);
|
|
|
|
BOOL bValidMove = TRUE;
|
|
for (int i = 0; i < DPA_GetPtrCount(m_hdpaSelectedAnnotations); i++)
|
|
{
|
|
CAnnotation* pAnnotation = (CAnnotation*)DPA_GetPtr(m_hdpaSelectedAnnotations, i);
|
|
|
|
pAnnotation->GetRect(rect);
|
|
rect.NormalizeRect();
|
|
rect.OffsetRect(size);
|
|
|
|
if (!rectNewPos.IntersectRect(rectImage, rect))
|
|
bValidMove = FALSE;
|
|
}
|
|
|
|
if (!bValidMove)
|
|
return TRUE;
|
|
|
|
for (int i = 0; i < DPA_GetPtrCount(m_hdpaSelectedAnnotations); i++)
|
|
{
|
|
CAnnotation* pAnnotation = (CAnnotation*)DPA_GetPtr(m_hdpaSelectedAnnotations, i);
|
|
pAnnotation->Move(size);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CAnnotation* pAnnotation = (CAnnotation*)DPA_GetPtr(m_hdpaSelectedAnnotations, 0);
|
|
if (pAnnotation->CanResize())
|
|
{
|
|
CRect rectTest;
|
|
|
|
rect = tracker.m_rect;
|
|
m_ctlPreview.GetImageFromWindow((LPPOINT)(LPRECT)rect, 2);
|
|
|
|
rectTest = rect;
|
|
|
|
// If the annotation being manipulated is a straight line then the rectangle
|
|
// returned from the tracker could be empty (ie left=right or top=bottom)
|
|
// In this case the IntersectRect test below would fail because windows
|
|
// assumes empty rectangle don't intersect anything.
|
|
if (pAnnotation->GetType() == MT_STRAIGHTLINE)
|
|
{
|
|
if (rectTest.left == rectTest.right)
|
|
rectTest.right++;
|
|
if (rectTest.top == rectTest.bottom)
|
|
rectTest.bottom++;
|
|
}
|
|
rectTest.NormalizeRect();
|
|
|
|
m_ctlPreview.GetImageFromWindow((LPPOINT)(LPRECT)rectImage, 2);
|
|
rectImage.DeflateRect(5, 5);
|
|
|
|
if (!rectTest.IntersectRect(rectImage, rectTest))
|
|
return TRUE;
|
|
|
|
if (m_ctlEdit.m_hWnd != NULL)
|
|
{
|
|
if (m_ctlEdit.IsWindowVisible())
|
|
{
|
|
_RotateRect(rect, pAnnotation);
|
|
}
|
|
}
|
|
|
|
// If this is a line then the rect is assumed to be
|
|
// a non-normalized array of points.
|
|
pAnnotation->Resize(rect);
|
|
|
|
}
|
|
else
|
|
{
|
|
if (size.cx == 0 && size.cy == 0)
|
|
return TRUE;
|
|
|
|
pAnnotation->Move(size);
|
|
}
|
|
}
|
|
m_fDirty = TRUE;
|
|
_RefreshSelection();
|
|
return TRUE;
|
|
}
|
|
|
|
void CPreviewWnd::_OnMouseDownForAnnotatingHelper(CPoint ptMouse, CRect rectImage)
|
|
{
|
|
CRect rect;
|
|
CSelectionTracker tracker;
|
|
_SetupAnnotatingTracker(tracker);
|
|
|
|
if (m_wNewAnnotation == ID_FREEHANDCMD)
|
|
{
|
|
_CreateFreeHandAnnotation(ptMouse);
|
|
return;
|
|
}
|
|
|
|
// If we are creating a line then make sure the tracker has the lineSelection
|
|
// style so we get the appropriate visual feedback.
|
|
if (m_wNewAnnotation == ID_LINECMD)
|
|
{
|
|
tracker.m_uStyle = CSelectionTracker::resizeOutside | CSelectionTracker::lineSelection;
|
|
}
|
|
|
|
if (tracker.TrackRubberBand(m_ctlPreview.m_hWnd, ptMouse, TRUE))
|
|
{
|
|
rect = tracker.m_rect;
|
|
rect.NormalizeRect();
|
|
|
|
if ((rect.Width() > 10) || (rect.Height() > 10))
|
|
{
|
|
if (m_wNewAnnotation != 0)
|
|
{
|
|
_CreateAnnotation(tracker.m_rect);
|
|
}
|
|
else
|
|
{
|
|
CRect rectTest;
|
|
CRect rectAnnotation;
|
|
CAnnotationSet* pAnnotations = m_ctlPreview.GetAnnotations();
|
|
|
|
m_ctlPreview.GetImageFromWindow((LPPOINT)(LPRECT)rect, 2);
|
|
|
|
INT_PTR nCount = pAnnotations->GetCount();
|
|
for (INT_PTR i = 0; i < nCount; i++)
|
|
{
|
|
CAnnotation* pAnnotation = pAnnotations->GetAnnotation(i);
|
|
|
|
pAnnotation->GetRect(rectAnnotation);
|
|
rectAnnotation.NormalizeRect();
|
|
rectTest.UnionRect(rect, rectAnnotation);
|
|
|
|
if (rectTest == rect)
|
|
{
|
|
DPA_AppendPtr(m_hdpaSelectedAnnotations, pAnnotation);
|
|
}
|
|
}
|
|
_RefreshSelection(DPA_GetPtrCount(m_hdpaSelectedAnnotations) == 0);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_wNewAnnotation == 0)
|
|
{
|
|
if (PtInRect(rectImage, ptMouse))
|
|
{
|
|
m_ctlPreview.GetImageFromWindow(&ptMouse, 1);
|
|
|
|
CAnnotationSet* pAnnotations = m_ctlPreview.GetAnnotations();
|
|
INT_PTR nCount = pAnnotations->GetCount();
|
|
|
|
// if the user is clicking a single point then
|
|
// we need to search the annotations in zorder
|
|
// from top to bottom
|
|
for (INT_PTR i = nCount - 1; i >= 0; i--)
|
|
{
|
|
CAnnotation* pAnnotation = pAnnotations->GetAnnotation(i);
|
|
|
|
pAnnotation->GetRect(rect);
|
|
rect.NormalizeRect();
|
|
|
|
if (PtInRect(rect, ptMouse))
|
|
{
|
|
DPA_AppendPtr(m_hdpaSelectedAnnotations, pAnnotation);
|
|
_RefreshSelection();
|
|
return;
|
|
}
|
|
}
|
|
_RefreshSelection(DPA_GetPtrCount(m_hdpaSelectedAnnotations) == 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_UpdateButtons(ID_SELECTCMD);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPreviewWnd::_CreateAnnotation(CRect rect)
|
|
{
|
|
if (m_wNewAnnotation == 0 || m_wNewAnnotation == ID_FREEHANDCMD)
|
|
return;
|
|
|
|
ULONG xDPI;
|
|
ULONG yDPI;
|
|
if (!(m_pImageData->GetResolution(&xDPI, &yDPI)))
|
|
return;
|
|
|
|
CAnnotation* pAnnotation = NULL;
|
|
switch(m_wNewAnnotation)
|
|
{
|
|
case ID_LINECMD:
|
|
pAnnotation = CAnnotation::CreateAnnotation(MT_STRAIGHTLINE, yDPI);
|
|
break;
|
|
case ID_FRAMECMD:
|
|
pAnnotation = CAnnotation::CreateAnnotation(MT_HOLLOWRECT, yDPI);
|
|
break;
|
|
case ID_RECTCMD:
|
|
pAnnotation = CAnnotation::CreateAnnotation(MT_FILLRECT, yDPI);
|
|
break;
|
|
case ID_TEXTCMD:
|
|
pAnnotation = CAnnotation::CreateAnnotation(MT_TYPEDTEXT, yDPI);
|
|
break;
|
|
case ID_NOTECMD:
|
|
pAnnotation = CAnnotation::CreateAnnotation(MT_ATTACHANOTE, yDPI);
|
|
break;
|
|
case ID_HIGHLIGHTCMD:
|
|
pAnnotation = CAnnotation::CreateAnnotation(MT_FILLRECT, yDPI);
|
|
if (pAnnotation != NULL)
|
|
pAnnotation->SetTransparent(TRUE);
|
|
break;
|
|
}
|
|
|
|
if (pAnnotation != NULL)
|
|
{
|
|
COLORREF crBackColor = RGB(255,255,0);
|
|
COLORREF crLineColor = RGB(255,0,0);
|
|
COLORREF crTextColor = RGB(0,0,0);
|
|
LOGFONT lfFont = {12, 0, 0, 0, 400, 0, 0, 0, 0, 0, 0, 0, 0, TEXT("Arial") };
|
|
|
|
DWORD dwWidth = 1;
|
|
|
|
CRegKey Key;
|
|
if (ERROR_SUCCESS == Key.Open(HKEY_CURRENT_USER, REGSTR_SHIMGVW))
|
|
{
|
|
Key.QueryValue(dwWidth, REGSTR_LINEWIDTH);
|
|
Key.QueryValue(crBackColor, REGSTR_BACKCOLOR);
|
|
Key.QueryValue(crLineColor, REGSTR_LINECOLOR);
|
|
Key.QueryValue(crTextColor, REGSTR_TEXTCOLOR);
|
|
|
|
DWORD dwType, cbSize;
|
|
cbSize = sizeof(lfFont);
|
|
::RegQueryValueEx(Key, REGSTR_FONT, NULL, &dwType, (LPBYTE)&lfFont, &cbSize);
|
|
}
|
|
|
|
if (m_wNewAnnotation != ID_LINECMD)
|
|
rect.NormalizeRect();
|
|
|
|
m_ctlPreview.GetImageFromWindow((LPPOINT)(LPRECT)rect, 2);
|
|
pAnnotation->Resize(rect);
|
|
|
|
if (pAnnotation->HasWidth())
|
|
pAnnotation->SetWidth(dwWidth);
|
|
|
|
if (pAnnotation->HasColor())
|
|
{
|
|
if (m_wNewAnnotation == ID_LINECMD || m_wNewAnnotation == ID_FRAMECMD)
|
|
pAnnotation->SetColor(crLineColor);
|
|
else
|
|
pAnnotation->SetColor(crBackColor);
|
|
}
|
|
|
|
if (pAnnotation->HasFont())
|
|
{
|
|
pAnnotation->SetFont(lfFont);
|
|
pAnnotation->SetFontColor(crTextColor);
|
|
}
|
|
|
|
DPA_DeleteAllPtrs(m_hdpaSelectedAnnotations);
|
|
DPA_AppendPtr(m_hdpaSelectedAnnotations, pAnnotation);
|
|
|
|
CAnnotationSet* pAnnotations = m_ctlPreview.GetAnnotations();
|
|
pAnnotations->AddAnnotation(pAnnotation);
|
|
|
|
m_fDirty = TRUE;
|
|
}
|
|
_UpdateButtons(ID_SELECTCMD);
|
|
}
|
|
|
|
void CPreviewWnd::_CreateFreeHandAnnotation(CPoint ptMouse)
|
|
{
|
|
if (m_wNewAnnotation != ID_FREEHANDCMD)
|
|
return;
|
|
|
|
// don't handle if capture already set
|
|
if (::GetCapture() != NULL)
|
|
return;
|
|
|
|
// set capture to the window which received this message
|
|
::SetCapture(m_ctlPreview.m_hWnd);
|
|
ASSERT(m_ctlPreview.m_hWnd == ::GetCapture());
|
|
|
|
::UpdateWindow(m_ctlPreview.m_hWnd);
|
|
|
|
ULONG xDPI;
|
|
ULONG yDPI;
|
|
if (!(m_pImageData->GetResolution(&xDPI, &yDPI)))
|
|
return;
|
|
|
|
CLineMark* pAnnotation = (CLineMark*)CAnnotation::CreateAnnotation(MT_FREEHANDLINE, yDPI);
|
|
if (pAnnotation == NULL)
|
|
return;
|
|
|
|
CDSA<POINT> Points;
|
|
Points.Create(256);
|
|
|
|
CPoint ptLast = ptMouse;
|
|
m_ctlPreview.GetImageFromWindow(&ptMouse, 1);
|
|
|
|
Points.AppendItem(&ptMouse);
|
|
|
|
// get DC for drawing
|
|
HDC hdcDraw;
|
|
|
|
// otherwise, just use normal DC
|
|
hdcDraw = ::GetDC(m_ctlPreview.m_hWnd);
|
|
ASSERT(hdcDraw != NULL);
|
|
|
|
COLORREF crLineColor = RGB(255,0,0);
|
|
DWORD dwWidth = 1;
|
|
|
|
CRegKey Key;
|
|
if (ERROR_SUCCESS == Key.Open(HKEY_CURRENT_USER, REGSTR_SHIMGVW))
|
|
{
|
|
Key.QueryValue(dwWidth, REGSTR_LINEWIDTH);
|
|
Key.QueryValue(crLineColor, REGSTR_LINECOLOR);
|
|
}
|
|
|
|
CRect rect(0,0,0,dwWidth);
|
|
m_ctlPreview.GetWindowFromImage((LPPOINT)(LPRECT)rect, 2);
|
|
DWORD dwRenderWidth = rect.Height();
|
|
|
|
HPEN hpen = ::CreatePen(PS_SOLID, dwRenderWidth, crLineColor);
|
|
HPEN hOld =(HPEN)::SelectObject(hdcDraw, hpen);
|
|
|
|
BOOL bCancel=FALSE;
|
|
|
|
// get messages until capture lost or cancelled/accepted
|
|
for (;;)
|
|
{
|
|
MSG msg;
|
|
if (!::GetMessage(&msg, NULL, 0, 0))
|
|
{
|
|
ASSERT(FALSE);
|
|
}
|
|
|
|
if (m_ctlPreview.m_hWnd != ::GetCapture())
|
|
{
|
|
bCancel = TRUE;
|
|
goto ExitLoop;
|
|
}
|
|
|
|
ptMouse.x = GET_X_LPARAM(msg.lParam);
|
|
ptMouse.y = GET_Y_LPARAM(msg.lParam);
|
|
|
|
switch (msg.message)
|
|
{
|
|
// handle movement/accept messages
|
|
case WM_LBUTTONUP:
|
|
case WM_MOUSEMOVE:
|
|
::MoveToEx(hdcDraw, ptLast.x, ptLast.y, NULL);
|
|
::LineTo(hdcDraw, ptMouse.x, ptMouse.y);
|
|
ptLast = ptMouse;
|
|
|
|
m_ctlPreview.GetImageFromWindow(&ptMouse, 1);
|
|
Points.AppendItem(&ptMouse);
|
|
|
|
if (msg.message == WM_LBUTTONUP)
|
|
goto ExitLoop;
|
|
break;
|
|
// handle cancel messages
|
|
case WM_KEYDOWN:
|
|
if (msg.wParam != VK_ESCAPE)
|
|
break;
|
|
// else fall through
|
|
case WM_RBUTTONDOWN:
|
|
bCancel = TRUE;
|
|
goto ExitLoop;
|
|
default:
|
|
::DispatchMessage(&msg);
|
|
break;
|
|
}
|
|
}
|
|
ExitLoop:
|
|
|
|
::SelectObject(hdcDraw, hOld);
|
|
::DeleteObject(hpen);
|
|
::ReleaseDC(m_ctlPreview.m_hWnd, hdcDraw);
|
|
::ReleaseCapture();
|
|
|
|
if (!bCancel)
|
|
{
|
|
int nAnnoPoints = Points.GetItemCount();
|
|
POINT* AnnoPoints = new POINT[nAnnoPoints];
|
|
if (AnnoPoints == NULL)
|
|
{
|
|
delete pAnnotation;
|
|
Points.Destroy();
|
|
_UpdateButtons(ID_SELECTCMD);
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < nAnnoPoints; i++)
|
|
{
|
|
CPoint pt;
|
|
Points.GetItem(i, &pt);
|
|
AnnoPoints[i].x = pt.x;
|
|
AnnoPoints[i].y = pt.y;
|
|
}
|
|
|
|
Points.Destroy();
|
|
|
|
pAnnotation->SetPoints(AnnoPoints, nAnnoPoints);
|
|
pAnnotation->SetWidth(dwWidth);
|
|
pAnnotation->SetColor(crLineColor);
|
|
|
|
DPA_DeleteAllPtrs(m_hdpaSelectedAnnotations);
|
|
DPA_AppendPtr(m_hdpaSelectedAnnotations, pAnnotation);
|
|
|
|
CAnnotationSet* pAnnotations = m_ctlPreview.GetAnnotations();
|
|
pAnnotations->AddAnnotation(pAnnotation);
|
|
m_fDirty = TRUE;
|
|
}
|
|
_UpdateButtons(ID_SELECTCMD);
|
|
}
|
|
|
|
void CPreviewWnd::_StartEditing(BOOL bUpdateText)
|
|
{
|
|
if (DPA_GetPtrCount(m_hdpaSelectedAnnotations) != 1)
|
|
return;
|
|
|
|
CTextAnnotation* pAnnotation = (CTextAnnotation*)DPA_GetPtr(m_hdpaSelectedAnnotations, 0);
|
|
if (!pAnnotation)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UINT uType = pAnnotation->GetType();
|
|
|
|
if (uType != MT_TYPEDTEXT && uType != MT_FILETEXT && uType != MT_STAMP && uType != MT_ATTACHANOTE)
|
|
return;
|
|
|
|
if (m_ctlEdit.m_hWnd == NULL)
|
|
{
|
|
HWND hwndEdit = ::CreateWindow(TEXT("EDIT"), NULL, ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL |
|
|
ES_WANTRETURN | WS_CHILD, 1, 1, 10, 10,
|
|
m_ctlPreview.m_hWnd, (HMENU)1496, NULL, NULL);
|
|
if (hwndEdit == NULL)
|
|
return;
|
|
|
|
m_ctlEdit.SubclassWindow(hwndEdit);
|
|
}
|
|
|
|
if (bUpdateText)
|
|
{
|
|
CComBSTR bstrText;
|
|
bstrText.Attach(pAnnotation->GetText());
|
|
if (bstrText.m_str != NULL)
|
|
m_ctlEdit.SetWindowText(bstrText);
|
|
else
|
|
m_ctlEdit.SetWindowText(TEXT(""));
|
|
}
|
|
|
|
m_ctlEdit.EnableWindow(TRUE);
|
|
|
|
LOGFONT lfFont;
|
|
pAnnotation->GetFont(lfFont);
|
|
|
|
HDC hdc = ::GetDC(NULL);
|
|
LONG lHeight = pAnnotation->GetFontHeight(hdc);
|
|
::ReleaseDC(NULL, hdc);
|
|
|
|
CRect rect(0,0,0,lHeight);
|
|
m_ctlPreview.GetWindowFromImage((LPPOINT)(LPRECT)rect, 2);
|
|
lfFont.lfHeight = -rect.Height();
|
|
|
|
HFONT hNewFont = ::CreateFontIndirect(&lfFont);
|
|
if (hNewFont)
|
|
{
|
|
::DeleteObject(m_hFont);
|
|
m_hFont = hNewFont;
|
|
m_ctlEdit.SendMessage(WM_SETFONT, (WPARAM)m_hFont, MAKELPARAM(TRUE,0));
|
|
}
|
|
|
|
pAnnotation->GetRect(rect);
|
|
_RotateRect(rect, pAnnotation);
|
|
m_ctlPreview.GetWindowFromImage((LPPOINT)(LPRECT)rect, 2);
|
|
m_ctlEdit.SetWindowPos(NULL, rect.left, rect.top, rect.Width(), rect.Height(), SWP_NOZORDER);
|
|
|
|
CSelectionTracker tracker;
|
|
_SetupAnnotatingTracker(tracker, FALSE);
|
|
|
|
CRect rectUpdate;
|
|
tracker.GetTrueRect(rectUpdate);
|
|
m_ctlPreview.InvalidateRect(rectUpdate);
|
|
|
|
_SetupAnnotatingTracker(tracker, TRUE);
|
|
tracker.GetTrueRect(rectUpdate);
|
|
m_ctlPreview.InvalidateRect(rectUpdate);
|
|
|
|
m_ctlEdit.ShowWindow(SW_SHOW);
|
|
m_ctlEdit.SetFocus();
|
|
|
|
m_fEditingAnnotation = TRUE;
|
|
}
|
|
|
|
void CPreviewWnd::_HideEditing()
|
|
{
|
|
if (m_ctlEdit.m_hWnd == NULL)
|
|
return;
|
|
|
|
if (!m_ctlEdit.IsWindowVisible())
|
|
return;
|
|
|
|
SetFocus();
|
|
m_ctlEdit.SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_HIDEWINDOW);
|
|
m_ctlEdit.EnableWindow(FALSE);
|
|
}
|
|
|
|
void CPreviewWnd::_StopEditing()
|
|
{
|
|
if (m_ctlEdit.m_hWnd == NULL)
|
|
return;
|
|
|
|
_HideEditing();
|
|
|
|
if (!m_fEditingAnnotation)
|
|
return;
|
|
|
|
m_fEditingAnnotation = FALSE;
|
|
|
|
if (DPA_GetPtrCount(m_hdpaSelectedAnnotations) != 1)
|
|
return;
|
|
|
|
CTextAnnotation* pAnnotation = (CTextAnnotation*)DPA_GetPtr(m_hdpaSelectedAnnotations, 0);
|
|
UINT uType = pAnnotation->GetType();
|
|
|
|
if (uType != MT_TYPEDTEXT && uType != MT_FILETEXT && uType != MT_STAMP && uType != MT_ATTACHANOTE)
|
|
return;
|
|
|
|
// if the length greater than zero we save it
|
|
// otherwise be blow away the annotation.
|
|
int nLen = m_ctlEdit.GetWindowTextLength();
|
|
if (nLen > 0)
|
|
{
|
|
CComBSTR bstrText(nLen+1);
|
|
m_ctlEdit.GetWindowText(bstrText, nLen+1);
|
|
pAnnotation->SetText(bstrText);
|
|
m_fDirty = TRUE;
|
|
}
|
|
else
|
|
{
|
|
CSelectionTracker tracker;
|
|
|
|
_SetupAnnotatingTracker(tracker, TRUE);
|
|
|
|
CRect rectUpdate;
|
|
tracker.GetTrueRect(rectUpdate);
|
|
|
|
CRect rect;
|
|
pAnnotation->GetRect(rect);
|
|
rectUpdate.UnionRect(rectUpdate, rect);
|
|
|
|
DPA_DeleteAllPtrs(m_hdpaSelectedAnnotations);
|
|
|
|
CAnnotationSet* pAnnotations = m_ctlPreview.GetAnnotations();
|
|
pAnnotations->RemoveAnnotation(pAnnotation);
|
|
delete pAnnotation;
|
|
|
|
m_ctlPreview.InvalidateRect(rectUpdate);
|
|
m_fDirty = TRUE;
|
|
}
|
|
}
|
|
|
|
LRESULT CPreviewWnd::OnEditKeyEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& fHandled)
|
|
{
|
|
switch (wParam)
|
|
{
|
|
case VK_ESCAPE:
|
|
{
|
|
CSelectionTracker tracker;
|
|
_SetupAnnotatingTracker(tracker);
|
|
CRect rectUpdate;
|
|
tracker.GetTrueRect(rectUpdate);
|
|
|
|
_HideEditing();
|
|
|
|
m_ctlPreview.InvalidateRect(rectUpdate);
|
|
_RefreshSelection();
|
|
|
|
fHandled = TRUE;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
fHandled = FALSE;
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
BOOL_PTR CALLBACK CPreviewWnd::_AnnoPropsDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
static LOGFONT lfFont;
|
|
static COLORREF crFont;
|
|
static COLORREF crColor;
|
|
CPreviewWnd* pThis;
|
|
|
|
switch (msg)
|
|
{
|
|
case WM_INITDIALOG:
|
|
{
|
|
HWND hwndCtl = NULL;
|
|
::SetWindowLongPtr(hwnd, DWLP_USER, lParam);
|
|
pThis = (CPreviewWnd*)lParam;
|
|
|
|
CAnnotation* pAnnotation = (CAnnotation*)DPA_GetPtr(pThis->m_hdpaSelectedAnnotations, 0);
|
|
|
|
hwndCtl = ::GetDlgItem(hwnd, IDC_WIDTHTEXT);
|
|
if (!pAnnotation->HasWidth())
|
|
{
|
|
::EnableWindow(hwndCtl, FALSE);
|
|
::ShowWindow(hwndCtl, SW_HIDE);
|
|
}
|
|
|
|
hwndCtl = ::GetDlgItem(hwnd, IDC_WIDTH);
|
|
if (pAnnotation->HasWidth())
|
|
{
|
|
UINT i = pAnnotation->GetWidth();
|
|
::SetDlgItemInt(hwnd, IDC_WIDTH, i, FALSE);
|
|
|
|
hwndCtl = ::GetDlgItem(hwnd, IDC_SPIN);
|
|
::SendMessage(hwndCtl, UDM_SETRANGE32, (WPARAM)1, (LPARAM)50);
|
|
::SendMessage(hwndCtl, UDM_SETPOS32, 0, (LPARAM)i);
|
|
}
|
|
else
|
|
{
|
|
::EnableWindow(hwndCtl, FALSE);
|
|
::ShowWindow(hwndCtl, SW_HIDE);
|
|
hwndCtl = ::GetDlgItem(hwnd, IDC_SPIN);
|
|
::EnableWindow(hwndCtl, FALSE);
|
|
::ShowWindow(hwndCtl, SW_HIDE);
|
|
}
|
|
|
|
hwndCtl = ::GetDlgItem(hwnd, IDC_TRANSPARENT);
|
|
if (pAnnotation->HasTransparent())
|
|
{
|
|
BOOL bTransparent = pAnnotation->GetTransparent();
|
|
::SendMessage(hwndCtl, BM_SETCHECK, (WPARAM)(bTransparent ? BST_CHECKED : BST_UNCHECKED), 0);
|
|
}
|
|
else
|
|
{
|
|
::EnableWindow(hwndCtl, FALSE);
|
|
::ShowWindow(hwndCtl, SW_HIDE);
|
|
}
|
|
|
|
if (pAnnotation->HasFont())
|
|
{
|
|
pAnnotation->GetFont(lfFont);
|
|
crFont = pAnnotation->GetFontColor();
|
|
}
|
|
else
|
|
{
|
|
hwndCtl = ::GetDlgItem(hwnd, IDC_FONT);
|
|
::EnableWindow(hwndCtl, FALSE);
|
|
::ShowWindow(hwndCtl, SW_HIDE);
|
|
}
|
|
|
|
|
|
if (pAnnotation->HasColor())
|
|
{
|
|
crColor = pAnnotation->GetColor();
|
|
}
|
|
else
|
|
{
|
|
hwndCtl = ::GetDlgItem(hwnd, IDC_COLOR);
|
|
::EnableWindow(hwndCtl, FALSE);
|
|
::ShowWindow(hwndCtl, SW_HIDE);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_COMMAND:
|
|
pThis = (CPreviewWnd*)::GetWindowLongPtr(hwnd, DWLP_USER);
|
|
|
|
switch (wParam)
|
|
{
|
|
case IDOK:
|
|
pThis->_RefreshSelection();
|
|
{
|
|
HWND hwndCtl = NULL;
|
|
CAnnotation* pAnnotation = (CAnnotation*)DPA_GetPtr(pThis->m_hdpaSelectedAnnotations, 0);
|
|
|
|
CRegKey Key;
|
|
if (ERROR_SUCCESS != Key.Open(HKEY_CURRENT_USER, REGSTR_SHIMGVW))
|
|
{
|
|
Key.Create(HKEY_CURRENT_USER, REGSTR_SHIMGVW);
|
|
}
|
|
|
|
if (pAnnotation->HasWidth())
|
|
{
|
|
UINT uWidth = ::GetDlgItemInt(hwnd, IDC_WIDTH, NULL, FALSE);
|
|
|
|
if (uWidth > 50 || uWidth < 1)
|
|
{
|
|
CComBSTR bstrMsg, bstrTitle;
|
|
|
|
if (bstrMsg.LoadString(IDS_WIDTHBAD_MSGBOX) && bstrTitle.LoadString(IDS_PROJNAME))
|
|
{
|
|
::MessageBox(hwnd, bstrMsg, bstrTitle, MB_OK | MB_ICONERROR | MB_APPLMODAL);
|
|
}
|
|
|
|
::SetDlgItemInt(hwnd, IDC_WIDTH, 50, FALSE);
|
|
return FALSE;
|
|
}
|
|
|
|
pAnnotation->SetWidth(uWidth);
|
|
if (Key.m_hKey != NULL)
|
|
{
|
|
Key.SetValue(uWidth, REGSTR_LINEWIDTH);
|
|
}
|
|
}
|
|
|
|
if (pAnnotation->HasTransparent())
|
|
{
|
|
hwndCtl = ::GetDlgItem(hwnd, IDC_TRANSPARENT);
|
|
BOOL bTransparent = FALSE;
|
|
if (::SendMessage(hwndCtl, BM_GETCHECK, 0, 0) == BST_CHECKED)
|
|
bTransparent = TRUE;
|
|
|
|
pAnnotation->SetTransparent(bTransparent);
|
|
}
|
|
|
|
if (pAnnotation->HasFont())
|
|
{
|
|
lfFont.lfHeight = (lfFont.lfHeight > 0) ? lfFont.lfHeight : -lfFont.lfHeight;
|
|
pAnnotation->SetFont(lfFont);
|
|
pAnnotation->SetFontColor(crFont);
|
|
if (Key.m_hKey != NULL)
|
|
{
|
|
Key.SetValue(crFont, REGSTR_TEXTCOLOR);
|
|
::RegSetValueEx(Key, REGSTR_FONT, 0, REG_BINARY, (LPBYTE)&lfFont, sizeof(lfFont));
|
|
}
|
|
}
|
|
|
|
if (pAnnotation->HasColor())
|
|
{
|
|
pAnnotation->SetColor(crColor);
|
|
UINT uType = pAnnotation->GetType();
|
|
if (Key.m_hKey != NULL)
|
|
{
|
|
if (uType == MT_STRAIGHTLINE || uType == MT_FREEHANDLINE || uType == MT_HOLLOWRECT)
|
|
Key.SetValue(crColor, REGSTR_LINECOLOR);
|
|
else
|
|
Key.SetValue(crColor, REGSTR_BACKCOLOR);
|
|
}
|
|
}
|
|
|
|
}
|
|
pThis->m_fDirty = TRUE;
|
|
pThis->_RefreshSelection();
|
|
EndDialog(hwnd, wParam);
|
|
return FALSE;
|
|
case IDCANCEL:
|
|
EndDialog(hwnd, wParam);
|
|
return FALSE;
|
|
case IDC_FONT:
|
|
{
|
|
CHOOSEFONT cf = {0};
|
|
LOGFONT lf;
|
|
|
|
lf = lfFont;
|
|
|
|
cf.lStructSize = sizeof(cf);
|
|
cf.hwndOwner = hwnd;
|
|
cf.lpLogFont = &lf;
|
|
cf.Flags = CF_SCREENFONTS | CF_EFFECTS | CF_INITTOLOGFONTSTRUCT | CF_NOVERTFONTS | CF_NOSCRIPTSEL;
|
|
cf.rgbColors = crFont;
|
|
|
|
if (::ChooseFont(&cf))
|
|
{
|
|
CopyMemory (&lfFont, &lf, sizeof(lfFont));
|
|
crFont = cf.rgbColors;
|
|
}
|
|
}
|
|
return FALSE;
|
|
case IDC_COLOR:
|
|
{
|
|
CHOOSECOLOR cc = {0};
|
|
|
|
cc.lStructSize = sizeof(cc);
|
|
cc.hwndOwner = hwnd;
|
|
cc.rgbResult = crColor;
|
|
cc.lpCustColors = g_crCustomColors;
|
|
cc.Flags = CC_RGBINIT | CC_SOLIDCOLOR;
|
|
|
|
if (::ChooseColor(&cc))
|
|
{
|
|
crColor = cc.rgbResult;
|
|
}
|
|
}
|
|
return FALSE;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL CPreviewWnd::_TrySetImage()
|
|
{
|
|
BOOL fRet = FALSE;
|
|
if (m_pNextImageData && m_pNextImageData->_iItem == m_iCurSlide)
|
|
{
|
|
if (SUCCEEDED(m_pNextImageData->_hr))
|
|
{
|
|
m_fCanSave = !m_pNextImageData->_fIsReadOnly;
|
|
|
|
// update the toolbar state, our child windows, and our sibling windows
|
|
_SetNewImage(m_pNextImageData);
|
|
ATOMICRELEASE(m_pNextImageData);
|
|
|
|
if (m_pImageData->IsAnimated() && _ShouldDisplayAnimations())
|
|
{
|
|
// start the animation timer
|
|
SetTimer(TIMER_ANIMATION, m_pImageData->GetDelay());
|
|
}
|
|
|
|
// Notify anyone listening to our events that a preview has been completed
|
|
// we only fire this upon success
|
|
if (m_pEvents)
|
|
{
|
|
m_pEvents->OnPreviewReady();
|
|
}
|
|
fRet = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// update the status to display an error message. This will also update the toolbar state.
|
|
StatusUpdate(IDS_LOADFAILED);
|
|
|
|
//
|
|
// We can't remove the item from the array because the user might try to delete it while
|
|
// the "load failed" string is still visible for it.
|
|
|
|
|
|
// even though the item failed to decode we must wait on the "Load Failed" state when we are in
|
|
// windowed mode, otherwise "open with..." is broken when you open a corrupted image or non-image.
|
|
// In slideshow mode we could simply skip to the next image.
|
|
|
|
if (m_pEvents)
|
|
m_pEvents->OnError();
|
|
}
|
|
}
|
|
|
|
return fRet;
|
|
}
|
|
|
|
LRESULT CPreviewWnd::IV_OnSetImageData(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& fHandled)
|
|
{
|
|
CDecodeTask * pData = (CDecodeTask *)wParam;
|
|
|
|
ATOMICRELEASE(m_pNextImageData);
|
|
|
|
m_pNextImageData = pData;
|
|
|
|
if (m_pNextImageData && m_iDecodingNextImage == m_pNextImageData->_iItem)
|
|
{
|
|
// We have finished decoding now, let's remember this.
|
|
m_iDecodingNextImage = -1;
|
|
|
|
// Let 's prepare the drawing now. This draws in the back buffer. Don't start this if we want to see
|
|
// the image now, as it would delay things.
|
|
if (SUCCEEDED(m_pNextImageData->_hr) && m_pNextImageData->_iItem != m_iCurSlide)
|
|
{
|
|
m_ctlPreview.PrepareImageData(m_pNextImageData);
|
|
}
|
|
}
|
|
|
|
_TrySetImage();
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
// Creation of the image data is asynchronous. When our worker thread is done decoding
|
|
// an image it posts a IV_SETIMAGEDATA message with the image data object. As a result,
|
|
// we must flush these messages when the window is destroyed to prevent leaking any handles.
|
|
|
|
void CPreviewWnd::FlushBitmapMessages()
|
|
{
|
|
// Pass TRUE to wait for task to be removed before peeking out its messages
|
|
// Otherwise, if the task is in the middle of running, our PeekMessage won't
|
|
// see anything and we will return. Then the task will finish, post its message,
|
|
// and leak the data since we're not around to receive it.
|
|
TASKOWNERID toid;
|
|
GetTaskIDFromMode(GTIDFM_DECODE, m_dwMode, &toid);
|
|
if (m_pTaskScheduler)
|
|
{
|
|
m_pTaskScheduler->RemoveTasks(toid, ITSAT_DEFAULT_LPARAM, TRUE);
|
|
}
|
|
|
|
// if we were waiting for another image frame to be generated then cut it out, we don't care about that anymore
|
|
// if we have an animation timer running then kill it and remove any WM_TIMER messages
|
|
KillTimer(TIMER_ANIMATION);
|
|
KillTimer(TIMER_SLIDESHOW);
|
|
|
|
MSG msg;
|
|
while (PeekMessage(&msg, m_hWnd, WM_TIMER, WM_TIMER, 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;
|
|
}
|
|
}
|
|
|
|
// make sure any posted messages get flushed and we free the associated data
|
|
while (PeekMessage(&msg, m_hWnd, IV_SETIMAGEDATA, IV_SETIMAGEDATA, 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;
|
|
}
|
|
|
|
CDecodeTask * pData = (CDecodeTask *)msg.wParam;
|
|
ATOMICRELEASE(pData);
|
|
}
|
|
}
|
|
|
|
LRESULT CPreviewWnd::OnCopyData(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL&)
|
|
{
|
|
// We can get into a situation where we are still trying to preview
|
|
// the previous oncopydata because the previous window to that was a
|
|
// tiff that was being annotated and is prompting you to save. In this
|
|
// case throw away any future data
|
|
|
|
if (_pdtobj != NULL || m_fPromptingUser)
|
|
return TRUE;
|
|
|
|
COPYDATASTRUCT *pcds = (COPYDATASTRUCT*)lParam;
|
|
if (pcds)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
switch (pcds->dwData)
|
|
{
|
|
case COPYDATATYPE_DATAOBJECT:
|
|
{
|
|
IStream *pstm;
|
|
if (SUCCEEDED(CreateStreamOnHGlobal(NULL, TRUE, &pstm)))
|
|
{
|
|
const LARGE_INTEGER li = {0, 0};
|
|
|
|
pstm->Write(pcds->lpData, pcds->cbData, NULL);
|
|
pstm->Seek(li, STREAM_SEEK_SET, NULL);
|
|
|
|
// unfortunaly we can not program the data object here as we are in a
|
|
// SendMessage() and any calls made on the data object will fail because
|
|
// of this. instead we grab a ref to the data object and set a timer
|
|
// so we can handle this once we have unwound from the send.
|
|
|
|
hr = CoUnmarshalInterface(pstm, IID_PPV_ARG(IDataObject, &_pdtobj));
|
|
pstm->Release();
|
|
}
|
|
}
|
|
break;
|
|
case COPYDATATYPE_FILENAME:
|
|
{
|
|
hr = GetUIObjectFromPath((LPCTSTR)pcds->lpData, IID_PPV_ARG(IDataObject, &_pdtobj));
|
|
}
|
|
break;
|
|
}
|
|
// unfortunaly we can not program the data object here as we are in a
|
|
// SendMessage() and any calls made on the data object will fail because
|
|
// of this. instead we grab a ref to the data object and set a timer
|
|
// so we can handle this once we have unwound from the send.
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
SetTimer(TIMER_DATAOBJECT, 100); // do the real work here
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
DWORD MakeFilterFromCodecs(LPTSTR szFilter, size_t cbFilter, UINT nCodecs, ImageCodecInfo *pCodecs, LPTSTR szExt, BOOL fExcludeTiff)
|
|
{
|
|
size_t nOffset = 0;
|
|
DWORD dwRet = 1;
|
|
for (UINT i = 0; i < nCodecs && nOffset < cbFilter - 1; i++)
|
|
{
|
|
if (fExcludeTiff && StrStrI(pCodecs->FilenameExtension, L"*.tif"))
|
|
{
|
|
continue;
|
|
}
|
|
// make sure there's space for nulls between strings and 2 at the end
|
|
if (4+lstrlen(pCodecs->FormatDescription) + lstrlen(pCodecs->FilenameExtension) + nOffset < cbFilter)
|
|
{
|
|
StrCpyN(szFilter+nOffset,pCodecs->FormatDescription, cbFilter -(nOffset + 1));
|
|
nOffset+=lstrlen(pCodecs->FormatDescription)+1;
|
|
StrCpyN(szFilter+nOffset,pCodecs->FilenameExtension, cbFilter -(nOffset + 1));
|
|
nOffset+=lstrlen(pCodecs->FilenameExtension)+1;
|
|
if (StrStrI(pCodecs->FilenameExtension, szExt))
|
|
{
|
|
dwRet = i + 1;
|
|
}
|
|
pCodecs++;
|
|
}
|
|
}
|
|
szFilter[nOffset] = 0;
|
|
return dwRet;
|
|
}
|
|
|
|
DWORD CPreviewWnd::_GetFilterStringForSave(LPTSTR szFilter, size_t cbFilter, LPTSTR szExt)
|
|
{
|
|
UINT nCodecs = 0;
|
|
UINT cbCodecs = 0;
|
|
BYTE *pData;
|
|
GetImageEncodersSize (&nCodecs, &cbCodecs);
|
|
DWORD dwRet = 1; // ofn.nFilterIndex is 1-based
|
|
if (cbCodecs)
|
|
{
|
|
pData = new BYTE[cbCodecs];
|
|
if (pData)
|
|
{
|
|
ImageCodecInfo *pCodecs = reinterpret_cast<ImageCodecInfo*>(pData);
|
|
if (Ok == GetImageEncoders (nCodecs, cbCodecs, pCodecs))
|
|
{
|
|
dwRet = MakeFilterFromCodecs(szFilter, cbFilter, nCodecs, pCodecs, szExt, m_pImageData->IsExtendedPixelFmt());
|
|
}
|
|
delete [] pData;
|
|
}
|
|
}
|
|
return dwRet;
|
|
}
|
|
|
|
HRESULT CPreviewWnd::SaveAs(BSTR bstrPath)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
|
|
if (m_pImageData && m_pImageFactory)
|
|
{
|
|
IShellImageData * pSID;
|
|
hr = m_pImageData->Lock(&pSID);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
GUID guidFmt;
|
|
if (SUCCEEDED(m_pImageFactory->GetDataFormatFromPath(bstrPath, &guidFmt)))
|
|
{
|
|
IPropertyBag *pbagEnc;
|
|
hr = SHCreatePropertyBagOnMemory(STGM_READWRITE, IID_PPV_ARG(IPropertyBag, &pbagEnc));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
VARIANT var;
|
|
hr = InitVariantFromGUID(&var, guidFmt);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pbagEnc->Write(SHIMGKEY_RAWFORMAT, &var);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pSID->SetEncoderParams(pbagEnc);
|
|
}
|
|
VariantClear(&var);
|
|
}
|
|
pbagEnc->Release();
|
|
}
|
|
}
|
|
|
|
IPersistFile *ppf;
|
|
hr = pSID->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = ppf->Save(bstrPath, TRUE);
|
|
ppf->Release();
|
|
}
|
|
m_pImageData->Unlock();
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
BOOL CPreviewWnd::_IsImageFile(LPCTSTR pszFile)
|
|
{
|
|
BOOL bRet = FALSE;
|
|
if (m_pici || _BuildDecoderList())
|
|
{
|
|
bRet = (-1 != FindInDecoderList(m_pici, m_cDecoders, pszFile));
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
BOOL CPreviewWnd::_BuildDecoderList()
|
|
{
|
|
UINT cb;
|
|
BOOL bRet = FALSE;
|
|
if (Ok == GetImageDecodersSize(&m_cDecoders, &cb))
|
|
{
|
|
m_pici = (ImageCodecInfo*)LocalAlloc(LPTR, cb);
|
|
if (m_pici)
|
|
{
|
|
if (Ok != GetImageDecoders(m_cDecoders, cb, m_pici))
|
|
{
|
|
LocalFree(m_pici);
|
|
m_pici = NULL;
|
|
}
|
|
else
|
|
{
|
|
bRet = TRUE;
|
|
}
|
|
}
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
void CPreviewWnd::OpenFileList(HWND hwnd, IDataObject *pdtobj)
|
|
{
|
|
if (NULL == hwnd)
|
|
hwnd = m_hWnd;
|
|
|
|
IStream *pstm;
|
|
HRESULT hr = CreateStreamOnHGlobal(NULL, TRUE, &pstm);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = CoMarshalInterface(pstm, IID_IDataObject, pdtobj, MSHCTX_NOSHAREDMEM, NULL, MSHLFLAGS_NORMAL);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
HGLOBAL hGlobal;
|
|
hr = GetHGlobalFromStream(pstm, &hGlobal);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
COPYDATASTRUCT cds = {0};
|
|
cds.dwData = COPYDATATYPE_DATAOBJECT;
|
|
cds.cbData = (DWORD)GlobalSize(hGlobal);
|
|
cds.lpData = GlobalLock(hGlobal);
|
|
SendMessage(hwnd, WM_COPYDATA, NULL, (LPARAM)&cds);
|
|
SetForegroundWindow(hwnd);
|
|
|
|
GlobalUnlock(hGlobal);
|
|
}
|
|
}
|
|
pstm->Release();
|
|
}
|
|
}
|
|
|
|
void CPreviewWnd::OpenFile(HWND hwnd, LPCTSTR pszFile)
|
|
{
|
|
if (NULL == hwnd)
|
|
hwnd = m_hWnd;
|
|
|
|
COPYDATASTRUCT cds = {0};
|
|
cds.dwData = COPYDATATYPE_FILENAME;
|
|
cds.cbData = (lstrlen(pszFile)+1)*sizeof(TCHAR);
|
|
cds.lpData = (void*)pszFile;
|
|
SendMessage(hwnd, WM_COPYDATA, NULL, (LPARAM)&cds);
|
|
SetForegroundWindow(hwnd);
|
|
}
|
|
|
|
// returns:
|
|
// TRUE window was re-used
|
|
|
|
BOOL CPreviewWnd::TryWindowReuse(IDataObject *pdtobj)
|
|
{
|
|
BOOL bRet = FALSE;
|
|
HWND hwnd = FindWindow(TEXT("ShImgVw:CPreviewWnd"), NULL);
|
|
if (hwnd)
|
|
{
|
|
// window reuse can't always work because shortcuts are launched on a thread that
|
|
// is too short lived to support the marshalled IDataObject given to us via WM_COPYDATA
|
|
// For now we'll try to close an existing window and open a new one.
|
|
::PostMessage(hwnd, WM_CLOSE, 0, 0);
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
// returns:
|
|
// TRUE window was re-used
|
|
|
|
BOOL CPreviewWnd::TryWindowReuse(LPCTSTR pszFileName)
|
|
{
|
|
BOOL bRet = FALSE;
|
|
HWND hwnd = FindWindow(TEXT("ShImgVw:CPreviewWnd"), NULL);
|
|
if (hwnd)
|
|
{
|
|
DWORD_PTR dwResult = FALSE;
|
|
SendMessageTimeout(hwnd, IV_ISAVAILABLE, 0, 0, SMTO_ABORTIFHUNG | SMTO_BLOCK, 1000, &dwResult);
|
|
if (dwResult)
|
|
{
|
|
OpenFile(hwnd, pszFileName);
|
|
bRet = TRUE;
|
|
}
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
STDMETHODIMP CPreviewWnd::QueryInterface(REFIID riid, void **ppv)
|
|
{
|
|
static const QITAB qit[] =
|
|
{
|
|
QITABENT(CPreviewWnd, IDropTarget),
|
|
QITABENT(CPreviewWnd, INamespaceWalkCB),
|
|
QITABENT(CPreviewWnd, IServiceProvider),
|
|
QITABENT(CPreviewWnd, IImgCmdTarget),
|
|
{ 0 },
|
|
};
|
|
return QISearch(this, qit, riid, ppv);
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CPreviewWnd::AddRef()
|
|
{
|
|
return 3;
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CPreviewWnd::Release()
|
|
{
|
|
return 2;
|
|
}
|
|
|
|
// INamespaceWalkCB
|
|
STDMETHODIMP CPreviewWnd::FoundItem(IShellFolder *psf, LPCITEMIDLIST pidl)
|
|
{
|
|
HRESULT hr = S_FALSE;
|
|
|
|
if (m_fFirstItem && (WINDOW_MODE == m_dwMode))
|
|
{
|
|
// REVIEW: Do this in other modes too?
|
|
StatusUpdate(IDS_LOADING);
|
|
m_fFirstItem = FALSE;
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
{
|
|
TCHAR szName[MAX_PATH];
|
|
DisplayNameOf(psf, pidl, SHGDN_FORPARSING | SHGDN_INFOLDER, szName, ARRAYSIZE(szName));
|
|
if (_IsImageFile(szName))
|
|
{
|
|
hr = S_OK;
|
|
}
|
|
|
|
}
|
|
if (WINDOW_MODE == m_dwMode)
|
|
{
|
|
MSG msg;
|
|
while (PeekMessage(&msg, m_hWnd, 0, 0, PM_REMOVE))
|
|
{
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CPreviewWnd::EnterFolder(IShellFolder *psf, LPCITEMIDLIST pidl)
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CPreviewWnd::LeaveFolder(IShellFolder *psf, LPCITEMIDLIST pidl)
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
// IDropTarget
|
|
STDMETHODIMP CPreviewWnd::DragEnter(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
|
|
{
|
|
m_dwEffect = DROPEFFECT_NONE;
|
|
//
|
|
// We only support CFSTR_SHELLIDLIST and CF_HDROP
|
|
//
|
|
static CLIPFORMAT cfidlist = 0;
|
|
if (!cfidlist)
|
|
{
|
|
cfidlist = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_SHELLIDLIST);
|
|
}
|
|
FORMATETC fmt = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
|
if (SUCCEEDED(pdtobj->QueryGetData(&fmt)))
|
|
{
|
|
m_dwEffect = DROPEFFECT_COPY;
|
|
}
|
|
else
|
|
{
|
|
fmt.cfFormat = cfidlist;
|
|
if (SUCCEEDED(pdtobj->QueryGetData(&fmt)))
|
|
{
|
|
m_dwEffect = DROPEFFECT_COPY;
|
|
}
|
|
}
|
|
*pdwEffect &= m_dwEffect;
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CPreviewWnd::DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
|
|
{
|
|
*pdwEffect &= m_dwEffect;
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CPreviewWnd::DragLeave()
|
|
{
|
|
m_dwEffect = DROPEFFECT_NONE;
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CPreviewWnd::Drop(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
|
|
{
|
|
if (m_dwEffect != DROPEFFECT_NONE)
|
|
{
|
|
PreviewItemsFromUnk(pdtobj);
|
|
}
|
|
*pdwEffect &= m_dwEffect;
|
|
return S_OK;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// IServiceProvider
|
|
//
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
STDMETHODIMP CPreviewWnd::QueryService(REFGUID guidService, REFIID riid, void **ppv)
|
|
{
|
|
if (SID_SImageView == guidService)
|
|
{
|
|
return QueryInterface(riid, ppv);
|
|
}
|
|
else if (m_punkSite)
|
|
{
|
|
return IUnknown_QueryService(m_punkSite, guidService, riid, ppv);
|
|
}
|
|
return E_FAIL;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// IImgCmdTarget
|
|
//
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
STDMETHODIMP CPreviewWnd::GetMode(DWORD * pdw)
|
|
{
|
|
*pdw = m_dwMode;
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CPreviewWnd::GetPageFlags(DWORD * pdw)
|
|
{
|
|
*pdw = m_dwMultiPageMode;
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CPreviewWnd::ZoomIn()
|
|
{
|
|
if (SLIDESHOW_MODE == m_dwMode)
|
|
{
|
|
m_ctlPreview.ZoomIn();
|
|
}
|
|
else
|
|
{
|
|
m_ctlPreview.SetMode(CZoomWnd::MODE_ZOOMIN);
|
|
m_ctlPreview.ZoomIn();
|
|
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_ACTUALSIZECMD, MAKELONG(TRUE, 0));
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_BESTFITCMD, MAKELONG(TRUE, 0));
|
|
|
|
_UpdateButtons(ID_ZOOMINCMD);
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CPreviewWnd::ZoomOut()
|
|
{
|
|
if (SLIDESHOW_MODE == m_dwMode)
|
|
{
|
|
m_ctlPreview.ZoomOut();
|
|
}
|
|
else
|
|
{
|
|
m_ctlPreview.SetMode(CZoomWnd::MODE_ZOOMOUT);
|
|
m_ctlPreview.ZoomOut();
|
|
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_ACTUALSIZECMD, MAKELONG(TRUE, 0));
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_BESTFITCMD, MAKELONG(!m_ctlPreview.IsBestFit(), 0));
|
|
|
|
_UpdateButtons(ID_ZOOMOUTCMD);
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CPreviewWnd::ActualSize()
|
|
{
|
|
_RefreshSelection(FALSE);
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_ACTUALSIZECMD, MAKELONG(FALSE, 0));
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_BESTFITCMD, MAKELONG(TRUE, 0));
|
|
|
|
m_ctlPreview.ActualSize();
|
|
if (m_pEvents)
|
|
{
|
|
m_pEvents->OnActualSizePress();
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CPreviewWnd::BestFit()
|
|
{
|
|
_RefreshSelection(FALSE);
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_ACTUALSIZECMD, MAKELONG(TRUE, 0));
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_BESTFITCMD, MAKELONG(FALSE, 0));
|
|
|
|
m_ctlPreview.BestFit();
|
|
if (m_pEvents)
|
|
{
|
|
m_pEvents->OnBestFitPress();
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CPreviewWnd::Rotate(DWORD dwAngle)
|
|
{
|
|
WORD wRotate;
|
|
switch (dwAngle)
|
|
{
|
|
case 90:
|
|
wRotate = ID_ROTATE90CMD;
|
|
break;
|
|
|
|
case 270:
|
|
wRotate = ID_ROTATE270CMD;
|
|
break;
|
|
|
|
default:
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
// If we don't have an image yet, there is nothing for us to do.
|
|
// Note: The keyboard accelerator will hit this path if no image is selected
|
|
if (!m_pImageData)
|
|
return E_FAIL;
|
|
|
|
// We quietly (the button is disabled but just in case you hit the
|
|
// accelerator key) don't rotate WMF or EMF.
|
|
if (IsEqualGUID(ImageFormatWMF, m_pImageData->_guidFormat) || IsEqualGUID(ImageFormatEMF, m_pImageData->_guidFormat))
|
|
return E_FAIL;
|
|
|
|
|
|
// Animated GIFs are not editable even though normal GIFs are. This can
|
|
// cause a lot of confusion, so provide some feedback if the user tries
|
|
// to rotate an animated image.
|
|
if (m_pImageData->IsAnimated())
|
|
{
|
|
TCHAR szPath[MAX_PATH];
|
|
PathFromImageData(szPath, ARRAYSIZE(szPath));
|
|
m_fPromptingUser = TRUE;
|
|
ShellMessageBox(_Module.GetModuleInstance(), m_hWnd, MAKEINTRESOURCE(IDS_ROTATE_MESSAGE), MAKEINTRESOURCE(IDS_PROJNAME), MB_OK | MB_ICONERROR, szPath);
|
|
m_fPromptingUser = FALSE;
|
|
return E_FAIL;
|
|
}
|
|
|
|
|
|
// From here on out you need to goto ErrorCleanup rather than return
|
|
_UpdateButtons(wRotate);
|
|
SetCursorState(SLIDESHOW_CURSOR_BUSY);
|
|
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_ROTATE90CMD, MAKELONG(FALSE, 0));
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_ROTATE270CMD, MAKELONG(FALSE, 0));
|
|
m_ctlToolbar.UpdateWindow();
|
|
|
|
if (m_pTaskScheduler)
|
|
{
|
|
TASKOWNERID toid;
|
|
GetTaskIDFromMode(GTIDFM_DRAW, m_dwMode, &toid);
|
|
m_pTaskScheduler->RemoveTasks(toid, ITSAT_DEFAULT_LPARAM, TRUE);
|
|
}
|
|
|
|
HRESULT hr = E_FAIL;
|
|
SIZE sz;
|
|
m_pImageData->GetSize(&sz);
|
|
|
|
// if we're thinking we can quietly save
|
|
if (m_pImageData->IsEditable() && !m_fDisableEdit && m_fCanSave)
|
|
{
|
|
// And the rotation might be lossy
|
|
if (::IsEqualGUID(ImageFormatJPEG, m_pImageData->_guidFormat) && ((sz.cx % 16 != 0) || (sz.cy % 16 != 0)))
|
|
{
|
|
int nResult = IDOK;
|
|
|
|
if (m_fWarnQuietSave)
|
|
{
|
|
CComBSTR bstrMsg, bstrTitle;
|
|
if (bstrMsg.LoadString(IDS_ROTATE_LOSS) && bstrTitle.LoadString(IDS_PROJNAME))
|
|
{
|
|
// Set default to return IDOK so we know if the user selected something or
|
|
// if the "don't show me this again" bit was respected
|
|
m_fPromptingUser = TRUE;
|
|
nResult = SHMessageBoxCheck(m_hWnd, bstrMsg, bstrTitle, MB_YESNO|MB_ICONWARNING, IDOK, REGSTR_LOSSYROTATE);
|
|
m_fPromptingUser = FALSE;
|
|
}
|
|
|
|
if (nResult != IDNO)
|
|
m_fWarnQuietSave = FALSE;
|
|
}
|
|
|
|
CRegKey Key;
|
|
if (ERROR_SUCCESS != Key.Open(HKEY_CURRENT_USER, REGSTR_SHIMGVW))
|
|
{
|
|
Key.Create(HKEY_CURRENT_USER, REGSTR_SHIMGVW);
|
|
}
|
|
|
|
if (Key.m_hKey != NULL)
|
|
{
|
|
if (nResult == IDOK) // If hidden, then load last result from registry
|
|
{
|
|
DWORD dwResult = 0;
|
|
Key.QueryValue(dwResult, REGSTR_LOSSYROTATE);
|
|
nResult = (int)dwResult;
|
|
}
|
|
else // Otherwise, write this as last result to registry
|
|
{
|
|
DWORD dwResult = (DWORD)nResult;
|
|
Key.SetValue(dwResult, REGSTR_LOSSYROTATE);
|
|
}
|
|
}
|
|
|
|
if (nResult == IDNO)
|
|
goto ErrorCleanup;
|
|
}
|
|
}
|
|
|
|
CAnnotationSet* pAnnotations = m_ctlPreview.GetAnnotations();
|
|
INT_PTR nCount = pAnnotations->GetCount();
|
|
for (INT_PTR i = 0; i < nCount; i++)
|
|
{
|
|
CAnnotation* pAnnotation = pAnnotations->GetAnnotation(i);
|
|
pAnnotation->Rotate(m_ctlPreview.m_cyImage, m_ctlPreview.m_cxImage, (ID_ROTATE90CMD == wRotate));
|
|
}
|
|
|
|
m_ctlPreview.CommitAnnotations();
|
|
|
|
hr = m_pImageData->Rotate(dwAngle);
|
|
if (FAILED(hr))
|
|
goto ErrorCleanup;
|
|
|
|
// Only if we have an encoder and we haven't been explicitly told not to edit and the source is writeable
|
|
if (m_pImageData->IsEditable() && !m_fDisableEdit && m_fCanSave)
|
|
{
|
|
// on successful edit we immediately save the result. If we want to do multiple edits
|
|
// before saving then you would simply need to wait and call Save later.
|
|
|
|
// NB: We currently only allow editing of items loaded from file system paths, no path means
|
|
// no edit. This is stupid, but that's how it is for now.
|
|
hr = ImageDataSave(NULL, FALSE);
|
|
if (SUCCEEDED(hr))
|
|
m_fDirty = FALSE;
|
|
else
|
|
{
|
|
// if we failed to save then go into can't save mode
|
|
if (WINDOW_MODE == m_dwMode)
|
|
m_fCanSave = FALSE;
|
|
}
|
|
}
|
|
|
|
_UpdateImage();
|
|
|
|
if ((!m_pImageData->IsEditable() || !m_fCanSave) && WINDOW_MODE == m_dwMode)
|
|
{
|
|
if (m_fWarnNoSave)
|
|
{
|
|
m_fWarnNoSave = FALSE;
|
|
|
|
CComBSTR bstrMsg, bstrTitle;
|
|
if (bstrMsg.LoadString(IDS_ROTATE_CANTSAVE) && bstrTitle.LoadString(IDS_PROJNAME))
|
|
{
|
|
m_fPromptingUser = TRUE;
|
|
SHMessageBoxCheck(m_hWnd, bstrMsg, bstrTitle, MB_OK|MB_ICONWARNING, IDOK, REGSTR_SAVELESS);
|
|
m_fPromptingUser = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
ErrorCleanup:
|
|
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_ROTATE90CMD, MAKELONG(TRUE, 0));
|
|
m_ctlToolbar.SendMessage(TB_ENABLEBUTTON, ID_ROTATE270CMD, MAKELONG(TRUE, 0));
|
|
|
|
SetCursorState(SLIDESHOW_CURSOR_NOTBUSY);
|
|
|
|
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CPreviewWnd::NextPage()
|
|
{
|
|
return _PrevNextPage(TRUE);
|
|
}
|
|
|
|
STDMETHODIMP CPreviewWnd::PreviousPage()
|
|
{
|
|
return _PrevNextPage(FALSE);
|
|
}
|
|
|
|
HRESULT CPreviewWnd::_PrevNextPage(BOOL fForward)
|
|
{
|
|
_RefreshSelection(FALSE);
|
|
if (m_pImageData && m_pImageData->IsMultipage())
|
|
{
|
|
if (m_fDirty)
|
|
{
|
|
m_ctlPreview.CommitAnnotations();
|
|
}
|
|
if (fForward)
|
|
{
|
|
m_pImageData->NextPage();
|
|
}
|
|
else
|
|
{
|
|
m_pImageData->PrevPage();
|
|
}
|
|
_UpdateImage();
|
|
_SetMultipageCommands();
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
//
|
|
// When the user saves to a format other than TIFF and the current
|
|
// TIFF has annotations, we need to burn annotations
|
|
// into the current image frame before saving.
|
|
// If we ever support other multi-page format encoding besides TIFF, this
|
|
// code will get more complicated
|
|
// assumes the pSID is already locked
|
|
// note that the resulting image is always a color image. Eventually we should make
|
|
// the annotation rendering code respect the bit depth and palette of the
|
|
// current image.
|
|
|
|
Image *CPreviewWnd::_BurnAnnotations(IShellImageData *pSID)
|
|
{
|
|
Image *pimg = NULL;
|
|
|
|
if (SUCCEEDED(pSID->CloneFrame(&pimg)))
|
|
{
|
|
HDC hdc = ::GetDC(NULL);
|
|
if (hdc)
|
|
{
|
|
LPVOID pBits;
|
|
BITMAPINFO bi = {0};
|
|
|
|
bi.bmiHeader.biBitCount = 24;
|
|
bi.bmiHeader.biHeight = pimg->GetHeight();
|
|
bi.bmiHeader.biWidth = pimg->GetWidth();
|
|
bi.bmiHeader.biPlanes = 1;
|
|
bi.bmiHeader.biCompression = BI_RGB;
|
|
bi.bmiHeader.biSize = sizeof(bi.bmiHeader);
|
|
|
|
HBITMAP hbm = CreateDIBSection(hdc, &bi, DIB_RGB_COLORS, &pBits, NULL, 0);
|
|
if (hbm)
|
|
{
|
|
//
|
|
// For ROP codes to work we need to use pure GDI, then convert the new
|
|
// DIBSection back to an Image object
|
|
//
|
|
HDC hdcMem = ::CreateCompatibleDC(hdc);
|
|
Status s = GenericError;
|
|
if (hdcMem)
|
|
{
|
|
HBITMAP hbmOld = (HBITMAP)::SelectObject(hdcMem, hbm);
|
|
Graphics *g = Graphics::FromHDC(hdcMem);
|
|
if (g)
|
|
{
|
|
s = g->DrawImage(pimg, 0L, 0L, pimg->GetWidth(), pimg->GetHeight());
|
|
g->ReleaseHDC(hdcMem);
|
|
delete g;
|
|
// now draw the annotations
|
|
m_ctlPreview.GetAnnotations()->RenderAllMarks(hdcMem);
|
|
}
|
|
::SelectObject(hdcMem, hbmOld);
|
|
::DeleteDC(hdcMem);
|
|
}
|
|
if (Ok == s)
|
|
{
|
|
//
|
|
// Now create a new Bitmap from our DIBSection
|
|
Bitmap *pbmNew = Bitmap::FromHBITMAP(hbm, NULL);
|
|
if (pbmNew)
|
|
{
|
|
pSID->ReplaceFrame(pbmNew);
|
|
}
|
|
}
|
|
DeleteObject(hbm);
|
|
}
|
|
::ReleaseDC(NULL, hdc);
|
|
}
|
|
}
|
|
return pimg;
|
|
}
|
|
|
|
void CPreviewWnd::_InvokePrintWizard()
|
|
{
|
|
if (m_fPrintable)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
if (m_fDirty)
|
|
{
|
|
m_ctlPreview.CommitAnnotations();
|
|
hr = ImageDataSave(NULL, FALSE);
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_fPromptingUser = TRUE;
|
|
m_fDirty = FALSE;
|
|
|
|
IPrintPhotosWizardSetInfo *pwiz;
|
|
HRESULT hr = CoCreateInstance(CLSID_PrintPhotosWizard,
|
|
NULL, CLSCTX_INPROC_SERVER,
|
|
IID_PPV_ARG(IPrintPhotosWizardSetInfo, &pwiz));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (m_pImageData != NULL && m_pImageData->_guidFormat == ImageFormatTIFF && m_pImageData->IsMultipage())
|
|
hr = pwiz->SetFileListArray(&(m_ppidls[m_iCurSlide]), 1, 0);
|
|
else
|
|
hr = pwiz->SetFileListArray(m_ppidls, m_cItems, m_iCurSlide);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pwiz->RunWizard();
|
|
}
|
|
pwiz->Release();
|
|
}
|
|
// fall back to the shell if the wizard fails
|
|
if (FAILED(hr))
|
|
{
|
|
_InvokeVerb(TEXT("print"));
|
|
}
|
|
m_fPromptingUser = FALSE;
|
|
}
|
|
else
|
|
{
|
|
CComBSTR bstrMsg, bstrTitle;
|
|
|
|
if (bstrMsg.LoadString(IDS_SAVEFAILED_MSGBOX) && bstrTitle.LoadString(IDS_PROJNAME))
|
|
{
|
|
m_fPromptingUser = TRUE;
|
|
MessageBox(bstrMsg, bstrTitle, MB_OK | MB_ICONERROR | MB_APPLMODAL);
|
|
m_fPromptingUser = FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GetTaskIDFromMode(DWORD dwTask, DWORD dwMode, TASKOWNERID *ptoid)
|
|
{
|
|
switch (dwTask)
|
|
{
|
|
case GTIDFM_DECODE:
|
|
*ptoid = (SLIDESHOW_MODE == dwMode) ? TOID_SlideshowDecode : TOID_PrimaryDecode;
|
|
break;
|
|
|
|
case GTIDFM_DRAW:
|
|
*ptoid = (SLIDESHOW_MODE == dwMode) ? TOID_DrawSlideshowFrame : TOID_DrawFrame;
|
|
break;
|
|
|
|
default:
|
|
ASSERTMSG(FALSE, "someone passed bad task to GetTaskIDFromMode");
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// Watch for changes in the file we are currently viewing. This ignores changes
|
|
// in the file being pre-fetched, but we'll live with that for now.
|
|
//
|
|
void CPreviewWnd::_RegisterForChangeNotify(BOOL fRegister)
|
|
{
|
|
// always deregister the current pidl first
|
|
if (m_uRegister)
|
|
{
|
|
SHChangeNotifyDeregister(m_uRegister);
|
|
m_uRegister = 0;
|
|
}
|
|
if (fRegister)
|
|
{
|
|
SHChangeNotifyEntry cne = {0};
|
|
if (SUCCEEDED(_GetItem(m_iCurSlide, (LPITEMIDLIST*)&cne.pidl)))
|
|
{
|
|
m_uRegister = SHChangeNotifyRegister(m_hWnd,
|
|
SHCNRF_ShellLevel | SHCNRF_InterruptLevel | SHCNRF_NewDelivery,
|
|
SHCNE_DISKEVENTS,
|
|
IV_ONCHANGENOTIFY,
|
|
1, &cne);
|
|
ILFree((LPITEMIDLIST)cne.pidl);
|
|
}
|
|
}
|
|
}
|
|
|
|
LRESULT CPreviewWnd::OnChangeNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
|
|
{
|
|
// We can assume this notify is for the currently viewed PIDL and the event
|
|
// is one that would force us to reload
|
|
//
|
|
|
|
LONG lEvent;
|
|
LPSHChangeNotificationLock pshcnl = SHChangeNotification_Lock((HANDLE)wParam, (DWORD)lParam, NULL, &lEvent);
|
|
if (pshcnl)
|
|
{
|
|
// we can't render or manipulate deleted files so don't try
|
|
if (!m_fDirty || lEvent == SHCNE_DELETE || lEvent == SHCNE_RENAMEITEM)
|
|
{
|
|
if (!m_fIgnoreNextNotify)
|
|
{
|
|
if (!m_fIgnoreAllNotifies)
|
|
{
|
|
m_fDirty = FALSE;
|
|
_PreviewItem(m_iCurSlide);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_fIgnoreNextNotify = FALSE;
|
|
}
|
|
bHandled = TRUE;
|
|
}
|
|
|
|
SHChangeNotification_Unlock(pshcnl);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
LRESULT CPreviewWnd::OnIsAvailable(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
|
|
{
|
|
bHandled = TRUE;
|
|
return !m_fPromptingUser;
|
|
}
|