Click here to Skip to main content
15,897,032 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 981.4K   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

#pragma once

#include "SkinManager.h"
#include <vector>
#include <map>

enum TMenuID
{
	MENU_NO=0,
	MENU_LAST=0,
	MENU_SEPARATOR,
	MENU_EMPTY,

	// standard menu items
	MENU_PROGRAMS,
	MENU_FAVORITES,
	MENU_DOCUMENTS,
		MENU_USERFILES,
		MENU_USERDOCUMENTS,
		MENU_USERPICTURES,
	MENU_SETTINGS,
		MENU_CONTROLPANEL,
		MENU_NETWORK,
		MENU_PRINTERS,
		MENU_TASKBAR,
		MENU_FEATURES,
		MENU_CLASSIC_SETTINGS,
	MENU_SEARCH,
		MENU_SEARCH_FILES,
		MENU_SEARCH_PRINTER,
		MENU_SEARCH_COMPUTERS,
		MENU_SEARCH_PEOPLE,
	MENU_HELP,
	MENU_RUN,
	MENU_LOGOFF,
	MENU_DISCONNECT,
	MENU_UNDOCK,
	MENU_SHUTDOWN_BOX,

	// additional commands
	MENU_CUSTOM, // used for any custom item
	MENU_SLEEP,
	MENU_HIBERNATE,
	MENU_RESTART,
	MENU_SHUTDOWN,
	MENU_SWITCHUSER,
};

struct StdMenuItem
{
	TMenuID id;
	const char *key; // localization key
	const wchar_t *name; // default name
	int icon; // index in shell32.dll
	TMenuID submenuID; // MENU_NO if no submenu
	const KNOWNFOLDERID *folder1; // NULL if not used
	const KNOWNFOLDERID *folder2; // NULL if not used
	const char *tipKey; // localization key for the tooltip
	const wchar_t *tip; // default tooltip
	const StdMenuItem *submenu;
	const wchar_t *link;
	const wchar_t *command;
	const wchar_t *iconPath;
};

class CMenuAccessible;

// CMenuContainer - implementation of a single menu box. Contains one or more vertical toolbars
class CMenuContainer: public CWindowImpl<CMenuContainer>, public IDropTarget
{
public:
	DECLARE_WND_CLASS_EX(L"ClassicShell.CMenuContainer",CS_DROPSHADOW,COLOR_MENU)

	// message handlers
	BEGIN_MSG_MAP( CMenuContainer )
		// forward all messages to m_pMenu2 and m_pMenu3 to ensure the context menu functions properly
		if (m_pMenu3)
		{
			if (SUCCEEDED(m_pMenu3->HandleMenuMsg2(uMsg,wParam,lParam,&lResult)))
				return TRUE;
		}
		else if (m_pMenu2)
		{
			if (SUCCEEDED(m_pMenu2->HandleMenuMsg(uMsg,wParam,lParam)))
			{
				lResult=0;
				return TRUE;
			}
		}
		MESSAGE_HANDLER( WM_CREATE, OnCreate )
		MESSAGE_HANDLER( WM_DESTROY, OnDestroy )
		MESSAGE_HANDLER( MCM_REFRESH, OnRefresh )
		MESSAGE_HANDLER( WM_ERASEBKGND, OnEraseBkgnd )
		MESSAGE_HANDLER( WM_ACTIVATE, OnActivate )
		MESSAGE_HANDLER( WM_MOUSEACTIVATE, OnMouseActivate )
		MESSAGE_HANDLER( WM_CONTEXTMENU, OnContextMenu )
		MESSAGE_HANDLER( WM_TIMER, OnTimer )
		MESSAGE_HANDLER( WM_SYSCOMMAND, OnSysCommand )
		MESSAGE_HANDLER( WM_GETOBJECT, OnGetAccObject )
		MESSAGE_HANDLER( MCM_SETHOTITEM, OnSetHotItem )
		NOTIFY_RANGE_CODE_HANDLER( ID_TOOLBAR_FIRST, ID_TOOLBAR_LAST, NM_CUSTOMDRAW, OnCustomDraw )
		NOTIFY_RANGE_CODE_HANDLER( ID_TOOLBAR_FIRST, ID_TOOLBAR_LAST, NM_CLICK, OnClick )
		NOTIFY_RANGE_CODE_HANDLER( ID_TOOLBAR_FIRST, ID_TOOLBAR_LAST, NM_KEYDOWN, OnKeyDown )
		NOTIFY_RANGE_CODE_HANDLER( ID_TOOLBAR_FIRST, ID_TOOLBAR_LAST, NM_CHAR, OnChar )
		NOTIFY_RANGE_CODE_HANDLER( ID_TOOLBAR_FIRST, ID_TOOLBAR_LAST, TBN_HOTITEMCHANGE, OnHotItemChange )
		NOTIFY_RANGE_CODE_HANDLER( ID_TOOLBAR_FIRST, ID_TOOLBAR_LAST, TBN_GETINFOTIP, OnGetInfoTip )
		NOTIFY_RANGE_CODE_HANDLER( ID_TOOLBAR_FIRST, ID_TOOLBAR_LAST, TBN_DRAGOUT, OnDragOut )
		NOTIFY_RANGE_CODE_HANDLER( ID_TOOLBAR_FIRST, ID_TOOLBAR_LAST, TBN_GETOBJECT, OnGetObject )
		NOTIFY_HANDLER( ID_PAGER, PGN_CALCSIZE, OnPagerCalcSize )
		NOTIFY_HANDLER( ID_PAGER, PGN_SCROLL, OnPagerScroll )
	END_MSG_MAP()

	// options when creating a container
	enum
	{
		CONTAINER_LARGE        = 0x0001, // use large icons
		CONTAINER_MULTICOLUMN  = 0x0002, // use multiple columns instead of a pager
		CONTAINER_NOSUBFOLDERS = 0x0004, // don't go into subfolders (for control panel)
		CONTAINER_PROGRAMS     = 0x0008, // this is a folder from the Start Menu hierarchy (drop operations prefer link over move)
		CONTAINER_DOCUMENTS    = 0x0010, // sort by time, limit the count (for recent documents)
		CONTAINER_LINK         = 0x0020, // this is an expanded link to a folder (always in a pager)
		CONTAINER_ADDTOP       = 0x0040, // put standard items at the top
		CONTAINER_DRAG         = 0x0080, // allow items to be dragged out
		CONTAINER_DROP         = 0x0100, // allow dropping of items
		CONTAINER_LEFT         = 0x0200, // the window is aligned on the left
		CONTAINER_TOP          = 0x0400, // the window is aligned on the top
		CONTAINER_AUTOSORT     = 0x0800, // the menu is always in alphabetical order
	};

	CMenuContainer( CMenuContainer *pParent, int index, int options, const StdMenuItem *pStdItem, PIDLIST_ABSOLUTE path1, PIDLIST_ABSOLUTE path2, const CString &regName );
	~CMenuContainer( void );

	void InitItems( void );
	void InitToolbars( void );

	static bool CloseStartMenu( void );
	static void HideStartMenu( void );
	static bool IsMenuOpened( void ) { return !s_Menus.empty(); }
	static bool IgnoreTaskbarTimers( void ) { return !s_Menus.empty() && (s_TaskbarState&ABS_AUTOHIDE); }
	static HWND ToggleStartMenu( HWND startButton, bool bKeyboard );

	// IUnknown
	virtual STDMETHODIMP QueryInterface( REFIID riid, void **ppvObject )
	{
		*ppvObject=NULL;
		if (IID_IUnknown==riid || IID_IDropTarget==riid)
		{
			AddRef();
			*ppvObject=static_cast<IDropTarget*>(this);
			return S_OK;
		}
		return E_NOINTERFACE;
	}

	virtual ULONG STDMETHODCALLTYPE AddRef( void ) 
	{ 
		return InterlockedIncrement(&m_RefCount);
	}

	virtual ULONG STDMETHODCALLTYPE Release( void )
	{
		long nTemp=InterlockedDecrement(&m_RefCount);
		if (!nTemp) delete this;
		return nTemp;
	}

	// IDropTarget
	virtual HRESULT STDMETHODCALLTYPE DragEnter( IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect );
	virtual HRESULT STDMETHODCALLTYPE DragOver( DWORD grfKeyState, POINTL pt, DWORD *pdwEffect );
	virtual HRESULT STDMETHODCALLTYPE DragLeave( void );
	virtual HRESULT STDMETHODCALLTYPE Drop( IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect );

protected:
	LRESULT OnCreate( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled );
	LRESULT OnDestroy( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled );
	LRESULT OnRefresh( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled );
	LRESULT OnEraseBkgnd( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled );
	LRESULT OnActivate( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled );
	LRESULT OnMouseActivate( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled );
	LRESULT OnContextMenu( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled );
	LRESULT OnTimer( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled );
	LRESULT OnSysCommand( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled );
	LRESULT OnGetAccObject( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled );
	LRESULT OnSetHotItem( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled );
	LRESULT OnCustomDraw( int idCtrl, LPNMHDR pnmh, BOOL& bHandled );
	LRESULT OnClick( int idCtrl, LPNMHDR pnmh, BOOL& bHandled );
	LRESULT OnKeyDown( int idCtrl, LPNMHDR pnmh, BOOL& bHandled );
	LRESULT OnChar( int idCtrl, LPNMHDR pnmh, BOOL& bHandled );
	LRESULT OnHotItemChange( int idCtrl, LPNMHDR pnmh, BOOL& bHandled );
	LRESULT OnGetInfoTip( int idCtrl, LPNMHDR pnmh, BOOL& bHandled );
	LRESULT OnDragOut( int idCtrl, LPNMHDR pnmh, BOOL& bHandled );
	LRESULT OnGetObject( int idCtrl, LPNMHDR pnmh, BOOL& bHandled );
	LRESULT OnPagerCalcSize( int idCtrl, LPNMHDR pnmh, BOOL& bHandled );
	LRESULT OnPagerScroll( int idCtrl, LPNMHDR pnmh, BOOL& bHandled );
	virtual void OnFinalMessage( HWND ) { Release(); }

private:
	// description of a menu item
	struct MenuItem
	{
		TMenuID id; // if pStdItem!=NULL, this is pStdItem->id. otherwise it can only be MENU_NO, MENU_SEPARATOR or MENU_EMPTY
		const StdMenuItem *pStdItem; // NULL if not a standard menu item
		CString name;
		unsigned int nameHash;
		int icon;
		int column; // index in m_Toolbars
		int btnIndex; // button index in the toolbar
		bool bFolder; // this is a folder - draw arrow
		bool bLink; // this is a link (if a link to a folder is expanded it is always in a pager)
		bool bPrograms; // this item is part of the Start Menu folder hierarchy

		// pair of shell items. 2 items are used to combine a user folder with a common folder (I.E. user programs/common programs)
		PIDLIST_ABSOLUTE pItem1;
		PIDLIST_ABSOLUTE pItem2;
		union
		{
			UINT accelerator; // accelerator character, 0 if none
			FILETIME time; // timestamp of the file (for sorting recent documents)
		};

		bool operator<( const MenuItem &x ) const
		{
			if (btnIndex<x.btnIndex) return true;
			if (btnIndex>x.btnIndex) return false;
			if (bFolder && !x.bFolder) return true;
			if (!bFolder && x.bFolder) return false;
			if (bFolder)
			{
				const wchar_t *drive1=name.IsEmpty()?NULL:wcschr((const wchar_t*)name+1,':');
				const wchar_t *drive2=x.name.IsEmpty()?NULL:wcschr((const wchar_t*)x.name+1,':');
				if (drive1 && !drive2) return true;
				if (!drive1 && drive2) return false;
				if (drive1)
					return drive1[-1]<drive2[-1];
			}
			return CompareString(LOCALE_USER_DEFAULT,LINGUISTIC_IGNORECASE,name,-1,x.name,-1)==CSTR_LESS_THAN;
		}
	};

	struct SortMenuItem
	{
		CString name;
		unsigned int nameHash;
		bool bFolder;

		bool operator<( const SortMenuItem &x ) const
		{
			if (bFolder && !x.bFolder) return true;
			if (!bFolder && x.bFolder) return false;
			if (bFolder)
			{
				const wchar_t *drive1=name.IsEmpty()?NULL:wcschr((const wchar_t*)name+1,':');
				const wchar_t *drive2=x.name.IsEmpty()?NULL:wcschr((const wchar_t*)x.name+1,':');
				if (drive1 && !drive2) return true;
				if (!drive1 && drive2) return false;
				if (drive1)
					return drive1[-1]<drive2[-1];
			}
			return CompareString(LOCALE_USER_DEFAULT,LINGUISTIC_IGNORECASE,name,-1,x.name,-1)==CSTR_LESS_THAN;
		}
	};

	LONG m_RefCount;
	bool m_bRefreshPosted;
	bool m_bDestroyed; // the menu is destroyed but not yet deleted
	int m_Options;
	const StdMenuItem *m_pStdItem; // the first item
	CMenuContainer *m_pParent; // parent menu
	int m_Submenu; // the item index of the opened submenu
	int m_ParentIndex; // the index of this menu in the parent (usually matches m_pParent->m_Submenu)
	CString m_RegName; // name of the registry key to store the item order
	PIDLIST_ABSOLUTE m_Path1;
	PIDLIST_ABSOLUTE m_Path2;
	CComPtr<IShellFolder> m_pDropFolder; // the primary folder (used only as a drop target)

	std::vector<MenuItem> m_Items; // all items in the menu (including separators)
	std::vector<CWindow> m_Toolbars; // one toolbar for each menu column
	CWindow m_Pager; // pager control if needed
	CComQIPtr<IContextMenu2> m_pMenu2; // additional interfaces used when a context menu is displayed
	CComQIPtr<IContextMenu3> m_pMenu3;

	CWindow m_DropToolbar;
	int m_DragHoverTime;
	int m_DragHoverItem;
	int m_DragIndex; // the index of the item being dragged
	CComPtr<IDropTargetHelper> m_pDropTargetHelper; // to show images while dragging
	CComPtr<IDataObject> m_pDragObject;

	LONG m_ClickTime; // last click time (for detecting double clicks)
	POINT m_ClickPos; // last click position (for detecting double clicks)
	DWORD m_HotPos; // last mouse position over a hot item (used to ignore TBN_HOTITEMCHANGE when the mouse didn't really move)
	int m_HoverItem; // item under the mouse (used for opening a submenu when the mouse hovers over an item)
	int m_ContextItem; // force this to be the hot item while a context menu is up
	HBITMAP m_Bitmap; // the background bitmap
	HRGN m_Region; // the outline region
	RECT m_rContent;
	CMenuAccessible *m_pAccessible;

	// additional commands for the context menu
	enum
	{
		CMD_OPEN_ALL=1,
		CMD_SORT,
		CMD_AUTOSORT,
		CMD_NEWFOLDER,

		CMD_LAST
	};

	// ways to activate a menu item
	enum TActivateType
	{
		ACTIVATE_SELECT, // just selects the item
		ACTIVATE_OPEN, // opens the submenu or selects if not a menu
		ACTIVATE_OPEN_KBD, // same as above, but when done with a keyboard
		ACTIVATE_EXECUTE, // executes the item
		ACTIVATE_MENU, // shows context menu
	};

	enum
	{
		// control IDs
		ID_PAGER=1,
		ID_TOOLBAR_FIRST=2,
		ID_TOOLBAR_LAST=1000,

		// added to the m_Item index to get the button user data. ensures the user data is used correctly
		ID_OFFSET=1000,

		// timer ID
		TIMER_HOVER=1,

		MCM_REFRESH=WM_USER+10, // posted to force the container to refresh its contents
		MCM_SETHOTITEM=WM_USER+11, // sets the hot item. wParam is the nameHash of the item
	};

	CWindow CreateToolbar( int id );
	// pPt - optional point in screen space (used only by ACTIVATE_EXECUTE and ACTIVATE_MENU)
	void ActivateItem( int index, TActivateType type, const POINT *pPt );
	void ShowKeyboardCues( void );
	void SetActiveWindow( void );
	void CreateBackground( int width, int height ); // width, height - the content area
	void PostRefreshMessage( void );
	void SaveItemOrder( const std::vector<SortMenuItem> &items );
	void LoadItemOrder( void );
	void FadeOutItem( int index );

	static int s_MaxRecentDocuments; // limit for the number of recent documents
	static bool s_bScrollMenus; // global scroll menus setting
	static bool s_bRTL; // RTL layout
	static bool s_bKeyboardCues; // show keyboard cues
	static bool s_bExpandRight; // prefer expanding submenus to the right
	static bool s_bBehindTaskbar; // the main menu is behind the taskbar (when the taskbar is horizontal)
	static bool s_bShowTopEmpty; // shows the empty item on the top menu so the user can drag items there
	static bool s_bNoDragDrop; // disables drag/drop
	static bool s_bNoContextMenu; // disables the context menu
	static bool s_bExpandLinks; // expand links to folders
	static char s_bActiveDirectory; // the Active Directory services are available (-1 - uninitialized)
	static CMenuContainer *s_pDragSource; // the source of the current drag operation
	static bool s_bRightDrag; // dragging with the right mouse button
	static RECT s_MainRect; // area of the main monitor
	static DWORD s_TaskbarState; // the state of the taskbar (ABS_AUTOHIDE and ABS_ALWAYSONTOP)
	static DWORD s_HoverTime;
	static DWORD s_SubmenuStyle;
	static CLIPFORMAT s_ShellFormat; // CFSTR_SHELLIDLIST
	static CComPtr<IShellFolder> s_pDesktop; // cached pointer of the desktop object
	static HWND s_LastFGWindow; // stores the foreground window to restore later when the menu closes

	static std::vector<CMenuContainer*> s_Menus; // all menus, in cascading order
	static std::map<unsigned int,int> s_PagerScrolls; // scroll offset for each sub menu

	static MenuSkin s_Skin;

	static LRESULT CALLBACK ToolbarSubclassProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData );
	static LRESULT CALLBACK PagerSubclassProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData );

	friend class COwnerWindow;
	friend class CMenuAccessible;
};

class CMenuFader: public CWindowImpl<CMenuFader>
{
public:
	CMenuFader( HBITMAP bmp, HRGN region, int duration, RECT &rect );
	~CMenuFader( void );
	DECLARE_WND_CLASS_EX(L"ClassicShell.CMenuFader",0,COLOR_MENU)

	// message handlers
	BEGIN_MSG_MAP( CMenuFader )
		MESSAGE_HANDLER( WM_ERASEBKGND, OnEraseBkgnd )
		MESSAGE_HANDLER( WM_TIMER, OnTimer )
	END_MSG_MAP()

	void Create( void );

	static void ClearAll( void );

protected:
	LRESULT OnEraseBkgnd( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled );
	LRESULT OnTimer( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled );
	virtual void OnFinalMessage( HWND ) { PostQuitMessage(0); delete this; }

private:
	int m_Time0;
	int m_Duration;
	int m_LastTime;
	HBITMAP m_Bitmap;
	HRGN m_Region;
	RECT m_Rect;

	static std::vector<CMenuFader*> s_Faders;
};

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