//+------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1992 - 1996. // // File: drag.cpp // // Contents: Api's for doing drag'n'drop // // Classes: CPoint // CDragOperation // CDropTarget // // History: dd-mmm-yy Author Comment // 08-Nov-94 alexgo converted to PrivDragDrop rpc // for Drag Drop protocol // 20-Oct-94 alexgo added Win3.1 style drag drop // for Chicago/NT shell // 30-Sep-94 ricksa Drag/Drop optimization. // 18-Jul-94 ricksa made cursors work in shared WOW // 21-Apr-94 ricksa made drag/drop handle WM_CANCELMODE // 04-Apr-94 ricksa rewrote DoDragDrop loop // 11-Jan-94 alexgo added VDATEHEAP to every function // 29-Dec-93 alexgo converted to RPC alogirithm for // getting IDropTarget, etc. // 06-Dec-93 alexgo commented, formatted // 93/94 Johann Posch (JohannP) created Drag/Drop for Ole 16 bit // // Notes: // // RPC Drag Drop algorithm: // // During a drag drop operation, the user is moving the mouse around // the screen, passing over many windows. For each window the mouse // is over, we need to determine if the window is a drop target. // If it is, then we remote the IDropTarget interface to the DropSource // so that the correct visual feedbacks can be given. // // To accomplish this, RegisterDragDrop adds two properties to the // drop target window: a public property, EndPoint ID (provided to // us by compobj), and a private property (available only to the calling // process), the IDropTarget pointer. // // During the DoDragDrop loop, we ask compobj to test each window for // the EndpointID property. If it is there, compobj (via // GetInterfaceFromWindowProp), then we will rpc to the drop target // process, get the IDropTarget pointer and marshal it back to the // drop source process. We also install a custom message filter to // ensure that messages (particularly mouse move messages) are handled // correctly. // // RevokeDragDrop simply removes the above mentioned properties from // the window handle. // // Because in Win32, you can always switch windows and mouse capture // depends on having the mouse button down, drag/drop processing // is changed slightly. Whenever, the user does an operation that // would switch windows, the clipboard window that we use for capture // will get a WM_CANCELMODE. It will notify the drag operation and // the drag operation will proceed as if the user aborted the operation. // // // Win 3.1 DragDrop algorithm: // // Win3.1 apps can register a window as a drop target via DragAcceptFiles. // This API sets the WS_EX_ACCEPTFILES bit in the window style. // // In Win3.1, these apps would get a WM_DROPFILES message when // files where dropped on them. An hglobal with the filenames is // sent in the wparam of WM_DROPFILES. // // In Chicago and NT3.5, CF_HDROP is a new clipboard format that is // identical to the data sent in WM_DROPFILES. If we see this format // available in a data object passed to DoDragDrop, then we enter // into our Win31 compatibility mode (which affects finding a drop // target). // // When finding a drop target for a given window, we check to // see if a window in the hierarchy is registered as a Win31 drop // target. If so, then we create a wrapper drop target. This wrapper // drop target will forward calls to the real drop target (if available). // // With Win3.1 drag drop, we can do a COPY. If the OLE target indicates // that no OLE drop can be performed (by returning DROPEFFECT_NONE), // then we substitute in DROPEFFECT_COPY. // // On Drop, if the OLE target chooses not to accept the drop, then // we will post the window a WM_DROPFILES message with the hglobal // obtained from IDataObject::GetData(CF_HDROP). // //-------------------------------------------------------------------------- #include #pragma SEG(drag) #include #include #include #include "enumgen.h" #include "clipbrd.h" #include "drag.h" NAME_SEG(Drag) ASSERTDATA ATOM g_aEndPointAtom; // DROPFILES is the structure of data contained in the CF_HDROP format. // However, this is private to the shell, so it is not declared in any // header files. typedef struct _DROPFILES { DWORD pFiles; // offset of file list POINTL pt; // drop point (client coords) DWORD fNC; // is it on NonClient area // and pt is in screen coords DWORD fWide; // WIDE character switch } DROPFILES, FAR * LPDROPFILES; #define WM_NCMOUSEFIRST 0x00A0 #define WM_NCMOUSELAST 0x00A9 // From ido.cpp to create shared memory formats HANDLE CreateSharedDragFormats(IDataObject *pIDataObject); #define VK_ALT VK_MENU static const struct { int keyCode; WPARAM keyFlag; } vKeyMap [] = { { VK_LBUTTON, MK_LBUTTON }, { VK_RBUTTON, MK_RBUTTON }, { VK_MBUTTON, MK_MBUTTON }, { VK_ALT , MK_ALT }, { VK_SHIFT , MK_SHIFT }, { VK_CONTROL, MK_CONTROL } }; // This is the default cursor object for 32 bit apps. Only one such object // is needed for 32 bit apps. 16 bit apps need one per shared WOW application // that is running. CDragDefaultCursors *cddcDefault32 = NULL; extern ATOM g_aDropTarget; extern ATOM g_aDropTargetMarshalHwnd; //+------------------------------------------------------------------------- // // Member: DragDropProcessUninitialize // // Synopsis: Does any Unitialization necessary at OleUninitialize time. // for the last Unitialize for the Process // // Returns: none // // Algorithm: // History: dd-mmm-yy Author Comment // 18-Jul-94 rogerg Created // // Note: We need a per thread default cursor object in WOW because // of the clean up that WOW does. For 32 bit apps, we just use // one for the entire process. // //-------------------------------------------------------------------------- void DragDropProcessUninitialize(void) { if (NULL != cddcDefault32) { delete cddcDefault32; cddcDefault32 = NULL; } } //+------------------------------------------------------------------------- // // Member: CDragDefaultCursors::GetDefaultCursorObject, static // // Synopsis: Get appropriate pointer to default cursor object // // Returns: NULL - error occurred // ~NULL - pointer to appropriate default cursor table // // Algorithm: If we are in a 32 bit app, just get a pointer to the // single cursor table. In 16 bit, get the per thread cursor // table. If there is none, then allocate and initialize it. // // History: dd-mmm-yy Author Comment // 18-Jul-94 Ricksa Created // // Note: We need a per thread default cursor object in WOW because // of the clean up that WOW does. For 32 bit apps, we just use // one for the entire process. // //-------------------------------------------------------------------------- CDragDefaultCursors *CDragDefaultCursors::GetDefaultCursorObject(void) { if (!IsWOWThread()) { // If we aren't in WOW, we can use the single common default cursor // object. We make sure that it is initialized before we use it. if (NULL == cddcDefault32) { cddcDefault32 = new CDragDefaultCursors; if (cddcDefault32) { if (!cddcDefault32->Init()) { delete cddcDefault32; cddcDefault32 = NULL; } } } return cddcDefault32; } COleTls tls; // We are in WOW. Get the cursor object if it has already been allocated CDragDefaultCursors *pccdc16 = (CDragDefaultCursors *) tls->pDragCursors; if (pccdc16 == NULL) { // No cursor table so allocate it -- Please note that we take advantage // of the fact that this object has only the default constructor by // simply allocating it rather than "newing" it. The point is that // we need to free the memory at thread release time and this happens // in code that doesn't know about the the object. pccdc16 = (CDragDefaultCursors *) PrivMemAlloc(sizeof(CDragDefaultCursors)); if (pccdc16 != NULL) { // Successfully allocated so initialize it if (!pccdc16->Init()) { PrivMemFree(pccdc16); return NULL; } tls->pDragCursors = pccdc16; } } return pccdc16; } //+------------------------------------------------------------------------- // // Function: CDragDefaultCursors::Init // // Synopsis: Initialize object by loading all the default cursors. // // History: dd-mmm-yy Author Comment // 19-Apr-94 Ricksa Created // // Note: We continue the Win16 practice of ignoring possible failure // cases when loading the cursors although we do put in a // debug verification that they all loaded. // //-------------------------------------------------------------------------- BOOL CDragDefaultCursors::Init(void) { // Make sure table is set to NULLs. memset(&ahcursorDefaults[0][0], 0, sizeof(ahcursorDefaults)); // Load cursors for operation if ( !(ahcursorDefaults[NO_SCROLL] [NO_DROP] = LoadCursor (g_hmodOLE2, MAKEINTRESOURCE(CURNONE))) ) return FALSE; if (!(ahcursorDefaults[NO_SCROLL] [MOVE_DROP] = LoadCursor (g_hmodOLE2, MAKEINTRESOURCE(CURMOVE))) ) return FALSE; if (!(ahcursorDefaults[NO_SCROLL] [COPY_DROP] = LoadCursor (g_hmodOLE2, MAKEINTRESOURCE(CURCOPY))) ) return FALSE; if (!(ahcursorDefaults[NO_SCROLL] [LINK_DROP] = LoadCursor(g_hmodOLE2, MAKEINTRESOURCE(CURLINK))) ) return FALSE; // Load cursors for operation ahcursorDefaults[SCROLL] [NO_DROP] = ahcursorDefaults[NO_SCROLL] [NO_DROP]; ahcursorDefaults[SCROLL] [MOVE_DROP] = ahcursorDefaults[NO_SCROLL] [MOVE_DROP]; ahcursorDefaults[SCROLL] [COPY_DROP] = ahcursorDefaults[NO_SCROLL] [COPY_DROP]; ahcursorDefaults[SCROLL] [LINK_DROP] = ahcursorDefaults[NO_SCROLL] [LINK_DROP]; #if DBG == 1 // For debug, verify that cursors were loaded correctly for (int i = 0; i < 2; i++) { for (int j = 0; j < 4; j++) { AssertSz((ahcursorDefaults[i] [j] != NULL), "Drag/Drop cursor initialization failed!"); } } #endif // DBG == 1 return TRUE; } //+------------------------------------------------------------------------- // // Function: CDragDefaultCursors::SetCursor // // Synopsis: Set cursor to appropriate value // // Algorithm: We use the input effect to calculate the appropriate offset // into the table for the cursor to use. // // History: dd-mmm-yy Author Comment // 19-Apr-94 Ricksa Created // // Note: We use the table approach so we to make consistent behavior // between scroll and non-scroll cursors. // //-------------------------------------------------------------------------- void CDragDefaultCursors::SetCursor(DWORD dwEffect) { // Get Scroll index int iScroll = (dwEffect & DROPEFFECT_SCROLL) ? SCROLL : NO_SCROLL; int iCursorType = NO_DROP; if (dwEffect & DROPEFFECT_LINK) { iCursorType = LINK_DROP; } else if (dwEffect & DROPEFFECT_COPY) { iCursorType = COPY_DROP; } else if (dwEffect & DROPEFFECT_MOVE) { iCursorType = MOVE_DROP; } ::SetCursor(ahcursorDefaults[iScroll] [iCursorType]); } // // Drag/Drop Operation Statics // LONG CDragOperation::s_wScrollInt = -1; //+------------------------------------------------------------------------- // // Function: GetControlKeysState // // Synopsis: queries the current status of the control keys // // Arguments: [fAll] -- if true, the just query the keys, not mouse // buttons too // // Returns: the MK flags for each key pressed // // Algorithm: Get key state either for all keys and mouse buttons in // the vKeyMap table or simply for the key portion of the table // and translate it to the WPARAM form as returned in mouse // messages. // // History: dd-mmm-yy Author Comment // 06-Dec-93 alexgo 32bit port // //-------------------------------------------------------------------------- WORD GetControlKeysState(BOOL fAll) { WORD grfKeyState = 0; int i = (fAll) ? 0 : 3; for (; i < sizeof(vKeyMap) / sizeof(vKeyMap[0]); i++) { if (GetKeyState(vKeyMap[i].keyCode) < 0) // Key down { grfKeyState |= vKeyMap[i].keyFlag; } } return grfKeyState; } //+------------------------------------------------------------------------- // // Function: GetControlKeysStateOfParam // // Synopsis: gets the key/button state of wparam (used with mouse messages) // // Arguments: [wParam] -- the wParam to parse apart // // Returns: the key's set in wParam // // Algorithm: First determine if keys we are interested in are set // in the wParam message. Then go check the state of the // ALT key and record that in the key state. We then return // that to the caller. // // History: dd-mmm-yy Author Comment // 06-Dec-93 alexgo 32bit port // //-------------------------------------------------------------------------- WORD GetControlKeysStateOfParam(WPARAM wParam) { // Check all the buttons we are interested in at once. WORD grfKeyState = (WORD) wParam & (MK_LBUTTON | MK_RBUTTON | MK_MBUTTON | MK_SHIFT | MK_CONTROL); // get the alt key if (GetKeyState(VK_ALT) < 0) // Key down { grfKeyState |= MK_ALT; } return grfKeyState; } //+------------------------------------------------------------------------- // // Function: IsWin31DropTarget // // Synopsis: determines whether the given hwnd is a valid drop target // for Win31 style drag drop // // Effects: // // Arguments: [hwnd] -- the window to check // // Requires: // // Returns: TRUE/ // FALSE // // Signals: // // Modifies: // // Algorithm: checks the WS_EX_ACCEPTFILES style bit. If this bit is // set and the window is not disabled, then it is a valid // Win3.1 drop target. // // History: dd-mmm-yy Author Comment // 25-Jan-95 alexgo added check for WS_DISABLED // 20-Oct-94 alexgo author // // Notes: // //-------------------------------------------------------------------------- BOOL IsWin31DropTarget( HWND hwnd ) { LONG exstyle; exstyle = GetWindowLong(hwnd, GWL_EXSTYLE); if( (exstyle & WS_EX_ACCEPTFILES) ) { LONG style; style = GetWindowLong(hwnd, GWL_STYLE); if( !(style & WS_DISABLED) ) { return TRUE; } } return FALSE; } //+------------------------------------------------------------------------- // // Function: UseWin31DragDrop // // Synopsis: tests the given data object to see if enough data is offered // to perform Win3.1 style drag drop // // Effects: // // Arguments: [pDataObject] -- pointer to the data object // // Requires: pdataobj must not be NULL // // Returns: TRUE/FALSE // // Signals: // // Modifies: // // Algorithm: does an IDataObject::QueryGetData for CF_HDROP // // History: dd-mmm-yy Author Comment // 30-Oct-94 alexgo author // // Notes: // //-------------------------------------------------------------------------- BOOL UseWin31DragDrop(IDataObject *pDataObject) { FORMATETC formatetc; INIT_FORETC(formatetc); formatetc.cfFormat = CF_HDROP; formatetc.tymed = TYMED_HGLOBAL; if( pDataObject->QueryGetData(&formatetc) == NOERROR ) { return TRUE; } else { return FALSE; } } //+------------------------------------------------------------------------- // // Function: IsNCDrop // // Synopsis: are we dropping into the non-client area of the window or // on an iconic window? // // Effects: *DOES A SEND MESSAGE*!!! // // Arguments: [hwnd] -- the window to ask // [pt] -- the point in screen coords // // Requires: // // Returns: TRUE/FALSE (TRUE if in non-client area) // // Signals: // // Modifies: // // Algorithm: // // History: dd-mmm-yy Author Comment // 25-Jan-95 alexgo borrowed from Win95 shell sources // // Notes: // //-------------------------------------------------------------------------- BOOL IsNCDrop(HWND hwnd, POINT pt) { return (!IsIconic(hwnd) && HTCLIENT!=SendMessage(hwnd, WM_NCHITTEST, 0, MAKELPARAM(pt.x, pt.y))); } //+------------------------------------------------------------------------- // // Member: GetDropTarget // // Synopsis: Gets the IDropTarget * from the closest window in the // hierachy up from the given window (if available, of // course ;-) // // Arguments: [hwndCur] -- the window to the cursor is currently over // [hwndDropTarget] -- the window that contains a valid DropTarget // // Returns: Result of drag enter operation at Target // // Algorithm: Loop calling PrivDragDrop until we get a drop target or // we run out of windows that are parent to the window that // the mouse is currently on. // // If a window in the hierarchy has registered itself for // Win3.1 drag drop, then we create a drop target wrapper // (CDropTarget) to handle the Win3.1 protocol. Note // that a window hierarchy may be both OLE *and* Win3.1 // targets. // // History: dd-mmm-yy Author Comment // 08-Nov-94 alexgo converted to use PrivDragDrop // 20-Oct-94 alexgo added Win31 drop target support // 30-Sep-94 ricksa Drag/Drop optimization. // 21-Jul-94 alexgo removed GetDropTargetFromWindow // optimization and put that functionality // in GetInterfaceFromWindowProp (to // help make clipboard faster). // 06-Apr-94 Ricksa Modified to call GetDropTargetFromWindow // to optimize local calls // 11-Jan-94 alexgo changed name from GetTopStm to // GetDropTarget, converted to the RPC-style // drag drop, added a VDATEHEAP macro // 06-Dec-93 alexgo commented // //-------------------------------------------------------------------------- HRESULT CDragOperation::GetDropTarget(HWND hwnd31,HWND hwndDropTarget) { IDropTarget *ptarget = NULL; DDInfo hDDInfo = NULL; VDATEHEAP(); DDDebugOut((DEB_ITRACE, "%p _IN GetDropTarget ( %x,%x)\n", this, hwnd31,hwndDropTarget)); _pDropTarget = NULL; HRESULT hr = E_FAIL; if (hwndDropTarget) { HWND hwndClipWindow; Assert(GetProp(hwndDropTarget, (LPCWSTR)g_aDropTarget)); // If the DropTarget hasn't been marshaled, Marshal it now. if (hwndClipWindow = (HWND) GetProp(hwndDropTarget,(LPCWSTR) g_aDropTargetMarshalHwnd)) { SSSendMessage(hwndClipWindow,WM_OLE_CLIPBRD_MARSHALDROPTARGET,0,(LPARAM) hwndDropTarget); } hr = PrivDragDrop(hwndDropTarget, DRAGOP_ENTER, _DOBuffer, _pDataObject, _grfKeyState, _cpt.GetPOINTL(), _pdwEffect, NULL, &hDDInfo); if (hr != NOERROR) { hwndDropTarget = NULL; } } Assert( (NULL == hwnd31) || IsWin31DropTarget(hwnd31)); if( hwndDropTarget || hwnd31 ) { ptarget = new CDropTarget(hwnd31, hwndDropTarget, *_pdwEffect, this, hDDInfo); if( ptarget == NULL ) { hr = E_OUTOFMEMORY; } else { hr = NOERROR; } // if we have a Win31 drop target AND the OLE drop target returned // DROPEFFECT_NONE, then we should return DROPEFFECT_COPY if( hr == NOERROR && *_pdwEffect == DROPEFFECT_NONE && hwnd31 ) { *_pdwEffect = DROPEFFECT_COPY; } _pDropTarget = ptarget; } DDDebugOut((DEB_ITRACE, "%p OUT GetDropTarget ( %lx ) [ %p ]\n", this, hr, _pDropTarget)); return hr; } //+------------------------------------------------------------------------- // // Function: CDragOperation::CDragOperation // // Synopsis: Initialize the object to start the operation // // Arguments: [pDataObject] - pointer to data object to drop // [pDropSource] - pointer to source for drop operation // [dwOKEffects] - effects allowed in drag operation // [pdwEffect] - how operation affected source data // [hr] - whether constructor succeeded // // Algorithm: Initialize data in object. Make sure that static data // is initialized. Wait for first mouse message to begin. // // History: dd-mmm-yy Author Comment // 20-Oct-94 alexgo added support for Win31 drag drop // 04-Apr-94 Ricksa Created // //-------------------------------------------------------------------------- CDragOperation::CDragOperation( LPDATAOBJECT pDataObject, LPDROPSOURCE pDropSource, DWORD dwOKEffects, DWORD FAR *pdwEffect, HRESULT& hr) : _pDataObject(pDataObject), _DOBuffer(NULL), _pDropSource(pDropSource), _pDropTarget(NULL), _pRealDropTarget(NULL), _hFormats(NULL), _dwOKEffects(dwOKEffects), _pdwEffect(pdwEffect), _fEscapePressed(FALSE), _curOld(GetCursor()), _hwndLast((HWND) -1), _grfKeyState(0), _hrDragResult(S_OK), _fReleasedCapture(FALSE), _pcddcDefault(NULL), _fUseWin31(FALSE) { VDATEHEAP(); // Set the default scroll interval if (s_wScrollInt < 0) { InitScrollInt(); } hr = GetMarshalledInterfaceBuffer(IID_IDataObject, pDataObject, &_DOBuffer); if( hr != NOERROR ) { Assert(NULL == _DOBuffer); return; } // Get appropriate default cursor table object if ((_pcddcDefault = CDragDefaultCursors::GetDefaultCursorObject()) == NULL) { // Some error occurred while we were trying to initialize the // so return an error. This should be highly unusual. DDDebugOut((DEB_ERROR, "CDragDefaultCursors::GetDefaultCursorObject Failed!\n")); hr = E_FAIL; return; } // We will use the clipboard window to capture the mouse but we // must have a clipboard window so we make sure it is created // if it is not already there. hr = ClipSetCaptureForDrag(this); if (FAILED(hr)) { return; } _hFormats = CreateSharedDragFormats(pDataObject); // it's OK for _hFormats to be NULL (indicates an empty or non-existant // formatetc enumertor // For following peek MSG msg; // Busy wait until a mouse or escape message is in the queue while (!PeekMessage(&msg, 0, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE)) { // Note: all keyboard messages except escape are tossed. This is // fairly reasonable since the user has to be holding the left // mouse button down at this point. They can't really be doing // too much data input one handed. if ((PeekMessage(&msg, 0, WM_KEYDOWN, WM_KEYDOWN, PM_REMOVE) || PeekMessage(&msg, 0, WM_SYSKEYDOWN, WM_SYSKEYDOWN, PM_REMOVE)) && msg.wParam == VK_ESCAPE) { _fEscapePressed = TRUE; break; } } // get mouse pos and key state if (!_fEscapePressed) { _cpt.Set(msg.pt.x, msg.pt.y); _grfKeyState = GetControlKeysStateOfParam(msg.wParam); } else { // We ask the cursor for its position since we didn't get a // position from the mouse. GetCursorPos(_cpt.GetAddressOfPOINT()); _grfKeyState = GetControlKeysState(TRUE); } // Check to see if we need to do Win3.1 style drag drop. // If we do, then set a flag so we can construct a fake drop target as // needed if( UseWin31DragDrop(pDataObject) ) { _fUseWin31 = TRUE; } } //+------------------------------------------------------------------------- // // Function: ~CDragOperation // // Synopsis: Clean up object // // Algorithm: Release mouse capture. Restore ole cursor. Remove enum // formats. // // History: dd-mmm-yy Author Comment // 04-Apr-94 Ricksa Created // //-------------------------------------------------------------------------- CDragOperation::~CDragOperation(void) { VDATEHEAP(); AssertSz((_pDropTarget == NULL), "CDragOperation::~CDragOperation"); // Stop the mouse capture ReleaseCapture(); // Restore the cursor if it got changed SetCursor(_curOld); // Close the handle to the shared memory if (_hFormats) { CloseHandle(_hFormats); _hFormats = NULL; } if( _DOBuffer ) { ReleaseMarshalledInterfaceBuffer(_DOBuffer); } } //+------------------------------------------------------------------------- // // Function: CDragOperation::InitScrollInt // // Synopsis: Initialize the scroll interval // // Algorithm: Look in profile for defined interval. If none set, then // default to zero. // // History: dd-mmm-yy Author Comment // 04-Apr-94 Ricksa Created // //-------------------------------------------------------------------------- void CDragOperation::InitScrollInt(void) { DWORD dw; OLECHAR szBuffer[20]; s_wScrollInt = DD_DEFSCROLLDELAY; dw = sizeof(szBuffer); if (ERROR_SUCCESS == RegQueryValueEx(HKEY_CURRENT_USER, OLESTR("Control Panel\\Mouse\\DragScrollDelay"), NULL, NULL, (LPBYTE)szBuffer, &dw)) { s_wScrollInt = wcstol(szBuffer, NULL, 0); } } //+------------------------------------------------------------------------- // // Function: CDragOperation::UpdateTarget // // Synopsis: Update the target window based on mouse location // // Returns: TRUE - continue drag operation // FALSE - error or time to drop // // Algorithm: First, we query the source to see if it wants to continue // with the drop. If so, we get current window for mouse. If // it is different than the previous window check to see whether // the targets are different. If they are different, then notify // the current target that we are leaving and then notify the // new target that we have arrived. // // History: dd-mmm-yy Author Comment // 04-Apr-94 Ricksa Created // 10-Jul-94 AlexT Allow same IDropTarget on different HWNDs // //-------------------------------------------------------------------------- BOOL CDragOperation::UpdateTarget(void) { VDATEHEAP(); DDDebugOut((DEB_ITRACE, "%p _IN CDragOperation::UpdateTarget ( )\n", this)); // Assume this operation will continue the drag drop BOOL fResult = TRUE; HRESULT hr; LPDROPTARGET lpCurDropTarget = NULL, lpOldDropTarget = NULL; HWND hWndTemp = NULL; HWND hwndCur = WindowFromPoint(_cpt.GetPOINT()); // Query continue can return telling us one of four things: // (1) Keep going (S_OK), (2) Drop operation should occur // (DRAGDROP_S_DROP), (3) Drop operation is canceled // (DRAGDROP_S_CANCEL) or (4) An unexpected error has occurred. HRESULT hrQuery = _pDropSource->QueryContinueDrag(_fEscapePressed, _grfKeyState); if (FAILED(hrQuery) || (hrQuery == ResultFromScode(DRAGDROP_S_CANCEL))) { // Unexpected error or the operation has been cancelled so give up. _hrDragResult = hrQuery; fResult = FALSE; goto UpdateTarget_exit; } // walk up the window list to find the actual pointer values for the current // and old IDropTarget interfaces if (hwndCur != _hwndLast) { hWndTemp = _hwndLast; BOOL fChangedWin31 = FALSE; HWND hWndOldDrop = NULL; HWND hWndNewDrop = NULL; HWND hWndWin31Drop = NULL; LPDROPTARGET lpRealDropTarget = NULL; HANDLE hTemp = NULL; DWORD dwCurrentProcessId = 0; if (hWndTemp != (HWND)-1) GetWindowThreadProcessId(hWndTemp, &dwCurrentProcessId); DWORD dwTempProcessID = dwCurrentProcessId; while (hWndTemp && !lpRealDropTarget && hWndTemp != (HWND)-1 && dwTempProcessID == dwCurrentProcessId) { if (lpRealDropTarget = (IDropTarget *)GetProp(hWndTemp, (LPCWSTR)g_aDropTarget)) { hWndOldDrop = hWndTemp; } hWndTemp = GetParent(hWndTemp); if (hWndTemp) { GetWindowThreadProcessId(hWndTemp, &dwTempProcessID); } } hWndTemp = hwndCur; if (hWndTemp != (HWND)-1) GetWindowThreadProcessId(hWndTemp, &dwCurrentProcessId); dwTempProcessID = dwCurrentProcessId; while (hWndTemp && dwTempProcessID == dwCurrentProcessId) { // If we haven't found the DropTarget yet, check this window. if (!lpCurDropTarget) { if (lpCurDropTarget = (IDropTarget *)GetProp(hWndTemp, (LPCWSTR)g_aDropTarget)) { hWndNewDrop = hWndTemp; } } // if the current window is a win31 drop target, update the win31 window // handle in our DropTarget Class. NOTE: Beware, this code relies on the // fact that we can party on the CDropTarget Class directly, knowing that // the class is reconstructed below as a result of the GetDropTarget() // when the real IDropTarget ptrs change. if (!fChangedWin31 && IsWin31DropTarget(hWndTemp) && _fUseWin31) { fChangedWin31 = TRUE; hWndWin31Drop = hWndTemp; if (_pDropTarget) { ((CDropTarget*)_pDropTarget)->_hwnd31 = hWndTemp; } } // if have a droptarget, and handle Win31 break. if (lpCurDropTarget && (!_fUseWin31 || fChangedWin31)) { break; } hWndTemp = GetParent(hWndTemp); if (hWndTemp) { GetWindowThreadProcessId(hWndTemp, &dwTempProcessID); } } // only update the drop target if the target has actually changed. // HACK ALERT: We must explicitly check _hwndLast for -1 because Excel does not // use OLE drag drop internally. When the cursor is moved outside the Overlapped // Excel window, DoDragDrop is called. At this point _pRealDropTarget == NULL // and lpCurDropTarget == NULL, and the no-smoking cursor does not appear. // the _pRealDropTarget==NULL relies on the fact that lpCurDropTarget==NULL. This // is true because the first case would short-circuit the rest of the condition // otherwise if ( (lpCurDropTarget != _pRealDropTarget) || (_hwndLast == (HWND)-1) || (hWndNewDrop != hWndOldDrop) || (_pRealDropTarget == NULL)) { DDDebugOut((DEB_ITRACE, "%p lpCurDropTarget != lpOldDropTarget\n", this)); // The window that we are working on has changed _hwndLast = hwndCur; _pRealDropTarget = lpCurDropTarget; //Allow the owner of the window to take foreground if it tries to. if (dwCurrentProcessId) AllowSetForegroundWindow(dwCurrentProcessId); // Assume that neither current or previous window are drop aware BOOL fCurAndLastNotDropAware = TRUE; if (_pDropTarget != NULL) { // There was a previous drop target // Last window was drag/drop aware fCurAndLastNotDropAware = FALSE; // Tell the drop target we are leaving & release it _pDropTarget->DragLeave(); _pDropTarget->Release(); _pDropTarget = NULL; } // Set up effects for query of target *_pdwEffect = _dwOKEffects; hr = GetDropTarget(hWndWin31Drop,hWndNewDrop); if (_pDropTarget != NULL) { // This window is drop awarre fCurAndLastNotDropAware = FALSE; // Errors from this call are ignored. We interpret them // as the drop being disallowed. Since we don't really // use this information here but in the DragOver call // we make shortly, we just use this call to notify // the application that we are beginning a drag operation. if (!HandleFeedBack(hr)) { goto UpdateTarget_exit; } } else { // Tell the source that nothing happened // only use DROPEFFECT_NONE if there is no new drop target. hr = _pDropSource->GiveFeedback(*_pdwEffect = DROPEFFECT_NONE); if (hr != NOERROR) { if (DRAGDROP_S_USEDEFAULTCURSORS == GetScode(hr)) { _pcddcDefault->SetCursorNone(); } else { // Unexpected error -- we will give up drag/drop. DDDebugOut((DEB_ERROR, "CDragOperation::UpdateTarget 1st GiveFeedback FAILED %x\n", hr)); _hrDragResult = hr; fResult = FALSE; goto UpdateTarget_exit; } } } if (fCurAndLastNotDropAware) { // Neither new or old window know about drag/drop so set // cursor accordingly. _pcddcDefault->SetCursorNone(); } } else { // The window that we are working on has changed _hwndLast = hwndCur; } } if (hrQuery != NOERROR) { // Query asked for a drop fResult = FALSE; _hrDragResult = hrQuery; } UpdateTarget_exit: DDDebugOut((DEB_ITRACE, "%p OUT CDragOperation::UpdateTarget ( %lx )\n", this, fResult)); return fResult; } //+------------------------------------------------------------------------- // // Function: CDragOperation::HandleFeedBack // // Synopsis: Handle feedback and update of cursor // // Arguments: [hr] - hresult from previous operation on drop target. // // Returns: TRUE - continue drag operation // FALSE - error // // Algorithm: If previous operation on the target failed, map this to a // disallowed drop. Then ask the source for feedback. If it // so requests, then update the cursor. If an unexpected // error occurs, let caller know that loop should break. // // History: dd-mmm-yy Author Comment // 19-Apr-94 Ricksa Created // //-------------------------------------------------------------------------- BOOL CDragOperation::HandleFeedBack(HRESULT hr) { VDATEHEAP(); DDDebugOut((DEB_ITRACE, "%p _IN CDragOperation::HandleFeedBack ( %x )\n", this, hr)); BOOL fResult = TRUE; if (hr != NOERROR) { // target not responding for some reason; treat // as if drop not possible, but don't preserve // the reason why. *_pdwEffect = DROPEFFECT_NONE; } // If bogus return from drag over, then make sure results are appropriate. // However, if we are in a WOW we need to do things a little differently // to maintain complete compatability with Win 3.1. In 16-bit OLE 2.0, // the *_pdwEffect value is not changed when displaying feedback (i.e., // the result of the & is not stored back into *_pdwEffect in Win 3.1... // in straight NT we do). Not storing the results back into *_pdwEffect // when InWow() is a hack specifically for Visio, and even more // specifically, for dragging from Visio's palette of "items" to an // Excel spreadsheet. if (IsWOWThread()) { hr = _pDropSource->GiveFeedback( *_pdwEffect & (_dwOKEffects | DROPEFFECT_SCROLL)); } else { *_pdwEffect &= (_dwOKEffects | DROPEFFECT_SCROLL); hr = _pDropSource->GiveFeedback(*_pdwEffect); } if(hr != NOERROR) { // Either we want to change the cursor or some unexpected // error has occurred. if (DRAGDROP_S_USEDEFAULTCURSORS == GetScode(hr)) { _pcddcDefault->SetCursor(*_pdwEffect); } else { DDDebugOut((DEB_ERROR, "CDragOperation::HandleFeedBack Feedback FAILED %x\n", hr)); fResult = FALSE; _hrDragResult = hr; } } DDDebugOut((DEB_ITRACE, "%p OUT CDragOperation::HandleFeedBack ( %lx )\n", this, fResult)); return fResult; } //+------------------------------------------------------------------------- // // Function: CDragOperation::DragOver // // Synopsis: Tell the target we are dragging over and process the result // // Returns: TRUE - continue drag operation // FALSE - error or time to drop // // Algorithm: Call the target's drag over if there is one and then // get the sources feedback to update the cursor accordingly. // // History: dd-mmm-yy Author Comment // 04-Apr-94 Ricksa Created // //-------------------------------------------------------------------------- BOOL CDragOperation::DragOver(void) { VDATEHEAP(); DDDebugOut((DEB_ITRACE, "%p _IN CDragOperation::DragOver ( )\n", this)); // Default the result of the function to continue the loop for // drag and drop. BOOL fResult = TRUE; // Local holder for errors. HRESULT hr; if (_pDropTarget != NULL) { // Keep effect in a local variable to save indirections // in this routine. *_pdwEffect = _dwOKEffects; hr = _pDropTarget->DragOver(_grfKeyState, _cpt.GetPOINTL(), _pdwEffect); // Get feedback from source & update cursor if necessary fResult = HandleFeedBack(hr); } DDDebugOut((DEB_ITRACE, "%p OUT CDragOperation::DragOver ( %lx )\n", this, fResult)); return fResult; } //+------------------------------------------------------------------------- // // Function: CDragOperation::HandleMessages // // Synopsis: Handle windows messages // // Returns: TRUE - continue drag operation // FALSE - error or time to drop // // Algorithm: Check for any windows message. If the message is a mouse // message then record the new position of the mouse. If it // is a key message, the record whether escape has been pushed. // If this is any other message, then dispatch it. Repeat this // process until the scroll interval has been exceeded. // // History: dd-mmm-yy Author Comment // 04-Apr-94 Ricksa Created // //-------------------------------------------------------------------------- BOOL CDragOperation::HandleMessages(void) { VDATEHEAP(); DDDebugOut((DEB_ITRACE, "%p _IN CDragOperation::HandleMessages ( )\n", this)); // Message buffer MSG msg; // Default result of function to continue BOOL fResult = TRUE; // Capture all messages (i.e. modal loop). // Process all input messages, dispatch other messages // // Note:we must NOT loop here until a hardware message comes in // scrolling will not work. // * yielding is important since other apps need to run // * look for mouse messages first since these are the most // impotant // Flag for whether we peeked a message BOOL fMsg; // // Sundown - The SetTimer return value can be truncated. // We are passing NULL as HWND and Win32 will returned // a value not greater than 4GB... // If a check is required we could consider a temporary // UINT_PTR value and do an ASSERT on its value... // UINT uTimer = (UINT)SetTimer(NULL, 0, s_wScrollInt, NULL); do { fMsg = FALSE; // Note: the order of peek is important - further messages can show up // in the last peek // If we looked for mouse messages first, we might never pick up // WM_QUIT or keyboard messages (because by the time we finished // processing the mouse message another might be on the queue). // So, we check for WM_QUIT and keyboard messages first. if (PeekMessage(&msg, 0, WM_QUIT, WM_QUIT, PM_REMOVE | PM_NOYIELD) || PeekMessage(&msg, 0, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE | PM_NOYIELD) || PeekMessage(&msg, 0, WM_SYSKEYDOWN, WM_SYSKEYUP, PM_REMOVE | PM_NOYIELD) || PeekMessage(&msg, 0, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE) || PeekMessage(&msg, 0, WM_NCMOUSEFIRST, WM_NCMOUSELAST, PM_REMOVE | PM_NOYIELD) || PeekMessage(&msg, 0, 0, 0, PM_REMOVE | PM_NOYIELD)) { fMsg = TRUE; if (msg.message == WM_QUIT) { // Quit message so we are done. PostQuitMessage((int) msg.wParam); // We are going exiting so the error doesn't matter too much _hrDragResult = ResultFromScode(E_UNSPEC); // Make sure we break out of the loop fResult = FALSE; } else if ((msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST) || (msg.message >= WM_SYSKEYDOWN && msg.message <= WM_SYSKEYUP)) { // Pull all keyboard messages from the queue - this keeps // the keyboard state in sync with the user's actions // We use a do/while so that we process the message we've // already peeked. do { // We only really pay attention to the escape key and dump // any other key board messages. if ((msg.message == WM_KEYDOWN || msg.message == WM_SYSKEYDOWN) && msg.wParam == VK_ESCAPE) { // Esc pressed: Cancel _fEscapePressed = TRUE; } } while (PeekMessage(&msg, 0, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE | PM_NOYIELD) || PeekMessage(&msg, 0, WM_SYSKEYDOWN, WM_SYSKEYUP, PM_REMOVE | PM_NOYIELD)); DWORD grfKeyState; // temp variable for key state // get the key state don't change the button states!! grfKeyState = GetControlKeysState(FALSE) | (_grfKeyState & (MK_LBUTTON | MK_RBUTTON | MK_MBUTTON)); // if the keyboard state is unchanged, then don't exit // this loop (as that will result in DragOver being called). // If we call DragOver for each keyboard message, then // performance is unacceptably slow. if ((grfKeyState == _grfKeyState) && !_fEscapePressed) { fMsg = FALSE; } else { DDDebugOut((DEB_ITRACE, "Updating key state\n")); _grfKeyState = grfKeyState; } } else if (msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST) { // we may not have the focus (e.g. if we are the Chicago // shell). Therefore, we won't ever get any WM_KEYDOWN // messages. Double check the esc key status here if( GetKeyState(VK_ESCAPE) < 0 ) { _fEscapePressed = TRUE; } // We got a mouse move message - we skip all the mouse messages // till we get to the last one. The point here is that // because of the length of DragOver calls, we can get behind // in processing messages which causes odd things to happen // on the screen. if (WM_MOUSEMOVE == msg.message) { MSG msg2; // Keep processing mouse move messages till there // aren't any more. // if PeekMessage returns true update the original msg. while(PeekMessage(&msg2, 0, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_REMOVE)) { msg = msg2; } } // Record position of the mouse _cpt.Set(msg.pt.x, msg.pt.y); // set mouse button state here _grfKeyState = GetControlKeysStateOfParam(msg.wParam); } else if (msg.message >= WM_NCMOUSEFIRST && msg.message <= WM_NCMOUSELAST) { // Nothing we need to do for these NC mouse actions NULL; } else if ( (msg.message == WM_TIMER) && (msg.wParam == uTimer) ) { // Our timer was triggered. We need to recheck the keyboard // state just in case it has changed. This is important for // the Chicago shell--if it doesn't have focus, then we won't // get any WM_KEYDOWN message (just mouse moves). _grfKeyState = GetControlKeysState(FALSE) | (_grfKeyState & (MK_LBUTTON | MK_RBUTTON | MK_MBUTTON)); if( GetKeyState(VK_ESCAPE) < 0 ) { _fEscapePressed = TRUE; } // go ahead and fall out of the loop so we call DragOver // (our timeout expired). } else { // Dispatch all other messages DispatchMessage(&msg); fMsg = FALSE; } } else { WaitMessage(); } // we have to leave the loop periodicially since apps // might rely on on it the DragOver is called freqeuntly. } while (!fMsg); // Get rid of the timer we created for the loop KillTimer(NULL, uTimer); DDDebugOut((DEB_ITRACE, "%p OUT CDragOperation::HandleMessages ( %lx )\n", this, fResult)); return fResult; } //+------------------------------------------------------------------------- // // Function: CDragOperation::CompleteDrop // // Synopsis: Complete the drag/drop operation // // Returns: Result of operation // // Algorithm: If there is a target and we have decided to drop, then // drop. Otherwise, release the target and return whatever // the other result of the operation was. // // History: dd-mmm-yy Author Comment // 04-Apr-94 Ricksa Created // //-------------------------------------------------------------------------- HRESULT CDragOperation::CompleteDrop(void) { VDATEHEAP(); DDDebugOut((DEB_ITRACE, "%p _IN CDragOperation::CompleteDrop ( )\n", this)); // Stop the mouse capture in case a dialog box is thrown up. ReleaseCapture(); if (_pDropTarget != NULL) { // Caller is Drag/Drop aware // and indicated it might accept drop // The drop source replies DRAG_S_DROP if the user has // released the left mouse button. However, we may be over // a drop target which has refused a drop (via the feedback // DROPEFFECT_NONE). Thus, both the drop source and drop // target need to agree before we commit the drop. if ((DRAGDROP_S_DROP == GetScode(_hrDragResult)) && (*_pdwEffect != DROPEFFECT_NONE)) { // We are going to try to drop *_pdwEffect = _dwOKEffects; HRESULT hr = _pDropTarget->Drop(_pDataObject, _grfKeyState, _cpt.GetPOINTL(), _pdwEffect); if (FAILED(hr)) { // If drop actually failed in the last stage, let the // caller know that this happened. _hrDragResult = hr; } } else { *_pdwEffect = DROPEFFECT_NONE; _pDropTarget->DragLeave(); } _pDropTarget->Release(); _pDropTarget = NULL; } else { *_pdwEffect = DROPEFFECT_NONE; } DDDebugOut((DEB_ITRACE, "%p OUT CDragOperation::CompleteDrop ( %lx )\n", this, _hrDragResult)); return _hrDragResult; } //+------------------------------------------------------------------------- // // Function: RegisterDragDrop // // Synopsis: Registers a drop target // // Arguments: [hwnd] -- a handle to the drop target window // [pDropTarget] -- the IDropTarget interface for the window // // Returns: HRESULT // // Algorithm: We ask compobj (via AssignEndpoinProperty) to put an // endpoint ID publicly available on the window handle. Then // we put the IDropTarget pointer on the window as a private // property (see the notes at the beginning of this file). // // History: dd-mmm-yy Author Comment // 06-Apr-94 ricksa Added tracing // 16-Jan-94 alexgo pDropTarget is now AddRef'ed // 11-Jan-94 alexgo added VDATEHEAP, converted to RPC-style // drag drop. // 06-Dec-93 alexgo commented // // Notes: By AddRef'ing the pDropTarget pointer, we are changing // the semantics of the 16bit code (which did not do an // AddRef). // //-------------------------------------------------------------------------- #pragma SEG(RegisterDragDrop) STDAPI RegisterDragDrop(HWND hwnd, LPDROPTARGET pDropTarget) { HRESULT hresult = NOERROR; BOOL fDelayDrop = FALSE; VDATEHEAP(); OLETRACEIN((API_RegisterDragDrop, PARAMFMT("hwnd= %h, pDropTarget= %p"), hwnd, pDropTarget)); DDDebugOut((DEB_ITRACE, "%p _IN RegisterDragDrop ( %lx %p )\n", NULL, hwnd, pDropTarget)); if (!IsValidInterface(pDropTarget)) { hresult = E_INVALIDARG; } else if (!IsWindow(hwnd)) { hresult = DRAGDROP_E_INVALIDHWND; } else { CALLHOOKOBJECT(S_OK,CLSID_NULL,IID_IDropTarget,(IUnknown **)&pDropTarget); if (GetProp(hwnd, (LPCWSTR)g_aDropTarget)) { hresult = DRAGDROP_E_ALREADYREGISTERED; } else if (!SetProp(hwnd, (LPCWSTR)g_aDropTarget, (HANDLE)pDropTarget)) { hresult = E_OUTOFMEMORY; } else { DWORD dwAssignAptID; Win4Assert(NOERROR == hresult); // HACK: We need to add this atom every time RegisterDragDrop // is called because 16-bit Word does not call RevokeDragDrop // and user will automatically clean-up this atom if Word is the // first app run, and then exited before another app calls // RegisterDragDrop. g_aEndPointAtom = GlobalAddAtom(ENDPOINT_PROP_NAME); // See if Delayed Drop can be set up. fDelayDrop = FALSE; if (g_aDropTargetMarshalHwnd && IsApartmentInitialized()) { HWND hwndClipboard = GetPrivateClipboardWindow(CLIP_CREATEIFNOTTHERE); if (hwndClipboard) { fDelayDrop = SetProp(hwnd,(LPCWSTR) g_aDropTargetMarshalHwnd,hwndClipboard); } } // if can't delay marshal then marshal immediately. if (!fDelayDrop) { hresult = AssignEndpointProperty(hwnd); } if (NOERROR == hresult) { pDropTarget->AddRef(); } else { // We don't free h. It's not a handle at all. HANDLE h = RemoveProp(hwnd, (LPCWSTR)g_aDropTarget); } } } DDDebugOut((DEB_ITRACE, "%p OUT RegisterDragDrop ( %lx )\n", NULL, hresult)); OLETRACEOUT((API_RegisterDragDrop, hresult)); return hresult; } //+------------------------------------------------------------------------- // // Function: RevokeDragDrop // // Synopsis: Unregisters a window as a drop target // // Arguments: [hwnd] -- the window to unregister // // Returns: HRESULT // // Algorithm: Removes the two window properties set by // RegisterDragDrop // // History: dd-mmm-yy Author Comment // 06-Apr-94 ricksa added tracing // 16-Jan-94 alexgo added a Release to the drag drop // pointer to match the AddRef in // RegisterDragDrop. // 11-Jan-94 alexgo converted to RPC-style drag drop, // added VDATEHEAP macro // 06-Dec-93 alexgo commented // // Notes: the DropTarget->Release call changes the semantics of // this function from the 16bit version (see Notes: for // RegisterDragDrop). // //-------------------------------------------------------------------------- #pragma SEG(RevokeDragDrop) STDAPI RevokeDragDrop(HWND hwnd) { HRESULT hr = NOERROR; LPDROPTARGET pDropTarget; BOOL fReleaseDropTarget = TRUE; VDATEHEAP(); OLETRACEIN((API_RevokeDragDrop, PARAMFMT("hwnd= %h"), hwnd)); DDDebugOut((DEB_ITRACE, "%p _IN RevokeDragDrop ( %lx )\n", NULL, hwnd)); if (!IsWindow(hwnd)) { hr = DRAGDROP_E_INVALIDHWND; } else if ((pDropTarget = (LPDROPTARGET)RemoveProp(hwnd, (LPCWSTR)g_aDropTarget)) == NULL) { hr = DRAGDROP_E_NOTREGISTERED; } else { fReleaseDropTarget = TRUE; if (GetProp(hwnd, (LPCWSTR) g_aEndPointAtom)) // see if there is an endpoint. { DWORD dwAssignAptID; // Ask compobj to remove the endpoint ID it placed on the window. if(SUCCEEDED(UnAssignEndpointProperty(hwnd,&dwAssignAptID))) { // Note: AptID == ThreadID in Apartment model. if( (dwAssignAptID != GetCurrentThreadId()) && (IsApartmentInitialized()) ) { fReleaseDropTarget = FALSE; } } Win4Assert(NULL == GetProp(hwnd,(LPCWSTR) g_aDropTargetMarshalHwnd)); } else { HWND hwndClipbrd; hwndClipbrd = (HWND) RemoveProp(hwnd,(LPCWSTR) g_aDropTargetMarshalHwnd); Win4Assert(hwndClipbrd); fReleaseDropTarget = (IsApartmentInitialized() && (hwndClipbrd != GetPrivateClipboardWindow(CLIP_QUERY )) ) ? FALSE : TRUE; } // Release our reference to the object since we are no longer using it. // NOTE: AddRef came from RegisterDragDrop // Warning: Only call Release if we are in the same thread that Registered the DropTarget // Or we are FreeThreading. // This mirrors the atom added in RegisterDragDrop GlobalDeleteAtom(g_aEndPointAtom); if (fReleaseDropTarget) { pDropTarget->Release(); hr = NOERROR; // Always return NOERROR even if UnAssignEndPoint Failed } else { LEDebugOut((DEB_WARN, "WARNING:Revoke Called on Different Thread than Register!!\n")); hr = RPC_E_WRONG_THREAD; } } DDDebugOut((DEB_ITRACE, "%p OUT RegisterDragDrop ( %lx )\n", NULL, hr)); OLETRACEOUT((API_RevokeDragDrop, hr)); return hr; } //+------------------------------------------------------------------------- // // Function: DoDragDrop // // Synopsis: The main drag'n'drop loop // // Effects: // // Arguments: [pDataObject] -- the object to drag // [pDropSource] -- the drop source // [dwOKEffects] -- effects flags (stuff to draw) // [pdwEffect] -- what actually happened in // the drag drop attempt // // Requires: // // Returns: // // Signals: // // Modifies: // // Algorithm: See the notes at the beginning of the file // // History: dd-mmm-yy Author Comment // 25-Nov-96 gopalk Fail the call if OleInitialize has not // been called // 05-Dec-94 JohannP added stack switching for WIN95 // 11-Jan-94 alexgo added VDATEHEAP macro, converted to // the RPC-style drag drop. // 31-Dec-93 erikgav chicago port // 06-Dec-93 alexgo formatted // // Notes: Under Win95 SSAPI(DoDragDrop) gets expanded to SSDoDragDrop. // This function is called by DoDragDrop (in stkswtch.cxx) // which switches to the 16 bit stack first. // IMPORTANT: this function has to be executed on the 16 bit // since call back via USER might occur. //-------------------------------------------------------------------------- #pragma SEG(DoDragDrop) STDAPI SSAPI(DoDragDrop)(LPDATAOBJECT pDataObject, LPDROPSOURCE pDropSource, DWORD dwOKEffects, DWORD *pdwEffect) { OLETRACEIN((API_DoDragDrop, PARAMFMT("pDataObject=%p, pDropSource=%p, dwOKEffects=%x, pdwEffect=%p"), pDataObject, pDropSource, dwOKEffects, pdwEffect)); DDDebugOut((DEB_ITRACE, "%p _IN DoDragDrop (%p %p %lx %p )\n", NULL, pDataObject, pDropSource, dwOKEffects, pdwEffect)); HRESULT hr = NOERROR; #ifndef _MAC // Validation checks VDATEHEAP(); CALLHOOKOBJECT(S_OK,CLSID_NULL,IID_IDataObject,(IUnknown **)&pDataObject); CALLHOOKOBJECT(S_OK,CLSID_NULL,IID_IDropSource,(IUnknown **)&pDropSource); if(!IsValidPtrOut(pdwEffect, sizeof(DWORD)) || !IsValidInterface(pDropSource) || !IsValidInterface(pDataObject)) hr = E_INVALIDARG; // Check if the thread has called oleinitialize if(!IsOleInitialized()) hr = CO_E_NOTINITIALIZED; if(hr == NOERROR) { // Create the object that does all the work. CDragOperation drgop(pDataObject, pDropSource, dwOKEffects, pdwEffect, hr); // Did the constructor succeeded? if(SUCCEEDED(hr)) { // Loop till worker object tells us to stop for(;;) { // Update target based on new window position if(!drgop.UpdateTarget()) { // Error so we are done break; } // Notify if(!drgop.DragOver()) { break; } // Handle any messages we get in the mean time if(!drgop.HandleMessages()) { break; } } // end for loop hr = drgop.CompleteDrop(); } } #endif // !_MAC DDDebugOut((DEB_ITRACE, "%p OUT DoDragDrop ( %lx )\n", NULL, hr)); OLETRACEOUT((API_DoDragDrop, hr)); return hr; } //+------------------------------------------------------------------------- // // Member: CDropTarget::CDropTarget // // Synopsis: constructor for the CDropTarget class // // Effects: // // Arguments: [hwnd31] -- the hwnd of the Win3.1 drop target // may be NULL // [hwndOLE] -- the hwnd of the OLE drop target // [dwEffectLast] -- the last effect given the the current // drop target that we are to emulate // [pdo] -- a pointer to the main drag drop class // [hDDInfo] -- handle to cached drag drag info // // // Requires: hwnd31 *must* be a handle to a valid Win3.1 drop source // // Returns: void // // Signals: // // Modifies: // // Derivation: // // Algorithm: initializes variables // // History: dd-mmm-yy Author Comment // 20-Oct-94 alexgo author // 08-Jan-95 // // Notes: there are two ways of determining if a given hwnd is // a valid Win3.1 drop target: // 1. send a WM_QUERYDROPOBJECT message for a TRUE/FALSE // reply // 2. check the extended style bits for WS_EX_ACCEPTFILES // // if ptarget is non-NULL, then the specific window to which // it belongs is *not* guaranteed to be the same window as // hwndtarget. hwndtarget is the window that is registered as // a Win3.1 target. All that is guaranteed is that the ole // target and hwndtarget are in the same window hierarchy. // //-------------------------------------------------------------------------- CDropTarget::CDropTarget( HWND hwnd31, HWND hwndOLE, DWORD dwEffectLast, CDragOperation *pdo, DDInfo hDDInfo ) { _crefs = 1; _hwndOLE = hwndOLE; _hwnd31 = hwnd31; _hDDInfo = hDDInfo; _dwEffectLast = dwEffectLast; _pdo = pdo; // pointer to the current drag operation class #if DBG ==1 // now do some checking (see Notes above) if( hwnd31 ) { LONG exstyle; exstyle = GetWindowLong(hwnd31, GWL_EXSTYLE); // strictly speaking, an app could process the WM_QUERYDROPOBJECT // message itself (and thus, not set the extended style bits). // However, this should be considered an application bug; the // documentation states that apps should call DragAcceptFiles, // which will set the WS_EX_ACCEPTFILES bit Assert( (exstyle & WS_EX_ACCEPTFILES) ); } #endif // DBG ==1 } //+------------------------------------------------------------------------- // // Member: CDropTarget::~CDropTarget // // Synopsis: frees the cached drag drop info handle // // Effects: // // Arguments: void // // Requires: // // Returns: void // // Signals: // // Modifies: // // Derivation: // // Algorithm: // // History: dd-mmm-yy Author Comment // 08-Jan-95 alexgo author // // Notes: // //-------------------------------------------------------------------------- CDropTarget::~CDropTarget() { if( _hDDInfo ) { FreeDragDropInfo(_hDDInfo); } } //+------------------------------------------------------------------------- // // Member: CDropTarget::QueryInterface // // Synopsis: returns available interfaces on this object // // Effects: // // Arguments: [riid] -- the requested interface // [ppv] -- where to put the interface // // Requires: // // Returns: E_UNEXPECTED // // Signals: // // Modifies: // // Derivation: IDropTarget // // Algorithm: CDropTarget is only used internally by OLE's drag drop code. // It should never do a QI. // // History: dd-mmm-yy Author Comment // 20-Oct-94 alexgo author // // Notes: // //-------------------------------------------------------------------------- STDMETHODIMP CDropTarget::QueryInterface( REFIID riid, LPVOID * ppv ) { (void)riid; // unused; (void)ppv; // unused; AssertSz(0, "Unexpected QI to CDropTarget"); return E_UNEXPECTED; } //+------------------------------------------------------------------------- // // Member: CDropTarget::AddRef // // Synopsis: increments the reference count // // Effects: // // Arguments: void // // Requires: // // Returns: ULONG, the new reference count // // Signals: // // Modifies: // // Derivation: IDropTarget // // Algorithm: // // History: dd-mmm-yy Author Comment // 20-Oct-94 alexgo author // // Notes: // //-------------------------------------------------------------------------- STDMETHODIMP_(ULONG) CDropTarget::AddRef( void ) { VDATEHEAP(); DDDebugOut((DEB_ITRACE, "%p _IN CDropTarget::AddRef ( )\n", this)); _crefs++; DDDebugOut((DEB_ITRACE, "%p OUT CDropTarget::AddRef ( %ld )\n", this, _crefs)); return _crefs; } //+------------------------------------------------------------------------- // // Member: CDropTarget::Release // // Synopsis: decrements the reference count // // Effects: may delete 'this' object // // Arguments: void // // Requires: // // Returns: ULONG, the new reference count // // Signals: // // Modifies: // // Derivation: IDropTarget // // Algorithm: // // History: dd-mmm-yy Author Comment // 20-Oct-94 alexgo author // // Notes: // //-------------------------------------------------------------------------- STDMETHODIMP_(ULONG) CDropTarget::Release( void ) { ULONG crefs; VDATEHEAP(); DDDebugOut((DEB_ITRACE, "%p _IN CDropTarget::Release ( )\n", this)); crefs = --_crefs; if( crefs == 0) { DDDebugOut((DEB_ITRACE, "DELETING CDropTarget %p\n", this)); delete this; } DDDebugOut((DEB_ITRACE, "%p OUT CDropTarget::Release ( %ld )\n", this, crefs)); return crefs; } //+------------------------------------------------------------------------- // // Member: CDropTarget::DragEnter // // Synopsis: sets the window up for drag drop // // Effects: // // Arguments: [pDataObject] -- the data object to drop // [grfKeyState] -- the current keyboard state // [pt] -- the cursor point // [pdwEffect] -- where to return the drag drop effect // // Requires: // // Returns: HRESULT // // Signals: // // Modifies: // // Derivation: IDropTarget // // Algorithm: should never be called. DragEnter is always called // via GetDropTarget // // History: dd-mmm-yy Author Comment // 08-Nov-93 alexgo eliminated // 20-Oct-94 alexgo author // // Notes: // //-------------------------------------------------------------------------- STDMETHODIMP CDropTarget::DragEnter( IDataObject * pDataObject, DWORD grfKeyState, POINTL ptl, DWORD *pdwEffect ) { AssertSz(0, "DragEnter unexpectedly called!"); return E_UNEXPECTED; } //+------------------------------------------------------------------------- // // Member: CDropTarget::DragOver // // Synopsis: called while the mouse is over a given window // // Effects: // // Arguments: [grfKeyState] -- the state of the keyboard // [ptl] -- the position of the cursor // [pdwEffect] -- the drag drop effect // // Requires: // // Returns: NOERROR // // Signals: // // Modifies: // // Derivation: IDropTarget // // Algorithm: If an OLE target is available, then we forward the call. // If the target says DROPEFFECT_NONE, then we go ahead // and return DROPEFFECT_COPY if a Win31 target window is // available. // // If there is no OLE target and we have a Win3.1 target, // then we go ahead and return DROPEFFECT_COPY. // // History: dd-mmm-yy Author Comment // 08-Nov-94 alexgo converted to PrivDragDrop protocol // 20-Oct-94 alexgo author // // Notes: // //-------------------------------------------------------------------------- STDMETHODIMP CDropTarget::DragOver( DWORD grfKeyState, POINTL ptl, DWORD *pdwEffect) { HRESULT hresult = NOERROR; VDATEHEAP(); DDDebugOut((DEB_ITRACE, "%p _IN CDropTarget::DragOver ( %lx , %lx " ", %lx )\n", this, grfKeyState, &ptl, *pdwEffect)); if( _hwndOLE ) { hresult = PrivDragDrop(_hwndOLE, DRAGOP_OVER, NULL, NULL, grfKeyState, ptl, pdwEffect, NULL, &_hDDInfo); _dwEffectLast = *pdwEffect; if( _hwnd31 ) { // we only want to stomp on the effect if the DragOver call // succeeded. If the call failed, then just assume that a // Win3.1 drop would fail as well. if( hresult == NOERROR && *pdwEffect == DROPEFFECT_NONE ) { *pdwEffect = DROPEFFECT_COPY; } } } else if ( _hwnd31 ) { *pdwEffect = DROPEFFECT_COPY; } DDDebugOut((DEB_ITRACE, "%p OUT CDropTarget::DragOver ( %lx ) [ " "%lx ]\n", this, hresult, *pdwEffect)); return hresult; } //+------------------------------------------------------------------------- // // Member: CDropTarget::DragLeave // // Synopsis: called when the cursor leaves the current target window // // Effects: // // Arguments: void // // Requires: // // Returns: NOERROR // // Signals: // // Modifies: // // Derivation: IDropTarget // // Algorithm: Forwards the DragLeave call to the OLE-drop target // (if it exists). // // History: dd-mmm-yy Author Comment // 08-Nov-94 alexgo converted to PrivDragDrop protocol // 20-Oct-94 alexgo author // // Notes: // //-------------------------------------------------------------------------- STDMETHODIMP CDropTarget::DragLeave() { HRESULT hresult = NOERROR; static POINTL ptl = {0, 0}; VDATEHEAP(); DDDebugOut((DEB_ITRACE, "%p _IN CDropTarget::DragLeave ( )\n", this)); if( _hwndOLE ) { hresult = PrivDragDrop(_hwndOLE, DRAGOP_LEAVE, NULL, NULL, NULL, ptl, NULL, NULL, &_hDDInfo); } DDDebugOut((DEB_ITRACE, "%p OUT CDropTarget::DragLeave ( %lx )\n", this, hresult)); return hresult; } //+------------------------------------------------------------------------- // // Member: CDropTarget::Drop // // Synopsis: called if the user lets go of the mouse button while // over a drop target // // Effects: // // Arguments: [pDataObject] -- the data object to use // [grfKeyState] -- the keyboard state // [ptl] -- the current mouse position // [pdwEffect] -- where to return cursor effect feedback // // Requires: // // Returns: HRESULT // // Signals: // // Modifies: // // Derivation: IDropTarget // // Algorithm: If there is an OLE-target available, then we first forward // the drop request to it. If the call fails // (or DROPEFFECT_NONE is returned), then we try the Win31 // drop by posting a WM_DROPFILES message to the Win31 target // window (ifit exists). // // History: dd-mmm-yy Author Comment // 08-Nov-94 alexgo converted to PrivDragDrop protocol // 20-Oct-94 alexgo author // // Notes: // //-------------------------------------------------------------------------- STDMETHODIMP CDropTarget::Drop( IDataObject *pDataObject, DWORD grfKeyState, POINTL ptl, DWORD *pdwEffect ) { STGMEDIUM medium; FORMATETC formatetc; HRESULT hresult = E_FAIL; IFBuffer DOBuffer = _pdo->GetDOBuffer(); VDATEHEAP(); DDDebugOut((DEB_ITRACE, "%p _IN CDropTarget::Drop ( %p , %lx , ", "%p , %lx )\n", this, pDataObject, grfKeyState, &ptl, *pdwEffect)); // we don't forward Drop calls to the target if the last effect // is DROPEFFECT_NONE. It is important that we check for this because // to DoDragDrop 'normally' would not call Drop if the last effect // was DROPEFFECT_NONE. However, this target wrapper will stomp // pdwEffect and return DROPEFFECT_COPY instead of DROPEFFECT_NONE. if( _hwndOLE && _dwEffectLast != DROPEFFECT_NONE ) { hresult = PrivDragDrop(_hwndOLE, DRAGOP_DROP, DOBuffer, pDataObject, grfKeyState, ptl, pdwEffect, GetPrivateClipboardWindow(CLIP_QUERY), &_hDDInfo); } else if( _hwndOLE ) { // if the 'real' drop effect is NONE, then we need to call // DragLeave here before going on to post the WM_DROPFILES // message. Otherwise, the app that is both an OLE and Win31 // and has been returning DROPEFFECT_NONE will never get a // Drop or DragLeave call (which is necessary to terminate // the OLE2 drag protocol). Capone in particular is sensitive // to this. *pdwEffect = DROPEFFECT_NONE; hresult = DragLeave(); } if( (hresult != NOERROR || *pdwEffect == DROPEFFECT_NONE) && (hresult != S_FALSE) && (_hwnd31) ) { medium.tymed = TYMED_NULL; INIT_FORETC(formatetc); formatetc.cfFormat = CF_HDROP; formatetc.tymed = TYMED_HGLOBAL; hresult = pDataObject->GetData(&formatetc, &medium); if( hresult == NOERROR ) { // we need to fixup the mouse point coordinates in the CF_HDROP // data. The point point should be in client coordinates // (whereas IDropTarget::Drop takes screen coordinates) DROPFILES *pdf = (DROPFILES *)GlobalLock(medium.hGlobal); POINT pt; pt.x = ptl.x; pt.y = ptl.y; if( pdf ) { // we also need to set the non-client (NC) flag of the // dropfile data. This lets the app do different behaviour // depending on whether the drop point is in the client or // non-client area (Word6, for example, opens the file if on // non-client area, otherwise makes a package object). pdf->fNC = IsNCDrop(_hwnd31, pt); if( ScreenToClient(_hwnd31, &pt) ) { pdf->pt.x = pt.x; pdf->pt.y = pt.y; } else { LEDebugOut((DEB_WARN, "WARNING: CF_HDROP pt coords" "not updated!!\n")); ; // don't do anything } GlobalUnlock(medium.hGlobal); } else { LEDebugOut((DEB_WARN, "WARNING: OUT OF MEMORY!\n")); ; // don't do anything } if( PostMessage(_hwnd31, WM_DROPFILES, (WPARAM)medium.hGlobal, 0) ) { *pdwEffect = DROPEFFECT_COPY; } else { // PostMessage failed, so free the data ReleaseStgMedium(&medium); *pdwEffect = DROPEFFECT_NONE; } } } DDDebugOut((DEB_ITRACE, "%p OUT CDropTarget::Drop ( %lx ) [ %lx ]\n", this, hresult, *pdwEffect)); return hresult; }