xref: /reactos/dll/win32/comctl32/draglist.c (revision 1a102501)
1 /*
2  * Drag List control
3  *
4  * Copyright 1999 Eric Kohl
5  * Copyright 2004 Robert Shearman
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  *
21  * NOTES
22  *
23  * This code was audited for completeness against the documented features
24  * of Comctl32.dll version 6.0 on Mar. 10, 2004, by Robert Shearman.
25  *
26  * Unless otherwise noted, we believe this code to be complete, as per
27  * the specification mentioned above.
28  * If you discover missing features or bugs please note them below.
29  *
30  */
31 
32 #include <stdarg.h>
33 
34 #include "windef.h"
35 #include "winbase.h"
36 #include "wingdi.h"
37 #include "winuser.h"
38 #include "winnls.h"
39 #include "commctrl.h"
40 #include "comctl32.h"
41 #include "wine/debug.h"
42 
43 WINE_DEFAULT_DEBUG_CHANNEL(commctrl);
44 
45 #define DRAGLIST_SUBCLASSID     0
46 #define DRAGLIST_SCROLLPERIOD 200
47 #define DRAGLIST_TIMERID      666
48 
49 /* properties relating to IDI_DRAGICON */
50 #define DRAGICON_HOTSPOT_X 17
51 #define DRAGICON_HOTSPOT_Y  7
52 #define DRAGICON_HEIGHT    32
53 
54 /* internal Wine specific data for the drag list control */
55 typedef struct _DRAGLISTDATA
56 {
57     /* are we currently in dragging mode? */
58     BOOL dragging;
59 
60     /* cursor to use as determined by DL_DRAGGING notification.
61      * NOTE: as we use LoadCursor we don't have to use DeleteCursor
62      * when we are finished with it */
63     HCURSOR cursor;
64 
65     /* optimisation so that we don't have to load the cursor
66      * all of the time whilst dragging */
67     LRESULT last_dragging_response;
68 
69     /* prevents flicker with drawing drag arrow */
70     RECT last_drag_icon_rect;
71 } DRAGLISTDATA;
72 
73 UINT uDragListMessage = 0; /* registered window message code */
74 static DWORD dwLastScrollTime = 0;
75 static HICON hDragArrow = NULL;
76 
77 /***********************************************************************
78  *		DragList_Notify (internal)
79  *
80  * Sends notification messages to the parent control. Note that it
81  * does not use WM_NOTIFY like the rest of the controls, but a registered
82  * window message.
83  */
DragList_Notify(HWND hwndLB,UINT uNotification)84 static LRESULT DragList_Notify(HWND hwndLB, UINT uNotification)
85 {
86     DRAGLISTINFO dli;
87     dli.hWnd = hwndLB;
88     dli.uNotification = uNotification;
89     GetCursorPos(&dli.ptCursor);
90     return SendMessageW(GetParent(hwndLB), uDragListMessage, GetDlgCtrlID(hwndLB), (LPARAM)&dli);
91 }
92 
93 /* cleans up after dragging */
DragList_EndDrag(HWND hwnd,DRAGLISTDATA * data)94 static void DragList_EndDrag(HWND hwnd, DRAGLISTDATA * data)
95 {
96     KillTimer(hwnd, DRAGLIST_TIMERID);
97     ReleaseCapture();
98     /* clear any drag insert icon present */
99     InvalidateRect(GetParent(hwnd), &data->last_drag_icon_rect, TRUE);
100     /* clear data for next use */
101     memset(data, 0, sizeof(*data));
102 }
103 
104 /***********************************************************************
105  *		DragList_SubclassWindowProc (internal)
106  *
107  * Handles certain messages to enable dragging for the ListBox and forwards
108  * the rest to the ListBox.
109  */
110 static LRESULT CALLBACK
DragList_SubclassWindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam,UINT_PTR uIdSubclass,DWORD_PTR dwRefData)111 DragList_SubclassWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
112 {
113     DRAGLISTDATA * data = (DRAGLISTDATA*)dwRefData;
114     switch (uMsg)
115     {
116     case WM_LBUTTONDOWN:
117         SetFocus(hwnd);
118         data->dragging = DragList_Notify(hwnd, DL_BEGINDRAG);
119         if (data->dragging)
120         {
121             SetCapture(hwnd);
122             SetTimer(hwnd, DRAGLIST_TIMERID, DRAGLIST_SCROLLPERIOD, NULL);
123         }
124         /* note that we don't absorb this message to let the list box
125          * do its thing (normally selecting an item) */
126         break;
127 
128     case WM_KEYDOWN:
129     case WM_RBUTTONDOWN:
130         /* user cancelled drag by either right clicking or
131          * by pressing the escape key */
132         if ((data->dragging) &&
133             ((uMsg == WM_RBUTTONDOWN) || (wParam == VK_ESCAPE)))
134         {
135             /* clean up and absorb message */
136             DragList_EndDrag(hwnd, data);
137             DragList_Notify(hwnd, DL_CANCELDRAG);
138             return 0;
139         }
140         break;
141 
142     case WM_MOUSEMOVE:
143     case WM_TIMER:
144         if (data->dragging)
145         {
146             LRESULT cursor = DragList_Notify(hwnd, DL_DRAGGING);
147             /* optimisation so that we don't have to load the cursor
148              * all of the time whilst dragging */
149             if (data->last_dragging_response != cursor)
150             {
151                 switch (cursor)
152                 {
153                 case DL_STOPCURSOR:
154                     data->cursor = LoadCursorW(NULL, (LPCWSTR)IDC_NO);
155                     SetCursor(data->cursor);
156                     break;
157                 case DL_COPYCURSOR:
158 #ifndef __REACTOS__
159                     data->cursor = LoadCursorW(COMCTL32_hModule, (LPCWSTR)IDC_COPY);
160 #else
161                     data->cursor = LoadCursorW(COMCTL32_hModule, MAKEINTRESOURCEW(IDC_COPY));
162 #endif
163                     SetCursor(data->cursor);
164                     break;
165                 case DL_MOVECURSOR:
166                     data->cursor = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW);
167                     SetCursor(data->cursor);
168                     break;
169                 }
170                 data->last_dragging_response = cursor;
171             }
172             /* don't pass this message on to List Box */
173             return 0;
174         }
175         break;
176 
177     case WM_LBUTTONUP:
178         if (data->dragging)
179         {
180             DragList_EndDrag(hwnd, data);
181             DragList_Notify(hwnd, DL_DROPPED);
182         }
183         break;
184 
185     case WM_GETDLGCODE:
186         /* tell dialog boxes that we want to receive WM_KEYDOWN events
187          * for keys like VK_ESCAPE */
188         if (data->dragging)
189             return DLGC_WANTALLKEYS;
190         break;
191     case WM_NCDESTROY:
192         RemoveWindowSubclass(hwnd, DragList_SubclassWindowProc, DRAGLIST_SUBCLASSID);
193         Free(data);
194         break;
195     }
196     return DefSubclassProc(hwnd, uMsg, wParam, lParam);
197 }
198 
199 /***********************************************************************
200  *		MakeDragList (COMCTL32.13)
201  *
202  * Makes a normal ListBox into a DragList by subclassing it.
203  *
204  * RETURNS
205  *      Success: Non-zero
206  *      Failure: Zero
207  */
MakeDragList(HWND hwndLB)208 BOOL WINAPI MakeDragList (HWND hwndLB)
209 {
210     DRAGLISTDATA *data = Alloc(sizeof(DRAGLISTDATA));
211 
212     TRACE("(%p)\n", hwndLB);
213 
214     if (!uDragListMessage)
215         uDragListMessage = RegisterWindowMessageW(DRAGLISTMSGSTRINGW);
216 
217     return SetWindowSubclass(hwndLB, DragList_SubclassWindowProc, DRAGLIST_SUBCLASSID, (DWORD_PTR)data);
218 }
219 
220 /***********************************************************************
221  *		DrawInsert (COMCTL32.15)
222  *
223  * Draws insert arrow by the side of the ListBox item in the parent window.
224  *
225  * RETURNS
226  *      Nothing.
227  */
DrawInsert(HWND hwndParent,HWND hwndLB,INT nItem)228 VOID WINAPI DrawInsert (HWND hwndParent, HWND hwndLB, INT nItem)
229 {
230     RECT rcItem, rcListBox, rcDragIcon;
231     HDC hdc;
232     DRAGLISTDATA * data;
233 
234     TRACE("(%p %p %d)\n", hwndParent, hwndLB, nItem);
235 
236     if (!hDragArrow)
237         hDragArrow = LoadIconW(COMCTL32_hModule, (LPCWSTR)IDI_DRAGARROW);
238 
239     if (LB_ERR == SendMessageW(hwndLB, LB_GETITEMRECT, nItem, (LPARAM)&rcItem))
240         return;
241 
242     if (!GetWindowRect(hwndLB, &rcListBox))
243         return;
244 
245     /* convert item rect to parent co-ordinates */
246     if (!MapWindowPoints(hwndLB, hwndParent, (LPPOINT)&rcItem, 2))
247         return;
248 
249     /* convert list box rect to parent co-ordinates */
250     if (!MapWindowPoints(HWND_DESKTOP, hwndParent, (LPPOINT)&rcListBox, 2))
251         return;
252 
253     rcDragIcon.left = rcListBox.left - DRAGICON_HOTSPOT_X;
254     rcDragIcon.top = rcItem.top - DRAGICON_HOTSPOT_Y;
255     rcDragIcon.right = rcListBox.left;
256     rcDragIcon.bottom = rcDragIcon.top + DRAGICON_HEIGHT;
257 
258     if (!GetWindowSubclass(hwndLB, DragList_SubclassWindowProc, DRAGLIST_SUBCLASSID, (DWORD_PTR*)&data))
259         return;
260 
261     if (nItem < 0)
262         SetRectEmpty(&rcDragIcon);
263 
264     /* prevent flicker by only redrawing when necessary */
265     if (!EqualRect(&rcDragIcon, &data->last_drag_icon_rect))
266     {
267         /* get rid of any previous inserts drawn */
268         RedrawWindow(hwndParent, &data->last_drag_icon_rect, NULL,
269             RDW_INTERNALPAINT | RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
270 
271         data->last_drag_icon_rect = rcDragIcon;
272 
273         if (nItem >= 0)
274         {
275             hdc = GetDC(hwndParent);
276 
277             DrawIcon(hdc, rcDragIcon.left, rcDragIcon.top, hDragArrow);
278 
279             ReleaseDC(hwndParent, hdc);
280         }
281     }
282 }
283 
284 /***********************************************************************
285  *		LBItemFromPt (COMCTL32.14)
286  *
287  * Gets the index of the ListBox item under the specified point,
288  * scrolling if bAutoScroll is TRUE and pt is outside of the ListBox.
289  *
290  * RETURNS
291  *      The ListBox item ID if pt is over a list item or -1 otherwise.
292  */
LBItemFromPt(HWND hwndLB,POINT pt,BOOL bAutoScroll)293 INT WINAPI LBItemFromPt (HWND hwndLB, POINT pt, BOOL bAutoScroll)
294 {
295     RECT rcClient;
296     INT nIndex;
297     DWORD dwScrollTime;
298 
299     TRACE("(%p %d x %d %s)\n",
300            hwndLB, pt.x, pt.y, bAutoScroll ? "TRUE" : "FALSE");
301 
302     ScreenToClient (hwndLB, &pt);
303     GetClientRect (hwndLB, &rcClient);
304     nIndex = (INT)SendMessageW (hwndLB, LB_GETTOPINDEX, 0, 0);
305 
306     if (PtInRect (&rcClient, pt))
307     {
308         /* point is inside -- get the item index */
309         while (TRUE)
310         {
311             if (SendMessageW (hwndLB, LB_GETITEMRECT, nIndex, (LPARAM)&rcClient) == LB_ERR)
312                 return -1;
313 
314             if (PtInRect (&rcClient, pt))
315                 return nIndex;
316 
317             nIndex++;
318         }
319     }
320     else
321     {
322         /* point is outside */
323         if (!bAutoScroll)
324             return -1;
325 
326         if ((pt.x > rcClient.right) || (pt.x < rcClient.left))
327             return -1;
328 
329         if (pt.y < 0)
330             nIndex--;
331         else
332             nIndex++;
333 
334         dwScrollTime = GetTickCount ();
335 
336         if ((dwScrollTime - dwLastScrollTime) < DRAGLIST_SCROLLPERIOD)
337             return -1;
338 
339         dwLastScrollTime = dwScrollTime;
340 
341         SendMessageW (hwndLB, LB_SETTOPINDEX, nIndex, 0);
342     }
343 
344     return -1;
345 }
346