windows-nt/Source/XPSP1/NT/shell/explorer/desktop2/sfthost.cpp
2020-09-26 16:20:57 +08:00

4013 lines
112 KiB
C++

#include "stdafx.h"
#include <browseui.h>
#include "sfthost.h"
#include <shellp.h>
#include "startmnu.h"
#define TF_HOST 0x00000010
#define TF_HOSTDD 0x00000040 // drag/drop
#define TF_HOSTPIN 0x00000080 // pin
#define ANIWND_WIDTH 80
#define ANIWND_HEIGHT 50
EXTERN_C void Tray_UnlockStartPane();
//---------BEGIN HACKS OF DEATH -------------
// HACKHACK - desktopp.h and browseui.h both define SHCreateFromDesktop
// What's worse, browseui.h includes desktopp.h! So you have to sneak it
// out in this totally wacky way.
#include <desktopp.h>
#define SHCreateFromDesktop _SHCreateFromDesktop
#include <browseui.h>
//---------END HACKS OF DEATH -------------
//****************************************************************************
//
// Dummy IContextMenu
//
// We use this when we can't get the real IContextMenu for an item.
// If the user pins an object and then deletes the underlying
// file, attempting to get the IContextMenu from the shell will fail,
// but we need something there so we can add the "Remove from this list"
// menu item.
//
// Since this dummy context menu has no state, we can make it a static
// singleton object.
class CEmptyContextMenu
: public IContextMenu
{
public:
// *** IUnknown ***
STDMETHODIMP QueryInterface(REFIID riid, void** ppvObj)
{
static const QITAB qit[] = {
QITABENT(CEmptyContextMenu, IContextMenu),
{ 0 },
};
return QISearch(this, qit, riid, ppvObj);
}
STDMETHODIMP_(ULONG) AddRef(void) { return 3; }
STDMETHODIMP_(ULONG) Release(void) { return 2; }
// *** IContextMenu ***
STDMETHODIMP QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
{
return ResultFromShort(0); // No items added
}
STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO pici)
{
ASSERT(FALSE);
return E_FAIL;
}
STDMETHODIMP GetCommandString(UINT_PTR idCmd, UINT uType, UINT *pwRes, LPSTR pszName, UINT cchMax)
{
return E_INVALIDARG; // no commands; therefore, no command strings!
}
public:
IContextMenu *GetContextMenu()
{
// Don't need to AddRef since we are a static object
return this;
}
};
static CEmptyContextMenu s_EmptyContextMenu;
//****************************************************************************
#define WC_SFTBARHOST TEXT("DesktopSFTBarHost")
BOOL GetFileCreationTime(LPCTSTR pszFile, FILETIME *pftCreate)
{
WIN32_FILE_ATTRIBUTE_DATA wfad;
BOOL fRc = GetFileAttributesEx(pszFile, GetFileExInfoStandard, &wfad);
if (fRc)
{
*pftCreate = wfad.ftCreationTime;
}
return fRc;
}
// {2A1339D7-523C-4E21-80D3-30C97B0698D2}
const CLSID TOID_SFTBarHostBackgroundEnum = {
0x2A1339D7, 0x523C, 0x4E21,
{ 0x80, 0xD3, 0x30, 0xC9, 0x7B, 0x06, 0x98, 0xD2} };
BOOL SFTBarHost::Register()
{
WNDCLASS wc;
wc.style = 0;
wc.lpfnWndProc = _WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = sizeof(void *);
wc.hInstance = _Module.GetModuleInstance();
wc.hIcon = 0;
// We specify a cursor so the OOBE window gets something
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = 0;
wc.lpszClassName = WC_SFTBARHOST;
return ::SHRegisterClass(&wc);
}
BOOL SFTBarHost::Unregister()
{
return ::UnregisterClass(WC_SFTBARHOST, _Module.GetModuleInstance());
}
LRESULT CALLBACK SFTBarHost::_WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
SFTBarHost *self = reinterpret_cast<SFTBarHost *>(GetWindowPtr0(hwnd));
if (uMsg == WM_NCCREATE)
{
return _OnNcCreate(hwnd, uMsg, wParam, lParam);
}
else if (self)
{
#define HANDLE_SFT_MESSAGE(wm, fn) case wm: return self->fn(hwnd, uMsg, wParam, lParam)
switch (uMsg)
{
HANDLE_SFT_MESSAGE(WM_CREATE, _OnCreate);
HANDLE_SFT_MESSAGE(WM_DESTROY, _OnDestroy);
HANDLE_SFT_MESSAGE(WM_NCDESTROY, _OnNcDestroy);
HANDLE_SFT_MESSAGE(WM_NOTIFY, _OnNotify);
HANDLE_SFT_MESSAGE(WM_SIZE, _OnSize);
HANDLE_SFT_MESSAGE(WM_ERASEBKGND, _OnEraseBackground);
HANDLE_SFT_MESSAGE(WM_CONTEXTMENU, _OnContextMenu);
HANDLE_SFT_MESSAGE(WM_CTLCOLORSTATIC,_OnCtlColorStatic);
HANDLE_SFT_MESSAGE(WM_TIMER, _OnTimer);
HANDLE_SFT_MESSAGE(WM_SETFOCUS, _OnSetFocus);
HANDLE_SFT_MESSAGE(WM_INITMENUPOPUP,_OnMenuMessage);
HANDLE_SFT_MESSAGE(WM_DRAWITEM, _OnMenuMessage);
HANDLE_SFT_MESSAGE(WM_MENUCHAR, _OnMenuMessage);
HANDLE_SFT_MESSAGE(WM_MEASUREITEM, _OnMenuMessage);
HANDLE_SFT_MESSAGE(WM_SYSCOLORCHANGE, _OnSysColorChange);
HANDLE_SFT_MESSAGE(WM_DISPLAYCHANGE, _OnForwardMessage);
HANDLE_SFT_MESSAGE(WM_SETTINGCHANGE, _OnForwardMessage);
HANDLE_SFT_MESSAGE(WM_UPDATEUISTATE, _OnUpdateUIState);
HANDLE_SFT_MESSAGE(SFTBM_REPOPULATE,_OnRepopulate);
HANDLE_SFT_MESSAGE(SFTBM_CHANGENOTIFY+0,_OnChangeNotify);
HANDLE_SFT_MESSAGE(SFTBM_CHANGENOTIFY+1,_OnChangeNotify);
HANDLE_SFT_MESSAGE(SFTBM_CHANGENOTIFY+2,_OnChangeNotify);
HANDLE_SFT_MESSAGE(SFTBM_CHANGENOTIFY+3,_OnChangeNotify);
HANDLE_SFT_MESSAGE(SFTBM_CHANGENOTIFY+4,_OnChangeNotify);
HANDLE_SFT_MESSAGE(SFTBM_CHANGENOTIFY+5,_OnChangeNotify);
HANDLE_SFT_MESSAGE(SFTBM_CHANGENOTIFY+6,_OnChangeNotify);
HANDLE_SFT_MESSAGE(SFTBM_CHANGENOTIFY+7,_OnChangeNotify);
HANDLE_SFT_MESSAGE(SFTBM_REFRESH, _OnRefresh);
HANDLE_SFT_MESSAGE(SFTBM_CASCADE, _OnCascade);
HANDLE_SFT_MESSAGE(SFTBM_ICONUPDATE, _OnIconUpdate);
}
// If this assert fires, you need to add more
// HANDLE_SFT_MESSAGE(SFTBM_CHANGENOTIFY+... entries.
COMPILETIME_ASSERT(SFTHOST_MAXNOTIFY == 8);
#undef HANDLE_SFT_MESSAGE
return self->OnWndProc(hwnd, uMsg, wParam, lParam);
}
return ::DefWindowProc(hwnd, uMsg, wParam, lParam);
}
LRESULT SFTBarHost::_OnNcCreate(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
SMPANEDATA *pspld = PaneDataFromCreateStruct(lParam);
SFTBarHost *self = NULL;
switch (pspld->iPartId)
{
case SPP_PROGLIST:
self = ByUsage_CreateInstance();
break;
case SPP_PLACESLIST:
self = SpecList_CreateInstance();
break;
default:
TraceMsg(TF_ERROR, "Unknown panetype %d", pspld->iPartId);
}
if (self)
{
SetWindowPtr0(hwnd, self);
self->_hwnd = hwnd;
self->_hTheme = pspld->hTheme;
if (FAILED(self->Initialize()))
{
TraceMsg(TF_ERROR, "SFTBarHost::NcCreate Initialize call failed");
return FALSE;
}
return ::DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return FALSE;
}
//
// The tile height is max(imagelist height, text height) + some margin
// The margin is "scientifically computed" to be the value that looks
// reasonably close to the bitmaps the designers gave us.
//
void SFTBarHost::_ComputeTileMetrics()
{
int cyTile = _cyIcon;
HDC hdc = GetDC(_hwndList);
if (hdc)
{
// SOMEDAY - get this to play friendly with themes
HFONT hf = GetWindowFont(_hwndList);
HFONT hfPrev = SelectFont(hdc, hf);
SIZE siz;
if (GetTextExtentPoint(hdc, TEXT("0"), 1, &siz))
{
if (_CanHaveSubtitles())
{
// Reserve space for the subtitle too
siz.cy *= 2;
}
if (cyTile < siz.cy)
cyTile = siz.cy;
}
SelectFont(hdc, hfPrev);
ReleaseDC(_hwndList, hdc);
}
// Listview draws text at left margin + icon + edge
_cxIndent = _cxMargin + _cxIcon + GetSystemMetrics(SM_CXEDGE);
_cyTile = cyTile + (4 * _cyMargin) + _cyTilePadding;
}
void SFTBarHost::_SetTileWidth(int cxTile)
{
LVTILEVIEWINFO tvi;
tvi.cbSize = sizeof(tvi);
tvi.dwMask = LVTVIM_TILESIZE | LVTVIM_COLUMNS;
tvi.dwFlags = LVTVIF_FIXEDSIZE;
// If we support cascading, then reserve space for the cascade arrows
if (_dwFlags & HOSTF_CASCADEMENU)
{
// WARNING! _OnLVItemPostPaint uses these margins
tvi.dwMask |= LVTVIM_LABELMARGIN;
tvi.rcLabelMargin.left = 0;
tvi.rcLabelMargin.top = 0;
tvi.rcLabelMargin.right = _cxMarlett;
tvi.rcLabelMargin.bottom = 0;
}
// Reserve space for subtitles if necessary
tvi.cLines = _CanHaveSubtitles() ? 1 : 0;
// _cyTile has the padding into account, but we want each item to be the height without padding
tvi.sizeTile.cy = _cyTile - _cyTilePadding;
tvi.sizeTile.cx = cxTile;
ListView_SetTileViewInfo(_hwndList, &tvi);
_cxTile = cxTile;
}
LRESULT SFTBarHost::_OnSize(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if (_hwndList)
{
SIZE sizeClient = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
sizeClient.cx -= (_margins.cxLeftWidth + _margins.cxRightWidth);
sizeClient.cy -= (_margins.cyTopHeight + _margins.cyBottomHeight);
SetWindowPos(_hwndList, NULL, _margins.cxLeftWidth, _margins.cyTopHeight,
sizeClient.cx, sizeClient.cy,
SWP_NOZORDER | SWP_NOOWNERZORDER);
_SetTileWidth(sizeClient.cx);
if (HasDynamicContent())
{
_InternalRepopulateList();
}
}
return 0;
}
LRESULT SFTBarHost::_OnSysColorChange(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// if we're in unthemed mode, then we need to update our colors
if (!_hTheme)
{
ListView_SetTextColor(_hwndList, GetSysColor(COLOR_MENUTEXT));
_clrHot = GetSysColor(COLOR_MENUTEXT);
_clrBG = GetSysColor(COLOR_MENU);
_clrSubtitle = CLR_NONE;
ListView_SetBkColor(_hwndList, _clrBG);
ListView_SetTextBkColor(_hwndList, _clrBG);
}
return _OnForwardMessage(hwnd, uMsg, wParam, lParam);
}
LRESULT SFTBarHost::_OnCtlColorStatic(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// Use the same colors as the listview itself.
HDC hdc = GET_WM_CTLCOLOR_HDC(wParam, lParam, uMsg);
SetTextColor(hdc, ListView_GetTextColor(_hwndList));
COLORREF clrBk = ListView_GetTextBkColor(_hwndList);
if (clrBk == CLR_NONE)
{
// The animate control really wants to get a text background color.
// It doesn't support transparency.
if (GET_WM_CTLCOLOR_HWND(wParam, lParam, uMsg) == _hwndAni)
{
if (_hTheme)
{
if (!_hBrushAni)
{
// We need to paint the theme background in a bitmap and use that
// to create a brush for the background of the flashlight animation
RECT rcClient;
GetClientRect(hwnd, &rcClient);
int x = (RECTWIDTH(rcClient) - ANIWND_WIDTH)/2; // IDA_SEARCH is ANIWND_WIDTH pix wide
int y = (RECTHEIGHT(rcClient) - ANIWND_HEIGHT)/2; // IDA_SEARCH is ANIWND_HEIGHT pix tall
RECT rc;
rc.top = y;
rc.bottom = y + ANIWND_HEIGHT;
rc.left = x;
rc.right = x + ANIWND_WIDTH;
HDC hdcBMP = CreateCompatibleDC(hdc);
HBITMAP hbmp = CreateCompatibleBitmap(hdc, ANIWND_WIDTH, ANIWND_HEIGHT);
POINT pt = {0, 0};
// Offset the viewport so that DrawThemeBackground draws the part that we care about
// at the right place
OffsetViewportOrgEx(hdcBMP, -x, -y, &pt);
SelectObject(hdcBMP, hbmp);
DrawThemeBackground(_hTheme, hdcBMP, _iThemePart, 0, &rcClient, 0);
// Our bitmap is now ready!
_hBrushAni = CreatePatternBrush(hbmp);
// Cleanup
SelectObject(hdcBMP, NULL);
DeleteObject(hbmp);
DeleteObject(hdcBMP);
}
return (LRESULT)_hBrushAni;
}
else
{
return (LRESULT)GetSysColorBrush(COLOR_MENU);
}
}
SetBkMode(hdc, TRANSPARENT);
return (LRESULT)GetStockBrush(HOLLOW_BRUSH);
}
else
{
return (LRESULT)GetSysColorBrush(COLOR_MENU);
}
}
//
// Appends the PaneItem to _dpaEnum, or deletes it (and nulls it out)
// if unable to append.
//
int SFTBarHost::_AppendEnumPaneItem(PaneItem *pitem)
{
int iItem = _dpaEnumNew.AppendPtr(pitem);
if (iItem < 0)
{
delete pitem;
iItem = -1;
}
return iItem;
}
BOOL SFTBarHost::AddItem(PaneItem *pitem, IShellFolder *psf, LPCITEMIDLIST pidlChild)
{
BOOL fSuccess = FALSE;
ASSERT(_fEnumerating);
if (_AppendEnumPaneItem(pitem) >= 0)
{
fSuccess = TRUE;
}
return fSuccess;
}
void SFTBarHost::_RepositionItems()
{
DEBUG_CODE(_fListUnstable++);
int iItem;
for (iItem = ListView_GetItemCount(_hwndList) - 1; iItem >= 0; iItem--)
{
PaneItem *pitem = _GetItemFromLV(iItem);
if (pitem)
{
POINT pt;
_ComputeListViewItemPosition(pitem->_iPos, &pt);
ListView_SetItemPosition(_hwndList, iItem, pt.x, pt.y);
}
}
DEBUG_CODE(_fListUnstable--);
}
int SFTBarHost::AddImage(HICON hIcon)
{
int iIcon = -1;
if (_IsPrivateImageList())
{
iIcon = ImageList_AddIcon(_himl, hIcon);
}
return iIcon;
}
//
// pvData = the window to receive the icon
// pvHint = pitem whose icon we just extracted
// iIconIndex = the icon we got
//
void SFTBarHost::SetIconAsync(LPCITEMIDLIST pidl, LPVOID pvData, LPVOID pvHint, INT iIconIndex, INT iOpenIconIndex)
{
HWND hwnd = (HWND)pvData;
if (IsWindow(hwnd))
{
PostMessage(hwnd, SFTBM_ICONUPDATE, iIconIndex, (LPARAM)pvHint);
}
}
//
// wParam = icon index
// lParam = pitem to update
//
LRESULT SFTBarHost::_OnIconUpdate(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
//
// Do not dereference lParam (pitem) until we are sure it is valid.
//
LVFINDINFO fi;
LVITEM lvi;
fi.flags = LVFI_PARAM;
fi.lParam = lParam;
lvi.iItem = ListView_FindItem(_hwndList, -1, &fi);
if (lvi.iItem >= 0)
{
lvi.mask = LVIF_IMAGE;
lvi.iSubItem = 0;
lvi.iImage = (int)wParam;
ListView_SetItem(_hwndList, &lvi);
// Now, we need to go update our cached bitmap version of the start menu.
_SendNotify(_hwnd, SMN_NEEDREPAINT, NULL);
}
return 0;
}
// An over-ridable method to let client direct an item at a particular image
int SFTBarHost::AddImageForItem(PaneItem *pitem, IShellFolder *psf, LPCITEMIDLIST pidl, int iPos)
{
if (_IsPrivateImageList())
{
return _ExtractImageForItem(pitem, psf, pidl);
}
else
{
// system image list: Make the shell do the work.
int iIndex;
SHMapIDListToImageListIndexAsync(_psched, psf, pidl, 0, SetIconAsync, _hwnd, pitem, &iIndex, NULL);
return iIndex;
}
}
HICON _IconOf(IShellFolder *psf, LPCITEMIDLIST pidl, int cxIcon)
{
HRESULT hr;
HICON hicoLarge = NULL, hicoSmall = NULL;
IExtractIcon *pxi;
hr = psf->GetUIObjectOf(NULL, 1, &pidl, IID_PPV_ARG_NULL(IExtractIcon, &pxi));
if (SUCCEEDED(hr))
{
TCHAR szPath[MAX_PATH];
int iIndex;
UINT uiFlags;
hr = pxi->GetIconLocation(0, szPath, ARRAYSIZE(szPath), &iIndex, &uiFlags);
// S_FALSE means "Please use the generic document icon"
if (hr == S_FALSE)
{
lstrcpyn(szPath, TEXT("shell32.dll"), ARRAYSIZE(szPath));
iIndex = II_DOCNOASSOC;
hr = S_OK;
}
if (SUCCEEDED(hr))
{
// Even though we don't care about the small icon, we have to
// ask for it anyway because some people fault on NULL.
hr = pxi->Extract(szPath, iIndex, &hicoLarge, &hicoSmall, cxIcon);
// S_FALSE means "I am too lazy to extract the icon myself.
// You do it for me."
if (hr == S_FALSE)
{
hr = SHDefExtractIcon(szPath, iIndex, uiFlags, &hicoLarge, &hicoSmall, cxIcon);
}
}
pxi->Release();
}
// If we can't get an icon (e.g., object is on a slow link),
// then use a generic folder or generic document, as appropriate.
if (FAILED(hr))
{
SFGAOF attr = SFGAO_FOLDER;
int iIndex;
if (SUCCEEDED(psf->GetAttributesOf(1, &pidl, &attr)) &&
(attr & SFGAO_FOLDER))
{
iIndex = II_FOLDER;
}
else
{
iIndex = II_DOCNOASSOC;
}
hr = SHDefExtractIcon(TEXT("shell32.dll"), iIndex, 0, &hicoLarge, &hicoSmall, cxIcon);
}
// Finally! we have an icon or have exhausted all attempts at getting
// one. If we got one, go add it and clean up.
if (hicoSmall)
DestroyIcon(hicoSmall);
return hicoLarge;
}
int SFTBarHost::_ExtractImageForItem(PaneItem *pitem, IShellFolder *psf, LPCITEMIDLIST pidl)
{
int iIcon = -1; // assume no icon
HICON hIcon = _IconOf(psf, pidl, _cxIcon);
if (hIcon)
{
iIcon = AddImage(hIcon);
DestroyIcon(hIcon);
}
return iIcon;
}
//
// There are two sets of numbers that keep track of items. Sorry.
// (I tried to reduce it to one, but things got hairy.)
//
// 1. Position numbers. Separators occupy a position number.
// 2. Item numbers (listview). Separators do not consume an item number.
//
// Example:
//
// iPos iItem
//
// A 0 0
// B 1 1
// ---- 2 N/A
// C 3 2
// ---- 4 N/A
// D 5 3
//
// _rgiSep[] = { 2, 4 };
//
// _PosToItemNo and _ItemNoToPos do the conversion.
int SFTBarHost::_PosToItemNo(int iPos)
{
// Subtract out the slots occupied by separators.
int iItem = iPos;
for (int i = 0; i < _cSep && _rgiSep[i] < iPos; i++)
{
iItem--;
}
return iItem;
}
int SFTBarHost::_ItemNoToPos(int iItem)
{
// Add in the slots occupied by separators.
int iPos = iItem;
for (int i = 0; i < _cSep && _rgiSep[i] <= iPos; i++)
{
iPos++;
}
return iPos;
}
void SFTBarHost::_ComputeListViewItemPosition(int iItem, POINT *pptOut)
{
// WARNING! _InternalRepopulateList uses an incremental version of this
// algorithm. Keep the two in sync!
ASSERT(_cyTilePadding >= 0);
int y = iItem * _cyTile;
// Adjust for all the separators in the list
for (int i = 0; i < _cSep; i++)
{
if (_rgiSep[i] < iItem)
{
y = y - _cyTile + _cySepTile;
}
}
pptOut->x = _cxMargin;
pptOut->y = y;
}
int SFTBarHost::_InsertListViewItem(int iPos, PaneItem *pitem)
{
ASSERT(pitem);
int iItem = -1;
IShellFolder *psf = NULL;
LPCITEMIDLIST pidl = NULL;
LVITEM lvi;
lvi.pszText = NULL;
lvi.mask = 0;
// If necessary, tell listview that we want to use column 1
// as the subtitle.
if (_iconsize == ICONSIZE_LARGE && pitem->HasSubtitle())
{
const static UINT One = 1;
lvi.mask = LVIF_COLUMNS;
lvi.cColumns = 1;
lvi.puColumns = const_cast<UINT*>(&One);
}
ASSERT(!pitem->IsSeparator());
lvi.mask |= LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
if (FAILED(GetFolderAndPidl(pitem, &psf, &pidl)))
{
goto exit;
}
if (lvi.mask & LVIF_IMAGE)
{
lvi.iImage = AddImageForItem(pitem, psf, pidl, iPos);
}
if (lvi.mask & LVIF_TEXT)
{
if (_iconsize == ICONSIZE_SMALL && pitem->HasSubtitle())
{
lvi.pszText = SubtitleOfItem(pitem, psf, pidl);
}
else
{
lvi.pszText = DisplayNameOfItem(pitem, psf, pidl, SHGDN_NORMAL);
}
if (!lvi.pszText)
{
goto exit;
}
}
lvi.iItem = iPos;
lvi.iSubItem = 0;
lvi.lParam = reinterpret_cast<LPARAM>(pitem);
iItem = ListView_InsertItem(_hwndList, &lvi);
// If the item has a subtitle, add it.
// If this fails, don't worry. The subtitle is just a fluffy bonus thing.
if (iItem >= 0 && (lvi.mask & LVIF_COLUMNS))
{
lvi.iItem = iItem;
lvi.iSubItem = 1;
lvi.mask = LVIF_TEXT;
SHFree(lvi.pszText);
lvi.pszText = SubtitleOfItem(pitem, psf, pidl);
if (lvi.pszText)
{
ListView_SetItem(_hwndList, &lvi);
}
}
exit:
ATOMICRELEASE(psf);
SHFree(lvi.pszText);
return iItem;
}
// Add items to our view, or at least as many as will fit
void SFTBarHost::_RepopulateList()
{
//
// Kill the async enum animation now that we're ready
//
if (_idtAni)
{
KillTimer(_hwnd, _idtAni);
_idtAni = 0;
}
if (_hwndAni)
{
if (_hBrushAni)
{
DeleteObject(_hBrushAni);
_hBrushAni = NULL;
}
DestroyWindow(_hwndAni);
_hwndAni = NULL;
}
// Let's see if anything changed
BOOL fChanged = FALSE;
if (_fForceChange)
{
_fForceChange = FALSE;
fChanged = TRUE;
}
else if (_dpaEnum.GetPtrCount() == _dpaEnumNew.GetPtrCount())
{
int iMax = _dpaEnum.GetPtrCount();
int i;
for (i=0; i<iMax; i++)
{
if (!_dpaEnum.FastGetPtr(i)->IsEqual(_dpaEnumNew.FastGetPtr(i)))
{
fChanged = TRUE;
break;
}
}
}
else
{
fChanged = TRUE;
}
// No need to do any real work if nothing changed.
if (fChanged)
{
// Now move the _dpaEnumNew to _dpaEnum
// Clear out the old DPA, we don't need it anymore
_dpaEnum.EnumCallbackEx(PaneItem::DPAEnumCallback, (void *)NULL);
_dpaEnum.DeleteAllPtrs();
// switch DPAs now
CDPA<PaneItem> dpaTemp = _dpaEnum;
_dpaEnum = _dpaEnumNew;
_dpaEnumNew = dpaTemp;
_InternalRepopulateList();
}
else
{
// Clear out the new DPA, we don't need it anymore
_dpaEnumNew.EnumCallbackEx(PaneItem::DPAEnumCallback, (void *)NULL);
_dpaEnumNew.DeleteAllPtrs();
}
_fNeedsRepopulate = FALSE;
}
// The internal version is when we decide to repopulate on our own,
// not at the prompting of the background thread. (Therefore, we
// don't nuke the animation.)
void SFTBarHost::_InternalRepopulateList()
{
//
// Start with a clean slate.
//
ListView_DeleteAllItems(_hwndList);
if (_IsPrivateImageList())
{
ImageList_RemoveAll(_himl);
}
int cPinned = 0;
int cNormal = 0;
_DebugConsistencyCheck();
SetWindowRedraw(_hwndList, FALSE);
DEBUG_CODE(_fPopulating++);
//
// To populate the list, we toss the pinned items at the top,
// then let the enumerated items flow beneath them.
//
// Separator "items" don't get added to the listview. They
// are added to the special "separators list".
//
// WARNING! We are computing incrementally the same values as
// _ComputeListViewItemPosition. Keep the two in sync.
//
int iPos; // the slot we are trying to fill
int iEnum; // the item index we will fill it from
int y = 0; // where the next item should be placed
BOOL fSepSeen = FALSE; // have we seen a separator yet?
PaneItem *pitem; // the item that will fill it
_cSep = 0; // no separators (yet)
RECT rc;
GetClientRect(_hwndList, &rc);
//
// Subtract out the bonus separator used by SPP_PROGLIST
//
if (_iThemePart == SPP_PROGLIST)
{
rc.bottom -= _cySep;
}
// Note that the loop control must be a _dpaEnum.GetPtr(), not a
// _dpaEnum.FastGetPtr(), because iEnum can go past the end of the
// array if we do't have enough items to fill the view.
//
//
// The "while" condition is "there is room for another non-separator
// item and there are items remaining in the enumeration".
BOOL fCheckMaxLength = HasDynamicContent();
for (iPos = iEnum = 0;
(pitem = _dpaEnum.GetPtr(iEnum)) != NULL;
iEnum++)
{
if (fCheckMaxLength)
{
if (y + _cyTile > rc.bottom)
{
break;
}
// Once we hit a separator, check if we satisfied the number
// of normal items. We have to wait until a separator is
// hit, because _cNormalDesired can be zero; otherwise we
// would end up stopping before adding even the pinned items!
if (fSepSeen && cNormal >= _cNormalDesired)
{
break;
}
}
#ifdef DEBUG
// Make sure that we are in sync with _ComputeListViewItemPosition
POINT pt;
_ComputeListViewItemPosition(iPos, &pt);
ASSERT(pt.x == _cxMargin);
ASSERT(pt.y == y);
#endif
if (pitem->IsSeparator())
{
fSepSeen = TRUE;
// Add the separator, but only if it actually separate something.
// If this EVAL fires, it means somebody added a separator
// and MAX_SEPARATORS needs to be increased
if (iPos > 0 && EVAL(_cSep < ARRAYSIZE(_rgiSep)))
{
_rgiSep[_cSep++] = iPos++;
y += _cySepTile;
}
}
else
{
if (_InsertListViewItem(iPos, pitem) >= 0)
{
pitem->_iPos = iPos++;
y += _cyTile;
if (pitem->IsPinned())
{
cPinned++;
}
else
{
cNormal++;
}
}
}
}
//
// If the last item was a separator, then delete it
// since it's not actually separating anything.
//
if (_cSep && _rgiSep[_cSep-1] == iPos - 1)
{
_cSep--;
}
_cPinned = cPinned;
//
// Now put the items where they belong.
//
_RepositionItems();
DEBUG_CODE(_fPopulating--);
SetWindowRedraw(_hwndList, TRUE);
// Now, we need to go update our cached bitmap version of the start menu.
_SendNotify(_hwnd, SMN_NEEDREPAINT, NULL);
_DebugConsistencyCheck();
}
LRESULT SFTBarHost::_OnCreate(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
RECT rc;
GetClientRect(_hwnd, &rc);
if (_hTheme)
{
GetThemeMargins(_hTheme, NULL, _iThemePart, 0, TMT_CONTENTMARGINS, &rc, &_margins);
}
else
{
_margins.cyTopHeight = 2*GetSystemMetrics(SM_CXEDGE);
_margins.cxLeftWidth = 2*GetSystemMetrics(SM_CXEDGE);
_margins.cxRightWidth = 2*GetSystemMetrics(SM_CXEDGE);
}
//
// Now to create the listview.
//
DWORD dwStyle = WS_CHILD | WS_VISIBLE |
WS_CLIPCHILDREN | WS_CLIPSIBLINGS |
// Do not set WS_TABSTOP; SFTBarHost handles tabbing
LVS_LIST |
LVS_SINGLESEL |
LVS_NOSCROLL |
LVS_SHAREIMAGELISTS;
if (_dwFlags & HOSTF_CANRENAME)
{
dwStyle |= LVS_EDITLABELS;
}
DWORD dwExStyle = 0;
_hwndList = CreateWindowEx(dwExStyle, WC_LISTVIEW, NULL, dwStyle,
_margins.cxLeftWidth, _margins.cyTopHeight, rc.right, rc.bottom, // no point in being too exact, we'll be resized later
_hwnd, NULL,
_Module.GetModuleInstance(), NULL);
if (!_hwndList)
return -1;
//
// Don't freak out if this fails. It just means that the accessibility
// stuff won't be perfect.
//
SetAccessibleSubclassWindow(_hwndList);
//
// Create two dummy columns. We will never display them, but they
// are necessary so that we have someplace to put our subtitle.
//
LVCOLUMN lvc;
lvc.mask = LVCF_WIDTH;
lvc.cx = 1;
ListView_InsertColumn(_hwndList, 0, &lvc);
ListView_InsertColumn(_hwndList, 1, &lvc);
//
// If we are topmost, then force the tooltip topmost, too.
// Otherwise we end up covering our own tooltip!
//
if (GetWindowExStyle(GetAncestor(_hwnd, GA_ROOT)) & WS_EX_TOPMOST)
{
HWND hwndTT = ListView_GetToolTips(_hwndList);
if (hwndTT)
{
SetWindowPos(hwndTT, HWND_TOPMOST, 0, 0, 0, 0,
SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
}
}
// Must do Marlett after doing the listview font, because we base the
// Marlett font metrics on the listview font metrics (so they match)
if (_dwFlags & HOSTF_CASCADEMENU)
{
if (!_CreateMarlett())
return -1;
}
// We can survive if these objects fail to be created
CoCreateInstance(CLSID_DragDropHelper, NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARG(IDropTargetHelper, &_pdth));
CoCreateInstance(CLSID_DragDropHelper, NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARG(IDragSourceHelper, &_pdsh));
//
// If this fails, no big whoop - you just don't get
// drag/drop, boo hoo.
//
RegisterDragDrop(_hwndList, this);
if (!_dpaEnum.Create(4))
return -1;
if (!_dpaEnumNew.Create(4))
return -1;
//-------------------------
// Imagelist goo
int iIconSize = ReadIconSize();
Shell_GetImageLists(iIconSize ? &_himl : NULL, iIconSize ? NULL : &_himl);
if (!_himl)
return -1;
// Preload values in case GetIconSize barfs
_cxIcon = GetSystemMetrics(iIconSize ? SM_CXICON : SM_CXSMICON);
_cyIcon = GetSystemMetrics(iIconSize ? SM_CYICON : SM_CYSMICON);
ImageList_GetIconSize(_himl, &_cxIcon, &_cyIcon);
//
// If we asked for the MEDIUM-sized icons, then create the real
// image list based on the system image list.
//
_iconsize = (ICONSIZE)iIconSize;
if (_iconsize == ICONSIZE_MEDIUM)
{
// These upcoming computations rely on the fact that ICONSIZE_LARGE
// and ICONSIZE_MEDIUM are both nonzero so when we fetched the icon
// sizes for ICONSIZE_MEDIUM, we got SM_CXICON (large).
COMPILETIME_ASSERT(ICONSIZE_LARGE && ICONSIZE_MEDIUM);
// SM_CXICON is the size of shell large icons. SM_CXSMICON is *not*
// the size of shell small icons! It is the size of caption small
// icons. Shell small icons are always 50% of shell large icons.
// We want to be halfway between shell small (50%) and shell
// large (100%); i.e., we want 75%.
_cxIcon = _cxIcon * 3 / 4;
_cyIcon = _cyIcon * 3 / 4;
//
// When the user is in Large Icon mode, we end up choosing 36x36
// (halfway between 24x24 and 48x48), but there is no 36x36 icon
// in the icon resource. But we do have a 32, which is close
// enough. (If we didn't do this, then the 36x36 icon would be
// the 32x32 icon stretched, which looks ugly.)
//
// So any square icon in the range 28..36 we round to 32.
//
if (_cxIcon == _cyIcon && _cxIcon >= 28 && _cxIcon <= 36)
{
_cxIcon = _cyIcon = 32;
}
// It is critical that we overwrite _himl even on failure, so our
// destructor doesn't try to destroy a system image list!
_himl = ImageList_Create(_cxIcon, _cyIcon, ImageList_GetFlags(_himl), 8, 2);
if (!_himl)
{
return -1;
}
}
ListView_SetImageList(_hwndList, _himl, LVSIL_NORMAL);
// Register for SHCNE_UPDATEIMAGE so we know when to reload our icons
_RegisterNotify(SFTHOST_HOSTNOTIFY_UPDATEIMAGE, SHCNE_UPDATEIMAGE, NULL, FALSE);
//-------------------------
_cxMargin = GetSystemMetrics(SM_CXEDGE);
_cyMargin = GetSystemMetrics(SM_CYEDGE);
_cyTilePadding = 0;
_ComputeTileMetrics();
//
// In the themed case, the designers want a narrow separator.
// In the nonthemed case, we need a fat separator because we need
// to draw an etch (which requires two pixels).
//
if (_hTheme)
{
SIZE siz={0};
GetThemePartSize(_hTheme, NULL, _iThemePartSep, 0, NULL, TS_TRUE, &siz);
_cySep = siz.cy;
}
else
{
_cySep = GetSystemMetrics(SM_CYEDGE);
}
_cySepTile = 4 * _cySep;
ASSERT(rc.left == 0 && rc.top == 0); // Should still be a client rectangle
_SetTileWidth(rc.right); // so rc.right = RCWIDTH and rc.bottom = RCHEIGHT
// In tile view, full-row-select really means full-tile-select
DWORD dwLvExStyle = LVS_EX_INFOTIP |
LVS_EX_FULLROWSELECT;
if (!GetSystemMetrics(SM_REMOTESESSION))
{
dwLvExStyle |= LVS_EX_DOUBLEBUFFER;
}
ListView_SetExtendedListViewStyleEx(_hwndList, dwLvExStyle,
dwLvExStyle);
if (!_hTheme)
{
ListView_SetTextColor(_hwndList, GetSysColor(COLOR_MENUTEXT));
_clrHot = GetSysColor(COLOR_MENUTEXT);
_clrBG = GetSysColor(COLOR_MENU); // default color for no theme case
_clrSubtitle = CLR_NONE;
}
else
{
COLORREF clrText;
GetThemeColor(_hTheme, _iThemePart, 0, TMT_HOTTRACKING, &_clrHot); // todo - use state
GetThemeColor(_hTheme, _iThemePart, 0, TMT_CAPTIONTEXT, &_clrSubtitle);
_clrBG = CLR_NONE;
GetThemeColor(_hTheme, _iThemePart, 0, TMT_TEXTCOLOR, &clrText);
ListView_SetTextColor(_hwndList, clrText);
ListView_SetOutlineColor(_hwndList, _clrHot);
}
ListView_SetBkColor(_hwndList, _clrBG);
ListView_SetTextBkColor(_hwndList, _clrBG);
ListView_SetView(_hwndList, LV_VIEW_TILE);
// USER will send us a WM_SIZE after the WM_CREATE, which will cause
// the listview to repopulate, if we chose to repopulate in the
// foreground.
return 0;
}
BOOL SFTBarHost::_CreateMarlett()
{
HDC hdc = GetDC(_hwndList);
if (hdc)
{
HFONT hfPrev = SelectFont(hdc, GetWindowFont(_hwndList));
if (hfPrev)
{
TEXTMETRIC tm;
if (GetTextMetrics(hdc, &tm))
{
LOGFONT lf;
ZeroMemory(&lf, sizeof(lf));
lf.lfHeight = tm.tmAscent;
lf.lfWeight = FW_NORMAL;
lf.lfCharSet = SYMBOL_CHARSET;
lstrcpy(lf.lfFaceName, TEXT("Marlett"));
_hfMarlett = CreateFontIndirect(&lf);
if (_hfMarlett)
{
SelectFont(hdc, _hfMarlett);
if (GetTextMetrics(hdc, &tm))
{
_tmAscentMarlett = tm.tmAscent;
SIZE siz;
if (GetTextExtentPoint(hdc, TEXT("8"), 1, &siz))
{
_cxMarlett = siz.cx;
}
}
}
}
SelectFont(hdc, hfPrev);
}
ReleaseDC(_hwndList, hdc);
}
return _cxMarlett;
}
void SFTBarHost::_CreateBoldFont()
{
if (!_hfBold)
{
HFONT hf = GetWindowFont(_hwndList);
if (hf)
{
LOGFONT lf;
if (GetObject(hf, sizeof(lf), &lf))
{
lf.lfWeight = FW_BOLD;
SHAdjustLOGFONT(&lf); // locale-specific adjustments
_hfBold = CreateFontIndirect(&lf);
}
}
}
}
void SFTBarHost::_ReloadText()
{
int iItem;
for (iItem = ListView_GetItemCount(_hwndList) - 1; iItem >= 0; iItem--)
{
TCHAR szText[MAX_PATH];
LVITEM lvi;
lvi.iItem = iItem;
lvi.iSubItem = 0;
lvi.mask = LVIF_PARAM | LVIF_TEXT;
lvi.pszText = szText;
lvi.cchTextMax = ARRAYSIZE(szText);
if (ListView_GetItem(_hwndList, &lvi))
{
PaneItem *pitem = _GetItemFromLVLParam(lvi.lParam);
if (!pitem)
{
break;
}
// Update the display name in case it changed behind our back.
// Note that this is not redundant with the creation of the items
// in _InsertListViewItem because this is done only on the second
// and subsequent enumeration. (We assume the first enumeration
// is just peachy.)
lvi.iItem = iItem;
lvi.iSubItem = 0;
lvi.mask = LVIF_TEXT;
lvi.pszText = _DisplayNameOfItem(pitem, SHGDN_NORMAL);
if (lvi.pszText)
{
if (StrCmpN(szText, lvi.pszText, ARRAYSIZE(szText)) != 0)
{
ListView_SetItem(_hwndList, &lvi);
_SendNotify(_hwnd, SMN_NEEDREPAINT, NULL);
}
SHFree(lvi.pszText);
}
}
}
}
void SFTBarHost::_RevalidateItems()
{
// If client does not require revalidation, then assume still valid
if (!(_dwFlags & HOSTF_REVALIDATE))
{
return;
}
int iItem;
for (iItem = ListView_GetItemCount(_hwndList) - 1; iItem >= 0; iItem--)
{
PaneItem *pitem = _GetItemFromLV(iItem);
if (!pitem || !IsItemStillValid(pitem))
{
_fEnumValid = FALSE;
break;
}
}
}
void SFTBarHost::_RevalidatePostPopup()
{
_RevalidateItems();
if (_dwFlags & HOSTF_RELOADTEXT)
{
SetTimer(_hwnd, IDT_RELOADTEXT, 250, NULL);
}
// If the list is still good, then don't bother redoing it
if (!_fEnumValid)
{
_EnumerateContents(FALSE);
}
}
void SFTBarHost::_EnumerateContents(BOOL fUrgent)
{
// If we have deferred refreshes until the window closes, then
// leave it alone.
if (!fUrgent && _fNeedsRepopulate)
{
return;
}
// If we're already enumerating, then just remember to do it again
if (_fBGTask)
{
// accumulate urgency so a low-priority request + an urgent request
// is treated as urgent
_fRestartUrgent |= fUrgent;
_fRestartEnum = TRUE;
return;
}
_fRestartEnum = FALSE;
_fRestartUrgent = FALSE;
// If the list is still good, then don't bother redoing it
if (_fEnumValid && !fUrgent)
{
return;
}
// This re-enumeration will make everything valid.
_fEnumValid = TRUE;
// Clear out all the leftover stuff from the previous enumeration
_dpaEnumNew.EnumCallbackEx(PaneItem::DPAEnumCallback, (void *)NULL);
_dpaEnumNew.DeleteAllPtrs();
// Let client do some work on the foreground thread
PrePopulate();
// Finish the enumeration either on the background thread (if requested)
// or on the foreground thread (if can't enumerate in the background).
HRESULT hr;
if (NeedBackgroundEnum())
{
if (_psched)
{
hr = S_OK;
}
else
{
// We need a separate task scheduler for each instance
hr = CoCreateInstance(CLSID_ShellTaskScheduler, NULL, CLSCTX_INPROC,
IID_PPV_ARG(IShellTaskScheduler, &_psched));
}
if (SUCCEEDED(hr))
{
CBGEnum *penum = new CBGEnum(this, fUrgent);
if (penum)
{
// We want to run at a priority slightly above normal
// because the user is sitting there waiting for the
// enumeration to complete.
#define ITSAT_BGENUM_PRIORITY (ITSAT_DEFAULT_PRIORITY + 0x1000)
hr = _psched->AddTask(penum, TOID_SFTBarHostBackgroundEnum, (DWORD_PTR)this, ITSAT_BGENUM_PRIORITY);
if (SUCCEEDED(hr))
{
_fBGTask = TRUE;
if (ListView_GetItemCount(_hwndList) == 0)
{
//
// Set a timer that will create the "please wait"
// animation if the enumeration takes too long.
//
_idtAni = IDT_ASYNCENUM;
SetTimer(_hwnd, _idtAni, 1000, NULL);
}
}
penum->Release();
}
}
}
if (!_fBGTask)
{
// Fallback: Do it on the foreground thread
_EnumerateContentsBackground();
_RepopulateList();
}
}
void SFTBarHost::_EnumerateContentsBackground()
{
// Start over
DEBUG_CODE(_fEnumerating = TRUE);
EnumItems();
DEBUG_CODE(_fEnumerating = FALSE);
#ifdef _ALPHA_
// Alpha compiler is lame
_dpaEnumNew.Sort((CDPA<PaneItem>::_PFNDPACOMPARE)_SortItemsAfterEnum, (LPARAM)this);
#else
_dpaEnumNew.SortEx(_SortItemsAfterEnum, this);
#endif
}
int CALLBACK SFTBarHost::_SortItemsAfterEnum(PaneItem *p1, PaneItem *p2, SFTBarHost *self)
{
//
// Put all pinned items (sorted by pin position) ahead of unpinned items.
//
if (p1->IsPinned())
{
if (p2->IsPinned())
{
return p1->GetPinPos() - p2->GetPinPos();
}
return -1;
}
else if (p2->IsPinned())
{
return +1;
}
//
// Both unpinned - let the client decide.
//
return self->CompareItems(p1, p2);
}
SFTBarHost::~SFTBarHost()
{
// We shouldn't be destroyed while in these temporary states.
// If this fires, it's possible that somebody incremented
// _fListUnstable/_fPopulating and forgot to decrement it.
ASSERT(!_fListUnstable);
ASSERT(!_fPopulating);
ATOMICRELEASE(_pdth);
ATOMICRELEASE(_pdsh);
ATOMICRELEASE(_psched);
ASSERT(_pdtoDragOut == NULL);
if (_dpaEnum)
{
_dpaEnum.DestroyCallbackEx(PaneItem::DPAEnumCallback, (void *)NULL);
}
if (_dpaEnumNew)
{
_dpaEnumNew.DestroyCallbackEx(PaneItem::DPAEnumCallback, (void *)NULL);
}
if (_IsPrivateImageList() && _himl)
{
ImageList_Destroy(_himl);
}
if (_hfList)
{
DeleteObject(_hfList);
}
if (_hfBold)
{
DeleteObject(_hfBold);
}
if (_hfMarlett)
{
DeleteObject(_hfMarlett);
}
if (_hBrushAni)
{
DeleteObject(_hBrushAni);
}
}
LRESULT SFTBarHost::_OnDestroy(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
UINT id;
for (id = 0; id < SFTHOST_MAXNOTIFY; id++)
{
UnregisterNotify(id);
}
if (_hwndList)
{
RevokeDragDrop(_hwndList);
}
return ::DefWindowProc(hwnd, uMsg, wParam, lParam);
}
LRESULT SFTBarHost::_OnNcDestroy(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// WARNING! "this" might be NULL (if WM_NCCREATE failed).
LRESULT lres = DefWindowProc(hwnd, uMsg, wParam, lParam);
SetWindowPtr0(hwnd, 0);
if (this) {
_hwndList = NULL;
_hwnd = NULL;
if (_psched)
{
// Remove all tasks now, and wait for them to finish
_psched->RemoveTasks(TOID_NULL, ITSAT_DEFAULT_LPARAM, TRUE);
ATOMICRELEASE(_psched);
}
Release();
}
return lres;
}
LRESULT SFTBarHost::_OnNotify(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LPNMHDR pnm = reinterpret_cast<LPNMHDR>(lParam);
if (pnm->hwndFrom == _hwndList)
{
switch (pnm->code)
{
case NM_CUSTOMDRAW:
return _OnLVCustomDraw(CONTAINING_RECORD(
CONTAINING_RECORD(pnm, NMCUSTOMDRAW, hdr),
NMLVCUSTOMDRAW, nmcd));
case NM_CLICK:
return _OnLVNItemActivate(CONTAINING_RECORD(pnm, NMITEMACTIVATE, hdr));
case NM_RETURN:
return _ActivateItem(_GetLVCurSel(), AIF_KEYBOARD);
case NM_KILLFOCUS:
// On loss of focus, deselect all items so they all draw
// in the plain state.
ListView_SetItemState(_hwndList, -1, 0, LVIS_SELECTED | LVIS_FOCUSED);
break;
case LVN_GETINFOTIP:
return _OnLVNGetInfoTip(CONTAINING_RECORD(pnm, NMLVGETINFOTIP, hdr));
case LVN_BEGINDRAG:
case LVN_BEGINRDRAG:
return _OnLVNBeginDrag(CONTAINING_RECORD(pnm, NMLISTVIEW, hdr));
case LVN_BEGINLABELEDIT:
return _OnLVNBeginLabelEdit(CONTAINING_RECORD(pnm, NMLVDISPINFO, hdr));
case LVN_ENDLABELEDIT:
return _OnLVNEndLabelEdit(CONTAINING_RECORD(pnm, NMLVDISPINFO, hdr));
case LVN_KEYDOWN:
return _OnLVNKeyDown(CONTAINING_RECORD(pnm, NMLVKEYDOWN, hdr));
}
}
else
{
switch (pnm->code)
{
case SMN_INITIALUPDATE:
_EnumerateContents(FALSE);
break;
case SMN_POSTPOPUP:
_RevalidatePostPopup();
break;
case SMN_GETMINSIZE:
return _OnSMNGetMinSize(CONTAINING_RECORD(pnm, SMNGETMINSIZE, hdr));
break;
case SMN_FINDITEM:
return _OnSMNFindItem(CONTAINING_RECORD(pnm, SMNDIALOGMESSAGE, hdr));
case SMN_DISMISS:
return _OnSMNDismiss();
case SMN_APPLYREGION:
return HandleApplyRegion(_hwnd, _hTheme, (SMNMAPPLYREGION *)lParam, _iThemePart, 0);
case SMN_SHELLMENUDISMISSED:
_iCascading = -1;
return 0;
}
}
// Give derived class a chance to respond
return OnWndProc(hwnd, uMsg, wParam, lParam);
}
LRESULT SFTBarHost::_OnTimer(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (wParam)
{
case IDT_ASYNCENUM:
KillTimer(hwnd, wParam);
// For some reason, we sometimes get spurious WM_TIMER messages,
// so ignore them if we aren't expecting them.
if (_idtAni)
{
_idtAni = 0;
if (_hwndList && !_hwndAni)
{
DWORD dwStyle = WS_CHILD | WS_VISIBLE |
WS_CLIPCHILDREN | WS_CLIPSIBLINGS |
ACS_AUTOPLAY | ACS_TIMER | ACS_TRANSPARENT;
RECT rcClient;
GetClientRect(_hwnd, &rcClient);
int x = (RECTWIDTH(rcClient) - ANIWND_WIDTH)/2; // IDA_SEARCH is ANIWND_WIDTH pix wide
int y = (RECTHEIGHT(rcClient) - ANIWND_HEIGHT)/2; // IDA_SEARCH is ANIWND_HEIGHT pix tall
_hwndAni = CreateWindow(ANIMATE_CLASS, NULL, dwStyle,
x, y, 0, 0,
_hwnd, NULL,
_Module.GetModuleInstance(), NULL);
if (_hwndAni)
{
SetWindowPos(_hwndAni, HWND_TOP, 0, 0, 0, 0,
SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
#define IDA_SEARCH 150 // from shell32
Animate_OpenEx(_hwndAni, GetModuleHandle(TEXT("SHELL32")), MAKEINTRESOURCE(IDA_SEARCH));
}
}
}
return 0;
case IDT_RELOADTEXT:
KillTimer(hwnd, wParam);
_ReloadText();
break;
case IDT_REFRESH:
KillTimer(hwnd, wParam);
PostMessage(hwnd, SFTBM_REFRESH, FALSE, 0);
break;
}
// Give derived class a chance to respond
return OnWndProc(hwnd, uMsg, wParam, lParam);
}
LRESULT SFTBarHost::_OnSetFocus(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if (_hwndList)
{
SetFocus(_hwndList);
}
return 0;
}
LRESULT SFTBarHost::_OnEraseBackground(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
RECT rc;
GetClientRect(hwnd, &rc);
if (_hTheme)
DrawThemeBackground(_hTheme, (HDC)wParam, _iThemePart, 0, &rc, 0);
else
{
SHFillRectClr((HDC)wParam, &rc, _clrBG);
if (_iThemePart == SPP_PLACESLIST) // we set this even in non-theme case, its how we tell them apart
DrawEdge((HDC)wParam, &rc, EDGE_ETCHED, BF_LEFT);
}
return TRUE;
}
LRESULT SFTBarHost::_OnLVCustomDraw(LPNMLVCUSTOMDRAW plvcd)
{
_DebugConsistencyCheck();
switch (plvcd->nmcd.dwDrawStage)
{
case CDDS_PREPAINT:
return _OnLVPrePaint(plvcd);
case CDDS_ITEMPREPAINT:
return _OnLVItemPrePaint(plvcd);
case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
return _OnLVSubItemPrePaint(plvcd);
case CDDS_ITEMPOSTPAINT:
return _OnLVItemPostPaint(plvcd);
case CDDS_POSTPAINT:
return _OnLVPostPaint(plvcd);
}
return CDRF_DODEFAULT;
}
//
// Listview makes it hard to detect whether you are in a real customdraw
// or a fake customdraw, since it frequently "gets confused" and gives
// you a 0x0 rectangle even though it really wants you to draw something.
//
// Even worse, within a single paint cycle, Listview uses multiple
// NMLVCUSTOMDRAW structures so you can't stash state inside the customdraw
// structure. You have to save it externally.
//
// The only trustworthy guy is CDDS_PREPAINT. Use his rectangle to
// determine whether this is a real or fake customdraw...
//
// What's even weirder is that inside a regular paint cycle, you
// can get re-entered with a sub-paint cycle, so we have to maintain
// a stack of "is the current customdraw cycle real or fake?" bits.
void SFTBarHost::_CustomDrawPush(BOOL fReal)
{
_dwCustomDrawState = (_dwCustomDrawState << 1) | fReal;
}
BOOL SFTBarHost::_IsRealCustomDraw()
{
return _dwCustomDrawState & 1;
}
void SFTBarHost::_CustomDrawPop()
{
_dwCustomDrawState >>= 1;
}
LRESULT SFTBarHost::_OnLVPrePaint(LPNMLVCUSTOMDRAW plvcd)
{
LRESULT lResult;
// Always ask for postpaint so we can maintain our customdraw stack
lResult = CDRF_NOTIFYITEMDRAW | CDRF_NOTIFYPOSTPAINT;
BOOL fReal = !IsRectEmpty(&plvcd->nmcd.rc);
_CustomDrawPush(fReal);
if (fReal)
{
if (_IsInsertionMarkActive())
{
if (_pdth)
{
_pdth->Show(FALSE);
}
}
}
return lResult;
}
//
// Hack! We want to know in _OnLvSubItemPrePaint whether the item
// is selected or not, We borrow the CDIS_CHECKED bit, which is
// otherwise used only by toolbar controls.
//
#define CDIS_WASSELECTED CDIS_CHECKED
LRESULT SFTBarHost::_OnLVItemPrePaint(LPNMLVCUSTOMDRAW plvcd)
{
LRESULT lResult = CDRF_DODEFAULT;
plvcd->nmcd.uItemState &= ~CDIS_WASSELECTED;
if (GetFocus() == _hwndList &&
(plvcd->nmcd.uItemState & CDIS_SELECTED))
{
plvcd->nmcd.uItemState |= CDIS_WASSELECTED;
// menu-highlighted tiles are always opaque
if (_hTheme)
{
plvcd->clrText = GetSysColor(COLOR_HIGHLIGHTTEXT);
plvcd->clrFace = plvcd->clrTextBk = GetSysColor(COLOR_MENUHILIGHT);
}
else
{
plvcd->clrText = GetSysColor(COLOR_HIGHLIGHTTEXT);
plvcd->clrFace = plvcd->clrTextBk = GetSysColor(COLOR_HIGHLIGHT);
}
}
// Turn off CDIS_SELECTED because it causes the icon to get alphablended
// and we don't want that. Turn off CDIS_FOCUS because that draws a
// focus rectangle and we don't want that either.
plvcd->nmcd.uItemState &= ~(CDIS_SELECTED | CDIS_FOCUS);
//
if (plvcd->nmcd.uItemState & CDIS_HOT && _clrHot != CLR_NONE)
plvcd->clrText = _clrHot;
// Turn off selection highlighting for everyone except
// the drop target highlight
if ((int)plvcd->nmcd.dwItemSpec != _iDragOver || !_pdtDragOver)
{
lResult |= LVCDRF_NOSELECT;
}
PaneItem *pitem = _GetItemFromLVLParam(plvcd->nmcd.lItemlParam);
if (!pitem)
{
// Sometimes ListView doesn't give us an lParam so we have to
// get it ourselves
pitem = _GetItemFromLV((int)plvcd->nmcd.dwItemSpec);
}
if (pitem)
{
if (IsBold(pitem))
{
_CreateBoldFont();
SelectFont(plvcd->nmcd.hdc, _hfBold);
lResult |= CDRF_NEWFONT;
}
if (pitem->IsCascade())
{
// Need subitem notification because that's what sets the colors
lResult |= CDRF_NOTIFYPOSTPAINT | CDRF_NOTIFYSUBITEMDRAW;
}
if (pitem->HasAccelerator())
{
// Need subitem notification because that's what sets the colors
lResult |= CDRF_NOTIFYPOSTPAINT | CDRF_NOTIFYSUBITEMDRAW;
}
if (pitem->HasSubtitle())
{
lResult |= CDRF_NOTIFYSUBITEMDRAW;
}
}
return lResult;
}
LRESULT SFTBarHost::_OnLVSubItemPrePaint(LPNMLVCUSTOMDRAW plvcd)
{
LRESULT lResult = CDRF_DODEFAULT;
if (plvcd->iSubItem == 1)
{
// Second line uses the regular font (first line was bold)
SelectFont(plvcd->nmcd.hdc, GetWindowFont(_hwndList));
lResult |= CDRF_NEWFONT;
if (GetFocus() == _hwndList &&
(plvcd->nmcd.uItemState & CDIS_WASSELECTED))
{
plvcd->clrText = GetSysColor(COLOR_HIGHLIGHTTEXT);
}
else
// Maybe there's a custom subtitle color
if (_clrSubtitle != CLR_NONE)
{
plvcd->clrText = _clrSubtitle;
}
else
{
plvcd->clrText = GetSysColor(COLOR_MENUTEXT);
}
}
return lResult;
}
// QUIRK! Listview often sends item postpaint messages even though we
// didn't ask for one. It does this because we set NOTIFYPOSTPAINT on
// the CDDS_PREPAINT notification ("please notify me when the entire
// listview is finished painting") and it thinks that that flag also
// turns on postpaint notifications for each item...
LRESULT SFTBarHost::_OnLVItemPostPaint(LPNMLVCUSTOMDRAW plvcd)
{
PaneItem *pitem = _GetItemFromLVLParam(plvcd->nmcd.lItemlParam);
if (_IsRealCustomDraw() && pitem)
{
RECT rc;
if (ListView_GetItemRect(_hwndList, plvcd->nmcd.dwItemSpec, &rc, LVIR_LABEL))
{
COLORREF clrBkPrev = SetBkColor(plvcd->nmcd.hdc, plvcd->clrFace);
COLORREF clrTextPrev = SetTextColor(plvcd->nmcd.hdc, plvcd->clrText);
int iModePrev = SetBkMode(plvcd->nmcd.hdc, TRANSPARENT);
BOOL fRTL = GetLayout(plvcd->nmcd.hdc) & LAYOUT_RTL;
if (pitem->IsCascade())
{
{
HFONT hfPrev = SelectFont(plvcd->nmcd.hdc, _hfMarlett);
if (hfPrev)
{
TCHAR chOut = fRTL ? TEXT('w') : TEXT('8');
UINT fuOptions = 0;
if (fRTL)
{
fuOptions |= ETO_RTLREADING;
}
ExtTextOut(plvcd->nmcd.hdc, rc.right - _cxMarlett,
rc.top + (rc.bottom - rc.top - _tmAscentMarlett)/2,
fuOptions, &rc, &chOut, 1, NULL);
SelectFont(plvcd->nmcd.hdc, hfPrev);
}
}
}
if (pitem->HasAccelerator() &&
(plvcd->nmcd.uItemState & CDIS_SHOWKEYBOARDCUES))
{
// Subtitles mess up our computations...
ASSERT(!pitem->HasSubtitle());
rc.right -= _cxMarlett; // Subtract out our margin
UINT uFormat = DT_VCENTER | DT_SINGLELINE | DT_PREFIXONLY |
DT_WORDBREAK | DT_EDITCONTROL | DT_WORD_ELLIPSIS;
if (fRTL)
{
uFormat |= DT_RTLREADING;
}
DrawText(plvcd->nmcd.hdc, pitem->_pszAccelerator, -1, &rc, uFormat);
rc.right += _cxMarlett; // restore it
}
SetBkMode(plvcd->nmcd.hdc, iModePrev);
SetTextColor(plvcd->nmcd.hdc, clrTextPrev);
SetBkColor(plvcd->nmcd.hdc, clrBkPrev);
}
}
return CDRF_DODEFAULT;
}
LRESULT SFTBarHost::_OnLVPostPaint(LPNMLVCUSTOMDRAW plvcd)
{
if (_IsRealCustomDraw())
{
_DrawInsertionMark(plvcd);
_DrawSeparators(plvcd);
if (_pdth)
{
_pdth->Show(TRUE);
}
}
_CustomDrawPop();
return CDRF_DODEFAULT;
}
LRESULT SFTBarHost::_OnUpdateUIState(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// Only need to do this when the Start Menu is visible; if not visible, then
// don't waste your time invalidating useless rectangles (and paging them in!)
if (IsWindowVisible(GetAncestor(_hwnd, GA_ROOT)))
{
// All UIS_SETs should happen when the Start Menu is hidden;
// we assume that the only thing we will be asked to do is to
// start showing the underlines
ASSERT(LOWORD(wParam) != UIS_SET);
DWORD dwLvExStyle = 0;
if (!GetSystemMetrics(SM_REMOTESESSION))
{
dwLvExStyle |= LVS_EX_DOUBLEBUFFER;
}
if ((ListView_GetExtendedListViewStyle(_hwndList) & LVS_EX_DOUBLEBUFFER) != dwLvExStyle)
{
ListView_SetExtendedListViewStyleEx(_hwndList, LVS_EX_DOUBLEBUFFER, dwLvExStyle);
}
int iItem;
for (iItem = ListView_GetItemCount(_hwndList) - 1; iItem >= 0; iItem--)
{
PaneItem *pitem = _GetItemFromLV(iItem);
if (pitem && pitem->HasAccelerator())
{
RECT rc;
if (ListView_GetItemRect(_hwndList, iItem, &rc, LVIR_LABEL))
{
// We need to repaint background because of cleartype double print issues
InvalidateRect(_hwndList, &rc, TRUE);
}
}
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
PaneItem *SFTBarHost::_GetItemFromLV(int iItem)
{
LVITEM lvi;
lvi.iItem = iItem;
lvi.iSubItem = 0;
lvi.mask = LVIF_PARAM;
if (iItem >= 0 && ListView_GetItem(_hwndList, &lvi))
{
PaneItem *pitem = _GetItemFromLVLParam(lvi.lParam);
return pitem;
}
return NULL;
}
LRESULT SFTBarHost::_OnMenuMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LRESULT lres;
if (_pcm3Pop && SUCCEEDED(_pcm3Pop->HandleMenuMsg2(uMsg, wParam, lParam, &lres)))
{
return lres;
}
if (_pcm2Pop && SUCCEEDED(_pcm2Pop->HandleMenuMsg(uMsg, wParam, lParam)))
{
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
LRESULT SFTBarHost::_OnForwardMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
SHPropagateMessage(hwnd, uMsg, wParam, lParam, SPM_SEND | SPM_ONELEVEL);
// Give derived class a chance to get the message, too
return OnWndProc(hwnd, uMsg, wParam, lParam);
}
BOOL SFTBarHost::UnregisterNotify(UINT id)
{
ASSERT(id < SFTHOST_MAXNOTIFY);
if (id < SFTHOST_MAXNOTIFY && _rguChangeNotify[id])
{
UINT uChangeNotify = _rguChangeNotify[id];
_rguChangeNotify[id] = 0;
return SHChangeNotifyDeregister(uChangeNotify);
}
return FALSE;
}
BOOL SFTBarHost::_RegisterNotify(UINT id, LONG lEvents, LPCITEMIDLIST pidl, BOOL fRecursive)
{
ASSERT(id < SFTHOST_MAXNOTIFY);
if (id < SFTHOST_MAXNOTIFY)
{
UnregisterNotify(id);
SHChangeNotifyEntry fsne;
fsne.fRecursive = fRecursive;
fsne.pidl = pidl;
int fSources = SHCNRF_NewDelivery | SHCNRF_ShellLevel | SHCNRF_InterruptLevel;
if (fRecursive)
{
// SHCNRF_RecursiveInterrupt means "Please use a recursive FindFirstChangeNotify"
fSources |= SHCNRF_RecursiveInterrupt;
}
_rguChangeNotify[id] = SHChangeNotifyRegister(_hwnd, fSources, lEvents,
SFTBM_CHANGENOTIFY + id, 1, &fsne);
return _rguChangeNotify[id];
}
return FALSE;
}
//
// wParam = 0 if this is not an urgent refresh (can be postponed)
// wParam = 1 if this is urgent (must refresh even if menu is open)
//
LRESULT SFTBarHost::_OnRepopulate(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// Don't update the list now if we are visible, except if the list was empty
_fBGTask = FALSE;
if (wParam || !IsWindowVisible(_hwnd) || ListView_GetItemCount(_hwndList) == 0)
{
_RepopulateList();
}
else
{
_fNeedsRepopulate = TRUE;
}
if (_fRestartEnum)
{
_EnumerateContents(_fRestartUrgent);
}
return 0;
}
LRESULT SFTBarHost::_OnChangeNotify(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LPITEMIDLIST *ppidl;
LONG lEvent;
LPSHChangeNotificationLock pshcnl;
pshcnl = SHChangeNotification_Lock((HANDLE)wParam, (DWORD)lParam, &ppidl, &lEvent);
if (pshcnl)
{
UINT id = uMsg - SFTBM_CHANGENOTIFY;
if (id < SFTHOST_MAXCLIENTNOTIFY)
{
OnChangeNotify(id, lEvent, ppidl[0], ppidl[1]);
}
else if (id == SFTHOST_HOSTNOTIFY_UPDATEIMAGE)
{
_OnUpdateImage(ppidl[0], ppidl[1]);
}
else
{
// Our wndproc shouldn't have dispatched to us
ASSERT(0);
}
SHChangeNotification_Unlock(pshcnl);
}
return 0;
}
void SFTBarHost::_OnUpdateImage(LPCITEMIDLIST pidl, LPCITEMIDLIST pidlExtra)
{
// Must use pidl and not pidlExtra because pidlExtra is sometimes NULL
SHChangeDWORDAsIDList *pdwidl = (SHChangeDWORDAsIDList *)pidl;
if (pdwidl->dwItem1 == 0xFFFFFFFF)
{
// Wholesale icon rebuild; just pitch everything and start over
::PostMessage(v_hwndTray, SBM_REBUILDMENU, 0, 0);
}
else
{
int iImage = SHHandleUpdateImage(pidlExtra);
if (iImage >= 0)
{
UpdateImage(iImage);
}
}
}
//
// See if anybody is using this image; if so, invalidate the cached bitmap.
//
void SFTBarHost::UpdateImage(int iImage)
{
ASSERT(!_IsPrivateImageList());
int iItem;
for (iItem = ListView_GetItemCount(_hwndList) - 1; iItem >= 0; iItem--)
{
LVITEM lvi;
lvi.iItem = iItem;
lvi.iSubItem = 0;
lvi.mask = LVIF_IMAGE;
if (ListView_GetItem(_hwndList, &lvi) && lvi.iImage == iImage)
{
// The cached bitmap is no good; an icon changed
_SendNotify(_hwnd, SMN_NEEDREPAINT, NULL);
break;
}
}
}
//
// wParam = 0 if this is not an urgent refresh (can be postponed)
// wParam = 1 if this is urgen (must refresh even if menu is open)
//
LRESULT SFTBarHost::_OnRefresh(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
_EnumerateContents((BOOL)wParam);
return 0;
}
LPTSTR _DisplayNameOf(IShellFolder *psf, LPCITEMIDLIST pidl, UINT shgno)
{
LPTSTR pszOut;
DisplayNameOfAsOLESTR(psf, pidl, shgno, &pszOut);
return pszOut;
}
LPTSTR SFTBarHost::_DisplayNameOfItem(PaneItem *pitem, UINT shgno)
{
IShellFolder *psf;
LPCITEMIDLIST pidl;
LPTSTR pszOut = NULL;
if (SUCCEEDED(_GetFolderAndPidl(pitem, &psf, &pidl)))
{
pszOut = DisplayNameOfItem(pitem, psf, pidl, (SHGNO)shgno);
psf->Release();
}
return pszOut;
}
HRESULT SFTBarHost::_GetUIObjectOfItem(PaneItem *pitem, REFIID riid, void * *ppv)
{
*ppv = NULL;
IShellFolder *psf;
LPCITEMIDLIST pidlItem;
HRESULT hr = _GetFolderAndPidl(pitem, &psf, &pidlItem);
if (SUCCEEDED(hr))
{
hr = psf->GetUIObjectOf(_hwnd, 1, &pidlItem, riid, NULL, ppv);
psf->Release();
}
return hr;
}
HRESULT SFTBarHost::_GetUIObjectOfItem(int iItem, REFIID riid, void * *ppv)
{
PaneItem *pitem = _GetItemFromLV(iItem);
if (pitem)
{
HRESULT hr = _GetUIObjectOfItem(pitem, riid, ppv);
return hr;
}
return E_FAIL;
}
HRESULT SFTBarHost::_GetFolderAndPidl(PaneItem *pitem, IShellFolder **ppsfOut, LPCITEMIDLIST *ppidlOut)
{
*ppsfOut = NULL;
*ppidlOut = NULL;
return pitem->IsSeparator() ? E_FAIL : GetFolderAndPidl(pitem, ppsfOut, ppidlOut);
}
//
// Given the coordinates of a context menu (lParam from WM_CONTEXTMENU),
// determine which item's context menu should be activated, or -1 if the
// context menu is not for us.
//
// Also, returns on success in *ppt the coordinates at which the
// context menu should be displayed.
//
int SFTBarHost::_ContextMenuCoordsToItem(LPARAM lParam, POINT *ppt)
{
int iItem;
ppt->x = GET_X_LPARAM(lParam);
ppt->y = GET_Y_LPARAM(lParam);
// If initiated from keyboard, act like they clicked on the center
// of the focused icon.
if (IS_WM_CONTEXTMENU_KEYBOARD(lParam))
{
iItem = _GetLVCurSel();
if (iItem >= 0)
{
RECT rc;
if (ListView_GetItemRect(_hwndList, iItem, &rc, LVIR_ICON))
{
MapWindowRect(_hwndList, NULL, &rc);
ppt->x = (rc.left+rc.right)/2;
ppt->y = (rc.top+rc.bottom)/2;
}
else
{
iItem = -1;
}
}
}
else
{
// Initiated from mouse; find the item they clicked on
LVHITTESTINFO hti;
hti.pt = *ppt;
MapWindowPoints(NULL, _hwndList, &hti.pt, 1);
iItem = ListView_HitTest(_hwndList, &hti);
}
return iItem;
}
LRESULT SFTBarHost::_OnContextMenu(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if(_AreChangesRestricted())
{
return 0;
}
TCHAR szBuf[MAX_PATH];
_DebugConsistencyCheck();
BOOL fSuccess = FALSE;
POINT pt;
int iItem = _ContextMenuCoordsToItem(lParam, &pt);
if (iItem >= 0)
{
PaneItem *pitem = _GetItemFromLV(iItem);
if (pitem)
{
// If we can't get the official shell context menu,
// then use a dummy one.
IContextMenu *pcm;
if (FAILED(_GetUIObjectOfItem(pitem, IID_PPV_ARG(IContextMenu, &pcm))))
{
pcm = s_EmptyContextMenu.GetContextMenu();
}
HMENU hmenu = ::CreatePopupMenu();
if (hmenu)
{
UINT uFlags = CMF_NORMAL;
if (GetKeyState(VK_SHIFT) < 0)
{
uFlags |= CMF_EXTENDEDVERBS;
}
if (_dwFlags & HOSTF_CANRENAME)
{
uFlags |= CMF_CANRENAME;
}
pcm->QueryContextMenu(hmenu, 0, IDM_QCM_MIN, IDM_QCM_MAX, uFlags);
// Remove "Create shortcut" from context menu because it creates
// the shortcut on the desktop, which the user can't see...
ContextMenu_DeleteCommandByName(pcm, hmenu, IDM_QCM_MIN, TEXT("link"));
// Remove "Cut" from context menu because we don't want objects
// to be deleted.
ContextMenu_DeleteCommandByName(pcm, hmenu, IDM_QCM_MIN, TEXT("cut"));
// Let clients override the "delete" option.
// Change "Delete" to "Remove from this list".
// If client doesn't support "delete" then nuke it outright.
// If client supports "delete" but the IContextMenu didn't create one,
// then create a fake one so we cn add the "Remove from list" option.
UINT uPosDelete = GetMenuIndexForCanonicalVerb(hmenu, pcm, IDM_QCM_MIN, TEXT("delete"));
UINT uiFlags = 0;
UINT idsDelete = AdjustDeleteMenuItem(pitem, &uiFlags);
if (idsDelete)
{
if (LoadString(_Module.GetResourceInstance(), idsDelete, szBuf, ARRAYSIZE(szBuf)))
{
if (uPosDelete != -1)
{
ModifyMenu(hmenu, uPosDelete, uiFlags | MF_BYPOSITION | MF_STRING, IDM_REMOVEFROMLIST, szBuf);
}
else
{
AppendMenu(hmenu, MF_SEPARATOR, -1, NULL);
AppendMenu(hmenu, uiFlags | MF_STRING, IDM_REMOVEFROMLIST, szBuf);
}
}
}
else
{
DeleteMenu(hmenu, uPosDelete, MF_BYPOSITION);
}
_SHPrettyMenu(hmenu);
ASSERT(_pcm2Pop == NULL); // Shouldn't be recursing
pcm->QueryInterface(IID_PPV_ARG(IContextMenu2, &_pcm2Pop));
ASSERT(_pcm3Pop == NULL); // Shouldn't be recursing
pcm->QueryInterface(IID_PPV_ARG(IContextMenu3, &_pcm3Pop));
int idCmd = TrackPopupMenuEx(hmenu,
TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTALIGN,
pt.x, pt.y, hwnd, NULL);
ATOMICRELEASE(_pcm2Pop);
ATOMICRELEASE(_pcm3Pop);
if (idCmd)
{
switch (idCmd)
{
case IDM_REMOVEFROMLIST:
lstrcpyn(szBuf, TEXT("delete"), ARRAYSIZE(szBuf));
break;
default:
ContextMenu_GetCommandStringVerb(pcm, idCmd - IDM_QCM_MIN, szBuf, ARRAYSIZE(szBuf));
break;
}
idCmd -= IDM_QCM_MIN;
CMINVOKECOMMANDINFOEX ici = {
sizeof(ici), // cbSize
CMIC_MASK_FLAG_LOG_USAGE | // this was an explicit user action
CMIC_MASK_ASYNCOK, // fMask
hwnd, // hwnd
(LPCSTR)IntToPtr(idCmd),// lpVerb
NULL, // lpParameters
NULL, // lpDirectory
SW_SHOWDEFAULT, // nShow
0, // dwHotKey
0, // hIcon
NULL, // lpTitle
(LPCWSTR)IntToPtr(idCmd),// lpVerbW
NULL, // lpParametersW
NULL, // lpDirectoryW
NULL, // lpTitleW
{ pt.x, pt.y }, // ptInvoke
};
if ((_dwFlags & HOSTF_CANRENAME) &&
StrCmpI(szBuf, TEXT("rename")) == 0)
{
_EditLabel(iItem);
}
else
{
ContextMenuInvokeItem(pitem, pcm, &ici, szBuf);
}
}
DestroyMenu(hmenu);
fSuccess = TRUE;
}
pcm->Release();
}
}
_DebugConsistencyCheck();
return fSuccess ? 0 : DefWindowProc(hwnd, uMsg, wParam, lParam);
}
void SFTBarHost::_EditLabel(int iItem)
{
_fAllowEditLabel = TRUE;
ListView_EditLabel(_hwndList, iItem);
_fAllowEditLabel = FALSE;
}
HRESULT SFTBarHost::ContextMenuInvokeItem(PaneItem *pitem, IContextMenu *pcm, CMINVOKECOMMANDINFOEX *pici, LPCTSTR pszVerb)
{
// Make sure none of our private menu items leaked through
ASSERT(PtrToLong(pici->lpVerb) >= 0);
return pcm->InvokeCommand(reinterpret_cast<LPCMINVOKECOMMANDINFO>(pici));
}
LRESULT SFTBarHost::_OnLVNItemActivate(LPNMITEMACTIVATE pnmia)
{
return _ActivateItem(pnmia->iItem, 0);
}
LRESULT SFTBarHost::_ActivateItem(int iItem, DWORD dwFlags)
{
// Activating a menu item indicates explicit user activity
Tray_UnlockStartPane();
PaneItem *pitem;
IShellFolder *psf;
LPCITEMIDLIST pidl;
DWORD dwCascadeFlags = 0;
if (dwFlags & AIF_KEYBOARD)
{
dwCascadeFlags = MPPF_KEYBOARD | MPPF_INITIALSELECT;
}
if (_OnCascade(iItem, dwCascadeFlags))
{
// We did the cascade thing; all finished!
}
else
if ((pitem = _GetItemFromLV(iItem)) &&
SUCCEEDED(_GetFolderAndPidl(pitem, &psf, &pidl)))
{
// See if the item is still valid.
// Do this only for SFGAO_FILESYSTEM objects because
// we can't be sure that other folders support SFGAO_VALIDATE,
// and besides, you can't resolve any other types of objects
// anyway...
DWORD dwAttr = SFGAO_FILESYSTEM | SFGAO_VALIDATE;
if (FAILED(psf->GetAttributesOf(1, &pidl, &dwAttr)) ||
(dwAttr & SFGAO_FILESYSTEM | SFGAO_VALIDATE) == SFGAO_FILESYSTEM ||
FAILED(_InvokeDefaultCommand(iItem, psf, pidl)))
{
// Object is bogus - offer to delete it
if ((_dwFlags & HOSTF_CANDELETE) && pitem->IsPinned())
{
_OfferDeleteBrokenItem(pitem, psf, pidl);
}
}
psf->Release();
}
return 0;
}
HRESULT SFTBarHost::_InvokeDefaultCommand(int iItem, IShellFolder *psf, LPCITEMIDLIST pidl)
{
HRESULT hr = SHInvokeDefaultCommand(GetShellWindow(), psf, pidl);
if (SUCCEEDED(hr))
{
if (_dwFlags & HOSTF_FIREUEMEVENTS)
{
_FireUEMPidlEvent(psf, pidl);
}
SMNMCOMMANDINVOKED ci;
ListView_GetItemRect(_hwndList, iItem, &ci.rcItem, LVIR_BOUNDS);
MapWindowRect(_hwndList, NULL, &ci.rcItem);
_SendNotify(_hwnd, SMN_COMMANDINVOKED, &ci.hdr);
}
return hr;
}
class OfferDelete
{
public:
LPTSTR _pszName;
LPITEMIDLIST _pidlFolder;
LPITEMIDLIST _pidlFull;
IStartMenuPin * _psmpin;
HWND _hwnd;
~OfferDelete()
{
SHFree(_pszName);
ILFree(_pidlFolder);
ILFree(_pidlFull);
}
BOOL _RepairBrokenItem();
void _ThreadProc();
static DWORD s_ThreadProc(LPVOID lpParameter)
{
OfferDelete *poffer = (OfferDelete *)lpParameter;
poffer->_ThreadProc();
delete poffer;
return 0;
}
};
BOOL OfferDelete::_RepairBrokenItem()
{
BOOL fSuccess = FALSE;
LPITEMIDLIST pidlNew;
HRESULT hr = _psmpin->Resolve(_hwnd, 0, _pidlFull, &pidlNew);
if (pidlNew)
{
ASSERT(hr == S_OK); // only the S_OK case should alloc a new pidl
// Update to reflect the new pidl
ILFree(_pidlFull);
_pidlFull = pidlNew;
// Re-invoke the default command; if it fails the second time,
// then I guess the Resolve didn't work after all.
IShellFolder *psf;
LPCITEMIDLIST pidlChild;
if (SUCCEEDED(SHBindToIDListParent(_pidlFull, IID_PPV_ARG(IShellFolder, &psf), &pidlChild)))
{
if (SUCCEEDED(SHInvokeDefaultCommand(_hwnd, psf, pidlChild)))
{
fSuccess = TRUE;
}
psf->Release();
}
}
return fSuccess;
}
void OfferDelete::_ThreadProc()
{
_hwnd = SHCreateWorkerWindow(NULL, NULL, 0, 0, NULL, NULL);
if (_hwnd)
{
if (SUCCEEDED(CoCreateInstance(CLSID_StartMenuPin, NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARG(IStartMenuPin, &_psmpin))))
{
//
// First try to repair it by invoking the shortcut tracking code.
// If that fails, then offer to delete.
if (!_RepairBrokenItem() &&
ShellMessageBox(_Module.GetResourceInstance(), NULL,
MAKEINTRESOURCE(IDS_SFTHOST_OFFERREMOVEITEM),
_pszName, MB_YESNO) == IDYES)
{
_psmpin->Modify(_pidlFull, NULL);
}
ATOMICRELEASE(_psmpin);
}
DestroyWindow(_hwnd);
}
}
void SFTBarHost::_OfferDeleteBrokenItem(PaneItem *pitem, IShellFolder *psf, LPCITEMIDLIST pidlChild)
{
//
// The offer is done on a separate thread because putting up modal
// UI while the Start Menu is open creates all sorts of weirdness.
// (The user might decide to switch to Classic Start Menu
// while the dialog is still up, and we get our infrastructure
// ripped out from underneath us and then USER faults inside
// MessageBox... Not good.)
//
OfferDelete *poffer = new OfferDelete;
if (poffer)
{
if ((poffer->_pszName = DisplayNameOfItem(pitem, psf, pidlChild, SHGDN_NORMAL)) != NULL &&
SUCCEEDED(SHGetIDListFromUnk(psf, &poffer->_pidlFolder)) &&
(poffer->_pidlFull = ILCombine(poffer->_pidlFolder, pidlChild)) != NULL &&
SHCreateThread(OfferDelete::s_ThreadProc, poffer, CTF_COINIT, NULL))
{
poffer = NULL; // thread took ownership
}
delete poffer;
}
}
BOOL ShowInfoTip()
{
// find out if infotips are on or off, from the registry settings
SHELLSTATE ss;
// force a refresh
SHGetSetSettings(&ss, 0, TRUE);
SHGetSetSettings(&ss, SSF_SHOWINFOTIP, FALSE);
return ss.fShowInfoTip;
}
// over-ridable method for getting the infotip on an item
void SFTBarHost::GetItemInfoTip(PaneItem *pitem, LPTSTR pszText, DWORD cch)
{
IShellFolder *psf;
LPCITEMIDLIST pidl;
if (pszText && cch)
{
*pszText = 0;
if (SUCCEEDED(_GetFolderAndPidl(pitem, &psf, &pidl)))
{
GetInfoTip(psf, pidl, pszText, cch);
}
psf->Release();
}
}
LRESULT SFTBarHost::_OnLVNGetInfoTip(LPNMLVGETINFOTIP plvn)
{
_DebugConsistencyCheck();
PaneItem *pitem;
if (ShowInfoTip() &&
(pitem = _GetItemFromLV(plvn->iItem)) &&
!pitem->IsCascade())
{
int cchName = (plvn->dwFlags & LVGIT_UNFOLDED) ? 0 : lstrlen(plvn->pszText);
if (cchName)
{
StrCatBuff(plvn->pszText, TEXT("\r\n"), plvn->cchTextMax);
cchName = lstrlen(plvn->pszText);
}
// If there is room in the buffer after we added CRLF, append the
// infotip text. We succeeded if there was nontrivial infotip text.
if (cchName < plvn->cchTextMax)
{
GetItemInfoTip(pitem, plvn->pszText + cchName, plvn->cchTextMax - cchName);
}
}
return 0;
}
LRESULT _SendNotify(HWND hwndFrom, UINT code, OPTIONAL NMHDR *pnm)
{
NMHDR nm;
if (pnm == NULL)
{
pnm = &nm;
}
pnm->hwndFrom = hwndFrom;
pnm->idFrom = GetDlgCtrlID(hwndFrom);
pnm->code = code;
return SendMessage(GetParent(hwndFrom), WM_NOTIFY, pnm->idFrom, (LPARAM)pnm);
}
//****************************************************************************
//
// Drag sourcing
//
// *** IDropSource::GiveFeedback ***
HRESULT SFTBarHost::GiveFeedback(DWORD dwEffect)
{
if (_fForceArrowCursor)
{
SetCursor(LoadCursor(NULL, IDC_ARROW));
return S_OK;
}
return DRAGDROP_S_USEDEFAULTCURSORS;
}
// *** IDropSource::QueryContinueDrag ***
HRESULT SFTBarHost::QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState)
{
if (fEscapePressed ||
(grfKeyState & (MK_LBUTTON | MK_RBUTTON)) == (MK_LBUTTON | MK_RBUTTON))
{
return DRAGDROP_S_CANCEL;
}
if ((grfKeyState & (MK_LBUTTON | MK_RBUTTON)) == 0)
{
return DRAGDROP_S_DROP;
}
return S_OK;
}
LRESULT SFTBarHost::_OnLVNBeginDrag(LPNMLISTVIEW plv)
{
//If changes are restricted, don't allow drag and drop!
if(_AreChangesRestricted())
return 0;
_DebugConsistencyCheck();
ASSERT(_pdtoDragOut == NULL);
_pdtoDragOut = NULL;
PaneItem *pitem = _GetItemFromLV(plv->iItem);
ASSERT(pitem);
IDataObject *pdto;
if (pitem && SUCCEEDED(_GetUIObjectOfItem(pitem, IID_PPV_ARG(IDataObject, &pdto))))
{
POINT pt;
pt = plv->ptAction;
ClientToScreen(_hwndList, &pt);
if (_pdsh)
{
_pdsh->InitializeFromWindow(_hwndList, &pt, pdto);
}
CLIPFORMAT cfOFFSETS = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_SHELLIDLISTOFFSET);
POINT *apts = (POINT*)GlobalAlloc(GPTR, sizeof(POINT)*2);
if (NULL != apts)
{
POINT ptOrigin = {0};
POINT ptItem = {0};
ListView_GetOrigin(_hwndList, &ptOrigin);
apts[0].x = plv->ptAction.x + ptOrigin.x;
apts[0].y = plv->ptAction.y + ptOrigin.y;
ListView_GetItemPosition(_hwndList,plv->iItem,&ptItem);
apts[1].x = ptItem.x - apts[0].x;
apts[1].y = ptItem.y - apts[0].y;
HRESULT hr = DataObj_SetGlobal(pdto, cfOFFSETS, apts);
if (FAILED(hr))
{
GlobalFree((HGLOBAL)apts);
}
}
// We don't need to refcount _pdtoDragOut since its lifetime
// is the same as pdto.
_pdtoDragOut = pdto;
_iDragOut = plv->iItem;
_iPosDragOut = pitem->_iPos;
// Notice that DROPEFFECT_MOVE is explicitly forbidden.
// You cannot move things out of the control.
DWORD dwEffect = DROPEFFECT_LINK | DROPEFFECT_COPY;
DoDragDrop(pdto, this, dwEffect, &dwEffect);
_pdtoDragOut = NULL;
pdto->Release();
}
return 0;
}
//
// Must perform validation of SFGAO_CANRENAME when the label edit begins
// because John Gray somehow can trick the listview into going into edit
// mode by clicking in the right magic place, so this is the only chance
// we get to reject things that aren't renamable...
//
LRESULT SFTBarHost::_OnLVNBeginLabelEdit(NMLVDISPINFO *plvdi)
{
LRESULT lres = 1;
PaneItem *pitem = _GetItemFromLVLParam(plvdi->item.lParam);
IShellFolder *psf;
LPCITEMIDLIST pidl;
if (_fAllowEditLabel &&
pitem && SUCCEEDED(_GetFolderAndPidl(pitem, &psf, &pidl)))
{
DWORD dwAttr = SFGAO_CANRENAME;
if (SUCCEEDED(psf->GetAttributesOf(1, &pidl, &dwAttr)) &&
(dwAttr & SFGAO_CANRENAME))
{
LPTSTR ptszName = _DisplayNameOf(psf, pidl,
SHGDN_INFOLDER | SHGDN_FOREDITING);
if (ptszName)
{
HWND hwndEdit = ListView_GetEditControl(_hwndList);
if (hwndEdit)
{
SetWindowText(hwndEdit, ptszName);
int cchLimit = MAX_PATH;
IItemNameLimits *pinl;
if (SUCCEEDED(psf->QueryInterface(IID_PPV_ARG(IItemNameLimits, &pinl))))
{
pinl->GetMaxLength(ptszName, &cchLimit);
pinl->Release();
}
Edit_LimitText(hwndEdit, cchLimit);
// use way-cool helper which pops up baloon tips if they enter an invalid folder....
SHLimitInputEdit(hwndEdit, psf);
// Block menu mode during editing so the user won't
// accidentally cancel out of rename mode just because
// they moved the mouse.
SMNMBOOL nmb;
nmb.f = TRUE;
_SendNotify(_hwnd, SMN_BLOCKMENUMODE, &nmb.hdr);
lres = 0;
}
SHFree(ptszName);
}
}
psf->Release();
}
return lres;
}
LRESULT SFTBarHost::_OnLVNEndLabelEdit(NMLVDISPINFO *plvdi)
{
// Unblock menu mode now that editing is over.
SMNMBOOL nmb;
nmb.f = FALSE;
_SendNotify(_hwnd, SMN_BLOCKMENUMODE, &nmb.hdr);
// If changing to NULL pointer, then user is cancelling
if (!plvdi->item.pszText)
return FALSE;
// Note: We allow the user to type blanks. Regfolder treats a blank
// name as "restore default name".
PathRemoveBlanks(plvdi->item.pszText);
PaneItem *pitem = _GetItemFromLVLParam(plvdi->item.lParam);
HRESULT hr = ContextMenuRenameItem(pitem, plvdi->item.pszText);
if (SUCCEEDED(hr))
{
LPTSTR ptszName = _DisplayNameOfItem(pitem, SHGDN_NORMAL);
if (ptszName)
{
ListView_SetItemText(_hwndList, plvdi->item.iItem, 0, ptszName);
_SendNotify(_hwnd, SMN_NEEDREPAINT, NULL);
}
}
else if (hr != HRESULT_FROM_WIN32(ERROR_CANCELLED))
{
_EditLabel(plvdi->item.iItem);
}
// Always return FALSE to prevent listview from changing the
// item text to what the user typed. If the rename succeeded,
// we manually set the name to the new name (which might not be
// the same as what the user typed).
return FALSE;
}
LRESULT SFTBarHost::_OnLVNKeyDown(LPNMLVKEYDOWN pkd)
{
// Plain F2 (no shift, ctrl or alt) = rename
if (pkd->wVKey == VK_F2 && GetKeyState(VK_SHIFT) >= 0 &&
GetKeyState(VK_CONTROL) >= 0 && GetKeyState(VK_MENU) >= 0 &&
(_dwFlags & HOSTF_CANRENAME))
{
int iItem = _GetLVCurSel();
if (iItem >= 0)
{
_EditLabel(iItem);
// cannot return TRUE because listview mistakenly thinks
// that all WM_KEYDOWNs lead to WM_CHARs (but this one doesn't)
}
}
return 0;
}
LRESULT SFTBarHost::_OnSMNGetMinSize(PSMNGETMINSIZE pgms)
{
// We need to synchronize here to get the proper size
if (_fBGTask && !HasDynamicContent())
{
// Wait for the enumeration to be done
while (TRUE)
{
MSG msg;
// Need to peek messages for all queues here or else WaitMessage will say
// that some messages are ready to be processed and we'll end up with an
// active loop
if (PeekMessage(&msg, NULL, NULL, NULL, PM_NOREMOVE))
{
if (PeekMessage(&msg, _hwnd, SFTBM_REPOPULATE, SFTBM_REPOPULATE, PM_REMOVE))
{
DispatchMessage(&msg);
break;
}
}
WaitMessage();
}
}
int cItems = _cPinnedDesired + _cNormalDesired;
int cSep = _cSep;
// if the repopulate hasn't happened yet, but we've got pinned items, we're going to have a separator
if (_cSep == 0 && _cPinnedDesired > 0)
cSep = 1;
int cy = (_cyTile * cItems) + (_cySepTile * cSep);
// add in theme margins
cy += _margins.cyTopHeight + _margins.cyBottomHeight;
// SPP_PROGLIST gets a bonus separator at the bottom
if (_iThemePart == SPP_PROGLIST)
{
cy += _cySep;
}
pgms->siz.cy = cy;
return 0;
}
LRESULT SFTBarHost::_OnSMNFindItem(PSMNDIALOGMESSAGE pdm)
{
LRESULT lres = _OnSMNFindItemWorker(pdm);
if (lres)
{
//
// If caller requested that the item also be selected, then do so.
//
if (pdm->flags & SMNDM_SELECT)
{
ListView_SetItemState(_hwndList, pdm->itemID,
LVIS_SELECTED | LVIS_FOCUSED,
LVIS_SELECTED | LVIS_FOCUSED);
if ((pdm->flags & SMNDM_FINDMASK) != SMNDM_HITTEST)
{
ListView_KeyboardSelected(_hwndList, pdm->itemID);
}
}
}
else
{
//
// If not found, then tell caller what our orientation is (vertical)
// and where the currently-selected item is.
//
pdm->flags |= SMNDM_VERTICAL;
int iItem = _GetLVCurSel();
RECT rc;
if (iItem >= 0 &&
ListView_GetItemRect(_hwndList, iItem, &rc, LVIR_BOUNDS))
{
pdm->pt.x = (rc.left + rc.right)/2;
pdm->pt.y = (rc.top + rc.bottom)/2;
}
else
{
pdm->pt.x = 0;
pdm->pt.y = 0;
}
}
return lres;
}
TCHAR SFTBarHost::GetItemAccelerator(PaneItem *pitem, int iItemStart)
{
TCHAR sz[2];
ListView_GetItemText(_hwndList, iItemStart, 0, sz, ARRAYSIZE(sz));
return CharUpperChar(sz[0]);
}
LRESULT SFTBarHost::_OnSMNFindItemWorker(PSMNDIALOGMESSAGE pdm)
{
LVFINDINFO lvfi;
LVHITTESTINFO lvhti;
switch (pdm->flags & SMNDM_FINDMASK)
{
case SMNDM_FINDFIRST:
L_SMNDM_FINDFIRST:
// Note: We can't just return item 0 because drag/drop pinning
// may have gotten the physical locations out of sync with the
// item numbers.
lvfi.vkDirection = VK_HOME;
lvfi.flags = LVFI_NEARESTXY;
pdm->itemID = ListView_FindItem(_hwndList, -1, &lvfi);
return pdm->itemID >= 0;
case SMNDM_FINDLAST:
// Note: We can't just return cItems-1 because drag/drop pinning
// may have gotten the physical locations out of sync with the
// item numbers.
lvfi.vkDirection = VK_END;
lvfi.flags = LVFI_NEARESTXY;
pdm->itemID = ListView_FindItem(_hwndList, -1, &lvfi);
return pdm->itemID >= 0;
case SMNDM_FINDNEAREST:
lvfi.pt = pdm->pt;
lvfi.vkDirection = VK_UP;
lvfi.flags = LVFI_NEARESTXY;
pdm->itemID = ListView_FindItem(_hwndList, -1, &lvfi);
return pdm->itemID >= 0;
case SMNDM_HITTEST:
lvhti.pt = pdm->pt;
pdm->itemID = ListView_HitTest(_hwndList, &lvhti);
return pdm->itemID >= 0;
case SMNDM_FINDFIRSTMATCH:
case SMNDM_FINDNEXTMATCH:
{
int iItemStart;
if ((pdm->flags & SMNDM_FINDMASK) == SMNDM_FINDFIRSTMATCH)
{
iItemStart = 0;
}
else
{
iItemStart = _GetLVCurSel() + 1;
}
TCHAR tch = CharUpperChar((TCHAR)pdm->pmsg->wParam);
int iItems = ListView_GetItemCount(_hwndList);
for (iItemStart; iItemStart < iItems; iItemStart++)
{
PaneItem *pitem = _GetItemFromLV(iItemStart);
if (GetItemAccelerator(pitem, iItemStart) == tch)
{
pdm->itemID = iItemStart;
return TRUE;
}
}
return FALSE;
}
break;
case SMNDM_FINDNEXTARROW:
if (pdm->pmsg->wParam == VK_UP)
{
pdm->itemID = ListView_GetNextItem(_hwndList, _GetLVCurSel(), LVNI_ABOVE);
return pdm->itemID >= 0;
}
if (pdm->pmsg->wParam == VK_DOWN)
{
// HACK! ListView_GetNextItem explicitly fails to find a "next item"
// if you tell it to start at -1 (no current item), so if there is no
// focus item, we have to change it to a SMNDM_FINDFIRST.
int iItem = _GetLVCurSel();
if (iItem == -1)
{
goto L_SMNDM_FINDFIRST;
}
pdm->itemID = ListView_GetNextItem(_hwndList, iItem, LVNI_BELOW);
return pdm->itemID >= 0;
}
if (pdm->flags & SMNDM_TRYCASCADE)
{
pdm->itemID = _GetLVCurSel();
return _OnCascade((int)pdm->itemID, MPPF_KEYBOARD | MPPF_INITIALSELECT);
}
return FALSE;
case SMNDM_INVOKECURRENTITEM:
{
int iItem = _GetLVCurSel();
if (iItem >= 0)
{
DWORD aif = 0;
if (pdm->flags & SMNDM_KEYBOARD)
{
aif |= AIF_KEYBOARD;
}
_ActivateItem(iItem, aif);
return TRUE;
}
}
return FALSE;
case SMNDM_OPENCASCADE:
{
DWORD mppf = 0;
if (pdm->flags & SMNDM_KEYBOARD)
{
mppf |= MPPF_KEYBOARD | MPPF_INITIALSELECT;
}
pdm->itemID = _GetLVCurSel();
return _OnCascade((int)pdm->itemID, mppf);
}
case SMNDM_FINDITEMID:
return TRUE;
default:
ASSERT(!"Unknown SMNDM command");
break;
}
return FALSE;
}
LRESULT SFTBarHost::_OnSMNDismiss()
{
if (_fNeedsRepopulate)
{
_RepopulateList();
}
return 0;
}
LRESULT SFTBarHost::_OnCascade(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
return _OnCascade((int)wParam, (DWORD)lParam);
}
BOOL SFTBarHost::_OnCascade(int iItem, DWORD dwFlags)
{
BOOL fSuccess = FALSE;
SMNTRACKSHELLMENU tsm;
tsm.dwFlags = dwFlags;
tsm.itemID = iItem;
if (iItem >= 0 &&
ListView_GetItemRect(_hwndList, iItem, &tsm.rcExclude, LVIR_BOUNDS))
{
PaneItem *pitem = _GetItemFromLV(iItem);
if (pitem && pitem->IsCascade())
{
if (SUCCEEDED(GetCascadeMenu(pitem, &tsm.psm)))
{
MapWindowRect(_hwndList, NULL, &tsm.rcExclude);
HWND hwnd = _hwnd;
_iCascading = iItem;
_SendNotify(_hwnd, SMN_TRACKSHELLMENU, &tsm.hdr);
tsm.psm->Release();
fSuccess = TRUE;
}
}
}
return fSuccess;
}
HRESULT SFTBarHost::QueryInterface(REFIID riid, void * *ppvOut)
{
static const QITAB qit[] = {
QITABENT(SFTBarHost, IDropTarget),
QITABENT(SFTBarHost, IDropSource),
QITABENT(SFTBarHost, IAccessible),
QITABENT(SFTBarHost, IDispatch), // IAccessible derives from IDispatch
{ 0 },
};
return QISearch(this, qit, riid, ppvOut);
}
ULONG SFTBarHost::AddRef()
{
return InterlockedIncrement(&_lRef);
}
ULONG SFTBarHost::Release()
{
ULONG cRef = InterlockedDecrement(&_lRef);
if (cRef)
return cRef;
delete this;
return 0;
}
void SFTBarHost::_SetDragOver(int iItem)
{
if (_iDragOver >= 0)
{
ListView_SetItemState(_hwndList, _iDragOver, 0, LVIS_DROPHILITED);
}
_iDragOver = iItem;
if (_iDragOver >= 0)
{
ListView_SetItemState(_hwndList, _iDragOver, LVIS_DROPHILITED, LVIS_DROPHILITED);
_tmDragOver = NonzeroGetTickCount();
}
else
{
_tmDragOver = 0;
}
}
void SFTBarHost::_ClearInnerDropTarget()
{
if (_pdtDragOver)
{
ASSERT(_iDragState == DRAGSTATE_ENTERED);
_pdtDragOver->DragLeave();
_pdtDragOver->Release();
_pdtDragOver = NULL;
DEBUG_CODE(_iDragState = DRAGSTATE_UNINITIALIZED);
}
_SetDragOver(-1);
}
HRESULT SFTBarHost::_TryInnerDropTarget(int iItem, DWORD grfKeyState, POINTL ptl, DWORD *pdwEffect)
{
HRESULT hr;
if (_iDragOver != iItem)
{
_ClearInnerDropTarget();
// Even if it fails, remember that we have this item so we don't
// query for the drop target again (and have it fail again).
_SetDragOver(iItem);
ASSERT(_pdtDragOver == NULL);
ASSERT(_iDragState == DRAGSTATE_UNINITIALIZED);
PaneItem *pitem = _GetItemFromLV(iItem);
if (pitem && pitem->IsDropTarget())
{
hr = _GetUIObjectOfItem(pitem, IID_PPV_ARG(IDropTarget, &_pdtDragOver));
if (SUCCEEDED(hr))
{
hr = _pdtDragOver->DragEnter(_pdtoDragIn, grfKeyState, ptl, pdwEffect);
if (SUCCEEDED(hr) && *pdwEffect)
{
DEBUG_CODE(_iDragState = DRAGSTATE_ENTERED);
}
else
{
DEBUG_CODE(_iDragState = DRAGSTATE_UNINITIALIZED);
ATOMICRELEASE(_pdtDragOver);
}
}
}
}
ASSERT(_iDragOver == iItem);
if (_pdtDragOver)
{
ASSERT(_iDragState == DRAGSTATE_ENTERED);
hr = _pdtDragOver->DragOver(grfKeyState, ptl, pdwEffect);
}
else
{
hr = E_FAIL; // No drop target
}
return hr;
}
void SFTBarHost::_PurgeDragDropData()
{
_SetInsertMarkPosition(-1);
_fForceArrowCursor = FALSE;
_ClearInnerDropTarget();
ATOMICRELEASE(_pdtoDragIn);
}
// *** IDropTarget::DragEnter ***
HRESULT SFTBarHost::DragEnter(IDataObject *pdto, DWORD grfKeyState, POINTL ptl, DWORD *pdwEffect)
{
if(_AreChangesRestricted())
{
*pdwEffect = DROPEFFECT_NONE;
return S_OK;
}
POINT pt = { ptl.x, ptl.y };
if (_pdth) {
_pdth->DragEnter(_hwnd, pdto, &pt, *pdwEffect);
}
return _DragEnter(pdto, grfKeyState, ptl, pdwEffect);
}
HRESULT SFTBarHost::_DragEnter(IDataObject *pdto, DWORD grfKeyState, POINTL ptl, DWORD *pdwEffect)
{
_PurgeDragDropData();
_fDragToSelf = SHIsSameObject(pdto, _pdtoDragOut);
_fInsertable = IsInsertable(pdto);
ASSERT(_pdtoDragIn == NULL);
_pdtoDragIn = pdto;
_pdtoDragIn->AddRef();
return DragOver(grfKeyState, ptl, pdwEffect);
}
// *** IDropTarget::DragOver ***
HRESULT SFTBarHost::DragOver(DWORD grfKeyState, POINTL ptl, DWORD *pdwEffect)
{
if(_AreChangesRestricted())
{
*pdwEffect = DROPEFFECT_NONE;
return S_OK;
}
_DebugConsistencyCheck();
ASSERT(_pdtoDragIn);
POINT pt = { ptl.x, ptl.y };
if (_pdth) {
_pdth->DragOver(&pt, *pdwEffect);
}
_fForceArrowCursor = FALSE;
// Need to remember this because at the point of the drop, OLE gives
// us the keystate after the user releases the button, so we can't
// tell what kind of a drag operation the user performed!
_grfKeyStateLast = grfKeyState;
#ifdef DEBUG
if (_fDragToSelf)
{
ASSERT(_pdtoDragOut);
ASSERT(_iDragOut >= 0);
PaneItem *pitem = _GetItemFromLV(_iDragOut);
ASSERT(pitem && (pitem->_iPos == _iPosDragOut));
}
#endif
// Find the last item above the cursor position. This allows us
// to treat the entire blank space at the bottom as belonging to the
// last item, and separators end up belonging to the item immediately
// above them. Note that we don't bother testing item zero since
// he is always above everything (since he's the first item).
ScreenToClient(_hwndList, &pt);
POINT ptItem;
int cItems = ListView_GetItemCount(_hwndList);
int iItem;
for (iItem = cItems - 1; iItem >= 1; iItem--)
{
ListView_GetItemPosition(_hwndList, iItem, &ptItem);
if (ptItem.y <= pt.y)
{
break;
}
}
//
// We didn't bother checking item 0 because we knew his position
// (by treating him special, this also causes all negative coordinates
// to be treated as belonging to item zero also).
//
if (iItem <= 0)
{
ptItem.y = 0;
iItem = 0;
}
//
// Decide whether this is a drag-between or a drag-over...
//
// For computational purposes, we treat each tile as four
// equal-sized "units" tall. For each unit, we consider the
// possible actions in the order listed.
//
// +-----
// | 0 insert above, drop on, reject
// | ----
// | 1 drop on, reject
// | ----
// | 2 drop on, reject
// | ----
// | 3 insert below, drop on, reject
// +-----
//
// If the listview is empty, then treat as an
// insert before (imaginary) item zero; i.e., pin
// to top of the list.
//
UINT uUnit = 0;
if (_cyTile && cItems)
{
int dy = pt.y - ptItem.y;
// Peg out-of-bounds values to the nearest edge.
if (dy < 0) dy = 0;
if (dy >= _cyTile) dy = _cyTile - 1;
// Decide which unit we are in.
uUnit = 4 * dy / _cyTile;
ASSERT(uUnit < 4);
}
//
// Now determine the appropriate action depending on which unit
// we are in.
//
int iInsert = -1; // Assume not inserting
if (_fInsertable)
{
// Note! Spec says that if you are in the non-pinned part of
// the list, we draw the insert bar at the very bottom of
// the pinned area.
switch (uUnit)
{
case 0:
iInsert = min(iItem, _cPinned);
break;
case 3:
iInsert = min(iItem+1, _cPinned);
break;
}
}
//
// If inserting above or below isn't allowed, try dropping on.
//
if (iInsert < 0)
{
_SetInsertMarkPosition(-1); // Not inserting
// Up above, we let separators be hit-tested as if they
// belongs to the item above them. But that doesn't work for
// drops, so reject them now.
//
// Also reject attempts to drop on the nonexistent item zero,
// and don't let the user drop an item on itself.
if (InRange(pt.y, ptItem.y, ptItem.y + _cyTile - 1) &&
cItems &&
!(_fDragToSelf && _iDragOut == iItem) &&
SUCCEEDED(_TryInnerDropTarget(iItem, grfKeyState, ptl, pdwEffect)))
{
// Woo-hoo, happy joy!
}
else
{
// Note that we need to convert a failed drop into a DROPEFFECT_NONE
// rather than returning a flat-out error code, because if we return
// an error code, OLE will stop sending us drag/drop notifications!
*pdwEffect = DROPEFFECT_NONE;
}
// If the user is hovering over a cascadable item, then open it.
// First see if the user has hovered long enough...
if (_tmDragOver && (GetTickCount() - _tmDragOver) >= _GetCascadeHoverTime())
{
_tmDragOver = 0;
// Now see if it's cascadable
PaneItem *pitem = _GetItemFromLV(_iDragOver);
if (pitem && pitem->IsCascade())
{
// Must post this message because the cascading is modal
// and we have to return a result to OLE
PostMessage(_hwnd, SFTBM_CASCADE, _iDragOver, 0);
}
}
}
else
{
_ClearInnerDropTarget(); // Not dropping
if (_fDragToSelf)
{
// Even though we're going to return DROPEFFECT_LINK,
// tell the drag source (namely, ourselves) that we would
// much prefer a regular arrow cursor because this is
// a Move operation from the user's point of view.
_fForceArrowCursor = TRUE;
}
//
// If user is dropping to a place where nothing would change,
// then don't draw an insert mark.
//
if (IsInsertMarkPointless(iInsert))
{
_SetInsertMarkPosition(-1);
}
else
{
_SetInsertMarkPosition(iInsert);
}
// Sigh. MergedFolder (used by the merged Start Menu)
// won't let you create shortcuts, so we pretend that
// we're copying if the data object doesn't permit
// linking.
if (*pdwEffect & DROPEFFECT_LINK)
{
*pdwEffect = DROPEFFECT_LINK;
}
else
{
*pdwEffect = DROPEFFECT_COPY;
}
}
return S_OK;
}
// *** IDropTarget::DragLeave ***
HRESULT SFTBarHost::DragLeave()
{
if(_AreChangesRestricted())
{
return S_OK;
}
if (_pdth) {
_pdth->DragLeave();
}
_PurgeDragDropData();
return S_OK;
}
// *** IDropTarget::Drop ***
HRESULT SFTBarHost::Drop(IDataObject *pdto, DWORD grfKeyState, POINTL ptl, DWORD *pdwEffect)
{
if(_AreChangesRestricted())
{
*pdwEffect = DROPEFFECT_NONE;
return S_OK;
}
_DebugConsistencyCheck();
// Use the key state from the last DragOver call
grfKeyState = _grfKeyStateLast;
// Need to go through the whole _DragEnter thing again because who knows
// maybe the data object and coordinates of the drop are different from
// the ones we got in DragEnter/DragOver... We use _DragEnter, which
// bypasses the IDropTargetHelper::DragEnter.
//
_DragEnter(pdto, grfKeyState, ptl, pdwEffect);
POINT pt = { ptl.x, ptl.y };
if (_pdth) {
_pdth->Drop(pdto, &pt, *pdwEffect);
}
int iInsert = _iInsert;
_SetInsertMarkPosition(-1);
if (*pdwEffect)
{
ASSERT(_pdtoDragIn);
if (iInsert >= 0) // "add to pin" or "move"
{
BOOL fTriedMove = FALSE;
// First see if it was just a move of an existing pinned item
if (_fDragToSelf)
{
PaneItem *pitem = _GetItemFromLV(_iDragOut);
if (pitem)
{
if (pitem->IsPinned())
{
// Yup, it was a move - so move it.
if (SUCCEEDED(MovePinnedItem(pitem, iInsert)))
{
// We used to try to update all the item positions
// incrementally. This was a major pain in the neck.
//
// So now we just do a full refresh. Turns out that a
// full refresh is fast enough anyway.
//
PostMessage(_hwnd, SFTBM_REFRESH, TRUE, 0);
}
// We tried to move a pinned item (return TRUE even if
// we actually failed).
fTriedMove = TRUE;
}
}
}
if (!fTriedMove)
{
if (SUCCEEDED(InsertPinnedItem(_pdtoDragIn, iInsert)))
{
PostMessage(_hwnd, SFTBM_REFRESH, TRUE, 0);
}
}
}
else if (_pdtDragOver) // Not an insert, maybe it was a plain drop
{
ASSERT(_iDragState == DRAGSTATE_ENTERED);
_pdtDragOver->Drop(_pdtoDragIn, grfKeyState, ptl, pdwEffect);
}
}
_PurgeDragDropData();
_DebugConsistencyCheck();
return S_OK;
}
void SFTBarHost::_SetInsertMarkPosition(int iInsert)
{
if (_iInsert != iInsert)
{
_InvalidateInsertMark();
_iInsert = iInsert;
_InvalidateInsertMark();
}
}
BOOL SFTBarHost::_GetInsertMarkRect(LPRECT prc)
{
if (_iInsert >= 0)
{
GetClientRect(_hwndList, prc);
POINT pt;
_ComputeListViewItemPosition(_iInsert, &pt);
int iBottom = pt.y;
int cyEdge = GetSystemMetrics(SM_CYEDGE);
prc->top = iBottom - cyEdge;
prc->bottom = iBottom + cyEdge;
return TRUE;
}
return FALSE;
}
void SFTBarHost::_InvalidateInsertMark()
{
RECT rc;
if (_GetInsertMarkRect(&rc))
{
InvalidateRect(_hwndList, &rc, TRUE);
}
}
void SFTBarHost::_DrawInsertionMark(LPNMLVCUSTOMDRAW plvcd)
{
RECT rc;
if (_GetInsertMarkRect(&rc))
{
FillRect(plvcd->nmcd.hdc, &rc, GetSysColorBrush(COLOR_WINDOWTEXT));
}
}
void SFTBarHost::_DrawSeparator(HDC hdc, int x, int y)
{
RECT rc;
rc.left = x;
rc.top = y;
rc.right = rc.left + _cxTile;
rc.bottom = rc.top + _cySep;
if (!_hTheme)
{
DrawEdge(hdc, &rc, EDGE_ETCHED,BF_TOPLEFT);
}
else
{
DrawThemeBackground(_hTheme, hdc, _iThemePartSep, 0, &rc, 0);
}
}
void SFTBarHost::_DrawSeparators(LPNMLVCUSTOMDRAW plvcd)
{
POINT pt;
RECT rc;
for (int iSep = 0; iSep < _cSep; iSep++)
{
_ComputeListViewItemPosition(_rgiSep[iSep], &pt);
pt.y = pt.y - _cyTilePadding + (_cySepTile - _cySep + _cyTilePadding)/2;
_DrawSeparator(plvcd->nmcd.hdc, pt.x, pt.y);
}
// Also draw a bonus separator at the bottom of the list to separate
// the MFU list from the More Programs button.
if (_iThemePart == SPP_PROGLIST)
{
_ComputeListViewItemPosition(0, &pt);
GetClientRect(_hwndList, &rc);
rc.bottom -= _cySep;
_DrawSeparator(plvcd->nmcd.hdc, pt.x, rc.bottom);
}
}
//****************************************************************************
//
// Accessibility
//
PaneItem *SFTBarHost::_GetItemFromAccessibility(const VARIANT& varChild)
{
if (varChild.lVal)
{
return _GetItemFromLV(varChild.lVal - 1);
}
return NULL;
}
//
// The default accessibility object reports listview items as
// ROLE_SYSTEM_LISTITEM, but we know that we are really a menu.
//
// Our items are either ROLE_SYSTEM_MENUITEM or ROLE_SYSTEM_MENUPOPUP.
//
HRESULT SFTBarHost::get_accRole(VARIANT varChild, VARIANT *pvarRole)
{
HRESULT hr = _paccInner->get_accRole(varChild, pvarRole);
if (SUCCEEDED(hr) && V_VT(pvarRole) == VT_I4)
{
switch (V_I4(pvarRole))
{
case ROLE_SYSTEM_LIST:
V_I4(pvarRole) = ROLE_SYSTEM_MENUPOPUP;
break;
case ROLE_SYSTEM_LISTITEM:
V_I4(pvarRole) = ROLE_SYSTEM_MENUITEM;
break;
}
}
return hr;
}
HRESULT SFTBarHost::get_accState(VARIANT varChild, VARIANT *pvarState)
{
HRESULT hr = _paccInner->get_accState(varChild, pvarState);
if (SUCCEEDED(hr) && V_VT(pvarState) == VT_I4)
{
PaneItem *pitem = _GetItemFromAccessibility(varChild);
if (pitem && pitem->IsCascade())
{
V_I4(pvarState) |= STATE_SYSTEM_HASPOPUP;
}
}
return hr;
}
HRESULT SFTBarHost::get_accKeyboardShortcut(VARIANT varChild, BSTR *pszKeyboardShortcut)
{
if (varChild.lVal)
{
PaneItem *pitem = _GetItemFromAccessibility(varChild);
if (pitem)
{
return CreateAcceleratorBSTR(GetItemAccelerator(pitem, varChild.lVal - 1), pszKeyboardShortcut);
}
}
*pszKeyboardShortcut = NULL;
return E_NOT_APPLICABLE;
}
//
// Default action for cascading menus is Open/Close (depending on
// whether the item is already open); for regular items
// is Execute.
//
HRESULT SFTBarHost::get_accDefaultAction(VARIANT varChild, BSTR *pszDefAction)
{
*pszDefAction = NULL;
if (varChild.lVal)
{
PaneItem *pitem = _GetItemFromAccessibility(varChild);
if (pitem && pitem->IsCascade())
{
DWORD dwRole = varChild.lVal - 1 == _iCascading ? ACCSTR_CLOSE : ACCSTR_OPEN;
return GetRoleString(dwRole, pszDefAction);
}
return GetRoleString(ACCSTR_EXECUTE, pszDefAction);
}
return E_NOT_APPLICABLE;
}
HRESULT SFTBarHost::accDoDefaultAction(VARIANT varChild)
{
if (varChild.lVal)
{
PaneItem *pitem = _GetItemFromAccessibility(varChild);
if (pitem && pitem->IsCascade())
{
if (varChild.lVal - 1 == _iCascading)
{
_SendNotify(_hwnd, SMN_CANCELSHELLMENU);
return S_OK;
}
}
}
return CAccessible::accDoDefaultAction(varChild);
}
//****************************************************************************
//
// Debugging helpers
//
#ifdef FULL_DEBUG
void SFTBarHost::_DebugConsistencyCheck()
{
int i;
int citems;
if (_hwndList && !_fListUnstable)
{
//
// Check that the items in the listview are in their correct positions.
//
citems = ListView_GetItemCount(_hwndList);
for (i = 0; i < citems; i++)
{
PaneItem *pitem = _GetItemFromLV(i);
if (pitem)
{
// Make sure the item number and the iPos are in agreement
ASSERT(pitem->_iPos == _ItemNoToPos(i));
ASSERT(_PosToItemNo(pitem->_iPos) == i);
// Make sure the item is where it should be
POINT pt, ptShould;
_ComputeListViewItemPosition(pitem->_iPos, &ptShould);
ListView_GetItemPosition(_hwndList, i, &pt);
ASSERT(pt.x == ptShould.x);
ASSERT(pt.y == ptShould.y);
}
}
}
}
#endif
// iFile is the zero-based index of the file being requested
// or 0xFFFFFFFF if you don't care about any particular file
//
// puFiles receives the number of files in the HDROP
// or NULL if you don't care about the number of files
//
STDAPI_(HRESULT)
IDataObject_DragQueryFile(IDataObject *pdto, UINT iFile, LPTSTR pszBuf, UINT cch, UINT *puFiles)
{
static FORMATETC const feHdrop =
{ CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
STGMEDIUM stgm;
HRESULT hr;
// Sigh. IDataObject::GetData has a bad prototype and says that
// the first parameter is a modifiable FORMATETC, even though it
// isn't.
hr = pdto->GetData(const_cast<FORMATETC*>(&feHdrop), &stgm);
if (SUCCEEDED(hr))
{
HDROP hdrop = reinterpret_cast<HDROP>(stgm.hGlobal);
if (puFiles)
{
*puFiles = DragQueryFile(hdrop, 0xFFFFFFFF, NULL, 0);
}
if (iFile != 0xFFFFFFFF)
{
hr = DragQueryFile(hdrop, iFile, pszBuf, cch) ? S_OK : E_FAIL;
}
ReleaseStgMedium(&stgm);
}
return hr;
}
/*
* If pidl has an alias, free the original pidl and return the alias.
* Otherwise, just return pidl unchanged.
*
* Expected usage is
*
* pidlTarget = ConvertToLogIL(pidlTarget);
*
*/
STDAPI_(LPITEMIDLIST) ConvertToLogIL(LPITEMIDLIST pidl)
{
LPITEMIDLIST pidlAlias = SHLogILFromFSIL(pidl);
if (pidlAlias)
{
ILFree(pidl);
return pidlAlias;
}
return pidl;
}
//****************************************************************************
//
STDAPI_(HFONT) LoadControlFont(HTHEME hTheme, int iPart, BOOL fUnderline, DWORD dwSizePercentage)
{
LOGFONT lf;
BOOL bSuccess;
if (hTheme)
{
bSuccess = SUCCEEDED(GetThemeFont(hTheme, NULL, iPart, 0, TMT_FONT, &lf));
}
else
{
bSuccess = SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, FALSE);
}
if (bSuccess)
{
// only apply size scaling factor in non-theme case, for themes it makes sense to specify the exact font in the theme
if (!hTheme && dwSizePercentage && dwSizePercentage != 100)
{
lf.lfHeight = (lf.lfHeight * (int)dwSizePercentage) / 100;
lf.lfWidth = 0; // get the closest based on aspect ratio
}
if (fUnderline)
{
lf.lfUnderline = TRUE;
}
return CreateFontIndirect(&lf);
}
return NULL;
}