| /* | |
| * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 <stdarg.h> | |
| #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); | |
| CopyRect(&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 %ld x %ld %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, (WPARAM)nIndex, 0); | |
| } | |
| return -1; | |
| } |