2020-09-30 17:12:32 +02:00

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;
}