#include "precomp.h" #include "shimgvw.h" #include "cowsite.h" #include "prevwnd.h" #include "shutil.h" #include "prwiziid.h" #pragma hdrstop // Context menu offset IDs enum { OFFSET_OPEN = 0, OFFSET_PRINTTO, OFFSET_ROT90, OFFSET_ROT270, OFFSET_SETWALL, OFFSET_ZOOMIN, OFFSET_ZOOMOUT, OFFSET_ACTUALSIZE, OFFSET_BESTFIT, OFFSET_NEXTPAGE, OFFSET_PREVPAGE, OFFSET_MAX }; #define PHOTOVERBS_THUMBNAIL 0x1 #define PHOTOVERBS_ICON 0x2 #define PHOTOVERBS_FILMSTRIP 0x3 #define PHOTOVERBS_SLIDESHOW 0x4 #define PHOTOVERBS_IMGPREVIEW 0x5 class CPhotoVerbs : public IContextMenu, public IShellExtInit, public IDropTarget, public CObjectWithSite, public NonATLObject { public: CPhotoVerbs(); // IUnknown STDMETHOD(QueryInterface)(REFIID riid, void **ppv); STDMETHOD_(ULONG, AddRef)(); STDMETHOD_(ULONG, Release)(); // IShellExtInit STDMETHODIMP Initialize(LPCITEMIDLIST pIDFolder, IDataObject *pdtobj, HKEY hKeyID); // IContextMenu STDMETHODIMP QueryContextMenu(HMENU hMenu, UINT uIndex, UINT uIDFirst, UINT uIDLast, UINT uFlags); STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO pCMI); STDMETHODIMP GetCommandString(UINT_PTR uID, UINT uFlags, UINT *res, LPSTR pName, UINT ccMax); // IDropTarget *** STDMETHODIMP DragEnter(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect); STDMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect); STDMETHODIMP DragLeave(void); STDMETHODIMP Drop(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect); private: ~CPhotoVerbs(); void _RotatePictures(int iAngle, UINT idPrompt); void _OpenPictures(); void _SetWallpaper(); // HRESULT _InvokePrintToInPPW(LPCMINVOKECOMMANDINFO pCMI, IDataObject *pdtobj); BOOL _ImageOptionExists(IQueryAssociations *pqa, DWORD dwOption); HRESULT _QueryAssociations(); DWORD _GetMode(); BOOL _CheckForcePreview(IQueryAssociations *pqa); HRESULT _MapVerb(LPCMINVOKECOMMANDINFO pici, int *pidVerb); LONG _cRef; IDataObject *_pdtobj; BOOL _fForcePreview; BOOL _fAcceptPreview; BOOL _fIncludeRotate; BOOL _fIncludeSetWallpaper; IImgCmdTarget * _pict; // if hosted in image preview, this allows us to delegate commands to it BOOL _fImgMode; // TRUE if we are hosted in defview and defview is in thumbnail or filmstip mode BOOL _fReadOnly; // TRUE if one or more items selected are SFGAO_READONLY }; CPhotoVerbs::CPhotoVerbs() : _cRef(1) { ASSERT(_pdtobj == NULL); ASSERT(_fForcePreview == FALSE); ASSERT(_fIncludeRotate == FALSE); ASSERT(_fIncludeSetWallpaper == FALSE); } CPhotoVerbs::~CPhotoVerbs() { IUnknown_Set(&_punkSite, NULL); IUnknown_Set((IUnknown**)&_pdtobj, NULL); ATOMICRELEASE(_pict); } STDAPI CPhotoVerbs_CreateInstance(IUnknown* pUnkOuter, IUnknown** ppunk, LPCOBJECTINFO poi) { CPhotoVerbs *psid = new CPhotoVerbs(); if (!psid) { *ppunk = NULL; // incase of failure return E_OUTOFMEMORY; } HRESULT hr = psid->QueryInterface(IID_PPV_ARG(IUnknown, ppunk)); psid->Release(); return hr; } STDMETHODIMP CPhotoVerbs::QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = { QITABENT(CPhotoVerbs, IShellExtInit), QITABENT(CPhotoVerbs, IContextMenu), QITABENT(CPhotoVerbs, IDropTarget), QITABENT(CPhotoVerbs, IObjectWithSite), { 0 }, }; return QISearch(this, qit, riid, ppv); } STDMETHODIMP_(ULONG) CPhotoVerbs::AddRef() { return InterlockedIncrement(&_cRef); } STDMETHODIMP_(ULONG) CPhotoVerbs::Release() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; } // IShellExtInit STDMETHODIMP CPhotoVerbs::Initialize(LPCITEMIDLIST pIDFolder, IDataObject *pdtobj, HKEY hKeyID) { IUnknown_Set((IUnknown**)&_pdtobj, pdtobj); _fImgMode = FALSE; DWORD dwAttributes = 0; SHGetAttributesFromDataObject(pdtobj, SFGAO_READONLY, &dwAttributes, NULL); _fReadOnly = BOOLIFY(dwAttributes); return S_OK; } BOOL CPhotoVerbs::_ImageOptionExists(IQueryAssociations *pqa, DWORD dwOption) { BOOL fRetVal = FALSE; DWORD dwFlags = 0; DWORD cbFlags = sizeof(dwFlags); if (SUCCEEDED(pqa->GetData(0, ASSOCDATA_VALUE, TEXT("ImageOptionFlags"), &dwFlags, &cbFlags))) { fRetVal = (dwFlags & dwOption); } return fRetVal; } BOOL _VerbExists(IQueryAssociations *pqa, LPCTSTR pszVerb) { DWORD cch; return SUCCEEDED(pqa->GetString(ASSOCF_VERIFY, ASSOCSTR_EXECUTABLE, pszVerb, NULL, &cch)) && cch; } BOOL CPhotoVerbs::_CheckForcePreview(IQueryAssociations *pqa) { // we force if the app has no a preview and the user has not customized // and we are not the current default (we install on open) BOOL fRet = FALSE; if (!_VerbExists(pqa, TEXT("preview"))) { // if nobody owns we always accept // this is for when somebody does an InvokeCommand("preview"); _fAcceptPreview = TRUE; if (S_FALSE == pqa->GetData(0, ASSOCDATA_HASPERUSERASSOC, NULL, NULL, NULL)) { WCHAR sz[MAX_PATH]; DWORD cch = ARRAYSIZE(sz); _fForcePreview = FAILED(pqa->GetString(0, ASSOCSTR_COMMAND, NULL, sz, &cch)); if (!_fForcePreview) { // there is a default handler // if its us hide the preview verb // because the static menu will do it for us if (StrStrIW(sz, L"shimgvw.dll")) { _fAcceptPreview = FALSE; } else _fForcePreview = TRUE; } } } return fRet; } HRESULT CPhotoVerbs::_QueryAssociations() { IQueryAssociations *pqa; HRESULT hr = IUnknown_QueryService(_punkSite, SID_CtxQueryAssociations, IID_PPV_ARG(IQueryAssociations, &pqa)); if (SUCCEEDED(hr)) { // dont do preview if the user has customized _CheckForcePreview(pqa); _fIncludeRotate = _ImageOptionExists(pqa, IMAGEOPTION_CANROTATE); _fIncludeSetWallpaper = _ImageOptionExists(pqa, IMAGEOPTION_CANWALLPAPER); pqa->Release(); return S_OK; } else { // we may have been invoked directly instead of via ShellExecute or right-click _fAcceptPreview = TRUE; } return S_FALSE; } DWORD CPhotoVerbs::_GetMode() { DWORD dwMode, dw; if (_pict) { _pict->GetMode(&dw); switch (dw) { case SLIDESHOW_MODE: dwMode = PHOTOVERBS_SLIDESHOW; break; case WINDOW_MODE: dwMode = PHOTOVERBS_IMGPREVIEW; break; case CONTROL_MODE: dwMode = PHOTOVERBS_FILMSTRIP; break; default: dwMode = PHOTOVERBS_ICON; break; } } else { dwMode = (_fImgMode) ? PHOTOVERBS_THUMBNAIL : PHOTOVERBS_ICON; } return dwMode; } // IContextMenu STDMETHODIMP CPhotoVerbs::QueryContextMenu(HMENU hMenu, UINT uIndex, UINT uIDFirst, UINT uIDLast, UINT uFlags) { TCHAR szBuffer[128]; HRESULT hr = _QueryAssociations(); DWORD dwMultiPage = MPCMD_HIDDEN; hr = IUnknown_QueryService(_punkSite, SID_SImageView, IID_PPV_ARG(IImgCmdTarget, &_pict)); if (SUCCEEDED(hr)) { _pict->GetPageFlags(&dwMultiPage); } IFolderView * pfv = NULL; hr = IUnknown_QueryService(_punkSite, SID_SFolderView, IID_PPV_ARG(IFolderView, &pfv)); if (SUCCEEDED(hr)) { UINT uViewMode; hr = pfv->GetCurrentViewMode(&uViewMode); if (SUCCEEDED(hr) && ((FVM_THUMBNAIL == uViewMode) || (FVM_THUMBSTRIP == uViewMode))) { _fImgMode = TRUE; } pfv->Release(); } DWORD dwMode = _GetMode(); // always load the Open verb if no static Open verb is registered if (_fAcceptPreview) { if (PHOTOVERBS_SLIDESHOW != dwMode && PHOTOVERBS_IMGPREVIEW != dwMode) { LoadString(_Module.GetModuleInstance(), IDS_PREVIEW_CTX, szBuffer, ARRAYSIZE(szBuffer)); InsertMenu(hMenu, uIndex, MF_BYPOSITION | MF_STRING, uIDFirst + OFFSET_OPEN, szBuffer); // only set to default if there isnt a preview already there if (_fForcePreview) SetMenuDefaultItem(hMenu, uIndex, MF_BYPOSITION); uIndex++; } } if (!(uFlags & CMF_DEFAULTONLY)) { InsertMenu(hMenu, uIndex++, MF_BYPOSITION | MF_SEPARATOR, 0, NULL); if (_fIncludeRotate) { if (PHOTOVERBS_ICON != dwMode) { UINT uFlags = MF_BYPOSITION | MF_STRING; if (_fReadOnly && PHOTOVERBS_THUMBNAIL == dwMode) { uFlags |= MF_GRAYED; } else { uFlags |= MF_ENABLED; // in all modes by thumbnails, we allow temporary rotation of readonly images } LoadString(_Module.GetModuleInstance(), IDS_ROTATE90_CTX, szBuffer, ARRAYSIZE(szBuffer)); InsertMenu(hMenu, uIndex++, uFlags, uIDFirst + OFFSET_ROT90, szBuffer); LoadString(_Module.GetModuleInstance(), IDS_ROTATE270_CTX, szBuffer, ARRAYSIZE(szBuffer)); InsertMenu(hMenu, uIndex++, uFlags, uIDFirst + OFFSET_ROT270, szBuffer); } } if (PHOTOVERBS_IMGPREVIEW == dwMode) { LoadString(_Module.GetModuleInstance(), IDS_ZOOMIN_CTX, szBuffer, ARRAYSIZE(szBuffer)); InsertMenu(hMenu, uIndex++, MF_BYPOSITION | MF_STRING, uIDFirst + OFFSET_ZOOMIN, szBuffer); LoadString(_Module.GetModuleInstance(), IDS_ZOOMOUT_CTX, szBuffer, ARRAYSIZE(szBuffer)); InsertMenu(hMenu, uIndex++, MF_BYPOSITION | MF_STRING, uIDFirst + OFFSET_ZOOMOUT, szBuffer); if (dwMultiPage != MPCMD_HIDDEN && dwMultiPage != MPCMD_DISABLED) { if (MPCMD_LASTPAGE != dwMultiPage) { LoadString(_Module.GetModuleInstance(), IDS_NEXTPAGE_CTX, szBuffer, ARRAYSIZE(szBuffer)); InsertMenu(hMenu, uIndex++, MF_BYPOSITION | MF_STRING, uIDFirst + OFFSET_NEXTPAGE, szBuffer); } if (MPCMD_FIRSTPAGE != dwMultiPage) { LoadString(_Module.GetModuleInstance(), IDS_PREVPAGE_CTX, szBuffer, ARRAYSIZE(szBuffer)); InsertMenu(hMenu, uIndex++, MF_BYPOSITION | MF_STRING, uIDFirst + OFFSET_PREVPAGE, szBuffer); } } } InsertMenu(hMenu, uIndex++, MF_BYPOSITION | MF_SEPARATOR, 0, NULL); if (_fIncludeSetWallpaper) { if (PHOTOVERBS_ICON != dwMode) { LoadString(_Module.GetModuleInstance(), IDS_WALLPAPER_CTX, szBuffer, ARRAYSIZE(szBuffer)); InsertMenu(hMenu, uIndex++, MF_BYPOSITION | MF_STRING, uIDFirst + OFFSET_SETWALL, szBuffer); } } } return MAKE_HRESULT(SEVERITY_SUCCESS, 0, OFFSET_MAX); } // IDropTarget::DragEnter HRESULT CPhotoVerbs::DragEnter(IDataObject *pdtobj, DWORD grfKeyState, POINTL ptl, DWORD *pdwEffect) { *pdwEffect = DROPEFFECT_COPY; return S_OK;; } // IDropTarget::DragOver HRESULT CPhotoVerbs::DragOver(DWORD grfKeyState, POINTL ptl, DWORD *pdwEffect) { *pdwEffect = DROPEFFECT_COPY; return S_OK;; } // IDropTarget::DragLeave HRESULT CPhotoVerbs::DragLeave(void) { return S_OK; } // IDropTarget::DragDrop HRESULT CPhotoVerbs::Drop(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { *pdwEffect = DROPEFFECT_COPY; HRESULT hr = Initialize(NULL, pdtobj, NULL); if (SUCCEEDED(hr)) { // we may need to get the verb. _OpenPictures(); } return hr; } class VerbThreadProc : public NonATLObject { public: STDMETHOD_(ULONG, AddRef)(); STDMETHOD_(ULONG, Release)(); BOOL CreateVerbThread(); VerbThreadProc(IDataObject *pdo, IUnknown *punk, HRESULT *phr); protected: virtual DWORD VerbWithThreadRefCB() PURE; virtual DWORD VerbWithThreadRef() PURE; virtual DWORD VerbWithoutThreadRefCB() PURE; virtual DWORD VerbWithoutThreadRef() PURE; virtual ~VerbThreadProc(); IDataObject *_pdo; // the un-marshalled versions... IFolderView *_pfv; private: static DWORD s_WithThreadRef(void *pv); static DWORD s_WithThreadRefCB(void *pv); static DWORD s_WithoutThreadRef(void *pv); static DWORD s_WithoutThreadRefCB(void *pv); void Unmarshall(); LONG _cRef; IStream *_pstmDataObj; // the marshalled IDataObject stream IStream *_pstmFolderView; // the marshalled IFolderView stream }; VerbThreadProc::VerbThreadProc(IDataObject* pdo, IUnknown *punk, HRESULT *phr) { _cRef = 1; if (punk) { IFolderView *pfv = NULL; if (SUCCEEDED(IUnknown_QueryService(punk, SID_SFolderView, IID_PPV_ARG(IFolderView, &pfv)))) { CoMarshalInterThreadInterfaceInStream(IID_IFolderView, pfv, &_pstmFolderView); pfv->Release(); } } if (pdo) { CoMarshalInterThreadInterfaceInStream(IID_IDataObject, pdo, &_pstmDataObj); } *phr = (_pstmDataObj || _pstmFolderView) ? S_OK : E_OUTOFMEMORY; } VerbThreadProc::~VerbThreadProc() { ATOMICRELEASE(_pstmDataObj); ATOMICRELEASE(_pstmFolderView); ATOMICRELEASE(_pdo); ATOMICRELEASE(_pfv); } STDMETHODIMP_(ULONG) VerbThreadProc::AddRef() { return InterlockedIncrement(&_cRef); } STDMETHODIMP_(ULONG) VerbThreadProc::Release() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; } DWORD VerbThreadProc::s_WithThreadRefCB(void *pv) { VerbThreadProc *potd = (VerbThreadProc *)pv; potd->AddRef(); potd->Unmarshall(); return potd->VerbWithThreadRefCB(); } DWORD VerbThreadProc::s_WithThreadRef(void *pv) { VerbThreadProc *potd = (VerbThreadProc *)pv; DWORD dw = potd->VerbWithThreadRef(); potd->Release(); return dw; } DWORD VerbThreadProc::s_WithoutThreadRefCB(void *pv) { VerbThreadProc *potd = (VerbThreadProc *)pv; potd->AddRef(); potd->Unmarshall(); return potd->VerbWithoutThreadRefCB(); } DWORD VerbThreadProc::s_WithoutThreadRef(void *pv) { VerbThreadProc *potd = (VerbThreadProc *)pv; DWORD dw = potd->VerbWithoutThreadRef(); potd->Release(); return dw; } void VerbThreadProc::Unmarshall() { if (_pstmDataObj) { CoGetInterfaceAndReleaseStream(_pstmDataObj, IID_PPV_ARG(IDataObject, &_pdo)); _pstmDataObj = NULL; } if (_pstmFolderView) { CoGetInterfaceAndReleaseStream(_pstmFolderView, IID_PPV_ARG(IFolderView, &_pfv)); _pstmFolderView = NULL; } } BOOL VerbThreadProc::CreateVerbThread() { BOOL bRet; // The thread ref is the more efficient start-up method, but we need to // handle the case where the caller doesn't have one. bRet = SHCreateThread(s_WithThreadRef, this, CTF_COINIT | CTF_THREAD_REF, s_WithThreadRefCB); if (!bRet) { bRet = SHCreateThread(s_WithoutThreadRef, this, CTF_COINIT | CTF_WAIT_ALLOWCOM, s_WithoutThreadRefCB); } return bRet; } class OpenThreadProc : public VerbThreadProc { public: DWORD VerbWithThreadRefCB(); DWORD VerbWithThreadRef(); DWORD VerbWithoutThreadRefCB(); DWORD VerbWithoutThreadRef(); OpenThreadProc(IDataObject *pdo, IUnknown *punk, HRESULT *phr) : VerbThreadProc(pdo, punk, phr) {}; private: HRESULT Walk(); void Preview(); CPreviewWnd* _pPreview; }; DWORD OpenThreadProc::VerbWithThreadRefCB() { return 0; } DWORD OpenThreadProc::VerbWithThreadRef() { HRESULT hr = Walk(); SHReleaseThreadRef(); if (S_OK == hr) { Preview(); } return 0; } DWORD OpenThreadProc::VerbWithoutThreadRefCB() { Walk(); return 0; } DWORD OpenThreadProc::VerbWithoutThreadRef() { Preview(); return 0; } HRESULT OpenThreadProc::Walk() { HRESULT hr = E_OUTOFMEMORY; if (_pdo) { _pPreview = new CPreviewWnd(); if (_pPreview) { if (!_pPreview->TryWindowReuse(_pdo)) { hr = _pPreview->Initialize(NULL, WINDOW_MODE, FALSE); if (SUCCEEDED(hr)) { // create the viewer window before doing the expensive namespace walk // so if a second instance is created it will find the window if (_pPreview->CreateViewerWindow()) { hr = _pPreview->WalkItemsToPreview(_pfv ? (IUnknown *)_pfv: (IUnknown *)_pdo); if (_pfv && FAILED(hr)) { hr = _pPreview->WalkItemsToPreview((IUnknown *)_pdo); } } else { DWORD dw = GetLastError(); hr = HRESULT_FROM_WIN32(dw); } } } else { hr = S_FALSE; } } // We're done with these ATOMICRELEASE(_pdo); ATOMICRELEASE(_pfv); } return hr; } void OpenThreadProc::Preview() { if (_pPreview) { // viewer window should have been created by now _pPreview->PreviewItems(); MSG msg; while (GetMessage(&msg, NULL, 0, 0) > 0) { TranslateMessage(&msg); DispatchMessage(&msg); } delete _pPreview; _pPreview = NULL; } } void CPhotoVerbs::_OpenPictures() { if (_pdtobj) { HRESULT hr; OpenThreadProc *potd = new OpenThreadProc(_pdtobj, _punkSite, &hr); if (potd) { if (SUCCEEDED(hr)) { potd->CreateVerbThread(); } potd->Release(); } } } // implement the rotate verb, this is a lengthy operation so put it onto a background // thread if we can, marshall the IDataObject and let it do its thing... class CRotateThreadProc : public VerbThreadProc { public: DWORD VerbWithThreadRefCB() { return 0; } DWORD VerbWithThreadRef() { return _Rotate(); } DWORD VerbWithoutThreadRefCB() { return _Rotate(); } DWORD VerbWithoutThreadRef() { return 0; } CRotateThreadProc(IDataObject* pdo, int iAngle, UINT idPrompt, HRESULT *phr); private: DWORD _Rotate(); int _iAngle; UINT _idPrompt; }; CRotateThreadProc::CRotateThreadProc(IDataObject* pdo, int iAngle, UINT idPrompt, HRESULT *phr) : VerbThreadProc(pdo, NULL, phr) { _iAngle = iAngle; _idPrompt = idPrompt; } DWORD CRotateThreadProc::_Rotate() { FORMATETC fmt = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; STGMEDIUM medium = {0}; if (_pdo) { HRESULT hr = _pdo->GetData(&fmt, &medium); if (SUCCEEDED(hr)) { IProgressDialog *ppd; hr = CoCreateInstance(CLSID_ProgressDialog, NULL, CLSCTX_INPROC, IID_PPV_ARG(IProgressDialog, &ppd)); if (SUCCEEDED(hr)) { TCHAR szBuffer[MAX_PATH]; TCHAR szFile[MAX_PATH]; HDROP hd = (HDROP)medium.hGlobal; UINT cItems = DragQueryFile(hd, (UINT)-1, NULL, 0); // prime the progress dialog if (cItems > 1) { LoadString(_Module.GetModuleInstance(), IDS_ROTATETITLE, szBuffer, ARRAYSIZE(szBuffer)); ppd->SetLine(1, T2W(szBuffer), FALSE, NULL); LoadString(_Module.GetModuleInstance(), _idPrompt, szBuffer, ARRAYSIZE(szBuffer)); ppd->SetTitle(T2W(szBuffer)); ppd->SetAnimation(_Module.GetModuleInstance(), IDA_ROTATEAVI); ppd->StartProgressDialog(NULL, NULL, PROGDLG_AUTOTIME, NULL); ppd->SetProgress(1, cItems); } // lets get GDI+, the encoder array and start messing with the bits. this is a // sync operation so check for the user cancelling the UI accordingly. IShellImageDataFactory *pif; hr = CoCreateInstance(CLSID_ShellImageDataFactory, NULL, CLSCTX_INPROC, IID_PPV_ARG(IShellImageDataFactory, &pif)); if (SUCCEEDED(hr)) { for (UINT i = 0; (i != cItems) && !((cItems > 1) ? ppd->HasUserCancelled() : FALSE); i++) { if (DragQueryFile(hd, i, szFile, ARRAYSIZE(szFile))) { if (cItems > 1) { ppd->SetLine(2, T2W(szFile), TRUE, NULL); ppd->SetProgress(i+1, cItems); } // construct an image object from the file, rotate it and save it back IShellImageData *pid; hr = pif->CreateImageFromFile(szFile, &pid); if (SUCCEEDED(hr)) { hr = pid->Decode(SHIMGDEC_DEFAULT,0,0); if (SUCCEEDED(hr)) { if (!((cItems > 1) ? ppd->HasUserCancelled() : FALSE)) { GUID guidFormat; SIZE sz; if ( SUCCEEDED(pid->GetRawDataFormat(&guidFormat)) && SUCCEEDED(pid->GetSize(&sz))) { if (S_OK == pid->IsEditable()) { hr = S_OK; if (::IsEqualGUID(ImageFormatJPEG, guidFormat)) { if ((sz.cx % 16) || (sz.cy % 16)) { if (cItems > 1) { LoadString(_Module.GetModuleInstance(), IDS_ROTATEDLGTITLE, szBuffer, ARRAYSIZE(szBuffer)); ppd->SetLine(1, T2W(szBuffer), FALSE, NULL); } TCHAR szTitle[MAX_PATH]; TCHAR szText[1024]; LoadString(_Module.GetModuleInstance(), IDS_ROTATE_LOSS, szText, ARRAYSIZE(szText)); LoadString(_Module.GetModuleInstance(), IDS_ROTATE_CAPTION, szTitle, ARRAYSIZE(szTitle)); // 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 int nResult = SHMessageBoxCheck(NULL, szText, szTitle, MB_YESNO|MB_ICONWARNING, IDOK, REGSTR_LOSSYROTATE); 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) hr = S_FALSE; // User said No, Don't make any other noise. if (cItems > 1) { LoadString(_Module.GetModuleInstance(), IDS_ROTATETITLE, szBuffer, ARRAYSIZE(szBuffer)); ppd->SetLine(1, T2W(szBuffer), FALSE, NULL); } } } if (hr == S_OK) { CAnnotationSet Annotations; Annotations.SetImageData(pid); INT_PTR nCount = Annotations.GetCount(); for (INT_PTR ix = 0; ix < nCount; ix++) { CAnnotation* pAnnotation = Annotations.GetAnnotation(ix); pAnnotation->Rotate(sz.cy, sz.cx, (_iAngle == 90)); } Annotations.CommitAnnotations(pid); hr = pid->Rotate(_iAngle); if (SUCCEEDED(hr)) { IPersistFile *ppf; hr = pid->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf)); if (SUCCEEDED(hr)) { hr = ppf->Save(NULL, TRUE); ppf->Release(); } } } } else { // 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 (S_OK == pid->IsAnimated()) { // Make some noise. ShellMessageBox(_Module.GetModuleInstance(), NULL, MAKEINTRESOURCE(IDS_ROTATE_MESSAGE), MAKEINTRESOURCE(IDS_PROJNAME), MB_OK | MB_ICONERROR, szFile); // Don't make any other noise. hr = S_FALSE; }// we can't safely rotate images with > 8 bits per channel either; we'd lose the extra bits else if (S_OK != pid->IsEditable()) { ShellMessageBox(_Module.GetModuleInstance(), NULL, MAKEINTRESOURCE(IDS_ROTATE_MESSAGE_EXT), MAKEINTRESOURCE(IDS_PROJNAME), MB_OK | MB_ICONERROR, szFile); // Don't make any other noise. hr = S_FALSE; } } } } } pid->Release(); } if (FAILED(hr)) { if (cItems > 1) { LoadString(_Module.GetModuleInstance(), IDS_ROTATEDLGTITLE, szBuffer, ARRAYSIZE(szBuffer)); ppd->SetLine(1, T2W(szBuffer), FALSE, NULL); } ShellMessageBox(_Module.GetModuleInstance(), NULL, MAKEINTRESOURCE(IDS_ROTATE_ERROR), MAKEINTRESOURCE(IDS_ROTATE_CAPTION), MB_OK|MB_ICONERROR); if (cItems > 1) { LoadString(_Module.GetModuleInstance(), IDS_ROTATETITLE, szBuffer, ARRAYSIZE(szBuffer)); ppd->SetLine(1, T2W(szBuffer), FALSE, NULL); } } } } pif->Release(); } if (cItems > 1) { ppd->StopProgressDialog(); } // Since we always create it, we must always Release it. ppd->Release(); } ReleaseStgMedium(&medium); } } return 0; } void CPhotoVerbs::_RotatePictures(int iAngle, UINT idPrompt) { if (_pict) { _pict->Rotate(iAngle); } else if (_pdtobj) { HRESULT hr; CRotateThreadProc *potd = new CRotateThreadProc(_pdtobj, iAngle, idPrompt, &hr); if (potd) { if (SUCCEEDED(hr)) { potd->CreateVerbThread(); } potd->Release(); } } } DWORD CALLBACK _WallpaperThreadProc(void *pv) { IStream *pstm = (IStream*)pv; IDataObject *pdtobj; HRESULT hr = CoGetInterfaceAndReleaseStream(pstm, IID_PPV_ARG(IDataObject, &pdtobj)); if (SUCCEEDED(hr)) { FORMATETC fmt = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; STGMEDIUM medium = {0}; hr = pdtobj->GetData(&fmt, &medium); if (SUCCEEDED(hr)) { TCHAR szPath[MAX_PATH]; HDROP hd = (HDROP)medium.hGlobal; if (DragQueryFile(hd, 0, szPath, ARRAYSIZE(szPath))) // only set the first one selected as the background { SetWallpaperHelper(szPath); } ReleaseStgMedium(&medium); } pdtobj->Release(); } return 0; } void CPhotoVerbs::_SetWallpaper() { if (_pdtobj) { IStream *pstm; if (FAILED(CoMarshalInterThreadInterfaceInStream(IID_IDataObject, _pdtobj, &pstm)) || !SHCreateThread(_WallpaperThreadProc, pstm, CTF_COINIT, NULL)) { ATOMICRELEASE(pstm); } } } HRESULT _InvokePrintToInPPW(LPCMINVOKECOMMANDINFO pCMI,IDataObject * pdtobj) { HRESULT hr = E_FAIL; HMODULE hDll = LoadLibrary( TEXT("photowiz.dll") ); if (hDll) { LPFNPPWPRINTTO pfnPrintTo = (LPFNPPWPRINTTO)GetProcAddress( hDll, PHOTO_PRINT_WIZARD_PRINTTO_ENTRY ); if (pfnPrintTo) { hr = pfnPrintTo( pCMI, pdtobj ); } FreeLibrary( hDll ); } return hr; } const struct { LPCSTR pszVerb; int idVerb; } c_szVerbs[] = { { "preview", OFFSET_OPEN}, { "printto", OFFSET_PRINTTO}, { "rotate90", OFFSET_ROT90}, { "rotate270", OFFSET_ROT270}, }; HRESULT CPhotoVerbs::_MapVerb(LPCMINVOKECOMMANDINFO pici, int *pidVerb) { HRESULT hr = S_OK; if (IS_INTRESOURCE(pici->lpVerb)) { *pidVerb = LOWORD(pici->lpVerb); } else { hr = E_INVALIDARG; for (int i = 0; i < ARRAYSIZE(c_szVerbs); i++) { if (0 == lstrcmpiA(pici->lpVerb, c_szVerbs[i].pszVerb)) { hr = S_OK; *pidVerb = c_szVerbs[i].idVerb; break; } } } return hr; } STDMETHODIMP CPhotoVerbs::InvokeCommand(LPCMINVOKECOMMANDINFO pCMI) { int idVerb; HRESULT hr = _MapVerb(pCMI, &idVerb); if (SUCCEEDED(hr)) { switch (idVerb) { case OFFSET_OPEN: if (_fAcceptPreview) _OpenPictures(); else hr = E_FAIL; break; case OFFSET_PRINTTO: hr = _InvokePrintToInPPW(pCMI,_pdtobj); break; case OFFSET_ROT90: _RotatePictures(90, IDS_ROTATE90); break; case OFFSET_ROT270: _RotatePictures(270, IDS_ROTATE270); break; case OFFSET_ZOOMIN: if (_pict) { _pict->ZoomIn(); } break; case OFFSET_ZOOMOUT: if (_pict) { _pict->ZoomOut(); } break; case OFFSET_ACTUALSIZE: if (_pict) { _pict->ActualSize(); } break; case OFFSET_BESTFIT: if (_pict) { _pict->BestFit(); } break; case OFFSET_NEXTPAGE: if (_pict) { _pict->NextPage(); } break; case OFFSET_PREVPAGE: if (_pict) { _pict->PreviousPage(); } break; case OFFSET_SETWALL: _SetWallpaper(); break; default: hr = E_INVALIDARG; break; } } return hr; } STDMETHODIMP CPhotoVerbs::GetCommandString(UINT_PTR uID, UINT uFlags, UINT *res, LPSTR pName, UINT cchMax) { HRESULT hr = S_OK; UINT idSel = (UINT)uID; switch (uFlags) { case GCS_VERBW: case GCS_VERBA: if (idSel < ARRAYSIZE(c_szVerbs)) { if (uFlags == GCS_VERBW) { SHAnsiToUnicode(c_szVerbs[idSel].pszVerb, (LPWSTR)pName, cchMax); } else { StrCpyNA(pName, c_szVerbs[idSel].pszVerb, cchMax); } } break; case GCS_HELPTEXTW: LoadStringW(_Module.GetResourceInstance(), idSel+IDH_HELP_FIRST, (LPWSTR)pName, cchMax); break; case GCS_HELPTEXTA: LoadStringA(_Module.GetResourceInstance(), idSel+IDH_HELP_FIRST, (LPSTR)pName, cchMax); break; case GCS_VALIDATEA: case GCS_VALIDATEW: hr = E_NOTIMPL; break; } return hr; } void WINAPI ImageView_Fullscreen(HWND hwnd, HINSTANCE hAppInstance, LPTSTR pszCmdLine, int nCmdShow) { HRESULT hr = SHCoInitialize(); // suppress OLE1 DDE window if (SUCCEEDED(hr)) { OleInitialize(NULL); // needed to get drag and drop to work IDataObject *pdtobj; hr = GetUIObjectFromPath(pszCmdLine, IID_PPV_ARG(IDataObject, &pdtobj)); if (SUCCEEDED(hr)) { // this scope is required to make sure cwndPreview gets destroyed before we call SHCoUninitialize // the preview wnd will init GDI+ too CPreviewWnd cwndPreview; if (!cwndPreview.TryWindowReuse(pszCmdLine)) { if (SUCCEEDED(cwndPreview.Initialize(NULL, WINDOW_MODE, FALSE)) && cwndPreview.CreateViewerWindow()) { cwndPreview.PreviewItemsFromUnk(pdtobj); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } } } pdtobj->Release(); } OleUninitialize(); } SHCoUninitialize(hr); } void WINAPI ImageView_FullscreenA(HWND hwnd, HINSTANCE hAppInstance, LPSTR pszCmdLine, int nCmdShow) { TCHAR szCmdLine[MAX_PATH*2]; SHAnsiToTChar(pszCmdLine, szCmdLine, ARRAYSIZE(szCmdLine)); ImageView_Fullscreen(hwnd, hAppInstance, szCmdLine, nCmdShow); } void WINAPI ImageView_FullscreenW(HWND hwnd, HINSTANCE hAppInstance, LPWSTR pszCmdLine, int nCmdShow) { ImageView_Fullscreen(hwnd, hAppInstance, pszCmdLine, nCmdShow); } // To work around ACDSEE lower cases the shell command stuff causing us to need this // export an all lowercase version of this function. The short is, case matters for RunDLL32 exports. void WINAPI imageview_fullscreenW(HWND hwnd, HINSTANCE hAppInstance, LPWSTR pszCmdLine, int nCmdShow) { ImageView_FullscreenW(hwnd, hAppInstance, pszCmdLine, nCmdShow); } LPTSTR ParseCmdLine( LPTSTR pInput, LPTSTR pOutput, BOOL bStripQuotes ) { // copies the next token on the line to pOutput and returns // the first white space character after the processed token if (!pInput || (!*pInput) || !pOutput) { return pInput; } // first, skip any leading whitespace while (*pInput == TEXT(' ')) { pInput++; } if (!(*pInput)) { return pInput; } // next, start copying token // if the token starts with a // quote, note that and copy it BOOL bStartedWithQuote = FALSE; if (*pInput == TEXT('\"')) { bStartedWithQuote = TRUE; if (bStripQuotes) { pInput++; } else { *pOutput++ = *pInput++; } } // figure out what to stop on TCHAR cStopChar; if (bStartedWithQuote) { cStopChar = TEXT('\"'); } else { cStopChar = TEXT(' '); } // copy up to the delimeter while( *pInput && (*pInput != cStopChar)) { *pOutput++ = *pInput++; } // if the delimeter was a quote // we need to copy it into the output if (bStartedWithQuote && (*pInput == TEXT('\"'))) { if (bStripQuotes) { pInput++; } else { *pOutput++ = *pInput++; } } *pOutput = 0; return pInput; } void WINAPI ImageView_PrintTo(HWND hwnd, HINSTANCE hAppInstance, LPTSTR pszCmdLine, int nCmdShow) { // The command line comes to us like this (everything inside the <>): // TCHAR szFileName[ 1024 ]; TCHAR szPrinterName[ 1024 ]; LPTSTR psz = pszCmdLine; if (*psz == TEXT('/')) { // skip the "/pt" psz = ParseCmdLine( psz, szFileName, TRUE ); } // Get the filename psz = ParseCmdLine( psz, szFileName, TRUE ); // Get the printer name psz = ParseCmdLine( psz, szPrinterName, TRUE ); // create a dataobject for the file in question, and then call // into photowiz to print it out... HRESULT hrInit = SHCoInitialize(); if (SUCCEEDED(hrInit)) { IDataObject *pdtobj; HRESULT hr = GetUIObjectFromPath(szFileName, IID_PPV_ARG(IDataObject, &pdtobj)); if (SUCCEEDED(hr)) { // Create CMINVOKECAMMANDINFO to pass to photowiz CMINVOKECOMMANDINFOEX cmi = {0}; cmi.cbSize = sizeof(cmi); cmi.fMask = CMIC_MASK_UNICODE; cmi.lpVerbW = L"printto"; cmi.lpParametersW = szPrinterName; hr = _InvokePrintToInPPW((LPCMINVOKECOMMANDINFO )&cmi, pdtobj); pdtobj->Release(); } } SHCoUninitialize(hrInit); } void WINAPI ImageView_PrintToA(HWND hwnd, HINSTANCE hAppInstance, LPSTR pszCmdLine, int nCmdShow) { TCHAR szCmdLine[1024]; SHAnsiToTChar(pszCmdLine, szCmdLine, ARRAYSIZE(szCmdLine)); ImageView_PrintTo(hwnd, hAppInstance, szCmdLine, nCmdShow); } void WINAPI ImageView_PrintToW(HWND hwnd, HINSTANCE hAppInstance, LPWSTR pszCmdLine, int nCmdShow) { ImageView_PrintTo( hwnd, hAppInstance, pszCmdLine, nCmdShow ); }