Add NM_RCLICK notification support for toolbars.
[wine] / dlls / 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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 /* for compiler compatibility we only accept literal ASCII strings */
46 #undef TEXT
47 #define TEXT(string) string
48
49 #define DRAGLIST_SUBCLASSID     0
50 #define DRAGLIST_SCROLLPERIOD 200
51 #define DRAGLIST_TIMERID      666
52
53 /* properties relating to IDI_DRAGICON */
54 #define DRAGICON_HOTSPOT_X 17
55 #define DRAGICON_HOTSPOT_Y  7
56 #define DRAGICON_HEIGHT    32
57
58 /* internal Wine specific data for the drag list control */
59 typedef struct _DRAGLISTDATA
60 {
61     /* are we currently in dragging mode? */
62     BOOL dragging;
63
64     /* cursor to use as determined by DL_DRAGGING notification.
65      * NOTE: as we use LoadCursor we don't have to use DeleteCursor
66      * when we are finished with it */
67     HCURSOR cursor;
68
69     /* optimisation so that we don't have to load the cursor
70      * all of the time whilst dragging */
71     LRESULT last_dragging_response;
72
73     /* prevents flicker with drawing drag arrow */
74     RECT last_drag_icon_rect;
75 } DRAGLISTDATA;
76
77 static UINT uDragListMessage = 0; /* registered window message code */
78 static DWORD dwLastScrollTime = 0;
79 static HICON hDragArrow = NULL;
80
81 /***********************************************************************
82  *              DragList_Notify (internal)
83  *
84  * Sends notification messages to the parent control. Note that it
85  * does not use WM_NOTIFY like the rest of the controls, but a registered
86  * window message.
87  */
88 static LRESULT DragList_Notify(HWND hwndLB, UINT uNotification)
89 {
90     DRAGLISTINFO dli;
91     dli.hWnd = hwndLB;
92     dli.uNotification = uNotification;
93     GetCursorPos(&dli.ptCursor);
94     return SendMessageW(GetParent(hwndLB), uDragListMessage, GetDlgCtrlID(hwndLB), (LPARAM)&dli);
95 }
96
97 /* cleans up after dragging */
98 static inline void DragList_EndDrag(HWND hwnd, DRAGLISTDATA * data)
99 {
100     KillTimer(hwnd, DRAGLIST_TIMERID);
101     ReleaseCapture();
102     data->dragging = FALSE;
103     /* clear any drag insert icon present */
104     InvalidateRect(GetParent(hwnd), &data->last_drag_icon_rect, TRUE);
105 }
106
107 /***********************************************************************
108  *              DragList_SubclassWindowProc (internal)
109  *
110  * Handles certain messages to enable dragging for the ListBox and forwards
111  * the rest to the ListBox.
112  */
113 static LRESULT CALLBACK
114 DragList_SubclassWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
115 {
116     DRAGLISTDATA * data = (DRAGLISTDATA*)dwRefData;
117     switch (uMsg)
118     {
119     case WM_LBUTTONDOWN:
120         SetFocus(hwnd);
121         data->cursor = NULL;
122         SetRectEmpty(&data->last_drag_icon_rect);
123         data->dragging = DragList_Notify(hwnd, DL_BEGINDRAG);
124         if (data->dragging)
125         {
126             SetCapture(hwnd);
127             SetTimer(hwnd, DRAGLIST_TIMERID, DRAGLIST_SCROLLPERIOD, NULL);
128         }
129         /* note that we don't absorb this message to let the list box
130          * do its thing (normally selecting an item) */
131         break;
132
133     case WM_KEYDOWN:
134     case WM_RBUTTONDOWN:
135         /* user cancelled drag by either right clicking or
136          * by pressing the escape key */
137         if ((data->dragging) &&
138             ((uMsg == WM_RBUTTONDOWN) || (wParam == VK_ESCAPE)))
139         {
140             /* clean up and absorb message */
141             DragList_EndDrag(hwnd, data);
142             DragList_Notify(hwnd, DL_CANCELDRAG);
143             return 0;
144         }
145         break;
146
147     case WM_MOUSEMOVE:
148     case WM_TIMER:
149         if (data->dragging)
150         {
151             LRESULT cursor = DragList_Notify(hwnd, DL_DRAGGING);
152             /* optimisation so that we don't have to load the cursor
153              * all of the time whilst dragging */
154             if (data->last_dragging_response != cursor)
155             {
156                 switch (cursor)
157                 {
158                 case DL_STOPCURSOR:
159                     data->cursor = LoadCursorW(NULL, (LPCWSTR)IDC_NO);
160                     SetCursor(data->cursor);
161                     break;
162                 case DL_COPYCURSOR:
163                     data->cursor = LoadCursorW(COMCTL32_hModule, (LPCWSTR)IDC_COPY);
164                     SetCursor(data->cursor);
165                     break;
166                 case DL_MOVECURSOR:
167                     data->cursor = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW);
168                     SetCursor(data->cursor);
169                     break;
170                 }
171                 data->last_dragging_response = cursor;
172             }
173             /* don't pass this message on to List Box */
174             return 0;
175         }
176         break;
177
178     case WM_LBUTTONUP:
179         if (data->dragging)
180         {
181             DragList_EndDrag(hwnd, data);
182             DragList_Notify(hwnd, DL_DROPPED);
183         }
184         break;
185
186         case WM_SETCURSOR:
187             /* if app has told us to set a cursor then do so */
188         if (data->dragging && data->cursor)
189         {
190             SetCursor(data->cursor);
191             return TRUE;
192         }
193         break;
194
195     case WM_GETDLGCODE:
196         /* tell dialog boxes that we want to receive WM_KEYDOWN events
197          * for keys like VK_ESCAPE */
198         if (data->dragging)
199             return DLGC_WANTALLKEYS;
200         break;
201     case WM_NCDESTROY:
202         RemoveWindowSubclass(hwnd, DragList_SubclassWindowProc, DRAGLIST_SUBCLASSID);
203         Free(data);
204         break;
205     }
206     return DefSubclassProc(hwnd, uMsg, wParam, lParam);
207 }
208
209 /***********************************************************************
210  *              MakeDragList (COMCTL32.13)
211  *
212  * Makes a normal ListBox into a DragList by subclassing it.
213  *
214  * RETURNS
215  *      Success: Non-zero
216  *      Failure: Zero
217  */
218 BOOL WINAPI MakeDragList (HWND hwndLB)
219 {
220     DRAGLISTDATA * data = Alloc(sizeof(DRAGLISTDATA));
221
222     TRACE("(%p)\n", hwndLB);
223
224     if (!uDragListMessage)
225         uDragListMessage = RegisterWindowMessageA(DRAGLISTMSGSTRING);
226
227     return SetWindowSubclass(hwndLB, DragList_SubclassWindowProc, DRAGLIST_SUBCLASSID, (DWORD_PTR)data);
228 }
229
230 /***********************************************************************
231  *              DrawInsert (COMCTL32.15)
232  *
233  * Draws insert arrow by the side of the ListBox item in the parent window.
234  *
235  * RETURNS
236  *      Nothing.
237  */
238 VOID WINAPI DrawInsert (HWND hwndParent, HWND hwndLB, INT nItem)
239 {
240     RECT rcItem, rcListBox, rcDragIcon;
241     HDC hdc;
242     DRAGLISTDATA * data;
243
244     TRACE("(%p %p %d)\n", hwndParent, hwndLB, nItem);
245
246     if (!hDragArrow)
247         hDragArrow = LoadIconW(COMCTL32_hModule, (LPCWSTR)IDI_DRAGARROW);
248
249     if (LB_ERR == SendMessageW(hwndLB, LB_GETITEMRECT, nItem, (LPARAM)&rcItem))
250         return;
251
252     if (!GetWindowRect(hwndLB, &rcListBox))
253         return;
254
255     /* convert item rect to parent co-ordinates */
256     if (!MapWindowPoints(hwndLB, hwndParent, (LPPOINT)&rcItem, 2))
257         return;
258
259     /* convert list box rect to parent co-ordinates */
260     if (!MapWindowPoints(HWND_DESKTOP, hwndParent, (LPPOINT)&rcListBox, 2))
261         return;
262
263     rcDragIcon.left = rcListBox.left - DRAGICON_HOTSPOT_X;
264     rcDragIcon.top = rcItem.top - DRAGICON_HOTSPOT_Y;
265     rcDragIcon.right = rcListBox.left;
266     rcDragIcon.bottom = rcDragIcon.top + DRAGICON_HEIGHT;
267
268     if (!GetWindowSubclass(hwndLB, DragList_SubclassWindowProc, DRAGLIST_SUBCLASSID, (DWORD_PTR*)&data))
269         return;
270
271     if (nItem < 0)
272         SetRectEmpty(&rcDragIcon);
273
274     /* prevent flicker by only redrawing when necessary */
275     if (!EqualRect(&rcDragIcon, &data->last_drag_icon_rect))
276     {
277         /* get rid of any previous inserts drawn */
278         RedrawWindow(hwndParent, &data->last_drag_icon_rect, NULL,
279             RDW_INTERNALPAINT | RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
280
281         CopyRect(&data->last_drag_icon_rect, &rcDragIcon);
282
283         if (nItem >= 0)
284         {
285             hdc = GetDC(hwndParent);
286
287             DrawIcon(hdc, rcDragIcon.left, rcDragIcon.top, hDragArrow);
288
289             ReleaseDC(hwndParent, hdc);
290         }
291     }
292 }
293
294 /***********************************************************************
295  *              LBItemFromPt (COMCTL32.14)
296  *
297  * Gets the index of the ListBox item under the specified point,
298  * scrolling if bAutoScroll is TRUE and pt is outside of the ListBox.
299  *
300  * RETURNS
301  *      The ListBox item ID if pt is over a list item or -1 otherwise.
302  */
303 INT WINAPI LBItemFromPt (HWND hwndLB, POINT pt, BOOL bAutoScroll)
304 {
305     RECT rcClient;
306     INT nIndex;
307     DWORD dwScrollTime;
308
309     TRACE("(%p %ld x %ld %s)\n",
310            hwndLB, pt.x, pt.y, bAutoScroll ? "TRUE" : "FALSE");
311
312     ScreenToClient (hwndLB, &pt);
313     GetClientRect (hwndLB, &rcClient);
314     nIndex = (INT)SendMessageA (hwndLB, LB_GETTOPINDEX, 0, 0);
315
316     if (PtInRect (&rcClient, pt))
317     {
318         /* point is inside -- get the item index */
319         while (TRUE)
320         {
321             if (SendMessageA (hwndLB, LB_GETITEMRECT, nIndex, (LPARAM)&rcClient) == LB_ERR)
322                 return -1;
323
324             if (PtInRect (&rcClient, pt))
325                 return nIndex;
326
327             nIndex++;
328         }
329     }
330     else
331     {
332         /* point is outside */
333         if (!bAutoScroll)
334             return -1;
335
336         if ((pt.x > rcClient.right) || (pt.x < rcClient.left))
337             return -1;
338
339         if (pt.y < 0)
340             nIndex--;
341         else
342             nIndex++;
343
344         dwScrollTime = GetTickCount ();
345
346         if ((dwScrollTime - dwLastScrollTime) < DRAGLIST_SCROLLPERIOD)
347             return -1;
348
349         dwLastScrollTime = dwScrollTime;
350
351         SendMessageA (hwndLB, LB_SETTOPINDEX, (WPARAM)nIndex, 0);
352     }
353
354     return -1;
355 }