mirror of
https://github.com/Open-Shell/Open-Shell-Menu.git
synced 2026-04-11 17:37:22 +10:00
In several cases we don't want menu(s) to close when an action on menu was done. For example on drag-n-drop or menu item deletion (where confirmation dialog is shown). In such situations `s_bPreventClosing` was set to true (and then back to false when closing was allowed again). Though original code honored this variable only in certain situations and typically (at least on Win10) menus were hidden/closed despite of it. This patch changes the behavior and menus(s) are not closed when `s_bPreventClosing` is set to true. Basically now menu(s) stay visible until there is an action that changes active window. Following functionality was also removed because it is not needed now: * CMenuContainer::HideTemp * COwnerWindow::OnClear WM_CLEAR was sent to the window only by already removed `HideTemp`
933 lines
28 KiB
C++
933 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 (std::vector<CMenuContainer*>::iterator it=s_Menus.begin();it!=s_Menus.end();++it)
|
|
if (!(*it)->m_bDestroyed)
|
|
(*it)->EnableWindow(FALSE); // disable all menus
|
|
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 (std::vector<CMenuContainer*>::iterator it=s_Menus.begin();it!=s_Menus.end();++it)
|
|
if (!(*it)->m_bDestroyed)
|
|
(*it)->EnableWindow(TRUE); // enable all menus
|
|
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);
|
|
}
|