You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4061 lines
118 KiB
4061 lines
118 KiB
#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;
|
|
}
|