3381 lines
106 KiB
C++
3381 lines
106 KiB
C++
/* sample source code for IE4 view extension
|
|
|
|
* Copyright Microsoft 1996
|
|
|
|
* This file implements the Window routines
|
|
*/
|
|
|
|
#include "precomp.h"
|
|
#include "shellp.h"
|
|
#include "regstr.h"
|
|
#ifdef DEBUG
|
|
#define SMALLCACHE 1
|
|
#endif
|
|
|
|
WCHAR const c_szDefault[] = L"Default";
|
|
|
|
// the view wind proc ....
|
|
LRESULT CALLBACK CThumbnailView_WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
|
|
DWORD GetAltColor();
|
|
|
|
#define SUBIDSTART 0x0000
|
|
#define SUBIDEND 0x7fff
|
|
|
|
|
|
void CThumbnailView::RegisterWindowClass()
|
|
{
|
|
// register the window class that will sit as a wrapper for the ListView.
|
|
WNDCLASSW wc;
|
|
|
|
if (!GetClassInfoWrapW(g_hinstDll, VIEWCLASSNAME, &wc)) {
|
|
// don't want vredraw and hredraw because that causes horrible
|
|
// flicker expecially with full drag
|
|
wc.style = CS_PARENTDC;
|
|
wc.lpfnWndProc = (WNDPROC)CThumbnailView_WndProc;
|
|
wc.cbClsExtra = 0;
|
|
wc.cbWndExtra = sizeof(CThumbnailView*);
|
|
wc.hInstance = g_hinstDll;
|
|
wc.hIcon = NULL;
|
|
wc.hCursor = LoadCursorA(NULL, (LPCSTR)IDC_ARROW);
|
|
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
|
|
wc.lpszMenuName = NULL;
|
|
wc.lpszClassName = VIEWCLASSNAME;
|
|
|
|
RegisterClassWrapW(&wc);
|
|
}
|
|
}
|
|
|
|
|
|
LRESULT CALLBACK CThumbnailView_WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
CThumbnailView* pThis = (CThumbnailView*)GetWindowLongPtrA(hWnd, 0);
|
|
LRESULT lRes = TRUE;
|
|
|
|
switch (iMessage) {
|
|
case WM_NOTIFYFORMAT:
|
|
// we are now a unicode window....
|
|
return NFR_UNICODE;
|
|
case WM_MENUCHAR:
|
|
if (pThis->m_pCMCache) {
|
|
lRes = 0;
|
|
IContextMenu3* pcm3;
|
|
HRESULT hr = pThis->m_pCMCache->QueryInterface(IID_IContextMenu3, (void**)&pcm3);
|
|
if (SUCCEEDED(hr)) {
|
|
hr = pcm3->HandleMenuMsg2(iMessage, wParam, lParam, &lRes);
|
|
pcm3->Release();
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
return lRes;
|
|
}
|
|
} else {
|
|
goto HandleDefWnd;
|
|
}
|
|
}
|
|
break;
|
|
case WM_INITMENUPOPUP:
|
|
case WM_DRAWITEM:
|
|
case WM_MEASUREITEM:
|
|
if (pThis->m_pCMCache) {
|
|
// if we have a cached context menu, then we have it displayed,
|
|
// so we need to forward these three message to the Menu handler instead
|
|
// of doing the default action....
|
|
LPCONTEXTMENU2 pcm2;
|
|
HRESULT hr = pThis->m_pCMCache->QueryInterface(IID_IContextMenu2, (void**)&pcm2);
|
|
if (SUCCEEDED(hr)) {
|
|
pcm2->HandleMenuMsg(iMessage, wParam, lParam);
|
|
pcm2->Release();
|
|
return 0;
|
|
}
|
|
}
|
|
break;
|
|
case WM_ERASEBKGND:
|
|
if (ListView_GetBkColor(pThis->m_hWndListView) == CLR_NONE) {
|
|
return SendMessageA(pThis->m_hWndParent, iMessage, wParam, lParam);
|
|
}
|
|
// We want to reduce flash
|
|
lRes = TRUE;
|
|
break;
|
|
case WM_CREATE:
|
|
{
|
|
// cast the create struct pointer to the object (as that is what we passed )
|
|
LPCREATESTRUCT pCS = (LPCREATESTRUCT)lParam;
|
|
pThis = (CThumbnailView*)pCS->lpCreateParams;
|
|
|
|
lRes = pThis->OnWmCreate(hWnd, pCS);
|
|
break;
|
|
}
|
|
case WM_DESTROY:
|
|
lRes = pThis->OnWmDestroy(hWnd);
|
|
break;
|
|
|
|
// stupid . . . handle case where windows only sends WM_WINDOWPOSCHANGING and not
|
|
// WM_SIZE to handle sizing of tv. . .
|
|
case WM_WINDOWPOSCHANGING:
|
|
{
|
|
WINDOWPOS* pWPos = (WINDOWPOS*)lParam;
|
|
if (pWPos->cx && pWPos->cy)
|
|
lRes = pThis->OnWmSize(hWnd, 0, pWPos->cx, pWPos->cy);
|
|
break;
|
|
}
|
|
case WM_SIZE:
|
|
// handle it if we need to ...
|
|
lRes = HANDLE_WM_SIZE(hWnd, wParam, lParam, pThis->OnWmSize);
|
|
break;
|
|
case WM_MENUSELECT:
|
|
lRes = pThis->OnWmMenuSelect(hWnd, wParam, lParam);
|
|
break;
|
|
case WM_ACTIVATE:
|
|
// force update on inactive to not ruin save bits
|
|
lRes = HANDLE_WM_ACTIVATE(hWnd, wParam, lParam, pThis->OnWmActivate);
|
|
break;
|
|
case WM_CLIPBOARD_CUTCOPY:
|
|
lRes = pThis->OnWmClipboardUpdate();
|
|
break;
|
|
case WM_TIMER:
|
|
{
|
|
if (GetTickCount() - pThis->m_dwCacheTickCount > 2000) {
|
|
DWORD dwMode;
|
|
if (pThis->m_pDiskCache->GetMode(&dwMode) == S_OK && pThis->m_pDiskCache->IsLocked() == S_FALSE) {
|
|
// two seconds since last access, close the cache.
|
|
pThis->m_pDiskCache->Close(NULL);
|
|
}
|
|
|
|
if (pThis->m_pScheduler->CountTasks(TOID_NULL) == 0) {
|
|
// there is nothing in the queue pending, so quit listening...
|
|
KillTimer(hWnd, TIMER_DISKCACHE);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case WM_QUERYNEWPALETTE:
|
|
{
|
|
HDC hdc = GetDC(pThis->m_hWndListView);
|
|
HPALETTE hpalOld = SelectPalette(hdc, pThis->m_hpal, FALSE);
|
|
RealizePalette(hdc);
|
|
SelectPalette(hdc, hpalOld, TRUE);
|
|
ReleaseDC(pThis->m_hWndListView, hdc);
|
|
return TRUE;
|
|
}
|
|
case WM_PALETTECHANGED:
|
|
{
|
|
if ((HWND)wParam != pThis->m_hWndListView) {
|
|
HDC hdc = GetDC(pThis->m_hWndListView);
|
|
HPALETTE hpalOld = SelectPalette(hdc, pThis->m_hpal, TRUE);
|
|
RealizePalette(hdc);
|
|
SelectPalette(hdc, hpalOld, TRUE);
|
|
ReleaseDC(pThis->m_hWndListView, hdc);
|
|
}
|
|
// we don't bother forwarding because the list view does nothing unless it
|
|
// has a background image, so just invalidate instead..
|
|
InvalidateRect(pThis->m_hWndListView, NULL, FALSE);
|
|
}
|
|
return TRUE;
|
|
case WM_SETFOCUS:
|
|
{
|
|
// NOTE: we use the defView as the IShellView to set as active ..
|
|
// NOTE: this allows the defview to handle issues such as menus
|
|
// NOTE: and to delegate them to view extensions where necessary
|
|
if (pThis->m_pBrowser != NULL) {
|
|
pThis->m_pBrowser->OnViewWindowActive(pThis->m_pDefView);
|
|
|
|
SetFocus(pThis->m_hWndListView);
|
|
}
|
|
|
|
lRes = FALSE;
|
|
break;
|
|
}
|
|
case WM_NOTIFY:
|
|
lRes = HANDLE_WM_NOTIFY(hWnd, wParam, lParam, pThis->OnWmNotify);
|
|
break;
|
|
case WM_CONTEXTMENU:
|
|
lRes = pThis->OnWmContextMenu(hWnd, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
|
|
break;
|
|
case GET_WM_CTLCOLOR_MSG(CTLCOLOR_STATIC):
|
|
SetBkColor(GET_WM_CTLCOLOR_HDC(wParam, lParam, iMessage),
|
|
GetSysColor(COLOR_WINDOW));
|
|
return (LRESULT)GetSysColorBrush(COLOR_WINDOW);
|
|
case WM_WININICHANGE:
|
|
case WM_SYSCOLORCHANGE:
|
|
if (pThis->m_hWndListView != NULL) {
|
|
SendMessageA(pThis->m_hWndListView, iMessage, wParam, lParam);
|
|
}
|
|
lRes = FALSE;
|
|
break;
|
|
case CWM_GETISHELLBROWSER:
|
|
// NOTE: the shell does not Addref this, so neither will we
|
|
lRes = (LRESULT)pThis->m_pBrowser;
|
|
break;
|
|
case WM_UPDATEITEMIMAGE:
|
|
{
|
|
LV_ITEMW rgItem;
|
|
rgItem.mask = LVIF_IMAGE;
|
|
rgItem.iItem = (int)wParam;
|
|
rgItem.iSubItem = 0;
|
|
|
|
if (pThis->m_hWndListView && ListView_GetItemWrapW(pThis->m_hWndListView, &rgItem)) {
|
|
if (rgItem.iImage != I_IMAGECALLBACK) {
|
|
Assert(pThis->m_pImageCache);
|
|
|
|
// free the previous image in the cache ...
|
|
pThis->m_pImageCache->FreeImage((UINT)rgItem.iImage);
|
|
}
|
|
|
|
if (lParam == rgItem.iImage) {
|
|
ListView_RedrawItems(pThis->m_hWndListView, wParam, wParam);
|
|
} else {
|
|
rgItem.iImage = (int)lParam;
|
|
ListView_SetItemWrapW(pThis->m_hWndListView, &rgItem);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case WM_STATUSBARUPDATE:
|
|
{
|
|
if (lParam == 0xffffffff) {
|
|
pThis->UpdateStatusBar(NULL, NULL);
|
|
} else if (pThis->m_hWndListView) {
|
|
WCHAR szText[MAX_PATH];
|
|
LV_ITEMW rgItem;
|
|
rgItem.mask = LVIF_TEXT;
|
|
rgItem.pszText = szText;
|
|
rgItem.cchTextMax = ARRAYSIZE(szText);
|
|
rgItem.iItem = (int)lParam;
|
|
rgItem.iSubItem = 0;
|
|
|
|
if (ListView_GetItemWrapW(pThis->m_hWndListView, &rgItem)) {
|
|
WCHAR szMessage[MAX_PATH];
|
|
WCHAR szMessageFinal[MAX_PATH * 2];
|
|
LoadStringWrapW(g_hinstDll, (UINT)wParam, szMessage, ARRAYSIZE(szMessage));
|
|
wnsprintfW(szMessageFinal, ARRAYSIZE(szMessageFinal), szMessage, szText);
|
|
pThis->UpdateStatusBar(szMessageFinal, NULL);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case WM_VIEWREFRESH:
|
|
pThis->Refresh();
|
|
lRes = FALSE;
|
|
break;
|
|
case WM_PROCESSITEMS:
|
|
pThis->OnWmProcessItems();
|
|
lRes = FALSE;
|
|
break;
|
|
case WM_HANDLESELCHANGE:
|
|
{
|
|
BOOL fSingular = TRUE;
|
|
BOOL fSelChange = (BOOL)wParam;
|
|
|
|
MSG msgTmp;
|
|
|
|
while (PeekMessage(&msgTmp, hWnd, WM_HANDLESELCHANGE, WM_HANDLESELCHANGE, PM_REMOVE)) {
|
|
if (fSingular) {
|
|
fSingular = FALSE;
|
|
}
|
|
}
|
|
|
|
// if there are multiple queued, then...
|
|
if (pThis->m_fSelChanges > 1) {
|
|
fSingular = FALSE;
|
|
}
|
|
|
|
if ((pThis->m_pCommDlg != NULL) && (fSelChange || !fSingular)) {
|
|
pThis->m_pCommDlg->OnStateChange(pThis->m_pDefView, CDBOSC_SELCHANGE);
|
|
}
|
|
|
|
pThis->UpdateStatusBar();
|
|
|
|
if (fSingular && !fSelChange) {
|
|
// figure out if the item is one of the selection,
|
|
// if so, then send the notification, otherwise don't
|
|
LV_ITEMW rgItem;
|
|
rgItem.iItem = (int)lParam;
|
|
rgItem.mask = LVIF_NORECOMPUTE | LVIF_STATE;
|
|
rgItem.iSubItem = 0;
|
|
rgItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
|
|
|
|
int iItem = ListView_GetItemWrapW(pThis->m_hWndListView, &rgItem);
|
|
|
|
if (rgItem.state & (LVIS_FOCUSED | LVIS_SELECTED)) {
|
|
fSelChange = TRUE;
|
|
}
|
|
}
|
|
|
|
if (pThis->m_pAuto && (!fSingular || fSelChange)) {
|
|
// only automate if you have the automaton
|
|
lRes = Invoke_OnConnectionPointerContainer((IUnknown*)pThis->m_pAuto
|
|
, DIID_DShellFolderViewEvents, DISPID_SELECTIONCHANGED, IID_NULL
|
|
, 0, DISPATCH_METHOD, NULL, NULL, NULL, NULL);
|
|
|
|
}
|
|
|
|
pThis->m_fSelChanges = 0;
|
|
break;
|
|
}
|
|
|
|
case WM_MOUSEWHEEL:
|
|
default:
|
|
// Handle the magellan mousewheel message.
|
|
if (iMessage == g_msgMSWheel && pThis) {
|
|
lRes = SendMessageA(pThis->m_hWndListView, iMessage, wParam, lParam);
|
|
} else {
|
|
HandleDefWnd:
|
|
lRes = DefWindowProcWrapW(hWnd, iMessage, wParam, lParam);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return lRes;
|
|
}
|
|
|
|
|
|
LRESULT CThumbnailView::OnWmSize(HWND hwnd, UINT fFlags, int iWidth, int iHeight)
|
|
{
|
|
// resize the enhanced list view.
|
|
::MoveWindow(m_hWndListView, 0, 0, iWidth, iHeight, TRUE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
LRESULT CThumbnailView::OnWmClipboardUpdate(void)
|
|
{
|
|
int iIndex = -1;
|
|
|
|
// go through everything in the list view.
|
|
do {
|
|
iIndex = ListView_GetNextItem(m_hWndListView, iIndex, LVNI_ALL);
|
|
if (iIndex == -1) {
|
|
break;
|
|
}
|
|
|
|
LV_ITEMW rgItem;
|
|
rgItem.iItem = iIndex;
|
|
rgItem.mask = LVIF_PARAM;
|
|
rgItem.iSubItem = 0;
|
|
|
|
int iItem = ListView_GetItemWrapW(m_hWndListView, &rgItem);
|
|
// just incase. ...
|
|
if (iItem != -1) {
|
|
LPCITEMIDLIST pidl = (LPCITEMIDLIST)rgItem.lParam;
|
|
|
|
// check if the item should be ghosted or not.
|
|
DWORD dwGhosted = SFGAO_GHOSTED;
|
|
WORD wCut = 0x0000;
|
|
WORD wStateOld = ListView_GetItemState(m_hWndListView,
|
|
rgItem.iItem, LVIS_CUT);
|
|
|
|
m_pFolder->GetAttributesOf(1, &pidl, &dwGhosted);
|
|
if (dwGhosted & SFGAO_GHOSTED)
|
|
wCut = LVIS_CUT;
|
|
|
|
if (wStateOld != wCut) {
|
|
// update and redraw the items that have changed.
|
|
ListView_SetItemState(m_hWndListView, rgItem.iItem, wCut, LVIS_CUT);
|
|
ListView_RedrawItems(m_hWndListView, rgItem.iItem, rgItem.iItem);
|
|
}
|
|
}
|
|
} while (TRUE);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
LRESULT CThumbnailView::OnWmNotify(HWND hWnd, int iID, NMHDR* pHdr)
|
|
{
|
|
LRESULT lRes = FALSE;
|
|
HRESULT hr = NOERROR;
|
|
|
|
switch (pHdr->code) {
|
|
case NM_CUSTOMDRAW:
|
|
return OnCustomDraw((NMCUSTOMDRAW*)pHdr);
|
|
|
|
case LVN_GETDISPINFOA:
|
|
{
|
|
LV_DISPINFOA* pInfo = (LV_DISPINFOA*)pHdr;
|
|
|
|
LPCITEMIDLIST pidl = NULL;
|
|
// if the enhanced view is looking for its thumbnnails.
|
|
if (pInfo->item.mask & LVIF_IMAGE) {
|
|
lRes = OnViewGetThumbnail(pHdr);
|
|
|
|
pInfo->item.mask |= LVIF_DI_SETITEM;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case LVN_GETDISPINFOW:
|
|
{
|
|
LV_DISPINFOW* pInfo = (LV_DISPINFOW*)pHdr;
|
|
|
|
LPCITEMIDLIST pidl = NULL;
|
|
// if the enhanced view is looking for its thumbnnails.
|
|
if (pInfo->item.mask & LVIF_IMAGE) {
|
|
lRes = OnViewGetThumbnail(pHdr);
|
|
|
|
pInfo->item.mask |= LVIF_DI_SETITEM;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case LVN_GETINFOTIPW:
|
|
lRes = OnInfoTipText((NMLVGETINFOTIPW*)pHdr);
|
|
break;
|
|
|
|
|
|
case LVN_COLUMNCLICK:
|
|
{
|
|
NM_LISTVIEW* pnmv = (NM_LISTVIEW*)pHdr;
|
|
this->SortBy(pnmv->iSubItem, TRUE);
|
|
lRes = FALSE;
|
|
break;
|
|
}
|
|
|
|
case LVN_ITEMCHANGED:
|
|
{
|
|
NM_LISTVIEW* pnmv = (NM_LISTVIEW*)pHdr;
|
|
BOOL fSelChange = (pnmv->uChanged & LVIF_STATE) != 0;
|
|
|
|
if (!m_fSelChanges) {
|
|
PostMessage(m_hWnd, WM_HANDLESELCHANGE, (WPARAM)fSelChange, (LPARAM)pnmv->iItem);
|
|
}
|
|
|
|
m_fSelChanges++;
|
|
break;
|
|
}
|
|
|
|
case LVN_BEGINLABELEDITW:
|
|
{
|
|
// failure case stop editing ...
|
|
lRes = TRUE;
|
|
LV_DISPINFOW* pInfo = (LV_DISPINFOW*)pHdr;
|
|
|
|
LPCITEMIDLIST pidl = (LPCITEMIDLIST)pInfo->item.lParam;
|
|
|
|
ULONG rgFlags = SFGAO_CANRENAME;
|
|
if (pidl == NULL ||
|
|
FAILED(m_pFolder->GetAttributesOf(1, (LPCITEMIDLIST*)&pidl, &rgFlags)) ||
|
|
!(rgFlags & SFGAO_CANRENAME))
|
|
break;
|
|
|
|
lRes = FALSE;
|
|
HWND hwndEdit = ListView_GetEditControl(m_hWndListView);
|
|
if (hwndEdit) {
|
|
int cchMax = 0;
|
|
m_pFolderCB->MessageSFVCB(SFVM_GETCCHMAX, (WPARAM)pidl, (LPARAM)&cchMax);
|
|
if (cchMax) {
|
|
Assert(cchMax < 1024);
|
|
SendMessageA(hwndEdit, EM_LIMITTEXT, cchMax, 0);
|
|
}
|
|
|
|
WCHAR szName[MAX_PATH];
|
|
STRRET str;
|
|
if (SUCCEEDED(m_pFolder->GetDisplayNameOf(pidl, SHGDN_INFOLDER | SHGDN_FOREDITING, &str)) &&
|
|
SUCCEEDED(StrRetToBufW(&str, pidl, szName, ARRAYSIZE(szName)))) {
|
|
SetWindowTextWrapW(hwndEdit, szName);
|
|
}
|
|
}
|
|
|
|
m_fTranslateAccel = TRUE;
|
|
break;
|
|
}
|
|
|
|
case LVN_ENDLABELEDITW:
|
|
{
|
|
m_fTranslateAccel = FALSE;
|
|
|
|
// check the HWND to see if we have been destroyed.... (this happens if someone
|
|
// navigates while the edit box is up)
|
|
if (m_hWnd) {
|
|
// AddRef, rename, then release, so that the
|
|
// view object stays around until the rename function returns.
|
|
// this in case the scope changes in the middle of the rename, the
|
|
// view would be released and deleted before the rename returned.
|
|
this->InternalAddRef();
|
|
this->OnLVNEndLabelEdit(pHdr);
|
|
this->InternalRelease();
|
|
}
|
|
lRes = FALSE;
|
|
break;
|
|
}
|
|
|
|
case LVN_BEGINDRAG:
|
|
case LVN_BEGINRDRAG:
|
|
{
|
|
HRESULT hr = OleInitialize(NULL);
|
|
if (SUCCEEDED(hr)) {
|
|
StartDragDrop((NM_LISTVIEW*)pHdr);
|
|
OleUninitialize();
|
|
}
|
|
|
|
SHChangeNotifyHandleEvents();
|
|
break;
|
|
}
|
|
case NM_SETFOCUS:
|
|
// NOTE: notify the browser we just got focus, we use the defview
|
|
// NOTE: for this so that it can handle the menu merging.
|
|
Assert(m_pDefView != NULL);
|
|
|
|
m_pBrowser->OnViewWindowActive(m_pDefView);
|
|
if (m_pCommDlg != NULL) {
|
|
m_pCommDlg->OnStateChange(m_pDefView, CDBOSC_SETFOCUS);
|
|
}
|
|
|
|
m_pDefView->UIActivate(SVUIA_ACTIVATE_FOCUS);
|
|
break;
|
|
|
|
case NM_KILLFOCUS:
|
|
if (m_pCommDlg != NULL) {
|
|
m_pCommDlg->OnStateChange(m_pDefView, CDBOSC_KILLFOCUS);
|
|
}
|
|
break;
|
|
|
|
case LVN_ITEMACTIVATE:
|
|
{
|
|
// make sure there is no context menu up...
|
|
if (m_hWndListView)
|
|
SendMessageA(m_hWndListView, WM_CANCELMODE, 0, 0);
|
|
HRESULT hRes = S_FALSE;
|
|
|
|
if (m_pCommDlg != NULL) {
|
|
// addref the def-view incase it decides to unwind us in the meantime.
|
|
IShellView* pView = m_pDefView;
|
|
pView->AddRef();
|
|
hRes = m_pCommDlg->OnDefaultCommand(pView);
|
|
pView->Release();
|
|
}
|
|
|
|
if (hRes == S_FALSE) {
|
|
lRes = this->OnDefaultAction(FALSE);
|
|
}
|
|
lRes = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
return lRes;
|
|
}
|
|
|
|
|
|
LRESULT CThumbnailView::OnWmActivate(HWND hWnd,
|
|
int fActive,
|
|
HWND hWndPrev,
|
|
BOOL fMinimized)
|
|
{
|
|
if (fActive == WA_INACTIVE) {
|
|
UpdateWindow(m_hWndListView);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
|
|
LRESULT CThumbnailView::OnWmContextMenu(HWND hWnd, int iX, int iY)
|
|
{
|
|
int iItem = -1;
|
|
HMENU hCMenu = NULL;
|
|
LPCONTEXTMENU pICMenu = NULL;
|
|
LPCITEMIDLIST* apidl = NULL;
|
|
HRESULT hRes;
|
|
LRESULT lRes = FALSE;
|
|
int iSelectedCount = 0;
|
|
UINT id;
|
|
UINT grfFlags;
|
|
|
|
POINT pt = {iX, iY};
|
|
RECT rc;
|
|
LV_HITTESTINFO rgInfo;
|
|
rgInfo.flags = 0;
|
|
rgInfo.iItem = -1;
|
|
BOOL fItemsFromSelection = TRUE;
|
|
|
|
// check the loword, as it was a lparam that was broken apart.
|
|
if ((LOWORD(iX) == 0xffff) && (LOWORD(iY) == 0xffff)) {
|
|
// it must have come from the keyboard contextmenu button
|
|
// we need to find an appropriate position....
|
|
|
|
// find the focussed item
|
|
int iItem = ListView_GetNextItem(m_hWndListView, -1, LVNI_FOCUSED | LVNI_SELECTED);
|
|
if (iItem != -1) {
|
|
RECT rcItem;
|
|
ListView_GetItemRect(m_hWndListView, iItem, &rcItem, LVIR_ICON);
|
|
pt.x = (rcItem.left + rcItem.right) / 2;
|
|
pt.y = (rcItem.top + rcItem.bottom) / 2;
|
|
|
|
rgInfo.flags = LVHT_ONITEM;
|
|
rgInfo.iItem = iItem;
|
|
} else {
|
|
pt.x = 0;
|
|
pt.y = 0;
|
|
}
|
|
|
|
MapWindowPoints(m_hWndListView, HWND_DESKTOP, (LPPOINT)&pt, 1);
|
|
|
|
iX = pt.x;
|
|
iY = pt.y;
|
|
} else {
|
|
GetClientRect(m_hWndListView, &rc);
|
|
ScreenToClient(m_hWndListView, &pt);
|
|
|
|
rgInfo.pt = pt;
|
|
rgInfo.flags = 0;
|
|
|
|
ListView_HitTest(m_hWndListView, &rgInfo);
|
|
}
|
|
|
|
hCMenu = CreatePopupMenu();
|
|
if (hCMenu == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL fItem = FALSE;
|
|
|
|
// did we hit anything ?
|
|
if (!(rgInfo.flags & LVHT_ONITEM) || (rgInfo.iItem == -1)) {
|
|
// background context menu .....
|
|
hRes = this->CreateBackgroundMenu(&pICMenu);
|
|
if (FAILED(hRes)) {
|
|
goto ContextCleanup;
|
|
}
|
|
} else {
|
|
iSelectedCount = ListView_GetSelectedCount(m_hWndListView);
|
|
if (iSelectedCount == 0) {
|
|
// we must catch this because sometimes the listview doesn't
|
|
// always have an item selected when you context menu and item
|
|
fItemsFromSelection = FALSE;
|
|
iSelectedCount = 1;
|
|
}
|
|
|
|
// fetch the pidl from the view ...
|
|
Assert(iSelectedCount > 0);
|
|
|
|
apidl = new LPCITEMIDLIST[iSelectedCount];
|
|
if (apidl == NULL) {
|
|
// do something about low memory ...
|
|
MessageBoxWrapW(m_hWnd,
|
|
(LPWSTR)MAKEINTRESOURCE(IDS_ERR_OUTOFMEM),
|
|
(LPWSTR)MAKEINTRESOURCE(IDS_ERR_SHELLTITLE),
|
|
MB_OK | MB_SETFOREGROUND | MB_ICONSTOP);
|
|
|
|
goto ContextCleanup;
|
|
}
|
|
|
|
if (fItemsFromSelection) {
|
|
// there is a selection
|
|
GetSelectionPidlList(m_hWndListView, iSelectedCount, apidl, rgInfo.iItem);
|
|
} else {
|
|
// we failed to get a selection, but we know what item we are
|
|
// over ...
|
|
LV_ITEMW rgItem;
|
|
rgItem.mask = LVIF_PARAM;
|
|
rgItem.iItem = rgInfo.iItem;
|
|
rgItem.iSubItem = 0;
|
|
int iItem = ListView_GetItemWrapW(m_hWndListView, &rgItem);
|
|
if (iItem == -1) {
|
|
goto ContextCleanup;
|
|
}
|
|
|
|
apidl[0] = (LPCITEMIDLIST)rgItem.lParam;
|
|
}
|
|
|
|
|
|
// get the context menu interface for the object ....
|
|
UINT rgfInOut = 0;
|
|
CComObject<CThumbnailMenu>* pMenuTmp = new CComObject<CThumbnailMenu>;
|
|
if (pMenuTmp == NULL) {
|
|
goto ContextCleanup;
|
|
}
|
|
|
|
hRes = pMenuTmp->QueryInterface(IID_IContextMenu, (void**)&pICMenu);
|
|
Assert(SUCCEEDED(hRes));
|
|
|
|
hRes = pMenuTmp->Init(this, &rgfInOut, apidl, iSelectedCount);
|
|
if (FAILED(hRes)) {
|
|
goto ContextCleanup;
|
|
}
|
|
|
|
// it is an item not the background...
|
|
fItem = TRUE;
|
|
}
|
|
|
|
grfFlags = CMF_NORMAL | CMF_CANRENAME;
|
|
if (m_fExploreMode != FALSE) {
|
|
grfFlags |= CMF_EXPLORE;
|
|
}
|
|
|
|
Assert(hCMenu != NULL);
|
|
|
|
m_idCMStartOffset = SUBIDSTART;
|
|
pICMenu->QueryContextMenu(hCMenu, 0, SUBIDSTART, SUBIDEND, grfFlags);
|
|
|
|
// set site on the context menu if it will let us...
|
|
IUnknown_SetSite(pICMenu, (IUnknown*)(IShellView*)this);
|
|
|
|
// If this is the common dialog browser, we need to make the
|
|
// default command "Select" so that double-clicking (which is
|
|
// open in common dialog) makes sense.
|
|
if (m_pCommDlg && fItem) {
|
|
HMENU hmSelect = LoadPopupMenu(g_hinstDll, IDM_COMMDLG_POPUPMERGE);
|
|
|
|
// NOTE: Since commdlg always eats the default command,
|
|
// we don't care what id we assign hmSelect, as long as it
|
|
// doesn't conflict with any other context menu id.
|
|
// SUBIDSTART won't conflict with anyone.
|
|
MergeMenus(hCMenu, hmSelect, (UINT)(SUBIDSTART - 1), 0);
|
|
|
|
SetMenuDefaultItem(hCMenu, 0, MF_BYPOSITION);
|
|
DestroyMenu(hmSelect);
|
|
}
|
|
|
|
// cache the context menu so we can do the status bar stuff ...
|
|
|
|
// This function needs to be reentrant because the TrackPopupMenu call below blocks in it's own message
|
|
// pump. If we already have an m_pCMCache that means there is another WM_CONTEXTMENU message that is still
|
|
// being processed and is currently blocked in the TrackPopupMenu call. We need to discard the previously
|
|
// stored m_pCMCache and use the new one. Note that we aren't dealing with multi-threaded reentrance, just
|
|
// with recursion in our window proc due to the message pump in TrackPopupMenu.
|
|
if (m_pCMCache) {
|
|
m_pCMCache->Release();
|
|
}
|
|
|
|
m_pCMCache = pICMenu;
|
|
m_pCMCache->AddRef();
|
|
|
|
id = TrackPopupMenu(hCMenu, TPM_RETURNCMD | TPM_RIGHTBUTTON,
|
|
iX, iY, 0, m_hWnd, NULL);
|
|
|
|
// At this point we might have already released the m_pCMCache because we could have recursed into this
|
|
// function. If we reentered then we would have already released the interface and set m_pCMCache to
|
|
// NULL. Notice that we aren't dealing with multiple threads, just a recursion in our window proc due to
|
|
// the message pump in TrackPopupMenu. None of this code is thread safe but it doesn't need to be.
|
|
if (m_pCMCache) {
|
|
m_pCMCache->Release();
|
|
m_pCMCache = NULL;
|
|
}
|
|
|
|
// did we get a valid menu selection ?
|
|
if (id > 0) {
|
|
if (id >= SUBIDSTART && id <= SUBIDEND) {
|
|
// need to see if it was the rename command....
|
|
CHAR szBuffer[MAX_PATH];
|
|
|
|
szBuffer[0] = TEXT('\0');
|
|
hRes = pICMenu->GetCommandString(id - SUBIDSTART,
|
|
GCS_VERBA, NULL, (LPSTR)szBuffer, ARRAYSIZE(szBuffer));
|
|
|
|
// check for the unicode version...
|
|
if (FAILED(hRes)) {
|
|
WCHAR szCommand[60];
|
|
szCommand[0] = L'\0';
|
|
hRes = pICMenu->GetCommandString(id - SUBIDSTART,
|
|
GCS_VERBW, NULL, (LPSTR)szCommand, ARRAYSIZE(szCommand));
|
|
SHUnicodeToAnsi(szCommand, szBuffer, ARRAYSIZE(szBuffer));
|
|
}
|
|
|
|
if (SUCCEEDED(hRes) && StrCmpIA(szBuffer, "Rename") == 0) {
|
|
// force the view to rename it instead...
|
|
int iItem = rgInfo.iItem;
|
|
if (fItemsFromSelection && iItem == -1)
|
|
iItem = ListView_GetNextItem(m_hWndListView, -1, LVNI_SELECTED);
|
|
|
|
ListView_EditLabel(m_hWndListView, iItem);
|
|
} else {
|
|
Assert(m_pidl);
|
|
WCHAR szWBuffer[MAX_PATH];
|
|
|
|
SHGetPathFromIDListWrapW(m_pidl, szWBuffer);
|
|
SHUnicodeToAnsi(szWBuffer, szBuffer, ARRAYSIZE(szBuffer));
|
|
|
|
if (IsOS(OS_NT)) {
|
|
// pass the command to the element ....
|
|
CMINVOKECOMMANDINFOEX rgCommand;
|
|
memset(&rgCommand, 0, sizeof(rgCommand));
|
|
rgCommand.cbSize = sizeof(rgCommand);
|
|
rgCommand.lpVerb = (LPCSTR)MAKEINTRESOURCE(id - SUBIDSTART);
|
|
rgCommand.lpVerbW = (LPWSTR)rgCommand.lpVerb;
|
|
rgCommand.fMask = CMIC_MASK_UNICODE;
|
|
m_pBrowser->GetWindow(&(rgCommand.hwnd));
|
|
rgCommand.nShow = SW_NORMAL;
|
|
rgCommand.lpDirectory = szBuffer;
|
|
rgCommand.lpDirectoryW = szWBuffer;
|
|
|
|
pICMenu->InvokeCommand((CMINVOKECOMMANDINFO*)&rgCommand);
|
|
} else {
|
|
// pass the command to the element ....
|
|
CMINVOKECOMMANDINFO rgCommand;
|
|
memset(&rgCommand, 0, sizeof(rgCommand));
|
|
rgCommand.cbSize = sizeof(rgCommand);
|
|
rgCommand.lpVerb = (LPCSTR)MAKEINTRESOURCE(id - SUBIDSTART);
|
|
m_pBrowser->GetWindow(&(rgCommand.hwnd));
|
|
rgCommand.nShow = SW_NORMAL;
|
|
rgCommand.lpDirectory = szBuffer;
|
|
pICMenu->InvokeCommand(&rgCommand);
|
|
}
|
|
}
|
|
} else if ((id == SUBIDSTART - 1) && (m_pCommDlg != NULL)) {
|
|
// we are in the common dialog, and we are being told to do the default ...
|
|
// NOTE: we pass the Defview as the IshellView (it will delegate back)
|
|
hRes = m_pCommDlg->OnDefaultCommand(m_pDefView);
|
|
}
|
|
}
|
|
|
|
// we handled the command
|
|
lRes = FALSE;
|
|
|
|
ContextCleanup:
|
|
// clean up .....
|
|
// release the menu first incase it needs to tidy up the menu....
|
|
IUnknown_SetSite(pICMenu, NULL);
|
|
if (pICMenu != NULL)
|
|
pICMenu->Release();
|
|
|
|
if (hCMenu != NULL)
|
|
DestroyMenu(hCMenu);
|
|
|
|
if (apidl != 0)
|
|
delete[] apidl;
|
|
|
|
return lRes;
|
|
}
|
|
|
|
|
|
HRESULT CThumbnailView::CreateBackgroundMenu(IContextMenu** ppMenu)
|
|
{
|
|
Assert(m_pDefView != NULL);
|
|
|
|
// Ask the def-view IShellView for the context-menu
|
|
return m_pDefView->GetItemObject(SVGIO_BACKGROUND,
|
|
IID_IContextMenu,
|
|
(void**)ppMenu);
|
|
}
|
|
|
|
|
|
void CThumbnailView::UpdateStatusBar(LPCWSTR pszText, LPCWSTR pszText2)
|
|
{
|
|
int iSelCount;
|
|
WCHAR szStatusText[MAX_PATH + 1];
|
|
LRESULT lRes;
|
|
|
|
// make sure we have a pointer to the browser.
|
|
if (m_pBrowser == NULL)
|
|
return;
|
|
|
|
if (!pszText) {
|
|
// get selection count from view that is being displayed.
|
|
iSelCount = ListView_GetSelectedCount(m_hWndListView);
|
|
|
|
UINT idMsg = 0;
|
|
if (iSelCount > 0) {
|
|
idMsg = MSG_STATUS_OBJECTS_SELECTED;
|
|
} else {
|
|
idMsg = MSG_STATUS_OBJECTS;
|
|
iSelCount = ListView_GetItemCount(m_hWndListView);
|
|
}
|
|
|
|
void* pArg = (LPVOID)iSelCount;
|
|
|
|
FormatMessageWrapW(FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ARGUMENT_ARRAY,
|
|
g_hinstDll,
|
|
idMsg,
|
|
0,
|
|
szStatusText,
|
|
MAX_PATH * 2,
|
|
(va_list*)&pArg);
|
|
pszText = szStatusText;
|
|
} else if (IS_INTRESOURCE(pszText)) {
|
|
LoadStringWrapW(g_hinstDll, PtrToUlong(pszText), szStatusText, ARRAYSIZE(szStatusText));
|
|
pszText = szStatusText;
|
|
}
|
|
|
|
// display the message.
|
|
m_pBrowser->SendControlMsg(FCW_STATUS, SB_SETTEXTW, 0, (LPARAM)pszText, &lRes);
|
|
|
|
if (pszText2 && IS_INTRESOURCE(pszText2)) {
|
|
LoadStringWrapW(g_hinstDll, PtrToUlong(pszText), szStatusText, ARRAYSIZE(szStatusText));
|
|
pszText2 = szStatusText;
|
|
}
|
|
|
|
// display the message.
|
|
m_pBrowser->SendControlMsg(FCW_STATUS, SB_SETTEXTW, 1, (LPARAM)pszText2, &lRes);
|
|
}
|
|
|
|
|
|
|
|
LRESULT CThumbnailView::OnWmMenuSelect(HWND hWnd, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
if (m_pCMCache == NULL) {
|
|
// there is no cached menu, forward it to the parent window
|
|
return SendMessageA(m_hWndParent, WM_MENUSELECT, wParam, lParam);
|
|
} else {
|
|
if (lParam != NULL && HIWORD(wParam) != 0xffff) {
|
|
// the user has not canceled the menu
|
|
WCHAR szMessage[MAX_PATH];
|
|
|
|
szMessage[0] = 0;
|
|
UINT idCmd = LOWORD(wParam) - m_idCMStartOffset;
|
|
UINT rgfFlags = HIWORD(wParam);
|
|
|
|
if (LOWORD(wParam) > m_idCMStartOffset
|
|
&& !(rgfFlags & MF_SEPARATOR)) {
|
|
HRESULT hRes = m_pCMCache->GetCommandString(idCmd,
|
|
GCS_HELPTEXTW,
|
|
NULL,
|
|
(LPSTR)szMessage,
|
|
ARRAYSIZE(szMessage));
|
|
|
|
// we have tried the uncode stuff, now try the ansi stuff.....
|
|
if (hRes != S_OK) {
|
|
CHAR szTmp[MAX_PATH];
|
|
szTmp[0] = 0;
|
|
|
|
hRes = m_pCMCache->GetCommandString(idCmd,
|
|
GCS_HELPTEXTA,
|
|
NULL,
|
|
szTmp,
|
|
ARRAYSIZE(szTmp));
|
|
if (hRes == S_OK) {
|
|
MultiByteToWideChar(CP_ACP, 0, szTmp, -1, szMessage, MAX_PATH);
|
|
}
|
|
}
|
|
}
|
|
|
|
LRESULT lRes = 0;
|
|
// send the text to the status bar ...
|
|
m_pBrowser->SendControlMsg(FCW_STATUS,
|
|
SB_SETTEXTW,
|
|
(WPARAM)0,
|
|
(LPARAM)(LPWSTR)szMessage,
|
|
&lRes);
|
|
|
|
|
|
m_pBrowser->SendControlMsg(FCW_STATUS,
|
|
SB_SETTEXTA,
|
|
(WPARAM)1,
|
|
(LPARAM)(LPTSTR)"",
|
|
&lRes);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
LRESULT CThumbnailView::OnLVNEndLabelEdit(NMHDR* pHdr)
|
|
{
|
|
LV_DISPINFOW* pInfo = (LV_DISPINFOW*)pHdr;
|
|
WCHAR szName[MAX_PATH];
|
|
LPITEMIDLIST pPidl = (LPITEMIDLIST)pInfo->item.lParam;
|
|
LPITEMIDLIST pNewPidl = NULL;
|
|
int iImage = pInfo->item.iImage;
|
|
|
|
if (pInfo->item.pszText == NULL || pInfo->item.iItem == -1) {
|
|
// the user has cancelled the edit....
|
|
return FALSE;
|
|
}
|
|
|
|
LV_ITEMW rgItem;
|
|
memset(&rgItem, 0, sizeof(LV_ITEM));
|
|
if (!(pInfo->item.mask & (LVIF_PARAM | LVIF_IMAGE))) {
|
|
// if we weren't give the pidl, go get it....
|
|
rgItem.mask = LVIF_PARAM | LVIF_IMAGE | LVIF_NORECOMPUTE;
|
|
rgItem.iItem = pInfo->item.iItem;
|
|
ListView_GetItemWrapW(m_hWndListView, &rgItem);
|
|
pPidl = (LPITEMIDLIST)pInfo->item.lParam;
|
|
iImage = rgItem.iImage;
|
|
}
|
|
|
|
// all UNICODE now ...
|
|
// copy the new name the user entered
|
|
// MultiByteToWideChar( CP_ACP, 0, pInfo->item.pszText, -1, szName, MAX_PATH );
|
|
StrCpyW(szName, pInfo->item.pszText);
|
|
|
|
// the pidl might have dispappeared from under us if they put up a dialog box
|
|
// on failure because of the disk notifications.
|
|
LPITEMIDLIST pTmpPidl = ILClone(pPidl);
|
|
if (pTmpPidl == NULL) {
|
|
MessageBoxWrapW(m_hWndListView,
|
|
(LPCWSTR)MAKEINTRESOURCE(IDS_ERR_OUTOFMEM),
|
|
(LPCWSTR)MAKEINTRESOURCE(IDS_RENAME_TITLE),
|
|
MB_OK | MB_SETFOREGROUND | MB_ICONSTOP);
|
|
return FALSE;
|
|
}
|
|
|
|
// the folder should handle the renaming including the old extension, as it is
|
|
// the only one that knows what the old extension was ....
|
|
HRESULT hRes = m_pFolder->SetNameOf(m_hWnd, pPidl, szName, 0, &pNewPidl);
|
|
if (SUCCEEDED(hRes)) {
|
|
// if we asked for a new pidl, then we should have one ....
|
|
Assert(pNewPidl != NULL);
|
|
|
|
// update the text on screen (I'm surprised it doesn't do this
|
|
// automatically ...
|
|
|
|
// update the pidl ..
|
|
rgItem.iItem = pInfo->item.iItem;
|
|
rgItem.mask = LVIF_PARAM | LVIF_TEXT | LVIF_IMAGE;
|
|
rgItem.lParam = (LPARAM)pNewPidl;
|
|
|
|
Assert(m_pImageCache);
|
|
m_pImageCache->DeleteImage(iImage);
|
|
|
|
// use the text we were given ...
|
|
rgItem.pszText = pInfo->item.pszText;
|
|
rgItem.iImage = I_IMAGECALLBACK;
|
|
ListView_SetItemWrapW(m_hWndListView, &rgItem);
|
|
|
|
// should free the old pidl
|
|
SHFree(pPidl);
|
|
} else {
|
|
// we have failed, we must find the item again because if they put up a dialog
|
|
// telling the user, it is highly likely that shell change notifications have
|
|
// been processed and we might not be on the same item. .....
|
|
int iItem = FindInView(m_hWndListView, m_pFolder, pTmpPidl);
|
|
|
|
if (iItem >= 0) {
|
|
SendMessageWrapW(m_hWndListView,
|
|
LVM_EDITLABEL,
|
|
pInfo->item.iItem,
|
|
(LPARAM)pInfo->item.pszText);
|
|
}
|
|
}
|
|
|
|
SHFree(pTmpPidl);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
LRESULT CThumbnailView::OnDefaultAction(BOOL fDoubleClick)
|
|
{
|
|
int iHitItem = -1;
|
|
BOOL fAlt = (GetAsyncKeyState(VK_MENU) < 0);
|
|
|
|
if (fDoubleClick) {
|
|
// only do a hit-test if there was a double-click.
|
|
POINT rgPoint;
|
|
DWORD dwPos = GetMessagePos();
|
|
rgPoint.x = LOWORD(dwPos);
|
|
rgPoint.y = HIWORD(dwPos);
|
|
|
|
BOOL bRes = ScreenToClient(m_hWndListView, &rgPoint);
|
|
LV_HITTESTINFO rgHit;
|
|
rgHit.pt = rgPoint;
|
|
rgHit.flags = LVHT_ONITEM;
|
|
// if there was a double click on the view, ignore it.
|
|
int iRes = ListView_HitTest(m_hWndListView, &rgHit);
|
|
if (iRes == -1) {
|
|
return FALSE;
|
|
}
|
|
iHitItem = iRes;
|
|
}
|
|
|
|
LPCONTEXTMENU pMenu = NULL;
|
|
HRESULT hRes = NOERROR;
|
|
|
|
// we hit something, use the selection from here on it....
|
|
int iCount = ListView_GetSelectedCount(m_hWndListView);
|
|
// if there is nothing selected ( this would only happen on enter ),
|
|
// then ignore it.
|
|
if (iCount <= 0) {
|
|
return FALSE;
|
|
}
|
|
|
|
LPCITEMIDLIST* apidl = new LPCITEMIDLIST[iCount];
|
|
if (apidl == NULL) {
|
|
// TODO: low memory situations
|
|
MessageBoxWrapW(m_hWnd,
|
|
(LPWSTR)MAKEINTRESOURCE(IDS_ERR_OUTOFMEM),
|
|
(LPWSTR)MAKEINTRESOURCE(IDS_ERR_SHELLTITLE),
|
|
MB_OK | MB_SETFOREGROUND | MB_ICONSTOP);
|
|
|
|
}
|
|
|
|
GetSelectionPidlList(m_hWndListView, iCount, apidl, iHitItem);
|
|
|
|
UINT rgfFlags = 0;
|
|
|
|
// now generate the context menu for it ...
|
|
hRes = m_pFolder->GetUIObjectOf(m_hWnd,
|
|
iCount,
|
|
apidl,
|
|
IID_IContextMenu,
|
|
&rgfFlags,
|
|
(void**)&pMenu);
|
|
|
|
if (FAILED(hRes)) {
|
|
MessageBoxWrapW(m_hWnd,
|
|
(LPWSTR)MAKEINTRESOURCE(IDS_ERR_CONTEXTMENUFAILED),
|
|
(LPWSTR)MAKEINTRESOURCE(IDS_ERR_SHELLTITLE),
|
|
MB_OK | MB_SETFOREGROUND | MB_ICONSTOP);
|
|
}
|
|
|
|
HMENU hMenu = CreatePopupMenu();
|
|
if (hMenu == NULL) {
|
|
MessageBoxWrapW(m_hWnd,
|
|
(LPWSTR)MAKEINTRESOURCE(IDS_ERR_CONTEXTMENUFAILED),
|
|
(LPWSTR)MAKEINTRESOURCE(IDS_ERR_SHELLTITLE),
|
|
MB_OK | MB_SETFOREGROUND | MB_ICONSTOP);
|
|
}
|
|
|
|
UINT grfFlags = CMF_DEFAULTONLY;
|
|
if (m_fExploreMode != FALSE) {
|
|
grfFlags |= CMF_EXPLORE;
|
|
}
|
|
hRes = pMenu->QueryContextMenu(hMenu,
|
|
0,
|
|
SUBIDSTART,
|
|
SUBIDEND,
|
|
grfFlags);
|
|
if (FAILED(hRes)) {
|
|
MessageBoxWrapW(m_hWnd,
|
|
(LPWSTR)MAKEINTRESOURCE(IDS_ERR_CONTEXTMENUFAILED),
|
|
(LPWSTR)MAKEINTRESOURCE(IDS_ERR_SHELLTITLE),
|
|
MB_OK | MB_SETFOREGROUND | MB_ICONSTOP);
|
|
}
|
|
|
|
|
|
int idCmd = -1;
|
|
if (!fAlt) {
|
|
// find the default command ID ..
|
|
int iSize = GetMenuItemCount(hMenu);
|
|
|
|
for (int iItem = 0; iItem < iSize; iItem++) {
|
|
MENUITEMINFOW mii;
|
|
mii.cbSize = sizeof(mii);
|
|
mii.fMask = MIIM_ID | MIIM_STATE;
|
|
GetMenuItemInfoWrapW(hMenu, iItem, TRUE, &mii);
|
|
|
|
if ((mii.fState & MFS_DEFAULT) && !(mii.fState & (MFS_GRAYED | MFS_DISABLED))) {
|
|
idCmd = (int)mii.wID;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// use the first item anyway if there is no default ....
|
|
if (idCmd == -1) {
|
|
MENUITEMINFOW mii;
|
|
mii.cbSize = sizeof(mii);
|
|
mii.fMask = MIIM_ID | MIIM_STATE;
|
|
GetMenuItemInfoWrapW(hMenu, 0, TRUE, &mii);
|
|
idCmd = mii.wID;
|
|
|
|
if (mii.fState & (MFS_GRAYED | MFS_DISABLED)) {
|
|
idCmd = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (idCmd != -1 || fAlt) {
|
|
WCHAR szWDirectory[MAX_PATH];
|
|
CHAR szDirectory[MAX_PATH];
|
|
|
|
Assert(m_pidl);
|
|
SHGetPathFromIDListWrapW(m_pidl, szWDirectory);
|
|
SHUnicodeToAnsi(szWDirectory, szDirectory, ARRAYSIZE(szDirectory));
|
|
|
|
if (IsOS(OS_NT)) {
|
|
CMINVOKECOMMANDINFOEX rgCmd;
|
|
memset(&rgCmd, 0, sizeof(rgCmd));
|
|
rgCmd.cbSize = sizeof(rgCmd);
|
|
|
|
if (fAlt) {
|
|
rgCmd.lpVerb = "Properties";
|
|
rgCmd.lpVerbW = L"Properties";
|
|
} else {
|
|
rgCmd.lpVerb = (LPSTR)idCmd - SUBIDSTART;
|
|
rgCmd.lpVerbW = (LPWSTR)rgCmd.lpVerb;
|
|
}
|
|
|
|
rgCmd.nShow = SW_NORMAL;
|
|
m_pBrowser->GetWindow(&(rgCmd.hwnd));
|
|
rgCmd.fMask = CMIC_MASK_UNICODE;
|
|
|
|
// execute the command finally !!
|
|
hRes = pMenu->InvokeCommand((CMINVOKECOMMANDINFO*)&rgCmd);
|
|
|
|
} else {
|
|
CMINVOKECOMMANDINFO rgCmd;
|
|
memset(&rgCmd, 0, sizeof(rgCmd));
|
|
rgCmd.cbSize = sizeof(rgCmd);
|
|
|
|
if (fAlt) {
|
|
rgCmd.lpVerb = "Properties";
|
|
} else {
|
|
rgCmd.lpVerb = (LPSTR)idCmd - SUBIDSTART;
|
|
}
|
|
rgCmd.nShow = SW_NORMAL;
|
|
m_pBrowser->GetWindow(&(rgCmd.hwnd));
|
|
rgCmd.lpDirectory = szDirectory;
|
|
|
|
// execute the command finally !!
|
|
hRes = pMenu->InvokeCommand(&rgCmd);
|
|
}
|
|
}
|
|
|
|
DestroyMenu(hMenu);
|
|
pMenu->Release();
|
|
delete[] apidl;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
LRESULT CThumbnailView::OnWmCreate(HWND hWnd, LPCREATESTRUCT pCreate)
|
|
{
|
|
SetWindowLongPtrA(hWnd, 0, (LONG_PTR)this);
|
|
|
|
// remember the hWnd
|
|
m_hWnd = hWnd;
|
|
|
|
// create the Enhanced view with a size of zero (remember the move is coming next ...)
|
|
DWORD dwStyle = LVS_ICON | LVS_SHAREIMAGELISTS | LVS_EDITLABELS | LVS_SHOWSELALWAYS;
|
|
if (m_rgSettings.fFlags & FWF_SINGLESEL) {
|
|
dwStyle |= LVS_SINGLESEL;
|
|
}
|
|
|
|
if (m_rgSettings.fFlags & FWF_NOSCROLL) {
|
|
dwStyle |= LVS_NOSCROLL;
|
|
}
|
|
|
|
if (m_rgSettings.fFlags & FWF_ALIGNLEFT) {
|
|
dwStyle |= LVS_ALIGNLEFT;
|
|
}
|
|
|
|
m_hWndListView = CreateWindowExWrapW(WS_EX_CLIENTEDGE,
|
|
WC_LISTVIEWW, NULL,
|
|
WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE | dwStyle,
|
|
0, 0,
|
|
0, 0, m_hWnd, NULL, g_hinstDll, NULL);
|
|
if (m_hWndListView == NULL) {
|
|
// fail the create...
|
|
return -1;
|
|
}
|
|
|
|
// get the imagelists we will use, here we just grab the system ones
|
|
SHFILEINFO rgInfo;
|
|
m_hSysLargeImgLst = (HIMAGELIST)SHGetFileInfo(TEXT("*.exe"), 0, &rgInfo, sizeof(rgInfo),
|
|
SHGFI_SYSICONINDEX | SHGFI_USEFILEATTRIBUTES | SHGFI_LARGEICON);
|
|
|
|
m_hSysSmallImgLst = (HIMAGELIST)SHGetFileInfo(TEXT("*.exe"), 0, &rgInfo, sizeof(rgInfo),
|
|
SHGFI_SYSICONINDEX | SHGFI_USEFILEATTRIBUTES | SHGFI_SMALLICON);
|
|
|
|
Assert(m_himlThumbs);
|
|
ListView_SetImageList(m_hWndListView, m_himlThumbs, LVSIL_NORMAL);
|
|
|
|
ListView_Arrange(m_hWndListView, LVA_SNAPTOGRID);
|
|
|
|
int cxIcon, cyIcon;
|
|
ImageList_GetIconSize(m_hSysLargeImgLst, &cxIcon, &cyIcon);
|
|
|
|
ListView_SetIconSpacing(m_hWndListView, cxIcon / 4 + m_iXSizeThumbnail + 1, 0);
|
|
|
|
// create the window's accelerator table.
|
|
// TODO:
|
|
// m_hAccel = LoadAccelerators( g_hinstDll, MAKEINTRESOURCE( IDR_ACCELERATOR1 ));
|
|
DWORD dwMainStyle;
|
|
if (!(m_rgSettings.fFlags & FWF_AUTOARRANGE)) {
|
|
// turn off autoarrange if not asked for ....
|
|
dwMainStyle = GetWindowStyle(m_hWndListView) & ~LVS_AUTOARRANGE;
|
|
SetWindowLongWrapW(m_hWndListView, GWL_STYLE, dwMainStyle);
|
|
} else {
|
|
// turn on auto arrange in enhanced view.
|
|
// this has to be done after setting the imagelist for the enhanced view,
|
|
// so that the auto-arrange will work with the appropriate thumbnail size.
|
|
dwMainStyle = GetWindowStyle(m_hWndListView) | LVS_AUTOARRANGE;
|
|
SetWindowLongWrapW(m_hWndListView, GWL_STYLE, dwMainStyle);
|
|
}
|
|
|
|
ListView_SetExtendedListViewStyle(m_hWndListView, LVS_EX_INFOTIP | LVS_EX_BORDERSELECT);
|
|
|
|
CheckViewOptions();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
LRESULT CThumbnailView::OnWmDestroy(HWND hWnd)
|
|
{
|
|
if (m_hWndListView != NULL) {
|
|
// ensure it has been emptied ....
|
|
HWND hWndList = m_hWndListView;
|
|
m_hWndListView = NULL;
|
|
Clear(hWndList);
|
|
DestroyWindow(hWndList);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
HRESULT CThumbnailView::StartDragDrop(NM_LISTVIEW* pNMHdr)
|
|
{
|
|
LPDATAOBJECT pDataObject = NULL;
|
|
LPDROPSOURCE pDropSrc = NULL;
|
|
HRESULT hRes = NOERROR;
|
|
DWORD dwEffect = DROPEFFECT_LINK | DROPEFFECT_MOVE | DROPEFFECT_COPY;
|
|
UINT rgfFlags;
|
|
CComObject<CViewDropSource>* pSrc = NULL;
|
|
LPCITEMIDLIST* apidl = NULL;
|
|
int iSelCount = 0;
|
|
|
|
// get the view origin
|
|
this->GetOrigin(&m_ptDragStart);
|
|
|
|
// add on the current position within the view, therefore getting the true view start pos
|
|
m_ptDragStart.x += pNMHdr->ptAction.x;
|
|
m_ptDragStart.y += pNMHdr->ptAction.y;
|
|
|
|
m_pDropTarget->DragStartHere(&m_ptDragStart);
|
|
|
|
// how many elements selected ?
|
|
iSelCount = ListView_GetSelectedCount(m_hWndListView);
|
|
apidl = new LPCITEMIDLIST[iSelCount];
|
|
if (apidl == NULL) {
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
// pidl list big enough for the selection
|
|
hRes = GetSelectionPidlList(m_hWndListView, iSelCount, apidl, pNMHdr->iItem);
|
|
if (FAILED(hRes)) {
|
|
hRes = E_FAIL;
|
|
goto SDDErrorRecovery;
|
|
}
|
|
|
|
// fetch the attributes of the selection
|
|
hRes = m_pFolder->GetAttributesOf(iSelCount, apidl, &dwEffect);
|
|
if (FAILED(hRes)) {
|
|
// use the hRes fail result
|
|
goto SDDErrorRecovery;
|
|
}
|
|
|
|
// ignore all other bits returned....
|
|
dwEffect &= (DROPEFFECT_LINK | DROPEFFECT_MOVE | DROPEFFECT_COPY);
|
|
|
|
hRes = NOERROR;
|
|
pSrc = new CComObject<CViewDropSource>;
|
|
if (pSrc != NULL) {
|
|
if (FAILED(hRes)) {
|
|
delete pSrc;
|
|
goto SDDErrorRecovery;
|
|
}
|
|
|
|
// get a ref on the object...
|
|
hRes = pSrc->QueryInterface(IID_IDropSource, (void**)&pDropSrc);
|
|
Assert(SUCCEEDED(hRes));
|
|
} else {
|
|
hRes = E_OUTOFMEMORY;
|
|
goto SDDErrorRecovery;
|
|
}
|
|
|
|
rgfFlags = SVGIO_SELECTION;
|
|
// get the DataObject covering the selection...
|
|
hRes = m_pFolder->GetUIObjectOf(m_hWnd, iSelCount, apidl, IID_IDataObject,
|
|
&rgfFlags, (void**)&pDataObject);
|
|
if (SUCCEEDED(hRes)) {
|
|
// used to detect if we are dropping in the same view ...
|
|
m_fDragStarted = TRUE;
|
|
|
|
// try and set the item positions in the data object...
|
|
SetPointData(pDataObject, apidl, iSelCount);
|
|
|
|
// Before we enter drag drop, try and initialize the drag images.
|
|
if (m_pDragImages)
|
|
m_pDragImages->InitializeFromWindow(m_hWndListView, 0, pDataObject);
|
|
|
|
// here we go, ole drag and drop ....
|
|
hRes = DoDragDrop(pDataObject, pDropSrc, dwEffect, &dwEffect);
|
|
|
|
m_fDragStarted = FALSE;
|
|
|
|
pDropSrc->Release();
|
|
pDropSrc = NULL;
|
|
pDataObject->Release();
|
|
}
|
|
|
|
SDDErrorRecovery:
|
|
if (pDropSrc != NULL) {
|
|
pDropSrc->Release();
|
|
}
|
|
|
|
if (apidl != NULL) {
|
|
delete[](LPITEMIDLIST*) apidl;
|
|
}
|
|
|
|
m_fDragStarted = FALSE;
|
|
|
|
return hRes;
|
|
}
|
|
|
|
|
|
LRESULT CThumbnailView::OnViewGetThumbnail(NMHDR* pHdr)
|
|
{
|
|
LV_DISPINFO* pInfo = (LV_DISPINFO*)pHdr;
|
|
WCHAR szFileName[MAX_PATH];
|
|
HBITMAP hBmpThumb = NULL;
|
|
UINT iImageIndex = (UINT)I_IMAGECALLBACK;
|
|
LPCITEMIDLIST pidl = (LPCITEMIDLIST)pInfo->item.lParam;
|
|
UINT rgFlags = 0;
|
|
UINT iIndex = (UINT)I_IMAGECALLBACK;
|
|
HRESULT hr = NOERROR;
|
|
BOOL fStampIcon = FALSE;
|
|
|
|
if (pidl == NULL) {
|
|
// we should always get a pidl...
|
|
Assert(FALSE);
|
|
return TRUE;
|
|
}
|
|
|
|
DWORD dwMask = GetOverlayMask(pidl);
|
|
|
|
DWORD dwStoreFlags = 0;
|
|
|
|
// is the thumbnail cache at its limit ? If so, we had better try and make space..
|
|
UINT uTmp = 0;
|
|
m_pImageCache->GetCacheSize(&uTmp);
|
|
int iCacheSpace = m_iMaxCacheSize - uTmp;
|
|
|
|
int iQueuedFetch = 0;
|
|
if (m_pScheduler) {
|
|
iQueuedFetch = m_pScheduler->CountTasks(TOID_ExtractImageTask);
|
|
iQueuedFetch += m_pScheduler->CountTasks(TOID_DiskCacheTask);
|
|
iQueuedFetch -= m_pScheduler->CountTasks(TOID_ImgCacheTidyup);
|
|
}
|
|
|
|
iCacheSpace -= iQueuedFetch;
|
|
|
|
// add enough scroungers to shrink the cache to the right size...
|
|
while ((iCacheSpace++) < 0) {
|
|
IRunnableTask* pTask;
|
|
hr = CImgCacheTidyup_Create(m_pImageCache, FALSE, m_hWndListView, &m_iLastCtr, &pTask);
|
|
if (SUCCEEDED(hr)) {
|
|
if (m_pScheduler) {
|
|
m_pScheduler->AddTask(pTask,
|
|
TOID_ImgCacheTidyup,
|
|
ITSAT_DEFAULT_LPARAM,
|
|
PRIORITY_NORMAL);
|
|
} else {
|
|
pTask->Run();
|
|
pTask->Release();
|
|
}
|
|
}
|
|
}
|
|
|
|
hr = ExtractItem(&iImageIndex, pInfo->item.iItem, pidl, TRUE, FALSE);
|
|
if (hr != S_OK) {
|
|
// create the default one for that file type,
|
|
// the index into the sys image list is used to detect items of the
|
|
// same type, thus we only generate one default thumbnail for each
|
|
// particular icon needed
|
|
iIndex = (UINT)ViewGetIconIndex(pidl);
|
|
|
|
if (iIndex == (UINT)I_IMAGECALLBACK)
|
|
iIndex = II_DOCNOASSOC;
|
|
|
|
// check if the image is already in the image cache.
|
|
IMAGECACHEINFO rgInfo;
|
|
rgInfo.cbSize = sizeof(rgInfo);
|
|
rgInfo.dwMask = ICIFLAG_NAME | ICIFLAG_FLAGS | ICIFLAG_INDEX;
|
|
rgInfo.pszName = c_szDefault;
|
|
rgInfo.dwFlags = dwMask;
|
|
rgInfo.iIndex = (int)iIndex;
|
|
|
|
hr = m_pImageCache->FindImage(&rgInfo, &iImageIndex);
|
|
if (hr != S_OK) {
|
|
hr = CreateDefaultThumbnail(iIndex, &hBmpThumb, m_fDrawBorder);
|
|
StrCpyW(szFileName, c_szDefault);
|
|
iImageIndex = (UINT)I_IMAGECALLBACK;
|
|
}
|
|
}
|
|
|
|
if (hBmpThumb != NULL) {
|
|
// we are creating a new one, so we shouldn't have an index yet ..
|
|
Assert(iImageIndex == I_IMAGECALLBACK);
|
|
|
|
#if 0 // never gets called, fStampIcon always false . . .
|
|
if (fStampIcon) {
|
|
// a small icon is stamped in bottom right-hand corner of thumbnail.
|
|
StampIconOnThumbnail(pidl, hBmpThumb);
|
|
}
|
|
#endif
|
|
|
|
// copy thumbnail into the imagelist.
|
|
IMAGECACHEINFO rgInfo;
|
|
rgInfo.cbSize = sizeof(rgInfo);
|
|
rgInfo.dwMask = ICIFLAG_NAME | ICIFLAG_FLAGS | ICIFLAG_INDEX | ICIFLAG_LARGE | ICIFLAG_BITMAP;
|
|
rgInfo.pszName = szFileName;
|
|
rgInfo.dwFlags = dwMask;
|
|
rgInfo.iIndex = (int)iIndex;
|
|
rgInfo.hBitmapLarge = hBmpThumb;
|
|
rgInfo.hMaskLarge = NULL;
|
|
|
|
if (!fStampIcon) {
|
|
rgInfo.dwMask |= ICIFLAG_NOUSAGE;
|
|
}
|
|
if (IS_BIDI_LOCALIZED_SYSTEM()) {
|
|
rgInfo.dwMask |= ICIFLAG_MIRROR;
|
|
}
|
|
hr = m_pImageCache->AddImage(&rgInfo, &iImageIndex);
|
|
DeleteObject(hBmpThumb);
|
|
}
|
|
|
|
pInfo->item.iImage = (int)iImageIndex;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
DWORD CThumbnailView::GetOverlayMask(LPCITEMIDLIST pidl)
|
|
{
|
|
DWORD dwLink = SFGAO_LINK | SFGAO_SHARE | SFGAO_GHOSTED;
|
|
|
|
m_pFolder->GetAttributesOf(1, &pidl, &dwLink);
|
|
return dwLink;
|
|
}
|
|
|
|
|
|
int CThumbnailView::ViewGetIconIndex(LPCITEMIDLIST pidl)
|
|
{
|
|
// do we have an IShellIcon interface ?
|
|
if (m_pIcon != NULL) {
|
|
int iIndex = -1;
|
|
|
|
HRESULT hRes = m_pIcon->GetIconOf(pidl, 0, &iIndex);
|
|
// check to see if we succeeded and we weren't told to extract the icon
|
|
// ourselves ...
|
|
|
|
if (SUCCEEDED(hRes) && hRes != S_FALSE) {
|
|
IShellIconOverlay* pio;
|
|
if (SUCCEEDED(m_pFolder->QueryInterface(IID_IShellIconOverlay, (LPVOID*)&pio))) {
|
|
int iOverlay;
|
|
if (SUCCEEDED(pio->GetOverlayIndex(pidl, &iOverlay))) {
|
|
iIndex |= iOverlay << 24;
|
|
}
|
|
pio->Release();
|
|
}
|
|
return iIndex;
|
|
}
|
|
}
|
|
|
|
SHFILEINFO rgInfo;
|
|
LPITEMIDLIST pidlFull;
|
|
|
|
rgInfo.iIcon = -1;
|
|
pidlFull = ILCombine(m_pidl, pidl);
|
|
if (pidlFull) {
|
|
SHGetFileInfo((LPCTSTR)pidlFull, 0, &rgInfo, sizeof(rgInfo),
|
|
SHGFI_SYSICONINDEX | SHGFI_LARGEICON | SHGFI_PIDL | SHGFI_OVERLAYINDEX);
|
|
ILFree(pidlFull);
|
|
}
|
|
|
|
return (rgInfo.iIcon >= 0) ? rgInfo.iIcon : II_DOCNOASSOC;
|
|
}
|
|
|
|
|
|
//puts the associated icon in bottom left-hand side of thumbnail.
|
|
void CThumbnailView::StampIconOnThumbnail(LPCITEMIDLIST pidl, HBITMAP hBmpThumb, BOOL fBorder)
|
|
{
|
|
HDC hDC = GetDC(m_hWndListView);
|
|
HDC hDCMem = CreateCompatibleDC(hDC);
|
|
int iImage = ViewGetIconIndex(pidl);
|
|
|
|
// select the thumbnail bitmap into the memory DC.
|
|
HGDIOBJ hTmp = SelectObject(hDCMem, hBmpThumb);
|
|
|
|
if (m_fIconStamp) {
|
|
ImageList_Draw(m_hSysSmallImgLst, (iImage & 0x00ffffff), hDCMem,
|
|
m_iXSizeThumbnail - 20, // left.
|
|
m_iYSizeThumbnail - 20, // top.
|
|
ILD_TRANSPARENT | (INDEXTOOVERLAYMASK(iImage >> 24)));
|
|
}
|
|
if (fBorder) {
|
|
// put a black shadow border on it...
|
|
// assume the bitmap is the size we specified...
|
|
DrawShadowBorder(hDCMem, 0, 0, m_iXSizeThumbnail, m_iYSizeThumbnail);
|
|
}
|
|
|
|
// get the bitmap back.
|
|
SelectObject(hDCMem, hTmp);
|
|
|
|
// release stuff.
|
|
ReleaseDC(m_hWndListView, hDC);
|
|
DeleteDC(hDCMem);
|
|
}
|
|
|
|
#define COLOR_BLACK RGB(0, 0, 0)
|
|
#define COLOR_DKGRAY RGB(128, 128, 128)
|
|
#define COLOR_LTGRAY RGB(192, 192, 192)
|
|
#define COLOR_WHITE RGB(255, 255, 255)
|
|
void CThumbnailView::DrawShadowBorder(HDC hdc, int x, int y, int dx, int dy)
|
|
{
|
|
RECT rc;
|
|
COLORREF clrSave = SetBkColor(hdc, COLOR_DKGRAY);
|
|
int iOldMM = SetMapMode(hdc, MM_TEXT);
|
|
//left
|
|
rc.left = x;
|
|
rc.top = y;
|
|
rc.right = x + 1;
|
|
rc.bottom = y + dy;
|
|
ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL);
|
|
//top
|
|
rc.left = rc.right;
|
|
rc.right = rc.left + dx - 2;
|
|
rc.bottom = rc.top + 1;
|
|
ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL);
|
|
//right inner
|
|
SetBkColor(hdc, COLOR_LTGRAY);
|
|
rc.left = rc.right - 1;
|
|
rc.top = rc.bottom;
|
|
rc.bottom = rc.top + dy - 3;
|
|
ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL);
|
|
// right outer
|
|
SetBkColor(hdc, COLOR_BLACK);
|
|
rc.left = rc.right;
|
|
rc.right = rc.left + 1;
|
|
rc.top -= 1;
|
|
rc.bottom = rc.top + dy;
|
|
ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL);
|
|
// bottom inner
|
|
SetBkColor(hdc, COLOR_LTGRAY);
|
|
rc.left = x + 1;
|
|
rc.right = rc.left + dx - 2;
|
|
rc.bottom -= 1;
|
|
rc.top = rc.bottom - 1;
|
|
ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL);
|
|
// bottom outer
|
|
SetBkColor(hdc, COLOR_BLACK);
|
|
rc.left -= 1;
|
|
rc.right += 1;
|
|
rc.top = rc.bottom;
|
|
rc.bottom = rc.top + 1;
|
|
ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL);
|
|
|
|
SetMapMode(hdc, iOldMM);
|
|
SetBkColor(hdc, clrSave);
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
HRESULT CThumbnailView::CreateDefaultThumbnail(int iIndex, HBITMAP* phBmpThumbnail, BOOL fDrawBorder)
|
|
{
|
|
|
|
HDC hdc = GetDC(NULL);
|
|
HDC hMemDC = CreateCompatibleDC(hdc);
|
|
HRESULT hr = E_FAIL;
|
|
|
|
// get the background for the default thumbnail.
|
|
if (!hMemDC)
|
|
goto LGone;
|
|
*phBmpThumbnail = CreateCompatibleBitmap(hdc, m_iXSizeThumbnail, m_iYSizeThumbnail);
|
|
if (*phBmpThumbnail) {
|
|
HGDIOBJ hTmp = SelectObject(hMemDC, *phBmpThumbnail);
|
|
COLORREF clrOld = SetBkColor(hMemDC, GetSysColor(COLOR_WINDOW));
|
|
RECT rc = {0, 0, m_iXSizeThumbnail, m_iYSizeThumbnail};
|
|
|
|
ExtTextOut(hMemDC, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL);
|
|
SetBkColor(hMemDC, clrOld);
|
|
|
|
int cxIcon, cyIcon, x, y, dx, dy;
|
|
|
|
// calculate position and width of icon.
|
|
ImageList_GetIconSize(m_hSysLargeImgLst, &cxIcon, &cyIcon);
|
|
if (cxIcon < m_iXSizeThumbnail) {
|
|
x = (m_iXSizeThumbnail - cxIcon) / 2;
|
|
dx = cxIcon;
|
|
} else {
|
|
// in case icon size is larger than thumbnail size.
|
|
x = 0;
|
|
dx = m_iXSizeThumbnail;
|
|
}
|
|
|
|
if (cyIcon < m_iYSizeThumbnail) {
|
|
y = (m_iYSizeThumbnail - cyIcon) / 2;
|
|
dy = cyIcon;
|
|
} else {
|
|
// in case icon size is larger than thumbnail size.
|
|
y = 0;
|
|
dy = m_iYSizeThumbnail;
|
|
}
|
|
|
|
ImageList_DrawEx(m_hSysLargeImgLst, (iIndex & 0x00ffffff), hMemDC,
|
|
x, // left.
|
|
y, // top.
|
|
dx,
|
|
dy,
|
|
CLR_DEFAULT,
|
|
CLR_DEFAULT,
|
|
ILD_TRANSPARENT | (INDEXTOOVERLAYMASK(iIndex >> 24)));
|
|
|
|
if (m_fDrawBorder) {
|
|
DrawShadowBorder(hMemDC, 0, 0, m_iXSizeThumbnail, m_iYSizeThumbnail);
|
|
}
|
|
|
|
// get the bitmap produced so that it will be returned.
|
|
*phBmpThumbnail = (HBITMAP)SelectObject(hMemDC, hTmp);
|
|
hr = S_OK;
|
|
}
|
|
|
|
LGone:
|
|
if (hMemDC)
|
|
DeleteDC(hMemDC);
|
|
ReleaseDC(NULL, hdc);
|
|
return hr;
|
|
}
|
|
|
|
|
|
int CThumbnailView::FindItem(LPCITEMIDLIST pidl)
|
|
{
|
|
return FindInView(m_hWndListView, m_pFolder, pidl);
|
|
}
|
|
|
|
|
|
HRESULT CThumbnailView::TaskUpdateItem(LPCITEMIDLIST pidl,
|
|
int iItem,
|
|
DWORD dwMask,
|
|
LPCWSTR pszPath,
|
|
const FILETIME* pftDateStamp,
|
|
int iThumbnail,
|
|
HBITMAP hBmp)
|
|
{
|
|
// check the size of the bitmap to make sure it is big enough, if it is not, then
|
|
// we must center it on a background...
|
|
BITMAP rgBitmap;
|
|
HBITMAP hBmpCleanup = NULL;
|
|
HRESULT hr = E_FAIL;
|
|
|
|
if (::GetObjectWrapW((HGDIOBJ)hBmp, sizeof(rgBitmap), (LPVOID)&rgBitmap)) {
|
|
// if the image is the wrong size, or the wrong colour depth, then do the funky stuff on it..
|
|
if (rgBitmap.bmWidth != m_iXSizeThumbnail ||
|
|
rgBitmap.bmHeight != m_iYSizeThumbnail ||
|
|
rgBitmap.bmBitsPixel > m_dwRecClrDepth) {
|
|
// alloc the colour table just incase....
|
|
LPBITMAPINFO pInfo = (LPBITMAPINFO)LocalAlloc(LPTR, sizeof(BITMAPINFO) + sizeof(RGBQUAD) * 256);
|
|
if (pInfo) {
|
|
// get a DC for this operation...
|
|
HDC hdcMem = CreateCompatibleDC(NULL);
|
|
if (hdcMem) {
|
|
pInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
if (GetDIBits(hdcMem, hBmp, 0, 0, NULL, pInfo, DIB_RGB_COLORS)) {
|
|
// we have the header, now get the data....
|
|
void* pBits = LocalAlloc(LPTR, pInfo->bmiHeader.biSizeImage);
|
|
if (pBits) {
|
|
if (GetDIBits(hdcMem, hBmp, 0, pInfo->bmiHeader.biHeight, pBits, pInfo, DIB_RGB_COLORS)) {
|
|
SIZE rgSize = {m_iXSizeThumbnail, m_iYSizeThumbnail};
|
|
RECT rgRect = {0, 0, rgBitmap.bmWidth, rgBitmap.bmHeight};
|
|
|
|
CalculateAspectRatio(&rgSize, &rgRect);
|
|
|
|
if (FactorAspectRatio(pInfo, pBits, &rgSize, rgRect, m_dwRecClrDepth, m_hpal, FALSE, &hBmpCleanup)) {
|
|
// finally success :-) we have the new image we can abandon the old one...
|
|
hBmp = hBmpCleanup;
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
|
|
LocalFree(pBits);
|
|
}
|
|
}
|
|
|
|
// cleanup ....
|
|
DeleteDC(hdcMem);
|
|
}
|
|
|
|
LocalFree(pInfo);
|
|
}
|
|
} else {
|
|
// the original bitmap is fine
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
|
|
UINT iImage;
|
|
if (SUCCEEDED(hr)) {
|
|
// a small icon is stamped in bottom right-hand corner of thumbnail.
|
|
StampIconOnThumbnail(pidl, hBmp, m_fDrawBorder);
|
|
|
|
// check if we are going away, if so, then don't use Sendmessage because it will block the
|
|
// destructor of the scheduler...
|
|
if (m_fDestroying) {
|
|
hr = E_FAIL;
|
|
} else {
|
|
// copy thumbnail into the cache.
|
|
IMAGECACHEINFO rgInfo;
|
|
rgInfo.cbSize = sizeof(rgInfo);
|
|
rgInfo.dwMask = ICIFLAG_NAME | ICIFLAG_FLAGS | ICIFLAG_INDEX | ICIFLAG_LARGE | ICIFLAG_BITMAP;
|
|
rgInfo.pszName = pszPath;
|
|
rgInfo.dwFlags = dwMask;
|
|
rgInfo.iIndex = (int)iThumbnail;
|
|
rgInfo.hBitmapLarge = hBmp;
|
|
rgInfo.hMaskLarge = NULL;
|
|
|
|
if (pftDateStamp) {
|
|
rgInfo.dwMask |= ICIFLAG_DATESTAMP;
|
|
rgInfo.ftDateStamp = *pftDateStamp;
|
|
}
|
|
|
|
if (IS_BIDI_LOCALIZED_SYSTEM()) {
|
|
rgInfo.dwMask |= ICIFLAG_MIRROR;
|
|
}
|
|
|
|
hr = m_pImageCache->AddImage(&rgInfo, &iImage);
|
|
}
|
|
}
|
|
|
|
if (hBmpCleanup) {
|
|
DeleteObject(hBmpCleanup);
|
|
}
|
|
|
|
if (hr == S_OK) {
|
|
// check if we are going away, if so, then don't use Sendmessage because it will block the
|
|
// destructor of the scheduler...
|
|
if (m_fDestroying) {
|
|
return E_FAIL;
|
|
}
|
|
|
|
// post a message to the main thread so we don't block....
|
|
::PostMessageA(m_hWnd, WM_UPDATEITEMIMAGE, iItem, iImage);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CThumbnailView::ViewUpdateThumbnail(LPCITEMIDLIST pidl, LPCWSTR pszPath, HBITMAP hBitmap)
|
|
{
|
|
int iItem = FindInView(m_hWndListView, m_pFolder, pidl);
|
|
if (iItem == -1) {
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
DWORD dwMask = GetOverlayMask(pidl);
|
|
|
|
LV_ITEMW rgItem;
|
|
ZeroMemory(&rgItem, sizeof(rgItem));
|
|
rgItem.mask = LVIF_IMAGE | LVIF_NORECOMPUTE;
|
|
rgItem.iItem = iItem;
|
|
|
|
BOOL bRes = ListView_GetItemWrapW(m_hWndListView, &rgItem);
|
|
Assert(bRes);
|
|
|
|
/*[TODO: do something about persisting the image ... and color reduction ]*/
|
|
|
|
// a small icon is stamped in bottom right-hand corner of thumbnail.
|
|
StampIconOnThumbnail(pidl, hBitmap, m_fDrawBorder);
|
|
|
|
if (rgItem.iImage != I_IMAGECALLBACK) {
|
|
// if it is a shared thumbnail, then the cache will handle if it is to be
|
|
// deleted or not.
|
|
m_pImageCache->FreeImage((UINT)rgItem.iItem);
|
|
}
|
|
|
|
// there is no item in the cache for it, so we can tag it on the end
|
|
UINT iImage;
|
|
IMAGECACHEINFO rgInfo;
|
|
rgInfo.cbSize = sizeof(rgInfo);
|
|
rgInfo.dwMask = ICIFLAG_NAME | ICIFLAG_FLAGS | ICIFLAG_BITMAP | ICIFLAG_LARGE;
|
|
rgInfo.pszName = pszPath;
|
|
rgInfo.dwFlags = dwMask;
|
|
rgInfo.hBitmapLarge = hBitmap;
|
|
rgInfo.hMaskLarge = NULL;
|
|
|
|
if (IS_BIDI_LOCALIZED_SYSTEM()) {
|
|
rgInfo.dwMask |= ICIFLAG_MIRROR;
|
|
}
|
|
|
|
HRESULT hr = m_pImageCache->AddImage(&rgInfo, &iImage);
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
rgItem.iImage = (int)iImage;
|
|
|
|
ListView_SetItemWrapW(m_hWndListView, &rgItem);
|
|
|
|
RECT rcImage;
|
|
ListView_GetItemRect(m_hWndListView, rgItem.iItem, &rcImage, LVIR_BOUNDS);
|
|
InvalidateRect(m_hWndListView, &rcImage, FALSE);
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
|
|
#define BLOCKSIZE 100
|
|
|
|
void CThumbnailView::UpdateWithoutRefresh()
|
|
{
|
|
// which one are we looking at ?
|
|
LPENUMIDLIST pEnum = NULL;
|
|
|
|
// on error do a refresh
|
|
BOOL fRefresh = FALSE;
|
|
int iIndex = -1;
|
|
ULONG celtFetched = 0;
|
|
ULONG celtThisBlock = 0;
|
|
HRESULT hRes = NOERROR;
|
|
ULONG ulType = SHCONTF_FOLDERS | SHCONTF_NONFOLDERS;
|
|
|
|
LPITEMIDLIST* apCurBlock = NULL;
|
|
LPITEMIDLIST* apElems = new LPITEMIDLIST[BLOCKSIZE + 1];
|
|
if (apElems == NULL) {
|
|
fRefresh = TRUE;
|
|
goto UpdateCleanup;
|
|
}
|
|
|
|
apCurBlock = apElems;
|
|
apCurBlock[BLOCKSIZE] = NULL;
|
|
|
|
if (m_fShowAllObjects) {
|
|
ulType |= SHCONTF_INCLUDEHIDDEN;
|
|
}
|
|
|
|
hRes = m_pFolder->EnumObjects(this->m_hWnd, ulType,
|
|
&pEnum);
|
|
|
|
if (FAILED(hRes)) {
|
|
fRefresh = TRUE;
|
|
goto UpdateCleanup;
|
|
}
|
|
|
|
// fetch all the new elements ...
|
|
do {
|
|
// we cannot assume that Next() can return more than one element at
|
|
// a time, as most of the IShellFolder implementers of this only
|
|
// support one at a time... (so much for relying on the spec....)
|
|
celtFetched = 0;
|
|
hRes = pEnum->Next(BLOCKSIZE - celtThisBlock, apCurBlock + celtThisBlock, &celtFetched);
|
|
if (celtFetched == 0) {
|
|
break;
|
|
}
|
|
|
|
// add this lot to the current block count
|
|
celtThisBlock += celtFetched;
|
|
|
|
if (celtThisBlock == BLOCKSIZE) {
|
|
// allocate next block
|
|
apCurBlock[BLOCKSIZE] = (LPITEMIDLIST) new LPITEMIDLIST[BLOCKSIZE + 1];
|
|
if (apCurBlock[BLOCKSIZE] == NULL) {
|
|
fRefresh = TRUE;
|
|
goto UpdateCleanup;
|
|
} else {
|
|
celtThisBlock = 0;
|
|
apCurBlock = (LPITEMIDLIST*)apCurBlock[BLOCKSIZE];
|
|
apCurBlock[BLOCKSIZE] = NULL;
|
|
}
|
|
}
|
|
} while (celtFetched != 0);
|
|
|
|
// if we reached here, we got a whole load of pidls in an array ...
|
|
// now walk the view checking to see if what we have are valid or not ...
|
|
do {
|
|
iIndex = ListView_GetNextItem(m_hWndListView, iIndex, LVNI_ALL);
|
|
if (iIndex == -1) {
|
|
break;
|
|
}
|
|
|
|
LV_ITEMW rgItem;
|
|
rgItem.iItem = iIndex;
|
|
rgItem.mask = LVIF_PARAM | LVIF_IMAGE | LVIF_NORECOMPUTE;
|
|
rgItem.iSubItem = 0;
|
|
|
|
int iItem = ListView_GetItemWrapW(m_hWndListView, &rgItem);
|
|
// just incase. ...
|
|
if (iItem != -1) {
|
|
LPITEMIDLIST pidl = (LPITEMIDLIST)rgItem.lParam;
|
|
|
|
// must walk our data chain to find the pidl ...
|
|
BOOL fFound = FALSE;
|
|
LPITEMIDLIST* apSearch = apElems;
|
|
while (apSearch != NULL) {
|
|
int iMaxSearch = BLOCKSIZE;
|
|
|
|
// the last block may not be full
|
|
if (apSearch[BLOCKSIZE] == NULL) {
|
|
iMaxSearch = celtThisBlock;
|
|
}
|
|
|
|
int iSearch;
|
|
for (iSearch = 0; iSearch < iMaxSearch; iSearch++) {
|
|
// skip used pidls.
|
|
if (apSearch[iSearch] == NULL) {
|
|
continue;
|
|
}
|
|
|
|
// try the legal method...
|
|
if (this->m_pFolder->CompareIDs(0, pidl, apSearch[iSearch]) == 0) {
|
|
fFound = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (fFound) {
|
|
// now we need to check if the image is out of date...
|
|
if (rgItem.iImage != I_IMAGECALLBACK) {
|
|
IExtractImage* pExtract;
|
|
IExtractImage2* pExtract2;
|
|
|
|
UINT rgfFlags = 0;
|
|
FILETIME ftDateStamp;
|
|
|
|
hRes = m_pFolder->GetUIObjectOf(m_hWnd, 1, (LPCITEMIDLIST*)&pidl,
|
|
IID_IExtractImage,
|
|
&rgfFlags,
|
|
(void**)&pExtract);
|
|
if (SUCCEEDED(hRes)) {
|
|
hRes = pExtract->QueryInterface(IID_IExtractImage, (void**)&pExtract2);
|
|
pExtract->Release();
|
|
}
|
|
|
|
if (SUCCEEDED(hRes)) {
|
|
hRes = pExtract2->GetDateStamp(&ftDateStamp);
|
|
pExtract2->Release();
|
|
}
|
|
if (SUCCEEDED(hRes)) {
|
|
// now fetch the datestamp from the icon cache so we can see if it has changed...
|
|
IMAGECACHEINFO rgInfo;
|
|
rgInfo.cbSize = sizeof(rgInfo);
|
|
rgInfo.dwMask = ICIFLAG_DATESTAMP;
|
|
|
|
hRes = m_pImageCache->GetImageInfo(rgItem.iImage, &rgInfo);
|
|
if (SUCCEEDED(hRes)) {
|
|
if (rgInfo.ftDateStamp.dwLowDateTime != ftDateStamp.dwLowDateTime ||
|
|
rgInfo.ftDateStamp.dwHighDateTime != ftDateStamp.dwHighDateTime) {
|
|
// Delete The image so that it will have to be re-fetched....
|
|
m_pImageCache->DeleteImage(rgItem.iImage);
|
|
|
|
rgItem.iImage = I_IMAGECALLBACK;
|
|
rgItem.mask = LVIF_IMAGE;
|
|
ListView_SetItemWrapW(m_hWndListView, &rgItem);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// free the pidl as don't need it
|
|
SHFree((LPVOID)apSearch[iSearch]);
|
|
|
|
// mark it as NULL
|
|
apSearch[iSearch] = NULL;
|
|
|
|
break;
|
|
}
|
|
// next block of pidls...
|
|
apSearch = (LPITEMIDLIST*)apSearch[BLOCKSIZE];
|
|
}
|
|
|
|
if (!fFound) {
|
|
// remove thumbnail from the cache.
|
|
if (rgItem.iImage != I_IMAGECALLBACK) {
|
|
// now remove it from the thumbnail cache.
|
|
m_pImageCache->FreeImage((UINT)rgItem.iImage);
|
|
}
|
|
ListView_DeleteItem(m_hWndListView, iIndex);
|
|
|
|
// move back an item in the listview ...
|
|
iIndex--;
|
|
|
|
// free the pidl...
|
|
SHFree((LPVOID)pidl);
|
|
}
|
|
}
|
|
} while (TRUE);
|
|
|
|
// now search the blocks for pidls we don't have and add them ....
|
|
apCurBlock = apElems;
|
|
while (apCurBlock != NULL) {
|
|
int iMaxIndex = BLOCKSIZE;
|
|
|
|
// the last block may not be full
|
|
if (apCurBlock[BLOCKSIZE] == NULL) {
|
|
iMaxIndex = celtThisBlock;
|
|
}
|
|
|
|
for (int iSearch = 0; iSearch < iMaxIndex; iSearch++) {
|
|
// skip used pidls.
|
|
if (apCurBlock[iSearch] == NULL) {
|
|
continue;
|
|
}
|
|
|
|
AddItem(apCurBlock[iSearch]);
|
|
// the view now owns the pidl...
|
|
apCurBlock[iSearch] = NULL;
|
|
}
|
|
|
|
// next block of pidls...
|
|
LPITEMIDLIST* apNext = (LPITEMIDLIST*)apCurBlock[BLOCKSIZE];
|
|
delete[] apCurBlock;
|
|
apCurBlock = apNext;
|
|
}
|
|
|
|
apElems = NULL;
|
|
|
|
UpdateCleanup:
|
|
|
|
// walk the list freeing and releasing memory.....
|
|
if (apElems != NULL) {
|
|
do {
|
|
apCurBlock = apElems;
|
|
|
|
int iMaxIndex = BLOCKSIZE;
|
|
if (apCurBlock[BLOCKSIZE] == NULL) {
|
|
iMaxIndex = celtThisBlock;
|
|
}
|
|
|
|
for (int iElem = 0; iElem < iMaxIndex; iElem++) {
|
|
if (apCurBlock[iElem] != NULL) {
|
|
SHFree(apCurBlock[iElem]);
|
|
}
|
|
}
|
|
apElems = (LPITEMIDLIST*)apElems[BLOCKSIZE];
|
|
delete[] apCurBlock;
|
|
} while (apElems != NULL);
|
|
}
|
|
|
|
if (fRefresh == TRUE) {
|
|
// a last resort, refresh...
|
|
Refresh();
|
|
}
|
|
|
|
if (pEnum) pEnum->Release();
|
|
}
|
|
|
|
|
|
|
|
HRESULT CThumbnailView::ExtractItem(UINT* puIndex, int iItem, LPCITEMIDLIST pidl, BOOL fBackground, BOOL fForce)
|
|
{
|
|
// this will do an extract and put it in the queue if possible
|
|
|
|
if (iItem == -1 && !pidl) {
|
|
// failure....
|
|
return FALSE;
|
|
}
|
|
|
|
if (iItem == -1) {
|
|
iItem = FindItem(pidl);
|
|
if (iItem == -1) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
UINT uIndex = 0;
|
|
if (!puIndex) {
|
|
puIndex = &uIndex;
|
|
}
|
|
|
|
if (!pidl) {
|
|
LV_ITEMW rgItem;
|
|
ZeroMemory(&rgItem, sizeof(rgItem));
|
|
rgItem.mask = LVIF_PARAM | LVIF_NORECOMPUTE;
|
|
rgItem.iItem = iItem;
|
|
|
|
if (ListView_GetItemWrapW(m_hWndListView, &rgItem) == -1) {
|
|
return FALSE;
|
|
}
|
|
|
|
pidl = (LPCITEMIDLIST)rgItem.lParam;
|
|
}
|
|
|
|
DWORD dwMask = GetOverlayMask(pidl);
|
|
|
|
// now create a task
|
|
IRunnableTask* pTask = NULL;
|
|
IExtractImage2* pExtract = NULL;
|
|
WCHAR szPath[MAX_PATH];
|
|
HRESULT hr = NOERROR;
|
|
UINT rgfFlags = 0;
|
|
|
|
// try for a thumbnail handler ...
|
|
hr = m_pFolder->GetUIObjectOf(m_hWnd,
|
|
1,
|
|
&pidl,
|
|
IID_IExtractImage,
|
|
&rgfFlags,
|
|
(void**)&pExtract);
|
|
if (SUCCEEDED(hr)) {
|
|
Assert(pExtract != NULL);
|
|
DWORD dwFlags = IEIFLAG_ASYNC | IEIFLAG_ORIGSIZE;
|
|
DWORD dwPriority = PRIORITY_NORMAL;
|
|
SIZE rgThumbSize = {m_iXSizeThumbnail, m_iYSizeThumbnail};
|
|
FILETIME ftImageTimeStamp;
|
|
BOOL fNoDateStamp = TRUE;
|
|
IExtractImage2* pExtract2;
|
|
|
|
// od they support date stamps....
|
|
if (SUCCEEDED(pExtract->QueryInterface(IID_IExtractImage2, (void**)&pExtract2))) {
|
|
if (FAILED(pExtract2->GetDateStamp(&ftImageTimeStamp))) {
|
|
ZeroMemory(&ftImageTimeStamp, sizeof(ftImageTimeStamp));
|
|
} else {
|
|
// Houston, we have a date stamp..
|
|
fNoDateStamp = FALSE;
|
|
}
|
|
|
|
pExtract2->Release();
|
|
}
|
|
|
|
if (m_fOffline) {
|
|
dwFlags |= IEIFLAG_OFFLINE;
|
|
}
|
|
|
|
// always extract at 24 bit incase we have to cache it ...
|
|
hr = pExtract->GetLocation(szPath, ARRAYSIZE(szPath), &dwPriority, &rgThumbSize, 24, &dwFlags);
|
|
if (SUCCEEDED(hr) || hr == E_PENDING) {
|
|
BOOL fAsync = (hr == E_PENDING);
|
|
hr = E_FAIL;
|
|
|
|
if (!fForce) {
|
|
// check if the image is already in the image cache.
|
|
IMAGECACHEINFO rgInfo;
|
|
rgInfo.cbSize = sizeof(rgInfo);
|
|
rgInfo.dwMask = ICIFLAG_NAME | ICIFLAG_FLAGS;
|
|
rgInfo.pszName = szPath;
|
|
rgInfo.dwFlags = dwMask;
|
|
|
|
if (!fNoDateStamp) {
|
|
rgInfo.dwMask |= ICIFLAG_DATESTAMP;
|
|
rgInfo.ftDateStamp = ftImageTimeStamp;
|
|
}
|
|
|
|
hr = m_pImageCache->FindImage(&rgInfo, puIndex);
|
|
}
|
|
|
|
if (hr != S_OK) {
|
|
// get the full path for the extraction...
|
|
WCHAR szFullPath[MAX_PATH];
|
|
STRRET rgStr;
|
|
|
|
hr = m_pFolder->GetDisplayNameOf(pidl, SHGDN_FORPARSING, &rgStr);
|
|
if (SUCCEEDED(hr)) {
|
|
StrRetToBufW(&rgStr, pidl, szFullPath, ARRAYSIZE(szFullPath));
|
|
} else {
|
|
szFullPath[0] = 0;
|
|
}
|
|
|
|
hr = CTestCacheTask_Create(this, pExtract, szPath, szFullPath,
|
|
(fNoDateStamp ? NULL : &ftImageTimeStamp),
|
|
pidl, iItem, dwFlags,
|
|
dwPriority, fAsync, fBackground, fForce, &pTask);
|
|
if (SUCCEEDED(hr)) {
|
|
// does it not support Async, or were we told to run it forground ?
|
|
if (!fAsync || !fBackground) {
|
|
if (!fBackground) {
|
|
// make sure there is no extract task already underway as we
|
|
// are not adding this to the queue...
|
|
m_pScheduler->RemoveTasks(TOID_ExtractImageTask, (DWORD)iItem, TRUE);
|
|
}
|
|
|
|
hr = pTask->Run();
|
|
} else {
|
|
// add the task to the scheduler...
|
|
hr = m_pScheduler->AddTask(pTask,
|
|
TOID_CheckCacheTask,
|
|
(DWORD)iItem,
|
|
dwPriority);
|
|
|
|
// signify we want a default icon for now....
|
|
hr = S_FALSE;
|
|
}
|
|
pTask->Release();
|
|
}
|
|
}
|
|
}
|
|
pExtract->Release();
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
HRESULT CDiskCacheTask_Create(CThumbnailView* pView,
|
|
int iItem,
|
|
LPCITEMIDLIST pidl,
|
|
LPCWSTR pszCache,
|
|
LPCWSTR pszPath,
|
|
const FILETIME* pftDateStamp,
|
|
IRunnableTask** ppTask)
|
|
{
|
|
if (!ppTask) {
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
CDiskCacheTask* pTask = new CComObject<CDiskCacheTask>;
|
|
if (pTask == NULL) {
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
pTask->m_pidl = ILClone(pidl);
|
|
if (!(pTask->m_pidl)) {
|
|
delete pTask;
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
StrCpyNW(pTask->m_szPath, pszPath, ARRAYSIZE(pTask->m_szPath));
|
|
StrCpyNW(pTask->m_szCache, pszCache, ARRAYSIZE(pTask->m_szCache));
|
|
|
|
pTask->m_pView = pView;
|
|
pView->InternalAddRef();
|
|
|
|
pTask->m_iItem = iItem;
|
|
|
|
pTask->m_fNoDateStamp = (pftDateStamp == NULL);
|
|
if (pftDateStamp) {
|
|
pTask->m_ftDateStamp = *pftDateStamp;
|
|
}
|
|
|
|
pTask->AddRef();
|
|
|
|
*ppTask = (LPRUNNABLETASK)pTask;
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
STDMETHODIMP CDiskCacheTask::Run()
|
|
{
|
|
if (m_lState == IRTIR_TASK_RUNNING) {
|
|
return S_FALSE;
|
|
}
|
|
|
|
if (m_lState == IRTIR_TASK_PENDING) {
|
|
// it is about to die, so fail
|
|
return E_FAIL;
|
|
}
|
|
|
|
LONG lRes = InterlockedExchange(&m_lState, IRTIR_TASK_RUNNING);
|
|
if (lRes == IRTIR_TASK_PENDING) {
|
|
m_lState = IRTIR_TASK_FINISHED;
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
// otherwise, run the task ....
|
|
HBITMAP hBmp;
|
|
DWORD dwLock;
|
|
|
|
// at this point, we assume that it IS in the cache...
|
|
HRESULT hr = m_pView->m_pDiskCache->Open(STGM_READ, &dwLock);
|
|
if (SUCCEEDED(hr)) {
|
|
hr = m_pView->m_pDiskCache->GetEntry(m_szPath, STGM_READ, &hBmp);
|
|
|
|
// set the tick count so we know when we last accessed the disk cache
|
|
m_pView->m_dwCacheTickCount = GetTickCount();
|
|
|
|
// release the lock, we don't need it...
|
|
m_pView->m_pDiskCache->ReleaseLock(&dwLock);
|
|
}
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
hr = m_pView->UpdateImageForItem(hBmp, m_iItem, m_pidl, m_szCache, m_szPath,
|
|
(m_fNoDateStamp ? NULL : &m_ftDateStamp), FALSE);
|
|
DeleteObject(hBmp);
|
|
}
|
|
|
|
m_lState = IRTIR_TASK_FINISHED;
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CDiskCacheTask::Kill(BOOL fWait)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP CDiskCacheTask::Suspend(void)
|
|
{
|
|
// not supported....
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP CDiskCacheTask::Resume(void)
|
|
{
|
|
// not supported....
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CDiskCacheTask::IsRunning()
|
|
{
|
|
return (ULONG)m_lState;
|
|
}
|
|
|
|
CDiskCacheTask::CDiskCacheTask()
|
|
{
|
|
m_iItem = -1;
|
|
m_lState = IRTIR_TASK_NOT_RUNNING;
|
|
m_pidl = 0;
|
|
m_pView = NULL;
|
|
}
|
|
|
|
CDiskCacheTask::~CDiskCacheTask()
|
|
{
|
|
if (m_pidl) {
|
|
SHFree((LPVOID)m_pidl);
|
|
}
|
|
|
|
if (m_pView) {
|
|
m_pView->InternalRelease();
|
|
}
|
|
}
|
|
|
|
|
|
LRESULT CThumbnailView::OnInfoTipText(NMLVGETINFOTIPW* plvn)
|
|
{
|
|
LRESULT lRes = FALSE;
|
|
LPCITEMIDLIST pidl = (LPCITEMIDLIST)plvn->lParam;
|
|
|
|
plvn->pszText[0] = 0;
|
|
|
|
if (!pidl) {
|
|
LV_ITEMW rgItem;
|
|
ZeroMemory(&rgItem, sizeof(rgItem));
|
|
rgItem.iItem = plvn->iItem;
|
|
rgItem.iSubItem = plvn->iSubItem;
|
|
rgItem.mask = LVIF_PARAM;
|
|
|
|
ListView_GetItemWrapW(m_hWndListView, &rgItem);
|
|
pidl = (LPCITEMIDLIST)rgItem.lParam;
|
|
}
|
|
|
|
if (pidl) {
|
|
IQueryInfo* pqi;
|
|
|
|
if (SUCCEEDED(m_pFolder->GetUIObjectOf(NULL, 1, &pidl, IID_IQueryInfo, NULL, (void**)&pqi))) {
|
|
WCHAR* pwszTip = NULL;
|
|
pqi->GetInfoTip(0, &pwszTip);
|
|
if (pwszTip) {
|
|
StrCpyNW(plvn->pszText, pwszTip, plvn->cchTextMax);
|
|
SHFree(pwszTip);
|
|
}
|
|
pqi->Release();
|
|
}
|
|
}
|
|
|
|
return lRes;
|
|
}
|
|
|
|
|
|
|
|
HRESULT CThumbnailView::UpdateImageForItem(HBITMAP hImage,
|
|
int iItem,
|
|
LPCITEMIDLIST pidl,
|
|
LPCWSTR pszCache,
|
|
LPCWSTR pszFullPath,
|
|
const FILETIME* pftDateStamp,
|
|
BOOL fCache)
|
|
{
|
|
if (!pszCache) {
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
DWORD dwLock;
|
|
BOOL fLock = FALSE;
|
|
if (fCache) {
|
|
Assert(m_pDiskCache);
|
|
DWORD dwMode = 0;
|
|
|
|
HRESULT hr = m_pDiskCache->Open(STGM_WRITE, &dwLock);
|
|
if (hr == STG_E_FILENOTFOUND) {
|
|
hr = m_pDiskCache->Create(STGM_WRITE, &dwLock);
|
|
}
|
|
|
|
fLock = SUCCEEDED(hr);
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
hr = m_pDiskCache->AddEntry(pszFullPath, pftDateStamp, STGM_WRITE, hImage);
|
|
|
|
// set the tick count so that when the timer goes off, we can know when we
|
|
// last used it...
|
|
m_dwCacheTickCount = GetTickCount();
|
|
if (fLock) {
|
|
hr = m_pDiskCache->ReleaseLock(&dwLock);
|
|
}
|
|
}
|
|
}
|
|
|
|
DWORD dwMask = GetOverlayMask(pidl);
|
|
TaskUpdateItem(pidl,
|
|
iItem,
|
|
dwMask,
|
|
pszCache,
|
|
pftDateStamp,
|
|
0,
|
|
hImage);
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
DWORD GetCurrentColorFlags(UINT* puBytesPerPixel)
|
|
{
|
|
DWORD dwFlags = 0;
|
|
UINT uBytesPerPix = 1;
|
|
int res = (int)GetCurColorRes();
|
|
switch (res) {
|
|
case 16: dwFlags = ILC_COLOR16;
|
|
uBytesPerPix = 2;
|
|
break;
|
|
case 24:
|
|
case 32: dwFlags = ILC_COLOR24;
|
|
uBytesPerPix = 3;
|
|
break;
|
|
default: dwFlags = ILC_COLOR8;
|
|
uBytesPerPix = 1;
|
|
}
|
|
if (puBytesPerPixel) {
|
|
*puBytesPerPixel = uBytesPerPix;
|
|
}
|
|
return dwFlags;
|
|
}
|
|
|
|
|
|
UINT CalcCacheMaxSize(const SIZE* prgSize, UINT uBytesPerPix)
|
|
{
|
|
MEMORYSTATUS rgMemStat;
|
|
UINT uMaxCache = 0;
|
|
|
|
// calculate the maximum number of thumbnails in the cache.
|
|
rgMemStat.dwLength = sizeof(MEMORYSTATUS);
|
|
GlobalMemoryStatus(&rgMemStat);
|
|
|
|
// the minimum in the cache is the number of thumbnails visible on the screen at once.
|
|
HDC hdc = GetDC(NULL);
|
|
int iWidth = GetDeviceCaps(hdc, HORZRES);
|
|
int iHeight = GetDeviceCaps(hdc, VERTRES);
|
|
ReleaseDC(NULL, hdc);
|
|
|
|
// the minimum number of thumbnails in the cache, is set to the maximum amount
|
|
// of thumbnails that can be diplayed by a single view at once.
|
|
int iRow = iWidth / (prgSize->cx + DEFSIZE_BORDER);
|
|
int iCol = iHeight / (prgSize->cy + DEFSIZE_VERTBDR);
|
|
UINT iMinThumbs = iRow * iCol;
|
|
|
|
// set the thumbnail maximum by calculating the memory required for a single thumbnail.
|
|
// then, divide half the available memory by the memory needed per thumbnail.
|
|
int iMemReqThumb = prgSize->cx * prgSize->cy * uBytesPerPix;
|
|
|
|
// the larger of the minimum cache required and the number calculated
|
|
// from the available memory.
|
|
UINT uiThumbsPerMem = UINT((rgMemStat.dwAvailPhys / 3) / iMemReqThumb);
|
|
uMaxCache = __max(uiThumbsPerMem, iMinThumbs + 1);
|
|
|
|
#if defined(SMALLCACHE)
|
|
// restrict the size of the thumbnail cache to force it to have to grovel
|
|
// entries..
|
|
#pragma message("Warning: possible Image Cache limiting in effect")
|
|
uMaxCache = 20;
|
|
#endif
|
|
|
|
return uMaxCache;
|
|
}
|
|
|
|
void CThumbnailView::ThreadUpdateStatusBar(UINT idMsg, int idItem)
|
|
{
|
|
PostMessageA(m_hWnd, WM_STATUSBARUPDATE, (WPARAM)idMsg, (LPARAM)idItem);
|
|
}
|
|
|
|
LRESULT CThumbnailView::OnCustomDraw(NMCUSTOMDRAW* pNM)
|
|
{
|
|
NMLVCUSTOMDRAW* plvcd = (NMLVCUSTOMDRAW*)pNM;
|
|
LRESULT lres = 0;
|
|
static HPALETTE hpalOld = NULL;
|
|
|
|
switch (plvcd->nmcd.dwDrawStage) {
|
|
case CDDS_PREPAINT:
|
|
if (m_fShowCompColor)
|
|
lres |= CDRF_NOTIFYITEMDRAW;
|
|
if (m_hpal && plvcd->nmcd.hdc) {
|
|
hpalOld = SelectPalette(plvcd->nmcd.hdc, m_hpal, TRUE);
|
|
RealizePalette(plvcd->nmcd.hdc);
|
|
lres |= CDRF_NOTIFYPOSTPAINT;
|
|
}
|
|
break;
|
|
case CDDS_ITEMPREPAINT:
|
|
{
|
|
LPCITEMIDLIST pidl = (LPCITEMIDLIST)plvcd->nmcd.lItemlParam;
|
|
|
|
// We hit a case (only in classic mode) in which pidl is bogus data. This happenes because we had
|
|
// alread called Clear and were in the process of calling ListView_DeleteAllItems when we were asked
|
|
// to paint an item. Dismissing the label edit box while the view was being emptied caused the item
|
|
// to be repainted and we faulted doing a GetAttributesOf on the already freed pidl. We protect
|
|
// against this case by ensureing that m_hWndListView is non-null (since we null it out before calling
|
|
// Clear).
|
|
|
|
if (pidl && m_hWndListView) {
|
|
DWORD uFlags = SFGAO_COMPRESSED;
|
|
HRESULT hres = m_pFolder->GetAttributesOf(1, &pidl, &uFlags);
|
|
|
|
if (SUCCEEDED(hres) && (uFlags & SFGAO_COMPRESSED)) {
|
|
plvcd->clrText = GetAltColor();
|
|
}
|
|
}
|
|
|
|
return CDRF_DODEFAULT;
|
|
}
|
|
case CDDS_POSTPAINT:
|
|
if (m_hpal && hpalOld && plvcd->nmcd.hdc) {
|
|
SelectPalette(plvcd->nmcd.hdc, hpalOld, TRUE);
|
|
RealizePalette(plvcd->nmcd.hdc);
|
|
}
|
|
break;
|
|
}
|
|
return lres;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HRESULT CUpdateDirTask_Create(CThumbnailView* pView,
|
|
IRunnableTask** ppTask)
|
|
{
|
|
CUpdateDirTask* pTask = new CComObject<CUpdateDirTask>;
|
|
if (pTask == NULL) {
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
pTask->m_pView = pView;
|
|
Assert(pView);
|
|
|
|
if (!pTask->m_hEvent || !pTask->m_apElems) {
|
|
delete pTask;
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
pTask->AddRef();
|
|
|
|
*ppTask = (LPRUNNABLETASK)pTask;
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
STDMETHODIMP CUpdateDirTask::Run()
|
|
{
|
|
if (m_lState == IRTIR_TASK_RUNNING) {
|
|
return S_FALSE;
|
|
}
|
|
|
|
if (m_lState == IRTIR_TASK_PENDING) {
|
|
// it is about to die, so fail
|
|
return E_FAIL;
|
|
}
|
|
|
|
LONG lRes = InterlockedExchange(&m_lState, IRTIR_TASK_RUNNING);
|
|
if (lRes == IRTIR_TASK_PENDING) {
|
|
m_lState = IRTIR_TASK_FINISHED;
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
// otherwise, run the task ....
|
|
HRESULT hr = InternalResume();
|
|
if (hr != E_PENDING)
|
|
m_lState = IRTIR_TASK_FINISHED;
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
STDMETHODIMP CUpdateDirTask::Suspend()
|
|
{
|
|
if (m_lState != IRTIR_TASK_RUNNING) {
|
|
return E_FAIL;
|
|
}
|
|
|
|
// suspend ourselves
|
|
LONG lRes = InterlockedExchange(&m_lState, IRTIR_TASK_SUSPENDED);
|
|
if (lRes == IRTIR_TASK_FINISHED) {
|
|
m_lState = lRes;
|
|
return NOERROR;
|
|
}
|
|
|
|
// if it is running, then there is an Event Handle, if we have passed where
|
|
// we are using it, then we are close to finish, so it will ignore the suspend
|
|
// request
|
|
Assert(m_hEvent);
|
|
SetEvent(m_hEvent);
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
STDMETHODIMP CUpdateDirTask::Resume()
|
|
{
|
|
if (m_lState != IRTIR_TASK_SUSPENDED) {
|
|
return E_FAIL;
|
|
}
|
|
|
|
ResetEvent(m_hEvent);
|
|
m_lState = IRTIR_TASK_RUNNING;
|
|
|
|
HRESULT hr = InternalResume();
|
|
if (hr != E_PENDING) {
|
|
m_lState = IRTIR_TASK_FINISHED;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
STDMETHODIMP CUpdateDirTask::Kill(BOOL fWait)
|
|
{
|
|
if (m_lState == IRTIR_TASK_RUNNING) {
|
|
LONG lRes = InterlockedExchange(&m_lState, IRTIR_TASK_PENDING);
|
|
if (lRes == IRTIR_TASK_FINISHED) {
|
|
m_lState = lRes;
|
|
} else if (m_hEvent) {
|
|
// signal the event it is likely to be waiting on
|
|
SetEvent(m_hEvent);
|
|
}
|
|
|
|
return NOERROR;
|
|
} else if (m_lState == IRTIR_TASK_PENDING || m_lState == IRTIR_TASK_FINISHED) {
|
|
return S_FALSE;
|
|
}
|
|
|
|
return E_FAIL;
|
|
}
|
|
|
|
|
|
STDMETHODIMP_(ULONG) CUpdateDirTask::IsRunning()
|
|
{
|
|
return m_lState;
|
|
}
|
|
|
|
|
|
CUpdateDirTask::CUpdateDirTask()
|
|
{
|
|
m_hEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
|
|
|
|
m_apElems = new LPITEMIDLIST[BLOCKSIZE + 1];
|
|
Assert(!m_apElems || m_apElems[BLOCKSIZE] == NULL);
|
|
|
|
m_iIndex = -1;
|
|
}
|
|
|
|
|
|
CUpdateDirTask::~CUpdateDirTask()
|
|
{
|
|
if (m_hEvent) {
|
|
CloseHandle(m_hEvent);
|
|
}
|
|
if (m_apElems) {
|
|
// empty the pidl array ...
|
|
do {
|
|
LPITEMIDLIST* apCurBlock = m_apElems;
|
|
|
|
int iMaxIndex = BLOCKSIZE;
|
|
if (apCurBlock[BLOCKSIZE] == NULL) {
|
|
iMaxIndex = m_celtThisBlock;
|
|
}
|
|
|
|
for (int iElem = 0; iElem < iMaxIndex; iElem++) {
|
|
if (apCurBlock[iElem] != NULL) {
|
|
SHFree(apCurBlock[iElem]);
|
|
}
|
|
}
|
|
m_apElems = (LPITEMIDLIST*)m_apElems[BLOCKSIZE];
|
|
delete[] apCurBlock;
|
|
} while (m_apElems != NULL);
|
|
}
|
|
if (m_pEnum) {
|
|
m_pEnum->Release();
|
|
}
|
|
m_pView->m_fUpdateDir = FALSE;
|
|
}
|
|
|
|
|
|
HRESULT CUpdateDirTask::InternalResume()
|
|
{
|
|
m_pView->m_fUpdateDir = TRUE;
|
|
|
|
// we have yet to do the enum.....
|
|
if (m_pEnum == NULL) {
|
|
HRESULT hRes;
|
|
ULONG ulType = SHCONTF_FOLDERS | SHCONTF_NONFOLDERS;
|
|
|
|
// the blocks are empty right now...
|
|
Assert(m_apElems[0] == NULL);
|
|
|
|
m_apCurBlock = m_apElems;
|
|
if (m_pView->m_fShowAllObjects) {
|
|
ulType |= SHCONTF_INCLUDEHIDDEN;
|
|
}
|
|
|
|
hRes = m_pView->m_pFolder->EnumObjects(NULL, ulType, &m_pEnum);
|
|
if (hRes != S_OK) {
|
|
PostMessageA(m_pView->m_hWnd, WM_VIEWREFRESH, 0, 0);
|
|
return E_FAIL;
|
|
}
|
|
|
|
// we are starting again, empty the update lists...
|
|
m_pView->ThreadEmptyUpdateLists();
|
|
}
|
|
|
|
if (m_pEnum && !m_fEnumDone) {
|
|
// fetch all the new elements ...
|
|
ULONG celtFetched;
|
|
do {
|
|
// we cannot assume that Next() can return more than one element at
|
|
// a time, as most of the IShellFolder implementers of this only
|
|
// support one at a time... (so much for relying on the spec....)
|
|
celtFetched = 0;
|
|
m_pEnum->Next(BLOCKSIZE - m_celtThisBlock, m_apCurBlock + m_celtThisBlock, &celtFetched);
|
|
if (celtFetched == 0) {
|
|
break;
|
|
}
|
|
|
|
// add this lot to the current block count
|
|
m_celtThisBlock += celtFetched;
|
|
|
|
if (m_celtThisBlock == BLOCKSIZE) {
|
|
// allocate next block
|
|
m_apCurBlock[BLOCKSIZE] = (LPITEMIDLIST) new LPITEMIDLIST[BLOCKSIZE + 1];
|
|
if (m_apCurBlock[BLOCKSIZE] == NULL) {
|
|
// out of memory, resort to the F5 key :-)
|
|
PostMessageA(m_pView->m_hWnd, WM_VIEWREFRESH, 0, 0);
|
|
return E_FAIL;
|
|
} else {
|
|
m_celtThisBlock = 0;
|
|
m_apCurBlock = (LPITEMIDLIST*)m_apCurBlock[BLOCKSIZE];
|
|
m_apCurBlock[BLOCKSIZE] = NULL;
|
|
}
|
|
}
|
|
if (WaitForSingleObject(m_hEvent, 0) == WAIT_OBJECT_0) {
|
|
// why were we signalled ...
|
|
if (m_lState == IRTIR_TASK_SUSPENDED) {
|
|
return E_PENDING;
|
|
} else {
|
|
return E_FAIL;
|
|
}
|
|
}
|
|
} while (celtFetched != 0);
|
|
|
|
m_fEnumDone = TRUE;
|
|
}
|
|
if (m_fEnumDone) {
|
|
// if we reached here, we got a whole load of pidls in an array ...
|
|
// now walk the view checking to see if what we have are valid or not ...
|
|
do {
|
|
m_iIndex = ListView_GetNextItem(m_pView->m_hWndListView, m_iIndex, LVNI_ALL);
|
|
if (m_iIndex == -1) {
|
|
break;
|
|
}
|
|
|
|
LV_ITEMW rgItem;
|
|
rgItem.iItem = m_iIndex;
|
|
rgItem.mask = LVIF_PARAM | LVIF_IMAGE | LVIF_NORECOMPUTE;
|
|
rgItem.iSubItem = 0;
|
|
|
|
int iItem = ListView_GetItemWrapW(m_pView->m_hWndListView, &rgItem);
|
|
// just incase. ...
|
|
if (iItem != -1) {
|
|
LPITEMIDLIST pidl = (LPITEMIDLIST)rgItem.lParam;
|
|
|
|
// must walk our data chain to find the pidl ...
|
|
BOOL fFound = FALSE;
|
|
LPITEMIDLIST* apSearch = m_apElems;
|
|
while (apSearch != NULL) {
|
|
int iMaxSearch = BLOCKSIZE;
|
|
|
|
// the last block may not be full
|
|
if (apSearch[BLOCKSIZE] == NULL) {
|
|
iMaxSearch = m_celtThisBlock;
|
|
}
|
|
|
|
int iSearch;
|
|
for (iSearch = 0; iSearch < iMaxSearch; iSearch++) {
|
|
// skip used pidls.
|
|
if (apSearch[iSearch] == NULL) {
|
|
continue;
|
|
}
|
|
|
|
// try the legal method...
|
|
if (m_pView->m_pFolder->CompareIDs(0, pidl, apSearch[iSearch]) == 0) {
|
|
fFound = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (fFound) {
|
|
rgItem.mask = 0;
|
|
|
|
// now we need to check if the image is out of date...
|
|
if (rgItem.iImage != I_IMAGECALLBACK) {
|
|
IExtractImage* pExtract;
|
|
IExtractImage2* pExtract2;
|
|
|
|
UINT rgfFlags = 0;
|
|
FILETIME ftDateStamp;
|
|
|
|
HRESULT hRes = m_pView->m_pFolder->GetUIObjectOf(m_pView->m_hWnd, 1, (LPCITEMIDLIST*)&pidl,
|
|
IID_IExtractImage,
|
|
&rgfFlags,
|
|
(void**)&pExtract);
|
|
if (SUCCEEDED(hRes)) {
|
|
hRes = pExtract->QueryInterface(IID_IExtractImage2, (void**)&pExtract2);
|
|
pExtract->Release();
|
|
}
|
|
|
|
if (SUCCEEDED(hRes)) {
|
|
hRes = pExtract2->GetDateStamp(&ftDateStamp);
|
|
pExtract2->Release();
|
|
}
|
|
if (SUCCEEDED(hRes)) {
|
|
// now fetch the datestamp from the icon cache so we can see if it has changed...
|
|
IMAGECACHEINFO rgInfo;
|
|
rgInfo.cbSize = sizeof(rgInfo);
|
|
rgInfo.dwMask = ICIFLAG_DATESTAMP;
|
|
|
|
hRes = m_pView->m_pImageCache->GetImageInfo(rgItem.iImage, &rgInfo);
|
|
if (SUCCEEDED(hRes)) {
|
|
if (rgInfo.ftDateStamp.dwLowDateTime != ftDateStamp.dwLowDateTime ||
|
|
rgInfo.ftDateStamp.dwHighDateTime != ftDateStamp.dwHighDateTime) {
|
|
m_pView->m_pImageCache->FreeImage(rgItem.iImage);
|
|
|
|
rgItem.mask |= LVIF_IMAGE;
|
|
rgItem.iImage = I_IMAGECALLBACK;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int iLen1 = GetPidlLength(apSearch[iSearch]);
|
|
int iLen2 = GetPidlLength(pidl);
|
|
|
|
// has the pidl changed ?
|
|
if (iLen1 != iLen2 || memcmp(apSearch[iSearch], pidl, iLen1) != 0) {
|
|
// swap the pidls....
|
|
rgItem.mask |= LVIF_PARAM;
|
|
rgItem.lParam = (LPARAM)apSearch[iSearch];
|
|
|
|
// put the old pidl in the array so it gets freed
|
|
apSearch[iSearch] = pidl;
|
|
}
|
|
|
|
if (rgItem.mask != 0) {
|
|
ListView_SetItemWrapW(m_pView->m_hWndListView, &rgItem);
|
|
}
|
|
|
|
// free the pidl as don't need it
|
|
SHFree((LPVOID)apSearch[iSearch]);
|
|
|
|
// mark it as NULL
|
|
apSearch[iSearch] = NULL;
|
|
|
|
break;
|
|
}
|
|
// next block of pidls...
|
|
apSearch = (LPITEMIDLIST*)apSearch[BLOCKSIZE];
|
|
}
|
|
|
|
if (!fFound) {
|
|
m_pView->ThreadDeleteItem(ILClone((LPITEMIDLIST)rgItem.lParam));
|
|
}
|
|
}
|
|
|
|
// check to see if we are told to give up....
|
|
if (WaitForSingleObject(m_hEvent, 0) == WAIT_OBJECT_0) {
|
|
// why were we signalled ...
|
|
if (m_lState == IRTIR_TASK_SUSPENDED) {
|
|
// give the view a chance to update
|
|
m_pView->m_fUpdateDir = FALSE;
|
|
PostMessageA(m_pView->m_hWnd, WM_PROCESSITEMS, 0, 0);
|
|
|
|
return E_PENDING;
|
|
} else {
|
|
return E_FAIL;
|
|
}
|
|
}
|
|
|
|
} while (TRUE);
|
|
|
|
m_fViewPassDone = TRUE;
|
|
}
|
|
|
|
if (m_fViewPassDone) {
|
|
LPITEMIDLIST* apCurBlock = m_apElems;
|
|
// now search the blocks for pidls we don't have and add them ....
|
|
while (apCurBlock != NULL) {
|
|
int iMaxIndex = BLOCKSIZE;
|
|
|
|
// the last block may not be full
|
|
if (apCurBlock[BLOCKSIZE] == NULL) {
|
|
iMaxIndex = m_celtThisBlock;
|
|
}
|
|
|
|
for (int iSearch = 0; iSearch < iMaxIndex; iSearch++) {
|
|
// skip used pidls.
|
|
if (apCurBlock[iSearch] == NULL) {
|
|
continue;
|
|
}
|
|
|
|
m_pView->ThreadAddItem(apCurBlock[iSearch]);
|
|
|
|
// the view now owns the pidl...
|
|
apCurBlock[iSearch] = NULL;
|
|
}
|
|
|
|
// next block of pidls...
|
|
LPITEMIDLIST* apNext = (LPITEMIDLIST*)apCurBlock[BLOCKSIZE];
|
|
delete[] apCurBlock;
|
|
apCurBlock = apNext;
|
|
}
|
|
|
|
m_apElems = NULL;
|
|
|
|
m_pView->m_fUpdateDir = FALSE;
|
|
|
|
PostMessageA(m_pView->m_hWnd, WM_PROCESSITEMS, 0, 0);
|
|
}
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
HRESULT CThumbnailView::ThreadAddItem(LPCITEMIDLIST pidl)
|
|
{
|
|
if (!pidl)
|
|
return E_INVALIDARG;
|
|
|
|
EnterCriticalSection(&m_csAddLock);
|
|
DPA_AppendPtr(m_hAddList, (LPVOID)pidl);
|
|
LeaveCriticalSection(&m_csAddLock);
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
HRESULT CThumbnailView::ThreadDeleteItem(LPCITEMIDLIST pidl)
|
|
{
|
|
if (!pidl)
|
|
return E_INVALIDARG;
|
|
|
|
EnterCriticalSection(&m_csAddLock);
|
|
DPA_AppendPtr(m_hDeleteList, (LPVOID)pidl);
|
|
LeaveCriticalSection(&m_csAddLock);
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
HRESULT CThumbnailView::OnWmProcessItems()
|
|
{
|
|
EnterCriticalSection(&m_csAddLock);
|
|
|
|
// do the add items first ....
|
|
for (int iLoop = DPA_GetPtrCount(m_hAddList) - 1; iLoop >= 0; iLoop--) {
|
|
LPITEMIDLIST pidl = (LPITEMIDLIST)DPA_GetPtr(m_hAddList, iLoop);
|
|
if (this->AddItem(pidl) < 0) {
|
|
SHFree(pidl); // failed to add it to the view, destroy it ...
|
|
} else if (m_pidlRename) {
|
|
if (m_pFolder->CompareIDs(0, pidl, m_pidlRename) == 0) {
|
|
this->SelectAndPositionItem(pidl, SVSI_EDIT, NULL);
|
|
SHFree(m_pidlRename);
|
|
m_pidlRename = NULL;
|
|
}
|
|
}
|
|
DPA_DeletePtr(m_hAddList, iLoop);
|
|
}
|
|
|
|
if (!m_fUpdateDir) {
|
|
// now do the delete items...
|
|
for (iLoop = DPA_GetPtrCount(m_hDeleteList) - 1; iLoop >= 0; iLoop--) {
|
|
LPITEMIDLIST pidl = (LPITEMIDLIST)DPA_GetPtr(m_hDeleteList, iLoop);
|
|
this->RemoveObject(pidl, NULL);
|
|
|
|
// we failed to add it to the view, destroy it ...
|
|
SHFree(pidl);
|
|
DPA_DeletePtr(m_hDeleteList, iLoop);
|
|
}
|
|
} else {
|
|
// we are currently doing an update dir, skip deleting things for a moment otherwise we'll screw up teh list order.
|
|
PostMessageA(m_hWnd, WM_PROCESSITEMS, 0, 0);
|
|
}
|
|
|
|
// if we still think we need a rename, cleanit up...
|
|
if (m_pidlRename) {
|
|
SHFree(m_pidlRename);
|
|
m_pidlRename = NULL;
|
|
}
|
|
LeaveCriticalSection(&m_csAddLock);
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
HRESULT CThumbnailView::ThreadEmptyUpdateLists()
|
|
{
|
|
EnterCriticalSection(&m_csAddLock);
|
|
|
|
for (int iLoop = DPA_GetPtrCount(m_hAddList) - 1; iLoop >= 0; iLoop--) {
|
|
LPCITEMIDLIST pidl = (LPCITEMIDLIST)DPA_GetPtr(m_hAddList, iLoop);
|
|
SHFree((LPITEMIDLIST)pidl);
|
|
DPA_DeletePtr(m_hAddList, iLoop);
|
|
}
|
|
|
|
// now do the delete items...
|
|
for (iLoop = DPA_GetPtrCount(m_hDeleteList) - 1; iLoop >= 0; iLoop--) {
|
|
LPITEMIDLIST pidl = (LPITEMIDLIST)DPA_GetPtr(m_hAddList, iLoop);
|
|
SHFree((LPITEMIDLIST)pidl);
|
|
DPA_DeletePtr(m_hAddList, iLoop);
|
|
}
|
|
|
|
LeaveCriticalSection(&m_csAddLock);
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
HRESULT CTestCacheTask_Create(CThumbnailView* pView,
|
|
IExtractImage* pExtract,
|
|
LPCWSTR pszCache,
|
|
LPCWSTR pszPath,
|
|
const FILETIME* pftDateStamp,
|
|
LPCITEMIDLIST pidl,
|
|
int iItem,
|
|
DWORD dwFlags,
|
|
DWORD dwPriority,
|
|
BOOL fAsync,
|
|
BOOL fBackground,
|
|
BOOL fForce,
|
|
IRunnableTask** ppTask)
|
|
{
|
|
*ppTask = NULL;
|
|
|
|
CTestCacheTask* pNew = new CTestCacheTask;
|
|
if (!pNew)
|
|
return E_OUTOFMEMORY;
|
|
|
|
pNew->m_pidl = ILClone(pidl);
|
|
|
|
if (pNew->m_pidl == NULL) {
|
|
delete pNew;
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
StrCpyNW(pNew->m_szFullPath, pszPath, ARRAYSIZE(pNew->m_szFullPath));
|
|
StrCpyNW(pNew->m_szCache, pszCache, ARRAYSIZE(pNew->m_szCache));
|
|
|
|
if (pftDateStamp) {
|
|
pNew->m_fDateStamp = TRUE;
|
|
pNew->m_ftDateStamp = *pftDateStamp;
|
|
}
|
|
|
|
pNew->m_iItem = iItem;
|
|
pNew->m_dwFlags = dwFlags;
|
|
pNew->m_dwPriority = dwPriority;
|
|
pNew->m_fAsync = fAsync;
|
|
pNew->m_fBackground = fBackground;
|
|
pNew->m_fForce = fForce;
|
|
|
|
pNew->m_pExtract = pExtract;
|
|
pExtract->AddRef();
|
|
|
|
pNew->m_pView = pView;
|
|
pView->InternalAddRef();
|
|
|
|
|
|
*ppTask = (IRunnableTask*)pNew;
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
STDMETHODIMP CTestCacheTask::RunInitRT()
|
|
{
|
|
DWORD dwLock = 0;
|
|
BOOL fLock = FALSE;
|
|
HRESULT hr = E_FAIL;
|
|
if (!m_fForce) {
|
|
if (m_pView->m_pDiskCache) {
|
|
// make sure the disk cache is open for reading.
|
|
hr = m_pView->m_pDiskCache->Open(STGM_READ, &dwLock);
|
|
fLock = SUCCEEDED(hr);
|
|
} else {
|
|
// no disk cache ...
|
|
hr = E_FAIL;
|
|
}
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
if (!m_pView->m_fTimerActive) {
|
|
// start the timer, once every two seconds....
|
|
SetTimer(m_pView->m_hWnd, TIMER_DISKCACHE, 2000, NULL);
|
|
m_pView->m_dwCacheTickCount = GetTickCount();
|
|
m_pView->m_fTimerActive = TRUE;
|
|
}
|
|
|
|
// is it in the cache....
|
|
FILETIME ftCacheTimeStamp;
|
|
hr = m_pView->m_pDiskCache->IsEntryInStore(m_szFullPath, &ftCacheTimeStamp);
|
|
|
|
// if it is in the cache, and it is an uptodate image, then fetch from disk....
|
|
// if the timestamps are wrong, then the extract code further down will then try
|
|
// and write its image back to the cache to update it anyway.....
|
|
if (hr == S_OK &&
|
|
((ftCacheTimeStamp.dwLowDateTime == m_ftDateStamp.dwLowDateTime &&
|
|
ftCacheTimeStamp.dwHighDateTime == m_ftDateStamp.dwHighDateTime) || !m_fDateStamp)) {
|
|
// try it in the background...
|
|
IRunnableTask* pTask = NULL;
|
|
hr = CDiskCacheTask_Create(m_pView, m_iItem, m_pidl, m_szCache, m_szFullPath,
|
|
(!m_fDateStamp ? NULL : &m_ftDateStamp), &pTask);
|
|
if (SUCCEEDED(hr) && !m_pView->m_fDestroying) {
|
|
Assert(m_pView->m_pScheduler);
|
|
|
|
// add the task to the scheduler...
|
|
hr = m_pView->m_pScheduler->AddTask(pTask, TOID_DiskCacheTask, (DWORD)m_iItem, PRIORITY_NORMAL);
|
|
if (SUCCEEDED(hr))
|
|
hr = S_FALSE;
|
|
|
|
pTask->Release();
|
|
} else {
|
|
hr = E_FAIL;
|
|
}
|
|
} else {
|
|
hr = E_FAIL;
|
|
}
|
|
}
|
|
if (fLock) {
|
|
// free the disk cache lock...
|
|
m_pView->m_pDiskCache->ReleaseLock(&dwLock);
|
|
}
|
|
}
|
|
if (FAILED(hr)) {
|
|
// Extract It....
|
|
|
|
IRunnableTask* pTask = NULL;
|
|
hr = CExtractImageTask_Create(m_pView,
|
|
m_pExtract,
|
|
m_szCache,
|
|
m_szFullPath,
|
|
m_pidl,
|
|
(!m_fDateStamp ? NULL : &m_ftDateStamp),
|
|
m_iItem,
|
|
m_dwFlags,
|
|
&pTask);
|
|
if (SUCCEEDED(hr)) {
|
|
// does it not support Async, or were we told to run it forground ?
|
|
if (!m_fAsync || !m_fBackground) {
|
|
if (!m_fBackground) {
|
|
// make sure there is no extract task already underway as we
|
|
// are not adding this to the queue...
|
|
m_pView->m_pScheduler->RemoveTasks(TOID_ExtractImageTask, (DWORD)m_iItem, TRUE);
|
|
}
|
|
|
|
hr = pTask->Run();
|
|
} else {
|
|
// add the task to the scheduler...
|
|
hr = m_pView->m_pScheduler->AddTask(pTask,
|
|
TOID_ExtractImageTask,
|
|
(DWORD)m_iItem,
|
|
m_dwPriority);
|
|
|
|
// signify we want a default icon for now....
|
|
hr = S_FALSE;
|
|
}
|
|
pTask->Release();
|
|
} else {
|
|
hr = E_FAIL;
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
CTestCacheTask::CTestCacheTask()
|
|
: CRunnableTask(RTF_DEFAULT)
|
|
{
|
|
}
|
|
|
|
CTestCacheTask::~CTestCacheTask()
|
|
{
|
|
if (m_pidl) {
|
|
SHFree((LPVOID)m_pidl);
|
|
}
|
|
|
|
if (m_pExtract) {
|
|
m_pExtract->Release();
|
|
}
|
|
|
|
if (m_pView) {
|
|
m_pView->InternalRelease();
|
|
}
|
|
}
|
|
|
|
HRESULT CThumbnailView::SetPointData(IDataObject* ptdObj, LPCITEMIDLIST* ppPidl, int cidl)
|
|
{
|
|
static UINT s_cfOffsets = 0;
|
|
|
|
ITEMSPACING rgSpacing;
|
|
|
|
// scaling is basically converted to large icon spacing...
|
|
HRESULT hres = GetItemSpacing(&rgSpacing);
|
|
Assert(SUCCEEDED(hres));
|
|
|
|
// the offsets format
|
|
if (!s_cfOffsets) {
|
|
s_cfOffsets = RegisterClipboardFormat(CFSTR_SHELLIDLISTOFFSET);
|
|
}
|
|
|
|
POINT* ppt = (POINT*)GlobalAlloc(GPTR, SIZEOF(POINT) * (1 + cidl));
|
|
if (ppt) {
|
|
int i;
|
|
|
|
// Grab the anchor point
|
|
Assert(m_fDragStarted);
|
|
ppt[0] = m_ptDragStart;
|
|
|
|
for (i = 1; i <= cidl; i++) {
|
|
int iItem = FindInView(m_hWndListView, m_pFolder, ppPidl[i - 1]);
|
|
|
|
Assert(iItem != -1);
|
|
|
|
ListView_GetItemPosition(m_hWndListView, iItem, &ppt[i]);
|
|
ppt[i].x -= m_ptDragStart.x;
|
|
ppt[i].y -= m_ptDragStart.y;
|
|
ppt[i].x = (ppt[i].x * rgSpacing.cxLarge) / rgSpacing.cxSmall;
|
|
ppt[i].y = (ppt[i].y * rgSpacing.cyLarge) / rgSpacing.cySmall;
|
|
}
|
|
|
|
FORMATETC fmte = {(CLIPFORMAT)s_cfOffsets, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
|
STGMEDIUM medium;
|
|
|
|
medium.tymed = TYMED_HGLOBAL;
|
|
medium.hGlobal = ppt;
|
|
medium.pUnkForRelease = NULL;
|
|
|
|
// give the data object ownership of ths
|
|
hres = ptdObj->SetData(&fmte, &medium, TRUE);
|
|
|
|
if (FAILED(hres))
|
|
GlobalFree((HGLOBAL)ppt);
|
|
}
|
|
return hres;
|
|
}
|
|
|
|
HRESULT CThumbnailView::AutoAutoArrange(DWORD dwReserved)
|
|
{
|
|
if (!m_fItemsMoved) {
|
|
ListView_Arrange(m_hWndListView, LVA_DEFAULT);
|
|
}
|
|
return NOERROR;
|
|
}
|
|
|
|
HRESULT CThumbnailView::SetStatusText(LPCWSTR pwszStatusText)
|
|
{
|
|
UpdateStatusBar();
|
|
return NOERROR;
|
|
}
|
|
|
|
// CLR_NONE is a special value that never matches a valid RGB
|
|
COLORREF g_crAltColor = CLR_NONE; // uninitialized magic value
|
|
|
|
DWORD GetAltColor()
|
|
{
|
|
// Fetch the alternate color (for compression) if supplied.
|
|
if (g_crAltColor == CLR_NONE) // initialized yet?
|
|
{
|
|
DWORD cbData = sizeof(COLORREF);
|
|
DWORD dwType;
|
|
HKEY hkey;
|
|
LONG lRes;
|
|
|
|
lRes = RegOpenKey(HKEY_CURRENT_USER, REGSTR_PATH_EXPLORER, &hkey);
|
|
if (lRes == ERROR_SUCCESS && hkey) {
|
|
if (SHQueryValueEx(hkey, TEXT("AltColor"), NULL, &dwType, (LPBYTE)&g_crAltColor, &cbData) != ERROR_SUCCESS)
|
|
g_crAltColor = RGB(0, 0, 255); // default value
|
|
RegCloseKey(hkey);
|
|
}
|
|
}
|
|
return g_crAltColor;
|
|
}
|