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 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 } 342