Click here to Skip to main content
15,894,546 members
Articles / Desktop Programming / ATL

Classic Shell

Rate me:
Please Sign up or sign in to vote.
4.99/5 (135 votes)
23 Feb 2010MIT33 min read 974.5K   10K   195  
Classic Start menu and other shell features for Windows 7 and Vista.
// Classic Shell (c) 2009-2010, Ivo Beltchev
// The sources for Classic Shell are distributed under the MIT open source license

// DragDrop.cpp - handles the drag and drop functionality of CMenuContainer

#include "stdafx.h"
#include "MenuContainer.h"
#include "FNVHash.h"
#include <algorithm>

// CIDropSource - a basic IDropSource implementation. nothing to see here
class CIDropSource: public IDropSource
{
public:
	CIDropSource( bool bRight ) { m_bRight=bRight; m_bClosed=false; m_Time=GetMessageTime(); }
	// IUnknown
	virtual STDMETHODIMP QueryInterface( REFIID riid, void **ppvObject )
	{
		*ppvObject=NULL;
		if (IID_IUnknown==riid || IID_IDropSource==riid)
		{
			*ppvObject=(IDropSource*)this;
			return S_OK;
		}
		return E_NOINTERFACE;
	}

	virtual ULONG STDMETHODCALLTYPE AddRef( void ) { return 1; }
	virtual ULONG STDMETHODCALLTYPE Release( void ) { return 1; }

	// IDropSource
	virtual STDMETHODIMP QueryContinueDrag( BOOL fEscapePressed, DWORD grfKeyState )
	{
		if (!m_bClosed)
		{
			// if the mouse is outside of the menu for more than 4 seconds close the menu
			DWORD pos=GetMessagePos();
			POINT pt={(short)LOWORD(pos),(short)HIWORD(pos)};
			HWND hWnd=WindowFromPoint(pt);
			if (hWnd) hWnd=GetAncestor(hWnd,GA_ROOT);
			wchar_t name[256];
			if (hWnd)
				GetClassName(hWnd,name,_countof(name));
			else
				name[0]=0;
			if (_wcsicmp(name,L"ClassicShell.CMenuContainer")==0)
			{
				m_Time=GetMessageTime();
			}
			else
			{
				int dt=GetMessageTime()-m_Time;
				if (dt>4000)
				{
					m_bClosed=true;
					CMenuContainer::HideStartMenu();
				}
			}
		}
		if (m_bRight)
		{
			if (fEscapePressed || (grfKeyState&MK_LBUTTON))
				return DRAGDROP_S_CANCEL;
			if (!(grfKeyState&MK_RBUTTON))
				return DRAGDROP_S_DROP;
		}
		else
		{
			if (fEscapePressed || (grfKeyState&MK_RBUTTON))
				return DRAGDROP_S_CANCEL;
			if (!(grfKeyState&MK_LBUTTON))
				return DRAGDROP_S_DROP;
		}
		return S_OK;

	}

	virtual STDMETHODIMP GiveFeedback( DWORD dwEffect )
	{
		return DRAGDROP_S_USEDEFAULTCURSORS;
	}

	bool IsClosed( void ) { return m_bClosed; }

private:
	bool m_bRight;
	bool m_bClosed;
	long m_Time;
};

///////////////////////////////////////////////////////////////////////////////

LRESULT CMenuContainer::OnDragOut( int idCtrl, LPNMHDR pnmh, BOOL& bHandled )
{
	if (!(m_Options&CONTAINER_DRAG) || s_bNoDragDrop) return 0;
	NMTOOLBAR *pInfo=(NMTOOLBAR*)pnmh;
	const MenuItem &item=m_Items[pInfo->iItem-ID_OFFSET];
	if (!item.pItem1 || item.id!=MENU_NO) return 0;

	bool bLeft=(GetKeyState(VK_LBUTTON)<0);
	bool bRight=(GetKeyState(VK_RBUTTON)<0);
	if (!bLeft && !bRight) return 0;

	// get IDataObject for the current item
	CComPtr<IShellFolder> pFolder;
	PCUITEMID_CHILD pidl;
	if (FAILED(SHBindToParent(item.pItem1,IID_IShellFolder,(void**)&pFolder,&pidl)))
		return 0;

	CComPtr<IDataObject> pDataObj;
	if (FAILED(pFolder->GetUIObjectOf(NULL,1,&pidl,IID_IDataObject,NULL,(void**)&pDataObj)))
		return 0;

	// force synchronous operation
	CComQIPtr<IAsyncOperation> pAsync=pDataObj;
	if (pAsync)
		pAsync->SetAsyncMode(FALSE);

	// do drag drop
	s_pDragSource=this;
	m_DragIndex=pInfo->iItem-ID_OFFSET;
	CIDropSource src(!bLeft);
	DWORD dwEffect=DROPEFFECT_COPY|DROPEFFECT_MOVE|DROPEFFECT_LINK;
	HRESULT res=SHDoDragDrop(NULL,pDataObj,&src,dwEffect,&dwEffect);

	s_pDragSource=NULL;

	if (src.IsClosed())
	{
		for (std::vector<CMenuContainer*>::iterator it=s_Menus.begin();it!=s_Menus.end();++it)
			if (!(*it)->m_bDestroyed)
				(*it)->PostMessage(WM_CLOSE);
		return 0;
	}

	if (res==DRAGDROP_S_DROP && !m_bDestroyed)
	{
		// check if the item still exists. refresh the menu if it doesn't
		SFGAOF flags=SFGAO_VALIDATE;
		if (FAILED(pFolder->GetAttributesOf(1,&pidl,&flags)))
		{
			SetActiveWindow();
			// close all submenus
			for (int i=(int)s_Menus.size()-1;s_Menus[i]!=this;i--)
				if (!s_Menus[i]->m_bDestroyed)
					s_Menus[i]->DestroyWindow();
			// update menu
			PostRefreshMessage();
		}
	}

	// activate the top non-destroyed menu
	for (int i=(int)s_Menus.size()-1;i>=0;i--)
		if (!s_Menus[i]->m_bDestroyed)
		{
			SetForegroundWindow(s_Menus[i]->m_hWnd);
			s_Menus[i]->SetActiveWindow();
			break;
		}

	return 0;
}

LRESULT CMenuContainer::OnGetObject( int idCtrl, LPNMHDR pnmh, BOOL& bHandled )
{
	NMOBJECTNOTIFY *pInfo=(NMOBJECTNOTIFY*)pnmh;
	if (*pInfo->piid==IID_IDropTarget)
	{
		AddRef();
		pInfo->pObject=(IDropTarget*)this;
		pInfo->hResult=S_OK;
	}
	else
		pInfo->hResult=E_NOINTERFACE;
	return 0;
}

HRESULT STDMETHODCALLTYPE CMenuContainer::DragEnter( IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect )
{
	s_bRightDrag=(grfKeyState&MK_RBUTTON)!=0;
	if (m_pDropTargetHelper)
	{
		POINT p={pt.x,pt.y};
		m_pDropTargetHelper->DragEnter(m_hWnd,pDataObj,&p,*pdwEffect);
	}
	m_DropToolbar=NULL;
	if (!m_pParent && m_Items[0].id==MENU_EMPTY && !s_bShowTopEmpty)
	{
		// when dragging over the main menu, show an (Empty) item at the top so the user can drop items there
		s_bShowTopEmpty=true;
		InitToolbars();
	}
	m_DragHoverTime=GetMessageTime()-10000;
	m_DragHoverItem=-1;
	m_pDragObject=pDataObj;
	return S_OK;
}

HRESULT STDMETHODCALLTYPE CMenuContainer::DragOver( DWORD grfKeyState, POINTL pt, DWORD *pdwEffect )
{
	s_bRightDrag=(grfKeyState&MK_RBUTTON)!=0;
	grfKeyState&=MK_SHIFT|MK_CONTROL;
	if (grfKeyState==MK_SHIFT)
		*pdwEffect&=DROPEFFECT_MOVE;
	else if (grfKeyState==MK_CONTROL)
		*pdwEffect&=DROPEFFECT_COPY;
	else if (grfKeyState==0 && s_pDragSource==this)
		*pdwEffect&=DROPEFFECT_MOVE;
	else
		*pdwEffect&=((s_pDragSource && (s_pDragSource->m_Options&CONTAINER_PROGRAMS))?DROPEFFECT_MOVE:DROPEFFECT_LINK);

	// only accept CFSTR_SHELLIDLIST data
	FORMATETC format={s_ShellFormat,NULL,DVASPECT_CONTENT,-1,TYMED_HGLOBAL};
	if (s_bNoDragDrop || !m_pDropFolder || !(m_Options&CONTAINER_DROP) || m_pDragObject->QueryGetData(&format)!=S_OK)
		*pdwEffect=DROPEFFECT_NONE;

	POINT p={pt.x,pt.y};
	if (m_pDropTargetHelper)
	{
		m_pDropTargetHelper->DragOver(&p,*pdwEffect);
	}

	// find toolbar under the mouse
	CWindow toolbar=NULL;
	for (std::vector<CWindow>::iterator it=m_Toolbars.begin();it!=m_Toolbars.end();++it)
	{
		RECT rc;
		it->GetWindowRect(&rc);
		if (PtInRect(&rc,p))
		{
			toolbar=*it;
			break;
		}
	}
	if (m_DropToolbar.m_hWnd && m_DropToolbar!=toolbar)
	{
		// clear the insert mark
		TBINSERTMARK mark={-1,0};
		m_DropToolbar.SendMessage(TB_SETINSERTMARK,0,(LPARAM)&mark);
	}

	m_DropToolbar=toolbar;
	if (m_DropToolbar.m_hWnd)
	{
		// mouse is over a button
		m_DropToolbar.ScreenToClient(&p);
		int btnIndex=(int)m_DropToolbar.SendMessage(TB_HITTEST,0,(LPARAM)&p);
		int index=-1;
		if (btnIndex>=0)
		{
			TBBUTTON button;
			m_DropToolbar.SendMessage(TB_GETBUTTON,btnIndex,(LPARAM)&button);
			index=(int)button.dwData-ID_OFFSET;

			// set the new insert mark
			TBINSERTMARK mark={btnIndex,0};
			RECT rc;
			m_DropToolbar.SendMessage(TB_GETITEMRECT,btnIndex,(LPARAM)&rc);
			int y=(rc.top+rc.bottom)/2;
			if (p.y<y)
			{
				// insert above
				if (m_Items[index].id!=MENU_NO && m_Items[index].id!=MENU_EMPTY && (index==0 || m_Items[index-1].id!=MENU_NO))
					mark.iButton=-1;
			}
			else
			{
				// insert below
				mark.dwFlags=TBIMHT_AFTER;
				if (m_Items[index].id!=MENU_NO && m_Items[index].id!=MENU_EMPTY && (index==m_Items.size()-1 || m_Items[index+1].id!=MENU_NO))
					mark.iButton=-1;
			}
			if (mark.iButton==-1 && m_Items[index].bFolder && (m_Items[index].bPrograms || m_Items[index].id==MENU_NO))
			{
				m_DropToolbar.SendMessage(TB_SETHOTITEM,btnIndex);
			}
			else
			{
				m_DropToolbar.SendMessage(TB_SETHOTITEM,-1);
			}
			if ((m_Options&CONTAINER_AUTOSORT) && s_pDragSource==this)
				mark.iButton=-1;
			m_DropToolbar.SendMessage(TB_SETINSERTMARK,0,(LPARAM)&mark);
		}
		else
		{
			// clear the insert mark
			TBINSERTMARK mark={-1,0};
			m_DropToolbar.SendMessage(TB_SETINSERTMARK,0,(LPARAM)&mark);
		}

		// check if the hover delay is done and it's time to open the item
		if (index>=0 && index==m_DragHoverItem)
		{
			if ((GetMessageTime()-m_DragHoverTime)>(int)s_HoverTime && m_Submenu!=m_DragHoverItem)
			{
				// expand m_DragHoverItem
				if (!m_Items[index].bFolder || m_Items[index].pItem1)
					ActivateItem(index,ACTIVATE_OPEN,NULL);
				if (!m_Items[index].bFolder)
					m_DropToolbar.SendMessage(TB_SETHOTITEM,-1);
			}
		}
		else
		{
			m_DragHoverItem=index;
			m_DragHoverTime=GetMessageTime();
		}
	}
	
	return S_OK;
}

HRESULT STDMETHODCALLTYPE CMenuContainer::DragLeave( void )
{
	if (m_pDropTargetHelper)
		m_pDropTargetHelper->DragLeave();
	if (m_DropToolbar.m_hWnd)
	{
		// clear the insert mark
		TBINSERTMARK mark={-1,0};
		m_DropToolbar.SendMessage(TB_SETINSERTMARK,0,(LPARAM)&mark);
		m_DropToolbar=NULL;
	}
	m_pDragObject.Release();
	return S_OK;
}

HRESULT STDMETHODCALLTYPE CMenuContainer::Drop( IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect )
{
	m_pDragObject.Release();

	grfKeyState&=MK_SHIFT|MK_CONTROL;
	if (!s_bRightDrag)
	{
		if (grfKeyState==MK_SHIFT)
			*pdwEffect&=DROPEFFECT_MOVE;
		else if (grfKeyState==MK_CONTROL)
			*pdwEffect&=DROPEFFECT_COPY;
		else if (grfKeyState==0 && s_pDragSource==this)
			*pdwEffect&=DROPEFFECT_MOVE;
		else
			*pdwEffect&=((s_pDragSource && (s_pDragSource->m_Options&CONTAINER_PROGRAMS))?DROPEFFECT_MOVE:DROPEFFECT_LINK);
		grfKeyState=0;
	}
	else if (!grfKeyState && (*pdwEffect&DROPEFFECT_LINK))
	{
		// when a file is dragged to the start menu he usually wants to make a shortcut
		// so when right-dragging, and linking is allowed, make it the default
		grfKeyState=MK_SHIFT|MK_CONTROL;
	}

	if (m_pDropTargetHelper)
	{
		POINT p={pt.x,pt.y};
		m_pDropTargetHelper->Drop(pDataObj,&p,*pdwEffect);
	}

	if (!m_DropToolbar.m_hWnd) return S_OK;

	TBINSERTMARK mark={-1,0};
	m_DropToolbar.SendMessage(TB_GETINSERTMARK,0,(LPARAM)&mark);
	int before=mark.iButton;
	if (before<0) return S_OK;
	for (std::vector<CWindow>::iterator it=m_Toolbars.begin();it!=m_Toolbars.end() && it->m_hWnd!=m_DropToolbar.m_hWnd;++it)
		before+=(int)it->SendMessage(TB_BUTTONCOUNT,0,0);
	if (mark.dwFlags==TBIMHT_AFTER && (before!=0 || m_Items[0].id!=MENU_EMPTY))
		before++;

	// clear the insert mark
	mark.iButton=-1;
	m_DropToolbar.SendMessage(TB_SETINSERTMARK,0,(LPARAM)&mark);
	m_DropToolbar=NULL;

	if (s_pDragSource==this && (*pdwEffect&DROPEFFECT_MOVE))
	{
		// dropped in the same menu, just rearrange the items
		if (!(m_Options&CONTAINER_AUTOSORT))
		{
			std::vector<SortMenuItem> items;
			for (std::vector<MenuItem>::const_iterator it=m_Items.begin();it!=m_Items.end();++it)
				if (it->id==MENU_NO)
				{
					SortMenuItem item={it->name,it->nameHash,it->bFolder};
					items.push_back(item);
				}
			SortMenuItem drag=items[m_DragIndex];
			items.erase(items.begin()+m_DragIndex);
			if (before>m_DragIndex)
				before--;
			items.insert(items.begin()+before,drag);
			SaveItemOrder(items);
			PostRefreshMessage();
		}
	}
	else if (m_pDropFolder)
	{
		// simulate dropping the object into the original folder
		CComPtr<IDropTarget> pTarget;
		if (FAILED(m_pDropFolder->CreateViewObject(m_hWnd,IID_IDropTarget,(void**)&pTarget)))
			return S_OK;
		DWORD dwEffect=*pdwEffect;
		if (FAILED(pTarget->DragEnter(pDataObj,grfKeyState,pt,&dwEffect)))
			return S_OK;

		if (s_bRightDrag)
		{
			dwEffect=*pdwEffect;
			pTarget->DragOver(MK_RBUTTON|MK_SHIFT|MK_CONTROL,pt,&dwEffect);
		}
		else
			pTarget->DragOver(grfKeyState,pt,pdwEffect);
		CComQIPtr<IAsyncOperation> pAsync=pDataObj;
		if (pAsync)
			pAsync->SetAsyncMode(FALSE);
		for (std::vector<CMenuContainer*>::iterator it=s_Menus.begin();it!=s_Menus.end();++it)
			if (!(*it)->m_bDestroyed)
				(*it)->EnableWindow(FALSE); // disable all menus
		CMenuContainer *pOld=s_pDragSource;
		if (!s_pDragSource) s_pDragSource=this; // HACK: ensure s_pDragSource is not NULL even if dragging from external source (prevents the menu from closing)
		pTarget->Drop(pDataObj,grfKeyState,pt,pdwEffect);
		s_pDragSource=pOld;
		for (std::vector<CMenuContainer*>::iterator it=s_Menus.begin();it!=s_Menus.end();++it)
			if (!(*it)->m_bDestroyed)
				(*it)->EnableWindow(TRUE); // enable all menus
		SetForegroundWindow(m_hWnd);
		SetActiveWindow();
		m_Toolbars[0].SetFocus();

		if (!(m_Options&CONTAINER_AUTOSORT))
		{
			std::vector<SortMenuItem> items;
			for (std::vector<MenuItem>::const_iterator it=m_Items.begin();it!=m_Items.end();++it)
				if (it->id==MENU_NO)
				{
					SortMenuItem item={it->name,it->nameHash,it->bFolder};
					items.push_back(item);
				}
			SortMenuItem ins={L"",CalcFNVHash(L""),false};
			items.insert(items.begin()+before,ins);
			SaveItemOrder(items);
		}
		PostRefreshMessage();
	}
	return S_OK;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer (Senior)
United States United States
Ivo started programming in 1985 on an Apple ][ clone. He graduated from Sofia University, Bulgaria with a MSCS degree. Ivo has been working as a professional programmer for over 12 years, and as a professional game programmer for over 10. He is currently employed in Pandemic Studios, a video game company in Los Angeles, California.

Comments and Discussions