/* * 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 #include "windef.h" #include "winbase.h" #include "wingdi.h" #include "winuser.h" #include "winnls.h" #include "commctrl.h" #include "comctl32.h" #include "wine/debug.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; }