mirror of
https://github.com/Open-Shell/Open-Shell-Menu.git
synced 2026-04-12 01:47:24 +10:00
943 lines
28 KiB
C++
943 lines
28 KiB
C++
// ## MenuContainer.h
|
|
// 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
|
|
|
|
// DragDrop.cpp - handles the drag and drop functionality of CMenuContainer
|
|
|
|
#include "stdafx.h"
|
|
#include "MenuContainer.h"
|
|
#include "StartMenuDLL.h"
|
|
#include "SettingsUI.h"
|
|
#include "FNVHash.h"
|
|
#include "Settings.h"
|
|
#include "ResourceHelper.h"
|
|
#include "Translations.h"
|
|
#include "FileHelper.h"
|
|
#include <algorithm>
|
|
|
|
class CMetroDataObject: public IDataObject
|
|
{
|
|
public:
|
|
CMetroDataObject( const wchar_t *path );
|
|
~CMetroDataObject( void );
|
|
|
|
// from IUnknown
|
|
virtual HRESULT STDMETHODCALLTYPE QueryInterface( REFIID riid, void **ppvObject );
|
|
virtual ULONG STDMETHODCALLTYPE AddRef( void )
|
|
{
|
|
return ++m_RefCount;
|
|
}
|
|
virtual ULONG STDMETHODCALLTYPE Release( void )
|
|
{
|
|
if (m_RefCount==1)
|
|
{
|
|
delete this;
|
|
return 0;
|
|
}
|
|
return --m_RefCount;
|
|
}
|
|
|
|
// from IDataObject
|
|
virtual HRESULT STDMETHODCALLTYPE GetData( FORMATETC *pformatetcIn, STGMEDIUM *pmedium );
|
|
virtual HRESULT STDMETHODCALLTYPE SetData( FORMATETC *pformatetc, STGMEDIUM *pmedium, BOOL fRelease ) { return E_NOTIMPL; }
|
|
virtual HRESULT STDMETHODCALLTYPE GetDataHere( FORMATETC *pformatetc, STGMEDIUM *pmedium ) { return E_NOTIMPL; }
|
|
virtual HRESULT STDMETHODCALLTYPE QueryGetData( FORMATETC *pformatetc );
|
|
virtual HRESULT STDMETHODCALLTYPE GetCanonicalFormatEtc( FORMATETC *pformatectIn, FORMATETC *pformatetcOut );
|
|
virtual HRESULT STDMETHODCALLTYPE EnumFormatEtc( DWORD dwDirection, IEnumFORMATETC **ppenumFormatEtc );
|
|
virtual HRESULT STDMETHODCALLTYPE DAdvise( FORMATETC *pformatetc, DWORD advf, IAdviseSink *pAdvSink, DWORD *pdwConnection ) { return OLE_E_ADVISENOTSUPPORTED; }
|
|
virtual HRESULT STDMETHODCALLTYPE DUnadvise( DWORD dwConnection ) { return E_NOTIMPL; }
|
|
virtual HRESULT STDMETHODCALLTYPE EnumDAdvise( IEnumSTATDATA **ppenumAdvise ) { return OLE_E_ADVISENOTSUPPORTED; }
|
|
|
|
private:
|
|
int m_RefCount;
|
|
std::map<CLIPFORMAT,HGLOBAL> m_Data;
|
|
bool m_bContents;
|
|
|
|
void SetData( CLIPFORMAT format, HGLOBAL data );
|
|
};
|
|
|
|
static CLIPFORMAT g_PreferredEffectFormat;
|
|
|
|
CMetroDataObject::CMetroDataObject( const wchar_t *path )
|
|
{
|
|
m_RefCount=0;
|
|
m_bContents=false;
|
|
SetData(CMenuContainer::s_MetroLinkFormat,NULL);
|
|
|
|
if (!path) return;
|
|
HANDLE file=CreateFile(path,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
|
|
if (file!=INVALID_HANDLE_VALUE)
|
|
{
|
|
DWORD size=GetFileSize(file,NULL);
|
|
if (size>0)
|
|
{
|
|
HGLOBAL hContents=NULL, hDesc=NULL;
|
|
hContents=GlobalAlloc(GMEM_MOVEABLE,size);
|
|
if (hContents)
|
|
hDesc=GlobalAlloc(GMEM_MOVEABLE|GMEM_ZEROINIT,sizeof(FILEGROUPDESCRIPTOR));
|
|
if (hDesc)
|
|
{
|
|
FILEGROUPDESCRIPTOR *desc=(FILEGROUPDESCRIPTOR*)GlobalLock(hDesc);
|
|
desc->cItems=1;
|
|
desc->fgd->dwFlags=FD_ATTRIBUTES|FD_FILESIZE|FD_LINKUI|FD_UNICODE;
|
|
desc->fgd->dwFileAttributes=FILE_ATTRIBUTE_NORMAL;
|
|
desc->fgd->nFileSizeLow=size;
|
|
Strcpy(desc->fgd->cFileName,_countof(desc->fgd->cFileName),PathFindFileName(path));
|
|
GlobalUnlock(hDesc);
|
|
SetData(CMenuContainer::s_DescriptorFormat,hDesc);
|
|
hDesc=NULL;
|
|
|
|
DWORD q;
|
|
if (!ReadFile(file,GlobalLock(hContents),size,&q,NULL))
|
|
q=0;
|
|
GlobalUnlock(hContents);
|
|
if (q==size)
|
|
{
|
|
SetData(CMenuContainer::s_ContentsFormat,hContents);
|
|
hContents=NULL;
|
|
m_bContents=true;
|
|
}
|
|
}
|
|
if (hContents) GlobalFree(hContents);
|
|
if (hDesc) GlobalFree(hDesc);
|
|
}
|
|
CloseHandle(file);
|
|
}
|
|
}
|
|
|
|
CMetroDataObject::~CMetroDataObject( void )
|
|
{
|
|
for (std::map<CLIPFORMAT,HGLOBAL>::iterator it=m_Data.begin();it!=m_Data.end();++it)
|
|
if (it->second)
|
|
GlobalFree(it->second);
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE CMetroDataObject::QueryInterface( REFIID riid, void **ppvObject )
|
|
{
|
|
if (riid==IID_IDataObject || riid==IID_IUnknown)
|
|
{
|
|
AddRef();
|
|
*ppvObject=this;
|
|
return S_OK;
|
|
}
|
|
*ppvObject=NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE CMetroDataObject::GetData( FORMATETC *pformatetcIn, STGMEDIUM *pmedium )
|
|
{
|
|
if (!pformatetcIn || !pmedium)
|
|
return E_INVALIDARG;
|
|
|
|
pmedium->hGlobal=NULL;
|
|
pmedium->pUnkForRelease=NULL;
|
|
|
|
if (pformatetcIn->dwAspect!=DVASPECT_CONTENT) return DV_E_DVASPECT;
|
|
if (!(pformatetcIn->tymed&TYMED_HGLOBAL)) return DV_E_TYMED;
|
|
|
|
std::map<CLIPFORMAT,HGLOBAL>::iterator it=m_Data.find(pformatetcIn->cfFormat);
|
|
if (it==m_Data.end()) return DV_E_FORMATETC;
|
|
|
|
wchar_t name[100];
|
|
GetClipboardFormatName(pformatetcIn->cfFormat,name,100);
|
|
Trace(L"GetData: %s, %d",name,pformatetcIn->cfFormat);
|
|
|
|
pmedium->tymed=TYMED_HGLOBAL;
|
|
SIZE_T size=GlobalSize(it->second);
|
|
pmedium->hGlobal=GlobalAlloc(GMEM_MOVEABLE,size);
|
|
if (!pmedium->hGlobal) return E_OUTOFMEMORY;
|
|
|
|
void *src=GlobalLock(it->second);
|
|
void *dst=GlobalLock(pmedium->hGlobal);
|
|
memcpy(dst,src,size);
|
|
GlobalUnlock(pmedium->hGlobal);
|
|
GlobalUnlock(it->second);
|
|
return S_OK;
|
|
}
|
|
|
|
void CMetroDataObject::SetData( CLIPFORMAT format, HGLOBAL data )
|
|
{
|
|
Assert(m_Data.find(format)==m_Data.end());
|
|
m_Data[format]=data;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE CMetroDataObject::QueryGetData( FORMATETC *pformatetc )
|
|
{
|
|
if (!pformatetc) return E_INVALIDARG;
|
|
if (pformatetc->dwAspect!=DVASPECT_CONTENT) return DV_E_DVASPECT;
|
|
if (!(pformatetc->tymed&TYMED_HGLOBAL)) return DV_E_TYMED;
|
|
if (m_Data.find(pformatetc->cfFormat)==m_Data.end()) return DV_E_CLIPFORMAT;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE CMetroDataObject::GetCanonicalFormatEtc( FORMATETC *pformatectIn, FORMATETC *pformatetcOut )
|
|
{
|
|
return pformatetcOut?DATA_S_SAMEFORMATETC:E_INVALIDARG;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE CMetroDataObject::EnumFormatEtc( DWORD dwDirection, IEnumFORMATETC **ppenumFormatEtc )
|
|
{
|
|
if (!ppenumFormatEtc) return E_POINTER;
|
|
*ppenumFormatEtc=NULL;
|
|
if (dwDirection == DATADIR_GET)
|
|
{
|
|
FORMATETC formats[] =
|
|
{
|
|
{CMenuContainer::s_MetroLinkFormat,NULL,DVASPECT_CONTENT,-1,TYMED_HGLOBAL},
|
|
{CMenuContainer::s_DescriptorFormat,NULL,DVASPECT_CONTENT,-1,TYMED_HGLOBAL},
|
|
{CMenuContainer::s_ContentsFormat,NULL,DVASPECT_CONTENT,-1,TYMED_HGLOBAL},
|
|
};
|
|
HRESULT hr = SHCreateStdEnumFmtEtc(m_bContents?3:1,formats,ppenumFormatEtc);
|
|
return hr;
|
|
}
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
CComPtr<IDataObject> CMenuContainer::CreateMetroDataObject( const CItemManager::ItemInfo *pInfo )
|
|
{
|
|
CString path;
|
|
if (!pInfo->PATH.IsEmpty())
|
|
{
|
|
CItemManager::RWLock lock(&g_ItemManager,false,CItemManager::RWLOCK_ITEMS);
|
|
path=pInfo->GetPath();
|
|
}
|
|
IDataObject *pDataObjectIn=new CMetroDataObject(path);
|
|
CComPtr<IDataObject> pDataObject;
|
|
SHCreateDataObject(NULL,0,NULL,pDataObjectIn,IID_IDataObject,(void**)&pDataObject);
|
|
if (pDataObject)
|
|
{
|
|
if (m_pDragSourceHelper)
|
|
{
|
|
g_ItemManager.UpdateItemInfo(pInfo,CItemManager::INFO_EXTRA_LARGE_ICON|CItemManager::INFO_REFRESH_NOW,false);
|
|
int iconSize=CItemManager::EXTRA_LARGE_ICON_SIZE;
|
|
SHDRAGIMAGE di={{iconSize,iconSize},{iconSize/2,iconSize},NULL,CLR_NONE};
|
|
di.hbmpDragImage=(HBITMAP)CopyImage(pInfo->extraLargeIcon->bitmap,IMAGE_BITMAP,0,0,0);
|
|
m_pDragSourceHelper->SetFlags(DSH_ALLOWDROPDESCRIPTIONTEXT);
|
|
if (di.hbmpDragImage)
|
|
m_pDragSourceHelper->InitializeFromBitmap(&di,pDataObject);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pDataObjectIn->Release();
|
|
}
|
|
return pDataObject;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool CMenuContainer::DragOutApps( const CItemManager::ItemInfo *pInfo )
|
|
{
|
|
// drag the Apps tree item for reordering
|
|
CComPtr<IDataObject> pDataObj=CreateMetroDataObject(pInfo);
|
|
|
|
// do drag drop
|
|
s_pDragSource=NULL;
|
|
s_bDragFromTree=false;
|
|
m_DragIndex=-1;
|
|
s_bPreventClosing=true;
|
|
m_DragTime=GetMessageTime();
|
|
SetTimer(TIMER_DRAG,100);
|
|
s_bDragClosed=false;
|
|
DWORD dwEffect=DROPEFFECT_MOVE;
|
|
HRESULT res=SHDoDragDrop(NULL,pDataObj,NULL,dwEffect,&dwEffect);
|
|
|
|
s_pDragSource=NULL;
|
|
s_bDragFromTree=false;
|
|
if (!m_bDestroyed)
|
|
KillTimer(TIMER_DRAG);
|
|
s_bPreventClosing=false;
|
|
|
|
if (s_bDragClosed)
|
|
{
|
|
for (std::vector<CMenuContainer*>::iterator it=s_Menus.begin();it!=s_Menus.end();++it)
|
|
if (!(*it)->m_bDestroyed)
|
|
(*it)->PostMessage(WM_CLOSE);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CMenuContainer::DragOut( int index, bool bApp )
|
|
{
|
|
if (!(m_Options&CONTAINER_DRAG) || s_bNoDragDrop) return false;
|
|
const MenuItem &item=m_Items[index];
|
|
if (!item.pItem1 || (item.id!=MENU_NO && item.id!=MENU_RECENT)) return false;
|
|
|
|
bool bLeft=(GetKeyState(VK_LBUTTON)<0);
|
|
bool bRight=(GetKeyState(VK_RBUTTON)<0);
|
|
if (!bLeft && !bRight) return false;
|
|
|
|
CComPtr<IShellFolder> pFolder;
|
|
PCUITEMID_CHILD child;
|
|
|
|
// get IDataObject for the current item
|
|
CComPtr<IDataObject> pDataObj;
|
|
bool bProtectedLink=false;
|
|
if (bApp && GetWinVersion()<WIN_VER_WIN10)
|
|
pDataObj=CreateMetroDataObject(item.pItemInfo);
|
|
else
|
|
{
|
|
bool bMetroLink=false;
|
|
if (FAILED(SHBindToParent(item.pItem1,IID_IShellFolder,(void**)&pFolder,&child)))
|
|
return true;
|
|
if (FAILED(pFolder->GetUIObjectOf(NULL,1,&child,IID_IDataObject,NULL,(void**)&pDataObj)))
|
|
return true;
|
|
|
|
{
|
|
CItemManager::RWLock lock(&g_ItemManager,false,CItemManager::RWLOCK_ITEMS);
|
|
bProtectedLink=!m_bSubMenu || item.pItemInfo->IsProtectedLink();
|
|
bMetroLink=item.pItemInfo->IsMetroLink();
|
|
}
|
|
if (m_pDragSourceHelper && bMetroLink)
|
|
{
|
|
g_ItemManager.UpdateItemInfo(item.pItemInfo,CItemManager::INFO_EXTRA_LARGE_ICON|CItemManager::INFO_REFRESH_NOW,false);
|
|
int iconSize=CItemManager::EXTRA_LARGE_ICON_SIZE;
|
|
SHDRAGIMAGE di={{iconSize,iconSize},{iconSize/2,iconSize},NULL,CLR_NONE};
|
|
di.hbmpDragImage=(HBITMAP)CopyImage(item.pItemInfo->extraLargeIcon->bitmap,IMAGE_BITMAP,0,0,0);
|
|
m_pDragSourceHelper->SetFlags(DSH_ALLOWDROPDESCRIPTIONTEXT);
|
|
if (di.hbmpDragImage)
|
|
m_pDragSourceHelper->InitializeFromBitmap(&di,pDataObj);
|
|
}
|
|
}
|
|
if (bProtectedLink)
|
|
{
|
|
// protected links default to DROPEFFECT_LINK
|
|
HGLOBAL hGlobal=GlobalAlloc(GMEM_MOVEABLE,4);
|
|
if (hGlobal)
|
|
{
|
|
*(DWORD*)GlobalLock(hGlobal)=DROPEFFECT_LINK;
|
|
GlobalUnlock(hGlobal);
|
|
FORMATETC format={s_PreferredEffectFormat,NULL,DVASPECT_CONTENT,-1,TYMED_HGLOBAL};
|
|
STGMEDIUM medium={TYMED_HGLOBAL};
|
|
medium.hGlobal=hGlobal;
|
|
if (FAILED(pDataObj->SetData(&format,&medium,TRUE)))
|
|
GlobalFree(hGlobal);
|
|
}
|
|
}
|
|
|
|
// force synchronous operation
|
|
{
|
|
CComQIPtr<IDataObjectAsyncCapability> pAsync(pDataObj);
|
|
if (pAsync)
|
|
pAsync->SetAsyncMode(FALSE);
|
|
}
|
|
|
|
// do drag drop
|
|
s_pDragSource=this;
|
|
s_bDragFromTree=(index==m_ProgramTreeIndex);
|
|
m_DragIndex=index;
|
|
s_bDragMovable=(item.id==MENU_NO && index<m_OriginalCount) || (item.jumpIndex>=0 && s_JumpList.groups[LOWORD(item.jumpIndex)].type==CJumpGroup::TYPE_PINNED);
|
|
|
|
s_bPreventClosing=true;
|
|
m_DragTime=GetMessageTime();
|
|
s_bDragClosed=false;
|
|
SetTimer(TIMER_DRAG,100);
|
|
DWORD dwEffect=DROPEFFECT_COPY|DROPEFFECT_MOVE|DROPEFFECT_LINK;
|
|
HRESULT res=SHDoDragDrop(NULL,pDataObj,NULL,dwEffect,&dwEffect);
|
|
|
|
s_pDragSource=NULL;
|
|
s_bDragFromTree=false;
|
|
if (!m_bDestroyed)
|
|
KillTimer(TIMER_DRAG);
|
|
s_bDragMovable=false;
|
|
s_bPreventClosing=false;
|
|
|
|
if (s_bDragClosed)
|
|
{
|
|
for (std::vector<CMenuContainer*>::iterator it=s_Menus.begin();it!=s_Menus.end();++it)
|
|
if (!(*it)->m_bDestroyed)
|
|
(*it)->PostMessage(WM_CLOSE);
|
|
return true;
|
|
}
|
|
|
|
if (pFolder && res==DRAGDROP_S_DROP && !m_bDestroyed)
|
|
{
|
|
// check if the item still exists. refresh the menu if it doesn't
|
|
SFGAOF flags=SFGAO_VALIDATE;
|
|
if (FAILED(pFolder->GetAttributesOf(1,&child,&flags)))
|
|
{
|
|
SetActiveWindow();
|
|
// close all submenus
|
|
for (int i=(int)s_Menus.size()-1;s_Menus[i]!=this;i--)
|
|
if (!s_Menus[i]->m_bDestroyed)
|
|
s_Menus[i]->DestroyWindow();
|
|
// update menu
|
|
PostRefreshMessage();
|
|
}
|
|
}
|
|
|
|
// activate the top non-destroyed menu
|
|
for (int i=(int)s_Menus.size()-1;i>=0;i--)
|
|
if (!s_Menus[i]->m_bDestroyed)
|
|
{
|
|
SetForegroundWindow(s_Menus[i]->m_hWnd);
|
|
s_Menus[i]->SetActiveWindow();
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CMenuContainer::SetDropTip( IDataObject *pDataObj, bool bPin )
|
|
{
|
|
DROPDESCRIPTION desc={bPin?DROPIMAGE_LINK:DROPIMAGE_INVALID};
|
|
Strcpy(desc.szMessage,_countof(desc.szMessage),bPin?FindTranslation(L"Menu.PinStart",L"Pin to Start menu"):L"");
|
|
HGLOBAL hDesc=GlobalAlloc(GMEM_MOVEABLE,sizeof(desc));
|
|
if (hDesc)
|
|
{
|
|
memcpy(GlobalLock(hDesc),&desc,sizeof(desc));
|
|
GlobalUnlock(hDesc);
|
|
FORMATETC fmte={CMenuContainer::s_DropDescriptionFormat,NULL,DVASPECT_CONTENT,-1,TYMED_HGLOBAL};
|
|
STGMEDIUM medium={};
|
|
medium.tymed=TYMED_HGLOBAL;
|
|
medium.hGlobal=hDesc;
|
|
|
|
if (FAILED(pDataObj->SetData(&fmte,&medium,TRUE)))
|
|
{
|
|
GlobalFree(hDesc);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CMenuContainer::GetDragEffect( DWORD &grfKeyState, DWORD *pdwEffect )
|
|
{
|
|
grfKeyState&=MK_SHIFT|MK_CONTROL|MK_ALT;
|
|
if (s_bNoDragDrop || !(m_Options&CONTAINER_DROP))
|
|
{
|
|
*pdwEffect=DROPEFFECT_NONE; // can't drop here
|
|
return;
|
|
}
|
|
if (!m_pDropFolder[0] && !(s_pDragSource==this && s_bDragMovable && !s_bDragFromTree))
|
|
{
|
|
*pdwEffect=DROPEFFECT_NONE; // can't drop here
|
|
return;
|
|
}
|
|
|
|
// only accept known data formats
|
|
FORMATETC format1={s_ShellFormat,NULL,DVASPECT_CONTENT,-1,TYMED_HGLOBAL};
|
|
FORMATETC format2={s_ShellUrlFormat,NULL,DVASPECT_CONTENT,-1,TYMED_HGLOBAL};
|
|
FORMATETC format3={s_MetroLinkFormat,NULL,DVASPECT_CONTENT,-1,TYMED_HGLOBAL};
|
|
if (m_pDragObject->QueryGetData(&format1)!=S_OK && m_pDragObject->QueryGetData(&format2)!=S_OK && m_pDragObject->QueryGetData(&format3)!=S_OK)
|
|
{
|
|
*pdwEffect=DROPEFFECT_NONE;
|
|
return;
|
|
}
|
|
|
|
if (s_pDragSource)
|
|
{
|
|
if (s_pDragSource->m_Items[s_pDragSource->m_DragIndex].id==MENU_RECENT)
|
|
*pdwEffect&=DROPEFFECT_LINK; // dragging a recent item (allow only link)
|
|
else if (grfKeyState==0 && !s_bRightDrag && s_pDragSource==this && s_bDragMovable && !s_bDragFromTree)
|
|
*pdwEffect&=DROPEFFECT_MOVE; // dragging within the same menu - use move by default
|
|
else if (grfKeyState==0 && !s_bRightDrag)
|
|
*pdwEffect&=(s_bDragMovable && m_bSubMenu && (s_pDragSource->m_Options&CONTAINER_PROGRAMS))?DROPEFFECT_MOVE:DROPEFFECT_LINK; // dragging normal item to a different menu - default to move or link
|
|
}
|
|
|
|
if (m_pDragObject->QueryGetData(&format3)==S_OK)
|
|
{
|
|
if (m_Options&CONTAINER_APPS)
|
|
*pdwEffect&=(s_pDragSource==this && !s_bDragFromTree)?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 (!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;
|
|
}
|
|
else if (!m_bSubMenu && grfKeyState==0 && (*pdwEffect&DROPEFFECT_LINK))
|
|
{
|
|
// when a file is dragged to the start menu he usually wants to make a shortcut
|
|
// so when right-dragging, and linking is allowed, make it the default
|
|
grfKeyState=MK_SHIFT|MK_CONTROL;
|
|
}
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE CMenuContainer::DragEnter( IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect )
|
|
{
|
|
s_bRightDrag=(grfKeyState&MK_RBUTTON)!=0;
|
|
m_pDragObject=pDataObj;
|
|
if (m_pDropTargetHelper)
|
|
{
|
|
POINT p={pt.x,pt.y};
|
|
m_pDropTargetHelper->DragEnter(m_hWnd,m_pDragObject,&p,*pdwEffect);
|
|
}
|
|
if (!m_bSubMenu && !s_bShowTopEmpty)
|
|
{
|
|
// when dragging over the main menu, show an (Empty) item at the top so the user can drop items there
|
|
for (size_t i=0;i<m_Items.size();i++)
|
|
if (m_Items[i].id==MENU_EMPTY_TOP)
|
|
{
|
|
s_bShowTopEmpty=true;
|
|
if (m_ScrollHeight>0 && m_FolderHash[0])
|
|
s_MenuScrolls[m_FolderHash[0]]=m_ScrollOffset;
|
|
else
|
|
s_MenuScrolls.erase(m_FolderHash[0]);
|
|
InitWindow();
|
|
break;
|
|
}
|
|
}
|
|
m_DragHoverTime=GetMessageTime()-10000;
|
|
m_DragHoverItem=-1;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE CMenuContainer::DragOver( DWORD grfKeyState, POINTL pt, DWORD *pdwEffect )
|
|
{
|
|
POINT p0={pt.x,pt.y};
|
|
if (m_pDropTargetHelper)
|
|
m_pDropTargetHelper->DragOver(&p0,*pdwEffect);
|
|
|
|
POINT p=p0;
|
|
ScreenToClient(&p);
|
|
int index=HitTest(p,NULL,true);
|
|
|
|
int mark=-1;
|
|
bool bAfter=false;
|
|
|
|
const CItemManager::ItemInfo *pInfo=NULL;
|
|
RECT rcItem;
|
|
if (index>=0)
|
|
{
|
|
GetItemRect(index,rcItem);
|
|
if (!m_bSubMenu && index<m_OriginalCount && (!s_pDragSource || s_bDragFromTree || s_bDragMovable || m_Items[index].id!=MENU_RECENT))
|
|
{
|
|
int h=(rcItem.bottom-rcItem.top)/4;
|
|
if (m_Items[index].id!=MENU_NO || (p.y>=rcItem.top+h && p.y<rcItem.bottom-h))
|
|
pInfo=m_Items[index].pItemInfo;
|
|
}
|
|
}
|
|
int dropTargetIndex=pInfo?index:-1;
|
|
if (m_pDropTargetInfo!=pInfo)
|
|
{
|
|
if (m_pDropTarget)
|
|
{
|
|
m_pDropTarget->DragLeave();
|
|
m_pDropTarget=NULL;
|
|
}
|
|
if (pInfo && pInfo->GetPidl())
|
|
{
|
|
m_pDropTargetInfo=pInfo;
|
|
CComPtr<IShellItem> pItem;
|
|
SHCreateItemFromIDList(pInfo->GetPidl(),IID_IShellItem,(void**)&pItem);
|
|
if (pItem)
|
|
{
|
|
pItem->BindToHandler(NULL,BHID_SFUIObject,IID_IDropTarget,(void**)&m_pDropTarget);
|
|
if (m_pDropTarget)
|
|
{
|
|
m_pDropTarget->DragEnter(m_pDragObject,grfKeyState,pt,pdwEffect);
|
|
if (*pdwEffect==0)
|
|
m_pDropTargetInfo=NULL;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
m_pDropTargetInfo=NULL;
|
|
}
|
|
if (!m_pDropTargetInfo)
|
|
{
|
|
dropTargetIndex=-1;
|
|
m_pDropTarget=NULL;
|
|
}
|
|
if (dropTargetIndex!=m_DropTargetIndex)
|
|
{
|
|
InvalidateItem(dropTargetIndex);
|
|
InvalidateItem(m_DropTargetIndex);
|
|
m_DropTargetIndex=dropTargetIndex;
|
|
}
|
|
if (m_pDropTarget)
|
|
{
|
|
SetDropTip(m_pDragObject,false);
|
|
SetInsertMark(-1,false);
|
|
return m_pDropTarget->DragOver(grfKeyState,pt,pdwEffect);
|
|
}
|
|
|
|
s_bRightDrag=(grfKeyState&MK_RBUTTON)!=0;
|
|
GetDragEffect(grfKeyState,pdwEffect);
|
|
|
|
if (index>=0 && index<m_OriginalCount && m_Items[index].id!=MENU_RECENT)
|
|
{
|
|
// set the new insert mark
|
|
mark=index;
|
|
int y=(rcItem.top+rcItem.bottom)/2;
|
|
if (p.y<y)
|
|
{
|
|
// insert above
|
|
if (m_Items[index].id!=MENU_NO && m_Items[index].id!=MENU_EMPTY && m_Items[index].id!=MENU_EMPTY_TOP && (index==0 || m_Items[index-1].id!=MENU_NO))
|
|
mark=-1;
|
|
}
|
|
else
|
|
{
|
|
// insert below
|
|
bAfter=true;
|
|
if (m_Items[index].id!=MENU_NO && m_Items[index].id!=MENU_EMPTY && m_Items[index].id!=MENU_EMPTY_TOP && (index==m_Items.size()-1 || m_Items[index+1].id!=MENU_NO))
|
|
mark=-1;
|
|
}
|
|
if (mark==-1 && m_Items[index].bFolder && (m_Items[index].bPrograms || m_Items[index].id==MENU_NO))
|
|
{
|
|
SetHotItem(index);
|
|
}
|
|
else
|
|
{
|
|
SetHotItem(-1);
|
|
}
|
|
if (!*pdwEffect || ((m_Options&CONTAINER_AUTOSORT) && s_pDragSource==this && !s_bDragFromTree && s_bDragMovable))
|
|
mark=-1;
|
|
}
|
|
else if (s_pDragSource==this && !s_bDragFromTree && s_bDragMovable && m_Items[m_DragIndex].jumpIndex>=0 && index>=0 && m_Items[index].jumpIndex>=0)
|
|
{
|
|
int groupIndex=LOWORD(m_Items[m_DragIndex].jumpIndex);
|
|
if (s_JumpList.groups[groupIndex].type==CJumpGroup::TYPE_PINNED && LOWORD(m_Items[index].jumpIndex)==groupIndex && (*pdwEffect&DROPEFFECT_MOVE))
|
|
{
|
|
// reorder jump list
|
|
*pdwEffect=DROPEFFECT_MOVE;
|
|
int y=(rcItem.top+rcItem.bottom)/2;
|
|
bAfter=p.y>=y;
|
|
SetHotItem(-1);
|
|
}
|
|
else
|
|
index=-1;
|
|
mark=index;
|
|
}
|
|
else
|
|
{
|
|
if (!m_bSubMenu && index<m_OriginalCount && PtInRect(&m_rContent,p) && (s_pDragSource!=this || s_bDragFromTree || !s_bDragMovable || m_Items[m_DragIndex].id==MENU_RECENT))
|
|
{
|
|
if (s_bWin7Style)
|
|
{
|
|
for (int i=0;i<m_ScrollCount;i++)
|
|
{
|
|
if (m_Items[i].id==MENU_NO || m_Items[i].id==MENU_EMPTY || m_Items[i].id==MENU_EMPTY_TOP)
|
|
mark=i;
|
|
}
|
|
}
|
|
else
|
|
mark=m_ScrollCount-1;
|
|
bAfter=true;
|
|
}
|
|
index=-1;
|
|
}
|
|
SetInsertMark(mark,bAfter);
|
|
UpdateScroll(&p,false);
|
|
|
|
// check if the hover delay is done and it's time to open the item
|
|
if (index>=0 && index==m_DragHoverItem)
|
|
{
|
|
int hoverTime=(int)s_HoverTime;
|
|
if (m_Items[index].id==MENU_PROGRAMS && GetSettingInt(L"ProgramsStyle")==PROGRAMS_INLINE)
|
|
hoverTime=(int)s_ProgramsHoverTime;
|
|
if ((GetMessageTime()-m_DragHoverTime)>hoverTime && m_Submenu!=m_DragHoverItem)
|
|
{
|
|
// expand m_DragHoverItem
|
|
if (index<m_OriginalCount && !m_Items[index].bHasJumpList && (!m_Items[index].bFolder || m_Items[index].pItem1))
|
|
{
|
|
ActivateData data;
|
|
data.bNoModifiers=GetKeyState(VK_SHIFT)<0;
|
|
ActivateItem(index,ACTIVATE_OPEN,NULL,&data);
|
|
}
|
|
if (!m_Items[index].bFolder)
|
|
SetHotItem(-1);
|
|
m_DragHoverTime=GetMessageTime();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_DragHoverItem=index;
|
|
m_DragHoverTime=GetMessageTime();
|
|
}
|
|
|
|
if (m_InsertMark<0)
|
|
*pdwEffect=0;
|
|
|
|
int before=m_InsertMark;
|
|
if (before>=0 && m_bInsertAfter && (before!=0 || (m_Items[0].id!=MENU_EMPTY && m_Items[0].id!=MENU_EMPTY_TOP)))
|
|
before++;
|
|
if (before>=0 && !m_bSubMenu && (*pdwEffect&DROPEFFECT_LINK) && (s_pDragSource!=this || s_bDragFromTree || !s_bDragMovable))
|
|
{
|
|
*pdwEffect=DROPEFFECT_LINK;
|
|
SetDropTip(m_pDragObject,true);
|
|
}
|
|
else
|
|
SetDropTip(m_pDragObject,false);
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE CMenuContainer::DragLeave( void )
|
|
{
|
|
if (m_pDropTarget)
|
|
{
|
|
m_pDropTarget->DragLeave();
|
|
m_pDropTarget=NULL;
|
|
}
|
|
InvalidateItem(m_DropTargetIndex);
|
|
m_DropTargetIndex=-1;
|
|
m_pDropTargetInfo=NULL;
|
|
if (m_pDropTargetHelper)
|
|
m_pDropTargetHelper->DragLeave();
|
|
SetDropTip(m_pDragObject,false);
|
|
SetInsertMark(-1,false);
|
|
m_pDragObject=NULL;
|
|
UpdateScroll(NULL,false);
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE CMenuContainer::Drop( IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect )
|
|
{
|
|
if (s_pDragSource)
|
|
{
|
|
if (!s_pDragSource->m_bDestroyed)
|
|
s_pDragSource->KillTimer(TIMER_DRAG);
|
|
}
|
|
if (m_pDropTargetHelper)
|
|
{
|
|
POINT p={pt.x,pt.y};
|
|
m_pDropTargetHelper->Drop(pDataObj,&p,*pdwEffect);
|
|
}
|
|
if (m_pDropTarget)
|
|
{
|
|
m_pDragObject=NULL;
|
|
HRESULT res=m_pDropTarget->Drop(pDataObj,grfKeyState,pt,pdwEffect);
|
|
m_pDropTarget=NULL;
|
|
return res;
|
|
}
|
|
|
|
GetDragEffect(grfKeyState,pdwEffect);
|
|
m_pDragObject=NULL;
|
|
|
|
int before=m_InsertMark;
|
|
if (before<0) return S_OK;
|
|
if (before>=0 && m_bInsertAfter && (before!=0 || (m_Items[0].id!=MENU_EMPTY && m_Items[0].id!=MENU_EMPTY_TOP)))
|
|
before++;
|
|
|
|
// clear the insert mark
|
|
SetInsertMark(-1,false);
|
|
|
|
int folderIndex=0;
|
|
if (before>=0)
|
|
folderIndex=m_Items[min(before,(int)m_Items.size()-1)].priority>1?1:0;
|
|
|
|
if (s_pDragSource==this && !s_bDragFromTree && s_bDragMovable && (*pdwEffect&DROPEFFECT_MOVE) && m_DragIndex!=m_ProgramTreeIndex && m_Items[m_DragIndex].priority==(m_Items[min(before,(int)m_Items.size()-1)].priority&2))
|
|
{
|
|
if (before==m_DragIndex || before==m_DragIndex+1)
|
|
return S_OK;
|
|
// dropped in the same menu, just rearrange the items
|
|
PlayMenuSound(SOUND_DROP);
|
|
if (m_Items[m_DragIndex].jumpIndex>=0 && s_JumpList.groups[LOWORD(m_Items[m_DragIndex].jumpIndex)].type==CJumpGroup::TYPE_PINNED)
|
|
{
|
|
// reordering pinned item
|
|
int groupIdx=LOWORD(m_Items[m_DragIndex].jumpIndex);
|
|
int itemIdx=HIWORD(m_Items[m_DragIndex].jumpIndex);
|
|
for (int i=0;i<=m_DragIndex;i++)
|
|
{
|
|
if (m_Items[i].jumpIndex>=0 && LOWORD(m_Items[i].jumpIndex)==groupIdx)
|
|
{
|
|
// found first pinned index
|
|
PinJumpItem(s_JumpAppInfo,s_JumpList,groupIdx,itemIdx,true,before-i);
|
|
PostRefreshMessage();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (!(m_Options&CONTAINER_AUTOSORT))
|
|
{
|
|
std::vector<SortMenuItem> items;
|
|
int skip1=0, skip2=0;
|
|
for (int i=0;i<m_OriginalCount;i++)
|
|
{
|
|
if (m_Items[i].id==MENU_NO)
|
|
{
|
|
SortMenuItem item(m_Items[i]);
|
|
items.push_back(item);
|
|
}
|
|
else
|
|
{
|
|
if (i<m_DragIndex) skip1++;
|
|
if (i<before) skip2++;
|
|
}
|
|
}
|
|
SortMenuItem drag=items[m_DragIndex-skip1];
|
|
items.erase(items.begin()+(m_DragIndex-skip1));
|
|
if (before-skip2>m_DragIndex-skip1)
|
|
before--;
|
|
items.insert(items.begin()+(before-skip2),drag);
|
|
SaveItemOrder(items);
|
|
if (m_bTwoColumns && s_MenuMode==MODE_JUMPLIST)
|
|
SetMenuMode(MODE_NORMAL);
|
|
PostRefreshMessage();
|
|
}
|
|
}
|
|
else if (m_pDropFolder[folderIndex])
|
|
{
|
|
// simulate dropping the object into the original folder
|
|
PlayMenuSound(SOUND_DROP);
|
|
|
|
if (before>=0 && !m_bSubMenu && (*pdwEffect&DROPEFFECT_LINK) && (s_pDragSource!=this || s_bDragFromTree || !s_bDragMovable))
|
|
*pdwEffect=DROPEFFECT_LINK;
|
|
|
|
bool bDropped=false;
|
|
if (!m_bSubMenu && (*pdwEffect&DROPEFFECT_LINK) && !s_bRightDrag)
|
|
{
|
|
// if dropping a single folder onto the main menu, create a fake folder
|
|
CComPtr<IShellItemArray> pArray;
|
|
if (SUCCEEDED(SHCreateShellItemArrayFromDataObject(pDataObj,IID_IShellItemArray,(void**)&pArray)))
|
|
{
|
|
DWORD count;
|
|
CComPtr<IShellItem> pItem;
|
|
if (SUCCEEDED(pArray->GetCount(&count)) && count==1 && SUCCEEDED(pArray->GetItemAt(0,&pItem)))
|
|
{
|
|
CComString pPath;
|
|
if (pItem->GetDisplayName(SIGDN_FILESYSPATH,&pPath)==S_OK && PathIsDirectory(pPath))
|
|
{
|
|
wchar_t path[_MAX_PATH];
|
|
if (SUCCEEDED(SHGetPathFromIDList(m_Path1[0],path)))
|
|
{
|
|
wchar_t fname[_MAX_FNAME];
|
|
Strcpy(fname,_countof(fname),PathFindFileName(pPath));
|
|
int len=Strlen(fname);
|
|
while (len>0 && (fname[len-1]=='\\' || fname[len-1]==':'))
|
|
len--;
|
|
fname[len]=0;
|
|
PathAppend(path,fname);
|
|
wchar_t finalPath[_MAX_PATH];
|
|
PathYetAnotherMakeUniqueName(finalPath,path,NULL,PathFindFileName(path));
|
|
if (CreateFakeFolder(pPath,finalPath))
|
|
{
|
|
bDropped=true;
|
|
wchar_t locName[_MAX_PATH];
|
|
int locIndex;
|
|
if (SHGetLocalizedName(pPath,locName,_countof(locName),&locIndex)==S_OK)
|
|
{
|
|
SHSetLocalizedName(finalPath,locName,locIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!bDropped)
|
|
{
|
|
// 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(m_Path1[folderIndex],NULL,IID_IShellFolder,(void**)&pFolder)) || FAILED(pFolder->CreateViewObject(g_OwnerWindow,IID_IDropTarget,(void**)&pTarget)))
|
|
return S_OK;
|
|
|
|
DWORD dwEffect=*pdwEffect;
|
|
|
|
if (s_bRightDrag)
|
|
{
|
|
if (FAILED(pTarget->DragEnter(pDataObj,MK_RBUTTON|grfKeyState,pt,&dwEffect)))
|
|
return S_OK;
|
|
dwEffect=*pdwEffect;
|
|
pTarget->DragOver(MK_RBUTTON|grfKeyState,pt,&dwEffect);
|
|
}
|
|
else
|
|
{
|
|
if (FAILED(pTarget->DragEnter(pDataObj,MK_LBUTTON|grfKeyState,pt,&dwEffect)))
|
|
return S_OK;
|
|
dwEffect=*pdwEffect;
|
|
pTarget->DragOver(MK_LBUTTON|grfKeyState,pt,pdwEffect);
|
|
}
|
|
CComQIPtr<IDataObjectAsyncCapability> pAsync=pDataObj;
|
|
if (pAsync)
|
|
pAsync->SetAsyncMode(FALSE);
|
|
for (auto& it : s_Menus)
|
|
{
|
|
if (!it->m_bDestroyed)
|
|
{
|
|
it->EnableWindow(FALSE); // disable all menus
|
|
it->SetWindowPos(HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
|
}
|
|
}
|
|
bool bAllPrograms=s_bAllPrograms;
|
|
if (bAllPrograms) ::EnableWindow(g_TopWin7Menu,FALSE);
|
|
bool bOld=s_bPreventClosing;
|
|
s_bPreventClosing=true;
|
|
AddRef();
|
|
pTarget->Drop(pDataObj,grfKeyState,pt,pdwEffect);
|
|
s_bPreventClosing=bOld;
|
|
for (auto& it : s_Menus)
|
|
{
|
|
if (!it->m_bDestroyed)
|
|
{
|
|
it->EnableWindow(TRUE); // enable all menus
|
|
it->SetWindowPos(HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
|
}
|
|
}
|
|
if (bAllPrograms) ::EnableWindow(g_TopWin7Menu,TRUE);
|
|
}
|
|
else
|
|
{
|
|
AddRef();
|
|
}
|
|
|
|
if (!m_bDestroyed)
|
|
{
|
|
SetForegroundWindow(m_hWnd);
|
|
SetActiveWindow();
|
|
SetFocus();
|
|
}
|
|
|
|
if (before>=0 && !(m_Options&CONTAINER_AUTOSORT))
|
|
{
|
|
std::vector<SortMenuItem> items;
|
|
int skip=0;
|
|
for (int i=0;i<m_OriginalCount;i++)
|
|
{
|
|
if (m_Items[i].id==MENU_NO)
|
|
{
|
|
SortMenuItem item(m_Items[i]);
|
|
items.push_back(item);
|
|
}
|
|
else if (i<before)
|
|
skip++;
|
|
}
|
|
SortMenuItem ins(L"",FNV_HASH0,false,false,folderIndex*2,0);
|
|
items.insert(items.begin()+(before-skip),ins);
|
|
SaveItemOrder(items);
|
|
}
|
|
if (m_bTwoColumns && s_MenuMode==MODE_JUMPLIST)
|
|
SetMenuMode(MODE_NORMAL);
|
|
PostRefreshMessage();
|
|
Release();
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
HRESULT STDMETHODCALLTYPE CDropTargetProxy::DragEnter( IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect )
|
|
{
|
|
if (!m_pOwner) return E_FAIL;
|
|
return m_pOwner->DragEnter(pDataObj,grfKeyState,pt,pdwEffect);
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE CDropTargetProxy::DragOver( DWORD grfKeyState, POINTL pt, DWORD *pdwEffect )
|
|
{
|
|
if (!m_pOwner) return E_FAIL;
|
|
return m_pOwner->DragOver(grfKeyState,pt,pdwEffect);
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE CDropTargetProxy::DragLeave( void )
|
|
{
|
|
if (!m_pOwner) return E_FAIL;
|
|
return m_pOwner->DragLeave();
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE CDropTargetProxy::Drop( IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect )
|
|
{
|
|
if (!m_pOwner) return E_FAIL;
|
|
return m_pOwner->Drop(pDataObj,grfKeyState,pt,pdwEffect);
|
|
}
|