#include "shellprv.h" #include "ids.h" #include "defview.h" #include "defviewp.h" #include "dvtasks.h" #include "guids.h" #include "prop.h" #include "CommonControls.h" #include "thumbutil.h" // Thumbnail support HRESULT CDefView::_SafeAddImage(BOOL fQuick, IMAGECACHEINFO* prgInfo, UINT* piImageIndex, int iListID) { HRESULT hr = S_FALSE; UINT uCacheSize = 0; _pImageCache->GetCacheSize(&uCacheSize); ASSERT(_iMaxCacheSize>0); BOOL bSpaceOpen = (uCacheSize < (UINT)_iMaxCacheSize); if (!bSpaceOpen) { BOOL bMakeSpace = TRUE; int iListIndex = -1; // Check to see if we are visible and need to make space if (-1 != iListID) { iListIndex = _MapIDToIndex(iListID); if (-1 == iListIndex) // Someone removed our item { hr = E_INVALIDARG; bMakeSpace = FALSE; } else if (!ListView_IsItemVisible(_hwndListview, iListIndex)) { hr = S_FALSE; bMakeSpace = FALSE; } } if (bMakeSpace) { // item is visible... try and make a space UINT uCacheIndex = 0; do { UINT uImageIndex; int iUsage; if (FAILED(_pImageCache->GetImageIndexFromCacheIndex(uCacheIndex, &uImageIndex)) || FAILED(_pImageCache->GetUsage(uImageIndex, (UINT*) &iUsage))) { break; } if (iUsage != ICD_USAGE_SYSTEM) // Magic number for System Image { TraceMsg(TF_DEFVIEW, "CDefView::_SafeAddImage -- FreeImage (CI::%d II::%d)", uCacheIndex, uImageIndex); _pImageCache->FreeImage(uImageIndex); _UpdateImage(uImageIndex); bSpaceOpen = TRUE; ASSERT((LONG)(uCacheSize - uCacheIndex) > (LONG)_ApproxItemsPerView()); } uCacheIndex++; } while (!bSpaceOpen); // If we repeatedly fail to add images to the list and are still decoding more images this means // we will have to re-walk the list view every time we finish decoding another image, only to then // throw away the result because we have no where to save it. This could lead to sluggish response // from the UI. In short, if the following Trace is common then we have a problem that needs to be // fixed (which might required considerable rearchitecting). if (!bSpaceOpen) { TraceMsg(TF_WARNING, "CDefView::_SafeAddImage failed to make room in cache!!"); hr = E_FAIL; } } } *piImageIndex = I_IMAGECALLBACK; if (bSpaceOpen) // There is space in the cache for this image { hr = _pImageCache->AddImage(prgInfo, piImageIndex); TraceMsg(TF_DEFVIEW, "CDefView::_SafeAddImage -- AddImage (HR:0x%08x name:%s,index:%u)", hr, prgInfo->pszName, *piImageIndex); } return hr; } COLORREF CDefView::_GetBackColor() { // SendMessage traffic is greatly reduced if we don't ask for the bkcolor // every time we need it... if (_rgbBackColor == CLR_INVALID) { _rgbBackColor = ListView_GetBkColor(_hwndListview); if (_rgbBackColor == CLR_NONE) _rgbBackColor = GetSysColor(COLOR_WINDOW); } return _rgbBackColor; } HRESULT CDefView::TaskUpdateItem(LPCITEMIDLIST pidl, int iItem, DWORD dwMask, LPCWSTR pszPath, FILETIME ftDateStamp, int iThumbnail, HBITMAP hBmp, DWORD dwItemID) { // 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 (::GetObject((HGDIOBJ)hBmp, sizeof(rgBitmap), &rgBitmap)) { // if the image is the wrong size, or the wrong colour depth, then do the funky stuff on it.. SIZE sizeThumbnail; _GetThumbnailSize(&sizeThumbnail); if (rgBitmap.bmWidth != sizeThumbnail.cx || rgBitmap.bmHeight != sizeThumbnail.cy || rgBitmap.bmBitsPixel > _dwRecClrDepth) { // alloc the colour table just incase.... BITMAPINFO *pInfo = (BITMAPINFO *)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)) { RECT rgRect = {0, 0, rgBitmap.bmWidth, rgBitmap.bmHeight}; CalculateAspectRatio(&sizeThumbnail, &rgRect); HPALETTE hpal = NULL; HRESULT hrPalette = _dwRecClrDepth <= 8 ? _GetBrowserPalette(&hpal) : S_OK; if (SUCCEEDED(hrPalette)) { if (FactorAspectRatio(pInfo, pBits, &sizeThumbnail, rgRect, _dwRecClrDepth, hpal, FALSE, _GetBackColor(), &hBmpCleanup)) { // finally success :-) we have the new image we can abandon the old one... hBmp = hBmpCleanup; hr = S_OK; } } } LocalFree(pBits); } } DeleteDC(hdcMem); } LocalFree(pInfo); } } else { // the original bitmap is fine hr = S_OK; } } UINT iImage; if (SUCCEEDED(hr)) { // check if we are going away, if so, then don't use Sendmessage because it will block the // destructor of the scheduler... if (_fDestroying) { hr = E_FAIL; } else { // copy thumbnail into the cache. IMAGECACHEINFO rgInfo = {0}; 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.ftDateStamp = ftDateStamp; if (!IsNullTime(&ftDateStamp)) rgInfo.dwMask |= ICIFLAG_DATESTAMP; if (IS_WINDOW_RTL_MIRRORED(_hwndListview)) rgInfo.dwMask |= ICIFLAG_MIRROR; hr = _SafeAddImage(FALSE, &rgInfo, &iImage, (int) dwItemID); } } if (hBmpCleanup) { DeleteObject(hBmpCleanup); } #ifdef USEMASK DeleteObject(hbmMask); #endif if (SUCCEEDED(hr)) { LPITEMIDLIST pidlToSend = ILClone(pidl); if (pidlToSend) { DSV_UPDATETHUMBNAIL* putn = (DSV_UPDATETHUMBNAIL*)LocalAlloc(LPTR, sizeof(DSV_UPDATETHUMBNAIL)); if (putn) { putn->iImage = (hr == S_OK) ? iImage : I_IMAGECALLBACK; putn->iItem = iItem; putn->pidl = pidlToSend; // post to the main thread so we don't deadlock if (!::PostMessage(_hwndView, WM_DSV_UPDATETHUMBNAIL, 0, (LPARAM)putn)) _CleanupUpdateThumbnail(putn); } else { ILFree(pidlToSend); } } } return hr; } int CDefView::_IncrementWriteTaskCount() { return InterlockedIncrement((PLONG)&_iWriteTaskCount); } int CDefView::_DecrementWriteTaskCount() { return InterlockedDecrement((PLONG)&_iWriteTaskCount); } HRESULT CDefView::UpdateImageForItem(DWORD dwTaskID, HBITMAP hImage, int iItem, LPCITEMIDLIST pidl, LPCWSTR pszPath, FILETIME ftDateStamp, BOOL fCache, DWORD dwPriority) { HRESULT hr = S_OK; TaskUpdateItem(pidl, iItem, _GetOverlayMask(pidl), pszPath, ftDateStamp, 0, hImage, dwTaskID); if (_pDiskCache && fCache && (_iWriteTaskCount < MAX_WRITECACHE_TASKS)) { // REVIEW: if pidl is an encrypted file but isn't in an encrytped folder, should avoid writing it's thumbnail? // If we don't, other users could otherwise view the thumbnail and thus know the contents of the encrypted file. // Add a cache write test IRunnableTask *pTask; if (SUCCEEDED(CWriteCacheTask_Create(dwTaskID, this, pszPath, ftDateStamp, hImage, &pTask))) { _AddTask(pTask, TOID_WriteCacheHandler, dwTaskID, dwPriority - PRIORITY_DELTA_WRITE, ADDTASK_ONLYONCE | ADDTASK_ATEND); pTask->Release(); hr = S_FALSE; } } return hr; } DWORD CDefView::_GetOverlayMask(LPCITEMIDLIST pidl) { DWORD dwLink = SFGAO_GHOSTED; // SFGAO_LINK | SFGAO_SHARE _pshf->GetAttributesOf(1, &pidl, &dwLink); return dwLink; } void CDefView::_UpdateThumbnail(int iItem, int iImage, LPCITEMIDLIST pidl) { if (!_IsOwnerData()) { if (_hwndListview) { int iFoundItem = _FindItemHint(pidl, iItem); if (-1 != iFoundItem) { LV_ITEM rgItem = {0}; rgItem.mask = LVIF_IMAGE; rgItem.iItem = iFoundItem; rgItem.iImage = iImage; // We are about to change the given item for purely internal reasons, we should not treat // this change as a "real change". As such we set a flag so that we ignore the LVN_ITEMCHANGED // notification that is generated by this LVM_SETITEM message. If we don't ingore this // next message then we would fire another DISPID_SELECTIONCHANGED every time we finish // extracting an image (if the image is selected). _fIgnoreItemChanged = TRUE; ListView_SetItem(_hwndListview, &rgItem); _fIgnoreItemChanged = FALSE; } } } else { RECT rc; ListView_GetItemRect(_hwndListview, iItem, &rc, LVIR_BOUNDS); InvalidateRect(_hwndListview, &rc, FALSE); } } void CDefView::_CleanupUpdateThumbnail(DSV_UPDATETHUMBNAIL* putn) { ILFree(putn->pidl); LocalFree((HLOCAL)putn); } int CDefView::ViewGetIconIndex(LPCITEMIDLIST pidl) { int iIndex = -1; if (_psi) { // check to see if we succeeded and we weren't told to extract the icon // ourselves ... if ((S_OK == _psi->GetIconOf(pidl, 0, &iIndex)) && _psio) { int iOverlay; if (SUCCEEDED(_psio->GetOverlayIndex(pidl, &iOverlay))) { iIndex |= iOverlay << 24; } } } if (-1 == iIndex) { iIndex = SHMapPIDLToSystemImageListIndex(_pshf, pidl, NULL); } return (iIndex >= 0) ? iIndex : II_DOCNOASSOC; } HRESULT CDefView::CreateDefaultThumbnail(int iIndex, HBITMAP *phBmpThumbnail, BOOL fCorner) { HRESULT hr = E_FAIL; // get the background for the default thumbnail. HDC hdc = GetDC(NULL); HDC hMemDC = CreateCompatibleDC(hdc); if (hMemDC) { SIZE sizeThumbnail; _GetThumbnailSize(&sizeThumbnail); *phBmpThumbnail = CreateCompatibleBitmap(hdc, sizeThumbnail.cx, sizeThumbnail.cy); if (*phBmpThumbnail) { HGDIOBJ hTmp = SelectObject(hMemDC, *phBmpThumbnail); RECT rc = {0, 0, sizeThumbnail.cx, sizeThumbnail.cy}; SHFillRectClr(hMemDC, &rc, _GetBackColor()); IImageList* piml; if (SUCCEEDED(SHGetImageList(SHIL_EXTRALARGE, IID_PPV_ARG(IImageList, &piml)))) { int cxIcon, cyIcon, x, y, dx, dy; // calculate position and width of icon. piml->GetIconSize(&cxIcon, &cyIcon); if (cxIcon < sizeThumbnail.cx) { if (fCorner) { x = 0; } else { x = (sizeThumbnail.cx - cxIcon) / 2; } dx = cxIcon; } else { // in case icon size is larger than thumbnail size. x = 0; dx = sizeThumbnail.cx; } if (cyIcon < sizeThumbnail.cy) { if (fCorner) { y = sizeThumbnail.cy - cyIcon; } else { y = (sizeThumbnail.cy - cyIcon) / 2; } dy = cyIcon; } else { // in case icon size is larger than thumbnail size. y = 0; dy = sizeThumbnail.cy; } IMAGELISTDRAWPARAMS idp = {sizeof(idp)}; idp.i = (iIndex & 0x00ffffff); idp.hdcDst = hMemDC; idp.x = x; idp.y = y; idp.cx = dx; idp.cy = dy; idp.rgbBk = CLR_DEFAULT; idp.rgbFg = CLR_DEFAULT; idp.fStyle = ILD_TRANSPARENT; piml->Draw(&idp); piml->Release(); } // get the bitmap produced so that it will be returned. *phBmpThumbnail = (HBITMAP) SelectObject(hMemDC, hTmp); hr = S_OK; } } if (hMemDC) DeleteDC(hMemDC); ReleaseDC(NULL, hdc); return hr; } void CDefView::_CacheDefaultThumbnail(LPCITEMIDLIST pidl, int* piIcon) { // 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 UINT iIndex = (UINT) ViewGetIconIndex(pidl); if (iIndex == (UINT) I_IMAGECALLBACK) { iIndex = II_DOCNOASSOC; } if (_pImageCache) { // 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 = L"Default"; rgInfo.dwFlags = _GetOverlayMask(pidl); rgInfo.iIndex = (int) iIndex; HRESULT hr = _pImageCache->FindImage(&rgInfo, (UINT*)piIcon); if (hr != S_OK) { HBITMAP hBmpThumb = NULL; hr = CreateDefaultThumbnail(iIndex, &hBmpThumb, FALSE); if (SUCCEEDED(hr)) { // we are creating a new one, so we shouldn't have an index yet .. Assert(*piIcon == I_IMAGECALLBACK); // copy thumbnail into the imagelist. rgInfo.dwMask = ICIFLAG_NAME | ICIFLAG_FLAGS | ICIFLAG_INDEX | ICIFLAG_LARGE | ICIFLAG_BITMAP; rgInfo.hBitmapLarge = hBmpThumb; rgInfo.hMaskLarge = NULL; if (IS_WINDOW_RTL_MIRRORED(_hwndListview)) rgInfo.dwMask |= ICIFLAG_MIRROR; hr = _SafeAddImage(TRUE, &rgInfo, (UINT*)piIcon, -1); DeleteObject(hBmpThumb); } else { *piIcon = (UINT) I_IMAGECALLBACK; } } } else { *piIcon = II_DOCNOASSOC; } } // // Creates an thumbnail overlay based on the system index // HRESULT CDefView::_CreateOverlayThumbnail(int iIndex, HBITMAP* phbmOverlay, HBITMAP* phbmMask) { HRESULT hr = CreateDefaultThumbnail(iIndex, phbmOverlay, TRUE); if (SUCCEEDED(hr)) { HDC hdc = GetDC(NULL); BITMAP bm; hr = E_FAIL; if (::GetObject(*phbmOverlay, sizeof(bm), &bm) == sizeof(bm)) { HDC hdcImg = ::CreateCompatibleDC(hdc); HDC hdcMask = ::CreateCompatibleDC(hdc); if (hdcImg && hdcMask) { *phbmMask = ::CreateBitmap(bm.bmWidth, bm.bmHeight, 1, 1, NULL); if (*phbmMask) { HBITMAP hbmpOldImg = (HBITMAP) ::SelectObject(hdcImg, *phbmOverlay); HBITMAP hbmpOldMsk = (HBITMAP) ::SelectObject(hdcMask, *phbmMask); COLORREF clrTransparent = ::GetPixel(hdcImg, 0, 0); ::SetBkColor(hdcImg, clrTransparent); ::BitBlt(hdcMask, 0, 0, bm.bmWidth, bm.bmHeight, hdcImg, 0, 0, SRCCOPY); ::SelectObject(hdcImg, hbmpOldImg); ::SelectObject(hdcMask, hbmpOldMsk); hr = S_OK; } } if (hdcImg) { DeleteDC(hdcImg); } if (hdcMask) { DeleteDC(hdcMask); } } ReleaseDC(NULL, hdc); } return hr; } void CDefView::_DoThumbnailReadAhead() { // Start up the ReadAheadHandler if: // 1) view requires thumbnails // 2) we have items in view (handle delayed enum) // 3) we haven't kicked it off already // 4) If we're not ownerdata if (_IsImageMode()) { UINT cItems = ListView_GetItemCount(_hwndListview); if (cItems && !_fReadAhead && !_IsOwnerData()) { // Start the read-ahead task _fReadAhead = TRUE; IRunnableTask *pTask; if (SUCCEEDED(CReadAheadTask_Create(this, &pTask))) { // add with a low prority, but higher than HTML extraction... _AddTask(pTask, TOID_ReadAheadHandler, 0, PRIORITY_READAHEAD, ADDTASK_ATEND); pTask->Release(); } } } } HRESULT CDefView::ExtractItem(UINT *puIndex, int iItem, LPCITEMIDLIST pidl, BOOL fBackground, BOOL fForce, DWORD dwMaxPriority) { if (!_pImageCache || _fDestroying) return S_FALSE; if (iItem == -1 && !pidl) { return S_FALSE; // failure.... } if (iItem == -1) { // LISTVIEW iItem = _FindItem(pidl, NULL, FALSE); if (iItem == -1) { return S_FALSE; } } IExtractImage *pExtract; HRESULT hr = _pshf->GetUIObjectOf(_hwndMain, 1, &pidl, IID_X_PPV_ARG(IExtractImage, 0, &pExtract)); if (FAILED(hr)) { hr = _GetDefaultTypeExtractor(pidl, &pExtract); } if (SUCCEEDED(hr)) { FILETIME ftImageTimeStamp = {0,0}; // do they support date stamps.... IExtractImage2 *pei2; if (SUCCEEDED(pExtract->QueryInterface(IID_PPV_ARG(IExtractImage2, &pei2)))) { pei2->GetDateStamp(&ftImageTimeStamp); pei2->Release(); } if (IsNullTime(&ftImageTimeStamp) && _pshf2) { // fall back to this (most common case) GetDateProperty(_pshf2, pidl, &SCID_WRITETIME, &ftImageTimeStamp); } // always extract at 24 bit incase we have to cache it ... WCHAR szPath[MAX_PATH]; DWORD dwFlags = IEIFLAG_ASYNC | IEIFLAG_ORIGSIZE; if (fForce) { dwFlags |= IEIFLAG_QUALITY; // Force means give me the high-quality thumbnail, if possible } // Let this run at a slightly higher priority so that we can get the eventual // cache read or extract task scheduled sooner DWORD dwPriority = PRIORITY_EXTRACT_NORMAL; SIZE sizeThumbnail; _GetThumbnailSize(&sizeThumbnail); hr = pExtract->GetLocation(szPath, ARRAYSIZE(szPath), &dwPriority, &sizeThumbnail, 24, &dwFlags); if (dwPriority == PRIORITY_EXTRACT_NORMAL) { dwPriority = dwMaxPriority; } else if (dwPriority > PRIORITY_EXTRACT_NORMAL) { dwPriority = dwMaxPriority + PRIORITY_DELTA_FAST; } else { dwPriority = dwMaxPriority - PRIORITY_DELTA_SLOW; } if (SUCCEEDED(hr) || (hr == E_PENDING)) { BOOL fAsync = (hr == E_PENDING); hr = E_FAIL; // use the name of the item in defview as the key for the caches DisplayNameOf(_pshf, pidl, SHGDN_INFOLDER | SHGDN_FORPARSING, szPath, ARRAYSIZE(szPath)); if (!fForce) { // check if the image is already in the in memory cache IMAGECACHEINFO rgInfo = {0}; rgInfo.cbSize = sizeof(rgInfo); rgInfo.dwMask = ICIFLAG_NAME | ICIFLAG_FLAGS; rgInfo.pszName = szPath; rgInfo.dwFlags = _GetOverlayMask(pidl); rgInfo.ftDateStamp = ftImageTimeStamp; if (!IsNullTime(&ftImageTimeStamp)) rgInfo.dwMask |= ICIFLAG_DATESTAMP; hr = _pImageCache->FindImage(&rgInfo, puIndex); } if (hr != S_OK) { DWORD dwTaskID = _MapIndexPIDLToID(iItem, pidl); if (dwTaskID != (DWORD) -1) { // create a task for a disk cache CTestCacheTask *pTask; hr = CTestCacheTask_Create(dwTaskID, this, pExtract, szPath, 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... _pScheduler->RemoveTasks(TOID_ExtractImageTask, dwTaskID, TRUE); } // NOTE: We must call RunInitRT, not Run, for CTestCacheTask. The reason is that RunInitRT // will return S_FALSE if it needs the default icon to be displayed but we would loose that // extra data if we call Run directly. hr = pTask->RunInitRT(); // If RunInitRT returns S_OK then the correct image index was generated, however we don't know what // that index is at this time. We will return S_OK and I_IMAGECALLBACK in this case because we // know that a WM_UPDATEITEMIMAGE message should have been posted } else { // add the task to the scheduler... TraceMsg(TF_DEFVIEW, "ExtractItem *ADDING* CCheckCacheTask (szPath=%s priority=%x index=%d ID=%d)", szPath, dwPriority, iItem, dwTaskID); hr = _AddTask((IRunnableTask *)pTask, TOID_CheckCacheTask, dwTaskID, dwPriority, ADDTASK_ONLYONCE); // signify we want a default icon for now.... hr = S_FALSE; } pTask->Release(); } } } } pExtract->Release(); } return hr; } 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 * psizeThumbnail, UINT uBytesPerPix) { // 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 / (psizeThumbnail->cx + DEFSIZE_BORDER); int iCol = iHeight / (psizeThumbnail->cy + DEFSIZE_VERTBDR); UINT iMinThumbs = iRow * iCol + NUM_OVERLAY_IMAGES; // calculate the maximum number of thumbnails in the cache based on available memory MEMORYSTATUS ms; ms.dwLength = sizeof(ms); GlobalMemoryStatus(&ms); // set the thumbnail maximum by calculating the memory required for a single thumbnail. // then use no more than 1/3 the available memory. // Say you had 80x80x32bpp thumbnails, this would be 13 images per MB of available memory. int iMemReqThumb = psizeThumbnail->cx * psizeThumbnail->cy * uBytesPerPix; UINT iMaxThumbs = UINT((ms.dwAvailPhys / 3) / iMemReqThumb); #ifdef DEBUG return iMinThumbs; #else return __max(iMaxThumbs, iMinThumbs); #endif } void ListView_InvalidateImageIndexes(HWND hwndList) { int iItem = -1; while ((iItem = ListView_GetNextItem(hwndList, iItem, 0)) != -1) { LV_ITEM lvi = {0}; lvi.mask = LVIF_IMAGE; lvi.iItem = iItem; lvi.iImage = I_IMAGECALLBACK; ListView_SetItem(hwndList, &lvi); } } ULONG CDefView::_ApproxItemsPerView() { RECT rcClient; ULONG ulItemsPerView = 0; if (_hwndView && GetClientRect(_hwndView, &rcClient)) { SIZE sizeThumbnail; _GetThumbnailSize(&sizeThumbnail); ULONG ulItemWidth = sizeThumbnail.cx + DEFSIZE_BORDER; ULONG ulItemHeight = sizeThumbnail.cy + DEFSIZE_VERTBDR; ulItemsPerView = (rcClient.right - rcClient.left + ulItemWidth / 2) / ulItemWidth; ulItemsPerView *= (rcClient.bottom - rcClient.top + ulItemHeight / 2) / ulItemHeight; } return ulItemsPerView; } void CDefView::_SetThumbview() { // Since we are switching into thumbnail view, remove any icon tasks if (_pScheduler) _pScheduler->RemoveTasks(TOID_DVIconExtract, ITSAT_DEFAULT_LPARAM, TRUE); if (_pImageCache == NULL) { // create the image cache (before we do the CreateWindow).... CoCreateInstance(CLSID_ImageListCache, NULL, CLSCTX_INPROC, IID_PPV_ARG(IImageCache3, &_pImageCache)); } if (_pDiskCache == NULL && !SHRestricted(REST_NOTHUMBNAILCACHE) && !SHRegGetBoolUSValue(REGSTR_EXPLORER_ADVANCED, TEXT("DisableThumbnailCache"), 0, FALSE)) { LPITEMIDLIST pidlFolder = _GetViewPidl(); if (pidlFolder) { LoadFromIDList(CLSID_ShellThumbnailDiskCache, pidlFolder, IID_PPV_ARG(IShellImageStore, &_pDiskCache)); ILFree(pidlFolder); } } if (_IsOwnerData()) _ThumbnailMapInit(); if (_pImageCache) { HRESULT hrInit = E_FAIL; UINT uBytesPerPix; IMAGECACHEINITINFO rgInitInfo; rgInitInfo.cbSize = sizeof(rgInitInfo); rgInitInfo.dwMask = ICIIFLAG_LARGE | ICIIFLAG_SORTBYUSED; _GetThumbnailSize(&rgInitInfo.rgSizeLarge); rgInitInfo.iStart = 0; rgInitInfo.iGrow = 5; _dwRecClrDepth = rgInitInfo.dwFlags = GetCurrentColorFlags(&uBytesPerPix); rgInitInfo.dwFlags |= ILC_MASK; _iMaxCacheSize = CalcCacheMaxSize(&rgInitInfo.rgSizeLarge, uBytesPerPix); hrInit = _pImageCache->GetImageList(&rgInitInfo); if (SUCCEEDED(hrInit)) { // GetImageList() will return S_FALSE if it was already created... if (_dwRecClrDepth <= 8) { HPALETTE hpal = NULL; HRESULT hrPalette = _GetBrowserPalette(&hpal); if (SUCCEEDED(_GetBrowserPalette(&hpal))) { PALETTEENTRY rgColours[256]; RGBQUAD rgDIBColours[256]; int nColours = GetPaletteEntries(hpal, 0, ARRAYSIZE(rgColours), rgColours); // translate from the LOGPALETTE structure to the RGBQUAD structure ... for (int iColour = 0; iColour < nColours; iColour ++) { rgDIBColours[iColour].rgbRed = rgColours[iColour].peRed; rgDIBColours[iColour].rgbBlue = rgColours[iColour].peBlue; rgDIBColours[iColour].rgbGreen = rgColours[iColour].peGreen; rgDIBColours[iColour].rgbReserved = 0; } ImageList_SetColorTable(rgInitInfo.himlLarge, 0, nColours, rgDIBColours); } // Make sure we are not using the double buffer stuff... ListView_SetExtendedListViewStyleEx(_hwndListview, LVS_EX_DOUBLEBUFFER, 0); } ListView_SetExtendedListViewStyleEx(_hwndListview, LVS_EX_BORDERSELECT, LVS_EX_BORDERSELECT); if (_fs.fFlags & FWF_OWNERDATA) { InvalidateRect(_hwndListview, NULL, TRUE); } else { ListView_InvalidateImageIndexes(_hwndListview); } ListView_SetImageList(_hwndListview, rgInitInfo.himlLarge, LVSIL_NORMAL); HIMAGELIST himlLarge; Shell_GetImageLists(&himlLarge, NULL); int cxIcon, cyIcon; ImageList_GetIconSize(himlLarge, &cxIcon, &cyIcon); int cySpacing = (_fs.fFlags & FWF_HIDEFILENAMES) ? cyIcon / 4 + rgInitInfo.rgSizeLarge.cy + 3 : 0; int cxSpacing = cxIcon / 4 + rgInitInfo.rgSizeLarge.cx + 1; // Usability issue: people have trouble unselecting, marquee selecting, and dropping // since they can't find the background. Add an extra 20 pixels between the thumbnails // to avoid this problem. // ListView_SetIconSpacing(_hwndListview, cxSpacing + 20, cySpacing); // NOTE: if you need to adjust cySpacing above, you can't do it directly since we // can't calculate the proper size of the icons. Do it this way: // DWORD dwOld = ListView_SetIconSpacing(_hwndListview, cxSpacing, cySpacing); // ListView_SetIconSpacing(_hwndListview, LOWORD(dwOld)+20, HIWORD(dwOld)+20); if (_fs.fFlags & FWF_HIDEFILENAMES) ListView_SetExtendedListViewStyleEx(_hwndListview, LVS_EX_HIDELABELS, LVS_EX_HIDELABELS); // We need to pre-populate the ImageList controled by _pImageCache // to contain the default system overlays so that our overlays will // work. We are going to get them from the already created shell image // lists as they are in hard-coded locations UINT uCacheSize = 0; _pImageCache->GetCacheSize(&uCacheSize); if (!uCacheSize) // If there are images in the cache the overlays are already there { IImageList* piml; if (SUCCEEDED(SHGetImageList(SHIL_EXTRALARGE, IID_PPV_ARG(IImageList, &piml)))) { struct _OverlayMap { int iSystemImage; int iThumbnailImage; } rgOverlay[NUM_OVERLAY_IMAGES]; // For whatever reason Overlays are one-based for (int i = 1; i <= NUM_OVERLAY_IMAGES; i++) { int iSysImageIndex; if (SUCCEEDED(piml->GetOverlayImage(i, &iSysImageIndex)) && (iSysImageIndex != -1)) { int iMap; for (iMap = 0; iMap < i - 1; iMap++) { if (rgOverlay[iMap].iSystemImage == iSysImageIndex) break; } if (iMap == (i - 1)) // We haven't used this System Image yet { HBITMAP hbmOverlay = NULL; HBITMAP hbmMask = NULL; if (SUCCEEDED(_CreateOverlayThumbnail(iSysImageIndex, &hbmOverlay, &hbmMask)) && hbmOverlay && hbmMask) { IMAGECACHEINFO rgInfo = {0}; int iThumbImageIndex; rgInfo.cbSize = sizeof(rgInfo); rgInfo.dwMask = ICIFLAG_SYSTEM | ICIFLAG_LARGE | ICIFLAG_BITMAP; rgInfo.hBitmapLarge = hbmOverlay; rgInfo.hMaskLarge = hbmMask; if (IS_WINDOW_RTL_MIRRORED(_hwndListview)) rgInfo.dwMask |= ICIFLAG_MIRROR; if (SUCCEEDED(_SafeAddImage(TRUE, &rgInfo, (UINT*)&iThumbImageIndex, -1))) { ImageList_SetOverlayImage(rgInitInfo.himlLarge, iThumbImageIndex, i); rgOverlay[iMap].iSystemImage = iSysImageIndex; rgOverlay[iMap].iThumbnailImage = iThumbImageIndex; } else { rgOverlay[i - 1].iSystemImage = -1; // failed to add the image ImageList_SetOverlayImage(rgInitInfo.himlLarge, -1, i); } } else { rgOverlay[i - 1].iSystemImage = -1; // failed to import htis image ImageList_SetOverlayImage(rgInitInfo.himlLarge, -1, i); } if (hbmOverlay) { DeleteObject(hbmOverlay); } if (hbmMask) { DeleteObject(hbmMask); } } else { ImageList_SetOverlayImage(rgInitInfo.himlLarge, rgOverlay[iMap].iThumbnailImage, i); rgOverlay[i - 1].iSystemImage = -1; // image already shows up in list } } else { rgOverlay[i - 1].iSystemImage = -1; // Didn't find a system image ImageList_SetOverlayImage(rgInitInfo.himlLarge, -1, i); } } } } } } } void CDefView::_ResetThumbview() { ListView_SetExtendedListViewStyleEx(_hwndListview, LVS_EX_BORDERSELECT, 0); if (_fs.fFlags & FWF_HIDEFILENAMES) ListView_SetExtendedListViewStyleEx(_hwndListview, LVS_EX_HIDELABELS, 0); if (_dwRecClrDepth <= 8) { ListView_SetExtendedListViewStyleEx(_hwndListview, LVS_EX_DOUBLEBUFFER, LVS_EX_DOUBLEBUFFER); } ListView_SetIconSpacing(_hwndListview, -1, -1); _SetSysImageList(); if (_IsOwnerData()) _ThumbnailMapClear(); } HRESULT CDefView::_GetDefaultTypeExtractor(LPCITEMIDLIST pidl, IExtractImage **ppExt) { IAssociationArray * paa; HRESULT hr = _pshf->GetUIObjectOf(NULL, 1, &pidl, IID_X_PPV_ARG(IAssociationArray, NULL, &paa)); if (SUCCEEDED(hr)) { LPWSTR psz; hr = paa->QueryString(ASSOCELEM_MASK_QUERYNORMAL, AQN_NAMED_VALUE, L"Thumbnail", &psz); if (SUCCEEDED(hr)) { LPITEMIDLIST pidlThumb; hr = SHILCreateFromPath(psz, &pidlThumb, NULL); if (SUCCEEDED(hr)) { SHGetUIObjectFromFullPIDL(pidlThumb, NULL, IID_PPV_ARG(IExtractImage, ppExt)); ILFree(pidlThumb); } CoTaskMemFree(psz); } paa->Release(); } return hr; } struct ThumbMapNode { int iIndex; LPITEMIDLIST pidl; ~ThumbMapNode() { ILFree(pidl); } }; int CDefView::_MapIndexPIDLToID(int iIndex, LPCITEMIDLIST pidl) { int ret = -1; if (_IsOwnerData()) { int cNodes = DPA_GetPtrCount(_dpaThumbnailMap); int iNode = 0; for (; iNode < cNodes; iNode++) { ThumbMapNode* pNode = (ThumbMapNode*) DPA_GetPtr(_dpaThumbnailMap, iNode); ASSERT(pNode); if (pNode->iIndex == iIndex) { if (!(_pshf->CompareIDs(0, pidl, pNode->pidl))) // 99 percent of the time we are good { ret = iNode; } else // Someone moved our pidl! { int iNodeStop = iNode; for (iNode = (iNode + 1) % cNodes; iNode != iNodeStop; iNode = (iNode + 1) % cNodes) { pNode = (ThumbMapNode*) DPA_GetPtr(_dpaThumbnailMap, iNode); if (!(_pshf->CompareIDs(0, pidl, pNode->pidl))) { ret = iNode; pNode->iIndex = iIndex; // Newer index for pidl break; } } } break; } } if (ret == -1) { ThumbMapNode* pNode = new ThumbMapNode; if (pNode) { pNode->iIndex = iIndex; pNode->pidl = ILClone(pidl); ret = DPA_AppendPtr(_dpaThumbnailMap, pNode); if (ret == -1) { delete pNode; } } } } else { ret = ListView_MapIndexToID(_hwndListview, iIndex); } return ret; } int CDefView::_MapIDToIndex(int iID) { int ret = -1; if (_IsOwnerData()) { ThumbMapNode* pNode = (ThumbMapNode*) DPA_GetPtr(_dpaThumbnailMap, iID); if (pNode) { ret = pNode->iIndex; } } else { ret = ListView_MapIDToIndex(_hwndListview, iID); } return ret; } void CDefView::_ThumbnailMapInit() { if (_dpaThumbnailMap) { _ThumbnailMapClear(); } else { _dpaThumbnailMap = DPA_Create(1); } } void CDefView::_ThumbnailMapClear() { if (_dpaThumbnailMap) { int i = DPA_GetPtrCount(_dpaThumbnailMap); while (--i >= 0) { ThumbMapNode* pNode = (ThumbMapNode*) DPA_FastGetPtr(_dpaThumbnailMap, i); delete pNode; } DPA_DeleteAllPtrs(_dpaThumbnailMap); } } HRESULT CDefView::_GetBrowserPalette(HPALETTE* phpal) { HRESULT hr = E_UNEXPECTED; if (_psb) { IBrowserService *pbs; hr = _psb->QueryInterface(IID_PPV_ARG(IBrowserService, &pbs)); if (SUCCEEDED(hr)) { hr = pbs->GetPalette(phpal); pbs->Release(); } } return hr; }