Click here to Skip to main content
12,635,802 members (26,002 online)
Click here to Skip to main content

Stats

601.6K views
9.1K downloads
193 bookmarked
Posted

Classic Shell

, 23 Feb 2010 MIT
Classic Start menu and other shell features for Windows 7 and Vista.
ClassicShellSetup.exe
ClassicShell
ClassicExplorer
ClassicCopyExt.rgs
ClassicExplorer.rgs
ClassicExplorer32.def
ClassicExplorer64.def
Explorer.ini
ExplorerBand.rgs
ExplorerBHO.rgs
ExplorerL10N.ini
up.ico
up2Disabled.ico
up2Hot.ico
up2Normal.ico
up2Pressed.ico
upDisabled.ico
ClassicShellSetup
ClassicShell.ico
ClassicShellBanner.jpg
ClassicShellSetup.manifest
ClassicShellSetup32
ClassicShellSetup32.vdproj
ClassicShellSetup64
ClassicShellSetup64.vdproj
ClassicStartMenu
ClassicStartMenu.manifest
ClassicStartMenuDLL
StartMenu.ini
StartMenuItems.ini
StartMenuL10N.ini
Docs
ClassicExplorer_files
after.png
before.png
ClassicShell.png
FolderView.gif
iconoffset.png
statusbar.png
titlebar.png
toolbar.png
ClassicShellEULA.rtf
ClassicShellReadme.rtf
ClassicStartMenu_files
ClassicShell.png
screenshot.png
skins.gif
ParseSettings
Skins
ClassicSkin
main_bitmap.bmp
selection.bmp
VistaAero
main_bitmap.bmp
selection.bmp
Win7Aero
main_bitmap.bmp
selection.bmp
Win7Basic
main_bitmap.bmp
selection.bmp
// 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

Share

About the Author

Ivo Beltchev
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.

You may also be interested in...

Pro
| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.161208.2 | Last Updated 23 Feb 2010
Article Copyright 2009 by Ivo Beltchev
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid