2344 lines
68 KiB
C++
2344 lines
68 KiB
C++
#include "shellprv.h"
|
|
#include "defview.h"
|
|
#include "lvutil.h"
|
|
#include "ids.h"
|
|
#include "idlcomm.h"
|
|
#pragma hdrstop
|
|
|
|
#include "datautil.h"
|
|
#include "apithk.h"
|
|
|
|
BOOL DAD_IsDraggingImage(void);
|
|
void DAD_SetDragCursor(int idCursor);
|
|
BOOL DAD_IsDragging();
|
|
|
|
#define MONITORS_MAX 16 // Is this really the max?
|
|
|
|
#define DCID_NULL 0
|
|
#define DCID_NO 1
|
|
#define DCID_MOVE 2
|
|
#define DCID_COPY 3
|
|
#define DCID_LINK 4
|
|
#define DCID_MAX 5
|
|
|
|
#define TF_DRAGIMAGES 0x02000000
|
|
#define DRAGDROP_ALPHA 120
|
|
#define MAX_WIDTH_ALPHA 200
|
|
#define MAX_HEIGHT_ALPHA 200
|
|
|
|
#define CIRCULAR_ALPHA // Circular Alpha Blending Centered on Center of image
|
|
|
|
class CDragImages : public IDragSourceHelper, IDropTargetHelper
|
|
{
|
|
public:
|
|
// IUnknown methods
|
|
STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
|
|
STDMETHODIMP_(ULONG) AddRef() { return 2; }; // One global Com object per process
|
|
STDMETHODIMP_(ULONG) Release() { return 1; }; // One global Com object per process
|
|
|
|
// IDragSourceHelper methods
|
|
STDMETHODIMP InitializeFromBitmap(LPSHDRAGIMAGE pshdi, IDataObject* pdtobj);
|
|
STDMETHODIMP InitializeFromWindow(HWND hwnd, POINT* ppt, IDataObject* pdtobj);
|
|
|
|
// IDropTargetHelper methods
|
|
STDMETHODIMP DragEnter(HWND hwndTarget, IDataObject* pdtobj, POINT* ppt, DWORD dwEffect);
|
|
STDMETHODIMP DragLeave();
|
|
STDMETHODIMP DragOver(POINT* ppt, DWORD dwEffect);
|
|
STDMETHODIMP Drop(IDataObject* pdtobj, POINT* ppt, DWORD dwEffect);
|
|
STDMETHODIMP Show(BOOL fShow);
|
|
|
|
// These are public so the DAD_* routines can access.
|
|
BOOL IsDragging() { return (Initialized() && _Single.bDragging); };
|
|
BOOL IsDraggingImage() { return (Initialized() && _fImage && _Single.bDragging); };
|
|
BOOL IsDraggingLayeredWindow() { return _shdi.hbmpDragImage != NULL; };
|
|
BOOL SetDragImage(HIMAGELIST himl, int index, POINT * pptOffset);
|
|
void SetDragCursor(int idCursor);
|
|
HWND GetTarget() { return _hwndTarget; }
|
|
BOOL Initialized();
|
|
DWORD GetThread() { return _idThread; };
|
|
void FreeDragData();
|
|
|
|
void ThreadDetach();
|
|
void ProcessDetach();
|
|
|
|
// for drag source feedback communication
|
|
void SetDropEffectCursor(int idCur);
|
|
|
|
CDragImages() {};
|
|
|
|
private:
|
|
~CDragImages();
|
|
|
|
void _InitDragData();
|
|
BOOL _IsLayeredSupported();
|
|
|
|
HRESULT _SaveToDataObject(IDataObject* pdtobj);
|
|
HRESULT _LoadFromDataObject(IDataObject* pdtobj);
|
|
|
|
HRESULT _LoadLayerdBitmapBits(HGLOBAL hGlobal);
|
|
HRESULT _SaveLayerdBitmapBits(HGLOBAL* phGlobal);
|
|
|
|
BOOL _ShowDragImageInterThread(HWND hwndLock, BOOL * pfShow);
|
|
|
|
// MultiRectDragging
|
|
void _MultipleDragShow(BOOL bShow);
|
|
void _MultipleDragStart(HWND hwndLock, LPRECT aRect, int nRects, POINT ptStart, POINT ptOffset);
|
|
void _MultipleDragMove(POINT ptNew);
|
|
HRESULT _SetLayerdDragging(LPSHDRAGIMAGE pshdi);
|
|
HRESULT _SetMultiItemDragging(HWND hwndLV, int cItems, POINT *pptOffset);
|
|
HRESULT _SetMultiRectDragging(int cItems, LPRECT prect, POINT *pptOffset);
|
|
|
|
// Merged Cursors
|
|
HBITMAP CreateColorBitmap(int cx, int cy);
|
|
void _DestroyCachedCursors();
|
|
HRESULT _GetCursorLowerRight(HCURSOR hcursor, int * px, int * py, POINT *pptHotSpot);
|
|
int _MapCursorIDToImageListIndex(int idCur);
|
|
int _AddCursorToImageList(HCURSOR hcur, LPCTSTR idMerge, POINT *pptHotSpot);
|
|
BOOL _MergeIcons(HCURSOR hcursor, LPCTSTR idMerge, HBITMAP *phbmImage, HBITMAP *phbmMask, POINT* pptHotSpot);
|
|
HCURSOR _SetCursorHotspot(HCURSOR hcur, POINT *ptHot);
|
|
|
|
// Helper Routines
|
|
BOOL _CreateDragWindow();
|
|
BOOL _PreProcessDragBitmap(void** ppvBits);
|
|
BOOL _IsTooBigForAlpha();
|
|
|
|
// Member Variables
|
|
SHDRAGIMAGE _shdi;
|
|
HWND _hwndTarget;
|
|
HWND _hwnd; // The HWND of the Layered Window
|
|
HDC _hdcDragImage;
|
|
HBITMAP _hbmpOld;
|
|
|
|
BOOL _fLayeredSupported;
|
|
BOOL _fCursorDataInited;
|
|
|
|
POINT _ptDebounce;
|
|
|
|
// Legacy drag support
|
|
BOOL _fImage;
|
|
POINT _ptOffset;
|
|
DWORD _idThread;
|
|
HIMAGELIST _himlCursors;
|
|
UINT _cRev;
|
|
int _aindex[DCID_MAX]; // will be initialized.
|
|
HCURSOR _ahcur[DCID_MAX];
|
|
POINT _aptHotSpot[DCID_MAX];
|
|
int _idCursor;
|
|
|
|
// _Single struct is used between DAD_Enter and DAD_Leave
|
|
struct
|
|
{
|
|
// Common part
|
|
BOOL bDragging;
|
|
BOOL bLocked;
|
|
HWND hwndLock;
|
|
BOOL bSingle; // Single imagelist dragging.
|
|
DWORD idThreadEntered;
|
|
|
|
// Multi-rect dragging specific part
|
|
struct
|
|
{
|
|
BOOL bShown;
|
|
LPRECT pRect;
|
|
int nRects;
|
|
POINT ptOffset;
|
|
POINT ptNow;
|
|
} _Multi;
|
|
} _Single;
|
|
|
|
// following fields are used only when fImage==FALSE
|
|
RECT* _parc; // cItems
|
|
UINT _cItems; // This is a sentinal. Needs to be the last item.
|
|
};
|
|
|
|
CDragImages::~CDragImages()
|
|
{
|
|
FreeDragData();
|
|
}
|
|
//
|
|
// Read 'Notes' in CDropSource_GiveFeedback for detail about this
|
|
// g_fDraggingOverSource flag, which is TRUE only if we are dragging
|
|
// over the source window itself with left mouse button
|
|
// (background and large/small icon mode only).
|
|
//
|
|
UINT g_cRev = 0;
|
|
CDragImages* g_pdiDragImages = NULL;
|
|
BOOL g_fDraggingOverSource = FALSE;
|
|
|
|
STDAPI CDragImages_CreateInstance(IUnknown* pUnkOuter, REFIID riid, void **ppvOut)
|
|
{
|
|
ASSERT(pUnkOuter == NULL); //Who's trying to aggregate us?
|
|
if (!g_pdiDragImages)
|
|
g_pdiDragImages = new CDragImages();
|
|
|
|
if (g_pdiDragImages && ppvOut) // ppvOut test for internal create usage
|
|
return g_pdiDragImages->QueryInterface(riid, ppvOut);
|
|
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
STDMETHODIMP CDragImages::QueryInterface(REFIID riid, void **ppv)
|
|
{
|
|
static const QITAB qit[] = {
|
|
QITABENT(CDragImages, IDragSourceHelper),
|
|
QITABENT(CDragImages, IDropTargetHelper),
|
|
{ 0 },
|
|
};
|
|
return QISearch(this, qit, riid, ppv);
|
|
}
|
|
|
|
#define UM_KILLYOURSELF WM_USER
|
|
|
|
LRESULT CALLBACK DragWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
if (uMsg == UM_KILLYOURSELF)
|
|
{
|
|
DestroyWindow(hwnd);
|
|
|
|
return 0;
|
|
}
|
|
|
|
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
|
|
BOOL CDragImages::_CreateDragWindow()
|
|
{
|
|
if (_hwnd == NULL)
|
|
{
|
|
WNDCLASS wc = {0};
|
|
|
|
wc.hInstance = g_hinst;
|
|
wc.lpfnWndProc = DragWndProc;
|
|
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
wc.lpszClassName = TEXT("SysDragImage");
|
|
wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); // NULL;
|
|
SHRegisterClass(&wc);
|
|
|
|
_hwnd = CreateWindowEx(WS_EX_TRANSPARENT | WS_EX_TOPMOST | WS_EX_LAYERED | WS_EX_TOOLWINDOW,
|
|
TEXT("SysDragImage"), TEXT("Drag"), WS_POPUPWINDOW,
|
|
0, 0, 50, 50, NULL, NULL, g_hinst, NULL);
|
|
|
|
if (!_hwnd)
|
|
return FALSE;
|
|
|
|
//
|
|
// This window should not be mirrored so that the image contents won't be flipped. [samera]
|
|
//
|
|
SetWindowBits(_hwnd, GWL_EXSTYLE, RTL_MIRRORED_WINDOW, 0);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL CDragImages::Initialized()
|
|
{
|
|
return _fCursorDataInited;
|
|
}
|
|
|
|
void CDragImages::FreeDragData()
|
|
{
|
|
|
|
if (_hwnd)
|
|
{
|
|
SendMessage(_hwnd, UM_KILLYOURSELF, 0, 0);
|
|
_hwnd = NULL;
|
|
}
|
|
|
|
_fCursorDataInited = FALSE;
|
|
|
|
// Make sure we destroy the cursors on an invalidate.
|
|
if (_himlCursors)
|
|
_DestroyCachedCursors();
|
|
|
|
// Do we have an array?
|
|
if (_parc)
|
|
{
|
|
delete _parc;
|
|
_parc = NULL;
|
|
}
|
|
|
|
if (_fImage)
|
|
ImageList_EndDrag();
|
|
|
|
if (_hbmpOld)
|
|
{
|
|
SelectObject(_hdcDragImage, _hbmpOld);
|
|
_hbmpOld = NULL;
|
|
}
|
|
|
|
if (_hdcDragImage)
|
|
{
|
|
DeleteDC(_hdcDragImage);
|
|
_hdcDragImage = NULL;
|
|
}
|
|
|
|
if (_shdi.hbmpDragImage)
|
|
DeleteObject(_shdi.hbmpDragImage);
|
|
|
|
ZeroMemory(&_Single, sizeof(_Single));
|
|
ZeroMemory(&_shdi, sizeof(_shdi));
|
|
|
|
_ptOffset.x = 0;
|
|
_ptOffset.y = 0;
|
|
|
|
_ptDebounce.x = 0;
|
|
_ptDebounce.y = 0;
|
|
|
|
_hwndTarget = _hwnd = NULL;
|
|
_fCursorDataInited = _fLayeredSupported = FALSE;
|
|
_fImage = FALSE;
|
|
_idThread = 0;
|
|
_himlCursors = NULL;
|
|
_cRev = 0;
|
|
_idCursor = 0;
|
|
}
|
|
|
|
void CDragImages::_InitDragData()
|
|
{
|
|
_idThread = GetCurrentThreadId();
|
|
|
|
if (_himlCursors && _cRev != g_cRev)
|
|
_DestroyCachedCursors();
|
|
|
|
if (_himlCursors == NULL)
|
|
{
|
|
UINT uFlags = ILC_MASK | ILC_SHARED;
|
|
if (IS_BIDI_LOCALIZED_SYSTEM())
|
|
uFlags |= ILC_MIRROR;
|
|
|
|
//
|
|
// if this is not a palette device, use a DDB for the imagelist
|
|
// this is important when displaying high-color cursors
|
|
//
|
|
HDC hdc = GetDC(NULL);
|
|
if (!(GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE))
|
|
{
|
|
uFlags |= ILC_COLORDDB;
|
|
}
|
|
ReleaseDC(NULL, hdc);
|
|
|
|
_himlCursors = ImageList_Create(GetSystemMetrics(SM_CXCURSOR),
|
|
GetSystemMetrics(SM_CYCURSOR),
|
|
uFlags, 1, 0);
|
|
|
|
_cRev = g_cRev;
|
|
|
|
// We need to initialize s_cursors._aindex[*]
|
|
_MapCursorIDToImageListIndex(-1);
|
|
}
|
|
_fCursorDataInited = TRUE;
|
|
}
|
|
|
|
BOOL AreAllMonitorsAtLeast(int iBpp)
|
|
{
|
|
DISPLAY_DEVICE DisplayDevice;
|
|
BOOL fAreAllMonitorsAtLeast = TRUE;
|
|
|
|
for (int iEnum = 0; fAreAllMonitorsAtLeast && iEnum < MONITORS_MAX; iEnum++)
|
|
{
|
|
ZeroMemory(&DisplayDevice, sizeof(DISPLAY_DEVICE));
|
|
DisplayDevice.cb = sizeof(DISPLAY_DEVICE);
|
|
|
|
if (EnumDisplayDevices(NULL, iEnum, &DisplayDevice, 0) &&
|
|
(DisplayDevice.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP))
|
|
{
|
|
|
|
HDC hdc = CreateDC(NULL, (LPTSTR)DisplayDevice.DeviceName, NULL, NULL);
|
|
if (hdc)
|
|
{
|
|
int iBits = GetDeviceCaps(hdc, BITSPIXEL);
|
|
|
|
if (iBits < iBpp)
|
|
fAreAllMonitorsAtLeast = FALSE;
|
|
|
|
DeleteDC(hdc);
|
|
}
|
|
}
|
|
}
|
|
|
|
return fAreAllMonitorsAtLeast;
|
|
}
|
|
|
|
BOOL CDragImages::_IsLayeredSupported()
|
|
{
|
|
// For the first rev, we will only support Layered drag images
|
|
// when the Color depth is greater than 65k colors.
|
|
|
|
// We should ask everytime....
|
|
_fLayeredSupported = AreAllMonitorsAtLeast(16);
|
|
|
|
if (_fLayeredSupported)
|
|
{
|
|
BOOL bDrag;
|
|
if (SystemParametersInfo(SPI_GETDRAGFULLWINDOWS, 0, &bDrag, 0))
|
|
{
|
|
_fLayeredSupported = BOOLIFY(bDrag);
|
|
}
|
|
|
|
if (_fLayeredSupported)
|
|
_fLayeredSupported = SHRegGetBoolUSValue(REGSTR_EXPLORER_ADVANCED, TEXT("NewDragImages"), FALSE, TRUE);
|
|
}
|
|
return _fLayeredSupported;
|
|
}
|
|
|
|
//
|
|
// initialize the static drag image manager from a structure
|
|
// this is implemented for WindowLess controls that can act as a
|
|
// drag source.
|
|
//
|
|
HRESULT CDragImages::_SetLayerdDragging(LPSHDRAGIMAGE pshdi)
|
|
{
|
|
// We don't support being initialized from a bitmap when Layered Windows are not supported
|
|
HRESULT hr;
|
|
if (_IsLayeredSupported())
|
|
{
|
|
RIP(IsValidHANDLE(pshdi->hbmpDragImage));
|
|
|
|
_shdi = *pshdi; // Keep a copy of this.
|
|
|
|
_idCursor = -1; // Initialize this... This is an arbitraty place and can be put
|
|
// anywhere before the first Setcursor call
|
|
_InitDragData();
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
hr = E_FAIL;
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CDragImages::InitializeFromBitmap(LPSHDRAGIMAGE pshdi, IDataObject* pdtobj)
|
|
{
|
|
FreeDragData();
|
|
|
|
HRESULT hr = _SetLayerdDragging(pshdi);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _SaveToDataObject(pdtobj);
|
|
if (FAILED(hr))
|
|
FreeDragData();
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
BOOL ListView_HasMask(HWND hwnd)
|
|
{
|
|
HIMAGELIST himl = ListView_GetImageList(hwnd, LVSIL_NORMAL);
|
|
return himl && (ImageList_GetFlags(himl) & ILC_MASK);
|
|
}
|
|
|
|
//
|
|
// initialize the static drag image manager from an HWND that
|
|
// can process the RegisteredWindowMessage(DI_GETDRAGIMAGE)
|
|
//
|
|
STDMETHODIMP CDragImages::InitializeFromWindow(HWND hwnd, POINT* ppt, IDataObject* pdtobj)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
|
|
FreeDragData();
|
|
|
|
if (_IsLayeredSupported())
|
|
{
|
|
// Register the message that gets us the Bitmap from the control.
|
|
static int g_msgGetDragImage = 0;
|
|
if (g_msgGetDragImage == 0)
|
|
g_msgGetDragImage = RegisterWindowMessage(DI_GETDRAGIMAGE);
|
|
|
|
// Can this HWND generate a drag image for me?
|
|
if (g_msgGetDragImage && SendMessage(hwnd, g_msgGetDragImage, 0, (LPARAM)&_shdi))
|
|
{
|
|
// Yes; Now we select that into the window
|
|
hr = _SetLayerdDragging(&_shdi);
|
|
}
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
TCHAR szClassName[50];
|
|
|
|
if (GetClassName(hwnd, szClassName, ARRAYSIZE(szClassName)))
|
|
{
|
|
if (lstrcmpi(szClassName, WC_LISTVIEW) == 0)
|
|
{
|
|
POINT ptOffset = {0,0};
|
|
|
|
if (ppt)
|
|
ptOffset = *ppt;
|
|
|
|
int cItems = ListView_GetSelectedCount(hwnd);
|
|
if (cItems >= 1)
|
|
{
|
|
if ((cItems == 1) && ListView_HasMask(hwnd))
|
|
{
|
|
POINT ptTemp;
|
|
HIMAGELIST himl = ListView_CreateDragImage(hwnd, ListView_GetNextItem(hwnd, -1, LVNI_SELECTED), &ptTemp);
|
|
if (himl)
|
|
{
|
|
ClientToScreen(hwnd, &ptTemp);
|
|
ptOffset.x -= ptTemp.x;
|
|
|
|
// Since the listview is mirrored, then mirror the selected
|
|
// icon coord. This would result in negative offset so let's
|
|
// compensate. [samera]
|
|
if (IS_WINDOW_RTL_MIRRORED(hwnd))
|
|
ptOffset.x *= -1;
|
|
|
|
ptOffset.y -= ptTemp.y;
|
|
SetDragImage(himl, 0, &ptOffset);
|
|
ImageList_Destroy(himl);
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = _SetMultiItemDragging(hwnd, cItems, &ptOffset);
|
|
}
|
|
}
|
|
}
|
|
else if (lstrcmpi(szClassName, WC_TREEVIEW) == 0)
|
|
{
|
|
HIMAGELIST himlDrag = TreeView_CreateDragImage(hwnd, NULL);
|
|
if (himlDrag)
|
|
{
|
|
SetDragImage(himlDrag, 0, NULL);
|
|
ImageList_Destroy(himlDrag);
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// ignore failure here as this will still work in process due to the globals
|
|
// fonts folder depends on this
|
|
_SaveToDataObject(pdtobj);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// create the drag window in the layered window case, or to begin drawing the
|
|
// Multi Rect or icon drag images.
|
|
//
|
|
STDMETHODIMP CDragImages::DragEnter(HWND hwndTarget, IDataObject* pdtobj, POINT* ppt, DWORD dwEffect)
|
|
{
|
|
HRESULT hr = _LoadFromDataObject(pdtobj);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
_hwndTarget = hwndTarget ? hwndTarget : GetDesktopWindow();
|
|
SetDragCursor(-1);
|
|
_Single.bDragging = TRUE;
|
|
_Single.bSingle = _fImage;
|
|
_Single.hwndLock = _hwndTarget;
|
|
_Single.bLocked = FALSE;
|
|
_Single.idThreadEntered = GetCurrentThreadId();
|
|
|
|
_ptDebounce.x = 0;
|
|
_ptDebounce.y = 0;
|
|
|
|
if (_shdi.hbmpDragImage)
|
|
{
|
|
TraceMsg(TF_DRAGIMAGES, "CDragImages::DragEnter : Creating Drag Window");
|
|
// At this point the information has been read from the data object.
|
|
// Reconstruct the HWND if necessary
|
|
if (_CreateDragWindow() && _hdcDragImage)
|
|
{
|
|
POINT ptSrc = {0, 0};
|
|
POINT pt;
|
|
|
|
SetWindowPos(_hwnd, NULL, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE |
|
|
SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW);
|
|
|
|
GetMsgPos(&pt);
|
|
|
|
pt.x -= _shdi.ptOffset.x;
|
|
pt.y -= _shdi.ptOffset.y;
|
|
|
|
BLENDFUNCTION blend;
|
|
blend.BlendOp = AC_SRC_OVER;
|
|
blend.BlendFlags = 0;
|
|
blend.AlphaFormat = AC_SRC_ALPHA;
|
|
blend.SourceConstantAlpha = 0xFF /*DRAGDROP_ALPHA*/;
|
|
|
|
HDC hdc = GetDC(_hwnd);
|
|
if (hdc)
|
|
{
|
|
DWORD fULWType = ULW_ALPHA;
|
|
|
|
// Should have been preprocess already
|
|
UpdateLayeredWindow(_hwnd, hdc, &pt, &(_shdi.sizeDragImage),
|
|
_hdcDragImage, &ptSrc, _shdi.crColorKey,
|
|
&blend, fULWType);
|
|
|
|
ReleaseDC(_hwnd, hdc);
|
|
}
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// These are in Client Cordinates, not screen coords. Translate:
|
|
POINT pt = *ppt;
|
|
RECT rc;
|
|
GetWindowRect(_hwndTarget, &rc);
|
|
pt.x -= rc.left;
|
|
pt.y -= rc.top;
|
|
if (_fImage)
|
|
{
|
|
// Avoid the flicker by always pass even coords
|
|
ImageList_DragEnter(hwndTarget, pt.x & ~1, pt.y & ~1);
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
{
|
|
_MultipleDragStart(hwndTarget, _parc, _cItems, pt, _ptOffset);
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We should always show the image whenever this function is called.
|
|
//
|
|
Show(TRUE);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// kill the Layered Window, or to stop painting the icon or rect drag images
|
|
//
|
|
STDMETHODIMP CDragImages::DragLeave()
|
|
{
|
|
TraceMsg(TF_DRAGIMAGES, "CDragImages::DragLeave");
|
|
if (Initialized())
|
|
{
|
|
if (_hwnd)
|
|
{
|
|
FreeDragData();
|
|
}
|
|
else if (_Single.bDragging &&
|
|
_Single.idThreadEntered == GetCurrentThreadId())
|
|
{
|
|
Show(FALSE);
|
|
|
|
if (_fImage)
|
|
{
|
|
ImageList_DragLeave(_Single.hwndLock);
|
|
}
|
|
|
|
_Single.bDragging = FALSE;
|
|
|
|
DAD_SetDragImage((HIMAGELIST)-1, NULL);
|
|
}
|
|
|
|
_ptDebounce.x = 0;
|
|
_ptDebounce.y = 0;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
// move the Layered window or to rerender the icon or rect images within
|
|
// the Window they are over.
|
|
//
|
|
STDMETHODIMP CDragImages::DragOver(POINT* ppt, DWORD dwEffect)
|
|
{
|
|
if (Initialized())
|
|
{
|
|
TraceMsg(TF_DRAGIMAGES, "CDragImages::DragOver pt {%d, %d}", ppt->x, ppt->y);
|
|
// Avoid the flicker by always pass even coords
|
|
ppt->x &= ~1;
|
|
ppt->y &= ~1;
|
|
|
|
if (_ptDebounce.x != ppt->x || _ptDebounce.y != ppt->y)
|
|
{
|
|
_ptDebounce.x = ppt->x;
|
|
_ptDebounce.y = ppt->y;
|
|
if (IsDraggingLayeredWindow())
|
|
{
|
|
POINT pt;
|
|
GetCursorPos(&pt);
|
|
pt.x -= _shdi.ptOffset.x;
|
|
pt.y -= _shdi.ptOffset.y;
|
|
|
|
SetWindowPos(_hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE |
|
|
SWP_NOSIZE | SWP_SHOWWINDOW);
|
|
|
|
UpdateLayeredWindow(_hwnd, NULL, &pt, NULL, NULL, NULL, 0,
|
|
NULL, 0);
|
|
}
|
|
else
|
|
{
|
|
// These are in Client Cordinates, not screen coords. Translate:
|
|
POINT pt = *ppt;
|
|
RECT rc;
|
|
GetWindowRect(_hwndTarget, &rc);
|
|
pt.x -= rc.left;
|
|
pt.y -= rc.top;
|
|
if (_fImage)
|
|
{
|
|
ImageList_DragMove(pt.x, pt.y);
|
|
}
|
|
else
|
|
{
|
|
_MultipleDragMove(pt);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
// do any cleanup after a drop (Currently calls DragLeave)
|
|
//
|
|
STDMETHODIMP CDragImages::Drop(IDataObject* pdtobj, POINT* ppt, DWORD dwEffect)
|
|
{
|
|
return DragLeave();
|
|
}
|
|
|
|
// initialize the static drag image manager from a structure
|
|
// this is implemented for WindowLess controls that can act as a
|
|
// drag source.
|
|
//
|
|
void CDragImages::SetDragCursor(int idCursor)
|
|
{
|
|
//
|
|
// Ignore if we are dragging over ourselves.
|
|
//
|
|
if (IsDraggingImage())
|
|
{
|
|
POINT ptHotSpot;
|
|
|
|
if (_himlCursors && (idCursor != -1))
|
|
{
|
|
int iIndex = _MapCursorIDToImageListIndex(idCursor);
|
|
if (iIndex != -1)
|
|
{
|
|
ImageList_GetDragImage(NULL, &ptHotSpot);
|
|
ptHotSpot.x -= _aptHotSpot[idCursor].x;
|
|
ptHotSpot.y -= _aptHotSpot[idCursor].y;
|
|
if (ptHotSpot.x < 0)
|
|
{
|
|
ptHotSpot.x = 0;
|
|
}
|
|
|
|
if (ptHotSpot.y < 0)
|
|
{
|
|
ptHotSpot.y = 0;
|
|
}
|
|
|
|
ImageList_SetDragCursorImage(_himlCursors, iIndex, ptHotSpot.x, ptHotSpot.y);
|
|
}
|
|
else
|
|
{
|
|
// You passed a bad Cursor ID.
|
|
ASSERT(0);
|
|
}
|
|
}
|
|
|
|
_idCursor = idCursor;
|
|
}
|
|
}
|
|
|
|
// init our state from the hGlobal so we can draw
|
|
HRESULT CDragImages::_LoadLayerdBitmapBits(HGLOBAL hGlobal)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
|
|
if (!Initialized())
|
|
{
|
|
ASSERT(_shdi.hbmpDragImage == NULL);
|
|
ASSERT(_hdcDragImage == NULL);
|
|
|
|
HDC hdcScreen = GetDC(NULL);
|
|
if (hdcScreen)
|
|
{
|
|
void *pvDragStuff = (void*)GlobalLock(hGlobal);
|
|
if (pvDragStuff)
|
|
{
|
|
CopyMemory(&_shdi, pvDragStuff, sizeof(SHDRAGIMAGE));
|
|
|
|
BITMAPINFO bmi = {0};
|
|
|
|
// Create a buffer to read the bits into
|
|
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
bmi.bmiHeader.biWidth = _shdi.sizeDragImage.cx;
|
|
bmi.bmiHeader.biHeight = _shdi.sizeDragImage.cy;
|
|
bmi.bmiHeader.biPlanes = 1;
|
|
bmi.bmiHeader.biBitCount = 32;
|
|
bmi.bmiHeader.biCompression = BI_RGB;
|
|
|
|
// Next create a DC and an HBITMAP.
|
|
_hdcDragImage = CreateCompatibleDC(hdcScreen);
|
|
if (_hdcDragImage)
|
|
{
|
|
void *pvBits;
|
|
_shdi.hbmpDragImage = CreateDIBSection(_hdcDragImage, &bmi, DIB_RGB_COLORS, &pvBits, NULL, NULL);
|
|
if (_shdi.hbmpDragImage)
|
|
{
|
|
_hbmpOld = (HBITMAP)SelectObject(_hdcDragImage, _shdi.hbmpDragImage);
|
|
|
|
// then Set the bits into the Bitmap
|
|
RGBQUAD* pvStart = (RGBQUAD*)((BYTE*)pvDragStuff + sizeof(SHDRAGIMAGE));
|
|
DWORD dwCount = _shdi.sizeDragImage.cx * _shdi.sizeDragImage.cy * sizeof(RGBQUAD);
|
|
CopyMemory((RGBQUAD*)pvBits, (RGBQUAD*)pvStart, dwCount);
|
|
|
|
hr = S_OK; // success!
|
|
}
|
|
}
|
|
GlobalUnlock(hGlobal);
|
|
}
|
|
ReleaseDC(NULL, hdcScreen);
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
// Writes the written information into phGlobal to recreate the drag image
|
|
HRESULT CDragImages::_SaveLayerdBitmapBits(HGLOBAL* phGlobal)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
if (Initialized())
|
|
{
|
|
ASSERT(_shdi.hbmpDragImage);
|
|
|
|
DWORD cbImageSize = _shdi.sizeDragImage.cx * _shdi.sizeDragImage.cy * sizeof(RGBQUAD);
|
|
*phGlobal = GlobalAlloc(GPTR, cbImageSize + sizeof(SHDRAGIMAGE));
|
|
if (*phGlobal)
|
|
{
|
|
void *pvDragStuff = GlobalLock(*phGlobal);
|
|
CopyMemory(pvDragStuff, &_shdi, sizeof(SHDRAGIMAGE));
|
|
|
|
void *pvBits;
|
|
hr = _PreProcessDragBitmap(&pvBits) ? S_OK : E_FAIL;
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
RGBQUAD* pvStart = (RGBQUAD*)((BYTE*)pvDragStuff + sizeof(SHDRAGIMAGE));
|
|
DWORD dwCount = _shdi.sizeDragImage.cx * _shdi.sizeDragImage.cy * sizeof(RGBQUAD);
|
|
CopyMemory((RGBQUAD*)pvStart, (RGBQUAD*)pvBits, dwCount);
|
|
}
|
|
GlobalUnlock(*phGlobal);
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
BOOL CDragImages::_IsTooBigForAlpha()
|
|
{
|
|
BOOL fTooBig = FALSE;
|
|
int dSelectionArea = _shdi.sizeDragImage.cx * _shdi.sizeDragImage.cy;
|
|
|
|
// The number here is "It just feels right" or
|
|
// about 3 Thumbnail icons linned up next to each other.
|
|
if ( dSelectionArea > 0x10000 )
|
|
fTooBig = TRUE;
|
|
|
|
return fTooBig;
|
|
}
|
|
|
|
|
|
BOOL IsColorKey(RGBQUAD rgbPixel, COLORREF crKey)
|
|
{
|
|
// COLORREF is backwards to RGBQUAD
|
|
return InRange( rgbPixel.rgbBlue, ((crKey & 0xFF0000) >> 16) - 5, ((crKey & 0xFF0000) >> 16) + 5) &&
|
|
InRange( rgbPixel.rgbGreen, ((crKey & 0x00FF00) >> 8) - 5, ((crKey & 0x00FF00) >> 8) + 5) &&
|
|
InRange( rgbPixel.rgbRed, ((crKey & 0x0000FF) >> 0) - 5, ((crKey & 0x0000FF) >> 0) + 5);
|
|
}
|
|
|
|
#ifdef RADIAL
|
|
|
|
int QuickRoot(int n, int iNum)
|
|
{
|
|
|
|
int iRoot = iNum;
|
|
for (int i=10; i > 0; i--)
|
|
{
|
|
int iOld = iRoot;
|
|
iRoot = (iRoot + iNum/iRoot)/2;
|
|
if (iRoot == iOld)
|
|
break;
|
|
}
|
|
|
|
return iRoot;
|
|
}
|
|
|
|
#endif
|
|
|
|
BOOL CDragImages::_PreProcessDragBitmap(void** ppvBits)
|
|
{
|
|
BOOL fRet = FALSE;
|
|
|
|
ASSERT(_hdcDragImage == NULL);
|
|
_hdcDragImage = CreateCompatibleDC(NULL);
|
|
if (_hdcDragImage)
|
|
{
|
|
ULONG* pul;
|
|
HBITMAP hbmpResult = NULL;
|
|
HBITMAP hbmpOld;
|
|
HDC hdcSource = NULL;
|
|
BITMAPINFO bmi = {0};
|
|
HBITMAP hbmp = _shdi.hbmpDragImage;
|
|
|
|
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
bmi.bmiHeader.biWidth = _shdi.sizeDragImage.cx;
|
|
bmi.bmiHeader.biHeight = _shdi.sizeDragImage.cy;
|
|
bmi.bmiHeader.biPlanes = 1;
|
|
bmi.bmiHeader.biBitCount = 32;
|
|
bmi.bmiHeader.biCompression = BI_RGB;
|
|
|
|
hdcSource = CreateCompatibleDC(_hdcDragImage);
|
|
if (hdcSource)
|
|
{
|
|
hbmpResult = CreateDIBSection(_hdcDragImage,
|
|
&bmi,
|
|
DIB_RGB_COLORS,
|
|
ppvBits,
|
|
NULL,
|
|
0);
|
|
|
|
if (hbmpResult)
|
|
{
|
|
_hbmpOld = (HBITMAP)SelectObject(_hdcDragImage, hbmpResult);
|
|
hbmpOld = (HBITMAP)SelectObject(hdcSource, hbmp);
|
|
|
|
BitBlt(_hdcDragImage, 0, 0, _shdi.sizeDragImage.cx, _shdi.sizeDragImage.cy,
|
|
hdcSource, 0, 0, SRCCOPY);
|
|
|
|
pul = (ULONG*)*ppvBits;
|
|
|
|
int iOffsetX = _shdi.ptOffset.x;
|
|
int iOffsetY = _shdi.ptOffset.y;
|
|
int iDenomX = max(_shdi.sizeDragImage.cx - iOffsetX, iOffsetX);
|
|
int iDenomY = max(_shdi.sizeDragImage.cy - iOffsetY, iOffsetY);
|
|
BOOL fRadialFade = TRUE;
|
|
// If both are less than the max, then no radial fade.
|
|
if (_shdi.sizeDragImage.cy <= MAX_HEIGHT_ALPHA && _shdi.sizeDragImage.cx <= MAX_WIDTH_ALPHA)
|
|
fRadialFade = FALSE;
|
|
|
|
for (int Y = 0; Y < _shdi.sizeDragImage.cy; Y++)
|
|
{
|
|
int y = _shdi.sizeDragImage.cy - Y; // Bottom up DIB.
|
|
for (int x = 0; x < _shdi.sizeDragImage.cx; x++)
|
|
{
|
|
RGBQUAD* prgb = (RGBQUAD*)&pul[Y * _shdi.sizeDragImage.cx + x];
|
|
|
|
if (_shdi.crColorKey != CLR_NONE &&
|
|
IsColorKey(*prgb, _shdi.crColorKey))
|
|
{
|
|
// Write a pre-multiplied value of 0:
|
|
|
|
*((DWORD*)prgb) = 0;
|
|
}
|
|
else
|
|
{
|
|
int Alpha = prgb->rgbReserved;
|
|
if (_shdi.crColorKey != CLR_NONE)
|
|
{
|
|
Alpha = DRAGDROP_ALPHA;
|
|
}
|
|
else
|
|
{
|
|
Alpha -= (Alpha / 3);
|
|
}
|
|
|
|
if (fRadialFade && Alpha > 0)
|
|
{
|
|
// This does not generate a smooth curve, but this is just
|
|
// an effect, not trying to be accurate here.
|
|
|
|
// 3 devides per pixel
|
|
int ddx = (x < iOffsetX)? iOffsetX - x : x - iOffsetX;
|
|
int ddy = (y < iOffsetY)? iOffsetY - y : y - iOffsetY;
|
|
|
|
__int64 iAlphaX = (100000l - (((__int64)ddx * 100000l) / (iDenomX )));
|
|
__int64 iAlphaY = (100000l - (((__int64)ddy * 100000l) / (iDenomY )));
|
|
|
|
ASSERT (iAlphaX >= 0);
|
|
ASSERT (iAlphaY >= 0);
|
|
|
|
__int64 iDenom = 100000;
|
|
iDenom *= 100000;
|
|
|
|
Alpha = (int) ((Alpha * iAlphaX * iAlphaY * 100000) / (iDenom* 141428));
|
|
}
|
|
|
|
ASSERT(Alpha <= 0xFF);
|
|
prgb->rgbReserved = (BYTE)Alpha;
|
|
prgb->rgbRed = ((prgb->rgbRed * Alpha) + 128) / 255;
|
|
prgb->rgbGreen = ((prgb->rgbGreen * Alpha) + 128) / 255;
|
|
prgb->rgbBlue = ((prgb->rgbBlue * Alpha) + 128) / 255;
|
|
}
|
|
}
|
|
}
|
|
|
|
DeleteObject(hbmp);
|
|
_shdi.hbmpDragImage = hbmpResult;
|
|
|
|
fRet = TRUE;
|
|
|
|
if (hbmpOld)
|
|
SelectObject(hdcSource, hbmpOld);
|
|
}
|
|
|
|
DeleteObject(hdcSource);
|
|
}
|
|
}
|
|
|
|
return fRet;
|
|
}
|
|
|
|
CLIPFORMAT _GetDragContentsCF()
|
|
{
|
|
static UINT s_cfDragContents = 0;
|
|
if (0 == s_cfDragContents)
|
|
s_cfDragContents = RegisterClipboardFormat(CFSTR_DRAGCONTEXT);
|
|
return (CLIPFORMAT) s_cfDragContents;
|
|
}
|
|
|
|
CLIPFORMAT _GetDragImageBitssCF()
|
|
{
|
|
static UINT s_cfDragImageBitss = 0;
|
|
if (0 == s_cfDragImageBitss)
|
|
s_cfDragImageBitss = RegisterClipboardFormat(TEXT("DragImageBits"));
|
|
return (CLIPFORMAT) s_cfDragImageBitss;
|
|
}
|
|
|
|
|
|
// persist our state into the data object. so on the target side they can grab this
|
|
// data out and render the thing being dragged
|
|
|
|
HRESULT CDragImages::_SaveToDataObject(IDataObject *pdtobj)
|
|
{
|
|
HRESULT hr = E_FAIL; // one form of the saves below must succeed
|
|
if (Initialized())
|
|
{
|
|
STGMEDIUM medium = {0};
|
|
medium.tymed = TYMED_ISTREAM;
|
|
|
|
if (SUCCEEDED(CreateStreamOnHGlobal(NULL, TRUE, &medium.pstm)))
|
|
{
|
|
// Set the header .
|
|
DragContextHeader hdr = {0};
|
|
hdr.fImage = _fImage;
|
|
hdr.fLayered = IsDraggingLayeredWindow();
|
|
hdr.ptOffset = _ptOffset;
|
|
|
|
//First Write the drag context header
|
|
ULONG ulWritten;
|
|
if (SUCCEEDED(medium.pstm->Write(&hdr, sizeof(hdr), &ulWritten)) &&
|
|
(ulWritten == sizeof(hdr)))
|
|
{
|
|
if (hdr.fLayered)
|
|
{
|
|
STGMEDIUM mediumBits = {0};
|
|
// Set the medium.
|
|
mediumBits.tymed = TYMED_HGLOBAL;
|
|
|
|
// Write out layered window information
|
|
hr = _SaveLayerdBitmapBits(&mediumBits.hGlobal);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
FORMATETC fmte = {_GetDragImageBitssCF(), NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
|
|
|
// Set the medium in the data.
|
|
hr = pdtobj->SetData(&fmte, &mediumBits, TRUE);
|
|
if (FAILED(hr))
|
|
ReleaseStgMedium(&mediumBits); // cleanup
|
|
}
|
|
}
|
|
else if (hdr.fImage)
|
|
{
|
|
// write an image
|
|
|
|
HIMAGELIST himl = ImageList_GetDragImage(NULL, NULL);
|
|
if (ImageList_Write(himl, medium.pstm))
|
|
{
|
|
hr = S_OK; // success
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// multi rect
|
|
|
|
if (SUCCEEDED(medium.pstm->Write(&_cItems, sizeof(_cItems), &ulWritten)) &&
|
|
(ulWritten == sizeof(_cItems)))
|
|
{
|
|
// Write the rects into the stream
|
|
if (SUCCEEDED(medium.pstm->Write(_parc, sizeof(_parc[0]) * _cItems, &ulWritten)) &&
|
|
(ulWritten == (sizeof(_parc[0]) * _cItems)))
|
|
{
|
|
hr = S_OK; // success
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Set the seek pointer at the beginning.
|
|
medium.pstm->Seek(g_li0, STREAM_SEEK_SET, NULL);
|
|
|
|
// Set the Formatetc
|
|
FORMATETC fmte = {_GetDragContentsCF(), NULL, DVASPECT_CONTENT, -1, TYMED_ISTREAM};
|
|
|
|
// Set the medium in the data.
|
|
hr = pdtobj->SetData(&fmte, &medium, TRUE);
|
|
}
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
ReleaseStgMedium(&medium);
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
// Gets the information to rebuild the drag images from the data object
|
|
HRESULT CDragImages::_LoadFromDataObject(IDataObject *pdtobj)
|
|
{
|
|
// Check if we have a drag context
|
|
HRESULT hr;
|
|
|
|
// NULL pdtobj is for the old DAD_DragEnterXXX() APIs...
|
|
// we hope this in the same process
|
|
if (Initialized() || !pdtobj)
|
|
{
|
|
hr = S_OK; // already loaded
|
|
}
|
|
else
|
|
{
|
|
// Set the format we are interested in
|
|
FORMATETC fmte = {_GetDragContentsCF(), NULL, DVASPECT_CONTENT, -1, TYMED_ISTREAM};
|
|
|
|
//if the data object has the format we are interested in
|
|
// then Get the data
|
|
STGMEDIUM medium = {0};
|
|
hr = pdtobj->GetData(&fmte, &medium);
|
|
if (SUCCEEDED(hr)) // if no pstm, bag out.
|
|
{
|
|
// Set the seek pointer at the beginning. PARANOIA: This is for people
|
|
// Who don't set the seek for me.
|
|
medium.pstm->Seek(g_li0, STREAM_SEEK_SET, NULL);
|
|
|
|
//First Read the drag context header
|
|
DragContextHeader hdr;
|
|
if (SUCCEEDED(IStream_Read(medium.pstm, &hdr, sizeof(hdr))))
|
|
{
|
|
if (hdr.fLayered)
|
|
{
|
|
STGMEDIUM mediumBits;
|
|
FORMATETC fmte = {_GetDragImageBitssCF(), NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
|
|
|
hr = pdtobj->GetData(&fmte, &mediumBits);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _LoadLayerdBitmapBits(mediumBits.hGlobal);
|
|
ReleaseStgMedium(&mediumBits);
|
|
}
|
|
}
|
|
else if (hdr.fImage)
|
|
{
|
|
// single image
|
|
HIMAGELIST himl = ImageList_Read(medium.pstm);
|
|
if (himl)
|
|
{
|
|
DAD_SetDragImage(himl, &(hdr.ptOffset));
|
|
ImageList_Destroy(himl);
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// multi rect
|
|
int cItems;
|
|
if (SUCCEEDED(IStream_Read(medium.pstm, &cItems, sizeof(cItems))))
|
|
{
|
|
RECT *prect = (RECT *)LocalAlloc(LPTR, sizeof(*prect) * cItems);
|
|
if (prect)
|
|
{
|
|
if (SUCCEEDED(IStream_Read(medium.pstm, prect, sizeof(*prect) * cItems)))
|
|
{
|
|
hr = _SetMultiRectDragging(cItems, prect, &hdr.ptOffset);
|
|
}
|
|
LocalFree(prect);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
_InitDragData();
|
|
|
|
// Set the seek pointer at the beginning. Just cleaning up...
|
|
medium.pstm->Seek(g_li0, STREAM_SEEK_SET, NULL);
|
|
|
|
// Release the stg medium.
|
|
ReleaseStgMedium(&medium);
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
// Shows or hides the drag images. NOTE: Doesn't do anything in the layered window case.
|
|
// We don't need to because this function is specifically for drawing to a locked window.
|
|
STDMETHODIMP CDragImages::Show(BOOL bShow)
|
|
{
|
|
BOOL fOld = bShow;
|
|
TraceMsg(TF_DRAGIMAGES, "CDragImages::Show(%s)", bShow? TEXT("true") : TEXT("false"));
|
|
|
|
if (!Initialized() || !_Single.bDragging)
|
|
{
|
|
return S_FALSE;
|
|
}
|
|
|
|
// No point in showing and hiding a Window. This causes unnecessary flicker.
|
|
if (_hwnd)
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
// If we're going across thread boundaries we have to try a context switch
|
|
if (GetCurrentThreadId() != GetWindowThreadProcessId(_Single.hwndLock, NULL) &&
|
|
_ShowDragImageInterThread(_Single.hwndLock, &fOld))
|
|
return fOld;
|
|
|
|
fOld = _Single.bLocked;
|
|
|
|
//
|
|
// If we are going to show the drag image, lock the target window.
|
|
//
|
|
if (bShow && !_Single.bLocked)
|
|
{
|
|
TraceMsg(TF_DRAGIMAGES, "CDragImages::Show : Shown and not locked");
|
|
UpdateWindow(_Single.hwndLock);
|
|
LockWindowUpdate(_Single.hwndLock);
|
|
_Single.bLocked = TRUE;
|
|
}
|
|
|
|
if (_Single.bSingle)
|
|
{
|
|
TraceMsg(TF_DRAGIMAGES, "CDragImages::Show : Calling ImageList_DragShowNoLock");
|
|
ImageList_DragShowNolock(bShow);
|
|
}
|
|
else
|
|
{
|
|
TraceMsg(TF_DRAGIMAGES, "CDragImages::Show : MultiDragShow");
|
|
_MultipleDragShow(bShow);
|
|
}
|
|
|
|
//
|
|
// If we have just hide the drag image, unlock the target window.
|
|
//
|
|
if (!bShow && _Single.bLocked)
|
|
{
|
|
TraceMsg(TF_DRAGIMAGES, "CDragImages::Show : hiding image, unlocking");
|
|
LockWindowUpdate(NULL);
|
|
_Single.bLocked = FALSE;
|
|
}
|
|
|
|
return fOld ? S_OK : S_FALSE;
|
|
}
|
|
|
|
// tell the drag source to hide or unhide the drag image to allow
|
|
// the destination to do drawing (unlock the screen)
|
|
//
|
|
// in:
|
|
// bShow FALSE - hide the drag image, allow drawing
|
|
// TRUE - show the drag image, no drawing allowed after this
|
|
|
|
// Helper function for DAD_ShowDragImage - handles the inter-thread case.
|
|
// We need to handle this case differently because LockWindowUpdate calls fail
|
|
// if they are on the wrong thread.
|
|
|
|
BOOL CDragImages::_ShowDragImageInterThread(HWND hwndLock, BOOL * pfShow)
|
|
{
|
|
TCHAR szClassName[50];
|
|
|
|
if (GetClassName(hwndLock, szClassName, ARRAYSIZE(szClassName)))
|
|
{
|
|
UINT uMsg = 0;
|
|
ULONG_PTR dw = 0;
|
|
|
|
if (lstrcmpi(szClassName, TEXT("SHELLDLL_DefView")) == 0)
|
|
uMsg = WM_DSV_SHOWDRAGIMAGE;
|
|
if (lstrcmpi(szClassName, TEXT("CabinetWClass")) == 0)
|
|
uMsg = CWM_SHOWDRAGIMAGE;
|
|
|
|
if (uMsg)
|
|
{
|
|
SendMessageTimeout(hwndLock, uMsg, 0, *pfShow, SMTO_ABORTIFHUNG, 1000, &dw);
|
|
*pfShow = (dw != 0);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void CDragImages::ThreadDetach()
|
|
{
|
|
if (_idThread == GetCurrentThreadId())
|
|
FreeDragData();
|
|
}
|
|
|
|
void CDragImages::ProcessDetach()
|
|
{
|
|
FreeDragData();
|
|
}
|
|
|
|
BOOL CDragImages::SetDragImage(HIMAGELIST himl, int index, POINT * pptOffset)
|
|
{
|
|
if (himl)
|
|
{
|
|
// We are setting
|
|
if (Initialized())
|
|
return FALSE;
|
|
|
|
_fImage = TRUE;
|
|
if (pptOffset)
|
|
{
|
|
// Avoid the flicker by always pass even coords
|
|
_ptOffset.x = (pptOffset->x & ~1);
|
|
_ptOffset.y = (pptOffset->y & ~1);
|
|
}
|
|
|
|
ImageList_BeginDrag(himl, index, _ptOffset.x, _ptOffset.y);
|
|
_InitDragData();
|
|
}
|
|
else
|
|
{
|
|
FreeDragData();
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
//=====================================================================
|
|
// Multile Drag show
|
|
//=====================================================================
|
|
|
|
void CDragImages::_MultipleDragShow(BOOL bShow)
|
|
{
|
|
HDC hDC;
|
|
int nRect;
|
|
RECT rc, rcClip;
|
|
|
|
if ((bShow && _Single._Multi.bShown) || (!bShow && !_Single._Multi.bShown))
|
|
return;
|
|
|
|
_Single._Multi.bShown = bShow;
|
|
|
|
// clip to window, NOT SM_CXSCREEN/SM_CYSCREEN (multiple monitors)
|
|
GetWindowRect(_Single.hwndLock, &rcClip);
|
|
rcClip.right -= rcClip.left;
|
|
rcClip.bottom -= rcClip.top;
|
|
|
|
hDC = GetDCEx(_Single.hwndLock, NULL, DCX_WINDOW | DCX_CACHE |
|
|
DCX_LOCKWINDOWUPDATE | DCX_CLIPSIBLINGS);
|
|
|
|
|
|
for (nRect = _Single._Multi.nRects - 1; nRect >= 0; --nRect)
|
|
{
|
|
rc = _Single._Multi.pRect[nRect];
|
|
OffsetRect(&rc, _Single._Multi.ptNow.x - _Single._Multi.ptOffset.x,
|
|
_Single._Multi.ptNow.y - _Single._Multi.ptOffset.y);
|
|
|
|
if ((rc.top < rcClip.bottom) && (rc.bottom > 0) &&
|
|
(rc.left < rcClip.right) && (rc.right > 0))
|
|
{
|
|
DrawFocusRect(hDC, &rc);
|
|
}
|
|
}
|
|
ReleaseDC(_Single.hwndLock, hDC);
|
|
}
|
|
|
|
void CDragImages::_MultipleDragStart(HWND hwndLock, LPRECT aRect, int nRects, POINT ptStart, POINT ptOffset)
|
|
{
|
|
_Single._Multi.bShown = FALSE;
|
|
_Single._Multi.pRect = aRect;
|
|
_Single._Multi.nRects = nRects;
|
|
_Single._Multi.ptOffset = ptOffset;
|
|
_Single._Multi.ptNow = ptStart;
|
|
}
|
|
|
|
void CDragImages::_MultipleDragMove(POINT ptNew)
|
|
{
|
|
if ((_Single._Multi.ptNow.x == ptNew.x) &&
|
|
(_Single._Multi.ptNow.y == ptNew.y))
|
|
{
|
|
// nothing has changed. bail
|
|
return;
|
|
}
|
|
|
|
if (_Single._Multi.bShown)
|
|
{
|
|
HDC hDC;
|
|
int nRect;
|
|
RECT rc, rcClip;
|
|
int dx1 = _Single._Multi.ptNow.x - _Single._Multi.ptOffset.x;
|
|
int dy1 = _Single._Multi.ptNow.y - _Single._Multi.ptOffset.y;
|
|
int dx2 = ptNew.x - _Single._Multi.ptNow.x;
|
|
int dy2 = ptNew.y - _Single._Multi.ptNow.y;
|
|
|
|
// clip to window, NOT SM_CXSCREEN/SM_CYSCREEN (multiple monitors)
|
|
GetWindowRect(_Single.hwndLock, &rcClip);
|
|
rcClip.right -= rcClip.left;
|
|
rcClip.bottom -= rcClip.top;
|
|
|
|
hDC = GetDCEx(_Single.hwndLock, NULL, DCX_WINDOW | DCX_CACHE |
|
|
DCX_LOCKWINDOWUPDATE | DCX_CLIPSIBLINGS);
|
|
|
|
for (nRect = _Single._Multi.nRects - 1; nRect >= 0; --nRect)
|
|
{
|
|
rc = _Single._Multi.pRect[nRect];
|
|
// hide pass
|
|
OffsetRect(&rc, dx1, dy1);
|
|
if ((rc.top < rcClip.bottom) && (rc.bottom > 0) &&
|
|
(rc.left < rcClip.right) && (rc.right > 0))
|
|
{
|
|
|
|
DrawFocusRect(hDC, &rc);
|
|
}
|
|
// show pass
|
|
OffsetRect(&rc, dx2, dy2);
|
|
if ((rc.top < rcClip.bottom) && (rc.bottom > 0) &&
|
|
(rc.left < rcClip.right) && (rc.right > 0))
|
|
{
|
|
DrawFocusRect(hDC, &rc);
|
|
}
|
|
}
|
|
ReleaseDC(_Single.hwndLock, hDC);
|
|
}
|
|
|
|
_Single._Multi.ptNow = ptNew;
|
|
}
|
|
|
|
HRESULT CDragImages::_SetMultiRectDragging(int cItems, LPRECT prect, POINT *pptOffset)
|
|
{
|
|
if (!Initialized())
|
|
{
|
|
// Multiple item drag
|
|
_cItems = cItems;
|
|
_parc = new RECT[2 * _cItems];
|
|
if (_parc)
|
|
{
|
|
for (int i = 0; i < cItems; i++)
|
|
_parc[i] = prect[i];
|
|
|
|
// Avoid the flicker by always pass even coords
|
|
_ptOffset.x = (pptOffset->x & ~1);
|
|
_ptOffset.y = (pptOffset->y & ~1);
|
|
_InitDragData();
|
|
}
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
#define ListView_IsIconView(hwndLV) ((GetWindowLong(hwndLV, GWL_STYLE) & (UINT)LVS_TYPEMASK) == (UINT)LVS_ICON)
|
|
|
|
HRESULT CDragImages::_SetMultiItemDragging(HWND hwndLV, int cItems, POINT *pptOffset)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
|
|
if (!Initialized())
|
|
{
|
|
// Multiple item drag
|
|
ASSERT(NULL == _parc);
|
|
|
|
_parc = new RECT[2 * cItems];
|
|
if (_parc)
|
|
{
|
|
POINT ptTemp;
|
|
int iLast, iNext;
|
|
int cxScreens, cyScreens;
|
|
LPRECT prcNext;
|
|
RECT rc;
|
|
|
|
_cItems = 0;
|
|
ASSERT(_fImage == FALSE);
|
|
|
|
//
|
|
// If this is a mirrored Window, then lead edge is going
|
|
// to be the far end in screen coord. So let's compute
|
|
// as the original code, and later in _MultipleDragMove
|
|
// we will compensate.
|
|
//
|
|
|
|
GetWindowRect( hwndLV , &rc );
|
|
ptTemp.x = rc.left;
|
|
ptTemp.y = rc.top;
|
|
|
|
//
|
|
// Reflect the shift the if the window is RTL mirrored.
|
|
//
|
|
if (IS_WINDOW_RTL_MIRRORED(hwndLV))
|
|
{
|
|
ptTemp.x = -ptTemp.x;
|
|
pptOffset->x = ((rc.right-rc.left)-pptOffset->x);
|
|
}
|
|
|
|
cxScreens = GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
|
cyScreens = GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
|
|
|
// for pre-Nashville platforms
|
|
if (!cxScreens || !cyScreens)
|
|
{
|
|
cxScreens = GetSystemMetrics(SM_CXSCREEN);
|
|
cyScreens = GetSystemMetrics(SM_CYSCREEN);
|
|
}
|
|
|
|
for (iNext = cItems - 1, iLast = -1, prcNext = _parc; iNext >= 0; --iNext)
|
|
{
|
|
iLast = ListView_GetNextItem(hwndLV, iLast, LVNI_SELECTED);
|
|
if (iLast != -1)
|
|
{
|
|
ListView_GetItemRect(hwndLV, iLast, &prcNext[0], LVIR_ICON);
|
|
OffsetRect(&prcNext[0], ptTemp.x, ptTemp.y);
|
|
|
|
if (((prcNext[0].left - pptOffset->x) < cxScreens) &&
|
|
((pptOffset->x - prcNext[0].right) < cxScreens) &&
|
|
((prcNext[0].top - pptOffset->y) < cyScreens))
|
|
{
|
|
|
|
ListView_GetItemRect(hwndLV, iLast, &prcNext[1], LVIR_LABEL);
|
|
OffsetRect(&prcNext[1], ptTemp.x, ptTemp.y);
|
|
if ((pptOffset->y - prcNext[1].bottom) < cxScreens)
|
|
{
|
|
//
|
|
// Fix 24857: Ask JoeB why we are drawing a bar instead of
|
|
// a text rectangle.
|
|
//
|
|
prcNext[1].top = (prcNext[1].top + prcNext[1].bottom)/2;
|
|
prcNext[1].bottom = prcNext[1].top + 2;
|
|
prcNext += 2;
|
|
_cItems += 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Avoid the flicker by always pass even coords
|
|
_ptOffset.x = (pptOffset->x & ~1);
|
|
_ptOffset.y = (pptOffset->y & ~1);
|
|
_InitDragData();
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//=====================================================================
|
|
// Cursor Merging
|
|
//=====================================================================
|
|
void CDragImages::_DestroyCachedCursors()
|
|
{
|
|
if (_himlCursors)
|
|
{
|
|
ImageList_Destroy(_himlCursors);
|
|
_himlCursors = NULL;
|
|
}
|
|
|
|
HCURSOR hcursor = GetCursor();
|
|
for (int i = 0; i < ARRAYSIZE(_ahcur); i++)
|
|
{
|
|
if (_ahcur[i])
|
|
{
|
|
if (_ahcur[i] == hcursor)
|
|
{
|
|
//
|
|
// Stuff in some random cursor so that we don't try to
|
|
// destroy the current cursor (and leak it too).
|
|
//
|
|
SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW)));
|
|
}
|
|
DestroyCursor(_ahcur[i]);
|
|
_ahcur[i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
HBITMAP CDragImages::CreateColorBitmap(int cx, int cy)
|
|
{
|
|
HDC hdc = GetDC(NULL);
|
|
HBITMAP hbm = CreateCompatibleBitmap(hdc, cx, cy);
|
|
ReleaseDC(NULL, hdc);
|
|
return hbm;
|
|
}
|
|
|
|
#define CreateMonoBitmap( cx, cy) CreateBitmap(cx, cy, 1, 1, NULL)
|
|
typedef WORD CURMASK;
|
|
#define _BitSizeOf(x) (sizeof(x)*8)
|
|
|
|
HRESULT CDragImages::_GetCursorLowerRight(HCURSOR hcursor, int * px, int * py, POINT *pptHotSpot)
|
|
{
|
|
ICONINFO iconinfo;
|
|
HRESULT hr = E_FAIL;
|
|
if (GetIconInfo(hcursor, &iconinfo))
|
|
{
|
|
CURMASK CurMask[16*8];
|
|
BITMAP bm;
|
|
int i;
|
|
int xFine = 16;
|
|
|
|
GetObject(iconinfo.hbmMask, sizeof(bm), (LPTSTR)&bm);
|
|
GetBitmapBits(iconinfo.hbmMask, sizeof(CurMask), CurMask);
|
|
pptHotSpot->x = iconinfo.xHotspot;
|
|
pptHotSpot->y = iconinfo.yHotspot;
|
|
if (iconinfo.hbmColor)
|
|
{
|
|
i = (int)(bm.bmWidth * bm.bmHeight / _BitSizeOf(CURMASK) - 1);
|
|
}
|
|
else
|
|
{
|
|
i = (int)(bm.bmWidth * (bm.bmHeight/2) / _BitSizeOf(CURMASK) - 1);
|
|
}
|
|
|
|
if ( i >= sizeof(CurMask))
|
|
{
|
|
i = sizeof(CurMask) -1;
|
|
}
|
|
|
|
// this assumes that the first pixel encountered on this bottom
|
|
// up/right to left search will be reasonably close to the rightmost pixel
|
|
// which for all of our cursors is correct, but it not necessarly correct.
|
|
|
|
// also, it assumes the cursor has a good mask... not like the IBeam XOR only
|
|
// cursor
|
|
for (; i >= 0; i--)
|
|
{
|
|
if (CurMask[i] != 0xFFFF)
|
|
{
|
|
// this is only accurate to 16 pixels... which is a big gap..
|
|
// so let's try to be a bit more accurate.
|
|
int j;
|
|
DWORD dwMask;
|
|
|
|
for (j = 0; j < 16; j++, xFine--)
|
|
{
|
|
if (j < 8)
|
|
{
|
|
dwMask = (1 << (8 + j));
|
|
}
|
|
else
|
|
{
|
|
dwMask = (1 << (j - 8));
|
|
}
|
|
|
|
if (!(CurMask[i] & dwMask))
|
|
break;
|
|
}
|
|
ASSERT(j < 16);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (iconinfo.hbmColor)
|
|
{
|
|
DeleteObject(iconinfo.hbmColor);
|
|
}
|
|
|
|
if (iconinfo.hbmMask)
|
|
{
|
|
DeleteObject(iconinfo.hbmMask);
|
|
}
|
|
|
|
// Compute the pointer height
|
|
// use width in both directions because the cursor is square, but the
|
|
// height might be doubleheight if it's mono
|
|
*py = ((i + 1) * _BitSizeOf(CURMASK)) / (int)bm.bmWidth;
|
|
*px = ((i * _BitSizeOf(CURMASK)) % (int)bm.bmWidth) + xFine + 2; // hang it off a little
|
|
hr = S_OK;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
// this will draw iiMerge's image over iiMain on main's lower right.
|
|
BOOL CDragImages::_MergeIcons(HCURSOR hcursor, LPCTSTR idMerge, HBITMAP *phbmImage, HBITMAP *phbmMask, POINT* pptHotSpot)
|
|
{
|
|
*phbmImage = NULL;
|
|
*phbmMask = NULL;
|
|
|
|
BOOL fRet = FALSE;
|
|
|
|
int xDraw;
|
|
int yDraw;
|
|
// find the lower corner of the cursor and put it there.
|
|
// do this whether or not we have an idMerge because it will set the hotspot
|
|
if (SUCCEEDED(_GetCursorLowerRight(hcursor, &xDraw, &yDraw, pptHotSpot)))
|
|
{
|
|
int xBitmap;
|
|
int yBitmap;
|
|
int xCursor = GetSystemMetrics(SM_CXCURSOR);
|
|
int yCursor = GetSystemMetrics(SM_CYCURSOR);
|
|
HBITMAP hbmp;
|
|
if (idMerge != (LPCTSTR)-1)
|
|
{
|
|
hbmp = (HBITMAP)LoadImage(HINST_THISDLL, idMerge, IMAGE_BITMAP, 0, 0, 0);
|
|
if (hbmp)
|
|
{
|
|
BITMAP bm;
|
|
GetObject(hbmp, sizeof(bm), &bm);
|
|
xBitmap = bm.bmWidth;
|
|
yBitmap = bm.bmHeight/2;
|
|
|
|
if (xDraw + xBitmap > xCursor)
|
|
xDraw = xCursor - xBitmap;
|
|
if (yDraw + yBitmap > yCursor)
|
|
yDraw = yCursor - yBitmap;
|
|
}
|
|
}
|
|
else
|
|
hbmp = NULL;
|
|
|
|
HDC hdcCursor = CreateCompatibleDC(NULL);
|
|
|
|
HBITMAP hbmMask = CreateMonoBitmap(xCursor, yCursor);
|
|
HBITMAP hbmImage = CreateColorBitmap(xCursor, yCursor);
|
|
|
|
if (hdcCursor && hbmMask && hbmImage)
|
|
{
|
|
HBITMAP hbmTemp = (HBITMAP)SelectObject(hdcCursor, hbmImage);
|
|
DrawIconEx(hdcCursor, 0, 0, hcursor, 0, 0, 0, NULL, DI_NORMAL);
|
|
|
|
HDC hdcBitmap;
|
|
if (hbmp)
|
|
{
|
|
hdcBitmap = CreateCompatibleDC(NULL);
|
|
SelectObject(hdcBitmap, hbmp);
|
|
|
|
//blt the two bitmaps onto the color and mask bitmaps for the cursor
|
|
BitBlt(hdcCursor, xDraw, yDraw, xBitmap, yBitmap, hdcBitmap, 0, 0, SRCCOPY);
|
|
}
|
|
|
|
SelectObject(hdcCursor, hbmMask);
|
|
|
|
DrawIconEx(hdcCursor, 0, 0, hcursor, 0, 0, 0, NULL, DI_MASK);
|
|
|
|
if (hbmp)
|
|
{
|
|
BitBlt(hdcCursor, xDraw, yDraw, xBitmap, yBitmap, hdcBitmap, 0, yBitmap, SRCCOPY);
|
|
|
|
// select back in the old bitmaps
|
|
SelectObject(hdcBitmap, hbmTemp);
|
|
DeleteDC(hdcBitmap);
|
|
DeleteObject(hbmp);
|
|
}
|
|
|
|
// select back in the old bitmaps
|
|
SelectObject(hdcCursor, hbmTemp);
|
|
}
|
|
|
|
if (hdcCursor)
|
|
DeleteDC(hdcCursor);
|
|
|
|
*phbmImage = hbmImage;
|
|
*phbmMask = hbmMask;
|
|
fRet = (hbmImage && hbmMask);
|
|
}
|
|
return fRet;
|
|
}
|
|
|
|
// this will take a cursor index and load
|
|
int CDragImages::_AddCursorToImageList(HCURSOR hcur, LPCTSTR idMerge, POINT *pptHotSpot)
|
|
{
|
|
int iIndex;
|
|
HBITMAP hbmImage, hbmMask;
|
|
|
|
// merge in the plus or link arrow if it's specified
|
|
if (_MergeIcons(hcur, idMerge, &hbmImage, &hbmMask, pptHotSpot))
|
|
{
|
|
iIndex = ImageList_Add(_himlCursors, hbmImage, hbmMask);
|
|
}
|
|
else
|
|
{
|
|
iIndex = -1;
|
|
}
|
|
|
|
if (hbmImage)
|
|
DeleteObject(hbmImage);
|
|
|
|
if (hbmMask)
|
|
DeleteObject(hbmMask);
|
|
|
|
return iIndex;
|
|
}
|
|
|
|
int _MapEffectToId(DWORD dwEffect)
|
|
{
|
|
int idCursor;
|
|
|
|
// DebugMsg(DM_TRACE, "sh TR - DAD_GiveFeedBack dwEffect=%x", dwEffect);
|
|
|
|
switch (dwEffect & (DROPEFFECT_COPY|DROPEFFECT_LINK|DROPEFFECT_MOVE))
|
|
{
|
|
case 0:
|
|
idCursor = DCID_NO;
|
|
break;
|
|
|
|
case DROPEFFECT_COPY:
|
|
idCursor = DCID_COPY;
|
|
break;
|
|
|
|
case DROPEFFECT_LINK:
|
|
idCursor = DCID_LINK;
|
|
break;
|
|
|
|
case DROPEFFECT_MOVE:
|
|
idCursor = DCID_MOVE;
|
|
break;
|
|
|
|
default:
|
|
// if it's a right drag, we can have any effect... we'll
|
|
// default to the arrow without merging in anything
|
|
idCursor = DCID_MOVE;
|
|
break;
|
|
}
|
|
|
|
return idCursor;
|
|
}
|
|
|
|
int CDragImages::_MapCursorIDToImageListIndex(int idCur)
|
|
{
|
|
const static struct
|
|
{
|
|
BOOL fSystem;
|
|
LPCTSTR idRes;
|
|
LPCTSTR idMerge;
|
|
}
|
|
c_acurmap[DCID_MAX] =
|
|
{
|
|
{ FALSE, MAKEINTRESOURCE(IDC_NULL), (LPCTSTR)-1},
|
|
{ TRUE, IDC_NO, (LPCTSTR)-1 },
|
|
{ TRUE, IDC_ARROW, (LPCTSTR)-1 },
|
|
{ TRUE, IDC_ARROW, MAKEINTRESOURCE(IDB_PLUS_MERGE) },
|
|
{ TRUE, IDC_ARROW, MAKEINTRESOURCE(IDB_LINK_MERGE) },
|
|
};
|
|
|
|
ASSERT(idCur >= -1 && idCur < (int)ARRAYSIZE(c_acurmap));
|
|
|
|
// -1 means "Initialize the image list index array".
|
|
if (idCur == -1)
|
|
{
|
|
for (int i = 0; i < ARRAYSIZE(c_acurmap); i++)
|
|
{
|
|
_aindex[i] = -1;
|
|
}
|
|
idCur = 0; // fall through to return -1
|
|
}
|
|
else
|
|
{
|
|
if (_aindex[idCur] == -1)
|
|
{
|
|
HINSTANCE hinst = c_acurmap[idCur].fSystem ? NULL : HINST_THISDLL;
|
|
HCURSOR hcur = LoadCursor(hinst, c_acurmap[idCur].idRes);
|
|
if (hcur)
|
|
{
|
|
_aindex[idCur] = _AddCursorToImageList(hcur, c_acurmap[idCur].idMerge, &_aptHotSpot[idCur]);
|
|
}
|
|
}
|
|
}
|
|
return _aindex[idCur];
|
|
}
|
|
|
|
HCURSOR CDragImages::_SetCursorHotspot(HCURSOR hcur, POINT *ptHot)
|
|
{
|
|
ICONINFO iconinfo = { 0 };
|
|
HCURSOR hcurHotspot;
|
|
|
|
GetIconInfo(hcur, &iconinfo);
|
|
iconinfo.xHotspot = ptHot->x;
|
|
iconinfo.yHotspot = ptHot->y;
|
|
iconinfo.fIcon = FALSE;
|
|
hcurHotspot = (HCURSOR)CreateIconIndirect(&iconinfo);
|
|
if (iconinfo.hbmColor)
|
|
{
|
|
DeleteObject(iconinfo.hbmColor);
|
|
}
|
|
|
|
if (iconinfo.hbmMask)
|
|
{
|
|
DeleteObject(iconinfo.hbmMask);
|
|
}
|
|
return hcurHotspot;
|
|
}
|
|
|
|
void CDragImages::SetDropEffectCursor(int idCur)
|
|
{
|
|
if (_himlCursors && (idCur != -1))
|
|
{
|
|
if (!_ahcur[idCur])
|
|
{
|
|
int iIndex = _MapCursorIDToImageListIndex(idCur);
|
|
if (iIndex != -1)
|
|
{
|
|
HCURSOR hcurColor = ImageList_GetIcon(_himlCursors, iIndex, 0);
|
|
//
|
|
// On non C1_COLORCURSOR displays, CopyImage() will enforce
|
|
// monochrome. So on color cursor displays, we'll get colored
|
|
// dragdrop pix.
|
|
//
|
|
HCURSOR hcurScreen = (HCURSOR)CopyImage(hcurColor, IMAGE_CURSOR,
|
|
0, 0, LR_COPYRETURNORG | LR_DEFAULTSIZE);
|
|
|
|
HCURSOR hcurFinal = _SetCursorHotspot(hcurScreen, &_aptHotSpot[idCur]);
|
|
|
|
if ((hcurScreen != hcurColor) && hcurColor)
|
|
{
|
|
DestroyCursor(hcurColor);
|
|
}
|
|
|
|
if (hcurFinal)
|
|
{
|
|
if (hcurScreen)
|
|
{
|
|
DestroyCursor(hcurScreen);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hcurFinal = hcurScreen;
|
|
}
|
|
|
|
_ahcur[idCur] = hcurFinal;
|
|
}
|
|
}
|
|
|
|
if (_ahcur[idCur])
|
|
{
|
|
//
|
|
// This code assumes that SetCursor is pretty quick if it is
|
|
// already set.
|
|
//
|
|
SetCursor(_ahcur[idCur]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//=====================================================================
|
|
// CDropSource
|
|
//=====================================================================
|
|
|
|
class CDropSource : public IDropSource
|
|
{
|
|
private:
|
|
LONG _cRef;
|
|
DWORD _grfInitialKeyState;
|
|
IDataObject* _pdtobj;
|
|
|
|
public:
|
|
explicit CDropSource(IDataObject *pdtobj);
|
|
virtual ~CDropSource();
|
|
|
|
// IUnknown methods
|
|
STDMETHODIMP QueryInterface(REFIID riid, void **ppvObj);
|
|
STDMETHODIMP_(ULONG) AddRef();
|
|
STDMETHODIMP_(ULONG) Release();
|
|
|
|
// IDropSource methods
|
|
STDMETHODIMP GiveFeedback(DWORD dwEffect);
|
|
STDMETHODIMP QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState);
|
|
};
|
|
|
|
|
|
void DAD_ShowCursor(BOOL fShow)
|
|
{
|
|
static BOOL s_fCursorHidden = FALSE;
|
|
|
|
if (fShow)
|
|
{
|
|
if (s_fCursorHidden)
|
|
{
|
|
ShowCursor(TRUE);
|
|
s_fCursorHidden = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!s_fCursorHidden)
|
|
{
|
|
ShowCursor(FALSE);
|
|
s_fCursorHidden = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
CDropSource::CDropSource(IDataObject *pdtobj) : _cRef(1), _pdtobj(pdtobj), _grfInitialKeyState(0)
|
|
{
|
|
_pdtobj->AddRef();
|
|
|
|
// Tell the data object that we're entering the drag loop.
|
|
DataObj_SetDWORD(_pdtobj, g_cfInDragLoop, 1);
|
|
}
|
|
|
|
CDropSource::~CDropSource()
|
|
{
|
|
DAD_ShowCursor(TRUE); // just in case
|
|
_pdtobj->Release();
|
|
}
|
|
|
|
//
|
|
// Create an instance of CDropSource
|
|
//
|
|
STDMETHODIMP CDropSource_CreateInstance(IDropSource **ppdsrc, IDataObject *pdtobj)
|
|
{
|
|
*ppdsrc = new CDropSource(pdtobj);
|
|
return *ppdsrc ? S_OK : E_OUTOFMEMORY;
|
|
}
|
|
|
|
STDMETHODIMP CDropSource::QueryInterface(REFIID riid, void **ppvObj)
|
|
{
|
|
static const QITAB qit[] = {
|
|
QITABENT(CDropSource, IDropSource),
|
|
{ 0 },
|
|
};
|
|
return QISearch(this, qit, riid, ppvObj);
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CDropSource::AddRef()
|
|
{
|
|
return InterlockedIncrement(&_cRef);
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CDropSource::Release()
|
|
{
|
|
if (InterlockedDecrement(&_cRef))
|
|
return _cRef;
|
|
|
|
delete this;
|
|
return 0;
|
|
}
|
|
|
|
STDMETHODIMP CDropSource::QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if (fEscapePressed)
|
|
{
|
|
hr = DRAGDROP_S_CANCEL;
|
|
}
|
|
else
|
|
{
|
|
// initialize ourself with the drag begin button
|
|
if (_grfInitialKeyState == 0)
|
|
_grfInitialKeyState = (grfKeyState & (MK_LBUTTON | MK_RBUTTON | MK_MBUTTON));
|
|
|
|
// If the window is hung for a while, the drag operation can happen before
|
|
// the first call to this function, so grfInitialKeyState will be 0. If this
|
|
// happened, then we did a drop. No need to assert...
|
|
//ASSERT(this->grfInitialKeyState);
|
|
|
|
if (!(grfKeyState & _grfInitialKeyState))
|
|
{
|
|
//
|
|
// A button is released.
|
|
//
|
|
hr = DRAGDROP_S_DROP;
|
|
}
|
|
else if (_grfInitialKeyState != (grfKeyState & (MK_LBUTTON | MK_RBUTTON | MK_MBUTTON)))
|
|
{
|
|
//
|
|
// If the button state is changed (except the drop case, which we handle
|
|
// above, cancel the drag&drop.
|
|
//
|
|
hr = DRAGDROP_S_CANCEL;
|
|
}
|
|
}
|
|
|
|
if (hr != S_OK)
|
|
{
|
|
SetCursor(LoadCursor(NULL, IDC_ARROW));
|
|
DAD_ShowCursor(TRUE);
|
|
DAD_SetDragCursor(DCID_NULL);
|
|
|
|
// Tell the data object that we're leaving the drag loop.
|
|
if (_pdtobj)
|
|
DataObj_SetDWORD(_pdtobj, g_cfInDragLoop, 0);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CDropSource::GiveFeedback(DWORD dwEffect)
|
|
{
|
|
int idCursor = _MapEffectToId(dwEffect);
|
|
|
|
//
|
|
// OLE does not give us DROPEFFECT_MOVE even though our IDT::DragOver
|
|
// returns it, if we haven't set that bit when we have called DoDragDrop.
|
|
// Instead of arguing whether or not this is a bug or by-design of OLE,
|
|
// we work around it. It is important to note that this hack around
|
|
// g_fDraggingOverSource is purely visual hack. It won't affect the
|
|
// actual drag&drop operations at all (DV_AlterEffect does it all).
|
|
//
|
|
// - SatoNa
|
|
//
|
|
if (idCursor == DCID_NO && g_fDraggingOverSource)
|
|
{
|
|
idCursor = DCID_MOVE;
|
|
}
|
|
|
|
//
|
|
// No need to merge the cursor, if we are not dragging over to
|
|
// one of shell windows.
|
|
//
|
|
if (DAD_IsDraggingImage())
|
|
{
|
|
// Feedback for single (image) dragging
|
|
DAD_ShowCursor(FALSE);
|
|
DAD_SetDragCursor(idCursor);
|
|
}
|
|
else if (DAD_IsDragging() && g_pdiDragImages)
|
|
{
|
|
// Feedback for multiple (rectangles) dragging
|
|
g_pdiDragImages->SetDropEffectCursor(idCursor);
|
|
DAD_ShowCursor(TRUE);
|
|
return NOERROR;
|
|
}
|
|
else
|
|
{
|
|
DAD_ShowCursor(TRUE);
|
|
}
|
|
|
|
return DRAGDROP_S_USEDEFAULTCURSORS;
|
|
}
|
|
|
|
//=====================================================================
|
|
// DAD
|
|
//=====================================================================
|
|
|
|
void FixupDragPoint(HWND hwnd, POINT* ppt)
|
|
{
|
|
if (hwnd)
|
|
{
|
|
RECT rc = {0};
|
|
GetWindowRect(hwnd, &rc);
|
|
ppt->x += rc.left;
|
|
ppt->y += rc.top;
|
|
}
|
|
}
|
|
|
|
BOOL DAD_InitDragImages()
|
|
{
|
|
if (!g_pdiDragImages)
|
|
CDragImages_CreateInstance(NULL, IID_IDragSourceHelper, NULL);
|
|
|
|
return g_pdiDragImages != NULL;
|
|
}
|
|
|
|
|
|
STDAPI_(BOOL) DAD_ShowDragImage(BOOL bShow)
|
|
{
|
|
if (DAD_InitDragImages())
|
|
return g_pdiDragImages->Show(bShow) == S_OK ? TRUE : FALSE;
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL DAD_IsDragging()
|
|
{
|
|
if (DAD_InitDragImages())
|
|
return g_pdiDragImages->IsDragging();
|
|
return FALSE;
|
|
}
|
|
|
|
void DAD_SetDragCursor(int idCursor)
|
|
{
|
|
if (DAD_InitDragImages())
|
|
g_pdiDragImages->SetDragCursor(idCursor);
|
|
}
|
|
|
|
STDAPI_(BOOL) DAD_DragEnterEx3(HWND hwndTarget, const POINTL ptStart, IDataObject *pdtobj)
|
|
{
|
|
RECT rc;
|
|
GetWindowRect(hwndTarget, &rc);
|
|
|
|
// If hwndTarget is RTL mirrored, then measure the
|
|
// the client point from the visual right edge
|
|
// (near edge in RTL mirrored windows). [samera]
|
|
POINT pt;
|
|
if (IS_WINDOW_RTL_MIRRORED(hwndTarget))
|
|
pt.x = rc.right - ptStart.x;
|
|
else
|
|
pt.x = ptStart.x - rc.left;
|
|
|
|
pt.y = ptStart.y - rc.top;
|
|
return DAD_DragEnterEx2(hwndTarget, pt, pdtobj);
|
|
}
|
|
|
|
STDAPI_(BOOL) DAD_DragEnterEx2(HWND hwndTarget, const POINT ptStart, IDataObject *pdtobj)
|
|
{
|
|
BOOL bRet = FALSE;
|
|
if (DAD_InitDragImages())
|
|
{
|
|
POINT pt = ptStart;
|
|
FixupDragPoint(hwndTarget, &pt);
|
|
bRet = SUCCEEDED(g_pdiDragImages->DragEnter(hwndTarget, pdtobj, &pt, NULL));
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
STDAPI_(BOOL) DAD_DragEnterEx(HWND hwndTarget, const POINT ptStart)
|
|
{
|
|
return DAD_DragEnterEx2(hwndTarget, ptStart, NULL);
|
|
}
|
|
|
|
STDAPI_(BOOL) DAD_DragEnter(HWND hwndTarget)
|
|
{
|
|
POINT ptStart;
|
|
|
|
GetCursorPos(&ptStart);
|
|
if (hwndTarget)
|
|
ScreenToClient(hwndTarget, &ptStart);
|
|
|
|
return DAD_DragEnterEx(hwndTarget, ptStart);
|
|
}
|
|
|
|
STDAPI_(BOOL) DAD_DragMoveEx(HWND hwndTarget, const POINTL ptStart)
|
|
{
|
|
RECT rc;
|
|
GetWindowRect(hwndTarget, &rc);
|
|
|
|
// If hwndTarget is RTL mirrored, then measure the
|
|
// the client point from the visual right edge
|
|
// (near edge in RTL mirrored windows). [samera]
|
|
POINT pt;
|
|
if (IS_WINDOW_RTL_MIRRORED(hwndTarget))
|
|
pt.x = rc.right - ptStart.x;
|
|
else
|
|
pt.x = ptStart.x - rc.left;
|
|
|
|
pt.y = ptStart.y - rc.top;
|
|
return DAD_DragMove(pt);
|
|
}
|
|
|
|
|
|
STDAPI_(BOOL) DAD_DragMove(POINT pt)
|
|
{
|
|
if (DAD_InitDragImages())
|
|
{
|
|
FixupDragPoint(g_pdiDragImages->GetTarget(), &pt);
|
|
return g_pdiDragImages->DragOver(&pt, 0);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
STDAPI_(BOOL) DAD_SetDragImage(HIMAGELIST him, POINT *pptOffset)
|
|
{
|
|
if (DAD_InitDragImages() && !g_pdiDragImages->IsDraggingLayeredWindow())
|
|
{
|
|
//
|
|
// DAD_SetDragImage(-1, NULL) means "clear the drag image only
|
|
// if the image is set by this thread"
|
|
//
|
|
if (him == (HIMAGELIST)-1)
|
|
{
|
|
BOOL fThisThreadHasImage = FALSE;
|
|
ENTERCRITICAL;
|
|
if (g_pdiDragImages->Initialized() && g_pdiDragImages->GetThread() == GetCurrentThreadId())
|
|
{
|
|
fThisThreadHasImage = TRUE;
|
|
}
|
|
LEAVECRITICAL;
|
|
|
|
if (fThisThreadHasImage)
|
|
{
|
|
g_pdiDragImages->FreeDragData();
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
return g_pdiDragImages->SetDragImage(him, 0, pptOffset);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// This function returns TRUE, if we are dragging an image. It means
|
|
// you have called either DAD_SetDragImage (with him != NULL) or
|
|
// DAD_SetDragImageFromListview.
|
|
//
|
|
BOOL DAD_IsDraggingImage(void)
|
|
{
|
|
if (DAD_InitDragImages())
|
|
return g_pdiDragImages->IsDraggingImage();
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
STDAPI_(BOOL) DAD_DragLeave()
|
|
{
|
|
if (DAD_InitDragImages())
|
|
return g_pdiDragImages->DragLeave();
|
|
return FALSE;
|
|
}
|
|
|
|
STDAPI_(void) DAD_ProcessDetach(void)
|
|
{
|
|
if (g_pdiDragImages)
|
|
{
|
|
g_pdiDragImages->ProcessDetach();
|
|
g_pdiDragImages->Release();
|
|
}
|
|
}
|
|
|
|
STDAPI_(void) DAD_ThreadDetach(void)
|
|
{
|
|
if (g_pdiDragImages)
|
|
g_pdiDragImages->ThreadDetach();
|
|
}
|
|
|
|
// called from defview on SPI_SETCURSORS (user changed the system cursors)
|
|
STDAPI_(void) DAD_InvalidateCursors(void)
|
|
{
|
|
g_cRev++;
|
|
}
|
|
|
|
STDAPI_(BOOL) DAD_SetDragImageFromWindow(HWND hwnd, POINT* ppt, IDataObject* pdtobj)
|
|
{
|
|
if (DAD_InitDragImages())
|
|
return S_OK == g_pdiDragImages->InitializeFromWindow(hwnd, ppt, pdtobj);
|
|
return FALSE;
|
|
}
|
|
|
|
// shell32.dll export, but only used by print queue window code
|
|
//
|
|
STDAPI_(BOOL) DAD_SetDragImageFromListView(HWND hwndLV, POINT ptOffset)
|
|
{
|
|
// really a nop, as this does not have access to the data object
|
|
return DAD_InitDragImages();
|
|
}
|
|
|
|
// wrapper around OLE DoDragDrop(), will create drag source on demand and supports
|
|
// drag images for you
|
|
|
|
STDAPI SHDoDragDrop(HWND hwnd, IDataObject *pdtobj, IDropSource *pdsrc, DWORD dwEffect, DWORD *pdwEffect)
|
|
{
|
|
IDropSource *pdsrcRelease = NULL;
|
|
|
|
if (pdsrc == NULL)
|
|
{
|
|
CDropSource_CreateInstance(&pdsrcRelease, pdtobj);
|
|
pdsrc = pdsrcRelease;
|
|
}
|
|
|
|
// if there is no drag contents clipboard format present, try to add it
|
|
FORMATETC fmte = {_GetDragContentsCF(), NULL, DVASPECT_CONTENT, -1, TYMED_ISTREAM};
|
|
if (S_OK != pdtobj->QueryGetData(&fmte))
|
|
{
|
|
if (DAD_InitDragImages())
|
|
g_pdiDragImages->InitializeFromWindow(hwnd, NULL, pdtobj);
|
|
}
|
|
|
|
HRESULT hr = DoDragDrop(pdtobj, pdsrc, dwEffect, pdwEffect);
|
|
|
|
if (pdsrcRelease)
|
|
pdsrcRelease->Release();
|
|
|
|
return hr;
|
|
}
|
|
|