1410 lines
49 KiB
C++
1410 lines
49 KiB
C++
|
#include "shellprv.h"
|
||
|
#include "filefldr.h"
|
||
|
#include "ids.h"
|
||
|
#include "prop.h"
|
||
|
#include "copy.h"
|
||
|
|
||
|
// If these values are modified, the logic in Extract() must be modified too.
|
||
|
#define SMALLEST_THUMBNAIL_WITH_4_PREVIEWS 96
|
||
|
#define MAX_MINIPREVIEWS_COLLECT 8 // collect more than we're going to show, just in case one fails
|
||
|
#define MAX_MINIPREVIEWS 4
|
||
|
|
||
|
#define FOLDER_GUID TEXT("{A42CD7B6-E9B9-4D02-B7A6-288B71AD28BA}")
|
||
|
|
||
|
// Function in defview
|
||
|
void SHGetThumbnailSize(SIZE *psize);
|
||
|
|
||
|
typedef enum
|
||
|
{
|
||
|
MINIPREVIEW_LAYOUT_1 = 0,
|
||
|
MINIPREVIEW_LAYOUT_4 = 1,
|
||
|
} MINIPREVIEW_LAYOUT;
|
||
|
|
||
|
// The size of the mini-thumbnails for each thumbnail size. For each thumbnail
|
||
|
// size, there is a mini-thumbnail size for single layout and 2x2 layout.
|
||
|
LONG const alFolder120MinipreviewSize[] = {104, 48};
|
||
|
LONG const alFolder96MinipreviewSize[] = {82, 40};
|
||
|
LONG const alFolder80MinipreviewSize[] = {69, 32};
|
||
|
|
||
|
// These are the margins at which the mini-thumbnails appear within the main thumbnail.
|
||
|
// For thumbnails with only one large minipreview, we can just use x1,y1.
|
||
|
LONG const alFolder120MinipreviewOffsets[] = { 8, 64, 13, 67 }; // x1, x2, y1, y2
|
||
|
LONG const alFolder96MinipreviewOffsets[] = { 7, 49, 11, 52 }; // x1, x2, y1, y2
|
||
|
LONG const alFolder80MinipreviewOffsets[] = { 5, 42, 9, 45 }; // x1, x2, y1, y2
|
||
|
|
||
|
|
||
|
void FreeMiniPreviewPidls(LPITEMIDLIST apidlPreviews[], UINT cpidlPreviews);
|
||
|
|
||
|
// Helper functions
|
||
|
MINIPREVIEW_LAYOUT _GetMiniPreviewLayout(SIZE size);
|
||
|
void _GetMiniPreviewLocations(MINIPREVIEW_LAYOUT uLayout, SIZE sizeRequested, SIZE *psizeFolderBmp,
|
||
|
POINT aptOrigins[], SIZE *psizeMiniPreview);
|
||
|
HRESULT _DrawMiniPreviewBackground(HDC hdc, SIZE sizeFolderBmp, BOOL fAlpha, BOOL* pIsAlpha, RGBQUAD *prgb);
|
||
|
HBITMAP _CreateDIBSection(HDC hdcBmp, int cx, int cy);
|
||
|
HRESULT _CreateMainRenderingDC(HDC* phdc, HBITMAP* phBmpThumbnail, HBITMAP* phbmpOld, int cx, int cy, RGBQUAD** pprgb);
|
||
|
void _DestroyMainRenderingDC(HDC hdc, HBITMAP hbmpOld);
|
||
|
HRESULT _AddBitmap(HDC hdc, HBITMAP hbmpSub, POINT ptMargin, SIZE sizeDest, SIZE sizeSource, BOOL fAlphaSource, BOOL fAlphaDest, RGBQUAD *prgbDest, SIZE cxFolderSize);
|
||
|
|
||
|
// The files that can serve as thumbnails for folders:
|
||
|
const LPCWSTR c_szFolderThumbnailPaths[] = { L"folder.jpg", L"folder.gif" };
|
||
|
|
||
|
// We always have four now.
|
||
|
MINIPREVIEW_LAYOUT _GetMiniPreviewLayout(SIZE size)
|
||
|
{
|
||
|
return MINIPREVIEW_LAYOUT_4;
|
||
|
}
|
||
|
|
||
|
|
||
|
void FreeMiniPreviewPidls(LPITEMIDLIST apidlPreviews[], UINT cpidlPreviews)
|
||
|
{
|
||
|
for (UINT u = 0; u < cpidlPreviews; u++)
|
||
|
{
|
||
|
ILFree(apidlPreviews[u]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* In: uLayout - The layout (1 or 4 mini previews)
|
||
|
* sizeRequested - The size of the thumbnail we are trying to generate
|
||
|
*
|
||
|
* Out:
|
||
|
* -psizeFolderBmp is set to
|
||
|
* the size of the bitmap.
|
||
|
*
|
||
|
* -aptOrigins array is filled in with the locations of the n minipreviews
|
||
|
* (note, aptOrigins is assumed to have MAX_MINIPREVIEWS cells)
|
||
|
* The size of the minipreviews (square) is returned in pSizeMinipreview;
|
||
|
*/
|
||
|
void _GetMiniPreviewLocations(MINIPREVIEW_LAYOUT uLayout, SIZE sizeRequested, SIZE *psizeFolderBmp,
|
||
|
POINT aptOrigins[], SIZE *psizeMiniPreview)
|
||
|
{
|
||
|
|
||
|
const LONG *alOffsets;
|
||
|
LONG lSize; // One of the standard sizes, that we have a folder bitmap for.
|
||
|
LONG lSmallestDimension = min(sizeRequested.cx, sizeRequested.cy);
|
||
|
|
||
|
if (lSmallestDimension > 96) // For stuff bigger than 96, we use the 120 size
|
||
|
{
|
||
|
lSize = 120;
|
||
|
alOffsets = alFolder120MinipreviewOffsets;
|
||
|
psizeMiniPreview->cx = psizeMiniPreview->cy = alFolder120MinipreviewSize[uLayout];
|
||
|
}
|
||
|
else if (lSmallestDimension > 80) // For stuff bigger than 80, but <= 96, we use the 96 size.
|
||
|
{
|
||
|
lSize = 96;
|
||
|
alOffsets = alFolder96MinipreviewOffsets;
|
||
|
psizeMiniPreview->cx = psizeMiniPreview->cy = alFolder96MinipreviewSize[uLayout];
|
||
|
}
|
||
|
else // For stuff <= 80, we use 80.
|
||
|
{
|
||
|
lSize = 80;
|
||
|
alOffsets = alFolder80MinipreviewOffsets;
|
||
|
psizeMiniPreview->cx = psizeMiniPreview->cy = alFolder80MinipreviewSize[uLayout];
|
||
|
}
|
||
|
|
||
|
psizeFolderBmp->cx = psizeFolderBmp->cy = lSize;
|
||
|
|
||
|
COMPILETIME_ASSERT(4 == MAX_MINIPREVIEWS);
|
||
|
|
||
|
aptOrigins[0].x = alOffsets[0];
|
||
|
aptOrigins[0].y = alOffsets[2];
|
||
|
aptOrigins[1].x = alOffsets[1];
|
||
|
aptOrigins[1].y = alOffsets[2];
|
||
|
aptOrigins[2].x = alOffsets[0];
|
||
|
aptOrigins[2].y = alOffsets[3];
|
||
|
aptOrigins[3].x = alOffsets[1];
|
||
|
aptOrigins[3].y = alOffsets[3];
|
||
|
}
|
||
|
|
||
|
HBITMAP _CreateDIBSection(HDC h, int cx, int cy, RGBQUAD** pprgb)
|
||
|
{
|
||
|
BITMAPINFO bi = {0};
|
||
|
bi.bmiHeader.biSize = sizeof(bi.bmiHeader);
|
||
|
bi.bmiHeader.biWidth = cx;
|
||
|
bi.bmiHeader.biHeight = cy;
|
||
|
bi.bmiHeader.biPlanes = 1;
|
||
|
bi.bmiHeader.biBitCount = 32;
|
||
|
bi.bmiHeader.biCompression = BI_RGB;
|
||
|
|
||
|
return CreateDIBSection(h, &bi, DIB_RGB_COLORS, (void**)pprgb, NULL, 0);
|
||
|
}
|
||
|
|
||
|
// Pre multiplies alpha channel
|
||
|
void PreProcessDIB(int cx, int cy, RGBQUAD* pargb)
|
||
|
{
|
||
|
int cTotal = cx * cy;
|
||
|
for (int i = 0; i < cTotal; i++)
|
||
|
{
|
||
|
RGBQUAD* prgb = &pargb[i];
|
||
|
if (prgb->rgbReserved != 0)
|
||
|
{
|
||
|
prgb->rgbRed = ((prgb->rgbRed * prgb->rgbReserved) + 128) / 255;
|
||
|
prgb->rgbGreen = ((prgb->rgbGreen * prgb->rgbReserved) + 128) / 255;
|
||
|
prgb->rgbBlue = ((prgb->rgbBlue * prgb->rgbReserved) + 128) / 255;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*((DWORD*)prgb) = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Is there an alpha channel? Check for a non-zero alpha byte.
|
||
|
BOOL _HasAlpha(RECT rc, int cx, RGBQUAD *pargb)
|
||
|
{
|
||
|
for (int y = rc.top; y < rc.bottom; y++)
|
||
|
{
|
||
|
for (int x = rc.left; x < rc.right; x++)
|
||
|
{
|
||
|
int iOffset = y * cx;
|
||
|
if (pargb[x + iOffset].rgbReserved != 0)
|
||
|
return TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
|
||
|
/** In:
|
||
|
* fAlpha: Do we want the folder background to have an alpha channel?
|
||
|
* sizeFolderBmp: size of the thumbnail
|
||
|
*
|
||
|
* Out:
|
||
|
* pIsAlpha: Did we get what we wanted, if we wanted an alpha channel?
|
||
|
* (e.g. we won't get it if we're in < 24bit mode.)
|
||
|
*/
|
||
|
HRESULT _DrawMiniPreviewBackground(HDC hdc, SIZE sizeFolderBmp, BOOL fAlpha, BOOL* pfIsAlpha, RGBQUAD *prgb)
|
||
|
{
|
||
|
HRESULT hr = E_FAIL;
|
||
|
|
||
|
HICON hicon = (HICON)LoadImage(HINST_THISDLL, MAKEINTRESOURCE(IDI_FOLDER), IMAGE_ICON, sizeFolderBmp.cx, sizeFolderBmp.cy, 0);
|
||
|
|
||
|
if (hicon)
|
||
|
{
|
||
|
*pfIsAlpha = FALSE;
|
||
|
if (fAlpha)
|
||
|
{
|
||
|
// Try to blt an alpha channel icon into the dc
|
||
|
ICONINFO io;
|
||
|
if (GetIconInfo(hicon, &io))
|
||
|
{
|
||
|
BITMAP bm;
|
||
|
if (GetObject(io.hbmColor, sizeof(bm), &bm))
|
||
|
{
|
||
|
if (bm.bmBitsPixel == 32)
|
||
|
{
|
||
|
HDC hdcSrc = CreateCompatibleDC(hdc);
|
||
|
if (hdcSrc)
|
||
|
{
|
||
|
HBITMAP hbmpOld = (HBITMAP)SelectObject(hdcSrc, io.hbmColor);
|
||
|
|
||
|
BitBlt(hdc, 0, 0, sizeFolderBmp.cx, sizeFolderBmp.cy, hdcSrc, 0, 0, SRCCOPY);
|
||
|
|
||
|
// Preprocess the alpha
|
||
|
PreProcessDIB(sizeFolderBmp.cx, sizeFolderBmp.cy, prgb);
|
||
|
|
||
|
*pfIsAlpha = TRUE;
|
||
|
SelectObject(hdcSrc, hbmpOld);
|
||
|
DeleteDC(hdcSrc);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
DeleteObject(io.hbmColor);
|
||
|
DeleteObject(io.hbmMask);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!*pfIsAlpha)
|
||
|
{
|
||
|
// Didn't create an alpha bitmap
|
||
|
// We're filling the background with background window color.
|
||
|
RECT rc = { 0, 0, (long)sizeFolderBmp.cx + 1, (long)sizeFolderBmp.cy + 1};
|
||
|
SHFillRectClr(hdc, &rc, GetSysColor(COLOR_WINDOW));
|
||
|
|
||
|
// Then drawing the icon on top.
|
||
|
DrawIconEx(hdc, 0, 0, hicon, sizeFolderBmp.cx, sizeFolderBmp.cy, 0, NULL, DI_NORMAL);
|
||
|
|
||
|
// This may have resulted in an alpha channel - we need to know. (If it
|
||
|
// did, then when we add a nonalpha minibitmap to this main one, we need to restore
|
||
|
// the nuked out alpha channel)
|
||
|
// Check if we have alpha (prgb is the bits for the DIB of size sizeFolderBmp):
|
||
|
rc.right = sizeFolderBmp.cx;
|
||
|
rc.bottom = sizeFolderBmp.cy;
|
||
|
*pfIsAlpha = _HasAlpha(rc, sizeFolderBmp.cx, prgb);
|
||
|
}
|
||
|
|
||
|
DestroyIcon(hicon);
|
||
|
hr = S_OK;
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
BOOL DoesFolderContainLogo(LPCITEMIDLIST pidlFull)
|
||
|
{
|
||
|
BOOL bRet = FALSE;
|
||
|
IPropertyBag * pPropBag;
|
||
|
if (SUCCEEDED(SHGetViewStatePropertyBag(pidlFull, VS_BAGSTR_EXPLORER, SHGVSPB_PERUSER | SHGVSPB_PERFOLDER, IID_PPV_ARG(IPropertyBag, &pPropBag))))
|
||
|
{
|
||
|
TCHAR szLogo[MAX_PATH];
|
||
|
szLogo[0] = 0;
|
||
|
if (SUCCEEDED(SHPropertyBag_ReadStr(pPropBag, TEXT("Logo"), szLogo, ARRAYSIZE(szLogo))) && szLogo[0])
|
||
|
{
|
||
|
bRet = TRUE;
|
||
|
}
|
||
|
pPropBag->Release();
|
||
|
}
|
||
|
return bRet;
|
||
|
}
|
||
|
|
||
|
BOOL DoesFolderContainFolderJPG(IShellFolder *psf, LPCITEMIDLIST pidl)
|
||
|
{
|
||
|
BOOL bRet = FALSE;
|
||
|
// return false if there's not folder.jpg, or if folder.jpg is a folder (doh!)
|
||
|
IShellFolder *psfSubfolder;
|
||
|
|
||
|
// SHBTO can deal with NULL psf, he turns it into psfDesktop
|
||
|
if (SUCCEEDED(SHBindToObject(psf, IID_X_PPV_ARG(IShellFolder, pidl, &psfSubfolder))))
|
||
|
{
|
||
|
for (int i = 0; i < ARRAYSIZE(c_szFolderThumbnailPaths); i++)
|
||
|
{
|
||
|
DWORD dwFlags = SFGAO_FILESYSTEM | SFGAO_FOLDER;
|
||
|
LPITEMIDLIST pidlItem;
|
||
|
if (SUCCEEDED(psfSubfolder->ParseDisplayName(NULL, NULL, (LPOLESTR)c_szFolderThumbnailPaths[i], NULL, &pidlItem, &dwFlags)))
|
||
|
{
|
||
|
ILFree(pidlItem);
|
||
|
if ((dwFlags & (SFGAO_FILESYSTEM | SFGAO_FOLDER)) == SFGAO_FILESYSTEM)
|
||
|
{
|
||
|
bRet = TRUE;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
psfSubfolder->Release();
|
||
|
}
|
||
|
|
||
|
return bRet;
|
||
|
}
|
||
|
|
||
|
BOOL _IsShortcutTargetACandidate(IShellFolder *psf, LPCITEMIDLIST pidlPreview, BOOL *pbTryCached)
|
||
|
{
|
||
|
BOOL bRet = FALSE;
|
||
|
*pbTryCached = TRUE;
|
||
|
IShellLink *psl;
|
||
|
if (SUCCEEDED(psf->GetUIObjectOf(NULL, 1, &pidlPreview, IID_PPV_ARG_NULL(IShellLink, &psl))))
|
||
|
{
|
||
|
LPITEMIDLIST pidlTarget = NULL;
|
||
|
if (SUCCEEDED(psl->GetIDList(&pidlTarget)) && pidlTarget)
|
||
|
{
|
||
|
DWORD dwTargetFlags = SFGAO_FOLDER;
|
||
|
if (SUCCEEDED(SHGetNameAndFlags(pidlTarget, 0, NULL, 0, &dwTargetFlags)))
|
||
|
{
|
||
|
// return true if its not a folder, or if the folder contains a logo
|
||
|
// note that this is kinda like recursing into the below function again
|
||
|
bRet = (0 == (dwTargetFlags & SFGAO_FOLDER));
|
||
|
|
||
|
if (!bRet)
|
||
|
{
|
||
|
bRet = (DoesFolderContainLogo(pidlTarget) || DoesFolderContainFolderJPG(NULL, pidlTarget));
|
||
|
if (bRet)
|
||
|
{
|
||
|
// It's a logo folder, don't try the cached image.
|
||
|
*pbTryCached = FALSE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ILFree(pidlTarget);
|
||
|
}
|
||
|
psl->Release();
|
||
|
}
|
||
|
return bRet;
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL _IsMiniPreviewCandidate(IShellFolder *psf, LPCITEMIDLIST pidl, BOOL *pbTryCached)
|
||
|
{
|
||
|
BOOL bRet = FALSE;
|
||
|
DWORD dwAttr = SHGetAttributes(psf, pidl, SFGAO_FOLDER | SFGAO_LINK | SFGAO_FILESYSANCESTOR);
|
||
|
*pbTryCached = TRUE;
|
||
|
|
||
|
// if its a folder, check and see if its got a logo
|
||
|
// note that folder shortcuts will have both folder and link, and since we check folder first, we won't recurse into folder shortcuts
|
||
|
// dont do anything unless pidl is a folder on a real filesystem (i.e. dont walk into zip/cab)
|
||
|
if ((dwAttr & (SFGAO_FOLDER | SFGAO_FILESYSANCESTOR)) == (SFGAO_FOLDER | SFGAO_FILESYSANCESTOR))
|
||
|
{
|
||
|
LPITEMIDLIST pidlParent;
|
||
|
if (SUCCEEDED(SHGetIDListFromUnk(psf, &pidlParent)))
|
||
|
{
|
||
|
LPITEMIDLIST pidlFull;
|
||
|
if (SUCCEEDED(SHILCombine(pidlParent, pidl, &pidlFull)))
|
||
|
{
|
||
|
bRet = DoesFolderContainLogo(pidlFull);
|
||
|
ILFree(pidlFull);
|
||
|
}
|
||
|
ILFree(pidlParent);
|
||
|
}
|
||
|
|
||
|
if (!bRet)
|
||
|
{
|
||
|
// no logo image, check for a "folder.jpg"
|
||
|
// if its not there, then don't display pidl as a mini-preview, as it would recurse and produce dumb-looking 1/16 scale previews
|
||
|
bRet = DoesFolderContainFolderJPG(psf, pidl);
|
||
|
}
|
||
|
|
||
|
if (bRet)
|
||
|
{
|
||
|
// For logo folders, we don't look for a cached image (cached image won't have alpha, which we want)
|
||
|
*pbTryCached = FALSE;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Only if its not a link, or if its a link to a valid candidate, then we can get its extractor
|
||
|
if (0 == (dwAttr & SFGAO_LINK) ||
|
||
|
_IsShortcutTargetACandidate(psf, pidl, pbTryCached))
|
||
|
{
|
||
|
IExtractImage *pei;
|
||
|
if (SUCCEEDED(psf->GetUIObjectOf(NULL, 1, &pidl, IID_X_PPV_ARG(IExtractImage, NULL, &pei))))
|
||
|
{
|
||
|
bRet = TRUE;
|
||
|
pei->Release();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return bRet;
|
||
|
}
|
||
|
|
||
|
|
||
|
// We return the bits to the dibsection in the dc, if asked for. We need this for preprocessing the alpha channel,
|
||
|
// if one exists.
|
||
|
HRESULT _CreateMainRenderingDC(HDC* phdc, HBITMAP* phbmp, HBITMAP* phbmpOld, int cx, int cy, RGBQUAD** pprgb)
|
||
|
{
|
||
|
HRESULT hr = E_OUTOFMEMORY;
|
||
|
HDC hdc = GetDC(NULL);
|
||
|
|
||
|
if (hdc)
|
||
|
{
|
||
|
*phdc = CreateCompatibleDC(hdc);
|
||
|
if (*phdc)
|
||
|
{
|
||
|
RGBQUAD *prgbDummy;
|
||
|
*phbmp = _CreateDIBSection(*phdc, cx, cy, &prgbDummy);
|
||
|
if (*phbmp)
|
||
|
{
|
||
|
*phbmpOld = (HBITMAP) SelectObject(*phdc, *phbmp);
|
||
|
if (pprgb)
|
||
|
*pprgb = prgbDummy;
|
||
|
hr = S_OK;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
DeleteDC(*phdc);
|
||
|
}
|
||
|
}
|
||
|
ReleaseDC(NULL, hdc);
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
void _DestroyMainRenderingDC(HDC hdc, HBITMAP hbmpOld) // Unselects the bitmap, and deletes the Dc
|
||
|
{
|
||
|
if (hbmpOld)
|
||
|
SelectObject(hdc, hbmpOld);
|
||
|
DeleteDC(hdc);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
// We just blt'd a nonalpha guy into an alpha'd bitmap. This nuked out the alpha channel.
|
||
|
// Repair it by setting alpha channel to 0xff (opaque).
|
||
|
void _SetAlpha(RECT rc, SIZE sizeBmp, RGBQUAD *pargb)
|
||
|
{
|
||
|
for (int y = (sizeBmp.cy - rc.bottom); y < (sizeBmp.cy - rc.top); y++) // Origin at bottom left.
|
||
|
{
|
||
|
int iOffset = y * sizeBmp.cx;
|
||
|
for (int x = rc.left; x < rc.right; x++)
|
||
|
{
|
||
|
pargb[x + iOffset].rgbReserved = 0xff;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* In
|
||
|
* hbmpSub - little bitmap that we're adding to the thumbnail bitmap.
|
||
|
* ptMargin - where we're adding it on the destination thumbnail bitmap.
|
||
|
* sizeDest - how big it needs to be on the destination thumbnail bitmap.
|
||
|
* sizeSource - how bit it is.
|
||
|
* fAlphaSource - does the bitmap we're adding have an alpha channel?
|
||
|
* fAlphaDest - does what we're adding it to, have an alpha channel?
|
||
|
* prgbDest - the bits of the destination bitmap - needed if we add a non-alpha bitmap
|
||
|
* to an alpha background, so we can reset the alpha.
|
||
|
* sizeFolderBmp - the size of the destination bitmap - need this along with prgbDest.
|
||
|
*/
|
||
|
HRESULT _AddBitmap(HDC hdc, HBITMAP hbmpSub, POINT ptMargin, SIZE sizeDest, SIZE sizeSource, BOOL fAlphaSource, BOOL fAlphaDest, RGBQUAD *prgbDest, SIZE sizeFolderBmp)
|
||
|
{
|
||
|
HRESULT hr = E_OUTOFMEMORY;
|
||
|
|
||
|
HDC hdcFrom = CreateCompatibleDC(hdc);
|
||
|
if (hdcFrom)
|
||
|
{
|
||
|
// Select the bitmap into the source hdc.
|
||
|
HBITMAP hbmpOld = (HBITMAP)SelectObject(hdcFrom, hbmpSub);
|
||
|
if (hbmpOld)
|
||
|
{
|
||
|
// Adjust destination size to preserve aspect ratio
|
||
|
SIZE sizeDestActual;
|
||
|
if ((1000 * sizeDest.cx / sizeSource.cx) < // 1000 -> float simulation
|
||
|
(1000 * sizeDest.cy / sizeSource.cy))
|
||
|
{
|
||
|
// Keep destination width
|
||
|
sizeDestActual.cy = sizeSource.cy * sizeDest.cx / sizeSource.cx;
|
||
|
sizeDestActual.cx = sizeDest.cx;
|
||
|
ptMargin.y += (sizeDest.cy - sizeDestActual.cy) / 2; // Center
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Keep destination height
|
||
|
sizeDestActual.cx = sizeSource.cx * sizeDest.cy / sizeSource.cy;
|
||
|
sizeDestActual.cy = sizeDest.cy;
|
||
|
ptMargin.x += (sizeDest.cx - sizeDestActual.cx) / 2; // Center
|
||
|
}
|
||
|
|
||
|
// Now blt the image onto our folder background.
|
||
|
// Three alpha possibilities:
|
||
|
// Dest: no alpha, Src: no alpha -> the normal case
|
||
|
// Dest: no alpha, Src: alpha -> one of the minipreviews is a logo-ized folder.
|
||
|
// Dest: alpha, Src: no alpha -> we're a logoized folder being rendered as a minipreview in
|
||
|
// the parent folder's thumbnail.
|
||
|
|
||
|
// If we got back an alpha image, we need to alphablend it.
|
||
|
if (fAlphaSource)
|
||
|
{
|
||
|
// We shouldn't have gotten back an alpha image, if we're alpha'd too. That would imply we're
|
||
|
// doing a minipreview of a minipreview (1/16 scale).
|
||
|
//ASSERT(!fAlphaDest);
|
||
|
BLENDFUNCTION bf;
|
||
|
bf.BlendOp = AC_SRC_OVER;
|
||
|
bf.SourceConstantAlpha = 255;
|
||
|
bf.AlphaFormat = AC_SRC_ALPHA;
|
||
|
bf.BlendFlags = 0;
|
||
|
if (AlphaBlend(hdc, ptMargin.x, ptMargin.y, sizeDestActual.cx, sizeDestActual.cy, hdcFrom, 0 ,0, sizeSource.cx, sizeSource.cy, bf))
|
||
|
hr = S_OK;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Otherwise, just blt it.
|
||
|
int iModeSave = SetStretchBltMode(hdc, HALFTONE);
|
||
|
if (StretchBlt(hdc, ptMargin.x, ptMargin.y, sizeDestActual.cx, sizeDestActual.cy, hdcFrom, 0 ,0, sizeSource.cx, sizeSource.cy, SRCCOPY))
|
||
|
hr = S_OK;
|
||
|
SetStretchBltMode(hdc, iModeSave);
|
||
|
|
||
|
// Are we alpha'd? We didn't have an alpha source, so where we blt'd it, we've
|
||
|
// lost the alpha channel. Restore it.
|
||
|
if (fAlphaDest)
|
||
|
{
|
||
|
// Set the alpha channel over where we just blt'd.
|
||
|
RECT rc = {ptMargin.x, ptMargin.y, ptMargin.x + sizeDestActual.cx, ptMargin.y + sizeDestActual.cy};
|
||
|
_SetAlpha(rc, sizeFolderBmp, prgbDest);
|
||
|
}
|
||
|
}
|
||
|
SelectObject(hdcFrom, hbmpOld);
|
||
|
}
|
||
|
DeleteDC(hdcFrom);
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
class CFolderExtractImage : public IExtractImage2,
|
||
|
public IPersistPropertyBag,
|
||
|
public IAlphaThumbnailExtractor,
|
||
|
public IRunnableTask
|
||
|
{
|
||
|
public:
|
||
|
CFolderExtractImage();
|
||
|
|
||
|
STDMETHOD (QueryInterface)(REFIID riid, void **ppv);
|
||
|
STDMETHOD_(ULONG, AddRef) ();
|
||
|
STDMETHOD_(ULONG, Release) ();
|
||
|
|
||
|
// IExtractImage/IExtractLogo
|
||
|
STDMETHOD (GetLocation)(LPWSTR pszPath, DWORD cch, DWORD *pdwPriority, const SIZE *prgSize, DWORD dwRecClrDepth, DWORD *pdwFlags);
|
||
|
|
||
|
STDMETHOD (Extract)(HBITMAP *phbm);
|
||
|
|
||
|
// IExtractImage2
|
||
|
STDMETHOD (GetDateStamp)(FILETIME *pftDateStamp);
|
||
|
|
||
|
// IPersist
|
||
|
STDMETHOD(GetClassID)(CLSID *pClassID);
|
||
|
|
||
|
// IPersistPropertyBag
|
||
|
STDMETHOD(InitNew)();
|
||
|
STDMETHOD(Load)(IPropertyBag *ppb, IErrorLog *pErr);
|
||
|
STDMETHOD(Save)(IPropertyBag *ppb, BOOL fClearDirty, BOOL fSaveAll)
|
||
|
{ return E_NOTIMPL; }
|
||
|
|
||
|
// IRunnableTask
|
||
|
STDMETHOD (Run)(void);
|
||
|
STDMETHOD (Kill)(BOOL fWait);
|
||
|
STDMETHOD (Suspend)(void);
|
||
|
STDMETHOD (Resume)(void);
|
||
|
STDMETHOD_(ULONG, IsRunning)(void);
|
||
|
|
||
|
// IAlphaThumbnailExtractor
|
||
|
STDMETHOD (RequestAlphaThumbnail)(void);
|
||
|
|
||
|
STDMETHOD(Init)(IShellFolder *psf, LPCITEMIDLIST pidl);
|
||
|
private:
|
||
|
~CFolderExtractImage();
|
||
|
LPCTSTR _GetImagePath(UINT cx);
|
||
|
HRESULT _CreateWithMiniPreviews(IShellFolder *psf, const LPCITEMIDLIST *apidlPreviews, BOOL *abTryCached, UINT cpidlPreviews, MINIPREVIEW_LAYOUT uLayout, IShellImageStore *pImageStore, HBITMAP *phBmpThumbnail);
|
||
|
HRESULT _FindMiniPreviews(LPITEMIDLIST apidlPreviews[], BOOL abTryCached[], UINT *cpidlPreviews);
|
||
|
HRESULT _CreateThumbnailFromIconResource(HBITMAP* phBmpThumbnail, int res);
|
||
|
HRESULT _CheckThumbnailCache(HBITMAP *phbmp);
|
||
|
void _CacheThumbnail(HBITMAP hbmp);
|
||
|
|
||
|
IExtractImage *_pExtract;
|
||
|
IRunnableTask *_pRun;
|
||
|
long _cRef;
|
||
|
TCHAR _szFolder[MAX_PATH];
|
||
|
TCHAR _szLogo[MAX_PATH];
|
||
|
TCHAR _szWideLogo[MAX_PATH];
|
||
|
IShellFolder2 *_psf;
|
||
|
SIZE _size;
|
||
|
LPITEMIDLIST _pidl;
|
||
|
IPropertyBag *_ppb;
|
||
|
LONG _lState;
|
||
|
BOOL _fAlpha;
|
||
|
|
||
|
DWORD _dwPriority;
|
||
|
DWORD _dwRecClrDepth;
|
||
|
|
||
|
DWORD _dwExtractFlags;
|
||
|
};
|
||
|
|
||
|
STDAPI CFolderExtractImage_Create(IShellFolder *psf, LPCITEMIDLIST pidl, REFIID riid, void **ppv)
|
||
|
{
|
||
|
HRESULT hr = E_OUTOFMEMORY;
|
||
|
CFolderExtractImage *pfei = new CFolderExtractImage;
|
||
|
if (pfei)
|
||
|
{
|
||
|
hr = pfei->Init(psf, pidl);
|
||
|
if (SUCCEEDED(hr))
|
||
|
hr = pfei->QueryInterface(riid, ppv);
|
||
|
pfei->Release();
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
CFolderExtractImage::CFolderExtractImage() : _cRef(1), _lState(IRTIR_TASK_NOT_RUNNING)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
CFolderExtractImage::~CFolderExtractImage()
|
||
|
{
|
||
|
ATOMICRELEASE(_pExtract);
|
||
|
ATOMICRELEASE(_psf);
|
||
|
ILFree(_pidl);
|
||
|
ATOMICRELEASE(_ppb);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFolderExtractImage::QueryInterface(REFIID riid, void **ppv)
|
||
|
{
|
||
|
static const QITAB qit[] = {
|
||
|
QITABENT (CFolderExtractImage, IExtractImage2),
|
||
|
QITABENTMULTI (CFolderExtractImage, IExtractImage, IExtractImage2),
|
||
|
QITABENTMULTI2(CFolderExtractImage, IID_IExtractLogo, IExtractImage2),
|
||
|
QITABENT (CFolderExtractImage, IPersistPropertyBag),
|
||
|
QITABENT (CFolderExtractImage, IRunnableTask),
|
||
|
QITABENT (CFolderExtractImage, IAlphaThumbnailExtractor),
|
||
|
QITABENTMULTI (CFolderExtractImage, IPersist, IPersistPropertyBag),
|
||
|
{ 0 },
|
||
|
};
|
||
|
return QISearch(this, qit, riid, ppv);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP_(ULONG) CFolderExtractImage::AddRef()
|
||
|
{
|
||
|
return InterlockedIncrement(&_cRef);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP_(ULONG) CFolderExtractImage::Release()
|
||
|
{
|
||
|
if (InterlockedDecrement(&_cRef))
|
||
|
return _cRef;
|
||
|
|
||
|
delete this;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFolderExtractImage::GetDateStamp(FILETIME *pftDateStamp)
|
||
|
{
|
||
|
HANDLE h = CreateFile(_szFolder, GENERIC_READ,
|
||
|
FILE_SHARE_READ | FILE_SHARE_DELETE, NULL,
|
||
|
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
|
||
|
HRESULT hr = (h != INVALID_HANDLE_VALUE) ? S_OK : E_FAIL;
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
hr = GetFileTime(h, NULL, NULL, pftDateStamp) ? S_OK : E_FAIL;
|
||
|
CloseHandle(h);
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
HRESULT CFolderExtractImage::InitNew()
|
||
|
{
|
||
|
IPropertyBag *ppb;
|
||
|
// load up the default property bag for peruser perfolder
|
||
|
// may have problems down the line with thumbs.db being alluser.
|
||
|
if (SUCCEEDED(SHGetViewStatePropertyBag(_pidl, VS_BAGSTR_EXPLORER, SHGVSPB_PERUSER | SHGVSPB_PERFOLDER, IID_PPV_ARG(IPropertyBag, &ppb))))
|
||
|
{
|
||
|
IUnknown_Set((IUnknown**)&_ppb, ppb);
|
||
|
ppb->Release();
|
||
|
}
|
||
|
// return success always -- SHGVSPB can fail if _pidl is on a removable drive,
|
||
|
// but we still want to do our thing.
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
HRESULT CFolderExtractImage::Load(IPropertyBag *ppb, IErrorLog *pErr)
|
||
|
{
|
||
|
IUnknown_Set((IUnknown**)&_ppb, ppb);
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
LPCTSTR CFolderExtractImage::_GetImagePath(UINT cx)
|
||
|
{
|
||
|
if (!_szLogo[0])
|
||
|
{
|
||
|
if (_ppb && SUCCEEDED(SHPropertyBag_ReadStr(_ppb, TEXT("Logo"), _szLogo, ARRAYSIZE(_szLogo))) && _szLogo[0])
|
||
|
{
|
||
|
if (SUCCEEDED(SHPropertyBag_ReadStr(_ppb, TEXT("WideLogo"), _szWideLogo, ARRAYSIZE(_szWideLogo))) && _szWideLogo[0])
|
||
|
PathCombine(_szWideLogo, _szFolder, _szWideLogo); // relative path support
|
||
|
|
||
|
PathCombine(_szLogo, _szFolder, _szLogo); // relative path support
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
TCHAR szFind[MAX_PATH];
|
||
|
|
||
|
for (int i = 0; i < ARRAYSIZE(c_szFolderThumbnailPaths); i++)
|
||
|
{
|
||
|
PathCombine(szFind, _szFolder, c_szFolderThumbnailPaths[i]);
|
||
|
if (PathFileExists(szFind))
|
||
|
{
|
||
|
lstrcpyn(_szLogo, szFind, ARRAYSIZE(_szLogo));
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
LPCTSTR psz = ((cx > 120) && _szWideLogo[0]) ? _szWideLogo : _szLogo;
|
||
|
return *psz ? psz : NULL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFolderExtractImage::RequestAlphaThumbnail()
|
||
|
{
|
||
|
_fAlpha = TRUE;
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFolderExtractImage::GetLocation(LPWSTR pszPath, DWORD cch,
|
||
|
DWORD *pdwPriority, const SIZE *prgSize,
|
||
|
DWORD dwRecClrDepth, DWORD *pdwFlags)
|
||
|
{
|
||
|
lstrcpyn(pszPath, _szFolder, cch);
|
||
|
|
||
|
HRESULT hr = S_OK;
|
||
|
_size = *prgSize;
|
||
|
_dwRecClrDepth = dwRecClrDepth;
|
||
|
_dwExtractFlags = *pdwFlags;
|
||
|
|
||
|
if (pdwFlags)
|
||
|
{
|
||
|
if (*pdwFlags & IEIFLAG_ASYNC)
|
||
|
hr = E_PENDING;
|
||
|
|
||
|
*pdwFlags &= ~IEIFLAG_CACHE; // We handle the caching of this thumbnail inside the folder
|
||
|
*pdwFlags |= IEIFLAG_REFRESH; // We still want to handle the refresh verb
|
||
|
}
|
||
|
|
||
|
if (pdwPriority)
|
||
|
{
|
||
|
_dwPriority = *pdwPriority;
|
||
|
*pdwPriority = 1; // very low
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFolderExtractImage::Extract(HBITMAP *phbm)
|
||
|
{
|
||
|
// Set it to running (only if we're in the not running state).
|
||
|
LONG lResOld = InterlockedCompareExchange(&_lState, IRTIR_TASK_RUNNING, IRTIR_TASK_NOT_RUNNING);
|
||
|
|
||
|
if (lResOld != IRTIR_TASK_NOT_RUNNING)
|
||
|
{
|
||
|
// If we weren't in the not running state, bail.
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
// If we have an extractor, use that.
|
||
|
HRESULT hr = E_FAIL;
|
||
|
hr = _CheckThumbnailCache(phbm);
|
||
|
if (FAILED(hr))
|
||
|
{
|
||
|
LPITEMIDLIST apidlPreviews[MAX_MINIPREVIEWS_COLLECT];
|
||
|
BOOL abTryCached[MAX_MINIPREVIEWS_COLLECT];
|
||
|
UINT cpidlPreviews = 0;
|
||
|
|
||
|
LPCTSTR pszLogo = _GetImagePath(_size.cx);
|
||
|
if (pszLogo)
|
||
|
{
|
||
|
// Don't do the standard mini-previews - we've got a special thumbnail
|
||
|
ATOMICRELEASE(_pExtract);
|
||
|
|
||
|
LPITEMIDLIST pidl;
|
||
|
hr = SHILCreateFromPath(pszLogo, &pidl, NULL);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
LPCITEMIDLIST pidlChild;
|
||
|
IShellFolder* psfLogo;
|
||
|
hr = SHBindToIDListParent(pidl, IID_PPV_ARG(IShellFolder, &psfLogo), &pidlChild);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
hr = _CreateWithMiniPreviews(psfLogo, &pidlChild, NULL, 1, MINIPREVIEW_LAYOUT_1, NULL, phbm);
|
||
|
psfLogo->Release();
|
||
|
}
|
||
|
ILFree(pidl);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
const struct
|
||
|
{
|
||
|
int csidl;
|
||
|
int res;
|
||
|
}
|
||
|
thumblist[] =
|
||
|
{
|
||
|
{CSIDL_PERSONAL, IDI_MYDOCS},
|
||
|
{CSIDL_MYMUSIC, IDI_MYMUSIC},
|
||
|
{CSIDL_MYPICTURES, IDI_MYPICS},
|
||
|
{CSIDL_MYVIDEO, IDI_MYVIDEOS},
|
||
|
{CSIDL_COMMON_DOCUMENTS, IDI_MYDOCS},
|
||
|
{CSIDL_COMMON_MUSIC, IDI_MYMUSIC},
|
||
|
{CSIDL_COMMON_PICTURES, IDI_MYPICS},
|
||
|
{CSIDL_COMMON_VIDEO, IDI_MYVIDEOS}
|
||
|
};
|
||
|
BOOL bFound = FALSE;
|
||
|
|
||
|
for (int i=0; i < ARRAYSIZE(thumblist) && !bFound; i++)
|
||
|
{
|
||
|
TCHAR szPath[MAX_PATH];
|
||
|
SHGetFolderPath(NULL, thumblist[i].csidl, NULL, 0, szPath);
|
||
|
if (!lstrcmp(_szFolder, szPath))
|
||
|
{
|
||
|
// We return failure in this case so that the requestor can do
|
||
|
// the default action.
|
||
|
hr = E_FAIL;
|
||
|
bFound = TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!bFound)
|
||
|
{
|
||
|
// Mini-previews.
|
||
|
IShellImageStore *pDiskCache = NULL;
|
||
|
|
||
|
// It's ok if this fails.
|
||
|
if (!SHRestricted(REST_NOTHUMBNAILCACHE) &&
|
||
|
!SHRegGetBoolUSValue(REGSTR_EXPLORER_ADVANCED, TEXT("DisableThumbnailCache"), 0, FALSE) &&
|
||
|
!(_dwExtractFlags & IEIFLAG_QUALITY))
|
||
|
{
|
||
|
LoadFromFile(CLSID_ShellThumbnailDiskCache, _szFolder, IID_PPV_ARG(IShellImageStore, &pDiskCache));
|
||
|
}
|
||
|
|
||
|
cpidlPreviews = ARRAYSIZE(apidlPreviews);
|
||
|
hr = _FindMiniPreviews(apidlPreviews, abTryCached, &cpidlPreviews);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
if (cpidlPreviews)
|
||
|
{
|
||
|
hr = _CreateWithMiniPreviews(_psf, apidlPreviews, abTryCached, cpidlPreviews, _GetMiniPreviewLayout(_size), pDiskCache, phbm);
|
||
|
FreeMiniPreviewPidls(apidlPreviews, cpidlPreviews);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// We return failure in this case so that the requestor can do
|
||
|
// the default action
|
||
|
hr = E_FAIL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ATOMICRELEASE(pDiskCache);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (SUCCEEDED(hr) && *phbm)
|
||
|
{
|
||
|
_CacheThumbnail(*phbm);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFolderExtractImage::GetClassID(CLSID *pClassID)
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFolderExtractImage::Init(IShellFolder *psf, LPCITEMIDLIST pidl)
|
||
|
{
|
||
|
HRESULT hr = DisplayNameOf(psf, pidl, SHGDN_FORPARSING, _szFolder, ARRAYSIZE(_szFolder));
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
LPITEMIDLIST pidlFolder;
|
||
|
hr = SHGetIDListFromUnk(psf, &pidlFolder);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
hr = SHILCombine(pidlFolder, pidl, &_pidl);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
// hold the _psf for this guy so we can enum
|
||
|
hr = psf->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder2, &_psf));
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
hr = InitNew();
|
||
|
}
|
||
|
}
|
||
|
ILFree(pidlFolder);
|
||
|
}
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
// Not necessary --- IExtractImage::Extract() starts us up.
|
||
|
STDMETHODIMP CFolderExtractImage::Run(void)
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFolderExtractImage::Kill(BOOL fWait)
|
||
|
{
|
||
|
// Try to kill the current subextraction task that's running, if any.
|
||
|
if (_pRun != NULL)
|
||
|
{
|
||
|
_pRun->Kill(fWait);
|
||
|
// If it didn't work, no big deal, we'll complete this subextraction task,
|
||
|
// and bail before starting the next one.
|
||
|
}
|
||
|
|
||
|
// If we're running, set to pending.
|
||
|
LONG lResOld = InterlockedCompareExchange(&_lState, IRTIR_TASK_PENDING, IRTIR_TASK_RUNNING);
|
||
|
if (lResOld == IRTIR_TASK_RUNNING)
|
||
|
{
|
||
|
// We've now set it to pending - ready to die.
|
||
|
return S_OK;
|
||
|
}
|
||
|
else if (lResOld == IRTIR_TASK_PENDING || lResOld == IRTIR_TASK_FINISHED)
|
||
|
{
|
||
|
// We've already been killed.
|
||
|
return S_FALSE;
|
||
|
}
|
||
|
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFolderExtractImage::Suspend(void)
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CFolderExtractImage::Resume(void)
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP_(ULONG) CFolderExtractImage::IsRunning(void)
|
||
|
{
|
||
|
return _lState;
|
||
|
}
|
||
|
|
||
|
HRESULT CFolderExtractImage::_CreateWithMiniPreviews(IShellFolder *psf, const LPCITEMIDLIST *apidlPreviews, BOOL *abTryCached, UINT cpidlPreviews, MINIPREVIEW_LAYOUT uLayout, IShellImageStore *pImageStore, HBITMAP *phBmpThumbnail)
|
||
|
{
|
||
|
*phBmpThumbnail = NULL;
|
||
|
|
||
|
HBITMAP hbmpOld;
|
||
|
HDC hdc;
|
||
|
|
||
|
SIZE sizeOriginal; // Size of the source bitmaps that go into the minipreview.
|
||
|
SIZE sizeFolderBmp; // Size of the folder bmp we use for the background.
|
||
|
SIZE sizeMiniPreview; // The size calculated for the minipreviews
|
||
|
POINT aptOrigins[MAX_MINIPREVIEWS];
|
||
|
RGBQUAD* prgb; // the bits of the destination bitmap.
|
||
|
|
||
|
_GetMiniPreviewLocations(uLayout, _size, &sizeFolderBmp,
|
||
|
aptOrigins, &sizeMiniPreview);
|
||
|
|
||
|
// sizeFolderBmp is the size of the folder background bitmap that we're working with,
|
||
|
// not the size of the final thumbnail.
|
||
|
HRESULT hr = _CreateMainRenderingDC(&hdc, phBmpThumbnail, &hbmpOld, sizeFolderBmp.cx, sizeFolderBmp.cy, &prgb);
|
||
|
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
BOOL fIsAlphaBackground;
|
||
|
hr = _DrawMiniPreviewBackground(hdc, sizeFolderBmp, _fAlpha, &fIsAlphaBackground, prgb);
|
||
|
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
ULONG uPreviewLocation = 0;
|
||
|
|
||
|
// Extract the images for the minipreviews
|
||
|
for (ULONG i = 0 ; i < cpidlPreviews && uPreviewLocation < ARRAYSIZE(aptOrigins) ; i++)
|
||
|
{
|
||
|
BOOL bFoundAlphaImage = FALSE;
|
||
|
|
||
|
// If we've been killed, stop the processing the minipreviews:
|
||
|
// PENDING?, we're now FINISHED.
|
||
|
InterlockedCompareExchange(&_lState, IRTIR_TASK_FINISHED, IRTIR_TASK_PENDING);
|
||
|
|
||
|
if (_lState == IRTIR_TASK_FINISHED)
|
||
|
{
|
||
|
// Get out.
|
||
|
hr = E_FAIL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
HBITMAP hbmpSubs;
|
||
|
BOOL bFoundImage = FALSE;
|
||
|
|
||
|
// Try the image store first
|
||
|
DWORD dwLock;
|
||
|
HRESULT hr2 = (pImageStore && abTryCached[i]) ? pImageStore->Open(STGM_READ, &dwLock) : E_FAIL;
|
||
|
if (SUCCEEDED(hr2))
|
||
|
{
|
||
|
// Get the fullpidl of this guy.
|
||
|
TCHAR szSubPath[MAX_PATH];
|
||
|
if (SUCCEEDED(DisplayNameOf(psf, apidlPreviews[i], SHGDN_INFOLDER | SHGDN_FORPARSING, szSubPath, MAX_PATH)))
|
||
|
{
|
||
|
if (SUCCEEDED(pImageStore->GetEntry(szSubPath, STGM_READ, &hbmpSubs)))
|
||
|
{
|
||
|
bFoundImage = TRUE;
|
||
|
}
|
||
|
}
|
||
|
pImageStore->ReleaseLock(&dwLock);
|
||
|
}
|
||
|
|
||
|
// Resort to calling extractor if the image was not in the cache.
|
||
|
if (!bFoundImage)
|
||
|
{
|
||
|
IExtractImage *peiSub;
|
||
|
hr2 = psf->GetUIObjectOf(NULL, 1, (LPCITEMIDLIST *)&apidlPreviews[i], IID_X_PPV_ARG(IExtractImage, NULL, &peiSub));
|
||
|
if (SUCCEEDED(hr2))
|
||
|
{
|
||
|
|
||
|
// Now extract the image.
|
||
|
DWORD dwPriority = 0;
|
||
|
DWORD dwFlags = IEIFLAG_ORIGSIZE | IEIFLAG_QUALITY;// ORIGSIZE -> preserve aspect ratio
|
||
|
|
||
|
WCHAR szPathBuffer[MAX_PATH];
|
||
|
hr2 = peiSub->GetLocation(szPathBuffer, ARRAYSIZE(szPathBuffer), &dwPriority, &sizeMiniPreview, 24, &dwFlags);
|
||
|
|
||
|
IAlphaThumbnailExtractor *pati;
|
||
|
if (SUCCEEDED(peiSub->QueryInterface(IID_PPV_ARG(IAlphaThumbnailExtractor, &pati))))
|
||
|
{
|
||
|
if (SUCCEEDED(pati->RequestAlphaThumbnail()))
|
||
|
{
|
||
|
bFoundAlphaImage = TRUE;
|
||
|
}
|
||
|
|
||
|
pati->Release();
|
||
|
}
|
||
|
|
||
|
if (SUCCEEDED(hr2))
|
||
|
{
|
||
|
// After we check for IRTIR_TASK_PENDING, but before
|
||
|
// we call peiSub->Extract, it is possible someone calls
|
||
|
// Kill on us.
|
||
|
// Since _pRun will be NULL, we will not kill
|
||
|
// the subtask, but will instead continue and call extract
|
||
|
// on it, and not bail until we try the next subthumbnail.
|
||
|
// Oh well.
|
||
|
// We could add another check here to reduce the window of
|
||
|
// opportunity in which this could happen.
|
||
|
|
||
|
// Try to get an IRunnableTask so that we can stop execution
|
||
|
// of this subtask if necessary.
|
||
|
peiSub->QueryInterface(IID_PPV_ARG(IRunnableTask, &_pRun));
|
||
|
|
||
|
if (SUCCEEDED(peiSub->Extract(&hbmpSubs)))
|
||
|
{
|
||
|
bFoundImage = TRUE;
|
||
|
}
|
||
|
|
||
|
ATOMICRELEASE(_pRun);
|
||
|
}
|
||
|
|
||
|
peiSub->Release();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Add the extracted bitmap to the main one...
|
||
|
if (bFoundImage)
|
||
|
{
|
||
|
// The bitmap will of course need to be resized:
|
||
|
BITMAP rgBitmap;
|
||
|
if (::GetObject((HGDIOBJ)hbmpSubs, sizeof(rgBitmap), &rgBitmap))
|
||
|
{
|
||
|
sizeOriginal.cx = rgBitmap.bmWidth;
|
||
|
sizeOriginal.cy = rgBitmap.bmHeight;
|
||
|
|
||
|
// We need to check if this is really an alpha bitmap. It's possible that the
|
||
|
// extractor said it could generate one, but ended up not being able to.
|
||
|
if (bFoundAlphaImage)
|
||
|
{
|
||
|
RECT rc = {0, 0, rgBitmap.bmWidth, rgBitmap.bmHeight};
|
||
|
bFoundAlphaImage = (rgBitmap.bmBitsPixel == 32) &&
|
||
|
_HasAlpha(rc, rgBitmap.bmWidth, (RGBQUAD*)rgBitmap.bmBits);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Couldn't get the info, oh well, no resize.
|
||
|
// alpha may also be screwed up here, but oh well.
|
||
|
sizeOriginal = sizeMiniPreview;
|
||
|
}
|
||
|
|
||
|
if (SUCCEEDED(_AddBitmap(hdc, hbmpSubs, aptOrigins[uPreviewLocation], sizeMiniPreview, sizeOriginal, bFoundAlphaImage, fIsAlphaBackground, prgb, sizeFolderBmp)))
|
||
|
{
|
||
|
uPreviewLocation++;
|
||
|
}
|
||
|
|
||
|
DeleteObject(hbmpSubs);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!uPreviewLocation)
|
||
|
{
|
||
|
// For whatever reason, we have no mini thumbnails to show, so fail this entire extraction.
|
||
|
hr = E_FAIL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
// Is the requested size one of the sizes of the folder background bitmaps?
|
||
|
// Test against smallest requested dimension, because we're square, and we'll fit into that rectangle
|
||
|
int iSmallestDimension = min(_size.cx, _size.cy);
|
||
|
if ((sizeFolderBmp.cx != iSmallestDimension) || (sizeFolderBmp.cy != iSmallestDimension))
|
||
|
{
|
||
|
// Nope - we need to do some scaling.
|
||
|
// Create another dc and bitmap the size of the requested bitmap
|
||
|
HBITMAP hBmpThumbnailFinal = NULL;
|
||
|
HBITMAP hbmpOld2;
|
||
|
HDC hdcFinal;
|
||
|
RGBQUAD *prgbFinal;
|
||
|
hr = _CreateMainRenderingDC(&hdcFinal, &hBmpThumbnailFinal, &hbmpOld2, iSmallestDimension, iSmallestDimension, &prgbFinal);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
// Now scale it.
|
||
|
if (fIsAlphaBackground)
|
||
|
{
|
||
|
BLENDFUNCTION bf;
|
||
|
bf.BlendOp = AC_SRC_OVER;
|
||
|
bf.SourceConstantAlpha = 255;
|
||
|
bf.AlphaFormat = AC_SRC_ALPHA;
|
||
|
bf.BlendFlags = 0;
|
||
|
if (AlphaBlend(hdcFinal, 0, 0, iSmallestDimension, iSmallestDimension, hdc, 0 ,0, sizeFolderBmp.cx, sizeFolderBmp.cy, bf))
|
||
|
hr = S_OK;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
int iModeSave = SetStretchBltMode(hdcFinal, HALFTONE);
|
||
|
|
||
|
if (StretchBlt(hdcFinal, 0, 0, iSmallestDimension, iSmallestDimension, hdc, 0 ,0, sizeFolderBmp.cx, sizeFolderBmp.cy, SRCCOPY))
|
||
|
hr = S_OK;
|
||
|
|
||
|
SetStretchBltMode(hdcFinal, iModeSave);
|
||
|
}
|
||
|
|
||
|
// Destroy the dc.
|
||
|
_DestroyMainRenderingDC(hdcFinal, hbmpOld2);
|
||
|
|
||
|
// Now do a switcheroo
|
||
|
// Don't need to check for success here. Down below, we'll delete *phBmpThumbnail
|
||
|
// if StretchBlt FAILED - and in that case, *pbBmpThumbnail will be hBmpThumbnailFinal.
|
||
|
DeleteObject(*phBmpThumbnail); // delete this, we don't need it.
|
||
|
*phBmpThumbnail = hBmpThumbnailFinal; // This is the one we want.
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
_DestroyMainRenderingDC(hdc, hbmpOld);
|
||
|
}
|
||
|
|
||
|
|
||
|
if (FAILED(hr) && *phBmpThumbnail) // Something didn't work? Make sure we delete our bmp
|
||
|
{
|
||
|
DeleteObject(*phBmpThumbnail);
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* In/Out: cpidlPreviews - the number of preview items we should look for. Returns the number found.
|
||
|
* number of pidls returned is cpidlPreviews.
|
||
|
* Out: apidlPreviews - array of pidls found. The caller must free them.
|
||
|
*/
|
||
|
HRESULT CFolderExtractImage::_FindMiniPreviews(LPITEMIDLIST apidlPreviews[], BOOL abTryCached[], UINT *pcpidlPreviews)
|
||
|
{
|
||
|
UINT cMaxPreviews = *pcpidlPreviews;
|
||
|
int uNumPreviewsSoFar = 0;
|
||
|
BOOL bKilled = FALSE;
|
||
|
|
||
|
// Make sure our aFileTimes array is the right size...
|
||
|
ASSERT(MAX_MINIPREVIEWS_COLLECT == cMaxPreviews);
|
||
|
|
||
|
*pcpidlPreviews = 0; // start with none in case of failure
|
||
|
|
||
|
IEnumIDList *penum;
|
||
|
if (S_OK == _psf->EnumObjects(NULL, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &penum))
|
||
|
{
|
||
|
FILETIME aFileTimes[MAX_MINIPREVIEWS_COLLECT] = {0};
|
||
|
|
||
|
LPITEMIDLIST pidl;
|
||
|
BOOL bTryCached;
|
||
|
while (S_OK == penum->Next(1, &pidl, NULL))
|
||
|
{
|
||
|
// _IsMiniPreviewCandidate is a potentially expensive operation, so before
|
||
|
// doing it, we'll check to see if anyone has killed us.
|
||
|
|
||
|
// Are we PENDING? Then we're FINISHED.
|
||
|
InterlockedCompareExchange(&_lState, IRTIR_TASK_FINISHED, IRTIR_TASK_PENDING);
|
||
|
|
||
|
// Get out?
|
||
|
bKilled = (_lState == IRTIR_TASK_FINISHED);
|
||
|
|
||
|
if (!bKilled && _IsMiniPreviewCandidate(_psf, pidl, &bTryCached))
|
||
|
{
|
||
|
// Get file time of this guy.
|
||
|
FILETIME ft;
|
||
|
if (SUCCEEDED(GetDateProperty(_psf, pidl, &SCID_WRITETIME, &ft)))
|
||
|
{
|
||
|
for (int i = 0; i < uNumPreviewsSoFar; i++)
|
||
|
{
|
||
|
if (CompareFileTime(&aFileTimes[i], &ft) < 0)
|
||
|
{
|
||
|
int j;
|
||
|
// Put it in this slot. First, move guys down by one.
|
||
|
// No need to copy last guy:
|
||
|
if (uNumPreviewsSoFar == (int)cMaxPreviews)
|
||
|
{
|
||
|
j = (cMaxPreviews - 2);
|
||
|
// And we must free the pidl we're nuking.
|
||
|
ILFree(apidlPreviews[cMaxPreviews - 1]);
|
||
|
apidlPreviews[cMaxPreviews - 1] = NULL;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
j = uNumPreviewsSoFar - 1;
|
||
|
uNumPreviewsSoFar++;
|
||
|
}
|
||
|
|
||
|
for (; j >= i; j--)
|
||
|
{
|
||
|
apidlPreviews[j+1] = apidlPreviews[j];
|
||
|
abTryCached[j+1] = abTryCached[j];
|
||
|
aFileTimes[j+1] = aFileTimes[j];
|
||
|
}
|
||
|
|
||
|
aFileTimes[i] = ft;
|
||
|
apidlPreviews[i] = pidl;
|
||
|
abTryCached[i] = bTryCached;
|
||
|
pidl = NULL; // don't free
|
||
|
break; // for loop
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Did we complete the loop?
|
||
|
if (i == uNumPreviewsSoFar)
|
||
|
{
|
||
|
if (i < (int)cMaxPreviews)
|
||
|
{
|
||
|
// We still have room for more previews, so tack this on at the end.
|
||
|
uNumPreviewsSoFar++;
|
||
|
aFileTimes[i] = ft;
|
||
|
apidlPreviews[i] = pidl;
|
||
|
abTryCached[i] = bTryCached;
|
||
|
pidl = NULL; // don't free below
|
||
|
}
|
||
|
}
|
||
|
|
||
|
*pcpidlPreviews = uNumPreviewsSoFar;
|
||
|
}
|
||
|
}
|
||
|
ILFree(pidl); // NULL pidl OK
|
||
|
|
||
|
if (bKilled)
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
penum->Release();
|
||
|
}
|
||
|
|
||
|
if (bKilled)
|
||
|
{
|
||
|
FreeMiniPreviewPidls(apidlPreviews, *pcpidlPreviews);
|
||
|
*pcpidlPreviews = 0;
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return (uNumPreviewsSoFar > 0) ? S_OK : S_FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
HRESULT CFolderExtractImage::_CreateThumbnailFromIconResource(HBITMAP* phBmpThumbnail, int res)
|
||
|
{
|
||
|
*phBmpThumbnail = NULL;
|
||
|
|
||
|
HBITMAP hbmpOld;
|
||
|
HDC hdc;
|
||
|
RGBQUAD* prgb; // the bits of the destination bitmap.
|
||
|
|
||
|
HRESULT hr = _CreateMainRenderingDC(&hdc, phBmpThumbnail, &hbmpOld, _size.cx, _size.cy, &prgb);
|
||
|
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
HICON hicon = (HICON)LoadImage(HINST_THISDLL, MAKEINTRESOURCE(res), IMAGE_ICON, _size.cx, _size.cy, 0);
|
||
|
|
||
|
if (hicon)
|
||
|
{
|
||
|
RECT rc = { 0, 0, _size.cx + 1, _size.cy + 1};
|
||
|
SHFillRectClr(hdc, &rc, GetSysColor(COLOR_WINDOW));
|
||
|
|
||
|
DrawIconEx(hdc, 0, 0, hicon, _size.cx, _size.cy, 0, NULL, DI_NORMAL);
|
||
|
|
||
|
DestroyIcon(hicon);
|
||
|
hr = S_OK;
|
||
|
}
|
||
|
|
||
|
_DestroyMainRenderingDC(hdc, hbmpOld);
|
||
|
}
|
||
|
|
||
|
if (FAILED(hr) && *phBmpThumbnail)
|
||
|
{
|
||
|
DeleteObject(*phBmpThumbnail);
|
||
|
*phBmpThumbnail = NULL;
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
HRESULT CFolderExtractImage::_CheckThumbnailCache(HBITMAP* phbmp)
|
||
|
{
|
||
|
HRESULT hr = E_FAIL;
|
||
|
|
||
|
if (!SHRestricted(REST_NOTHUMBNAILCACHE) &&
|
||
|
!SHRegGetBoolUSValue(REGSTR_EXPLORER_ADVANCED, TEXT("DisableThumbnailCache"), 0, FALSE) &&
|
||
|
!(_dwExtractFlags & IEIFLAG_QUALITY))
|
||
|
{
|
||
|
IShellImageStore *pDiskCache = NULL;
|
||
|
|
||
|
hr = LoadFromFile(CLSID_ShellThumbnailDiskCache, _szFolder, IID_PPV_ARG(IShellImageStore, &pDiskCache));
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
DWORD dwLock;
|
||
|
|
||
|
hr = pDiskCache->Open(STGM_READ, &dwLock);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
FILETIME ftTimeStamp = {0,0};
|
||
|
|
||
|
hr = GetDateStamp(&ftTimeStamp);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
FILETIME ftTimeStampCache = {0,0};
|
||
|
hr = pDiskCache->IsEntryInStore(FOLDER_GUID, &ftTimeStampCache);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
if (hr == S_OK && (0 == CompareFileTime(&ftTimeStampCache, &ftTimeStamp)))
|
||
|
{
|
||
|
hr = pDiskCache->GetEntry(FOLDER_GUID, STGM_READ, phbmp);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hr = E_FAIL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
pDiskCache->ReleaseLock(&dwLock);
|
||
|
pDiskCache->Close(NULL);
|
||
|
}
|
||
|
pDiskCache->Release();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
TraceMsg(TF_DEFVIEW, "CFolderExtractImage::_CheckThumbnailCache (%s, %x)", _szFolder, hr);
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
void CFolderExtractImage::_CacheThumbnail(HBITMAP hbmp)
|
||
|
{
|
||
|
HRESULT hr = E_UNEXPECTED;
|
||
|
|
||
|
if (!SHRestricted(REST_NOTHUMBNAILCACHE) &&
|
||
|
!SHRegGetBoolUSValue(REGSTR_EXPLORER_ADVANCED, TEXT("DisableThumbnailCache"), 0, FALSE))
|
||
|
{
|
||
|
SIZE sizeThumbnail;
|
||
|
SHGetThumbnailSize(&sizeThumbnail); // Don't cache the mini-thumbnail preview
|
||
|
if (sizeThumbnail.cx == _size.cx && sizeThumbnail.cy == _size.cy)
|
||
|
{
|
||
|
IShellImageStore *pDiskCache = NULL;
|
||
|
|
||
|
hr = LoadFromIDList(CLSID_ShellThumbnailDiskCache, _pidl, IID_PPV_ARG(IShellImageStore, &pDiskCache));
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
DWORD dwLock;
|
||
|
|
||
|
hr = pDiskCache->Open(STGM_READWRITE, &dwLock);
|
||
|
if (hr == STG_E_FILENOTFOUND)
|
||
|
{
|
||
|
if (!IsCopyEngineRunning())
|
||
|
{
|
||
|
hr = pDiskCache->Create(STGM_WRITE, &dwLock);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
FILETIME ftTimeStamp = {0,0};
|
||
|
|
||
|
hr = GetDateStamp(&ftTimeStamp);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
hr = pDiskCache->AddEntry(FOLDER_GUID, &ftTimeStamp, STGM_WRITE, hbmp);
|
||
|
}
|
||
|
pDiskCache->ReleaseLock(&dwLock);
|
||
|
pDiskCache->Close(NULL);
|
||
|
}
|
||
|
pDiskCache->Release();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
TraceMsg(TF_DEFVIEW, "CFolderExtractImage::_CacheThumbnail (%s, %x)", _szFolder, hr);
|
||
|
}
|
||
|
|