You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
434 lines
13 KiB
434 lines
13 KiB
/****************************** 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:
|
|
/*
|
|
* <Esc> cancels drag detection
|
|
*/
|
|
if (msg.wParam == VK_ESCAPE)
|
|
fCheck = FALSE;
|
|
break;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
Cleanup:
|
|
if (pti->pq->spwndCapture == pwnd)
|
|
xxxReleaseCapture();
|
|
|
|
ThreadUnlock(&tlpwndDragging);
|
|
return fDragging ;
|
|
}
|