/****************************** Module Header ******************************\ * Module Name: dragdrop.c * * Copyright (c) 1985 - 1999, Microsoft Corporation * * Stuff for object-oriented direct manipulation, designed first for the shell. * * History: * 08-06-91 darrinm Ported from Win 3.1. \***************************************************************************/ #include "precomp.h" #pragma hdrstop PCURSOR xxxQueryDropObject(PWND pwnd, LPDROPSTRUCT lpds); /***************************************************************************\ * DragObject (API) * * Contains the main dragging loop. * * History: * 08-06-91 darrinm Ported from Win 3.1 sources. \***************************************************************************/ DWORD xxxDragObject( PWND pwndParent, PWND pwndFrom, // NULL is valid UINT wFmt, ULONG_PTR dwData, PCURSOR pcur) { MSG msg, msgKey; DWORD result = 0; BOOL fDrag = TRUE; LPDROPSTRUCT lpds; PWND pwndDragging = NULL; PWND pwndTop; PCURSOR pcurOld, pcurT; PWND pwndT; TL tlpwndT; TL tlpwndTop; TL tlpwndDragging; TL tlPool; PTHREADINFO pti = PtiCurrent(); CheckLock(pwndParent); CheckLock(pwndFrom); CheckLock(pcur); UserAssert(IsWinEventNotifyDeferredOK()); lpds = (LPDROPSTRUCT)UserAllocPoolWithQuota(2 * sizeof(DROPSTRUCT), TAG_DRAGDROP); if (lpds == NULL) return 0; ThreadLockPool(pti, lpds, &tlPool); lpds->hwndSource = HW(pwndFrom); lpds->wFmt = wFmt; lpds->dwData = dwData; if (pcur != NULL) { /* * No need to DeferWinEventNotify() - pwndFrom is locked */ pcurOld = zzzSetCursor(pcur); } else { pcurOld = pti->pq->spcurCurrent; } if (pwndFrom) { for (pwndTop = pwndFrom; TestwndChild(pwndTop); pwndTop = pwndTop->spwndParent) ; ThreadLockWithPti(pti, pwndTop, &tlpwndTop); xxxUpdateWindow(pwndTop); ThreadUnlock(&tlpwndTop); } xxxWindowEvent(EVENT_SYSTEM_DRAGDROPSTART, pwndFrom, OBJID_WINDOW, INDEXID_CONTAINER, 0); xxxSetCapture(pwndFrom); zzzShowCursor(TRUE); ThreadLockWithPti(pti, pwndDragging, &tlpwndDragging); while (fDrag && pti->pq->spwndCapture == pwndFrom) { while (!(xxxPeekMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE) || xxxPeekMessage(&msg, NULL, WM_QUEUESYNC, WM_QUEUESYNC, PM_REMOVE) || xxxPeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE))) { if (!xxxSleepThread(QS_MOUSE | QS_KEY, 0, TRUE)) { ThreadUnlock(&tlpwndDragging); ThreadUnlockAndFreePool(pti, &tlPool); return 0; } } /* * Be sure to eliminate any extra keydown messages that are * being queued up by MOUSE message processing. */ while (xxxPeekMessage(&msgKey, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)) ; if ( (pti->pq->spwndCapture != pwndFrom) || (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE) ) { if (pcurT = SYSCUR(NO)) zzzSetCursor(pcurT); break; } RtlCopyMemory(lpds + 1, lpds, sizeof(DROPSTRUCT)); /* * in screen coordinates */ lpds->ptDrop = msg.pt; pcurT = xxxQueryDropObject(pwndParent, lpds); /* * Returning FALSE to a WM_QUERYDROPOBJECT message means drops * aren't supported and the 'illegal drop target' cursor should be * displayed. Returning TRUE means the target is valid and the * regular drag cursor should be displayed. Also, through a bit * of polymorphic magic one can return a cursor handle to override * the normal drag cursor. */ if (pcurT == (PCURSOR)FALSE) { pcurT = SYSCUR(NO); lpds->hwndSink = NULL; } else if (pcurT == (PCURSOR)TRUE) { pcurT = pcur; } if (pcurT != NULL) zzzSetCursor(pcurT); /* * send the WM_DRAGLOOP after the above zzzSetCursor() to allow the * receiver to change the cursor at WM_DRAGLOOP time with a zzzSetCursor() */ if (pwndFrom) { xxxSendMessage(pwndFrom, WM_DRAGLOOP, (pcurT != SYSCUR(NO)), (LPARAM)lpds); } /* * send these messages internally only */ if (pwndDragging != RevalidateHwnd(lpds->hwndSink)) { if (pwndDragging != NULL) { xxxSendMessage(pwndDragging, WM_DRAGSELECT, FALSE, (LPARAM)(lpds + 1)); } pwndDragging = RevalidateHwnd(lpds->hwndSink); ThreadUnlock(&tlpwndDragging); ThreadLockWithPti(pti, pwndDragging, &tlpwndDragging); if (pwndDragging != NULL) { xxxSendMessage(pwndDragging, WM_DRAGSELECT, TRUE, (LPARAM)lpds); } } else { if (pwndDragging != NULL) { xxxSendMessage(pwndDragging, WM_DRAGMOVE, 0, (LPARAM)lpds); } } switch (msg.message) { case WM_LBUTTONUP: case WM_NCLBUTTONUP: fDrag = FALSE; break; } } ThreadUnlock(&tlpwndDragging); /* * If the capture has been lost (i.e. fDrag == TRUE), don't do the drop. */ if (fDrag) pcurT = SYSCUR(NO); /* * before the actual drop, clean up the cursor, as the app may do * stuff here... */ xxxReleaseCapture(); zzzShowCursor(FALSE); zzzSetCursor(pcurOld); /* * we either got lbuttonup or enter */ if (pcurT != SYSCUR(NO)) { /* * object allows drop... send drop message */ pwndT = ValidateHwnd(lpds->hwndSink); if (pwndT != NULL) { ThreadLockAlwaysWithPti(pti, pwndT, &tlpwndT); /* * Allow this guy to activate. */ GETPTI(pwndT)->TIF_flags |= TIF_ALLOWFOREGROUNDACTIVATE; TAGMSG1(DBGTAG_FOREGROUND, "xxxDragObject set TIF %#p", GETPTI(pwndT)); result = (DWORD)xxxSendMessage(pwndT, WM_DROPOBJECT, (WPARAM)HW(pwndFrom), (LPARAM)lpds); ThreadUnlock(&tlpwndT); } } xxxWindowEvent(EVENT_SYSTEM_DRAGDROPEND, pwndFrom, OBJID_WINDOW, INDEXID_CONTAINER, 0); ThreadUnlockAndFreePool(pti, &tlPool); return result; } /***************************************************************************\ * QueryDropObject * * Determines where in the window heirarchy the "drop" takes place, and * sends a message to the deepest child window first. If that window does * not respond, we go up the heirarchy (recursively, for the moment) until * we either get a window that does respond or the parent doesn't respond. * * History: * 08-06-91 darrinm Ported from Win 3.1 sources. \***************************************************************************/ PCURSOR xxxQueryDropObject( PWND pwnd, LPDROPSTRUCT lpds) { PWND pwndT; PCURSOR pcurT = NULL; POINT pt; BOOL fNC; TL tlpwndT; CheckLock(pwnd); /* * pt is in screen coordinates */ pt = lpds->ptDrop; /* * reject points outside this window or if the window is disabled */ if (!PtInRect(&pwnd->rcWindow, pt) || TestWF(pwnd, WFDISABLED)) return NULL; /* * Check to see if in window region (if it has one) */ if (pwnd->hrgnClip != NULL) { if (!GrePtInRegion(pwnd->hrgnClip, pt.x, pt.y)) return NULL; } /* * are we dropping in the nonclient area of the window or on an iconic * window? */ if (fNC = (TestWF(pwnd, WFMINIMIZED) || !PtInRect(&pwnd->rcClient, pt))) { goto SendQueryDrop; } /* * dropping in client area */ _ScreenToClient(pwnd, &pt); pwndT = _ChildWindowFromPointEx(pwnd, pt, CWP_SKIPDISABLED | CWP_SKIPINVISIBLE); _ClientToScreen(pwnd, &pt); pcurT = NULL; if (pwndT && pwndT != pwnd) { ThreadLock(pwndT, &tlpwndT); pcurT = xxxQueryDropObject(pwndT, lpds); ThreadUnlock(&tlpwndT); } if (pcurT == NULL) { /* * there are no children who are in the right place or who want * drops... convert the point into client coordinates of the * current window. Because of the recursion, this is already * done if a child window grabbed the drop. */ SendQueryDrop: _ScreenToClient(pwnd, &lpds->ptDrop); lpds->hwndSink = HWq(pwnd); /* * To avoid hanging dropper (sender) app we do a SendMessageTimeout to * the droppee (receiver) */ if ((PCURSOR)xxxSendMessageTimeout(pwnd, WM_QUERYDROPOBJECT, fNC, (LPARAM)lpds, SMTO_ABORTIFHUNG, 3*1000, (PLONG_PTR)&pcurT) == FALSE) pcurT = (PCURSOR)FALSE; if (pcurT != (PCURSOR)FALSE && pcurT != (PCURSOR)TRUE) pcurT = HMValidateHandle((HCURSOR)pcurT, TYPE_CURSOR); /* * restore drop point to screen coordinates if this window won't * take drops */ if (pcurT == NULL) lpds->ptDrop = pt; } return pcurT; } /***************************************************************************\ * xxxDragDetect (API) * * * * History: * 08-06-91 darrinm Ported from Win 3.1 sources. \***************************************************************************/ BOOL xxxDragDetect( PWND pwnd, POINT pt) { return xxxIsDragging(pwnd, pt, WM_LBUTTONUP); } /***************************************************************************\ * xxxIsDragging * * * * History: * 05-17-94 johnl Ported from Chicago sources \***************************************************************************/ BOOL xxxIsDragging(PWND pwnd, POINT ptScreen, UINT uMsg) { RECT rc; MSG msg; BOOL fDragging; BOOL fCheck; TL tlpwndDragging; PTHREADINFO pti = PtiCurrent(); /* * Check synchronous mouse state, and punt if the mouse isn't down * according to the queue. */ if (!(_GetKeyState((uMsg == WM_LBUTTONUP ? VK_LBUTTON : VK_RBUTTON)) & 0x8000)) return FALSE; xxxSetCapture(pwnd); *(LPPOINT)&rc.left = ptScreen; *(LPPOINT)&rc.right = ptScreen; InflateRect(&rc, SYSMET(CXDRAG), SYSMET(CYDRAG)); fDragging = FALSE; fCheck = TRUE; ThreadLockWithPti(pti, pwnd, &tlpwndDragging); while (fCheck) { while ( !( xxxPeekMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST,PM_REMOVE) || xxxPeekMessage(&msg, NULL, WM_QUEUESYNC, WM_QUEUESYNC,PM_REMOVE) || xxxPeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST,PM_REMOVE) ) && (pti->pq->spwndCapture == pwnd)) { /* * If there is no input for half a second (500ms) consider that * we are dragging. If we don't specify a timeout value, the * thread may sleep here forever and wouldn't repaint, etc. */ if (!xxxSleepThread(QS_MOUSE | QS_KEY, 500, TRUE)) { fDragging = TRUE; goto Cleanup; } } /* * Cancel if the button was released or we no longer have the capture. */ if ( pti->pq->spwndCapture != pwnd || msg.message == uMsg) { fCheck = FALSE; } else { switch (msg.message) { case WM_MOUSEMOVE: if (!PtInRect(&rc, msg.pt)) { fDragging = TRUE; fCheck = FALSE; } break; case WM_QUEUESYNC: /* * CBT Hook needs to know */ xxxCallHook(HCBT_QS, 0, 0, WH_CBT); break; case WM_KEYDOWN: /* * cancels drag detection */ if (msg.wParam == VK_ESCAPE) fCheck = FALSE; break; } } } Cleanup: if (pti->pq->spwndCapture == pwnd) xxxReleaseCapture(); ThreadUnlock(&tlpwndDragging); return fDragging ; }