mirror of
https://github.com/reactos/reactos.git
synced 2024-08-21 10:53:15 +00:00
333 lines
9.9 KiB
C
333 lines
9.9 KiB
C
/*
|
|
* Drag List control
|
|
*
|
|
* Copyright 1999 Eric Kohl
|
|
* Copyright 2004 Robert Shearman
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*
|
|
* NOTES
|
|
*
|
|
* This code was audited for completeness against the documented features
|
|
* of Comctl32.dll version 6.0 on Mar. 10, 2004, by Robert Shearman.
|
|
*
|
|
* Unless otherwise noted, we believe this code to be complete, as per
|
|
* the specification mentioned above.
|
|
* If you discover missing features or bugs please note them below.
|
|
*
|
|
*/
|
|
|
|
#include "comctl32.h"
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(commctrl);
|
|
|
|
#define DRAGLIST_SUBCLASSID 0
|
|
#define DRAGLIST_SCROLLPERIOD 200
|
|
#define DRAGLIST_TIMERID 666
|
|
|
|
/* properties relating to IDI_DRAGICON */
|
|
#define DRAGICON_HOTSPOT_X 17
|
|
#define DRAGICON_HOTSPOT_Y 7
|
|
#define DRAGICON_HEIGHT 32
|
|
|
|
/* internal Wine specific data for the drag list control */
|
|
typedef struct _DRAGLISTDATA
|
|
{
|
|
/* are we currently in dragging mode? */
|
|
BOOL dragging;
|
|
|
|
/* cursor to use as determined by DL_DRAGGING notification.
|
|
* NOTE: as we use LoadCursor we don't have to use DeleteCursor
|
|
* when we are finished with it */
|
|
HCURSOR cursor;
|
|
|
|
/* optimisation so that we don't have to load the cursor
|
|
* all of the time whilst dragging */
|
|
LRESULT last_dragging_response;
|
|
|
|
/* prevents flicker with drawing drag arrow */
|
|
RECT last_drag_icon_rect;
|
|
} DRAGLISTDATA;
|
|
|
|
UINT uDragListMessage = 0; /* registered window message code */
|
|
static DWORD dwLastScrollTime = 0;
|
|
static HICON hDragArrow = NULL;
|
|
|
|
/***********************************************************************
|
|
* DragList_Notify (internal)
|
|
*
|
|
* Sends notification messages to the parent control. Note that it
|
|
* does not use WM_NOTIFY like the rest of the controls, but a registered
|
|
* window message.
|
|
*/
|
|
static LRESULT DragList_Notify(HWND hwndLB, UINT uNotification)
|
|
{
|
|
DRAGLISTINFO dli;
|
|
dli.hWnd = hwndLB;
|
|
dli.uNotification = uNotification;
|
|
GetCursorPos(&dli.ptCursor);
|
|
return SendMessageW(GetParent(hwndLB), uDragListMessage, GetDlgCtrlID(hwndLB), (LPARAM)&dli);
|
|
}
|
|
|
|
/* cleans up after dragging */
|
|
static void DragList_EndDrag(HWND hwnd, DRAGLISTDATA * data)
|
|
{
|
|
KillTimer(hwnd, DRAGLIST_TIMERID);
|
|
ReleaseCapture();
|
|
/* clear any drag insert icon present */
|
|
InvalidateRect(GetParent(hwnd), &data->last_drag_icon_rect, TRUE);
|
|
/* clear data for next use */
|
|
memset(data, 0, sizeof(*data));
|
|
}
|
|
|
|
/***********************************************************************
|
|
* DragList_SubclassWindowProc (internal)
|
|
*
|
|
* Handles certain messages to enable dragging for the ListBox and forwards
|
|
* the rest to the ListBox.
|
|
*/
|
|
static LRESULT CALLBACK
|
|
DragList_SubclassWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
|
|
{
|
|
DRAGLISTDATA * data = (DRAGLISTDATA*)dwRefData;
|
|
switch (uMsg)
|
|
{
|
|
case WM_LBUTTONDOWN:
|
|
SetFocus(hwnd);
|
|
data->dragging = DragList_Notify(hwnd, DL_BEGINDRAG);
|
|
if (data->dragging)
|
|
{
|
|
SetCapture(hwnd);
|
|
SetTimer(hwnd, DRAGLIST_TIMERID, DRAGLIST_SCROLLPERIOD, NULL);
|
|
}
|
|
/* note that we don't absorb this message to let the list box
|
|
* do its thing (normally selecting an item) */
|
|
break;
|
|
|
|
case WM_KEYDOWN:
|
|
case WM_RBUTTONDOWN:
|
|
/* user cancelled drag by either right clicking or
|
|
* by pressing the escape key */
|
|
if ((data->dragging) &&
|
|
((uMsg == WM_RBUTTONDOWN) || (wParam == VK_ESCAPE)))
|
|
{
|
|
/* clean up and absorb message */
|
|
DragList_EndDrag(hwnd, data);
|
|
DragList_Notify(hwnd, DL_CANCELDRAG);
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case WM_MOUSEMOVE:
|
|
case WM_TIMER:
|
|
if (data->dragging)
|
|
{
|
|
LRESULT cursor = DragList_Notify(hwnd, DL_DRAGGING);
|
|
/* optimisation so that we don't have to load the cursor
|
|
* all of the time whilst dragging */
|
|
if (data->last_dragging_response != cursor)
|
|
{
|
|
switch (cursor)
|
|
{
|
|
case DL_STOPCURSOR:
|
|
data->cursor = LoadCursorW(NULL, (LPCWSTR)IDC_NO);
|
|
SetCursor(data->cursor);
|
|
break;
|
|
case DL_COPYCURSOR:
|
|
data->cursor = LoadCursorW(COMCTL32_hModule, (LPCWSTR)IDC_COPY);
|
|
SetCursor(data->cursor);
|
|
break;
|
|
case DL_MOVECURSOR:
|
|
data->cursor = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW);
|
|
SetCursor(data->cursor);
|
|
break;
|
|
}
|
|
data->last_dragging_response = cursor;
|
|
}
|
|
/* don't pass this message on to List Box */
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case WM_LBUTTONUP:
|
|
if (data->dragging)
|
|
{
|
|
DragList_EndDrag(hwnd, data);
|
|
DragList_Notify(hwnd, DL_DROPPED);
|
|
}
|
|
break;
|
|
|
|
case WM_GETDLGCODE:
|
|
/* tell dialog boxes that we want to receive WM_KEYDOWN events
|
|
* for keys like VK_ESCAPE */
|
|
if (data->dragging)
|
|
return DLGC_WANTALLKEYS;
|
|
break;
|
|
case WM_NCDESTROY:
|
|
RemoveWindowSubclass(hwnd, DragList_SubclassWindowProc, DRAGLIST_SUBCLASSID);
|
|
Free(data);
|
|
break;
|
|
}
|
|
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
/***********************************************************************
|
|
* MakeDragList (COMCTL32.13)
|
|
*
|
|
* Makes a normal ListBox into a DragList by subclassing it.
|
|
*
|
|
* RETURNS
|
|
* Success: Non-zero
|
|
* Failure: Zero
|
|
*/
|
|
BOOL WINAPI MakeDragList (HWND hwndLB)
|
|
{
|
|
DRAGLISTDATA *data = Alloc(sizeof(DRAGLISTDATA));
|
|
|
|
TRACE("(%p)\n", hwndLB);
|
|
|
|
if (!uDragListMessage)
|
|
uDragListMessage = RegisterWindowMessageW(DRAGLISTMSGSTRINGW);
|
|
|
|
return SetWindowSubclass(hwndLB, DragList_SubclassWindowProc, DRAGLIST_SUBCLASSID, (DWORD_PTR)data);
|
|
}
|
|
|
|
/***********************************************************************
|
|
* DrawInsert (COMCTL32.15)
|
|
*
|
|
* Draws insert arrow by the side of the ListBox item in the parent window.
|
|
*
|
|
* RETURNS
|
|
* Nothing.
|
|
*/
|
|
VOID WINAPI DrawInsert (HWND hwndParent, HWND hwndLB, INT nItem)
|
|
{
|
|
RECT rcItem, rcListBox, rcDragIcon;
|
|
HDC hdc;
|
|
DRAGLISTDATA * data;
|
|
|
|
TRACE("(%p %p %d)\n", hwndParent, hwndLB, nItem);
|
|
|
|
if (!hDragArrow)
|
|
hDragArrow = LoadIconW(COMCTL32_hModule, (LPCWSTR)IDI_DRAGARROW);
|
|
|
|
if (LB_ERR == SendMessageW(hwndLB, LB_GETITEMRECT, nItem, (LPARAM)&rcItem))
|
|
return;
|
|
|
|
if (!GetWindowRect(hwndLB, &rcListBox))
|
|
return;
|
|
|
|
/* convert item rect to parent co-ordinates */
|
|
if (!MapWindowPoints(hwndLB, hwndParent, (LPPOINT)&rcItem, 2))
|
|
return;
|
|
|
|
/* convert list box rect to parent co-ordinates */
|
|
if (!MapWindowPoints(HWND_DESKTOP, hwndParent, (LPPOINT)&rcListBox, 2))
|
|
return;
|
|
|
|
rcDragIcon.left = rcListBox.left - DRAGICON_HOTSPOT_X;
|
|
rcDragIcon.top = rcItem.top - DRAGICON_HOTSPOT_Y;
|
|
rcDragIcon.right = rcListBox.left;
|
|
rcDragIcon.bottom = rcDragIcon.top + DRAGICON_HEIGHT;
|
|
|
|
if (!GetWindowSubclass(hwndLB, DragList_SubclassWindowProc, DRAGLIST_SUBCLASSID, (DWORD_PTR*)&data))
|
|
return;
|
|
|
|
if (nItem < 0)
|
|
SetRectEmpty(&rcDragIcon);
|
|
|
|
/* prevent flicker by only redrawing when necessary */
|
|
if (!EqualRect(&rcDragIcon, &data->last_drag_icon_rect))
|
|
{
|
|
/* get rid of any previous inserts drawn */
|
|
RedrawWindow(hwndParent, &data->last_drag_icon_rect, NULL,
|
|
RDW_INTERNALPAINT | RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
|
|
|
|
data->last_drag_icon_rect = rcDragIcon;
|
|
|
|
if (nItem >= 0)
|
|
{
|
|
hdc = GetDC(hwndParent);
|
|
|
|
DrawIcon(hdc, rcDragIcon.left, rcDragIcon.top, hDragArrow);
|
|
|
|
ReleaseDC(hwndParent, hdc);
|
|
}
|
|
}
|
|
}
|
|
|
|
/***********************************************************************
|
|
* LBItemFromPt (COMCTL32.14)
|
|
*
|
|
* Gets the index of the ListBox item under the specified point,
|
|
* scrolling if bAutoScroll is TRUE and pt is outside of the ListBox.
|
|
*
|
|
* RETURNS
|
|
* The ListBox item ID if pt is over a list item or -1 otherwise.
|
|
*/
|
|
INT WINAPI LBItemFromPt (HWND hwndLB, POINT pt, BOOL bAutoScroll)
|
|
{
|
|
RECT rcClient;
|
|
INT nIndex;
|
|
DWORD dwScrollTime;
|
|
|
|
TRACE("(%p %d x %d %s)\n",
|
|
hwndLB, pt.x, pt.y, bAutoScroll ? "TRUE" : "FALSE");
|
|
|
|
ScreenToClient (hwndLB, &pt);
|
|
GetClientRect (hwndLB, &rcClient);
|
|
nIndex = (INT)SendMessageW (hwndLB, LB_GETTOPINDEX, 0, 0);
|
|
|
|
if (PtInRect (&rcClient, pt))
|
|
{
|
|
/* point is inside -- get the item index */
|
|
while (TRUE)
|
|
{
|
|
if (SendMessageW (hwndLB, LB_GETITEMRECT, nIndex, (LPARAM)&rcClient) == LB_ERR)
|
|
return -1;
|
|
|
|
if (PtInRect (&rcClient, pt))
|
|
return nIndex;
|
|
|
|
nIndex++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* point is outside */
|
|
if (!bAutoScroll)
|
|
return -1;
|
|
|
|
if ((pt.x > rcClient.right) || (pt.x < rcClient.left))
|
|
return -1;
|
|
|
|
if (pt.y < 0)
|
|
nIndex--;
|
|
else
|
|
nIndex++;
|
|
|
|
dwScrollTime = GetTickCount ();
|
|
|
|
if ((dwScrollTime - dwLastScrollTime) < DRAGLIST_SCROLLPERIOD)
|
|
return -1;
|
|
|
|
dwLastScrollTime = dwScrollTime;
|
|
|
|
SendMessageW (hwndLB, LB_SETTOPINDEX, nIndex, 0);
|
|
}
|
|
|
|
return -1;
|
|
}
|