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