Files
Open-Shell-Menu/Src/StartMenu/StartMenuDLL/DragDrop.cpp
ge0rdi 9ebe53fdbf Fix issue with confirmation dialog being blocked
Delete confirmation dialog may be (partially) hidden behing start menu.
As a result the dialog may be not accessible because start menu(s) are
displayed as top-most windows.

Thus when executing menu command we will make menu(s) non-topmost. So that
other windows can be drawn on top of menu(s).

Fixes #257
2020-02-08 11:23:05 +01:00

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);
}