/******************************Module*Header*******************************\ * Module Name: DragDrop.c * * An attempt to implement dragging and dropping between Multi-selection * listboxes. * * Created: dd-mm-93 * Author: Stephen Estrop [StephenE] * * Copyright (c) 1993 Microsoft Corporation \**************************************************************************/ #define NOOLE #define NODRAGLIST #include #include #include "resource.h" #include "dragdrop.h" #define LONG2POINT(l, pt) ((pt).x = (SHORT)LOWORD(l), \ (pt).y = (SHORT)HIWORD(l)) #define DF_ACTUALLYDRAG 0x0001 #define DF_DEFERRED 0x0002 #define INITLINESPERSECOND 36 #define VERTCHANGENUMLINES 25 #define TIMERID 238 #define TIMERLEN 250 #define TIMERLEN2 50 #define DX_INSERT 16 #define DY_INSERT 16 typedef struct { WNDPROC lpfnDefProc; HWND hwndDrag; UINT uFlags; DWORD dwState; } DRAGPROP, *PDRAGPROP; UINT uDragListMsg = 0L; TCHAR szDragListMsgString[] = TEXT(SJE_DRAGLISTMSGSTRING); TCHAR szDragProp[] = TEXT("DragMultiProp"); extern HINSTANCE g_hInst; /******************************Public*Routine******************************\ * InitDragMultiList * * * * History: * dd-mm-94 - StephenE - Created * \**************************************************************************/ UINT WINAPI InitDragMultiList( void ) { if (!uDragListMsg) { uDragListMsg = RegisterWindowMessage(szDragListMsgString); if (!uDragListMsg) { return 0; } } return uDragListMsg; } /******************************Public*Routine******************************\ * DragListSubclassProc * * * * History: * dd-mm-93 - StephenE - Created * \**************************************************************************/ LRESULT CALLBACK DragListSubclassProc( HWND hLB, UINT uMsg, WPARAM wParam, LPARAM lParam ) { PDRAGPROP pDragProp; DRAGMULTILISTINFO sNotify; WNDPROC lpfnDefProc; BOOL bDragging; pDragProp = (PDRAGPROP)GetProp(hLB, szDragProp); bDragging = pDragProp->hwndDrag == hLB; // Save this in case anything happens to pDragProp before we return. lpfnDefProc = pDragProp->lpfnDefProc; switch (uMsg) { case WM_DESTROY: if (bDragging) SendMessage(hLB, WM_RBUTTONDOWN, 0, 0L); // cancel drag // Restore the window proc just in case. SubclassWindow( hLB, lpfnDefProc ); if (pDragProp) { LocalFree((HLOCAL)pDragProp); RemoveProp(hLB, szDragProp); } break; case WM_LBUTTONDOWN: { POINT pt; int nItem; if (bDragging) // nested button-down SendMessage(hLB, WM_RBUTTONDOWN, 0, 0L); // cancel drag SetFocus(hLB); LONG2POINT(lParam, pt); ClientToScreen(hLB, &pt); nItem = LBMultiItemFromPt(hLB, pt, FALSE); if ( nItem >= 0 ) { // // We can only allow dragging if the item is selected. // If the item is not selected - pass the message on. // if ( ListBox_GetSel( hLB, nItem ) <= 0 ) { return CallWindowProc( lpfnDefProc, hLB, uMsg, wParam, lParam ); } pDragProp->dwState = (wParam & MK_CONTROL) ? DL_COPY : DL_MOVE; sNotify.uNotification = DL_BEGINDRAG; goto QueryParent; } else { goto FakeDrag; } } case WM_TIMER: if (wParam != TIMERID) { break; } { POINT CursorPos; GetCursorPos( &CursorPos ); ScreenToClient( hLB, &CursorPos ); lParam = MAKELPARAM((WORD)CursorPos.x, (WORD)CursorPos.y); } // Fall through case WM_MOUSEMOVE: if (bDragging) { HWND hwndParent; DWORD dwRet; // We may be just simulating a drag, but not actually doing // anything. if (!(pDragProp->uFlags&DF_ACTUALLYDRAG)) { return(0L); } if ( pDragProp->uFlags & DF_DEFERRED ) { pDragProp->uFlags &= ~DF_DEFERRED; KillTimer(hLB, TIMERID); SetTimer(hLB, TIMERID, TIMERLEN2, NULL); } sNotify.uNotification = DL_DRAGGING; QueryParent: hwndParent = GetParent( hLB ); sNotify.hWnd = hLB; sNotify.dwState = pDragProp->dwState; LONG2POINT( lParam, sNotify.ptCursor ); ClientToScreen( hLB, &sNotify.ptCursor ); dwRet = SendMessage( hwndParent, uDragListMsg, GetDlgCtrlID(hLB), (LPARAM)(LPDRAGMULTILISTINFO)&sNotify ); if ( uMsg == WM_LBUTTONDOWN ) { // Some things may not be draggable if (dwRet) { SetTimer(hLB, TIMERID, TIMERLEN, NULL); pDragProp->uFlags = DF_DEFERRED | DF_ACTUALLYDRAG; } else { FakeDrag: pDragProp->uFlags = 0; } // Set capture and change mouse cursor pDragProp->hwndDrag = hLB; SetCapture( hLB ); } // Don't call the def proc, since it may try to change the // selection or set timers or things like that. return 0L; } break; case WM_RBUTTONDOWN: case WM_LBUTTONUP: // if we are capturing mouse - release it and check for an // acceptable place where mouse is now to decide drop or not if (bDragging) { HWND hwndParent; pDragProp->hwndDrag = NULL; KillTimer(hLB, TIMERID); ReleaseCapture(); SetCursor(LoadCursor(NULL, IDC_ARROW)); hwndParent = GetParent(hLB); sNotify.uNotification = (uMsg == WM_LBUTTONUP) ? DL_DROPPED : DL_CANCELDRAG; sNotify.hWnd = hLB; sNotify.dwState = pDragProp->dwState; LONG2POINT( lParam, sNotify.ptCursor ); ClientToScreen( hLB, &sNotify.ptCursor ); SendMessage( hwndParent, uDragListMsg, GetDlgCtrlID(hLB), (LPARAM)(LPDRAGMULTILISTINFO)&sNotify); // // If we didn't actually do any dragging just fake a button // click at the current location. // if ( pDragProp->uFlags & DF_DEFERRED ) { CallWindowProc(lpfnDefProc, hLB, WM_LBUTTONDOWN, wParam, lParam); CallWindowProc(lpfnDefProc, hLB, uMsg, wParam, lParam); } // We need to make sure to return 0 in case this is from a // keyboard message. return 0L; } break; case WM_GETDLGCODE: if (bDragging) return (CallWindowProc(lpfnDefProc, hLB, uMsg, wParam, lParam) | DLGC_WANTMESSAGE); break; case WM_KEYDOWN: if (wParam == VK_ESCAPE) { SendMessage(hLB, WM_RBUTTONDOWN, 0, 0L); } case WM_CHAR: case WM_KEYUP: // We don't want the listbox processing this if we are dragging. if (bDragging) return 0L; break; default: break; } return CallWindowProc(lpfnDefProc, hLB, uMsg, wParam, lParam); } /******************************Public*Routine******************************\ * MakeMultiDragList * * * * History: * dd-mm-93 - StephenE - Created * \**************************************************************************/ BOOL WINAPI MakeMultiDragList( HWND hLB ) { PDRAGPROP pDragProp; if (!uDragListMsg) { return FALSE; } // // Check that we have not already subclassed this window. // if (GetProp(hLB, szDragProp)) { return TRUE; } pDragProp = (PDRAGPROP)LocalAlloc(LPTR, sizeof(DRAGPROP)); if (pDragProp == NULL ) { return FALSE; } SetProp(hLB, szDragProp, (HANDLE)pDragProp); pDragProp->lpfnDefProc = SubclassWindow( hLB, DragListSubclassProc ); return TRUE; } /******************************Public*Routine******************************\ * LBMultiItemFromPt * * * * History: * dd-mm-93 - StephenE - Created * \**************************************************************************/ int WINAPI LBMultiItemFromPt( HWND hLB, POINT pt, BOOL bAutoScroll ) { static LONG dwLastScroll = 0; RECT rc; DWORD dwNow; int nItem; WORD wScrollDelay, wActualDelay; ScreenToClient(hLB, &pt); GetClientRect(hLB, &rc); nItem = ListBox_GetTopIndex( hLB ); // // Is the point in the LB client area? // if ( PtInRect(&rc, pt) ) { // // Check each visible item in turn. // for ( ; ; ++nItem) { if ( LB_ERR == ListBox_GetItemRect( hLB, nItem, &rc) ) { break; } if ( PtInRect(&rc, pt) ) { return nItem; } } } else { // // If we want autoscroll and the point is directly above or below the // LB, determine the direction and if it is time to scroll yet. // if ( bAutoScroll && (UINT)pt.x < (UINT)rc.right ) { if (pt.y <= 0) { --nItem; } else { ++nItem; pt.y = rc.bottom - pt.y; } wScrollDelay = (WORD)(1000 / (INITLINESPERSECOND - pt.y/VERTCHANGENUMLINES)); dwNow = GetTickCount(); wActualDelay = (WORD)(dwNow - dwLastScroll); if (wActualDelay > wScrollDelay) { // // This will the actual number of scrolls per second to be // much closer to the required number. // if (wActualDelay > wScrollDelay * 2) dwLastScroll = dwNow; else dwLastScroll += wScrollDelay; ListBox_SetTopIndex( hLB, nItem ); } } } return -1; } /******************************Public*Routine******************************\ * DrawInsert * * * * History: * dd-mm-93 - StephenE - Created * \**************************************************************************/ VOID WINAPI DrawMultiInsert( HWND hwndParent, HWND hLB, int nItem ) { static POINT ptLastInsert; static INT nLastInsert = -1; RECT rc; // // Erase the old mark if necessary // if ( nLastInsert >= 0 && nItem != nLastInsert ) { rc.left = ptLastInsert.x; rc.top = ptLastInsert.y; rc.right = rc.left + DX_INSERT; rc.bottom = rc.top + DY_INSERT; // // Need to update immediately in case the insert rects overlap. // InvalidateRect( hwndParent, &rc, TRUE ); UpdateWindow( hwndParent ); nLastInsert = -1; } // // Draw a new mark if necessary // if ( nItem != nLastInsert && nItem >= 0 ) { static HICON hInsert = NULL; if ( !hInsert ) { hInsert = LoadIcon( g_hInst, MAKEINTRESOURCE(IDR_INSERT)); } if ( hInsert ) { HDC hDC; int iItemHeight; GetWindowRect( hLB, &rc ); ScreenToClient( hLB, (LPPOINT)&rc ); ptLastInsert.x = rc.left - DX_INSERT; iItemHeight = ListBox_GetItemHeight( hLB, nItem ); nLastInsert = nItem; nItem -= ListBox_GetTopIndex( hLB ); ptLastInsert.y = (nItem * iItemHeight) - DY_INSERT / 2; ClientToScreen(hLB, &ptLastInsert); ScreenToClient(hwndParent, &ptLastInsert); hDC = GetDC(hwndParent); DrawIcon(hDC, ptLastInsert.x, ptLastInsert.y, hInsert); ReleaseDC(hwndParent, hDC); } } }