|
|
#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
//---------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) { StrCpyN(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 this fails, then disable "fancy droptarget" since we won't be
// able to manage it properly.
if (!SetWindowSubclass(_hwndList, s_DropTargetSubclassProc, 0, reinterpret_cast<DWORD_PTR>(this))) { ATOMICRELEASE(_pdth); }
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) && !GetSystemMetrics(SM_REMOTECONTROL)) { 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; StrCpyN(lf.lfFaceName, TEXT("Marlett"), ARRAYSIZE(lf.lfFaceName)); _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_SERVER, 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; }
//
// Catch WM_PAINT messages headed to ListView and hide any drop effect
// so it doesn't interfere with painting. WM_PAINT messages might nest
// under extreme conditions, so do this only at outer level.
//
LRESULT CALLBACK SFTBarHost::s_DropTargetSubclassProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) { SFTBarHost *self = reinterpret_cast<SFTBarHost *>(dwRefData); LRESULT lres;
switch (uMsg) { case WM_PAINT:
// If entering outermost paint cycle, hide the drop feedback
++self->_cPaint; if (self->_cPaint == 1 && self->_pdth) { self->_pdth->Show(FALSE); } lres = DefSubclassProc(hwnd, uMsg, wParam, lParam);
// If exiting outermost paint cycle, restore the drop feedback
// Don't decrement _cPaint until really finished because
// Show() will call UpdateWindow and trigger a nested paint cycle.
if (self->_cPaint == 1 && self->_pdth) { self->_pdth->Show(TRUE); } --self->_cPaint;
return lres;
case WM_NCDESTROY: RemoveWindowSubclass(hwnd, s_DropTargetSubclassProc, uIdSubclass); break; }
return DefSubclassProc(hwnd, uMsg, wParam, lParam); }
//
// 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);
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); } _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) && !GetSystemMetrics(SM_REMOTECONTROL)) { 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: StrCpyN(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);
// FUSION: When we call out to 3rd party code we want it to use
// the process default context. This means that the 3rd party code will get
// v5 in the explorer process. However, if shell32 is hosted in a v6 process,
// then the 3rd party code will still get v6.
ULONG_PTR cookie = 0; ActivateActCtx(NULL, &cookie);
HRESULT hr = pcm->InvokeCommand(reinterpret_cast<LPCMINVOKECOMMANDINFO>(pici)); if (cookie != 0) { DeactivateActCtx(0, cookie); }
return hr; }
LRESULT SFTBarHost::_OnLVNItemActivate(LPNMITEMACTIVATE pnmia) { return _ActivateItem(pnmia->iItem, 0); }
LRESULT SFTBarHost::_ActivateItem(int iItem, DWORD dwFlags) { 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() { ASSERT( 0 != _lRef ); ULONG cRef = InterlockedDecrement(&_lRef); if ( 0 == cRef ) { delete this; } return cRef; }
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; }
|