478 lines
11 KiB
C++
478 lines
11 KiB
C++
|
/******************************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 "precomp.h"
|
||
|
#include "cdopti.h"
|
||
|
#include "cdoptimp.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 DRAGPROP
|
||
|
{
|
||
|
WNDPROC lpfnDefProc;
|
||
|
HWND hwndDrag;
|
||
|
UINT uFlags;
|
||
|
DWORD dwState;
|
||
|
CCDOpt * pCDOpt;
|
||
|
|
||
|
} DRAGPROP, *PDRAGPROP;
|
||
|
|
||
|
|
||
|
TCHAR szDragListMsgString[] = TEXT(SJE_DRAGLISTMSGSTRING);
|
||
|
TCHAR szDragProp[] = TEXT("DragMultiProp");
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
STDMETHODIMP_(UINT) CCDOpt::InitDragMultiList(void)
|
||
|
{
|
||
|
if (!m_uDragListMsg) {
|
||
|
|
||
|
m_uDragListMsg = RegisterWindowMessage(szDragListMsgString);
|
||
|
|
||
|
if (!m_uDragListMsg) {
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
return m_uDragListMsg;
|
||
|
}
|
||
|
|
||
|
|
||
|
STDMETHODIMP_(LRESULT) CCDOpt::DragList(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) ? DG_COPY : DG_MOVE;
|
||
|
sNotify.uNotification = DG_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;
|
||
|
LRESULT lRet;
|
||
|
|
||
|
// 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 = DG_DRAGGING;
|
||
|
|
||
|
QueryParent:
|
||
|
hwndParent = GetParent( hLB );
|
||
|
sNotify.hWnd = hLB;
|
||
|
sNotify.dwState = pDragProp->dwState;
|
||
|
|
||
|
LONG2POINT( lParam, sNotify.ptCursor );
|
||
|
|
||
|
ClientToScreen( hLB, &sNotify.ptCursor );
|
||
|
|
||
|
lRet = SendMessage( hwndParent, m_uDragListMsg, GetDlgCtrlID(hLB),
|
||
|
(LPARAM)(LPDRAGMULTILISTINFO)&sNotify );
|
||
|
|
||
|
if ( uMsg == WM_LBUTTONDOWN ) {
|
||
|
|
||
|
// Some things may not be draggable
|
||
|
|
||
|
if (lRet) {
|
||
|
|
||
|
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)
|
||
|
? DG_DROPPED : DG_CANCELDRAG;
|
||
|
sNotify.hWnd = hLB;
|
||
|
sNotify.dwState = pDragProp->dwState;
|
||
|
|
||
|
LONG2POINT( lParam, sNotify.ptCursor );
|
||
|
|
||
|
ClientToScreen( hLB, &sNotify.ptCursor );
|
||
|
|
||
|
SendMessage( hwndParent, m_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);
|
||
|
}
|
||
|
|
||
|
LRESULT CALLBACK CCDOpt::DragListProc(HWND hLB, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
||
|
{
|
||
|
PDRAGPROP pDragProp = (PDRAGPROP)GetProp(hLB, szDragProp);
|
||
|
|
||
|
return(pDragProp->pCDOpt->DragList(hLB, uMsg, wParam, lParam));
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
STDMETHODIMP_(BOOL) CCDOpt::MakeMultiDragList(HWND hLB)
|
||
|
{
|
||
|
PDRAGPROP pDragProp;
|
||
|
|
||
|
if (!m_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;
|
||
|
}
|
||
|
|
||
|
pDragProp->pCDOpt = this;
|
||
|
|
||
|
SetProp(hLB, szDragProp, (HANDLE)pDragProp);
|
||
|
pDragProp->lpfnDefProc = SubclassWindow( hLB, DragListProc );
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
STDMETHODIMP_(int) CCDOpt::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;
|
||
|
}
|
||
|
|
||
|
|
||
|
STDMETHODIMP_(void) CCDOpt::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(m_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);
|
||
|
}
|
||
|
}
|
||
|
}
|