// 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 #include 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((CWindowImpl*)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::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 pItem; if (FAILED(SHCreateItemFromIDList(pTreeItem->pItemInfo1->GetPidl(),IID_IShellItem,(void**)&pItem))) return 0; CComPtr 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 &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 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 &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 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::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 &items ) { unsigned int hash0Prog=CalcFNVHash(L"\\",FNV_HASH0); unsigned int hash0=FNV_HASH0; std::vector links; GetMetroLinks(links,true); for (std::vector::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 &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 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 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::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::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 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::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 items; GetFolderItems(items,hParent); HTREEITEM hAfter=NULL; for (std::vector::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 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;iname; 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->orderorder; 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.toprcClient.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.yrc.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=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=(dym_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 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 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 pDesktop; SHGetDesktopFolder(&pDesktop); CComPtr pFolder; CComPtr 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 pAsync=pDataObj; if (pAsync) pAsync->SetAsyncMode(FALSE); for (std::vector::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::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 &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 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 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); }