1 /* 2 * Listbox controls 3 * 4 * Copyright 1996 Alexandre Julliard 5 * Copyright 2005 Frank Richter 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 * TODO: 22 * - LBS_NODATA 23 */ 24 25 #include <string.h> 26 #include <stdlib.h> 27 #include <stdarg.h> 28 #include <stdio.h> 29 #include "windef.h" 30 #include "winbase.h" 31 #include "wingdi.h" 32 #include "winuser.h" 33 #include "commctrl.h" 34 #include "uxtheme.h" 35 #include "vssym32.h" 36 #include "wine/unicode.h" 37 #include "wine/exception.h" 38 #include "wine/debug.h" 39 40 #include "comctl32.h" 41 42 WINE_DEFAULT_DEBUG_CHANNEL(listbox2); 43 44 /* Items array granularity */ 45 #define LB_ARRAY_GRANULARITY 16 46 47 /* Scrolling timeout in ms */ 48 #define LB_SCROLL_TIMEOUT 50 49 50 /* Listbox system timer id */ 51 #define LB_TIMER_ID 2 52 53 /* flag listbox changed while setredraw false - internal style */ 54 #define LBS_DISPLAYCHANGED 0x80000000 55 56 /* Item structure */ 57 typedef struct 58 { 59 LPWSTR str; /* Item text */ 60 BOOL selected; /* Is item selected? */ 61 UINT height; /* Item height (only for OWNERDRAWVARIABLE) */ 62 ULONG_PTR data; /* User data */ 63 } LB_ITEMDATA; 64 65 /* Listbox structure */ 66 typedef struct 67 { 68 HWND self; /* Our own window handle */ 69 HWND owner; /* Owner window to send notifications to */ 70 UINT style; /* Window style */ 71 INT width; /* Window width */ 72 INT height; /* Window height */ 73 LB_ITEMDATA *items; /* Array of items */ 74 INT nb_items; /* Number of items */ 75 INT top_item; /* Top visible item */ 76 INT selected_item; /* Selected item */ 77 INT focus_item; /* Item that has the focus */ 78 INT anchor_item; /* Anchor item for extended selection */ 79 INT item_height; /* Default item height */ 80 INT page_size; /* Items per listbox page */ 81 INT column_width; /* Column width for multi-column listboxes */ 82 INT horz_extent; /* Horizontal extent */ 83 INT horz_pos; /* Horizontal position */ 84 INT nb_tabs; /* Number of tabs in array */ 85 INT *tabs; /* Array of tabs */ 86 INT avg_char_width; /* Average width of characters */ 87 INT wheel_remain; /* Left over scroll amount */ 88 BOOL caret_on; /* Is caret on? */ 89 BOOL captured; /* Is mouse captured? */ 90 BOOL in_focus; 91 HFONT font; /* Current font */ 92 LCID locale; /* Current locale for string comparisons */ 93 HEADCOMBO *lphc; /* ComboLBox */ 94 } LB_DESCR; 95 96 97 #define IS_OWNERDRAW(descr) \ 98 ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE)) 99 100 #define HAS_STRINGS(descr) \ 101 (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS)) 102 103 104 #define IS_MULTISELECT(descr) \ 105 ((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \ 106 !((descr)->style & LBS_NOSEL)) 107 108 #define SEND_NOTIFICATION(descr,code) \ 109 (SendMessageW( (descr)->owner, WM_COMMAND, \ 110 MAKEWPARAM( GetWindowLongPtrW((descr->self),GWLP_ID), (code)), (LPARAM)(descr->self) )) 111 112 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03) 113 114 /* Current timer status */ 115 typedef enum 116 { 117 LB_TIMER_NONE, 118 LB_TIMER_UP, 119 LB_TIMER_LEFT, 120 LB_TIMER_DOWN, 121 LB_TIMER_RIGHT 122 } TIMER_DIRECTION; 123 124 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE; 125 126 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect ); 127 128 /*********************************************************************** 129 * LISTBOX_GetCurrentPageSize 130 * 131 * Return the current page size 132 */ 133 static INT LISTBOX_GetCurrentPageSize( const LB_DESCR *descr ) 134 { 135 INT i, height; 136 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size; 137 for (i = descr->top_item, height = 0; i < descr->nb_items; i++) 138 { 139 if ((height += descr->items[i].height) > descr->height) break; 140 } 141 if (i == descr->top_item) return 1; 142 else return i - descr->top_item; 143 } 144 145 146 /*********************************************************************** 147 * LISTBOX_GetMaxTopIndex 148 * 149 * Return the maximum possible index for the top of the listbox. 150 */ 151 static INT LISTBOX_GetMaxTopIndex( const LB_DESCR *descr ) 152 { 153 INT max, page; 154 155 if (descr->style & LBS_OWNERDRAWVARIABLE) 156 { 157 page = descr->height; 158 for (max = descr->nb_items - 1; max >= 0; max--) 159 if ((page -= descr->items[max].height) < 0) break; 160 if (max < descr->nb_items - 1) max++; 161 } 162 else if (descr->style & LBS_MULTICOLUMN) 163 { 164 if ((page = descr->width / descr->column_width) < 1) page = 1; 165 max = (descr->nb_items + descr->page_size - 1) / descr->page_size; 166 max = (max - page) * descr->page_size; 167 } 168 else 169 { 170 max = descr->nb_items - descr->page_size; 171 } 172 if (max < 0) max = 0; 173 return max; 174 } 175 176 177 /*********************************************************************** 178 * LISTBOX_UpdateScroll 179 * 180 * Update the scrollbars. Should be called whenever the content 181 * of the listbox changes. 182 */ 183 static void LISTBOX_UpdateScroll( LB_DESCR *descr ) 184 { 185 SCROLLINFO info; 186 187 /* Check the listbox scroll bar flags individually before we call 188 SetScrollInfo otherwise when the listbox style is WS_HSCROLL and 189 no WS_VSCROLL, we end up with an uninitialized, visible horizontal 190 scroll bar when we do not need one. 191 if (!(descr->style & WS_VSCROLL)) return; 192 */ 193 194 /* It is important that we check descr->style, and not wnd->dwStyle, 195 for WS_VSCROLL, as the former is exactly the one passed in 196 argument to CreateWindow. 197 In Windows (and from now on in Wine :) a listbox created 198 with such a style (no WS_SCROLL) does not update 199 the scrollbar with listbox-related data, thus letting 200 the programmer use it for his/her own purposes. */ 201 202 if (descr->style & LBS_NOREDRAW) return; 203 info.cbSize = sizeof(info); 204 205 if (descr->style & LBS_MULTICOLUMN) 206 { 207 info.nMin = 0; 208 info.nMax = (descr->nb_items - 1) / descr->page_size; 209 info.nPos = descr->top_item / descr->page_size; 210 info.nPage = descr->width / descr->column_width; 211 if (info.nPage < 1) info.nPage = 1; 212 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE; 213 if (descr->style & LBS_DISABLENOSCROLL) 214 info.fMask |= SIF_DISABLENOSCROLL; 215 if (descr->style & WS_HSCROLL) 216 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE ); 217 info.nMax = 0; 218 info.fMask = SIF_RANGE; 219 if (descr->style & WS_VSCROLL) 220 SetScrollInfo( descr->self, SB_VERT, &info, TRUE ); 221 } 222 else 223 { 224 info.nMin = 0; 225 info.nMax = descr->nb_items - 1; 226 info.nPos = descr->top_item; 227 info.nPage = LISTBOX_GetCurrentPageSize( descr ); 228 info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE; 229 if (descr->style & LBS_DISABLENOSCROLL) 230 info.fMask |= SIF_DISABLENOSCROLL; 231 if (descr->style & WS_VSCROLL) 232 SetScrollInfo( descr->self, SB_VERT, &info, TRUE ); 233 234 if ((descr->style & WS_HSCROLL) && descr->horz_extent) 235 { 236 info.nPos = descr->horz_pos; 237 info.nPage = descr->width; 238 info.fMask = SIF_POS | SIF_PAGE; 239 if (descr->style & LBS_DISABLENOSCROLL) 240 info.fMask |= SIF_DISABLENOSCROLL; 241 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE ); 242 } 243 else 244 { 245 if (descr->style & LBS_DISABLENOSCROLL) 246 { 247 info.nMin = 0; 248 info.nMax = 0; 249 info.fMask = SIF_RANGE | SIF_DISABLENOSCROLL; 250 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE ); 251 } 252 else 253 { 254 ShowScrollBar( descr->self, SB_HORZ, FALSE ); 255 } 256 } 257 } 258 } 259 260 261 /*********************************************************************** 262 * LISTBOX_SetTopItem 263 * 264 * Set the top item of the listbox, scrolling up or down if necessary. 265 */ 266 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll ) 267 { 268 INT max = LISTBOX_GetMaxTopIndex( descr ); 269 270 TRACE("setting top item %d, scroll %d\n", index, scroll); 271 272 if (index > max) index = max; 273 if (index < 0) index = 0; 274 if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size; 275 if (descr->top_item == index) return LB_OKAY; 276 if (scroll) 277 { 278 INT diff; 279 if (descr->style & LBS_MULTICOLUMN) 280 diff = (descr->top_item - index) / descr->page_size * descr->column_width; 281 else if (descr->style & LBS_OWNERDRAWVARIABLE) 282 { 283 INT i; 284 diff = 0; 285 if (index > descr->top_item) 286 { 287 for (i = index - 1; i >= descr->top_item; i--) 288 diff -= descr->items[i].height; 289 } 290 else 291 { 292 for (i = index; i < descr->top_item; i++) 293 diff += descr->items[i].height; 294 } 295 } 296 else 297 diff = (descr->top_item - index) * descr->item_height; 298 299 ScrollWindowEx( descr->self, 0, diff, NULL, NULL, 0, NULL, 300 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN ); 301 } 302 else 303 InvalidateRect( descr->self, NULL, TRUE ); 304 descr->top_item = index; 305 LISTBOX_UpdateScroll( descr ); 306 return LB_OKAY; 307 } 308 309 310 /*********************************************************************** 311 * LISTBOX_UpdatePage 312 * 313 * Update the page size. Should be called when the size of 314 * the client area or the item height changes. 315 */ 316 static void LISTBOX_UpdatePage( LB_DESCR *descr ) 317 { 318 INT page_size; 319 320 if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1) 321 page_size = 1; 322 if (page_size == descr->page_size) return; 323 descr->page_size = page_size; 324 if (descr->style & LBS_MULTICOLUMN) 325 InvalidateRect( descr->self, NULL, TRUE ); 326 LISTBOX_SetTopItem( descr, descr->top_item, FALSE ); 327 } 328 329 330 /*********************************************************************** 331 * LISTBOX_UpdateSize 332 * 333 * Update the size of the listbox. Should be called when the size of 334 * the client area changes. 335 */ 336 static void LISTBOX_UpdateSize( LB_DESCR *descr ) 337 { 338 RECT rect; 339 340 GetClientRect( descr->self, &rect ); 341 descr->width = rect.right - rect.left; 342 descr->height = rect.bottom - rect.top; 343 if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE)) 344 { 345 INT remaining; 346 RECT rect; 347 348 GetWindowRect( descr->self, &rect ); 349 if(descr->item_height != 0) 350 remaining = descr->height % descr->item_height; 351 else 352 remaining = 0; 353 if ((descr->height > descr->item_height) && remaining) 354 { 355 TRACE("[%p]: changing height %d -> %d\n", 356 descr->self, descr->height, descr->height - remaining ); 357 SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left, 358 rect.bottom - rect.top - remaining, 359 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE ); 360 return; 361 } 362 } 363 TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height ); 364 LISTBOX_UpdatePage( descr ); 365 LISTBOX_UpdateScroll( descr ); 366 367 /* Invalidate the focused item so it will be repainted correctly */ 368 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1) 369 { 370 InvalidateRect( descr->self, &rect, FALSE ); 371 } 372 } 373 374 375 /*********************************************************************** 376 * LISTBOX_GetItemRect 377 * 378 * Get the rectangle enclosing an item, in listbox client coordinates. 379 * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error. 380 */ 381 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect ) 382 { 383 /* Index <= 0 is legal even on empty listboxes */ 384 if (index && (index >= descr->nb_items)) 385 { 386 SetRectEmpty(rect); 387 SetLastError(ERROR_INVALID_INDEX); 388 return LB_ERR; 389 } 390 SetRect( rect, 0, 0, descr->width, descr->height ); 391 if (descr->style & LBS_MULTICOLUMN) 392 { 393 INT col = (index / descr->page_size) - 394 (descr->top_item / descr->page_size); 395 rect->left += col * descr->column_width; 396 rect->right = rect->left + descr->column_width; 397 rect->top += (index % descr->page_size) * descr->item_height; 398 rect->bottom = rect->top + descr->item_height; 399 } 400 else if (descr->style & LBS_OWNERDRAWVARIABLE) 401 { 402 INT i; 403 rect->right += descr->horz_pos; 404 if ((index >= 0) && (index < descr->nb_items)) 405 { 406 if (index < descr->top_item) 407 { 408 for (i = descr->top_item-1; i >= index; i--) 409 rect->top -= descr->items[i].height; 410 } 411 else 412 { 413 for (i = descr->top_item; i < index; i++) 414 rect->top += descr->items[i].height; 415 } 416 rect->bottom = rect->top + descr->items[index].height; 417 418 } 419 } 420 else 421 { 422 rect->top += (index - descr->top_item) * descr->item_height; 423 rect->bottom = rect->top + descr->item_height; 424 rect->right += descr->horz_pos; 425 } 426 427 TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect)); 428 429 return ((rect->left < descr->width) && (rect->right > 0) && 430 (rect->top < descr->height) && (rect->bottom > 0)); 431 } 432 433 434 /*********************************************************************** 435 * LISTBOX_GetItemFromPoint 436 * 437 * Return the item nearest from point (x,y) (in client coordinates). 438 */ 439 static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y ) 440 { 441 INT index = descr->top_item; 442 443 if (!descr->nb_items) return -1; /* No items */ 444 if (descr->style & LBS_OWNERDRAWVARIABLE) 445 { 446 INT pos = 0; 447 if (y >= 0) 448 { 449 while (index < descr->nb_items) 450 { 451 if ((pos += descr->items[index].height) > y) break; 452 index++; 453 } 454 } 455 else 456 { 457 while (index > 0) 458 { 459 index--; 460 if ((pos -= descr->items[index].height) <= y) break; 461 } 462 } 463 } 464 else if (descr->style & LBS_MULTICOLUMN) 465 { 466 if (y >= descr->item_height * descr->page_size) return -1; 467 if (y >= 0) index += y / descr->item_height; 468 if (x >= 0) index += (x / descr->column_width) * descr->page_size; 469 else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size; 470 } 471 else 472 { 473 index += (y / descr->item_height); 474 } 475 if (index < 0) return 0; 476 if (index >= descr->nb_items) return -1; 477 return index; 478 } 479 480 481 /*********************************************************************** 482 * LISTBOX_PaintItem 483 * 484 * Paint an item. 485 */ 486 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect, 487 INT index, UINT action, BOOL ignoreFocus ) 488 { 489 LB_ITEMDATA *item = NULL; 490 if (index < descr->nb_items) item = &descr->items[index]; 491 492 if (IS_OWNERDRAW(descr)) 493 { 494 DRAWITEMSTRUCT dis; 495 RECT r; 496 HRGN hrgn; 497 498 if (!item) 499 { 500 if (action == ODA_FOCUS) 501 DrawFocusRect( hdc, rect ); 502 else 503 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items); 504 return; 505 } 506 507 /* some programs mess with the clipping region when 508 drawing the item, *and* restore the previous region 509 after they are done, so a region has better to exist 510 else everything ends clipped */ 511 GetClientRect(descr->self, &r); 512 hrgn = set_control_clipping( hdc, &r ); 513 514 dis.CtlType = ODT_LISTBOX; 515 dis.CtlID = GetWindowLongPtrW( descr->self, GWLP_ID ); 516 dis.hwndItem = descr->self; 517 dis.itemAction = action; 518 dis.hDC = hdc; 519 dis.itemID = index; 520 dis.itemState = 0; 521 if (item->selected) dis.itemState |= ODS_SELECTED; 522 if (!ignoreFocus && (descr->focus_item == index) && 523 (descr->caret_on) && 524 (descr->in_focus)) dis.itemState |= ODS_FOCUS; 525 if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED; 526 dis.itemData = item->data; 527 dis.rcItem = *rect; 528 TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n", 529 descr->self, index, debugstr_w(item->str), action, 530 dis.itemState, wine_dbgstr_rect(rect) ); 531 SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis); 532 SelectClipRgn( hdc, hrgn ); 533 if (hrgn) DeleteObject( hrgn ); 534 } 535 else 536 { 537 COLORREF oldText = 0, oldBk = 0; 538 539 if (action == ODA_FOCUS) 540 { 541 DrawFocusRect( hdc, rect ); 542 return; 543 } 544 if (item && item->selected) 545 { 546 oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) ); 547 oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT)); 548 } 549 550 TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n", 551 descr->self, index, item ? debugstr_w(item->str) : "", action, 552 wine_dbgstr_rect(rect) ); 553 if (!item) 554 ExtTextOutW( hdc, rect->left + 1, rect->top, 555 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL ); 556 else if (!(descr->style & LBS_USETABSTOPS)) 557 ExtTextOutW( hdc, rect->left + 1, rect->top, 558 ETO_OPAQUE | ETO_CLIPPED, rect, item->str, 559 strlenW(item->str), NULL ); 560 else 561 { 562 /* Output empty string to paint background in the full width. */ 563 ExtTextOutW( hdc, rect->left + 1, rect->top, 564 ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL ); 565 TabbedTextOutW( hdc, rect->left + 1 , rect->top, 566 item->str, strlenW(item->str), 567 descr->nb_tabs, descr->tabs, 0); 568 } 569 if (item && item->selected) 570 { 571 SetBkColor( hdc, oldBk ); 572 SetTextColor( hdc, oldText ); 573 } 574 if (!ignoreFocus && (descr->focus_item == index) && 575 (descr->caret_on) && 576 (descr->in_focus)) DrawFocusRect( hdc, rect ); 577 } 578 } 579 580 581 /*********************************************************************** 582 * LISTBOX_SetRedraw 583 * 584 * Change the redraw flag. 585 */ 586 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on ) 587 { 588 if (on) 589 { 590 if (!(descr->style & LBS_NOREDRAW)) return; 591 descr->style &= ~LBS_NOREDRAW; 592 if (descr->style & LBS_DISPLAYCHANGED) 593 { /* page was changed while setredraw false, refresh automatically */ 594 InvalidateRect(descr->self, NULL, TRUE); 595 if ((descr->top_item + descr->page_size) > descr->nb_items) 596 { /* reset top of page if less than number of items/page */ 597 descr->top_item = descr->nb_items - descr->page_size; 598 if (descr->top_item < 0) descr->top_item = 0; 599 } 600 descr->style &= ~LBS_DISPLAYCHANGED; 601 } 602 LISTBOX_UpdateScroll( descr ); 603 } 604 else descr->style |= LBS_NOREDRAW; 605 } 606 607 608 /*********************************************************************** 609 * LISTBOX_RepaintItem 610 * 611 * Repaint a single item synchronously. 612 */ 613 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action ) 614 { 615 HDC hdc; 616 RECT rect; 617 HFONT oldFont = 0; 618 HBRUSH hbrush, oldBrush = 0; 619 620 /* Do not repaint the item if the item is not visible */ 621 if (!IsWindowVisible(descr->self)) return; 622 if (descr->style & LBS_NOREDRAW) 623 { 624 descr->style |= LBS_DISPLAYCHANGED; 625 return; 626 } 627 if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return; 628 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return; 629 if (descr->font) oldFont = SelectObject( hdc, descr->font ); 630 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX, 631 (WPARAM)hdc, (LPARAM)descr->self ); 632 if (hbrush) oldBrush = SelectObject( hdc, hbrush ); 633 if (!IsWindowEnabled(descr->self)) 634 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) ); 635 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL ); 636 LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE ); 637 if (oldFont) SelectObject( hdc, oldFont ); 638 if (oldBrush) SelectObject( hdc, oldBrush ); 639 ReleaseDC( descr->self, hdc ); 640 } 641 642 643 /*********************************************************************** 644 * LISTBOX_DrawFocusRect 645 */ 646 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on ) 647 { 648 HDC hdc; 649 RECT rect; 650 HFONT oldFont = 0; 651 652 /* Do not repaint the item if the item is not visible */ 653 if (!IsWindowVisible(descr->self)) return; 654 655 if (descr->focus_item == -1) return; 656 if (!descr->caret_on || !descr->in_focus) return; 657 658 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return; 659 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return; 660 if (descr->font) oldFont = SelectObject( hdc, descr->font ); 661 if (!IsWindowEnabled(descr->self)) 662 SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) ); 663 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL ); 664 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, !on ); 665 if (oldFont) SelectObject( hdc, oldFont ); 666 ReleaseDC( descr->self, hdc ); 667 } 668 669 670 /*********************************************************************** 671 * LISTBOX_InitStorage 672 */ 673 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items ) 674 { 675 LB_ITEMDATA *item; 676 677 nb_items += LB_ARRAY_GRANULARITY - 1; 678 nb_items -= (nb_items % LB_ARRAY_GRANULARITY); 679 if (descr->items) { 680 nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item); 681 item = HeapReAlloc( GetProcessHeap(), 0, descr->items, 682 nb_items * sizeof(LB_ITEMDATA)); 683 } 684 else { 685 item = HeapAlloc( GetProcessHeap(), 0, 686 nb_items * sizeof(LB_ITEMDATA)); 687 } 688 689 if (!item) 690 { 691 SEND_NOTIFICATION( descr, LBN_ERRSPACE ); 692 return LB_ERRSPACE; 693 } 694 descr->items = item; 695 return LB_OKAY; 696 } 697 698 699 /*********************************************************************** 700 * LISTBOX_SetTabStops 701 */ 702 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs ) 703 { 704 INT i; 705 706 if (!(descr->style & LBS_USETABSTOPS)) 707 { 708 SetLastError(ERROR_LB_WITHOUT_TABSTOPS); 709 return FALSE; 710 } 711 712 HeapFree( GetProcessHeap(), 0, descr->tabs ); 713 if (!(descr->nb_tabs = count)) 714 { 715 descr->tabs = NULL; 716 return TRUE; 717 } 718 if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0, 719 descr->nb_tabs * sizeof(INT) ))) 720 return FALSE; 721 memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) ); 722 723 /* convert into "dialog units"*/ 724 for (i = 0; i < descr->nb_tabs; i++) 725 descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4); 726 727 return TRUE; 728 } 729 730 731 /*********************************************************************** 732 * LISTBOX_GetText 733 */ 734 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode ) 735 { 736 DWORD len; 737 738 if ((index < 0) || (index >= descr->nb_items)) 739 { 740 SetLastError(ERROR_INVALID_INDEX); 741 return LB_ERR; 742 } 743 744 if (HAS_STRINGS(descr)) 745 { 746 if (!buffer) 747 return strlenW(descr->items[index].str); 748 749 TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str)); 750 751 __TRY /* hide a Delphi bug that passes a read-only buffer */ 752 { 753 strcpyW( buffer, descr->items[index].str ); 754 len = strlenW(buffer); 755 } 756 __EXCEPT_PAGE_FAULT 757 { 758 WARN( "got an invalid buffer (Delphi bug?)\n" ); 759 SetLastError( ERROR_INVALID_PARAMETER ); 760 return LB_ERR; 761 } 762 __ENDTRY 763 } else 764 { 765 if (buffer) 766 *((DWORD *)buffer) = *(DWORD *)&descr->items[index].data; 767 len = sizeof(DWORD); 768 } 769 return len; 770 } 771 772 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 ) 773 { 774 INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 ); 775 if (ret == CSTR_LESS_THAN) 776 return -1; 777 if (ret == CSTR_EQUAL) 778 return 0; 779 if (ret == CSTR_GREATER_THAN) 780 return 1; 781 return -1; 782 } 783 784 /*********************************************************************** 785 * LISTBOX_FindStringPos 786 * 787 * Find the nearest string located before a given string in sort order. 788 * If 'exact' is TRUE, return an error if we don't get an exact match. 789 */ 790 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact ) 791 { 792 INT index, min, max, res; 793 794 if (!(descr->style & LBS_SORT)) return -1; /* Add it at the end */ 795 min = 0; 796 max = descr->nb_items; 797 while (min != max) 798 { 799 index = (min + max) / 2; 800 if (HAS_STRINGS(descr)) 801 res = LISTBOX_lstrcmpiW( descr->locale, str, descr->items[index].str); 802 else 803 { 804 COMPAREITEMSTRUCT cis; 805 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID ); 806 807 cis.CtlType = ODT_LISTBOX; 808 cis.CtlID = id; 809 cis.hwndItem = descr->self; 810 /* note that some application (MetaStock) expects the second item 811 * to be in the listbox */ 812 cis.itemID1 = -1; 813 cis.itemData1 = (ULONG_PTR)str; 814 cis.itemID2 = index; 815 cis.itemData2 = descr->items[index].data; 816 cis.dwLocaleId = descr->locale; 817 res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis ); 818 } 819 if (!res) return index; 820 if (res < 0) max = index; 821 else min = index + 1; 822 } 823 return exact ? -1 : max; 824 } 825 826 827 /*********************************************************************** 828 * LISTBOX_FindFileStrPos 829 * 830 * Find the nearest string located before a given string in directory 831 * sort order (i.e. first files, then directories, then drives). 832 */ 833 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str ) 834 { 835 INT min, max, res; 836 837 if (!HAS_STRINGS(descr)) 838 return LISTBOX_FindStringPos( descr, str, FALSE ); 839 min = 0; 840 max = descr->nb_items; 841 while (min != max) 842 { 843 INT index = (min + max) / 2; 844 LPCWSTR p = descr->items[index].str; 845 if (*p == '[') /* drive or directory */ 846 { 847 if (*str != '[') res = -1; 848 else if (p[1] == '-') /* drive */ 849 { 850 if (str[1] == '-') res = str[2] - p[2]; 851 else res = -1; 852 } 853 else /* directory */ 854 { 855 if (str[1] == '-') res = 1; 856 else res = LISTBOX_lstrcmpiW( descr->locale, str, p ); 857 } 858 } 859 else /* filename */ 860 { 861 if (*str == '[') res = 1; 862 else res = LISTBOX_lstrcmpiW( descr->locale, str, p ); 863 } 864 if (!res) return index; 865 if (res < 0) max = index; 866 else min = index + 1; 867 } 868 return max; 869 } 870 871 872 /*********************************************************************** 873 * LISTBOX_FindString 874 * 875 * Find the item beginning with a given string. 876 */ 877 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact ) 878 { 879 INT i; 880 LB_ITEMDATA *item; 881 882 if (start >= descr->nb_items) start = -1; 883 item = descr->items + start + 1; 884 if (HAS_STRINGS(descr)) 885 { 886 if (!str || ! str[0] ) return LB_ERR; 887 if (exact) 888 { 889 for (i = start + 1; i < descr->nb_items; i++, item++) 890 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i; 891 for (i = 0, item = descr->items; i <= start; i++, item++) 892 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i; 893 } 894 else 895 { 896 /* Special case for drives and directories: ignore prefix */ 897 #define CHECK_DRIVE(item) \ 898 if ((item)->str[0] == '[') \ 899 { \ 900 if (!strncmpiW( str, (item)->str+1, len )) return i; \ 901 if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \ 902 return i; \ 903 } 904 905 INT len = strlenW(str); 906 for (i = start + 1; i < descr->nb_items; i++, item++) 907 { 908 if (!strncmpiW( str, item->str, len )) return i; 909 CHECK_DRIVE(item); 910 } 911 for (i = 0, item = descr->items; i <= start; i++, item++) 912 { 913 if (!strncmpiW( str, item->str, len )) return i; 914 CHECK_DRIVE(item); 915 } 916 #undef CHECK_DRIVE 917 } 918 } 919 else 920 { 921 if (exact && (descr->style & LBS_SORT)) 922 /* If sorted, use a WM_COMPAREITEM binary search */ 923 return LISTBOX_FindStringPos( descr, str, TRUE ); 924 925 /* Otherwise use a linear search */ 926 for (i = start + 1; i < descr->nb_items; i++, item++) 927 if (item->data == (ULONG_PTR)str) return i; 928 for (i = 0, item = descr->items; i <= start; i++, item++) 929 if (item->data == (ULONG_PTR)str) return i; 930 } 931 return LB_ERR; 932 } 933 934 935 /*********************************************************************** 936 * LISTBOX_GetSelCount 937 */ 938 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr ) 939 { 940 INT i, count; 941 const LB_ITEMDATA *item = descr->items; 942 943 if (!(descr->style & LBS_MULTIPLESEL) || 944 (descr->style & LBS_NOSEL)) 945 return LB_ERR; 946 for (i = count = 0; i < descr->nb_items; i++, item++) 947 if (item->selected) count++; 948 return count; 949 } 950 951 952 /*********************************************************************** 953 * LISTBOX_GetSelItems 954 */ 955 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array ) 956 { 957 INT i, count; 958 const LB_ITEMDATA *item = descr->items; 959 960 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR; 961 for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++) 962 if (item->selected) array[count++] = i; 963 return count; 964 } 965 966 967 /*********************************************************************** 968 * LISTBOX_Paint 969 */ 970 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc ) 971 { 972 INT i, col_pos = descr->page_size - 1; 973 RECT rect; 974 RECT focusRect = {-1, -1, -1, -1}; 975 HFONT oldFont = 0; 976 HBRUSH hbrush, oldBrush = 0; 977 978 if (descr->style & LBS_NOREDRAW) return 0; 979 980 SetRect( &rect, 0, 0, descr->width, descr->height ); 981 if (descr->style & LBS_MULTICOLUMN) 982 rect.right = rect.left + descr->column_width; 983 else if (descr->horz_pos) 984 { 985 SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL ); 986 rect.right += descr->horz_pos; 987 } 988 989 if (descr->font) oldFont = SelectObject( hdc, descr->font ); 990 hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX, 991 (WPARAM)hdc, (LPARAM)descr->self ); 992 if (hbrush) oldBrush = SelectObject( hdc, hbrush ); 993 if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) ); 994 995 if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on && 996 (descr->in_focus)) 997 { 998 /* Special case for empty listbox: paint focus rect */ 999 rect.bottom = rect.top + descr->item_height; 1000 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED, 1001 &rect, NULL, 0, NULL ); 1002 LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE ); 1003 rect.top = rect.bottom; 1004 } 1005 1006 /* Paint all the item, regarding the selection 1007 Focus state will be painted after */ 1008 1009 for (i = descr->top_item; i < descr->nb_items; i++) 1010 { 1011 if (!(descr->style & LBS_OWNERDRAWVARIABLE)) 1012 rect.bottom = rect.top + descr->item_height; 1013 else 1014 rect.bottom = rect.top + descr->items[i].height; 1015 1016 /* keep the focus rect, to paint the focus item after */ 1017 if (i == descr->focus_item) 1018 focusRect = rect; 1019 1020 LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE ); 1021 rect.top = rect.bottom; 1022 1023 if ((descr->style & LBS_MULTICOLUMN) && !col_pos) 1024 { 1025 if (!IS_OWNERDRAW(descr)) 1026 { 1027 /* Clear the bottom of the column */ 1028 if (rect.top < descr->height) 1029 { 1030 rect.bottom = descr->height; 1031 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED, 1032 &rect, NULL, 0, NULL ); 1033 } 1034 } 1035 1036 /* Go to the next column */ 1037 rect.left += descr->column_width; 1038 rect.right += descr->column_width; 1039 rect.top = 0; 1040 col_pos = descr->page_size - 1; 1041 } 1042 else 1043 { 1044 col_pos--; 1045 if (rect.top >= descr->height) break; 1046 } 1047 } 1048 1049 /* Paint the focus item now */ 1050 if (focusRect.top != focusRect.bottom && 1051 descr->caret_on && descr->in_focus) 1052 LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE ); 1053 1054 if (!IS_OWNERDRAW(descr)) 1055 { 1056 /* Clear the remainder of the client area */ 1057 if (rect.top < descr->height) 1058 { 1059 rect.bottom = descr->height; 1060 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED, 1061 &rect, NULL, 0, NULL ); 1062 } 1063 if (rect.right < descr->width) 1064 { 1065 rect.left = rect.right; 1066 rect.right = descr->width; 1067 rect.top = 0; 1068 rect.bottom = descr->height; 1069 ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED, 1070 &rect, NULL, 0, NULL ); 1071 } 1072 } 1073 if (oldFont) SelectObject( hdc, oldFont ); 1074 if (oldBrush) SelectObject( hdc, oldBrush ); 1075 return 0; 1076 } 1077 1078 static void LISTBOX_NCPaint( LB_DESCR *descr, HRGN region ) 1079 { 1080 DWORD exstyle = GetWindowLongW( descr->self, GWL_EXSTYLE); 1081 HTHEME theme = GetWindowTheme( descr->self ); 1082 HRGN cliprgn = region; 1083 int cxEdge, cyEdge; 1084 HDC hdc; 1085 RECT r; 1086 1087 if (!theme || !(exstyle & WS_EX_CLIENTEDGE)) 1088 return; 1089 1090 cxEdge = GetSystemMetrics(SM_CXEDGE), 1091 cyEdge = GetSystemMetrics(SM_CYEDGE); 1092 1093 GetWindowRect(descr->self, &r); 1094 1095 /* New clipping region passed to default proc to exclude border */ 1096 cliprgn = CreateRectRgn(r.left + cxEdge, r.top + cyEdge, 1097 r.right - cxEdge, r.bottom - cyEdge); 1098 if (region != (HRGN)1) 1099 CombineRgn(cliprgn, cliprgn, region, RGN_AND); 1100 OffsetRect(&r, -r.left, -r.top); 1101 1102 #ifdef __REACTOS__ /* r73789 */ 1103 hdc = GetWindowDC(descr->self); 1104 /* Exclude client part */ 1105 ExcludeClipRect(hdc, 1106 r.left + cxEdge, 1107 r.top + cyEdge, 1108 r.right - cxEdge, 1109 r.bottom -cyEdge); 1110 #else 1111 hdc = GetDCEx(descr->self, region, DCX_WINDOW|DCX_INTERSECTRGN); 1112 OffsetRect(&r, -r.left, -r.top); 1113 #endif 1114 1115 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0)) 1116 DrawThemeParentBackground(descr->self, hdc, &r); 1117 DrawThemeBackground (theme, hdc, 0, 0, &r, 0); 1118 ReleaseDC(descr->self, hdc); 1119 } 1120 1121 /*********************************************************************** 1122 * LISTBOX_InvalidateItems 1123 * 1124 * Invalidate all items from a given item. If the specified item is not 1125 * visible, nothing happens. 1126 */ 1127 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index ) 1128 { 1129 RECT rect; 1130 1131 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1) 1132 { 1133 if (descr->style & LBS_NOREDRAW) 1134 { 1135 descr->style |= LBS_DISPLAYCHANGED; 1136 return; 1137 } 1138 rect.bottom = descr->height; 1139 InvalidateRect( descr->self, &rect, TRUE ); 1140 if (descr->style & LBS_MULTICOLUMN) 1141 { 1142 /* Repaint the other columns */ 1143 rect.left = rect.right; 1144 rect.right = descr->width; 1145 rect.top = 0; 1146 InvalidateRect( descr->self, &rect, TRUE ); 1147 } 1148 } 1149 } 1150 1151 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index ) 1152 { 1153 RECT rect; 1154 1155 if (LISTBOX_GetItemRect( descr, index, &rect ) == 1) 1156 InvalidateRect( descr->self, &rect, TRUE ); 1157 } 1158 1159 /*********************************************************************** 1160 * LISTBOX_GetItemHeight 1161 */ 1162 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index ) 1163 { 1164 if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0) 1165 { 1166 if ((index < 0) || (index >= descr->nb_items)) 1167 { 1168 SetLastError(ERROR_INVALID_INDEX); 1169 return LB_ERR; 1170 } 1171 return descr->items[index].height; 1172 } 1173 else return descr->item_height; 1174 } 1175 1176 1177 /*********************************************************************** 1178 * LISTBOX_SetItemHeight 1179 */ 1180 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint ) 1181 { 1182 if (height > MAXBYTE) 1183 return -1; 1184 1185 if (!height) height = 1; 1186 1187 if (descr->style & LBS_OWNERDRAWVARIABLE) 1188 { 1189 if ((index < 0) || (index >= descr->nb_items)) 1190 { 1191 SetLastError(ERROR_INVALID_INDEX); 1192 return LB_ERR; 1193 } 1194 TRACE("[%p]: item %d height = %d\n", descr->self, index, height ); 1195 descr->items[index].height = height; 1196 LISTBOX_UpdateScroll( descr ); 1197 if (repaint) 1198 LISTBOX_InvalidateItems( descr, index ); 1199 } 1200 else if (height != descr->item_height) 1201 { 1202 TRACE("[%p]: new height = %d\n", descr->self, height ); 1203 descr->item_height = height; 1204 LISTBOX_UpdatePage( descr ); 1205 LISTBOX_UpdateScroll( descr ); 1206 if (repaint) 1207 InvalidateRect( descr->self, 0, TRUE ); 1208 } 1209 return LB_OKAY; 1210 } 1211 1212 1213 /*********************************************************************** 1214 * LISTBOX_SetHorizontalPos 1215 */ 1216 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos ) 1217 { 1218 INT diff; 1219 1220 if (pos > descr->horz_extent - descr->width) 1221 pos = descr->horz_extent - descr->width; 1222 if (pos < 0) pos = 0; 1223 if (!(diff = descr->horz_pos - pos)) return; 1224 TRACE("[%p]: new horz pos = %d\n", descr->self, pos ); 1225 descr->horz_pos = pos; 1226 LISTBOX_UpdateScroll( descr ); 1227 if (abs(diff) < descr->width) 1228 { 1229 RECT rect; 1230 /* Invalidate the focused item so it will be repainted correctly */ 1231 if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1) 1232 InvalidateRect( descr->self, &rect, TRUE ); 1233 ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL, 1234 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN ); 1235 } 1236 else 1237 InvalidateRect( descr->self, NULL, TRUE ); 1238 } 1239 1240 1241 /*********************************************************************** 1242 * LISTBOX_SetHorizontalExtent 1243 */ 1244 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent ) 1245 { 1246 if (descr->style & LBS_MULTICOLUMN) 1247 return LB_OKAY; 1248 if (extent == descr->horz_extent) return LB_OKAY; 1249 TRACE("[%p]: new horz extent = %d\n", descr->self, extent ); 1250 descr->horz_extent = extent; 1251 if (descr->style & WS_HSCROLL) { 1252 SCROLLINFO info; 1253 info.cbSize = sizeof(info); 1254 info.nMin = 0; 1255 info.nMax = descr->horz_extent ? descr->horz_extent - 1 : 0; 1256 info.fMask = SIF_RANGE; 1257 if (descr->style & LBS_DISABLENOSCROLL) 1258 info.fMask |= SIF_DISABLENOSCROLL; 1259 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE ); 1260 } 1261 if (descr->horz_pos > extent - descr->width) 1262 LISTBOX_SetHorizontalPos( descr, extent - descr->width ); 1263 return LB_OKAY; 1264 } 1265 1266 1267 /*********************************************************************** 1268 * LISTBOX_SetColumnWidth 1269 */ 1270 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT width) 1271 { 1272 if (width == descr->column_width) return LB_OKAY; 1273 TRACE("[%p]: new column width = %d\n", descr->self, width ); 1274 descr->column_width = width; 1275 LISTBOX_UpdatePage( descr ); 1276 return LB_OKAY; 1277 } 1278 1279 1280 /*********************************************************************** 1281 * LISTBOX_SetFont 1282 * 1283 * Returns the item height. 1284 */ 1285 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font ) 1286 { 1287 HDC hdc; 1288 HFONT oldFont = 0; 1289 const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 1290 SIZE sz; 1291 1292 descr->font = font; 1293 1294 if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) 1295 { 1296 ERR("unable to get DC.\n" ); 1297 return 16; 1298 } 1299 if (font) oldFont = SelectObject( hdc, font ); 1300 GetTextExtentPointA( hdc, alphabet, 52, &sz); 1301 if (oldFont) SelectObject( hdc, oldFont ); 1302 ReleaseDC( descr->self, hdc ); 1303 1304 descr->avg_char_width = (sz.cx / 26 + 1) / 2; 1305 if (!IS_OWNERDRAW(descr)) 1306 LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE ); 1307 return sz.cy; 1308 } 1309 1310 1311 /*********************************************************************** 1312 * LISTBOX_MakeItemVisible 1313 * 1314 * Make sure that a given item is partially or fully visible. 1315 */ 1316 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully ) 1317 { 1318 INT top; 1319 1320 TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully); 1321 1322 if (index <= descr->top_item) top = index; 1323 else if (descr->style & LBS_MULTICOLUMN) 1324 { 1325 INT cols = descr->width; 1326 if (!fully) cols += descr->column_width - 1; 1327 if (cols >= descr->column_width) cols /= descr->column_width; 1328 else cols = 1; 1329 if (index < descr->top_item + (descr->page_size * cols)) return; 1330 top = index - descr->page_size * (cols - 1); 1331 } 1332 else if (descr->style & LBS_OWNERDRAWVARIABLE) 1333 { 1334 INT height = fully ? descr->items[index].height : 1; 1335 for (top = index; top > descr->top_item; top--) 1336 if ((height += descr->items[top-1].height) > descr->height) break; 1337 } 1338 else 1339 { 1340 if (index < descr->top_item + descr->page_size) return; 1341 if (!fully && (index == descr->top_item + descr->page_size) && 1342 (descr->height > (descr->page_size * descr->item_height))) return; 1343 top = index - descr->page_size + 1; 1344 } 1345 LISTBOX_SetTopItem( descr, top, TRUE ); 1346 } 1347 1348 /*********************************************************************** 1349 * LISTBOX_SetCaretIndex 1350 * 1351 * NOTES 1352 * index must be between 0 and descr->nb_items-1, or LB_ERR is returned. 1353 * 1354 */ 1355 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible ) 1356 { 1357 INT oldfocus = descr->focus_item; 1358 1359 TRACE("old focus %d, index %d\n", oldfocus, index); 1360 1361 if (descr->style & LBS_NOSEL) return LB_ERR; 1362 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR; 1363 if (index == oldfocus) return LB_OKAY; 1364 1365 LISTBOX_DrawFocusRect( descr, FALSE ); 1366 descr->focus_item = index; 1367 1368 LISTBOX_MakeItemVisible( descr, index, fully_visible ); 1369 LISTBOX_DrawFocusRect( descr, TRUE ); 1370 1371 return LB_OKAY; 1372 } 1373 1374 1375 /*********************************************************************** 1376 * LISTBOX_SelectItemRange 1377 * 1378 * Select a range of items. Should only be used on a MULTIPLESEL listbox. 1379 */ 1380 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first, 1381 INT last, BOOL on ) 1382 { 1383 INT i; 1384 1385 /* A few sanity checks */ 1386 1387 if (descr->style & LBS_NOSEL) return LB_ERR; 1388 if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR; 1389 1390 if (!descr->nb_items) return LB_OKAY; 1391 1392 if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1; 1393 if (first < 0) first = 0; 1394 if (last < first) return LB_OKAY; 1395 1396 if (on) /* Turn selection on */ 1397 { 1398 for (i = first; i <= last; i++) 1399 { 1400 if (descr->items[i].selected) continue; 1401 descr->items[i].selected = TRUE; 1402 LISTBOX_InvalidateItemRect(descr, i); 1403 } 1404 } 1405 else /* Turn selection off */ 1406 { 1407 for (i = first; i <= last; i++) 1408 { 1409 if (!descr->items[i].selected) continue; 1410 descr->items[i].selected = FALSE; 1411 LISTBOX_InvalidateItemRect(descr, i); 1412 } 1413 } 1414 return LB_OKAY; 1415 } 1416 1417 /*********************************************************************** 1418 * LISTBOX_SetSelection 1419 */ 1420 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index, 1421 BOOL on, BOOL send_notify ) 1422 { 1423 TRACE( "cur_sel=%d index=%d notify=%s\n", 1424 descr->selected_item, index, send_notify ? "YES" : "NO" ); 1425 1426 if (descr->style & LBS_NOSEL) 1427 { 1428 descr->selected_item = index; 1429 return LB_ERR; 1430 } 1431 if ((index < -1) || (index >= descr->nb_items)) return LB_ERR; 1432 if (descr->style & LBS_MULTIPLESEL) 1433 { 1434 if (index == -1) /* Select all items */ 1435 return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on ); 1436 else /* Only one item */ 1437 return LISTBOX_SelectItemRange( descr, index, index, on ); 1438 } 1439 else 1440 { 1441 INT oldsel = descr->selected_item; 1442 if (index == oldsel) return LB_OKAY; 1443 if (oldsel != -1) descr->items[oldsel].selected = FALSE; 1444 if (index != -1) descr->items[index].selected = TRUE; 1445 if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT ); 1446 descr->selected_item = index; 1447 if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT ); 1448 if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr, 1449 (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL ); 1450 else 1451 if( descr->lphc ) /* set selection change flag for parent combo */ 1452 descr->lphc->wState |= CBF_SELCHANGE; 1453 } 1454 return LB_OKAY; 1455 } 1456 1457 1458 /*********************************************************************** 1459 * LISTBOX_MoveCaret 1460 * 1461 * Change the caret position and extend the selection to the new caret. 1462 */ 1463 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible ) 1464 { 1465 TRACE("old focus %d, index %d\n", descr->focus_item, index); 1466 1467 if ((index < 0) || (index >= descr->nb_items)) 1468 return; 1469 1470 /* Important, repaint needs to be done in this order if 1471 you want to mimic Windows behavior: 1472 1. Remove the focus and paint the item 1473 2. Remove the selection and paint the item(s) 1474 3. Set the selection and repaint the item(s) 1475 4. Set the focus to 'index' and repaint the item */ 1476 1477 /* 1. remove the focus and repaint the item */ 1478 LISTBOX_DrawFocusRect( descr, FALSE ); 1479 1480 /* 2. then turn off the previous selection */ 1481 /* 3. repaint the new selected item */ 1482 if (descr->style & LBS_EXTENDEDSEL) 1483 { 1484 if (descr->anchor_item != -1) 1485 { 1486 INT first = min( index, descr->anchor_item ); 1487 INT last = max( index, descr->anchor_item ); 1488 if (first > 0) 1489 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE ); 1490 LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE ); 1491 LISTBOX_SelectItemRange( descr, first, last, TRUE ); 1492 } 1493 } 1494 else if (!(descr->style & LBS_MULTIPLESEL)) 1495 { 1496 /* Set selection to new caret item */ 1497 LISTBOX_SetSelection( descr, index, TRUE, FALSE ); 1498 } 1499 1500 /* 4. repaint the new item with the focus */ 1501 descr->focus_item = index; 1502 LISTBOX_MakeItemVisible( descr, index, fully_visible ); 1503 LISTBOX_DrawFocusRect( descr, TRUE ); 1504 } 1505 1506 1507 /*********************************************************************** 1508 * LISTBOX_InsertItem 1509 */ 1510 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index, 1511 LPWSTR str, ULONG_PTR data ) 1512 { 1513 LB_ITEMDATA *item; 1514 INT max_items; 1515 INT oldfocus = descr->focus_item; 1516 1517 if (index == -1) index = descr->nb_items; 1518 else if ((index < 0) || (index > descr->nb_items)) return LB_ERR; 1519 if (!descr->items) max_items = 0; 1520 else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item); 1521 if (descr->nb_items == max_items) 1522 { 1523 /* We need to grow the array */ 1524 max_items += LB_ARRAY_GRANULARITY; 1525 if (descr->items) 1526 item = HeapReAlloc( GetProcessHeap(), 0, descr->items, 1527 max_items * sizeof(LB_ITEMDATA) ); 1528 else 1529 item = HeapAlloc( GetProcessHeap(), 0, 1530 max_items * sizeof(LB_ITEMDATA) ); 1531 if (!item) 1532 { 1533 SEND_NOTIFICATION( descr, LBN_ERRSPACE ); 1534 return LB_ERRSPACE; 1535 } 1536 descr->items = item; 1537 } 1538 1539 /* Insert the item structure */ 1540 1541 item = &descr->items[index]; 1542 if (index < descr->nb_items) 1543 RtlMoveMemory( item + 1, item, 1544 (descr->nb_items - index) * sizeof(LB_ITEMDATA) ); 1545 item->str = str; 1546 item->data = HAS_STRINGS(descr) ? 0 : data; 1547 item->height = 0; 1548 item->selected = FALSE; 1549 descr->nb_items++; 1550 1551 /* Get item height */ 1552 1553 if (descr->style & LBS_OWNERDRAWVARIABLE) 1554 { 1555 MEASUREITEMSTRUCT mis; 1556 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID ); 1557 1558 mis.CtlType = ODT_LISTBOX; 1559 mis.CtlID = id; 1560 mis.itemID = index; 1561 mis.itemData = data; 1562 mis.itemHeight = descr->item_height; 1563 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis ); 1564 item->height = mis.itemHeight ? mis.itemHeight : 1; 1565 TRACE("[%p]: measure item %d (%s) = %d\n", 1566 descr->self, index, str ? debugstr_w(str) : "", item->height ); 1567 } 1568 1569 /* Repaint the items */ 1570 1571 LISTBOX_UpdateScroll( descr ); 1572 LISTBOX_InvalidateItems( descr, index ); 1573 1574 /* Move selection and focused item */ 1575 /* If listbox was empty, set focus to the first item */ 1576 if (descr->nb_items == 1) 1577 LISTBOX_SetCaretIndex( descr, 0, FALSE ); 1578 /* single select don't change selection index in win31 */ 1579 else if ((ISWIN31) && !(IS_MULTISELECT(descr))) 1580 { 1581 descr->selected_item++; 1582 LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE ); 1583 } 1584 else 1585 { 1586 if (index <= descr->selected_item) 1587 { 1588 descr->selected_item++; 1589 descr->focus_item = oldfocus; /* focus not changed */ 1590 } 1591 } 1592 return LB_OKAY; 1593 } 1594 1595 1596 /*********************************************************************** 1597 * LISTBOX_InsertString 1598 */ 1599 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str ) 1600 { 1601 LPWSTR new_str = NULL; 1602 LRESULT ret; 1603 1604 if (HAS_STRINGS(descr)) 1605 { 1606 static const WCHAR empty_stringW[] = { 0 }; 1607 if (!str) str = empty_stringW; 1608 if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) ))) 1609 { 1610 SEND_NOTIFICATION( descr, LBN_ERRSPACE ); 1611 return LB_ERRSPACE; 1612 } 1613 strcpyW(new_str, str); 1614 } 1615 1616 if (index == -1) index = descr->nb_items; 1617 if ((ret = LISTBOX_InsertItem( descr, index, new_str, (ULONG_PTR)str )) != 0) 1618 { 1619 HeapFree( GetProcessHeap(), 0, new_str ); 1620 return ret; 1621 } 1622 1623 TRACE("[%p]: added item %d %s\n", 1624 descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" ); 1625 return index; 1626 } 1627 1628 1629 /*********************************************************************** 1630 * LISTBOX_DeleteItem 1631 * 1632 * Delete the content of an item. 'index' must be a valid index. 1633 */ 1634 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index ) 1635 { 1636 /* save the item data before it gets freed by LB_RESETCONTENT */ 1637 ULONG_PTR item_data = descr->items[index].data; 1638 LPWSTR item_str = descr->items[index].str; 1639 1640 if (!descr->nb_items) 1641 SendMessageW( descr->self, LB_RESETCONTENT, 0, 0 ); 1642 1643 /* Note: Win 3.1 only sends DELETEITEM on owner-draw items, 1644 * while Win95 sends it for all items with user data. 1645 * It's probably better to send it too often than not 1646 * often enough, so this is what we do here. 1647 */ 1648 if (IS_OWNERDRAW(descr) || item_data) 1649 { 1650 DELETEITEMSTRUCT dis; 1651 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID ); 1652 1653 dis.CtlType = ODT_LISTBOX; 1654 dis.CtlID = id; 1655 dis.itemID = index; 1656 dis.hwndItem = descr->self; 1657 dis.itemData = item_data; 1658 SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis ); 1659 } 1660 if (HAS_STRINGS(descr)) 1661 HeapFree( GetProcessHeap(), 0, item_str ); 1662 } 1663 1664 1665 /*********************************************************************** 1666 * LISTBOX_RemoveItem 1667 * 1668 * Remove an item from the listbox and delete its content. 1669 */ 1670 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index ) 1671 { 1672 LB_ITEMDATA *item; 1673 INT max_items; 1674 1675 if ((index < 0) || (index >= descr->nb_items)) return LB_ERR; 1676 1677 /* We need to invalidate the original rect instead of the updated one. */ 1678 LISTBOX_InvalidateItems( descr, index ); 1679 1680 descr->nb_items--; 1681 LISTBOX_DeleteItem( descr, index ); 1682 1683 if (!descr->nb_items) return LB_OKAY; 1684 1685 /* Remove the item */ 1686 1687 item = &descr->items[index]; 1688 if (index < descr->nb_items) 1689 RtlMoveMemory( item, item + 1, 1690 (descr->nb_items - index) * sizeof(LB_ITEMDATA) ); 1691 if (descr->anchor_item == descr->nb_items) descr->anchor_item--; 1692 1693 /* Shrink the item array if possible */ 1694 1695 max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA); 1696 if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY) 1697 { 1698 max_items -= LB_ARRAY_GRANULARITY; 1699 item = HeapReAlloc( GetProcessHeap(), 0, descr->items, 1700 max_items * sizeof(LB_ITEMDATA) ); 1701 if (item) descr->items = item; 1702 } 1703 /* Repaint the items */ 1704 1705 LISTBOX_UpdateScroll( descr ); 1706 /* if we removed the scrollbar, reset the top of the list 1707 (correct for owner-drawn ???) */ 1708 if (descr->nb_items == descr->page_size) 1709 LISTBOX_SetTopItem( descr, 0, TRUE ); 1710 1711 /* Move selection and focused item */ 1712 if (!IS_MULTISELECT(descr)) 1713 { 1714 if (index == descr->selected_item) 1715 descr->selected_item = -1; 1716 else if (index < descr->selected_item) 1717 { 1718 descr->selected_item--; 1719 if (ISWIN31) /* win 31 do not change the selected item number */ 1720 LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE); 1721 } 1722 } 1723 1724 if (descr->focus_item >= descr->nb_items) 1725 { 1726 descr->focus_item = descr->nb_items - 1; 1727 if (descr->focus_item < 0) descr->focus_item = 0; 1728 } 1729 return LB_OKAY; 1730 } 1731 1732 1733 /*********************************************************************** 1734 * LISTBOX_ResetContent 1735 */ 1736 static void LISTBOX_ResetContent( LB_DESCR *descr ) 1737 { 1738 INT i; 1739 1740 for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i); 1741 HeapFree( GetProcessHeap(), 0, descr->items ); 1742 descr->nb_items = 0; 1743 descr->top_item = 0; 1744 descr->selected_item = -1; 1745 descr->focus_item = 0; 1746 descr->anchor_item = -1; 1747 descr->items = NULL; 1748 } 1749 1750 1751 /*********************************************************************** 1752 * LISTBOX_SetCount 1753 */ 1754 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count ) 1755 { 1756 LRESULT ret; 1757 1758 if (HAS_STRINGS(descr)) 1759 { 1760 SetLastError(ERROR_SETCOUNT_ON_BAD_LB); 1761 return LB_ERR; 1762 } 1763 1764 /* FIXME: this is far from optimal... */ 1765 if (count > descr->nb_items) 1766 { 1767 while (count > descr->nb_items) 1768 if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0) 1769 return ret; 1770 } 1771 else if (count < descr->nb_items) 1772 { 1773 while (count < descr->nb_items) 1774 if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0) 1775 return ret; 1776 } 1777 1778 InvalidateRect( descr->self, NULL, TRUE ); 1779 return LB_OKAY; 1780 } 1781 1782 1783 /*********************************************************************** 1784 * LISTBOX_Directory 1785 */ 1786 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib, 1787 LPCWSTR filespec, BOOL long_names ) 1788 { 1789 HANDLE handle; 1790 LRESULT ret = LB_OKAY; 1791 WIN32_FIND_DATAW entry; 1792 int pos; 1793 LRESULT maxinsert = LB_ERR; 1794 1795 /* don't scan directory if we just want drives exclusively */ 1796 if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) { 1797 /* scan directory */ 1798 if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE) 1799 { 1800 int le = GetLastError(); 1801 if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR; 1802 } 1803 else 1804 { 1805 do 1806 { 1807 WCHAR buffer[270]; 1808 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) 1809 { 1810 static const WCHAR bracketW[] = { ']',0 }; 1811 static const WCHAR dotW[] = { '.',0 }; 1812 if (!(attrib & DDL_DIRECTORY) || 1813 !strcmpW( entry.cFileName, dotW )) continue; 1814 buffer[0] = '['; 1815 if (!long_names && entry.cAlternateFileName[0]) 1816 strcpyW( buffer + 1, entry.cAlternateFileName ); 1817 else 1818 strcpyW( buffer + 1, entry.cFileName ); 1819 strcatW(buffer, bracketW); 1820 } 1821 else /* not a directory */ 1822 { 1823 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \ 1824 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE) 1825 1826 if ((attrib & DDL_EXCLUSIVE) && 1827 ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS))) 1828 continue; 1829 #undef ATTRIBS 1830 if (!long_names && entry.cAlternateFileName[0]) 1831 strcpyW( buffer, entry.cAlternateFileName ); 1832 else 1833 strcpyW( buffer, entry.cFileName ); 1834 } 1835 if (!long_names) CharLowerW( buffer ); 1836 pos = LISTBOX_FindFileStrPos( descr, buffer ); 1837 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0) 1838 break; 1839 if (ret <= maxinsert) maxinsert++; else maxinsert = ret; 1840 } while (FindNextFileW( handle, &entry )); 1841 FindClose( handle ); 1842 } 1843 } 1844 if (ret >= 0) 1845 { 1846 ret = maxinsert; 1847 1848 /* scan drives */ 1849 if (attrib & DDL_DRIVES) 1850 { 1851 WCHAR buffer[] = {'[','-','a','-',']',0}; 1852 WCHAR root[] = {'A',':','\\',0}; 1853 int drive; 1854 for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++) 1855 { 1856 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue; 1857 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0) 1858 break; 1859 } 1860 } 1861 } 1862 return ret; 1863 } 1864 1865 1866 /*********************************************************************** 1867 * LISTBOX_HandleVScroll 1868 */ 1869 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos ) 1870 { 1871 SCROLLINFO info; 1872 1873 if (descr->style & LBS_MULTICOLUMN) return 0; 1874 switch(scrollReq) 1875 { 1876 case SB_LINEUP: 1877 LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE ); 1878 break; 1879 case SB_LINEDOWN: 1880 LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE ); 1881 break; 1882 case SB_PAGEUP: 1883 LISTBOX_SetTopItem( descr, descr->top_item - 1884 LISTBOX_GetCurrentPageSize( descr ), TRUE ); 1885 break; 1886 case SB_PAGEDOWN: 1887 LISTBOX_SetTopItem( descr, descr->top_item + 1888 LISTBOX_GetCurrentPageSize( descr ), TRUE ); 1889 break; 1890 case SB_THUMBPOSITION: 1891 LISTBOX_SetTopItem( descr, pos, TRUE ); 1892 break; 1893 case SB_THUMBTRACK: 1894 info.cbSize = sizeof(info); 1895 info.fMask = SIF_TRACKPOS; 1896 GetScrollInfo( descr->self, SB_VERT, &info ); 1897 LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE ); 1898 break; 1899 case SB_TOP: 1900 LISTBOX_SetTopItem( descr, 0, TRUE ); 1901 break; 1902 case SB_BOTTOM: 1903 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE ); 1904 break; 1905 } 1906 return 0; 1907 } 1908 1909 1910 /*********************************************************************** 1911 * LISTBOX_HandleHScroll 1912 */ 1913 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos ) 1914 { 1915 SCROLLINFO info; 1916 INT page; 1917 1918 if (descr->style & LBS_MULTICOLUMN) 1919 { 1920 switch(scrollReq) 1921 { 1922 case SB_LINELEFT: 1923 LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size, 1924 TRUE ); 1925 break; 1926 case SB_LINERIGHT: 1927 LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size, 1928 TRUE ); 1929 break; 1930 case SB_PAGELEFT: 1931 page = descr->width / descr->column_width; 1932 if (page < 1) page = 1; 1933 LISTBOX_SetTopItem( descr, 1934 descr->top_item - page * descr->page_size, TRUE ); 1935 break; 1936 case SB_PAGERIGHT: 1937 page = descr->width / descr->column_width; 1938 if (page < 1) page = 1; 1939 LISTBOX_SetTopItem( descr, 1940 descr->top_item + page * descr->page_size, TRUE ); 1941 break; 1942 case SB_THUMBPOSITION: 1943 LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE ); 1944 break; 1945 case SB_THUMBTRACK: 1946 info.cbSize = sizeof(info); 1947 info.fMask = SIF_TRACKPOS; 1948 GetScrollInfo( descr->self, SB_VERT, &info ); 1949 LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size, 1950 TRUE ); 1951 break; 1952 case SB_LEFT: 1953 LISTBOX_SetTopItem( descr, 0, TRUE ); 1954 break; 1955 case SB_RIGHT: 1956 LISTBOX_SetTopItem( descr, descr->nb_items, TRUE ); 1957 break; 1958 } 1959 } 1960 else if (descr->horz_extent) 1961 { 1962 switch(scrollReq) 1963 { 1964 case SB_LINELEFT: 1965 LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 ); 1966 break; 1967 case SB_LINERIGHT: 1968 LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 ); 1969 break; 1970 case SB_PAGELEFT: 1971 LISTBOX_SetHorizontalPos( descr, 1972 descr->horz_pos - descr->width ); 1973 break; 1974 case SB_PAGERIGHT: 1975 LISTBOX_SetHorizontalPos( descr, 1976 descr->horz_pos + descr->width ); 1977 break; 1978 case SB_THUMBPOSITION: 1979 LISTBOX_SetHorizontalPos( descr, pos ); 1980 break; 1981 case SB_THUMBTRACK: 1982 info.cbSize = sizeof(info); 1983 info.fMask = SIF_TRACKPOS; 1984 GetScrollInfo( descr->self, SB_HORZ, &info ); 1985 LISTBOX_SetHorizontalPos( descr, info.nTrackPos ); 1986 break; 1987 case SB_LEFT: 1988 LISTBOX_SetHorizontalPos( descr, 0 ); 1989 break; 1990 case SB_RIGHT: 1991 LISTBOX_SetHorizontalPos( descr, 1992 descr->horz_extent - descr->width ); 1993 break; 1994 } 1995 } 1996 return 0; 1997 } 1998 1999 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta ) 2000 { 2001 UINT pulScrollLines = 3; 2002 2003 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0); 2004 2005 /* if scrolling changes direction, ignore left overs */ 2006 if ((delta < 0 && descr->wheel_remain < 0) || 2007 (delta > 0 && descr->wheel_remain > 0)) 2008 descr->wheel_remain += delta; 2009 else 2010 descr->wheel_remain = delta; 2011 2012 if (descr->wheel_remain && pulScrollLines) 2013 { 2014 int cLineScroll; 2015 pulScrollLines = min((UINT) descr->page_size, pulScrollLines); 2016 cLineScroll = pulScrollLines * (float)descr->wheel_remain / WHEEL_DELTA; 2017 descr->wheel_remain -= WHEEL_DELTA * cLineScroll / (int)pulScrollLines; 2018 LISTBOX_SetTopItem( descr, descr->top_item - cLineScroll, TRUE ); 2019 } 2020 return 0; 2021 } 2022 2023 /*********************************************************************** 2024 * LISTBOX_HandleLButtonDown 2025 */ 2026 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y ) 2027 { 2028 INT index = LISTBOX_GetItemFromPoint( descr, x, y ); 2029 2030 TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n", 2031 descr->self, x, y, index, descr->focus_item); 2032 2033 if (!descr->caret_on && (descr->in_focus)) return 0; 2034 2035 if (!descr->in_focus) 2036 { 2037 if( !descr->lphc ) SetFocus( descr->self ); 2038 else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self ); 2039 } 2040 2041 if (index == -1) return 0; 2042 2043 if (!descr->lphc) 2044 { 2045 if (descr->style & LBS_NOTIFY ) 2046 SendMessageW( descr->owner, WM_LBTRACKPOINT, index, 2047 MAKELPARAM( x, y ) ); 2048 } 2049 2050 descr->captured = TRUE; 2051 SetCapture( descr->self ); 2052 2053 if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL)) 2054 { 2055 /* we should perhaps make sure that all items are deselected 2056 FIXME: needed for !LBS_EXTENDEDSEL, too ? 2057 if (!(keys & (MK_SHIFT|MK_CONTROL))) 2058 LISTBOX_SetSelection( descr, -1, FALSE, FALSE); 2059 */ 2060 2061 if (!(keys & MK_SHIFT)) descr->anchor_item = index; 2062 if (keys & MK_CONTROL) 2063 { 2064 LISTBOX_SetCaretIndex( descr, index, FALSE ); 2065 LISTBOX_SetSelection( descr, index, 2066 !descr->items[index].selected, 2067 (descr->style & LBS_NOTIFY) != 0); 2068 } 2069 else 2070 { 2071 LISTBOX_MoveCaret( descr, index, FALSE ); 2072 2073 if (descr->style & LBS_EXTENDEDSEL) 2074 { 2075 LISTBOX_SetSelection( descr, index, 2076 descr->items[index].selected, 2077 (descr->style & LBS_NOTIFY) != 0 ); 2078 } 2079 else 2080 { 2081 LISTBOX_SetSelection( descr, index, 2082 !descr->items[index].selected, 2083 (descr->style & LBS_NOTIFY) != 0 ); 2084 } 2085 } 2086 } 2087 else 2088 { 2089 descr->anchor_item = index; 2090 LISTBOX_MoveCaret( descr, index, FALSE ); 2091 LISTBOX_SetSelection( descr, index, 2092 TRUE, (descr->style & LBS_NOTIFY) != 0 ); 2093 } 2094 2095 if (!descr->lphc) 2096 { 2097 if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT) 2098 { 2099 POINT pt; 2100 2101 pt.x = x; 2102 pt.y = y; 2103 2104 if (DragDetect( descr->self, pt )) 2105 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 ); 2106 } 2107 } 2108 return 0; 2109 } 2110 2111 2112 /************************************************************************* 2113 * LISTBOX_HandleLButtonDownCombo [Internal] 2114 * 2115 * Process LButtonDown message for the ComboListBox 2116 * 2117 * PARAMS 2118 * pWnd [I] The windows internal structure 2119 * pDescr [I] The ListBox internal structure 2120 * keys [I] Key Flag (WM_LBUTTONDOWN doc for more info) 2121 * x [I] X Mouse Coordinate 2122 * y [I] Y Mouse Coordinate 2123 * 2124 * RETURNS 2125 * 0 since we are processing the WM_LBUTTONDOWN Message 2126 * 2127 * NOTES 2128 * This function is only to be used when a ListBox is a ComboListBox 2129 */ 2130 2131 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y) 2132 { 2133 RECT clientRect, screenRect; 2134 POINT mousePos; 2135 2136 mousePos.x = x; 2137 mousePos.y = y; 2138 2139 GetClientRect(descr->self, &clientRect); 2140 2141 if(PtInRect(&clientRect, mousePos)) 2142 { 2143 /* MousePos is in client, resume normal processing */ 2144 if (msg == WM_LBUTTONDOWN) 2145 { 2146 descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1; 2147 return LISTBOX_HandleLButtonDown( descr, keys, x, y); 2148 } 2149 else if (descr->style & LBS_NOTIFY) 2150 SEND_NOTIFICATION( descr, LBN_DBLCLK ); 2151 } 2152 else 2153 { 2154 POINT screenMousePos; 2155 HWND hWndOldCapture; 2156 2157 /* Check the Non-Client Area */ 2158 screenMousePos = mousePos; 2159 hWndOldCapture = GetCapture(); 2160 ReleaseCapture(); 2161 GetWindowRect(descr->self, &screenRect); 2162 ClientToScreen(descr->self, &screenMousePos); 2163 2164 if(!PtInRect(&screenRect, screenMousePos)) 2165 { 2166 LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE ); 2167 LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE ); 2168 COMBO_FlipListbox( descr->lphc, FALSE, FALSE ); 2169 } 2170 else 2171 { 2172 /* Check to see the NC is a scrollbar */ 2173 INT nHitTestType=0; 2174 LONG style = GetWindowLongW( descr->self, GWL_STYLE ); 2175 /* Check Vertical scroll bar */ 2176 if (style & WS_VSCROLL) 2177 { 2178 clientRect.right += GetSystemMetrics(SM_CXVSCROLL); 2179 if (PtInRect( &clientRect, mousePos )) 2180 nHitTestType = HTVSCROLL; 2181 } 2182 /* Check horizontal scroll bar */ 2183 if (style & WS_HSCROLL) 2184 { 2185 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL); 2186 if (PtInRect( &clientRect, mousePos )) 2187 nHitTestType = HTHSCROLL; 2188 } 2189 /* Windows sends this message when a scrollbar is clicked 2190 */ 2191 2192 if(nHitTestType != 0) 2193 { 2194 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType, 2195 MAKELONG(screenMousePos.x, screenMousePos.y)); 2196 } 2197 /* Resume the Capture after scrolling is complete 2198 */ 2199 if(hWndOldCapture != 0) 2200 SetCapture(hWndOldCapture); 2201 } 2202 } 2203 return 0; 2204 } 2205 2206 /*********************************************************************** 2207 * LISTBOX_HandleLButtonUp 2208 */ 2209 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr ) 2210 { 2211 if (LISTBOX_Timer != LB_TIMER_NONE) 2212 KillSystemTimer( descr->self, LB_TIMER_ID ); 2213 LISTBOX_Timer = LB_TIMER_NONE; 2214 if (descr->captured) 2215 { 2216 descr->captured = FALSE; 2217 if (GetCapture() == descr->self) ReleaseCapture(); 2218 if ((descr->style & LBS_NOTIFY) && descr->nb_items) 2219 SEND_NOTIFICATION( descr, LBN_SELCHANGE ); 2220 } 2221 return 0; 2222 } 2223 2224 2225 /*********************************************************************** 2226 * LISTBOX_HandleTimer 2227 * 2228 * Handle scrolling upon a timer event. 2229 * Return TRUE if scrolling should continue. 2230 */ 2231 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir ) 2232 { 2233 switch(dir) 2234 { 2235 case LB_TIMER_UP: 2236 if (descr->top_item) index = descr->top_item - 1; 2237 else index = 0; 2238 break; 2239 case LB_TIMER_LEFT: 2240 if (descr->top_item) index -= descr->page_size; 2241 break; 2242 case LB_TIMER_DOWN: 2243 index = descr->top_item + LISTBOX_GetCurrentPageSize( descr ); 2244 if (index == descr->focus_item) index++; 2245 if (index >= descr->nb_items) index = descr->nb_items - 1; 2246 break; 2247 case LB_TIMER_RIGHT: 2248 if (index + descr->page_size < descr->nb_items) 2249 index += descr->page_size; 2250 break; 2251 case LB_TIMER_NONE: 2252 break; 2253 } 2254 if (index == descr->focus_item) return FALSE; 2255 LISTBOX_MoveCaret( descr, index, FALSE ); 2256 return TRUE; 2257 } 2258 2259 2260 /*********************************************************************** 2261 * LISTBOX_HandleSystemTimer 2262 * 2263 * WM_SYSTIMER handler. 2264 */ 2265 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr ) 2266 { 2267 if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer )) 2268 { 2269 KillSystemTimer( descr->self, LB_TIMER_ID ); 2270 LISTBOX_Timer = LB_TIMER_NONE; 2271 } 2272 return 0; 2273 } 2274 2275 2276 /*********************************************************************** 2277 * LISTBOX_HandleMouseMove 2278 * 2279 * WM_MOUSEMOVE handler. 2280 */ 2281 static void LISTBOX_HandleMouseMove( LB_DESCR *descr, 2282 INT x, INT y ) 2283 { 2284 INT index; 2285 TIMER_DIRECTION dir = LB_TIMER_NONE; 2286 2287 if (!descr->captured) return; 2288 2289 if (descr->style & LBS_MULTICOLUMN) 2290 { 2291 if (y < 0) y = 0; 2292 else if (y >= descr->item_height * descr->page_size) 2293 y = descr->item_height * descr->page_size - 1; 2294 2295 if (x < 0) 2296 { 2297 dir = LB_TIMER_LEFT; 2298 x = 0; 2299 } 2300 else if (x >= descr->width) 2301 { 2302 dir = LB_TIMER_RIGHT; 2303 x = descr->width - 1; 2304 } 2305 } 2306 else 2307 { 2308 if (y < 0) dir = LB_TIMER_UP; /* above */ 2309 else if (y >= descr->height) dir = LB_TIMER_DOWN; /* below */ 2310 } 2311 2312 index = LISTBOX_GetItemFromPoint( descr, x, y ); 2313 if (index == -1) index = descr->focus_item; 2314 if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE; 2315 2316 /* Start/stop the system timer */ 2317 2318 if (dir != LB_TIMER_NONE) 2319 SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL); 2320 else if (LISTBOX_Timer != LB_TIMER_NONE) 2321 KillSystemTimer( descr->self, LB_TIMER_ID ); 2322 LISTBOX_Timer = dir; 2323 } 2324 2325 2326 /*********************************************************************** 2327 * LISTBOX_HandleKeyDown 2328 */ 2329 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key ) 2330 { 2331 INT caret = -1; 2332 BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */ 2333 if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item)) 2334 bForceSelection = FALSE; /* only for single select list */ 2335 2336 if (descr->style & LBS_WANTKEYBOARDINPUT) 2337 { 2338 caret = SendMessageW( descr->owner, WM_VKEYTOITEM, 2339 MAKEWPARAM(LOWORD(key), descr->focus_item), 2340 (LPARAM)descr->self ); 2341 if (caret == -2) return 0; 2342 } 2343 if (caret == -1) switch(key) 2344 { 2345 case VK_LEFT: 2346 if (descr->style & LBS_MULTICOLUMN) 2347 { 2348 bForceSelection = FALSE; 2349 if (descr->focus_item >= descr->page_size) 2350 caret = descr->focus_item - descr->page_size; 2351 break; 2352 } 2353 /* fall through */ 2354 case VK_UP: 2355 caret = descr->focus_item - 1; 2356 if (caret < 0) caret = 0; 2357 break; 2358 case VK_RIGHT: 2359 if (descr->style & LBS_MULTICOLUMN) 2360 { 2361 bForceSelection = FALSE; 2362 if (descr->focus_item + descr->page_size < descr->nb_items) 2363 caret = descr->focus_item + descr->page_size; 2364 break; 2365 } 2366 /* fall through */ 2367 case VK_DOWN: 2368 caret = descr->focus_item + 1; 2369 if (caret >= descr->nb_items) caret = descr->nb_items - 1; 2370 break; 2371 2372 case VK_PRIOR: 2373 if (descr->style & LBS_MULTICOLUMN) 2374 { 2375 INT page = descr->width / descr->column_width; 2376 if (page < 1) page = 1; 2377 caret = descr->focus_item - (page * descr->page_size) + 1; 2378 } 2379 else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1; 2380 if (caret < 0) caret = 0; 2381 break; 2382 case VK_NEXT: 2383 if (descr->style & LBS_MULTICOLUMN) 2384 { 2385 INT page = descr->width / descr->column_width; 2386 if (page < 1) page = 1; 2387 caret = descr->focus_item + (page * descr->page_size) - 1; 2388 } 2389 else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1; 2390 if (caret >= descr->nb_items) caret = descr->nb_items - 1; 2391 break; 2392 case VK_HOME: 2393 caret = 0; 2394 break; 2395 case VK_END: 2396 caret = descr->nb_items - 1; 2397 break; 2398 case VK_SPACE: 2399 if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item; 2400 else if (descr->style & LBS_MULTIPLESEL) 2401 { 2402 LISTBOX_SetSelection( descr, descr->focus_item, 2403 !descr->items[descr->focus_item].selected, 2404 (descr->style & LBS_NOTIFY) != 0 ); 2405 } 2406 break; 2407 default: 2408 bForceSelection = FALSE; 2409 } 2410 if (bForceSelection) /* focused item is used instead of key */ 2411 caret = descr->focus_item; 2412 if (caret >= 0) 2413 { 2414 if (((descr->style & LBS_EXTENDEDSEL) && 2415 !(GetKeyState( VK_SHIFT ) & 0x8000)) || 2416 !IS_MULTISELECT(descr)) 2417 descr->anchor_item = caret; 2418 LISTBOX_MoveCaret( descr, caret, TRUE ); 2419 2420 if (descr->style & LBS_MULTIPLESEL) 2421 descr->selected_item = caret; 2422 else 2423 LISTBOX_SetSelection( descr, caret, TRUE, FALSE); 2424 if (descr->style & LBS_NOTIFY) 2425 { 2426 if (descr->lphc && IsWindowVisible( descr->self )) 2427 { 2428 /* make sure that combo parent doesn't hide us */ 2429 descr->lphc->wState |= CBF_NOROLLUP; 2430 } 2431 if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE ); 2432 } 2433 } 2434 return 0; 2435 } 2436 2437 2438 /*********************************************************************** 2439 * LISTBOX_HandleChar 2440 */ 2441 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW ) 2442 { 2443 INT caret = -1; 2444 WCHAR str[2]; 2445 2446 str[0] = charW; 2447 str[1] = '\0'; 2448 2449 if (descr->style & LBS_WANTKEYBOARDINPUT) 2450 { 2451 caret = SendMessageW( descr->owner, WM_CHARTOITEM, 2452 MAKEWPARAM(charW, descr->focus_item), 2453 (LPARAM)descr->self ); 2454 if (caret == -2) return 0; 2455 } 2456 if (caret == -1) 2457 caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE); 2458 if (caret != -1) 2459 { 2460 if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1) 2461 LISTBOX_SetSelection( descr, caret, TRUE, FALSE); 2462 LISTBOX_MoveCaret( descr, caret, TRUE ); 2463 if ((descr->style & LBS_NOTIFY) && descr->nb_items) 2464 SEND_NOTIFICATION( descr, LBN_SELCHANGE ); 2465 } 2466 return 0; 2467 } 2468 2469 2470 /*********************************************************************** 2471 * LISTBOX_Create 2472 */ 2473 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc ) 2474 { 2475 LB_DESCR *descr; 2476 MEASUREITEMSTRUCT mis; 2477 RECT rect; 2478 2479 if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) ))) 2480 return FALSE; 2481 2482 GetClientRect( hwnd, &rect ); 2483 descr->self = hwnd; 2484 descr->owner = GetParent( descr->self ); 2485 descr->style = GetWindowLongW( descr->self, GWL_STYLE ); 2486 descr->width = rect.right - rect.left; 2487 descr->height = rect.bottom - rect.top; 2488 descr->items = NULL; 2489 descr->nb_items = 0; 2490 descr->top_item = 0; 2491 descr->selected_item = -1; 2492 descr->focus_item = 0; 2493 descr->anchor_item = -1; 2494 descr->item_height = 1; 2495 descr->page_size = 1; 2496 descr->column_width = 150; 2497 descr->horz_extent = 0; 2498 descr->horz_pos = 0; 2499 descr->nb_tabs = 0; 2500 descr->tabs = NULL; 2501 descr->wheel_remain = 0; 2502 descr->caret_on = !lphc; 2503 if (descr->style & LBS_NOSEL) descr->caret_on = FALSE; 2504 descr->in_focus = FALSE; 2505 descr->captured = FALSE; 2506 descr->font = 0; 2507 descr->locale = GetUserDefaultLCID(); 2508 descr->lphc = lphc; 2509 2510 if( lphc ) 2511 { 2512 TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self ); 2513 descr->owner = lphc->self; 2514 } 2515 2516 SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr ); 2517 2518 /* if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY; 2519 */ 2520 if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL; 2521 if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE; 2522 if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT; 2523 descr->item_height = LISTBOX_SetFont( descr, 0 ); 2524 2525 if (descr->style & LBS_OWNERDRAWFIXED) 2526 { 2527 if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN)) 2528 { 2529 /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */ 2530 descr->item_height = lphc->fixedOwnerDrawHeight; 2531 } 2532 else 2533 { 2534 UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID ); 2535 mis.CtlType = ODT_LISTBOX; 2536 mis.CtlID = id; 2537 mis.itemID = -1; 2538 mis.itemWidth = 0; 2539 mis.itemData = 0; 2540 mis.itemHeight = descr->item_height; 2541 SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis ); 2542 descr->item_height = mis.itemHeight ? mis.itemHeight : 1; 2543 } 2544 } 2545 2546 OpenThemeData( descr->self, WC_LISTBOXW ); 2547 2548 TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height); 2549 return TRUE; 2550 } 2551 2552 2553 /*********************************************************************** 2554 * LISTBOX_Destroy 2555 */ 2556 static BOOL LISTBOX_Destroy( LB_DESCR *descr ) 2557 { 2558 HTHEME theme = GetWindowTheme( descr->self ); 2559 CloseThemeData( theme ); 2560 LISTBOX_ResetContent( descr ); 2561 SetWindowLongPtrW( descr->self, 0, 0 ); 2562 HeapFree( GetProcessHeap(), 0, descr ); 2563 return TRUE; 2564 } 2565 2566 2567 /*********************************************************************** 2568 * ListBoxWndProc_common 2569 */ 2570 static LRESULT CALLBACK LISTBOX_WindowProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ) 2571 { 2572 LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 ); 2573 HEADCOMBO *lphc = NULL; 2574 HTHEME theme; 2575 LRESULT ret; 2576 2577 if (!descr) 2578 { 2579 if (!IsWindow(hwnd)) return 0; 2580 2581 if (msg == WM_CREATE) 2582 { 2583 CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam; 2584 if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams; 2585 if (!LISTBOX_Create( hwnd, lphc )) return -1; 2586 TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) ); 2587 return 0; 2588 } 2589 /* Ignore all other messages before we get a WM_CREATE */ 2590 return DefWindowProcW( hwnd, msg, wParam, lParam ); 2591 } 2592 if (descr->style & LBS_COMBOBOX) lphc = descr->lphc; 2593 2594 TRACE("[%p]: msg %#x wp %08lx lp %08lx\n", descr->self, msg, wParam, lParam ); 2595 2596 switch(msg) 2597 { 2598 case LB_RESETCONTENT: 2599 LISTBOX_ResetContent( descr ); 2600 LISTBOX_UpdateScroll( descr ); 2601 InvalidateRect( descr->self, NULL, TRUE ); 2602 return 0; 2603 2604 case LB_ADDSTRING: 2605 { 2606 const WCHAR *textW = (const WCHAR *)lParam; 2607 INT index = LISTBOX_FindStringPos( descr, textW, FALSE ); 2608 return LISTBOX_InsertString( descr, index, textW ); 2609 } 2610 2611 case LB_INSERTSTRING: 2612 return LISTBOX_InsertString( descr, wParam, (const WCHAR *)lParam ); 2613 2614 case LB_ADDFILE: 2615 { 2616 const WCHAR *textW = (const WCHAR *)lParam; 2617 INT index = LISTBOX_FindFileStrPos( descr, textW ); 2618 return LISTBOX_InsertString( descr, index, textW ); 2619 } 2620 2621 case LB_DELETESTRING: 2622 if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR) 2623 return descr->nb_items; 2624 else 2625 { 2626 SetLastError(ERROR_INVALID_INDEX); 2627 return LB_ERR; 2628 } 2629 2630 case LB_GETITEMDATA: 2631 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items)) 2632 { 2633 SetLastError(ERROR_INVALID_INDEX); 2634 return LB_ERR; 2635 } 2636 return descr->items[wParam].data; 2637 2638 case LB_SETITEMDATA: 2639 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items)) 2640 { 2641 SetLastError(ERROR_INVALID_INDEX); 2642 return LB_ERR; 2643 } 2644 descr->items[wParam].data = lParam; 2645 /* undocumented: returns TRUE, not LB_OKAY (0) */ 2646 return TRUE; 2647 2648 case LB_GETCOUNT: 2649 return descr->nb_items; 2650 2651 case LB_GETTEXT: 2652 return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, TRUE ); 2653 2654 case LB_GETTEXTLEN: 2655 if ((INT)wParam >= descr->nb_items || (INT)wParam < 0) 2656 { 2657 SetLastError(ERROR_INVALID_INDEX); 2658 return LB_ERR; 2659 } 2660 if (!HAS_STRINGS(descr)) return sizeof(DWORD); 2661 return strlenW( descr->items[wParam].str ); 2662 2663 case LB_GETCURSEL: 2664 if (descr->nb_items == 0) 2665 return LB_ERR; 2666 if (!IS_MULTISELECT(descr)) 2667 return descr->selected_item; 2668 if (descr->selected_item != -1) 2669 return descr->selected_item; 2670 return descr->focus_item; 2671 /* otherwise, if the user tries to move the selection with the */ 2672 /* arrow keys, we will give the application something to choke on */ 2673 case LB_GETTOPINDEX: 2674 return descr->top_item; 2675 2676 case LB_GETITEMHEIGHT: 2677 return LISTBOX_GetItemHeight( descr, wParam ); 2678 2679 case LB_SETITEMHEIGHT: 2680 return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE ); 2681 2682 case LB_ITEMFROMPOINT: 2683 { 2684 POINT pt; 2685 RECT rect; 2686 int index; 2687 BOOL hit = TRUE; 2688 2689 /* The hiword of the return value is not a client area 2690 hittest as suggested by MSDN, but rather a hittest on 2691 the returned listbox item. */ 2692 2693 if(descr->nb_items == 0) 2694 return 0x1ffff; /* win9x returns 0x10000, we copy winnt */ 2695 2696 pt.x = (short)LOWORD(lParam); 2697 pt.y = (short)HIWORD(lParam); 2698 2699 SetRect(&rect, 0, 0, descr->width, descr->height); 2700 2701 if(!PtInRect(&rect, pt)) 2702 { 2703 pt.x = min(pt.x, rect.right - 1); 2704 pt.x = max(pt.x, 0); 2705 pt.y = min(pt.y, rect.bottom - 1); 2706 pt.y = max(pt.y, 0); 2707 hit = FALSE; 2708 } 2709 2710 index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y); 2711 2712 if(index == -1) 2713 { 2714 index = descr->nb_items - 1; 2715 hit = FALSE; 2716 } 2717 return MAKELONG(index, hit ? 0 : 1); 2718 } 2719 2720 case LB_SETCARETINDEX: 2721 if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR; 2722 if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR) 2723 return LB_ERR; 2724 else if (ISWIN31) 2725 return wParam; 2726 else 2727 return LB_OKAY; 2728 2729 case LB_GETCARETINDEX: 2730 return descr->focus_item; 2731 2732 case LB_SETTOPINDEX: 2733 return LISTBOX_SetTopItem( descr, wParam, TRUE ); 2734 2735 case LB_SETCOLUMNWIDTH: 2736 return LISTBOX_SetColumnWidth( descr, wParam ); 2737 2738 case LB_GETITEMRECT: 2739 return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam ); 2740 2741 case LB_FINDSTRING: 2742 return LISTBOX_FindString( descr, wParam, (const WCHAR *)lParam, FALSE ); 2743 2744 case LB_FINDSTRINGEXACT: 2745 return LISTBOX_FindString( descr, wParam, (const WCHAR *)lParam, TRUE ); 2746 2747 case LB_SELECTSTRING: 2748 { 2749 const WCHAR *textW = (const WCHAR *)lParam; 2750 INT index; 2751 2752 if (HAS_STRINGS(descr)) 2753 TRACE("LB_SELECTSTRING: %s\n", debugstr_w(textW)); 2754 2755 index = LISTBOX_FindString( descr, wParam, textW, FALSE ); 2756 if (index != LB_ERR) 2757 { 2758 LISTBOX_MoveCaret( descr, index, TRUE ); 2759 LISTBOX_SetSelection( descr, index, TRUE, FALSE ); 2760 } 2761 return index; 2762 } 2763 2764 case LB_GETSEL: 2765 if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items)) 2766 return LB_ERR; 2767 return descr->items[wParam].selected; 2768 2769 case LB_SETSEL: 2770 return LISTBOX_SetSelection( descr, lParam, wParam, FALSE ); 2771 2772 case LB_SETCURSEL: 2773 if (IS_MULTISELECT(descr)) return LB_ERR; 2774 LISTBOX_SetCaretIndex( descr, wParam, TRUE ); 2775 ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE ); 2776 if (ret != LB_ERR) ret = descr->selected_item; 2777 return ret; 2778 2779 case LB_GETSELCOUNT: 2780 return LISTBOX_GetSelCount( descr ); 2781 2782 case LB_GETSELITEMS: 2783 return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam ); 2784 2785 case LB_SELITEMRANGE: 2786 if (LOWORD(lParam) <= HIWORD(lParam)) 2787 return LISTBOX_SelectItemRange( descr, LOWORD(lParam), 2788 HIWORD(lParam), wParam ); 2789 else 2790 return LISTBOX_SelectItemRange( descr, HIWORD(lParam), 2791 LOWORD(lParam), wParam ); 2792 2793 case LB_SELITEMRANGEEX: 2794 if ((INT)lParam >= (INT)wParam) 2795 return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE ); 2796 else 2797 return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE); 2798 2799 case LB_GETHORIZONTALEXTENT: 2800 return descr->horz_extent; 2801 2802 case LB_SETHORIZONTALEXTENT: 2803 return LISTBOX_SetHorizontalExtent( descr, wParam ); 2804 2805 case LB_GETANCHORINDEX: 2806 return descr->anchor_item; 2807 2808 case LB_SETANCHORINDEX: 2809 if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items)) 2810 { 2811 SetLastError(ERROR_INVALID_INDEX); 2812 return LB_ERR; 2813 } 2814 descr->anchor_item = (INT)wParam; 2815 return LB_OKAY; 2816 2817 case LB_DIR: 2818 return LISTBOX_Directory( descr, wParam, (const WCHAR *)lParam, msg == LB_DIR ); 2819 2820 case LB_GETLOCALE: 2821 return descr->locale; 2822 2823 case LB_SETLOCALE: 2824 { 2825 LCID ret; 2826 if (!IsValidLocale((LCID)wParam, LCID_INSTALLED)) 2827 return LB_ERR; 2828 ret = descr->locale; 2829 descr->locale = (LCID)wParam; 2830 return ret; 2831 } 2832 2833 case LB_INITSTORAGE: 2834 return LISTBOX_InitStorage( descr, wParam ); 2835 2836 case LB_SETCOUNT: 2837 return LISTBOX_SetCount( descr, (INT)wParam ); 2838 2839 case LB_SETTABSTOPS: 2840 return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam ); 2841 2842 case LB_CARETON: 2843 if (descr->caret_on) 2844 return LB_OKAY; 2845 descr->caret_on = TRUE; 2846 if ((descr->focus_item != -1) && (descr->in_focus)) 2847 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS ); 2848 return LB_OKAY; 2849 2850 case LB_CARETOFF: 2851 if (!descr->caret_on) 2852 return LB_OKAY; 2853 descr->caret_on = FALSE; 2854 if ((descr->focus_item != -1) && (descr->in_focus)) 2855 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS ); 2856 return LB_OKAY; 2857 2858 case LB_GETLISTBOXINFO: 2859 return descr->page_size; 2860 2861 case WM_DESTROY: 2862 return LISTBOX_Destroy( descr ); 2863 2864 case WM_ENABLE: 2865 InvalidateRect( descr->self, NULL, TRUE ); 2866 return 0; 2867 2868 case WM_SETREDRAW: 2869 LISTBOX_SetRedraw( descr, wParam != 0 ); 2870 return 0; 2871 2872 case WM_GETDLGCODE: 2873 return DLGC_WANTARROWS | DLGC_WANTCHARS; 2874 2875 case WM_PRINTCLIENT: 2876 case WM_PAINT: 2877 { 2878 PAINTSTRUCT ps; 2879 HDC hdc = ( wParam ) ? ((HDC)wParam) : BeginPaint( descr->self, &ps ); 2880 ret = LISTBOX_Paint( descr, hdc ); 2881 if( !wParam ) EndPaint( descr->self, &ps ); 2882 } 2883 return ret; 2884 2885 case WM_NCPAINT: 2886 LISTBOX_NCPaint( descr, (HRGN)wParam ); 2887 break; 2888 2889 case WM_SIZE: 2890 LISTBOX_UpdateSize( descr ); 2891 return 0; 2892 case WM_GETFONT: 2893 return (LRESULT)descr->font; 2894 case WM_SETFONT: 2895 LISTBOX_SetFont( descr, (HFONT)wParam ); 2896 if (lParam) InvalidateRect( descr->self, 0, TRUE ); 2897 return 0; 2898 case WM_SETFOCUS: 2899 descr->in_focus = TRUE; 2900 descr->caret_on = TRUE; 2901 if (descr->focus_item != -1) 2902 LISTBOX_DrawFocusRect( descr, TRUE ); 2903 SEND_NOTIFICATION( descr, LBN_SETFOCUS ); 2904 return 0; 2905 case WM_KILLFOCUS: 2906 LISTBOX_HandleLButtonUp( descr ); /* Release capture if we have it */ 2907 descr->in_focus = FALSE; 2908 descr->wheel_remain = 0; 2909 if ((descr->focus_item != -1) && descr->caret_on) 2910 LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS ); 2911 SEND_NOTIFICATION( descr, LBN_KILLFOCUS ); 2912 return 0; 2913 case WM_HSCROLL: 2914 return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) ); 2915 case WM_VSCROLL: 2916 return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) ); 2917 case WM_MOUSEWHEEL: 2918 if (wParam & (MK_SHIFT | MK_CONTROL)) 2919 return DefWindowProcW( descr->self, msg, wParam, lParam ); 2920 return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) ); 2921 case WM_LBUTTONDOWN: 2922 if (lphc) 2923 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam, 2924 (INT16)LOWORD(lParam), 2925 (INT16)HIWORD(lParam) ); 2926 return LISTBOX_HandleLButtonDown( descr, wParam, 2927 (INT16)LOWORD(lParam), 2928 (INT16)HIWORD(lParam) ); 2929 case WM_LBUTTONDBLCLK: 2930 if (lphc) 2931 return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam, 2932 (INT16)LOWORD(lParam), 2933 (INT16)HIWORD(lParam) ); 2934 if (descr->style & LBS_NOTIFY) 2935 SEND_NOTIFICATION( descr, LBN_DBLCLK ); 2936 return 0; 2937 case WM_MOUSEMOVE: 2938 if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) ) 2939 { 2940 BOOL captured = descr->captured; 2941 POINT mousePos; 2942 RECT clientRect; 2943 2944 mousePos.x = (INT16)LOWORD(lParam); 2945 mousePos.y = (INT16)HIWORD(lParam); 2946 2947 /* 2948 * If we are in a dropdown combobox, we simulate that 2949 * the mouse is captured to show the tracking of the item. 2950 */ 2951 if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos )) 2952 descr->captured = TRUE; 2953 2954 LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y); 2955 2956 descr->captured = captured; 2957 } 2958 else if (GetCapture() == descr->self) 2959 { 2960 LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam), 2961 (INT16)HIWORD(lParam) ); 2962 } 2963 return 0; 2964 case WM_LBUTTONUP: 2965 if (lphc) 2966 { 2967 POINT mousePos; 2968 RECT clientRect; 2969 2970 /* 2971 * If the mouse button "up" is not in the listbox, 2972 * we make sure there is no selection by re-selecting the 2973 * item that was selected when the listbox was made visible. 2974 */ 2975 mousePos.x = (INT16)LOWORD(lParam); 2976 mousePos.y = (INT16)HIWORD(lParam); 2977 2978 GetClientRect(descr->self, &clientRect); 2979 2980 /* 2981 * When the user clicks outside the combobox and the focus 2982 * is lost, the owning combobox will send a fake buttonup with 2983 * 0xFFFFFFF as the mouse location, we must also revert the 2984 * selection to the original selection. 2985 */ 2986 if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) ) 2987 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE ); 2988 } 2989 return LISTBOX_HandleLButtonUp( descr ); 2990 case WM_KEYDOWN: 2991 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE ) 2992 { 2993 /* for some reason Windows makes it possible to 2994 * show/hide ComboLBox by sending it WM_KEYDOWNs */ 2995 2996 if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) || 2997 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED) 2998 && (wParam == VK_DOWN || wParam == VK_UP)) ) 2999 { 3000 COMBO_FlipListbox( lphc, FALSE, FALSE ); 3001 return 0; 3002 } 3003 } 3004 return LISTBOX_HandleKeyDown( descr, wParam ); 3005 case WM_CHAR: 3006 return LISTBOX_HandleChar( descr, wParam ); 3007 3008 case WM_SYSTIMER: 3009 return LISTBOX_HandleSystemTimer( descr ); 3010 case WM_ERASEBKGND: 3011 if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED)) 3012 { 3013 RECT rect; 3014 HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX, 3015 wParam, (LPARAM)descr->self ); 3016 TRACE("hbrush = %p\n", hbrush); 3017 if(!hbrush) 3018 hbrush = GetSysColorBrush(COLOR_WINDOW); 3019 if(hbrush) 3020 { 3021 GetClientRect(descr->self, &rect); 3022 FillRect((HDC)wParam, &rect, hbrush); 3023 } 3024 } 3025 return 1; 3026 case WM_DROPFILES: 3027 if( lphc ) return 0; 3028 return SendMessageW( descr->owner, msg, wParam, lParam ); 3029 3030 case WM_NCDESTROY: 3031 if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE ) 3032 lphc->hWndLBox = 0; 3033 break; 3034 3035 case WM_NCACTIVATE: 3036 if (lphc) return 0; 3037 break; 3038 3039 case WM_THEMECHANGED: 3040 theme = GetWindowTheme( hwnd ); 3041 CloseThemeData( theme ); 3042 OpenThemeData( hwnd, WC_LISTBOXW ); 3043 break; 3044 3045 default: 3046 if ((msg >= WM_USER) && (msg < 0xc000)) 3047 WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n", 3048 hwnd, msg, wParam, lParam ); 3049 } 3050 3051 return DefWindowProcW( hwnd, msg, wParam, lParam ); 3052 } 3053 3054 void LISTBOX_Register(void) 3055 { 3056 WNDCLASSW wndClass; 3057 3058 memset(&wndClass, 0, sizeof(wndClass)); 3059 wndClass.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS; 3060 wndClass.lpfnWndProc = LISTBOX_WindowProc; 3061 wndClass.cbClsExtra = 0; 3062 wndClass.cbWndExtra = sizeof(LB_DESCR *); 3063 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW); 3064 wndClass.hbrBackground = NULL; 3065 wndClass.lpszClassName = WC_LISTBOXW; 3066 RegisterClassW(&wndClass); 3067 } 3068 3069 void COMBOLBOX_Register(void) 3070 { 3071 static const WCHAR combolboxW[] = {'C','o','m','b','o','L','B','o','x',0}; 3072 WNDCLASSW wndClass; 3073 3074 memset(&wndClass, 0, sizeof(wndClass)); 3075 wndClass.style = CS_SAVEBITS | CS_DBLCLKS | CS_DROPSHADOW | CS_GLOBALCLASS; 3076 wndClass.lpfnWndProc = LISTBOX_WindowProc; 3077 wndClass.cbClsExtra = 0; 3078 wndClass.cbWndExtra = sizeof(LB_DESCR *); 3079 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW); 3080 wndClass.hbrBackground = NULL; 3081 wndClass.lpszClassName = combolboxW; 3082 RegisterClassW(&wndClass); 3083 } 3084 3085 #ifdef __REACTOS__ 3086 void LISTBOX_Unregister(void) 3087 { 3088 UnregisterClassW(WC_LISTBOXW, NULL); 3089 } 3090 3091 void COMBOLBOX_Unregister(void) 3092 { 3093 static const WCHAR combolboxW[] = {'C','o','m','b','o','L','B','o','x',0}; 3094 UnregisterClassW(combolboxW, NULL); 3095 } 3096 #endif 3097