#include "precomp.h" #include "PrevWnd.h" #include "PrevCtrl.h" #include "resource.h" #include #include "shutil.h" #include "tasks.h" #include #include #include #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 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(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; }