Files
Open-Shell-Menu/Src/StartMenu/StartMenuDLL/ProgramsTree.cpp
Xenhat f4dd56155b Rebrand to Open-Shell (#36) (#58)
* Rebrand to Open-Shell

* Slight installer branding improvement
2018-08-05 15:22:10 -04:00

1772 lines
51 KiB
C++

// 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(m_hWnd,L"treeview");
if (skin.BHasScrollbar)
m_ScrollTheme=OpenThemeData(m_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\\Menu",KEY_WRITE)!=ERROR_SUCCESS)
regKey.Create(HKEY_CURRENT_USER,L"Software\\OpenShell\\Menu");
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);
if (!bOld)
CMenuContainer::HideTemp(false);
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);
}