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 #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 */ 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 */ 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 */ 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