* Bring back rbuild build to be used until bug 6372 is fixed.
[reactos.git] / dll / win32 / comctl32 / draglist.c
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 */
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 */
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
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 data->cursor = LoadCursorW(COMCTL32_hModule, (LPCWSTR)IDC_COPY);
159 SetCursor(data->cursor);
160 break;
161 case DL_MOVECURSOR:
162 data->cursor = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW);
163 SetCursor(data->cursor);
164 break;
165 }
166 data->last_dragging_response = cursor;
167 }
168 /* don't pass this message on to List Box */
169 return 0;
170 }
171 break;
172
173 case WM_LBUTTONUP:
174 if (data->dragging)
175 {
176 DragList_EndDrag(hwnd, data);
177 DragList_Notify(hwnd, DL_DROPPED);
178 }
179 break;
180
181 case WM_GETDLGCODE:
182 /* tell dialog boxes that we want to receive WM_KEYDOWN events
183 * for keys like VK_ESCAPE */
184 if (data->dragging)
185 return DLGC_WANTALLKEYS;
186 break;
187 case WM_NCDESTROY:
188 RemoveWindowSubclass(hwnd, DragList_SubclassWindowProc, DRAGLIST_SUBCLASSID);
189 Free(data);
190 break;
191 }
192 return DefSubclassProc(hwnd, uMsg, wParam, lParam);
193 }
194
195 /***********************************************************************
196 * MakeDragList (COMCTL32.13)
197 *
198 * Makes a normal ListBox into a DragList by subclassing it.
199 *
200 * RETURNS
201 * Success: Non-zero
202 * Failure: Zero
203 */
204 BOOL WINAPI MakeDragList (HWND hwndLB)
205 {
206 DRAGLISTDATA *data = Alloc(sizeof(DRAGLISTDATA));
207
208 TRACE("(%p)\n", hwndLB);
209
210 if (!uDragListMessage)
211 uDragListMessage = RegisterWindowMessageW(DRAGLISTMSGSTRINGW);
212
213 return SetWindowSubclass(hwndLB, DragList_SubclassWindowProc, DRAGLIST_SUBCLASSID, (DWORD_PTR)data);
214 }
215
216 /***********************************************************************
217 * DrawInsert (COMCTL32.15)
218 *
219 * Draws insert arrow by the side of the ListBox item in the parent window.
220 *
221 * RETURNS
222 * Nothing.
223 */
224 VOID WINAPI DrawInsert (HWND hwndParent, HWND hwndLB, INT nItem)
225 {
226 RECT rcItem, rcListBox, rcDragIcon;
227 HDC hdc;
228 DRAGLISTDATA * data;
229
230 TRACE("(%p %p %d)\n", hwndParent, hwndLB, nItem);
231
232 if (!hDragArrow)
233 hDragArrow = LoadIconW(COMCTL32_hModule, (LPCWSTR)IDI_DRAGARROW);
234
235 if (LB_ERR == SendMessageW(hwndLB, LB_GETITEMRECT, nItem, (LPARAM)&rcItem))
236 return;
237
238 if (!GetWindowRect(hwndLB, &rcListBox))
239 return;
240
241 /* convert item rect to parent co-ordinates */
242 if (!MapWindowPoints(hwndLB, hwndParent, (LPPOINT)&rcItem, 2))
243 return;
244
245 /* convert list box rect to parent co-ordinates */
246 if (!MapWindowPoints(HWND_DESKTOP, hwndParent, (LPPOINT)&rcListBox, 2))
247 return;
248
249 rcDragIcon.left = rcListBox.left - DRAGICON_HOTSPOT_X;
250 rcDragIcon.top = rcItem.top - DRAGICON_HOTSPOT_Y;
251 rcDragIcon.right = rcListBox.left;
252 rcDragIcon.bottom = rcDragIcon.top + DRAGICON_HEIGHT;
253
254 if (!GetWindowSubclass(hwndLB, DragList_SubclassWindowProc, DRAGLIST_SUBCLASSID, (DWORD_PTR*)&data))
255 return;
256
257 if (nItem < 0)
258 SetRectEmpty(&rcDragIcon);
259
260 /* prevent flicker by only redrawing when necessary */
261 if (!EqualRect(&rcDragIcon, &data->last_drag_icon_rect))
262 {
263 /* get rid of any previous inserts drawn */
264 RedrawWindow(hwndParent, &data->last_drag_icon_rect, NULL,
265 RDW_INTERNALPAINT | RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
266
267 CopyRect(&data->last_drag_icon_rect, &rcDragIcon);
268
269 if (nItem >= 0)
270 {
271 hdc = GetDC(hwndParent);
272
273 DrawIcon(hdc, rcDragIcon.left, rcDragIcon.top, hDragArrow);
274
275 ReleaseDC(hwndParent, hdc);
276 }
277 }
278 }
279
280 /***********************************************************************
281 * LBItemFromPt (COMCTL32.14)
282 *
283 * Gets the index of the ListBox item under the specified point,
284 * scrolling if bAutoScroll is TRUE and pt is outside of the ListBox.
285 *
286 * RETURNS
287 * The ListBox item ID if pt is over a list item or -1 otherwise.
288 */
289 INT WINAPI LBItemFromPt (HWND hwndLB, POINT pt, BOOL bAutoScroll)
290 {
291 RECT rcClient;
292 INT nIndex;
293 DWORD dwScrollTime;
294
295 TRACE("(%p %d x %d %s)\n",
296 hwndLB, pt.x, pt.y, bAutoScroll ? "TRUE" : "FALSE");
297
298 ScreenToClient (hwndLB, &pt);
299 GetClientRect (hwndLB, &rcClient);
300 nIndex = (INT)SendMessageW (hwndLB, LB_GETTOPINDEX, 0, 0);
301
302 if (PtInRect (&rcClient, pt))
303 {
304 /* point is inside -- get the item index */
305 while (TRUE)
306 {
307 if (SendMessageW (hwndLB, LB_GETITEMRECT, nIndex, (LPARAM)&rcClient) == LB_ERR)
308 return -1;
309
310 if (PtInRect (&rcClient, pt))
311 return nIndex;
312
313 nIndex++;
314 }
315 }
316 else
317 {
318 /* point is outside */
319 if (!bAutoScroll)
320 return -1;
321
322 if ((pt.x > rcClient.right) || (pt.x < rcClient.left))
323 return -1;
324
325 if (pt.y < 0)
326 nIndex--;
327 else
328 nIndex++;
329
330 dwScrollTime = GetTickCount ();
331
332 if ((dwScrollTime - dwLastScrollTime) < DRAGLIST_SCROLLPERIOD)
333 return -1;
334
335 dwLastScrollTime = dwScrollTime;
336
337 SendMessageW (hwndLB, LB_SETTOPINDEX, nIndex, 0);
338 }
339
340 return -1;
341 }