// Classic Shell (c) 2009-2017, Ivo Beltchev
// Open-Shell (c) 2017-2018, The Open-Shell Team
// Confidential information of Ivo Beltchev. Not for disclosure or distribution without prior written consent from the author

// ProgramsTree.cpp - contains the implementation of the programs tree for the Win7 style

#include "stdafx.h"
#include "ProgramsTree.h"
#include "MenuContainer.h"
#include "MetroLinkManager.h"
#include "Translations.h"
#include "Settings.h"
#include "ResourceHelper.h"
#include "FNVHash.h"
#include "LogManager.h"
#include "StartMenuDLL.h"
#include "SettingsUI.h"
#include <uxtheme.h>
#include <algorithm>

bool CProgramsTree::s_bFoldersFirst;

const wchar_t *ORDER_PREFIX=L"#";

CProgramsTree::CProgramsTree( void )
{
	m_RefCount=1;
	m_pOwner=NULL;
	m_ImageList=NULL;
	m_bHoverTimer=m_bTrackMouse=m_bRefreshPosted=m_bAutoSort=m_bDragApps=m_bInsertAfter=false;
	m_LastMousePos.x=m_LastMousePos.y=0;
	m_MinX=m_MaxX=m_RootX=0;
	m_DropLocation=DROP_NOWHERE;
	m_DragItem=m_DropTarget=m_HoverItem=NULL;
	m_TreeTheme=m_ScrollTheme=NULL;
	m_InsertMark=NULL;
}

// Subclass the tooltip to delay the tip when the mouse moves from one tree item to the next
static LRESULT CALLBACK SubclassInfoTipProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData )
{
	if (uMsg==TTM_UPDATE)
	{
		int time=(int)SendMessage(hWnd,TTM_GETDELAYTIME,TTDT_RESHOW,0);
		SetTimer(hWnd,'CLSH',time,NULL);
		return 0;
	}
	if (uMsg==WM_TIMER && wParam=='CLSH')
	{
		KillTimer(hWnd,wParam);
		DefSubclassProc(hWnd,TTM_UPDATE,0,0);
		return 0;
	}
	return DefSubclassProc(hWnd,uMsg,wParam,lParam);
}

void CProgramsTree::Create( CMenuContainer *pOwner )
{
	m_pOwner=pOwner;
	HWND hWnd=CreateWindowEx(0,WC_TREEVIEW,NULL,WS_CHILD|TVS_EDITLABELS|TVS_FULLROWSELECT|(CMenuContainer::s_TipHideTime?TVS_INFOTIP:0)|TVS_NOHSCROLL|TVS_SHOWSELALWAYS|TVS_NONEVENHEIGHT,0,0,0,0,pOwner->m_hWnd,NULL,g_Instance,NULL);
	TreeView_SetExtendedStyle(hWnd,TVS_EX_AUTOHSCROLL,TVS_EX_AUTOHSCROLL);
	const MenuSkin &skin=CMenuContainer::s_Skin;
	m_TreeTheme=OpenThemeData(hWnd,L"treeview");
	if (skin.BHasScrollbar)
		m_ScrollTheme=OpenThemeData(hWnd,L"scrollbar");

	const MenuSkin::ItemDrawSettings &settings=skin.ItemSettings[MenuSkin::PROGRAMS_TREE_ITEM];

	SendMessage(hWnd,WM_SETFONT,(LPARAM)settings.font,FALSE);

	TreeView_SetItemHeight(hWnd,settings.itemHeight);

	SubclassWindow(hWnd);
	m_ImageList=ImageList_Create(CItemManager::SMALL_ICON_SIZE+settings.iconPadding.left+settings.iconPadding.right,CItemManager::SMALL_ICON_SIZE,ILC_COLOR32|ILC_MASK|ILC_MIRROR,1,16);
	TreeView_SetImageList(hWnd,m_ImageList,TVSIL_NORMAL);
	TreeView_SetIndent(hWnd,TreeView_GetIndent(hWnd)+skin.Programs_indent);
	m_DragItem=NULL;
	m_DropLocation=DROP_NOWHERE;
	m_DropTarget=NULL;
	m_bRefreshPosted=false;
	m_pDropTargetProxy=new CDropTargetProxy(this);
	RegisterDragDrop(hWnd,m_pDropTargetProxy);
	m_LastMousePos.x=m_LastMousePos.y=-1;
	s_bFoldersFirst=GetSettingBool(L"FoldersFirst");

	HWND tooltip=TreeView_GetToolTips(m_hWnd);
	SendMessage(tooltip,TTM_SETDELAYTIME,TTDT_AUTOPOP,CMenuContainer::s_TipHideTime);
	SendMessage(tooltip,TTM_SETDELAYTIME,TTDT_INITIAL,CMenuContainer::s_TipShowTime);
	SendMessage(tooltip,TTM_SETDELAYTIME,TTDT_RESHOW,CMenuContainer::s_TipShowTime);
	SetWindowSubclass(tooltip,SubclassInfoTipProc,'CLSH',0);

	AddRef(); // this will be released in OnFinalMeessage
}

LRESULT CALLBACK CProgramsTree::CustomWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	CProgramsTree *pThis=static_cast<CProgramsTree *>((CWindowImpl<CProgramsTree>*)hWnd);
	if (!pThis->m_ScrollTheme)
	{
		return WindowProc(hWnd,uMsg,wParam,lParam);
	}
	DWORD oldThread=g_CustomScrollbarThread;
	g_CustomScrollbarThread=GetCurrentThreadId();
	g_CustomScrollbarTheme=pThis->m_ScrollTheme;
	LRESULT res=WindowProc(hWnd,uMsg,wParam,lParam);
	g_CustomScrollbarThread=oldThread;
	if (!oldThread)
		g_CustomScrollbarTheme=NULL;
	return res;
}

LRESULT CProgramsTree::OnDestroy( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
{
	m_pDropTargetProxy->Reset();
	m_pDropTargetProxy=NULL;
	RevokeDragDrop(m_hWnd);
	ImageList_Destroy(m_ImageList);
	m_ImageList=NULL;
	if (m_TreeTheme)
	{
		CloseThemeData(m_TreeTheme);
		m_TreeTheme=NULL;
	}
	if (m_ScrollTheme)
	{
		CloseThemeData(m_ScrollTheme);
		m_ScrollTheme=NULL;
	}
	return 0;
}

LRESULT CProgramsTree::OnDeleteItem( int idCtrl, LPNMHDR pnmh, BOOL& bHandled )
{
	// free data
	NMTREEVIEW *pItem=(NMTREEVIEW*)pnmh;
	delete (CTreeItem*)pItem->itemOld.lParam;
	return 0;
}

LRESULT CProgramsTree::OnItemExpanding( int idCtrl, LPNMHDR pnmh, BOOL& bHandled )
{
	// generate child items
	NMTREEVIEW *pView=(NMTREEVIEW*)pnmh;
	if (pView->itemNew.state&TVIS_EXPANDEDONCE)
		return 0;
	CreateFolderItems(pView->itemNew.hItem);
	HTREEITEM hChild=TreeView_GetChild(m_hWnd,pView->itemNew.hItem);
	if (hChild && !TreeView_GetNextSibling(m_hWnd,hChild))
		PostMessage(TVM_EXPAND,TVE_EXPAND,(LPARAM)hChild);
	return 0;
}

LRESULT CProgramsTree::OnSingleExpand( int idCtrl, LPNMHDR pnmh, BOOL& bHandled )
{
	return TVNRET_SKIPOLD;
}

LRESULT CProgramsTree::OnBeginDrag( int idCtrl, LPNMHDR pnmh, BOOL& bHandled )
{
	NMTREEVIEW *pDrag=(NMTREEVIEW*)pnmh;
	CTreeItem *pItem=(CTreeItem*)pDrag->itemNew.lParam;
	if (!pItem) return 0;
	m_DragItem=pDrag->itemNew.hItem;
	m_bDragApps=pItem->bApps;
	if (pItem->bApps)
	{
		m_pOwner->DragOutApps(pItem->pItemInfo1);
	}
	else
	{
		m_pOwner->DragTreeItem(pItem,pItem->bApp);
	}
	m_DropLocation=DROP_NOWHERE;
	m_DragItem=NULL;
	return 0;
}

LRESULT CProgramsTree::OnKeyDown( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
{
	if (wParam==VK_RETURN)
	{
		HTREEITEM hItem;
		const CTreeItem *pItem=GetSelectedItem(hItem);
		if (!pItem) return 0;
		if (pItem->bFolder)
			TreeView_Expand(m_hWnd,hItem,TVE_TOGGLE);
		else if (pItem->pItemInfo1)
		{
			RECT rc;
			TreeView_GetItemRect(m_hWnd,hItem,&rc,FALSE);
			MapWindowPoints(m_pOwner->m_hWnd,&rc);
			CMenuContainer::ActivateData data;
			data.bProgramsTree=true;
			m_pOwner->ActivateTreeItem(pItem,rc,CMenuContainer::ACTIVATE_EXECUTE,NULL,&data);
		}
		return 0;
	}
	if (wParam==VK_TAB)
	{
		m_pOwner->SendMessage(WM_KEYDOWN,wParam,lParam);
		return 0;
	}

	// Del to delete, F2 to rename
	if (wParam==VK_DELETE || wParam==VK_F2)
	{
		HTREEITEM hItem;
		const CTreeItem *pItem=GetSelectedItem(hItem);
		if (!pItem) return 0;
		if (pItem->pItemInfo1)
		{
			RECT rc;
			TreeView_GetItemRect(m_hWnd,hItem,&rc,FALSE);
			MapWindowPoints(m_pOwner->m_hWnd,&rc);
			CMenuContainer::ActivateData data;
			data.bProgramsTree=true;
			m_pOwner->ActivateTreeItem(pItem,rc,wParam==VK_F2?CMenuContainer::ACTIVATE_RENAME:CMenuContainer::ACTIVATE_DELETE,NULL,&data);
			if (data.command==CMenuContainer::CMD_DELETE)
			{
				HTREEITEM hParent=TreeView_GetParent(m_hWnd,hItem);
				if (hParent)
				{
					TVITEM item={TVIF_PARAM,hParent};
					TreeView_GetItem(m_hWnd,&item);
					const CTreeItem *pParent=(CTreeItem*)item.lParam;
					PostRefreshMessage(pParent->pItemInfo1);
				}
				else
					PostRefreshMessage();
			}
			else if (data.command==CMenuContainer::CMD_RENAME)
			{
				if (data.pNewItemInfo)
					SaveRenamedOrder(hItem,data.pNewItemInfo);
				PostRefreshMessage(data.pNewItemInfo);
			}
		}
		return 0;
	}

	if (wParam==VK_ESCAPE)
	{
		m_pOwner->SetMenuMode(CMenuContainer::MODE_NORMAL);
		m_pOwner->SetHotItem(m_pOwner->m_ProgramButtonIndex);
		return 0;
	}

	HTREEITEM hSelect1=TreeView_GetSelection(m_hWnd);
	DWORD state1=hSelect1?TreeView_GetItemState(m_hWnd,hSelect1,TVIS_EXPANDED)&TVIS_EXPANDED:0;

	LRESULT res=DefWindowProc(uMsg,wParam,lParam);

	HTREEITEM hSelect2=TreeView_GetSelection(m_hWnd);
	DWORD state2=hSelect2?TreeView_GetItemState(m_hWnd,hSelect2,TVIS_EXPANDED)&TVIS_EXPANDED:0;

	if (hSelect1==hSelect2 && state1==state2)
	{
		if (wParam==VK_UP || wParam==VK_DOWN || wParam==VK_LEFT || wParam==VK_RIGHT)
		{
			m_pOwner->SendMessage(uMsg,wParam,lParam);
		}
	}
	return res;
}

LRESULT CProgramsTree::OnSysKeyDown( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
{
	if (wParam==VK_RETURN)
	{
		HTREEITEM hItem;
		const CTreeItem *pItem=GetSelectedItem(hItem);
		if (!pItem) return 0;
		if (pItem->pItemInfo1)
		{
			RECT rc;
			TreeView_GetItemRect(m_hWnd,hItem,&rc,FALSE);
			MapWindowPoints(m_pOwner->m_hWnd,&rc);
			CMenuContainer::ActivateData data;
			data.bProgramsTree=true;
			m_pOwner->ActivateTreeItem(pItem,rc,CMenuContainer::ACTIVATE_PROPERTIES,NULL,&data);
		}
	}
	else
		bHandled=FALSE;
	return 0;
}

LRESULT CProgramsTree::OnChar( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
{
	bHandled=(wParam==VK_TAB || wParam==VK_RETURN || wParam==VK_ESCAPE);
	return 0;
}

LRESULT CProgramsTree::OnSetFocus( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
{
	for (std::vector<CMenuContainer*>::reverse_iterator it=CMenuContainer::s_Menus.rbegin();*it!=m_pOwner;++it)
		if (!(*it)->m_bDestroyed)
			(*it)->PostMessage(WM_CLOSE);
	bHandled=FALSE;
	return 0;
}

LRESULT CProgramsTree::OnMouseMove( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
{
	TVHITTESTINFO test={{(short)LOWORD(lParam),(short)HIWORD(lParam)}};
	if (m_LastMousePos.x!=-1 && (m_LastMousePos.x!=test.pt.x || m_LastMousePos.y!=test.pt.y) && TreeView_HitTest(m_hWnd,&test))
	{
		RECT rc;
		TreeView_GetItemRect(m_hWnd,test.hItem,&rc,FALSE);
		int bottom=rc.bottom;
		GetClientRect(&rc);
		if (bottom<=rc.bottom)
			TreeView_SelectItem(m_hWnd,test.hItem);

		if (!m_bHoverTimer)
		{
			SetTimer(TIMER_HOVER,CMenuContainer::s_HoverTime);
			m_bHoverTimer=true;
		}
		if (m_pOwner->m_Submenu<0)
			SetFocus();
		m_pOwner->SetHotItem(m_pOwner->m_ProgramTreeIndex);
	}
	m_LastMousePos=test.pt;

	if (!m_bTrackMouse)
	{
		TRACKMOUSEEVENT track={sizeof(track),TME_LEAVE,m_hWnd,0};
		TrackMouseEvent(&track);
		m_bTrackMouse=true;
	}

	bHandled=FALSE;
	return 0;
}

LRESULT CProgramsTree::OnMouseLeave( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
{
	m_pOwner->SetHotItem(-1);
	m_bTrackMouse=false;
	if (m_bHoverTimer)
	{
		KillTimer(TIMER_HOVER);
		m_bHoverTimer=false;
	}
	return 0;
}

LRESULT CProgramsTree::OnNcMouseMove( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
{
	TreeView_SelectItem(m_hWnd,NULL);
	if (!m_bHoverTimer)
	{
		SetTimer(TIMER_HOVER,CMenuContainer::s_HoverTime);
		m_bHoverTimer=true;
	}
	if (m_pOwner->m_Submenu<0)
		SetFocus();
	m_pOwner->SetHotItem(m_pOwner->m_ProgramTreeIndex);
	bHandled=FALSE;
	return 0;
}

LRESULT CProgramsTree::OnNcMouseLeave( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
{
	m_bTrackMouse=false;
	if (m_bHoverTimer)
	{
		KillTimer(TIMER_HOVER);
		m_bHoverTimer=false;
	}
	bHandled=FALSE;
	return 0;
}

LRESULT CProgramsTree::OnNcLButtonDown( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
{
	SetFocus();
	TreeView_SelectItem(m_hWnd,NULL);
	bHandled=FALSE;
	return 0;
}

LRESULT CProgramsTree::OnMouseActivate( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
{
	SetFocus();
	if (CMenuContainer::s_bBehindTaskbar && CMenuContainer::s_TaskBar)
		m_pOwner->SetWindowPos(CMenuContainer::s_TaskBar,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE);
	return MA_NOACTIVATE;
}

LRESULT CProgramsTree::OnTimer( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
{
	if (wParam==TIMER_HOVER)
	{
		KillTimer(TIMER_HOVER);
		m_bHoverTimer=false;
		RECT rc;
		GetWindowRect(&rc);
		if (PtInRect(&rc,CPoint(GetMessagePos())))
			SetFocus();
	}
	else
		bHandled=FALSE;
	return 0;
}

LRESULT CProgramsTree::OnContextMenu( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
{
	if (CMenuContainer::s_bNoContextMenu) return 0;
	HTREEITEM hItem;
	const CTreeItem *pItem=GetSelectedItem(hItem);
	if (pItem)
	{
		RECT rc;
		TreeView_GetItemRect(m_hWnd,hItem,&rc,FALSE);
		MapWindowPoints(m_pOwner->m_hWnd,&rc);
		CMenuContainer::ActivateData data;
		data.bProgramsTree=true;
		data.bExpanded=(TreeView_GetItemState(m_hWnd,hItem,TVIS_EXPANDED)&TVIS_EXPANDED)!=0;
		HTREEITEM hParent=TreeView_GetParent(m_hWnd,hItem);
		const CTreeItem *pParent=NULL;
		if (hParent)
		{
			TVITEM item={TVIF_PARAM,hParent};
			TreeView_GetItem(m_hWnd,&item);
			pParent=(CTreeItem*)item.lParam;
			data.parent=pParent->pItemInfo1->GetPidl();
			data.bAutoSort=pParent->bAutoSort;
		}
		else
		{
			data.parent=m_pOwner->m_Path1[0];
			data.bAutoSort=m_bAutoSort;
		}
		data.hTreeItem=hItem;
		m_pOwner->ActivateTreeItem(pItem,rc,CMenuContainer::ACTIVATE_MENU,NULL,&data);
		if (data.command==CMenuContainer::CMD_RENAME && data.pNewItemInfo)
			SaveRenamedOrder(hItem,data.pNewItemInfo);
		if (data.command==CMenuContainer::CMD_TOGGLE)
			TreeView_Expand(m_hWnd,hItem,TVE_TOGGLE);
		else if (data.command==CMenuContainer::CMD_DELETE || data.command==CMenuContainer::CMD_MARKOLD)
			PostRefreshMessage(pParent?pParent->pItemInfo1:NULL);
		else if (data.command==CMenuContainer::CMD_RENAME || data.command==CMenuContainer::CMD_NEWFOLDER)
			PostRefreshMessage(data.pNewItemInfo);
	}

	return 0;
}

LRESULT CProgramsTree::OnScroll( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
{
	if (CMenuContainer::s_Skin.Programs_background==0)
		Invalidate();
	bHandled=FALSE;
	return 0;
}

bool CProgramsTree::GetInsertRect( RECT &rc ) const
{
	if (!m_InsertMark) return false;

	RECT itemRect;
	TreeView_GetItemRect(m_hWnd,m_InsertMark,&itemRect,TRUE);

	GetClientRect(&rc);
	rc.left=itemRect.left-CItemManager::SMALL_ICON_SIZE-3-CMenuContainer::s_Skin.ItemSettings[MenuSkin::PROGRAMS_TREE_ITEM].iconPadding.right;
	const POINT *sizes=CMenuContainer::s_Skin.GetArrowsBitmapSizes();
	int h=sizes[1].y;
	rc.top=(m_bInsertAfter?itemRect.bottom:itemRect.top)-h/2;
	rc.bottom=rc.top+h;
	return true;
}

LRESULT CProgramsTree::OnSetInsertMark( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
{
	RECT rc;
	if (GetInsertRect(rc))
		InvalidateRect(&rc);
	m_InsertMark=(HTREEITEM)lParam;
	m_bInsertAfter=wParam!=0;
	if (GetInsertRect(rc))
		InvalidateRect(&rc);
	bHandled=FALSE;
	return 0;
}

LRESULT CProgramsTree::OnRefresh( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
{
	m_bRefreshPosted=false;
	RefreshTree(NULL,(CItemManager::ItemInfo*)lParam);
	return 0;
}

// Subclass the renaming editbox to handle Esc and Enter
static LRESULT CALLBACK SubclassEditboxProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData )
{
	if (uMsg==WM_GETDLGCODE)
		return DLGC_WANTALLKEYS;
	return DefSubclassProc(hWnd,uMsg,wParam,lParam);
}

LRESULT CProgramsTree::OnBeginLabelEdit( int idCtrl, LPNMHDR pnmh, BOOL& bHandled )
{
	SetWindowSubclass(TreeView_GetEditControl(m_hWnd),SubclassEditboxProc,'CLSH',0);
/*	NMTVDISPINFO *pInfo=(NMTVDISPINFO*)pnmh;
	if (!pInfo->item.lParam || ((CTreeItem*)pInfo->item.lParam)->bSeparator)
		return TRUE;*/
	return FALSE;
}

LRESULT CProgramsTree::OnEndLabelEdit( int idCtrl, LPNMHDR pnmh, BOOL& bHandled )
{
/*	NMTVDISPINFO *pInfo=(NMTVDISPINFO*)pnmh;
	if (pInfo->item.pszText)
	{
		if (!*pInfo->item.pszText)
		{
			::MessageBox(m_hWnd,LoadStringEx(IDS_ERROR_EMPTY),LoadStringEx(IDS_ERROR_TITLE),MB_OK|MB_ICONERROR);
			return FALSE;
		}
		bool bValid=true;
		for (const wchar_t *c=pInfo->item.pszText;*c;c++)
		{
			if ((*c>='a' && *c<='z') || (*c>='A' && *c<='Z') || (*c>='0' && *c<='9') || *c=='_')
				continue;
			::MessageBox(m_hWnd,LoadStringEx(IDS_ERROR_ASCII),LoadStringEx(IDS_ERROR_TITLE),MB_OK|MB_ICONERROR);
			return FALSE;
		}
		wchar_t text[1024];
		if (m_pOwner->IsSeparator(pInfo->item.pszText))
		{
			Sprintf(text,_countof(text),LoadStringEx(IDS_ERROR_SEPARATOR),pInfo->item.pszText);
			::MessageBox(m_hWnd,text,LoadStringEx(IDS_ERROR_TITLE),MB_OK|MB_ICONERROR);
			return FALSE;
		}
		if (FindItemByName(NULL,pInfo->item.pszText,pInfo->item.hItem))
		{
			Sprintf(text,_countof(text),LoadStringEx(IDS_DUPLICATE_ITEM),pInfo->item.pszText);
			::MessageBox(m_hWnd,text,LoadStringEx(IDS_ERROR_TITLE),MB_OK|MB_ICONERROR);
			return FALSE;
		}
		((CTreeItem*)pInfo->item.lParam)->name=pInfo->item.pszText;
		return TRUE;
	}*/
	return FALSE;
}

LRESULT CProgramsTree::OnGetInfoTip( int idCtrl, LPNMHDR pnmh, BOOL& bHandled )
{
	NMTVGETINFOTIP *pTip=(NMTVGETINFOTIP*)pnmh;
	const CTreeItem *pTreeItem=(CTreeItem*)pTip->lParam;
	if (pTreeItem && pTreeItem->pItemInfo1 && !pTreeItem->bFolder)
	{
		// get the tip from the shell
		CComPtr<IShellItem> pItem;
		if (FAILED(SHCreateItemFromIDList(pTreeItem->pItemInfo1->GetPidl(),IID_IShellItem,(void**)&pItem)))
			return 0;

		CComPtr<IQueryInfo> pQueryInfo;
		if (FAILED(pItem->BindToHandler(NULL,BHID_SFUIObject,IID_IQueryInfo,(void**)&pQueryInfo)))
			return 0;

		CComString pText;
		if (FAILED(pQueryInfo->GetInfoTip(QITIPF_LINKNOTARGET,&pText)) || !pText)
			return 0;

		Strcpy(pTip->pszText,pTip->cchTextMax,pText);
	}
	return 0;
}

LRESULT CProgramsTree::OnClick( int idCtrl, LPNMHDR pnmh, BOOL& bHandled )
{
	CPoint pt(GetMessagePos());
	ScreenToClient(&pt);
	TVHITTESTINFO test={pt};
	TreeView_HitTest(m_hWnd,&test);
	if (test.flags&TVHT_ONITEM|TVHT_ONITEMINDENT)
	{
		TreeView_SelectItem(m_hWnd,test.hItem);
		TVITEM item={TVIF_PARAM,test.hItem};
		TreeView_GetItem(m_hWnd,&item);
		const CTreeItem *pItem=(CTreeItem*)item.lParam;
		if (!pItem) return TRUE;
		if (pItem->bFolder)
			TreeView_Expand(m_hWnd,test.hItem,TVE_TOGGLE);
		else if (pItem->pItemInfo1)
		{
			RECT rc;
			TreeView_GetItemRect(m_hWnd,test.hItem,&rc,FALSE);
			MapWindowPoints(m_pOwner->m_hWnd,&rc);
			CMenuContainer::ActivateData data;
			data.bProgramsTree=true;
			m_pOwner->ActivateTreeItem(pItem,rc,CMenuContainer::ACTIVATE_EXECUTE,NULL,&data);
		}
	}
	return TRUE;
}

LRESULT CProgramsTree::OnRClick( int idCtrl, LPNMHDR pnmh, BOOL& bHandled )
{
	if (CMenuContainer::s_bNoContextMenu) return 1;
	CPoint pt(GetMessagePos());
	TVHITTESTINFO test={pt};
	ScreenToClient(&test.pt);
	TreeView_HitTest(m_hWnd,&test);
	if (test.flags&TVHT_ONITEM|TVHT_ONITEMINDENT)
	{
		TreeView_SelectItem(m_hWnd,test.hItem);
		TVITEM item={TVIF_PARAM,test.hItem};
		TreeView_GetItem(m_hWnd,&item);
		const CTreeItem *pItem=(CTreeItem*)item.lParam;
		if (pItem)
		{
			RECT rc;
			TreeView_GetItemRect(m_hWnd,test.hItem,&rc,FALSE);
			MapWindowPoints(m_pOwner->m_hWnd,&rc);
			CMenuContainer::ActivateData data;
			data.bProgramsTree=true;
			data.bExpanded=(TreeView_GetItemState(m_hWnd,test.hItem,TVIS_EXPANDED)&TVIS_EXPANDED)!=0;
			HTREEITEM hParent=TreeView_GetParent(m_hWnd,test.hItem);
			const CTreeItem *pParent=NULL;
			if (hParent)
			{
				TVITEM item={TVIF_PARAM,hParent};
				TreeView_GetItem(m_hWnd,&item);
				pParent=(CTreeItem*)item.lParam;
				data.parent=pParent->pItemInfo1->GetPidl();
				data.bAutoSort=pParent->bAutoSort;
			}
			else
			{
				data.parent=m_pOwner->m_Path1[0];
				data.bAutoSort=m_bAutoSort;
			}
			data.hTreeItem=test.hItem;
			m_pOwner->ActivateTreeItem(pItem,rc,CMenuContainer::ACTIVATE_MENU,&pt,&data);
			if (data.command==CMenuContainer::CMD_RENAME && data.pNewItemInfo)
				SaveRenamedOrder(test.hItem,data.pNewItemInfo);
			if (data.command==CMenuContainer::CMD_TOGGLE)
				TreeView_Expand(m_hWnd,test.hItem,TVE_TOGGLE);
			else if (data.command==CMenuContainer::CMD_DELETE || data.command==CMenuContainer::CMD_MARKOLD)
				PostRefreshMessage(pParent?pParent->pItemInfo1:NULL);
			else if (data.command==CMenuContainer::CMD_RENAME || data.command==CMenuContainer::CMD_NEWFOLDER)
				PostRefreshMessage(data.pNewItemInfo);
		}
	}
	return 1;
}

LRESULT CProgramsTree::OnPaint( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
{
	// use buffered paint to allow the tree control to appear on a transparent background
	PAINTSTRUCT ps;
	HDC hdc=BeginPaint(&ps);
	DrawTree(hdc,ps.rcPaint);
	EndPaint(&ps);
	return 0;
}

void CProgramsTree::DeleteAllItems( void )
{
	TreeView_DeleteAllItems(m_hWnd);
}

void CProgramsTree::ClearAllNewRec( HTREEITEM hParent )
{
	for (HTREEITEM hItem=hParent?TreeView_GetChild(m_hWnd,hParent):TreeView_GetRoot(m_hWnd);hItem;hItem=TreeView_GetNextSibling(m_hWnd,hItem))
	{
		TVITEM item={TVIF_PARAM,hItem};
		TreeView_GetItem(m_hWnd,&item);
		CTreeItem *pTreeItem=(CTreeItem*)item.lParam;
		pTreeItem->bNew=false;
		ClearAllNewRec(hItem);
	}
}

void CProgramsTree::ClearAllNew( void )
{
	ClearAllNewRec(NULL);
}

void CProgramsTree::AddFirstFolder( std::vector<CTreeItem*> &items, IShellItem *pParent, bool bPrograms )
{
	unsigned int hash0Prog=CalcFNVHash(L"\\",FNV_HASH0);
	unsigned int hash0=FNV_HASH0;
	CShellItemEnumerator enumerator(pParent);
	if (!enumerator.IsValid()) return;

	CComPtr<IShellItem> pChild;
	CAbsolutePidl childPidl;
	while (enumerator.GetNext(pChild,childPidl))
	{
		SFGAOF itemFlags;
		if (SUCCEEDED(pChild->GetAttributes(SFGAO_FOLDER|SFGAO_HIDDEN,&itemFlags)))
		{
			if (itemFlags&SFGAO_HIDDEN)
				continue;
			CTreeItem *pItem=new CTreeItem();
			pItem->bFolder=(itemFlags&SFGAO_FOLDER)!=0;
			pItem->bEmpty=false;
			pItem->pItemInfo1=g_ItemManager.GetItemInfo(pChild,childPidl,CItemManager::INFO_SMALL_ICON|CItemManager::INFO_LINK|CItemManager::INFO_METRO);
			{
				CItemManager::RWLock lock(&g_ItemManager,false,CItemManager::RWLOCK_ITEMS);
				if (pItem->pItemInfo1->IsMetroLink())
				{
					if (GetSettingBool(L"HideProgramsMetro") || _wcsicmp(pItem->pItemInfo1->GetAppid(),DESKTOP_APP_ID)==0)
						continue;
					CString name=pItem->pItemInfo1->GetMetroName();
					pItem->SetName(name,false);
					name.MakeUpper();
					pItem->nameHash=CalcFNVHash(name,hash0);
					pItem->nameHashProg=CalcFNVHash(name,hash0Prog);
				}
			}
			if (pItem->name.IsEmpty())
			{
				CComString pName;
				if (FAILED(pChild->GetDisplayName(SIGDN_NORMALDISPLAY,&pName)))
					continue;
				pItem->SetName(pName,false);
				CComString pName2;
				if (SUCCEEDED(pChild->GetDisplayName(SIGDN_PARENTRELATIVEPARSING,&pName2)))
				{
					pName2.MakeUpper();
					pItem->nameHash=CalcFNVHash(pName2,hash0);
					pItem->nameHashProg=CalcFNVHash(pName2,hash0Prog);
				}
				else
				{
					pName.MakeUpper();
					pItem->nameHash=CalcFNVHash(pName,hash0);
					pItem->nameHashProg=CalcFNVHash(pName2,hash0Prog);
				}
			}
			if (pItem->bFolder)
				pItem->folderHash=CalcFNVHash(pItem->pItemInfo1->PATH,CalcFNVHash(ORDER_PREFIX));
			pItem->pItemInfo2=NULL;
			pItem->order=-1;
			pItem->bPrograms=bPrograms;
			pItem->bNew=g_ItemManager.IsNewProgram(pItem->pItemInfo1->GetPidl(),pItem->bFolder,false);
			items.push_back(pItem);
		}
	}
}

void CProgramsTree::AddSecondFolder( std::vector<CTreeItem*> &items, IShellItem *pParent, bool bPrograms )
{
	unsigned int hash0Prog=CalcFNVHash(L"\\",FNV_HASH0);
	unsigned int hash0=FNV_HASH0;
	CShellItemEnumerator enumerator(pParent);
	if (!enumerator.IsValid()) return;

	CComPtr<IShellItem> pChild;
	CAbsolutePidl childPidl;
	while (enumerator.GetNext(pChild,childPidl))
	{
		SFGAOF itemFlags;
		if (SUCCEEDED(pChild->GetAttributes(SFGAO_FOLDER|SFGAO_HIDDEN,&itemFlags)))
		{
			if (itemFlags&SFGAO_HIDDEN)
				continue;
			CTreeItem *pItem=new CTreeItem();
			pItem->bFolder=(itemFlags&SFGAO_FOLDER)!=0;
			pItem->bEmpty=false;
			pItem->pItemInfo1=g_ItemManager.GetItemInfo(pChild,childPidl,CItemManager::INFO_SMALL_ICON|CItemManager::INFO_LINK|CItemManager::INFO_METRO);
			{
				CItemManager::RWLock lock(&g_ItemManager,false,CItemManager::RWLOCK_ITEMS);
				if (pItem->pItemInfo1->IsMetroLink())
				{
					if (GetSettingBool(L"HideProgramsMetro") || _wcsicmp(pItem->pItemInfo1->GetAppid(),DESKTOP_APP_ID)==0)
						continue;
					CString name=pItem->pItemInfo1->GetMetroName();
					pItem->SetName(name,false);
					name.MakeUpper();
					pItem->nameHash=CalcFNVHash(name,hash0);
					pItem->nameHashProg=CalcFNVHash(name,hash0Prog);
				}
			}
			if (pItem->name.IsEmpty())
			{
				CComString pName;
				if (FAILED(pChild->GetDisplayName(SIGDN_NORMALDISPLAY,&pName)))
					continue;
				pItem->SetName(pName,false);
				CComString pName2;
				if (SUCCEEDED(pChild->GetDisplayName(SIGDN_PARENTRELATIVEPARSING,&pName2)))
				{
					pName2.MakeUpper();
					pItem->nameHash=CalcFNVHash(pName2,hash0);
					pItem->nameHashProg=CalcFNVHash(pName2,hash0Prog);
				}
				else
				{
					pName.MakeUpper();
					pItem->nameHash=CalcFNVHash(pName,hash0);
					pItem->nameHashProg=CalcFNVHash(pName2,hash0Prog);
				}
			}
			if (pItem->bFolder)
				pItem->folderHash=CalcFNVHash(pItem->pItemInfo1->PATH,CalcFNVHash(ORDER_PREFIX));
			pItem->pItemInfo2=NULL;
			pItem->order=-1;
			pItem->bPrograms=bPrograms;
			pItem->bNew=g_ItemManager.IsNewProgram(pItem->pItemInfo1->GetPidl(),pItem->bFolder,false);
			unsigned int nameHash=pItem->GetNameHash();
			for (std::vector<CTreeItem*>::iterator it=items.begin();it!=items.end();++it)
			{
				if ((*it)->GetNameHash()==nameHash)
				{
					(*it)->pItemInfo2=pItem->pItemInfo1;
					if (pItem->bNew)
						(*it)->bNew=true;
					delete pItem;
					pItem=NULL;
					break;
				}
			}
			if (pItem)
				items.push_back(pItem);
		}
	}
}

void CProgramsTree::AddMetroApps( std::vector<CTreeItem*> &items )
{
	unsigned int hash0Prog=CalcFNVHash(L"\\",FNV_HASH0);
	unsigned int hash0=FNV_HASH0;
	std::vector<MetroLink> links;
	GetMetroLinks(links,true);
	for (std::vector<MetroLink>::const_iterator it=links.begin();it!=links.end();++it)
	{
		const CItemManager::ItemInfo *pItemInfo=g_ItemManager.GetItemInfo(it->pItem,it->pidl,CItemManager::INFO_SMALL_ICON|CItemManager::INFO_LINK|CItemManager::INFO_METRO);
		CItemManager::RWLock lock(&g_ItemManager,false,CItemManager::RWLOCK_ITEMS);
		if (_wcsicmp(pItemInfo->GetAppid(),DESKTOP_APP_ID)==0)
			continue;
		CString name;
		if (pItemInfo->IsMetroLink())
			name=pItemInfo->GetMetroName();
		else
		{
			CComString pName;
			if (FAILED(it->pItem->GetDisplayName(SIGDN_NORMALDISPLAY,&pName)) || wcsncmp(pName,L"@{",2)==0)
				continue;
			name=pName;
		}
		CTreeItem *pItem=new CTreeItem();
		pItem->pItemInfo1=pItemInfo;
		pItem->SetName(name,false);
		name.MakeUpper();
		pItem->nameHash=CalcFNVHash(name,hash0);
		pItem->nameHashProg=CalcFNVHash(name,hash0Prog);
		pItem->bApp=true;
		pItem->order=-1;
		pItem->bNew=g_ItemManager.IsNewProgram(pItemInfo->GetPidl(),false,true);
		items.push_back(pItem);
	}

	ULONGLONG curTime;
	GetSystemTimeAsFileTime((FILETIME*)&curTime);
	CRegKey regKey;
	if (regKey.Open(HKEY_CURRENT_USER,L"Software\\OpenShell\\StartMenu",KEY_WRITE)!=ERROR_SUCCESS)
		regKey.Create(HKEY_CURRENT_USER,L"Software\\OpenShell\\StartMenu");
	regKey.SetQWORDValue(L"LastAppsTime",curTime);
}

void CProgramsTree::GetFolderItems( std::vector<CTreeItem*> &items, HTREEITEM hParent )
{
	CTreeItem *pTreeItem=NULL;
	if (hParent)
	{
		TVITEM item={TVIF_PARAM,hParent};
		TreeView_GetItem(m_hWnd,&item);
		pTreeItem=(CTreeItem*)item.lParam;
		if (pTreeItem->bApps)
		{
			AddMetroApps(items);
		}
		else
		{
			CComPtr<IShellItem> pFolder1, pFolder2;
			SHCreateItemFromIDList(pTreeItem->pItemInfo1->GetPidl(),IID_IShellItem,(void**)&pFolder1);
			if (!pFolder1) return;

			if (pTreeItem->pItemInfo2)
				SHCreateItemFromIDList(pTreeItem->pItemInfo2->GetPidl(),IID_IShellItem,(void**)&pFolder2);

			AddFirstFolder(items,pFolder1,false);

			if (pFolder2)
				AddSecondFolder(items,pFolder2,false);
			if (items.empty())
			{
				CTreeItem *pItem=new CTreeItem();
				pItem->bFolder=false;
				pItem->bEmpty=true;
				pItem->SetName(FindTranslation(L"Menu.Empty",L"(Empty)"),false);
				pItem->nameHash=pItem->nameHashProg=0;
				pItem->pItemInfo1=pItem->pItemInfo2=NULL;
				items.push_back(pItem);
			}
		}
	}
	else
	{
		CComPtr<IShellItem> pRoot;
		CAbsolutePidl pidl;
		if (GetSettingInt(L"PinnedPrograms")==PINNED_PROGRAMS_PINNED)
		{
			if (SUCCEEDED(ShGetKnownFolderIDList(FOLDERID_StartMenu,&pidl)))
			{
				SHCreateItemFromIDList(pidl,IID_IShellItem,(void**)&pRoot);
				if (pRoot)
				{
					AddFirstFolder(items,pRoot,false);
					pRoot=NULL;
				}
				pidl.Clear();
			}
			if (!CMenuContainer::s_bNoCommonFolders && SUCCEEDED(ShGetKnownFolderIDList(FOLDERID_CommonStartMenu,&pidl)))
			{
				SHCreateItemFromIDList(pidl,IID_IShellItem,(void**)&pRoot);
				if (pRoot)
				{
					AddSecondFolder(items,pRoot,false);
					pRoot=NULL;
				}
				pidl.Clear();
			}
		}
		if (SUCCEEDED(ShGetKnownFolderIDList(FOLDERID_Programs,&pidl)))
		{
			SHCreateItemFromIDList(pidl,IID_IShellItem,(void**)&pRoot);
			if (pRoot)
			{
				AddFirstFolder(items,pRoot,true);
				pRoot=NULL;
				for (std::vector<CTreeItem*>::iterator it=items.begin();it!=items.end();++it)
				{
					if (ILIsEqual((*it)->pItemInfo1->GetPidl(),pidl))
					{
						items.erase(it);
						break;
					}
				}
			}
			pidl.Clear();
		}
		if (!CMenuContainer::s_bNoCommonFolders && SUCCEEDED(ShGetKnownFolderIDList(FOLDERID_CommonPrograms,&pidl)))
		{
			SHCreateItemFromIDList(pidl,IID_IShellItem,(void**)&pRoot);
			if (pRoot)
			{
				AddSecondFolder(items,pRoot,true);
				pRoot=NULL;
				for (std::vector<CTreeItem*>::iterator it=items.begin();it!=items.end();++it)
				{
					if (ILIsEqual((*it)->pItemInfo1->GetPidl(),pidl))
					{
						items.erase(it);
						break;
					}
				}
			}
			pidl.Clear();
		}
		if (GetSettingBool(L"AllProgramsMetro"))
		{
			CTreeItem *pItem=new CTreeItem();
			pItem->bFolder=true;
			pItem->bApps=true;
			pItem->SetName(FindTranslation(L"Menu.Apps",L"Apps"),false);
			pItem->pItemInfo1=g_ItemManager.GetCustomIcon(L",2",CItemManager::ICON_SIZE_TYPE_SMALL);
			if (pItem->bFolder)
				pItem->folderHash=CalcFNVHash(L"?Apps",CalcFNVHash(ORDER_PREFIX));
			pItem->pItemInfo2=NULL;
			pItem->order=-1;
			pItem->bNew=g_ItemManager.HasNewApps(false);
			items.push_back(pItem);
		}
	}
	unsigned int folderHash=pTreeItem?pTreeItem->folderHash:CalcFNVHash(ORDER_PREFIX);

	// load item names from the registry
	std::vector<unsigned int> hashes;
	CRegKey regOrder;
	if (pTreeItem)
		pTreeItem->bAutoSort=false;
	else
		m_bAutoSort=false;
	if (regOrder.Open(HKEY_CURRENT_USER,L"Software\\OpenShell\\StartMenu\\Order",KEY_READ)==ERROR_SUCCESS)
	{
		wchar_t name[100];
		{
			ULONG size=0;
			Sprintf(name,_countof(name),L"%08X",folderHash);
			regOrder.QueryBinaryValue(name,NULL,&size);
			if (size>0 && !(size&3))
			{
				hashes.resize(size/4);
				regOrder.QueryBinaryValue(name,&hashes[0],&size);
			}
		}
	}
	if (hashes.size()==1 && hashes[0]=='AUTO')
	{
		if (pTreeItem)
			pTreeItem->bAutoSort=true;
		else
			m_bAutoSort=true;
	}
	else
	{
		// assign each item an index based on its position in items. store in order
		for (std::vector<CTreeItem*>::iterator it=items.begin();it!=items.end();++it)
		{
			unsigned int hash=(*it)->GetNameHash();
			(*it)->order=32768;
			for (int i=0;i<(int)hashes.size();i++)
			{
				if (hashes[i]==hash)
				{
					(*it)->order=i;
					break;
				}
			}
		}
	}

	std::sort(items.begin(),items.end(),CmpTreeItems);
}

void CProgramsTree::CreateFolderItems( HTREEITEM hParent )
{
	std::vector<CTreeItem*> items;
	GetFolderItems(items,hParent);

	HTREEITEM hAfter=NULL;
	for (std::vector<CTreeItem*>::const_iterator it=items.begin();it!=items.end();++it)
	{
		TVINSERTSTRUCT insert={hParent,hAfter?hAfter:TVI_FIRST,{TVIF_CHILDREN|TVIF_PARAM|TVIF_TEXT}};
		insert.itemex.pszText=(LPWSTR)(LPCWSTR)(*it)->name;
		insert.itemex.cChildren=(*it)->bFolder?1:0;
		insert.itemex.lParam=(LPARAM)*it;
		hAfter=TreeView_InsertItem(m_hWnd,&insert);
	}
}

void CProgramsTree::CreateItems( void )
{
	TreeView_DeleteAllItems(m_hWnd);
	CreateFolderItems(NULL);
	RECT rc;
	TreeView_GetItemRect(m_hWnd,TreeView_GetRoot(m_hWnd),&rc,TRUE);
	m_RootX=rc.left;
}

void CProgramsTree::RefreshTree( HTREEITEM hParent, const CItemManager::ItemInfo *pSelectItem )
{
	std::vector<CTreeItem*> newItems;
	GetFolderItems(newItems,hParent);
	int index=0;
	HTREEITEM hAfter=NULL;
	for (HTREEITEM hItem=hParent?TreeView_GetChild(m_hWnd,hParent):TreeView_GetRoot(m_hWnd);hItem;)
	{
		TVITEM item={TVIF_PARAM|TVIF_STATE,hItem,0,TVIS_SELECTED};
		TreeView_GetItem(m_hWnd,&item);
		CTreeItem *pOldItem=(CTreeItem*)item.lParam;
		int nextIndex=-1;
		for (int i=index;i<(int)newItems.size();i++)
		{
			if (*pOldItem==*newItems[i])
			{
				pOldItem->bNew=newItems[i]->bNew;
				nextIndex=i;
				break;
			}
		}
		HTREEITEM hNext=TreeView_GetNextSibling(m_hWnd,hItem);
		if (nextIndex>=0)
		{
			for (int i=index;i<nextIndex;i++)
			{
				TVINSERTSTRUCT insert={hParent,hAfter?hAfter:TVI_FIRST,{TVIF_CHILDREN|TVIF_PARAM|TVIF_TEXT}};
				insert.itemex.pszText=(LPWSTR)(LPCWSTR)newItems[i]->name;
				insert.itemex.cChildren=newItems[i]->bFolder?1:0;
				insert.itemex.lParam=(LPARAM)newItems[i];
				hAfter=TreeView_InsertItem(m_hWnd,&insert);
			}
			delete newItems[nextIndex];
			index=nextIndex+1;
			hAfter=hItem;
		}
		else
		{
			if (item.state&TVIS_SELECTED)
				TreeView_SelectItem(m_hWnd,NULL);
			TreeView_DeleteItem(m_hWnd,hItem);
		}
		hItem=hNext;
	}
	for (int i=index;i<(int)newItems.size();i++)
	{
		TVINSERTSTRUCT insert={hParent,hAfter?hAfter:TVI_FIRST,{TVIF_CHILDREN|TVIF_PARAM|TVIF_TEXT}};
		insert.itemex.pszText=(LPWSTR)(LPCWSTR)newItems[i]->name;
		insert.itemex.cChildren=newItems[i]->bFolder?1:0;
		insert.itemex.lParam=(LPARAM)newItems[i];
		hAfter=TreeView_InsertItem(m_hWnd,&insert);
	}
	size_t count=0;
	for (HTREEITEM hItem=hParent?TreeView_GetChild(m_hWnd,hParent):TreeView_GetRoot(m_hWnd);hItem;hItem=TreeView_GetNextSibling(m_hWnd,hItem))
	{
		TVITEM item={TVIF_STATE|TVIF_PARAM,hItem,0,TVIS_EXPANDEDONCE};
		TreeView_GetItem(m_hWnd,&item);
		if (item.state&TVIS_EXPANDEDONCE)
			RefreshTree(hItem,pSelectItem);
		count++;
		if (pSelectItem && ((CTreeItem*)item.lParam)->pItemInfo1==pSelectItem)
			TreeView_SelectItem(m_hWnd,hItem);
	}
	Assert(count==newItems.size());
}

const CProgramsTree::CTreeItem *CProgramsTree::GetSelectedItem( HTREEITEM &hItem )
{
	hItem=TreeView_GetSelection(m_hWnd);
	if (!hItem) return NULL;
	TVITEM item={TVIF_PARAM,hItem};
	TreeView_GetItem(m_hWnd,&item);
	return (CTreeItem*)item.lParam;
}

bool CProgramsTree::CmpTreeItems( const CTreeItem *item1, const CTreeItem *item2 )
{
	if (item1->order!=item2->order) return item1->order<item2->order;
	if (!item1->bFolder && item2->bFolder) return !s_bFoldersFirst;
	if (item1->bFolder && !item2->bFolder) return s_bFoldersFirst;
	if (item1->bApps && !item2->bApps) return false;
	if (!item1->bApps && item2->bApps) return true;
	return CMenuContainer::CompareMenuString(item1->name,item2->name)<0;
}

void CProgramsTree::SelectItem( int y )
{
	SetFocus();
	RECT rcClient;
	GetClientRect(&rcClient);
	HTREEITEM hSelect=NULL, hLast=NULL;
	for (HTREEITEM hItem=TreeView_GetFirstVisible(m_hWnd);hItem;hItem=TreeView_GetNextVisible(m_hWnd,hItem))
	{
		RECT rc;
		TreeView_GetItemRect(m_hWnd,hItem,&rc,FALSE);
		if (rc.top<rcClient.top)
			continue;
		if (rc.bottom>rcClient.bottom)
			break;
		TVITEM item={TVIF_PARAM,hItem};
		TreeView_GetItem(m_hWnd,&item);
		const CTreeItem *pItem=(CTreeItem*)item.lParam;
		if (!hSelect)
			hSelect=hItem;
		else if (rc.top<=y && rc.bottom>y)
		{
			hSelect=hItem;
			break;
		}
		hLast=hItem;
	}
	if (!hSelect) hSelect=hLast;
	if (hSelect)
		TreeView_SelectItem(m_hWnd,hSelect);
}

void CProgramsTree::SelectFirst( void )
{
	SetFocus();
	HTREEITEM hSelect=TreeView_GetRoot(m_hWnd);
	if (hSelect)
		TreeView_SelectItem(m_hWnd,hSelect);
}

void CProgramsTree::SelectLast( void )
{
	SetFocus();
	HTREEITEM hSelect=TreeView_GetRoot(m_hWnd);
	if (!hSelect) return;
	while (1)
	{
		for (HTREEITEM hItem=hSelect;hItem;hItem=TreeView_GetNextSibling(m_hWnd,hItem))
			hSelect=hItem;
		if (!(TreeView_GetItemState(m_hWnd,hSelect,TVIS_EXPANDED)&TVIS_EXPANDED))
			break;

		HTREEITEM hChild=TreeView_GetChild(m_hWnd,hSelect);
		if (!hChild) break;
		hSelect=hChild;
	}
	TreeView_SelectItem(m_hWnd,hSelect);
}

// IDropTarget
HRESULT CProgramsTree::DragEnter( IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect )
{
	CMenuContainer::s_bRightDrag=(grfKeyState&MK_RBUTTON)!=0;
	if (m_pOwner->m_pDropTargetHelper)
	{
		POINT p={pt.x,pt.y};
		m_pOwner->m_pDropTargetHelper->DragEnter(m_hWnd,pDataObj,&p,*pdwEffect);
	}
	m_pOwner->m_DragHoverTime=GetMessageTime()-10000;
	m_HoverItem=NULL;
	m_pOwner->m_pDragObject=pDataObj;
	return S_OK;
}

void CProgramsTree::GetDragEffect( DWORD &grfKeyState, DWORD *pdwEffect )
{
	grfKeyState&=MK_SHIFT|MK_CONTROL|MK_ALT;
	if (CMenuContainer::s_bNoDragDrop)
	{
		*pdwEffect=DROPEFFECT_NONE; // can't drop here
		return;
	}

	if (m_bDragApps)
	{
		*pdwEffect&=DROPEFFECT_MOVE;
		return;
	}

	// only accept known data formats
	FORMATETC format1={CMenuContainer::s_ShellFormat,NULL,DVASPECT_CONTENT,-1,TYMED_HGLOBAL};
	FORMATETC format2={CMenuContainer::s_ShellUrlFormat,NULL,DVASPECT_CONTENT,-1,TYMED_HGLOBAL};
	FORMATETC format3={CMenuContainer::s_MetroLinkFormat,NULL,DVASPECT_CONTENT,-1,TYMED_HGLOBAL};
	if (m_pOwner->m_pDragObject->QueryGetData(&format1)!=S_OK && m_pOwner->m_pDragObject->QueryGetData(&format2)!=S_OK && m_pOwner->m_pDragObject->QueryGetData(&format3)!=S_OK)
	{
		*pdwEffect=DROPEFFECT_NONE;
		return;
	}

	bool bDragApp=m_pOwner->m_pDragObject->QueryGetData(&format3)==S_OK; // dragging a wrapped metro link or the Apps folder
	bool bDropApps=false; // dropping in the Apps folder
	HTREEITEM hDropFolder=NULL;
	if (m_DropTarget && (m_DropLocation==DROP_BEFORE || m_DropLocation==DROP_AFTER || m_DropLocation==DROP_LAST))
		hDropFolder=TreeView_GetParent(m_hWnd,m_DropTarget);
	else
		hDropFolder=m_DropTarget;
	if (hDropFolder)
	{
		TVITEM item={TVIF_PARAM,hDropFolder};
		TreeView_GetItem(m_hWnd,&item);
		const CTreeItem *pTreeItem=(CTreeItem*)item.lParam;
		bDropApps=pTreeItem->bApps;
	}

	if (CMenuContainer::s_pDragSource)
	{
		if (CMenuContainer::s_pDragSource->m_Items[CMenuContainer::s_pDragSource->m_DragIndex].id==MENU_RECENT)
			*pdwEffect&=DROPEFFECT_LINK; // dragging a recent item (allow only link)
		else if (!CMenuContainer::s_bRightDrag && grfKeyState==0 && CMenuContainer::s_bDragMovable)
			*pdwEffect&=(bDragApp && !bDropApps)?DROPEFFECT_LINK:DROPEFFECT_MOVE; // dragging normal item - default to move
	}

	if (bDragApp)
	{
		if (bDropApps)
			*pdwEffect&=(m_DragItem && hDropFolder && TreeView_GetParent(m_hWnd,m_DragItem)==hDropFolder)?DROPEFFECT_MOVE:DROPEFFECT_NONE; // dragging a metro link to Apps folder
		else
			*pdwEffect&=DROPEFFECT_LINK; // dragging a metro link to another folder
	}

	// handle keys
	if (!CMenuContainer::s_bRightDrag)
	{
		if (grfKeyState==MK_SHIFT)
			*pdwEffect&=DROPEFFECT_MOVE;
		if (grfKeyState==MK_CONTROL)
			*pdwEffect&=DROPEFFECT_COPY;
		if (grfKeyState==(MK_CONTROL|MK_SHIFT) || grfKeyState==MK_ALT)
			*pdwEffect&=DROPEFFECT_LINK;
	}
}

HRESULT CProgramsTree::DragOver( DWORD grfKeyState, POINTL pt, DWORD *pdwEffect )
{
	CMenuContainer::s_bRightDrag=(grfKeyState&MK_RBUTTON)!=0;
	POINT p={pt.x,pt.y};
	if (m_pOwner->m_pDropTargetHelper)
	{
		m_pOwner->m_pDropTargetHelper->DragOver(&p,*pdwEffect);
	}
	ScreenToClient(&p);

	if (m_bDragApps)
	{
		// scroll tree during dragging Apps
		if ((GetMessageTime()-m_pOwner->m_DragHoverTime)>200)
		{
			m_pOwner->m_DragHoverTime=GetMessageTime();
			RECT rc;
			GetClientRect(&rc);
			int h=TreeView_GetItemHeight(m_hWnd);
			if (p.y<h)
				SendMessage(WM_VSCROLL,SB_LINEUP);
			else if (p.y>rc.bottom-h)
				SendMessage(WM_VSCROLL,SB_LINEDOWN);
		}
	}

	TVHITTESTINFO test={p};
	if (TreeView_HitTest(m_hWnd,&test))
	{
		m_DropTarget=test.hItem;
	}
	else
	{
		HTREEITEM hLast=TreeView_GetLastVisible(m_hWnd);
		if (hLast)
		{
			RECT rc;
			TreeView_GetItemRect(m_hWnd,hLast,&rc,FALSE);
			if (test.pt.y>=rc.bottom)
			{
				// after the last visible item
				m_DropTarget=hLast;
			}
		}
	}

	const CTreeItem *pTreeItem=NULL;
	bool bAutoSort=false;
	if (m_DropTarget)
	{
		TVITEM item={TVIF_PARAM,m_DropTarget};
		TreeView_GetItem(m_hWnd,&item);
		pTreeItem=(CTreeItem*)item.lParam;
		if (m_DragItem)
		{
			// can't drop inside itself
			for (HTREEITEM hItem=TreeView_GetParent(m_hWnd,m_DropTarget);hItem;hItem=TreeView_GetParent(m_hWnd,hItem))
			{
				if (hItem==m_DragItem)
				{
					m_DropTarget=NULL;
					break;
				}
			}
			if (m_DropTarget)
			{
				HTREEITEM hParent=TreeView_GetParent(m_hWnd,m_DropTarget);
				if (hParent==TreeView_GetParent(m_hWnd,m_DragItem))
				{
					if (hParent)
					{
						TVITEM parent={TVIF_PARAM,hParent};
						TreeView_GetItem(m_hWnd,&parent);
						bAutoSort=((CTreeItem*)parent.lParam)->bAutoSort;
					}
					else
						bAutoSort=m_bAutoSort;
				}
			}
		}
	}
	if (m_DropTarget)
	{
		RECT rc;
		TreeView_GetItemRect(m_hWnd,m_DropTarget,&rc,FALSE);
		int dy=test.pt.y-rc.top;
		if (bAutoSort)
		{
			if (pTreeItem->bFolder && !m_bDragApps)
				m_DropLocation=DROP_INSIDE;
			else
				m_DropTarget=NULL;
		}
		else if (pTreeItem->bFolder && !m_bDragApps)
		{
			int h1=(rc.bottom-rc.top)/4;
			int h2=(rc.bottom-rc.top)-h1;
			if (dy<h1)
				m_DropLocation=DROP_BEFORE;
			else if (dy>=h2)
				m_DropLocation=DROP_AFTER;
			else
				m_DropLocation=DROP_INSIDE;
		}
		else if (m_bDragApps && TreeView_GetParent(m_hWnd,m_DropTarget))
			m_DropTarget=NULL;
		else
		{
			int h=(rc.bottom-rc.top)/2;
			m_DropLocation=(dy<h)?DROP_BEFORE:DROP_AFTER;
		}
	}

	GetDragEffect(grfKeyState,pdwEffect);

	// display drop location
	if (!m_DropTarget)
	{
		TreeView_SelectDropTarget(m_hWnd,NULL);
		TreeView_SetInsertMark(m_hWnd,NULL,FALSE);
		*pdwEffect=0;
	}
	else
	{
		if (m_DropLocation==DROP_INSIDE)
		{
			TreeView_SelectDropTarget(m_hWnd,m_DropTarget);
			TreeView_SetInsertMark(m_hWnd,NULL,FALSE);
		}
		else
		{
			TreeView_SelectDropTarget(m_hWnd,NULL);
			TreeView_SetInsertMark(m_hWnd,m_DropTarget,m_DropLocation==DROP_AFTER);
		}
	}

	if (!m_bDragApps)
	{
		// check if the hover delay is done and it's time to open the item
		if (m_DropTarget && m_DropTarget==m_HoverItem)
		{
			if ((GetMessageTime()-m_pOwner->m_DragHoverTime)>(int)CMenuContainer::s_HoverTime*4)
			{
				TreeView_Expand(m_hWnd,m_HoverItem,TVE_EXPAND);
			}
		}
		else
		{
			m_HoverItem=m_DropTarget;
			m_pOwner->m_DragHoverTime=GetMessageTime();
		}
	}
	return S_OK;
}

HRESULT CProgramsTree::DragLeave( void )
{
	if (m_pOwner->m_pDropTargetHelper)
		m_pOwner->m_pDropTargetHelper->DragLeave();
	m_pOwner->m_pDragObject.Release();
	TreeView_SelectDropTarget(m_hWnd,NULL);
	TreeView_SetInsertMark(m_hWnd,NULL,FALSE);
	m_DropLocation=DROP_NOWHERE;
	return S_OK;
}

HRESULT CProgramsTree::Drop( IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect )
{
	if (CMenuContainer::s_pDragSource)
	{
		if (!CMenuContainer::s_pDragSource->m_bDestroyed)
			CMenuContainer::s_pDragSource->KillTimer(CMenuContainer::TIMER_DRAG);
	}
	GetDragEffect(grfKeyState,pdwEffect);
	m_pOwner->m_pDragObject.Release();

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

	std::vector<unsigned int> order;
	if (m_DropTarget && (m_DropLocation==DROP_BEFORE || m_DropLocation==DROP_AFTER || m_DropLocation==DROP_LAST))
	{
		// calculate new order
		HTREEITEM hParent=TreeView_GetParent(m_hWnd,m_DropTarget);
		size_t dropIndex=-1;
		unsigned int dragHash=0;
		if (m_DragItem)
		{
			TVITEM item={TVIF_PARAM,m_DragItem};
			TreeView_GetItem(m_hWnd,&item);
			const CTreeItem *pTreeItem=(CTreeItem*)item.lParam;
			dragHash=hParent?pTreeItem->nameHash:pTreeItem->nameHashProg;
		}
		else
		{
			// get name from dragged item
			CString name;
			if (CMenuContainer::s_pDragSource && CMenuContainer::s_pDragSource->m_Items[CMenuContainer::s_pDragSource->m_DragIndex].pItemInfo)
			{
				const CItemManager::ItemInfo *pInfo=CMenuContainer::s_pDragSource->m_Items[CMenuContainer::s_pDragSource->m_DragIndex].pItemInfo;
				{
					CItemManager::RWLock lock(&g_ItemManager,false,CItemManager::RWLOCK_ITEMS);
					if (pInfo->IsMetroLink())
						name=pInfo->GetMetroName();
				}
				if (name.IsEmpty())
				{
					CComPtr<IShellItem> pItem;
					SHCreateItemFromIDList(pInfo->GetPidl(),IID_IShellItem,(void**)&pItem);
					CComString pName;
					if (pItem && (SUCCEEDED(pItem->GetDisplayName(SIGDN_PARENTRELATIVEPARSING,&pName)) || SUCCEEDED(pItem->GetDisplayName(SIGDN_NORMALDISPLAY,&pName))))
						name=pName;
				}
			}
			else
			{
				// TODO: get name from data object
			}
			if (!name.IsEmpty())
			{
				name.MakeUpper();
				unsigned int hash0=hParent?FNV_HASH0:CalcFNVHash(L"\\",FNV_HASH0);
				dragHash=CalcFNVHash(name,hash0);
			}
		}
		for (HTREEITEM hItem=hParent?TreeView_GetChild(m_hWnd,hParent):TreeView_GetRoot(m_hWnd);hItem;hItem=TreeView_GetNextSibling(m_hWnd,hItem))
		{
			TVITEM item={TVIF_PARAM,hItem};
			TreeView_GetItem(m_hWnd,&item);
			const CTreeItem *pTreeItem=(CTreeItem*)item.lParam;
			if (hItem==m_DropTarget && m_DropLocation==DROP_BEFORE)
				dropIndex=order.size();
			if (hItem!=m_DragItem)
				order.push_back(pTreeItem->GetNameHash());
			if (hItem==m_DropTarget && m_DropLocation==DROP_AFTER)
				dropIndex=order.size();
		}
		if (m_DropLocation==DROP_LAST)
			dropIndex=order.size();
		order.insert(order.begin()+dropIndex,dragHash);

		if (m_DragItem && hParent==TreeView_GetParent(m_hWnd,m_DragItem))
		{
			// dropped in the same folder, just reorder
			OrderElements(m_DragItem,hParent,order,false,true);
			CMenuContainer::PlayMenuSound(SOUND_DROP);
			m_DropTarget=NULL;
		}
	}

	// clear the insert mark
	TreeView_SelectDropTarget(m_hWnd,NULL);
	TreeView_SetInsertMark(m_hWnd,NULL,FALSE);
	TDropLocation dropLocation=m_DropLocation;
	m_DropLocation=DROP_NOWHERE;
	if (!m_DropTarget) return 0;

	// simulate dropping the object into the original folder
	CMenuContainer::PlayMenuSound(SOUND_DROP);

	if (dropLocation!=DROP_INSIDE)
	{
		// drop in parent folder
		m_DropTarget=TreeView_GetParent(m_hWnd,m_DropTarget);
		dropLocation=DROP_INSIDE;
	}

	CAbsolutePidl dropFolder;
	if (m_DropTarget)
	{
		// drop inside a folder
		TVITEM item={TVIF_PARAM,m_DropTarget};
		TreeView_GetItem(m_hWnd,&item);
		const CTreeItem *pTreeItem=(CTreeItem*)item.lParam;
		dropFolder=pTreeItem->pItemInfo1->GetPidl();
	}
	else
	{
		// drop at top
		ShGetKnownFolderIDList(FOLDERID_Programs,&dropFolder);
	}
	if (!dropFolder) return E_FAIL;

	// must use IShellFolder to get to the drop target because the BindToHandler doesn't support passing the parent window (easily)
	CComPtr<IShellFolder> pDesktop;
	SHGetDesktopFolder(&pDesktop);
	CComPtr<IShellFolder> pFolder;
	CComPtr<IDropTarget> pTarget;
	if (!pDesktop || FAILED(pDesktop->BindToObject(dropFolder,NULL,IID_IShellFolder,(void**)&pFolder)) || FAILED(pFolder->CreateViewObject(g_OwnerWindow,IID_IDropTarget,(void**)&pTarget)))
		return S_OK;

	DWORD dwEffect=*pdwEffect;
	if (CMenuContainer::s_bRightDrag)
	{
		if (FAILED(pTarget->DragEnter(pDataObj,MK_RBUTTON|grfKeyState,pt,&dwEffect)))
			return E_FAIL;
		dwEffect=*pdwEffect;
		pTarget->DragOver(MK_RBUTTON|grfKeyState,pt,&dwEffect);
	}
	else
	{
		if (FAILED(pTarget->DragEnter(pDataObj,MK_LBUTTON|grfKeyState,pt,&dwEffect)))
			return E_FAIL;
		dwEffect=*pdwEffect;
		pTarget->DragOver(MK_LBUTTON|grfKeyState,pt,pdwEffect);
	}
	CComQIPtr<IDataObjectAsyncCapability> pAsync=pDataObj;
	if (pAsync)
		pAsync->SetAsyncMode(FALSE);
	for (std::vector<CMenuContainer*>::iterator it=CMenuContainer::s_Menus.begin();it!=CMenuContainer::s_Menus.end();++it)
		if (!(*it)->m_bDestroyed)
			(*it)->EnableWindow(FALSE); // disable all menus
	bool bOld=CMenuContainer::s_bPreventClosing;
	CMenuContainer::s_bPreventClosing=true;
	m_pOwner->AddRef();
	pTarget->Drop(pDataObj,grfKeyState,pt,pdwEffect);
	CMenuContainer::s_bPreventClosing=bOld;
	for (std::vector<CMenuContainer*>::iterator it=CMenuContainer::s_Menus.begin();it!=CMenuContainer::s_Menus.end();++it)
		if (!(*it)->m_bDestroyed)
			(*it)->EnableWindow(TRUE); // enable all menus
	if (!m_pOwner->m_bDestroyed)
	{
		SetForegroundWindow(m_pOwner->m_hWnd);
		m_pOwner->SetActiveWindow();
		m_pOwner->SetHotItem(m_pOwner->m_ProgramTreeIndex);
		SetFocus();
	}
	if (!order.empty())
	{
		OrderElements(NULL,m_DropTarget,order,false,false);
	}
	PostRefreshMessage();
	m_pOwner->Release();

	return S_OK;
}

void CProgramsTree::PostRefreshMessage( const CItemManager::ItemInfo *pSelectItem )
{
	if (!m_bRefreshPosted && !m_pOwner->m_bDestroyed)
	{
		m_bRefreshPosted=true;
		PostMessage(TVM_REFRESH,0,(LPARAM)pSelectItem);
	}
}

int CALLBACK CProgramsTree::CmpTreeItemsCB( LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort )
{
	const CTreeItem *pTreeItem1=(CTreeItem*)lParam1;
	const CTreeItem *pTreeItem2=(CTreeItem*)lParam2;
	return pTreeItem1->order-pTreeItem2->order;
}

// reorders the tree elements and saves the order in registry
// bResort - resort the tree with the given order
void CProgramsTree::OrderElements( HTREEITEM hChild, HTREEITEM hParent, const std::vector<unsigned int> &order, bool bAutoSort, bool bResort )
{
	CTreeItem *pParentItem=NULL;
	unsigned int folderHash;
	if (hParent)
	{
		TVITEM item={TVIF_PARAM,hParent};
		TreeView_GetItem(m_hWnd,&item);
		pParentItem=(CTreeItem*)item.lParam;
		folderHash=pParentItem->folderHash;
	}
	else
		folderHash=CalcFNVHash(ORDER_PREFIX);

	if (order.empty())
	{
		if (pParentItem)
			pParentItem->bAutoSort=bAutoSort;
		else
			m_bAutoSort=bAutoSort;

		if (hChild)
		{
			TVITEM item={TVIF_PARAM,hChild};
			TreeView_GetItem(m_hWnd,&item);
			PostMessage(TVM_REFRESH,0,item.lParam);
		}
		else
			PostMessage(TVM_REFRESH);
	}
	else if (bResort)
	{
		// set item order
		for (HTREEITEM hItem=hParent?TreeView_GetChild(m_hWnd,hParent):TreeView_GetRoot(m_hWnd);hItem;hItem=TreeView_GetNextSibling(m_hWnd,hItem))
		{
			TVITEM item={TVIF_PARAM,hItem};
			TreeView_GetItem(m_hWnd,&item);
			CTreeItem *pTreeItem=(CTreeItem*)item.lParam;
			pTreeItem->order=32768;
			unsigned int nameHash=pTreeItem->GetNameHash();
			for (int i=0;i<(int)order.size();i++)
			{
				if (nameHash==order[i])
				{
					pTreeItem->order=i;
					break;
				}
			}
		}
		// sort by order
		TVSORTCB sort={hParent,CmpTreeItemsCB,0};
		TreeView_SortChildrenCB(m_hWnd,&sort,0);
	}

	// save order
	CRegKey regOrder;
	wchar_t name[100];
	if (regOrder.Open(HKEY_CURRENT_USER,L"Software\\OpenShell\\StartMenu\\Order")!=ERROR_SUCCESS)
		regOrder.Create(HKEY_CURRENT_USER,L"Software\\OpenShell\\StartMenu\\Order");

	Sprintf(name,_countof(name),L"%08X",folderHash);
	if (bAutoSort)
	{
		DWORD cAuto='AUTO';
		regOrder.SetBinaryValue(name,&cAuto,4);
	}
	else if (order.empty())
		regOrder.SetBinaryValue(name,NULL,0);
	else
		regOrder.SetBinaryValue(name,&order[0],(int)order.size()*4);
}

// saves the item order by replacing the item with the new info
void CProgramsTree::SaveRenamedOrder( HTREEITEM hItem, const CItemManager::ItemInfo *pNewInfo )
{
	HTREEITEM hParent=TreeView_GetParent(m_hWnd,hItem);
	CTreeItem *pParentItem=NULL;
	unsigned int folderHash;
	if (hParent)
	{
		TVITEM item={TVIF_PARAM,hParent};
		TreeView_GetItem(m_hWnd,&item);
		pParentItem=(CTreeItem*)item.lParam;
		if (pParentItem->bAutoSort)
			return;
		folderHash=pParentItem->folderHash;
	}
	else
	{
		if (m_bAutoSort)
			return;
		folderHash=CalcFNVHash(ORDER_PREFIX);
	}

	std::vector<unsigned int> order;
	for (HTREEITEM hChild=hParent?TreeView_GetChild(m_hWnd,hParent):TreeView_GetRoot(m_hWnd);hChild;hChild=TreeView_GetNextSibling(m_hWnd,hChild))
	{
		TVITEM item={TVIF_PARAM,hChild};
		TreeView_GetItem(m_hWnd,&item);
		const CTreeItem *pTreeItem=(CTreeItem*)item.lParam;
		if (hChild==hItem)
		{
			CComPtr<IShellItem> pChild;
			if (FAILED(SHCreateItemFromIDList(pNewInfo->GetPidl(),IID_IShellItem,(void**)&pChild)))
				return;
			CComString pName;
			if (FAILED(pChild->GetDisplayName(SIGDN_PARENTRELATIVEPARSING,&pName)) && FAILED(pChild->GetDisplayName(SIGDN_NORMALDISPLAY,&pName)))
				return;
			pName.MakeUpper();
			unsigned int hash0=pTreeItem->bPrograms?CalcFNVHash(L"\\",FNV_HASH0):FNV_HASH0;
			order.push_back(CalcFNVHash(pName,hash0));
		}
		else
			order.push_back(pTreeItem->GetNameHash());
	}
	if (order.empty())
		return;

	// save order
	CRegKey regOrder;
	wchar_t name[100];
	if (regOrder.Open(HKEY_CURRENT_USER,L"Software\\OpenShell\\StartMenu\\Order")!=ERROR_SUCCESS)
		regOrder.Create(HKEY_CURRENT_USER,L"Software\\OpenShell\\StartMenu\\Order");

	Sprintf(name,_countof(name),L"%08X",folderHash);
	regOrder.SetBinaryValue(name,&order[0],(int)order.size()*4);
}
