1 /* 2 * Combo controls 3 * 4 * Copyright 1997 Alex Korobka 5 * Copyright (c) 2005 by 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 * - ComboBox_[GS]etMinVisible() 23 * - CB_GETMINVISIBLE, CB_SETMINVISIBLE 24 * - CB_SETTOPINDEX 25 */ 26 27 #include <stdarg.h> 28 #include <string.h> 29 30 #define OEMRESOURCE 31 32 #include "windef.h" 33 #include "winbase.h" 34 #include "wingdi.h" 35 #include "winuser.h" 36 #include "uxtheme.h" 37 #include "vssym32.h" 38 #include "commctrl.h" 39 #include "wine/unicode.h" 40 #include "wine/debug.h" 41 #include "wine/heap.h" 42 43 #include "comctl32.h" 44 45 WINE_DEFAULT_DEBUG_CHANNEL(combo); 46 47 /* bits in the dwKeyData */ 48 #define KEYDATA_ALT 0x2000 49 #define KEYDATA_PREVSTATE 0x4000 50 51 /* 52 * Additional combo box definitions 53 */ 54 55 #define CB_NOTIFY( lphc, code ) \ 56 (SendMessageW((lphc)->owner, WM_COMMAND, \ 57 MAKEWPARAM(GetWindowLongPtrW((lphc)->self,GWLP_ID), (code)), (LPARAM)(lphc)->self)) 58 59 #define CB_DISABLED( lphc ) (!IsWindowEnabled((lphc)->self)) 60 #define CB_OWNERDRAWN( lphc ) ((lphc)->dwStyle & (CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE)) 61 #define CB_HASSTRINGS( lphc ) ((lphc)->dwStyle & CBS_HASSTRINGS) 62 #define CB_HWND( lphc ) ((lphc)->self) 63 #define CB_GETTYPE( lphc ) ((lphc)->dwStyle & (CBS_DROPDOWNLIST)) 64 65 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03) 66 67 /* 68 * Drawing globals 69 */ 70 static HBITMAP hComboBmp = 0; 71 static UINT CBitHeight, CBitWidth; 72 73 /* 74 * Look and feel dependent "constants" 75 */ 76 77 #define COMBO_YBORDERGAP 5 78 #define COMBO_XBORDERSIZE() 2 79 #define COMBO_YBORDERSIZE() 2 80 #define COMBO_EDITBUTTONSPACE() 0 81 #define EDIT_CONTROL_PADDING() 1 82 83 #define ID_CB_LISTBOX 1000 84 #define ID_CB_EDIT 1001 85 86 /*********************************************************************** 87 * COMBO_Init 88 * 89 * Load combo button bitmap. 90 */ 91 static BOOL COMBO_Init(void) 92 { 93 HDC hDC; 94 95 if( hComboBmp ) return TRUE; 96 if( (hDC = CreateCompatibleDC(0)) ) 97 { 98 BOOL bRet = FALSE; 99 if( (hComboBmp = LoadBitmapW(0, MAKEINTRESOURCEW(OBM_COMBO))) ) 100 { 101 BITMAP bm; 102 HBITMAP hPrevB; 103 RECT r; 104 105 GetObjectW( hComboBmp, sizeof(bm), &bm ); 106 CBitHeight = bm.bmHeight; 107 CBitWidth = bm.bmWidth; 108 109 TRACE("combo bitmap [%i,%i]\n", CBitWidth, CBitHeight ); 110 111 hPrevB = SelectObject( hDC, hComboBmp); 112 SetRect( &r, 0, 0, CBitWidth, CBitHeight ); 113 InvertRect( hDC, &r ); 114 SelectObject( hDC, hPrevB ); 115 bRet = TRUE; 116 } 117 DeleteDC( hDC ); 118 return bRet; 119 } 120 return FALSE; 121 } 122 123 /*********************************************************************** 124 * COMBO_NCCreate 125 */ 126 static LRESULT COMBO_NCCreate(HWND hwnd, LONG style) 127 { 128 HEADCOMBO *lphc; 129 130 if (COMBO_Init() && (lphc = heap_alloc_zero(sizeof(*lphc)))) 131 { 132 lphc->self = hwnd; 133 SetWindowLongPtrW( hwnd, 0, (LONG_PTR)lphc ); 134 135 /* some braindead apps do try to use scrollbar/border flags */ 136 137 lphc->dwStyle = style & ~(WS_BORDER | WS_HSCROLL | WS_VSCROLL); 138 SetWindowLongW( hwnd, GWL_STYLE, style & ~(WS_BORDER | WS_HSCROLL | WS_VSCROLL) ); 139 140 /* 141 * We also have to remove the client edge style to make sure 142 * we don't end-up with a non client area. 143 */ 144 SetWindowLongW( hwnd, GWL_EXSTYLE, 145 GetWindowLongW( hwnd, GWL_EXSTYLE ) & ~WS_EX_CLIENTEDGE ); 146 147 if( !(style & (CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE)) ) 148 lphc->dwStyle |= CBS_HASSTRINGS; 149 if( !(GetWindowLongW( hwnd, GWL_EXSTYLE ) & WS_EX_NOPARENTNOTIFY) ) 150 lphc->wState |= CBF_NOTIFY; 151 152 TRACE("[%p], style = %08x\n", lphc, lphc->dwStyle ); 153 return TRUE; 154 } 155 return FALSE; 156 } 157 158 /*********************************************************************** 159 * COMBO_NCDestroy 160 */ 161 static LRESULT COMBO_NCDestroy( HEADCOMBO *lphc ) 162 { 163 if (lphc) 164 { 165 TRACE("[%p]: freeing storage\n", lphc->self); 166 167 if ( (CB_GETTYPE(lphc) != CBS_SIMPLE) && lphc->hWndLBox ) 168 DestroyWindow( lphc->hWndLBox ); 169 170 SetWindowLongPtrW( lphc->self, 0, 0 ); 171 heap_free( lphc ); 172 } 173 174 return 0; 175 } 176 177 /*********************************************************************** 178 * CBGetTextAreaHeight 179 * 180 * This method will calculate the height of the text area of the 181 * combobox. 182 * The height of the text area is set in two ways. 183 * It can be set explicitly through a combobox message or through a 184 * WM_MEASUREITEM callback. 185 * If this is not the case, the height is set to font height + 4px 186 * This height was determined through experimentation. 187 * CBCalcPlacement will add 2*COMBO_YBORDERSIZE pixels for the border 188 */ 189 static INT CBGetTextAreaHeight( 190 HWND hwnd, 191 LPHEADCOMBO lphc) 192 { 193 INT iTextItemHeight; 194 195 if( lphc->editHeight ) /* explicitly set height */ 196 { 197 iTextItemHeight = lphc->editHeight; 198 } 199 else 200 { 201 TEXTMETRICW tm; 202 HDC hDC = GetDC(hwnd); 203 HFONT hPrevFont = 0; 204 INT baseUnitY; 205 206 if (lphc->hFont) 207 hPrevFont = SelectObject( hDC, lphc->hFont ); 208 209 GetTextMetricsW(hDC, &tm); 210 211 baseUnitY = tm.tmHeight; 212 213 if( hPrevFont ) 214 SelectObject( hDC, hPrevFont ); 215 216 ReleaseDC(hwnd, hDC); 217 218 iTextItemHeight = baseUnitY + 4; 219 } 220 221 /* 222 * Check the ownerdraw case if we haven't asked the parent the size 223 * of the item yet. 224 */ 225 if ( CB_OWNERDRAWN(lphc) && 226 (lphc->wState & CBF_MEASUREITEM) ) 227 { 228 MEASUREITEMSTRUCT measureItem; 229 RECT clientRect; 230 INT originalItemHeight = iTextItemHeight; 231 UINT id = (UINT)GetWindowLongPtrW( lphc->self, GWLP_ID ); 232 233 /* 234 * We use the client rect for the width of the item. 235 */ 236 GetClientRect(hwnd, &clientRect); 237 238 lphc->wState &= ~CBF_MEASUREITEM; 239 240 /* 241 * Send a first one to measure the size of the text area 242 */ 243 measureItem.CtlType = ODT_COMBOBOX; 244 measureItem.CtlID = id; 245 measureItem.itemID = -1; 246 measureItem.itemWidth = clientRect.right; 247 measureItem.itemHeight = iTextItemHeight - 6; /* ownerdrawn cb is taller */ 248 measureItem.itemData = 0; 249 SendMessageW(lphc->owner, WM_MEASUREITEM, id, (LPARAM)&measureItem); 250 iTextItemHeight = 6 + measureItem.itemHeight; 251 252 /* 253 * Send a second one in the case of a fixed ownerdraw list to calculate the 254 * size of the list items. (we basically do this on behalf of the listbox) 255 */ 256 if (lphc->dwStyle & CBS_OWNERDRAWFIXED) 257 { 258 measureItem.CtlType = ODT_COMBOBOX; 259 measureItem.CtlID = id; 260 measureItem.itemID = 0; 261 measureItem.itemWidth = clientRect.right; 262 measureItem.itemHeight = originalItemHeight; 263 measureItem.itemData = 0; 264 SendMessageW(lphc->owner, WM_MEASUREITEM, id, (LPARAM)&measureItem); 265 lphc->fixedOwnerDrawHeight = measureItem.itemHeight; 266 } 267 268 /* 269 * Keep the size for the next time 270 */ 271 lphc->editHeight = iTextItemHeight; 272 } 273 274 return iTextItemHeight; 275 } 276 277 /*********************************************************************** 278 * CBForceDummyResize 279 * 280 * The dummy resize is used for listboxes that have a popup to trigger 281 * a re-arranging of the contents of the combobox and the recalculation 282 * of the size of the "real" control window. 283 */ 284 static void CBForceDummyResize( 285 LPHEADCOMBO lphc) 286 { 287 RECT windowRect; 288 int newComboHeight; 289 290 newComboHeight = CBGetTextAreaHeight(lphc->self,lphc) + 2*COMBO_YBORDERSIZE(); 291 292 GetWindowRect(lphc->self, &windowRect); 293 294 /* 295 * We have to be careful, resizing a combobox also has the meaning that the 296 * dropped rect will be resized. In this case, we want to trigger a resize 297 * to recalculate layout but we don't want to change the dropped rectangle 298 * So, we pass the height of text area of control as the height. 299 * this will cancel-out in the processing of the WM_WINDOWPOSCHANGING 300 * message. 301 */ 302 SetWindowPos( lphc->self, 303 NULL, 304 0, 0, 305 windowRect.right - windowRect.left, 306 newComboHeight, 307 SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE ); 308 } 309 310 /*********************************************************************** 311 * CBCalcPlacement 312 * 313 * Set up component coordinates given valid lphc->RectCombo. 314 */ 315 static void CBCalcPlacement( 316 HWND hwnd, 317 LPHEADCOMBO lphc, 318 LPRECT lprEdit, 319 LPRECT lprButton, 320 LPRECT lprLB) 321 { 322 /* 323 * Again, start with the client rectangle. 324 */ 325 GetClientRect(hwnd, lprEdit); 326 327 /* 328 * Remove the borders 329 */ 330 InflateRect(lprEdit, -COMBO_XBORDERSIZE(), -COMBO_YBORDERSIZE()); 331 332 /* 333 * Chop off the bottom part to fit with the height of the text area. 334 */ 335 lprEdit->bottom = lprEdit->top + CBGetTextAreaHeight(hwnd, lphc); 336 337 /* 338 * The button starts the same vertical position as the text area. 339 */ 340 CopyRect(lprButton, lprEdit); 341 342 /* 343 * If the combobox is "simple" there is no button. 344 */ 345 if( CB_GETTYPE(lphc) == CBS_SIMPLE ) 346 lprButton->left = lprButton->right = lprButton->bottom = 0; 347 else 348 { 349 /* 350 * Let's assume the combobox button is the same width as the 351 * scrollbar button. 352 * size the button horizontally and cut-off the text area. 353 */ 354 lprButton->left = lprButton->right - GetSystemMetrics(SM_CXVSCROLL); 355 lprEdit->right = lprButton->left; 356 } 357 358 /* 359 * In the case of a dropdown, there is an additional spacing between the 360 * text area and the button. 361 */ 362 if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) 363 { 364 lprEdit->right -= COMBO_EDITBUTTONSPACE(); 365 } 366 367 /* 368 * If we have an edit control, we space it away from the borders slightly. 369 */ 370 if (CB_GETTYPE(lphc) != CBS_DROPDOWNLIST) 371 { 372 InflateRect(lprEdit, -EDIT_CONTROL_PADDING(), -EDIT_CONTROL_PADDING()); 373 } 374 375 /* 376 * Adjust the size of the listbox popup. 377 */ 378 if( CB_GETTYPE(lphc) == CBS_SIMPLE ) 379 { 380 /* 381 * Use the client rectangle to initialize the listbox rectangle 382 */ 383 GetClientRect(hwnd, lprLB); 384 385 /* 386 * Then, chop-off the top part. 387 */ 388 lprLB->top = lprEdit->bottom + COMBO_YBORDERSIZE(); 389 } 390 else 391 { 392 /* 393 * Make sure the dropped width is as large as the combobox itself. 394 */ 395 if (lphc->droppedWidth < (lprButton->right + COMBO_XBORDERSIZE())) 396 { 397 lprLB->right = lprLB->left + (lprButton->right + COMBO_XBORDERSIZE()); 398 399 /* 400 * In the case of a dropdown, the popup listbox is offset to the right. 401 * so, we want to make sure it's flush with the right side of the 402 * combobox 403 */ 404 if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) 405 lprLB->right -= COMBO_EDITBUTTONSPACE(); 406 } 407 else 408 lprLB->right = lprLB->left + lphc->droppedWidth; 409 } 410 411 /* don't allow negative window width */ 412 if (lprEdit->right < lprEdit->left) 413 lprEdit->right = lprEdit->left; 414 415 TRACE("\ttext\t= (%s)\n", wine_dbgstr_rect(lprEdit)); 416 417 TRACE("\tbutton\t= (%s)\n", wine_dbgstr_rect(lprButton)); 418 419 TRACE("\tlbox\t= (%s)\n", wine_dbgstr_rect(lprLB)); 420 } 421 422 /*********************************************************************** 423 * CBGetDroppedControlRect 424 */ 425 static void CBGetDroppedControlRect( LPHEADCOMBO lphc, LPRECT lpRect) 426 { 427 /* In windows, CB_GETDROPPEDCONTROLRECT returns the upper left corner 428 of the combo box and the lower right corner of the listbox */ 429 430 GetWindowRect(lphc->self, lpRect); 431 432 lpRect->right = lpRect->left + lphc->droppedRect.right - lphc->droppedRect.left; 433 lpRect->bottom = lpRect->top + lphc->droppedRect.bottom - lphc->droppedRect.top; 434 435 } 436 437 /*********************************************************************** 438 * COMBO_Create 439 */ 440 static LRESULT COMBO_Create( HWND hwnd, LPHEADCOMBO lphc, HWND hwndParent, LONG style ) 441 { 442 static const WCHAR clbName[] = {'C','o','m','b','o','L','B','o','x',0}; 443 static const WCHAR editName[] = {'E','d','i','t',0}; 444 445 OpenThemeData( hwnd, WC_COMBOBOXW ); 446 if( !CB_GETTYPE(lphc) ) lphc->dwStyle |= CBS_SIMPLE; 447 if( CB_GETTYPE(lphc) != CBS_DROPDOWNLIST ) lphc->wState |= CBF_EDIT; 448 449 lphc->owner = hwndParent; 450 451 /* 452 * The item height and dropped width are not set when the control 453 * is created. 454 */ 455 lphc->droppedWidth = lphc->editHeight = 0; 456 457 /* 458 * The first time we go through, we want to measure the ownerdraw item 459 */ 460 lphc->wState |= CBF_MEASUREITEM; 461 462 /* M$ IE 3.01 actually creates (and rapidly destroys) an ownerless combobox */ 463 464 if( lphc->owner || !(style & WS_VISIBLE) ) 465 { 466 UINT lbeStyle = 0; 467 UINT lbeExStyle = 0; 468 469 /* 470 * Initialize the dropped rect to the size of the client area of the 471 * control and then, force all the areas of the combobox to be 472 * recalculated. 473 */ 474 GetClientRect( hwnd, &lphc->droppedRect ); 475 CBCalcPlacement(hwnd, lphc, &lphc->textRect, &lphc->buttonRect, &lphc->droppedRect ); 476 477 /* 478 * Adjust the position of the popup listbox if it's necessary 479 */ 480 if ( CB_GETTYPE(lphc) != CBS_SIMPLE ) 481 { 482 lphc->droppedRect.top = lphc->textRect.bottom + COMBO_YBORDERSIZE(); 483 484 /* 485 * If it's a dropdown, the listbox is offset 486 */ 487 if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) 488 lphc->droppedRect.left += COMBO_EDITBUTTONSPACE(); 489 490 if (lphc->droppedRect.bottom < lphc->droppedRect.top) 491 lphc->droppedRect.bottom = lphc->droppedRect.top; 492 if (lphc->droppedRect.right < lphc->droppedRect.left) 493 lphc->droppedRect.right = lphc->droppedRect.left; 494 MapWindowPoints( hwnd, 0, (LPPOINT)&lphc->droppedRect, 2 ); 495 } 496 497 /* create listbox popup */ 498 499 lbeStyle = (LBS_NOTIFY | LBS_COMBOBOX | WS_BORDER | WS_CLIPSIBLINGS | WS_CHILD) | 500 (style & (WS_VSCROLL | CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE)); 501 502 if( lphc->dwStyle & CBS_SORT ) 503 lbeStyle |= LBS_SORT; 504 if( lphc->dwStyle & CBS_HASSTRINGS ) 505 lbeStyle |= LBS_HASSTRINGS; 506 if( lphc->dwStyle & CBS_NOINTEGRALHEIGHT ) 507 lbeStyle |= LBS_NOINTEGRALHEIGHT; 508 if( lphc->dwStyle & CBS_DISABLENOSCROLL ) 509 lbeStyle |= LBS_DISABLENOSCROLL; 510 511 if( CB_GETTYPE(lphc) == CBS_SIMPLE ) /* child listbox */ 512 { 513 lbeStyle |= WS_VISIBLE; 514 515 /* 516 * In win 95 look n feel, the listbox in the simple combobox has 517 * the WS_EXCLIENTEDGE style instead of the WS_BORDER style. 518 */ 519 lbeStyle &= ~WS_BORDER; 520 lbeExStyle |= WS_EX_CLIENTEDGE; 521 } 522 else 523 { 524 lbeExStyle |= (WS_EX_TOPMOST | WS_EX_TOOLWINDOW); 525 } 526 527 lphc->hWndLBox = CreateWindowExW(lbeExStyle, clbName, NULL, lbeStyle, 528 lphc->droppedRect.left, lphc->droppedRect.top, lphc->droppedRect.right - lphc->droppedRect.left, 529 lphc->droppedRect.bottom - lphc->droppedRect.top, hwnd, (HMENU)ID_CB_LISTBOX, 530 (HINSTANCE)GetWindowLongPtrW( hwnd, GWLP_HINSTANCE ), lphc ); 531 if( lphc->hWndLBox ) 532 { 533 BOOL bEdit = TRUE; 534 lbeStyle = WS_CHILD | WS_VISIBLE | ES_NOHIDESEL | ES_LEFT | ES_COMBO; 535 536 if( lphc->wState & CBF_EDIT ) 537 { 538 if( lphc->dwStyle & CBS_OEMCONVERT ) 539 lbeStyle |= ES_OEMCONVERT; 540 if( lphc->dwStyle & CBS_AUTOHSCROLL ) 541 lbeStyle |= ES_AUTOHSCROLL; 542 if( lphc->dwStyle & CBS_LOWERCASE ) 543 lbeStyle |= ES_LOWERCASE; 544 else if( lphc->dwStyle & CBS_UPPERCASE ) 545 lbeStyle |= ES_UPPERCASE; 546 547 if (!IsWindowEnabled(hwnd)) lbeStyle |= WS_DISABLED; 548 549 lphc->hWndEdit = CreateWindowExW(0, editName, NULL, lbeStyle, 550 lphc->textRect.left, lphc->textRect.top, 551 lphc->textRect.right - lphc->textRect.left, 552 lphc->textRect.bottom - lphc->textRect.top, 553 hwnd, (HMENU)ID_CB_EDIT, 554 (HINSTANCE)GetWindowLongPtrW( hwnd, GWLP_HINSTANCE ), NULL ); 555 if( !lphc->hWndEdit ) 556 bEdit = FALSE; 557 } 558 559 if( bEdit ) 560 { 561 if( CB_GETTYPE(lphc) != CBS_SIMPLE ) 562 { 563 /* Now do the trick with parent */ 564 SetParent(lphc->hWndLBox, HWND_DESKTOP); 565 /* 566 * If the combo is a dropdown, we must resize the control 567 * to fit only the text area and button. To do this, 568 * we send a dummy resize and the WM_WINDOWPOSCHANGING message 569 * will take care of setting the height for us. 570 */ 571 CBForceDummyResize(lphc); 572 } 573 574 TRACE("init done\n"); 575 return 0; 576 } 577 ERR("edit control failure.\n"); 578 } else ERR("listbox failure.\n"); 579 } else ERR("no owner for visible combo.\n"); 580 581 /* CreateWindow() will send WM_NCDESTROY to cleanup */ 582 583 return -1; 584 } 585 586 /*********************************************************************** 587 * CBPaintButton 588 * 589 * Paint combo button (normal, pressed, and disabled states). 590 */ 591 static void CBPaintButton( LPHEADCOMBO lphc, HDC hdc, RECT rectButton) 592 { 593 UINT buttonState = DFCS_SCROLLCOMBOBOX; 594 595 if( lphc->wState & CBF_NOREDRAW ) 596 return; 597 598 599 if (lphc->wState & CBF_BUTTONDOWN) 600 buttonState |= DFCS_PUSHED; 601 602 if (CB_DISABLED(lphc)) 603 buttonState |= DFCS_INACTIVE; 604 605 DrawFrameControl(hdc, &rectButton, DFC_SCROLL, buttonState); 606 } 607 608 /*********************************************************************** 609 * COMBO_PrepareColors 610 * 611 * This method will sent the appropriate WM_CTLCOLOR message to 612 * prepare and setup the colors for the combo's DC. 613 * 614 * It also returns the brush to use for the background. 615 */ 616 static HBRUSH COMBO_PrepareColors( 617 LPHEADCOMBO lphc, 618 HDC hDC) 619 { 620 HBRUSH hBkgBrush; 621 622 /* 623 * Get the background brush for this control. 624 */ 625 if (CB_DISABLED(lphc)) 626 { 627 hBkgBrush = (HBRUSH)SendMessageW(lphc->owner, WM_CTLCOLORSTATIC, 628 (WPARAM)hDC, (LPARAM)lphc->self ); 629 630 /* 631 * We have to change the text color since WM_CTLCOLORSTATIC will 632 * set it to the "enabled" color. This is the same behavior as the 633 * edit control 634 */ 635 SetTextColor(hDC, GetSysColor(COLOR_GRAYTEXT)); 636 } 637 else 638 { 639 /* FIXME: In which cases WM_CTLCOLORLISTBOX should be sent? */ 640 hBkgBrush = (HBRUSH)SendMessageW(lphc->owner, WM_CTLCOLOREDIT, 641 (WPARAM)hDC, (LPARAM)lphc->self ); 642 } 643 644 /* 645 * Catch errors. 646 */ 647 if( !hBkgBrush ) 648 hBkgBrush = GetSysColorBrush(COLOR_WINDOW); 649 650 return hBkgBrush; 651 } 652 653 /*********************************************************************** 654 * CBPaintText 655 * 656 * Paint CBS_DROPDOWNLIST text field / update edit control contents. 657 */ 658 static void CBPaintText(HEADCOMBO *lphc, HDC hdc_paint) 659 { 660 RECT rectEdit = lphc->textRect; 661 INT id, size = 0; 662 LPWSTR pText = NULL; 663 664 TRACE("\n"); 665 666 /* follow Windows combobox that sends a bunch of text 667 * inquiries to its listbox while processing WM_PAINT. */ 668 669 if( (id = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0) ) != LB_ERR ) 670 { 671 size = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, id, 0); 672 if (size == LB_ERR) 673 FIXME("LB_ERR probably not handled yet\n"); 674 if ((pText = heap_alloc((size + 1) * sizeof(WCHAR)))) 675 { 676 /* size from LB_GETTEXTLEN may be too large, from LB_GETTEXT is accurate */ 677 size=SendMessageW(lphc->hWndLBox, LB_GETTEXT, id, (LPARAM)pText); 678 pText[size] = '\0'; /* just in case */ 679 } else return; 680 } 681 682 if( lphc->wState & CBF_EDIT ) 683 { 684 static const WCHAR empty_stringW[] = { 0 }; 685 if( CB_HASSTRINGS(lphc) ) SetWindowTextW( lphc->hWndEdit, pText ? pText : empty_stringW ); 686 if( lphc->wState & CBF_FOCUSED ) 687 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, MAXLONG); 688 } 689 else if(!(lphc->wState & CBF_NOREDRAW) && IsWindowVisible( lphc->self )) 690 { 691 /* paint text field ourselves */ 692 HDC hdc = hdc_paint ? hdc_paint : GetDC(lphc->self); 693 UINT itemState = ODS_COMBOBOXEDIT; 694 HFONT hPrevFont = (lphc->hFont) ? SelectObject(hdc, lphc->hFont) : 0; 695 HBRUSH hPrevBrush, hBkgBrush; 696 697 /* 698 * Give ourselves some space. 699 */ 700 InflateRect( &rectEdit, -1, -1 ); 701 702 hBkgBrush = COMBO_PrepareColors( lphc, hdc ); 703 hPrevBrush = SelectObject( hdc, hBkgBrush ); 704 FillRect( hdc, &rectEdit, hBkgBrush ); 705 706 if( CB_OWNERDRAWN(lphc) ) 707 { 708 DRAWITEMSTRUCT dis; 709 HRGN clipRegion; 710 UINT ctlid = (UINT)GetWindowLongPtrW( lphc->self, GWLP_ID ); 711 712 /* setup state for DRAWITEM message. Owner will highlight */ 713 if ( (lphc->wState & CBF_FOCUSED) && 714 !(lphc->wState & CBF_DROPPED) ) 715 itemState |= ODS_SELECTED | ODS_FOCUS; 716 717 if (!IsWindowEnabled(lphc->self)) itemState |= ODS_DISABLED; 718 719 dis.CtlType = ODT_COMBOBOX; 720 dis.CtlID = ctlid; 721 dis.hwndItem = lphc->self; 722 dis.itemAction = ODA_DRAWENTIRE; 723 dis.itemID = id; 724 dis.itemState = itemState; 725 dis.hDC = hdc; 726 dis.rcItem = rectEdit; 727 dis.itemData = SendMessageW(lphc->hWndLBox, LB_GETITEMDATA, id, 0); 728 729 /* 730 * Clip the DC and have the parent draw the item. 731 */ 732 clipRegion = set_control_clipping( hdc, &rectEdit ); 733 734 SendMessageW(lphc->owner, WM_DRAWITEM, ctlid, (LPARAM)&dis ); 735 736 SelectClipRgn( hdc, clipRegion ); 737 if (clipRegion) DeleteObject( clipRegion ); 738 } 739 else 740 { 741 static const WCHAR empty_stringW[] = { 0 }; 742 743 if ( (lphc->wState & CBF_FOCUSED) && 744 !(lphc->wState & CBF_DROPPED) ) { 745 746 /* highlight */ 747 FillRect( hdc, &rectEdit, GetSysColorBrush(COLOR_HIGHLIGHT) ); 748 SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) ); 749 SetTextColor( hdc, GetSysColor( COLOR_HIGHLIGHTTEXT ) ); 750 } 751 752 ExtTextOutW( hdc, 753 rectEdit.left + 1, 754 rectEdit.top + 1, 755 ETO_OPAQUE | ETO_CLIPPED, 756 &rectEdit, 757 pText ? pText : empty_stringW , size, NULL ); 758 759 if(lphc->wState & CBF_FOCUSED && !(lphc->wState & CBF_DROPPED)) 760 DrawFocusRect( hdc, &rectEdit ); 761 } 762 763 if( hPrevFont ) 764 SelectObject(hdc, hPrevFont ); 765 766 if( hPrevBrush ) 767 SelectObject( hdc, hPrevBrush ); 768 769 if( !hdc_paint ) 770 ReleaseDC( lphc->self, hdc ); 771 } 772 773 heap_free(pText); 774 } 775 776 /*********************************************************************** 777 * CBPaintBorder 778 */ 779 static void CBPaintBorder( 780 HWND hwnd, 781 const HEADCOMBO *lphc, 782 HDC hdc) 783 { 784 RECT clientRect; 785 786 if (CB_GETTYPE(lphc) != CBS_SIMPLE) 787 { 788 GetClientRect(hwnd, &clientRect); 789 } 790 else 791 { 792 clientRect = lphc->textRect; 793 794 InflateRect(&clientRect, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING()); 795 InflateRect(&clientRect, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE()); 796 } 797 798 DrawEdge(hdc, &clientRect, EDGE_SUNKEN, BF_RECT); 799 } 800 801 static LRESULT COMBO_ThemedPaint(HTHEME theme, HEADCOMBO *lphc, HDC hdc) 802 { 803 int button_state; 804 RECT frame; 805 806 /* paint border */ 807 if (CB_GETTYPE(lphc) != CBS_SIMPLE) 808 GetClientRect(lphc->self, &frame); 809 else 810 { 811 frame = lphc->textRect; 812 InflateRect(&frame, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING()); 813 InflateRect(&frame, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE()); 814 } 815 816 DrawThemeBackground(theme, hdc, 0, IsWindowEnabled(lphc->self) ? CBXS_NORMAL : CBXS_DISABLED, &frame, NULL); 817 818 /* Paint button */ 819 if (!IsRectEmpty(&lphc->buttonRect)) 820 { 821 if (!IsWindowEnabled(lphc->self)) 822 button_state = CBXS_DISABLED; 823 else if (lphc->wState & CBF_BUTTONDOWN) 824 button_state = CBXS_PRESSED; 825 else if (lphc->wState & CBF_HOT) 826 button_state = CBXS_HOT; 827 else 828 button_state = CBXS_NORMAL; 829 DrawThemeBackground(theme, hdc, CP_DROPDOWNBUTTON, button_state, &lphc->buttonRect, NULL); 830 } 831 832 if ((lphc->dwStyle & CBS_DROPDOWNLIST) == CBS_DROPDOWNLIST) 833 CBPaintText(lphc, hdc); 834 835 return 0; 836 } 837 838 /*********************************************************************** 839 * COMBO_Paint 840 */ 841 static LRESULT COMBO_Paint(HEADCOMBO *lphc, HDC hdc) 842 { 843 HBRUSH hPrevBrush, hBkgBrush; 844 845 TRACE("hdc=%p\n", hdc); 846 847 /* 848 * Retrieve the background brush and select it in the 849 * DC. 850 */ 851 hBkgBrush = COMBO_PrepareColors(lphc, hdc); 852 hPrevBrush = SelectObject(hdc, hBkgBrush); 853 if (!(lphc->wState & CBF_EDIT)) 854 FillRect(hdc, &lphc->textRect, hBkgBrush); 855 856 /* 857 * In non 3.1 look, there is a sunken border on the combobox 858 */ 859 CBPaintBorder(lphc->self, lphc, hdc); 860 861 if (!IsRectEmpty(&lphc->buttonRect)) 862 CBPaintButton(lphc, hdc, lphc->buttonRect); 863 864 /* paint the edit control padding area */ 865 if (CB_GETTYPE(lphc) != CBS_DROPDOWNLIST) 866 { 867 RECT rPadEdit = lphc->textRect; 868 869 InflateRect(&rPadEdit, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING()); 870 871 FrameRect(hdc, &rPadEdit, GetSysColorBrush(COLOR_WINDOW)); 872 } 873 874 if (!(lphc->wState & CBF_EDIT)) 875 CBPaintText( lphc, hdc ); 876 877 if (hPrevBrush) 878 SelectObject( hdc, hPrevBrush ); 879 880 return 0; 881 } 882 883 /*********************************************************************** 884 * CBUpdateLBox 885 * 886 * Select listbox entry according to the contents of the edit control. 887 */ 888 static INT CBUpdateLBox( LPHEADCOMBO lphc, BOOL bSelect ) 889 { 890 INT length, idx; 891 LPWSTR pText = NULL; 892 893 idx = LB_ERR; 894 length = SendMessageW( lphc->hWndEdit, WM_GETTEXTLENGTH, 0, 0 ); 895 896 if (length > 0) 897 pText = heap_alloc((length + 1) * sizeof(WCHAR)); 898 899 TRACE("\t edit text length %i\n", length ); 900 901 if( pText ) 902 { 903 GetWindowTextW( lphc->hWndEdit, pText, length + 1); 904 idx = SendMessageW(lphc->hWndLBox, LB_FINDSTRING, -1, (LPARAM)pText); 905 heap_free( pText ); 906 } 907 908 SendMessageW(lphc->hWndLBox, LB_SETCURSEL, bSelect ? idx : -1, 0); 909 910 /* probably superfluous but Windows sends this too */ 911 SendMessageW(lphc->hWndLBox, LB_SETCARETINDEX, idx < 0 ? 0 : idx, 0); 912 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, idx < 0 ? 0 : idx, 0); 913 914 return idx; 915 } 916 917 /*********************************************************************** 918 * CBUpdateEdit 919 * 920 * Copy a listbox entry to the edit control. 921 */ 922 static void CBUpdateEdit( LPHEADCOMBO lphc , INT index ) 923 { 924 INT length; 925 LPWSTR pText = NULL; 926 static const WCHAR empty_stringW[] = { 0 }; 927 928 TRACE("\t %i\n", index ); 929 930 if( index >= 0 ) /* got an entry */ 931 { 932 length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, index, 0); 933 if( length != LB_ERR) 934 { 935 if ((pText = heap_alloc((length + 1) * sizeof(WCHAR)))) 936 SendMessageW(lphc->hWndLBox, LB_GETTEXT, index, (LPARAM)pText); 937 } 938 } 939 940 if( CB_HASSTRINGS(lphc) ) 941 { 942 lphc->wState |= (CBF_NOEDITNOTIFY | CBF_NOLBSELECT); 943 SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, pText ? (LPARAM)pText : (LPARAM)empty_stringW); 944 lphc->wState &= ~(CBF_NOEDITNOTIFY | CBF_NOLBSELECT); 945 } 946 947 if( lphc->wState & CBF_FOCUSED ) 948 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, -1); 949 950 heap_free( pText ); 951 } 952 953 /*********************************************************************** 954 * CBDropDown 955 * 956 * Show listbox popup. 957 */ 958 static void CBDropDown( LPHEADCOMBO lphc ) 959 { 960 HMONITOR monitor; 961 MONITORINFO mon_info; 962 RECT rect,r; 963 int nItems; 964 int nDroppedHeight; 965 966 TRACE("[%p]: drop down\n", lphc->self); 967 968 CB_NOTIFY( lphc, CBN_DROPDOWN ); 969 970 /* set selection */ 971 972 lphc->wState |= CBF_DROPPED; 973 if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) 974 { 975 lphc->droppedIndex = CBUpdateLBox( lphc, TRUE ); 976 977 /* Update edit only if item is in the list */ 978 if( !(lphc->wState & CBF_CAPTURE) && lphc->droppedIndex >= 0) 979 CBUpdateEdit( lphc, lphc->droppedIndex ); 980 } 981 else 982 { 983 lphc->droppedIndex = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0); 984 985 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, 986 lphc->droppedIndex == LB_ERR ? 0 : lphc->droppedIndex, 0); 987 SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0); 988 } 989 990 /* now set popup position */ 991 GetWindowRect( lphc->self, &rect ); 992 993 /* 994 * If it's a dropdown, the listbox is offset 995 */ 996 if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) 997 rect.left += COMBO_EDITBUTTONSPACE(); 998 999 /* if the dropped height is greater than the total height of the dropped 1000 items list, then force the drop down list height to be the total height 1001 of the items in the dropped list */ 1002 1003 /* And Remove any extra space (Best Fit) */ 1004 nDroppedHeight = lphc->droppedRect.bottom - lphc->droppedRect.top; 1005 /* if listbox length has been set directly by its handle */ 1006 GetWindowRect(lphc->hWndLBox, &r); 1007 if (nDroppedHeight < r.bottom - r.top) 1008 nDroppedHeight = r.bottom - r.top; 1009 nItems = (int)SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0); 1010 1011 if (nItems > 0) 1012 { 1013 int nHeight; 1014 int nIHeight; 1015 1016 nIHeight = (int)SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, 0, 0); 1017 1018 nHeight = nIHeight*nItems; 1019 1020 if (nHeight < nDroppedHeight - COMBO_YBORDERSIZE()) 1021 nDroppedHeight = nHeight + COMBO_YBORDERSIZE(); 1022 1023 if (nDroppedHeight < nHeight) 1024 { 1025 if (nItems < 5) 1026 nDroppedHeight = (nItems+1)*nIHeight; 1027 else if (nDroppedHeight < 6*nIHeight) 1028 nDroppedHeight = 6*nIHeight; 1029 } 1030 } 1031 1032 r.left = rect.left; 1033 r.top = rect.bottom; 1034 r.right = r.left + lphc->droppedRect.right - lphc->droppedRect.left; 1035 r.bottom = r.top + nDroppedHeight; 1036 1037 /*If height of dropped rectangle gets beyond a screen size it should go up, otherwise down.*/ 1038 monitor = MonitorFromRect( &rect, MONITOR_DEFAULTTOPRIMARY ); 1039 mon_info.cbSize = sizeof(mon_info); 1040 GetMonitorInfoW( monitor, &mon_info ); 1041 1042 if (r.bottom > mon_info.rcWork.bottom) 1043 { 1044 r.top = max( rect.top - nDroppedHeight, mon_info.rcWork.top ); 1045 r.bottom = min( r.top + nDroppedHeight, mon_info.rcWork.bottom ); 1046 } 1047 1048 SetWindowPos( lphc->hWndLBox, HWND_TOPMOST, r.left, r.top, r.right - r.left, r.bottom - r.top, 1049 SWP_NOACTIVATE | SWP_SHOWWINDOW ); 1050 1051 1052 if( !(lphc->wState & CBF_NOREDRAW) ) 1053 RedrawWindow( lphc->self, NULL, 0, RDW_INVALIDATE | 1054 RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN ); 1055 1056 EnableWindow( lphc->hWndLBox, TRUE ); 1057 if (GetCapture() != lphc->self) 1058 SetCapture(lphc->hWndLBox); 1059 } 1060 1061 /*********************************************************************** 1062 * CBRollUp 1063 * 1064 * Hide listbox popup. 1065 */ 1066 static void CBRollUp( LPHEADCOMBO lphc, BOOL ok, BOOL bButton ) 1067 { 1068 HWND hWnd = lphc->self; 1069 1070 TRACE("[%p]: sel ok? [%i] dropped? [%i]\n", 1071 lphc->self, ok, (INT)(lphc->wState & CBF_DROPPED)); 1072 1073 CB_NOTIFY( lphc, (ok) ? CBN_SELENDOK : CBN_SELENDCANCEL ); 1074 1075 if( IsWindow( hWnd ) && CB_GETTYPE(lphc) != CBS_SIMPLE ) 1076 { 1077 1078 if( lphc->wState & CBF_DROPPED ) 1079 { 1080 RECT rect; 1081 1082 lphc->wState &= ~CBF_DROPPED; 1083 ShowWindow( lphc->hWndLBox, SW_HIDE ); 1084 1085 if(GetCapture() == lphc->hWndLBox) 1086 { 1087 ReleaseCapture(); 1088 } 1089 1090 if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) 1091 { 1092 rect = lphc->buttonRect; 1093 } 1094 else 1095 { 1096 if( bButton ) 1097 { 1098 UnionRect( &rect, 1099 &lphc->buttonRect, 1100 &lphc->textRect); 1101 } 1102 else 1103 rect = lphc->textRect; 1104 1105 bButton = TRUE; 1106 } 1107 1108 if( bButton && !(lphc->wState & CBF_NOREDRAW) ) 1109 RedrawWindow( hWnd, &rect, 0, RDW_INVALIDATE | 1110 RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN ); 1111 CB_NOTIFY( lphc, CBN_CLOSEUP ); 1112 } 1113 } 1114 } 1115 1116 /*********************************************************************** 1117 * COMBO_FlipListbox 1118 * 1119 * Used by the ComboLBox to show/hide itself in response to VK_F4, etc... 1120 */ 1121 BOOL COMBO_FlipListbox( LPHEADCOMBO lphc, BOOL ok, BOOL bRedrawButton ) 1122 { 1123 if( lphc->wState & CBF_DROPPED ) 1124 { 1125 CBRollUp( lphc, ok, bRedrawButton ); 1126 return FALSE; 1127 } 1128 1129 CBDropDown( lphc ); 1130 return TRUE; 1131 } 1132 1133 /*********************************************************************** 1134 * CBRepaintButton 1135 */ 1136 static void CBRepaintButton( LPHEADCOMBO lphc ) 1137 { 1138 InvalidateRect(lphc->self, &lphc->buttonRect, TRUE); 1139 UpdateWindow(lphc->self); 1140 } 1141 1142 /*********************************************************************** 1143 * COMBO_SetFocus 1144 */ 1145 static void COMBO_SetFocus( LPHEADCOMBO lphc ) 1146 { 1147 if( !(lphc->wState & CBF_FOCUSED) ) 1148 { 1149 if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST ) 1150 SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0); 1151 1152 /* This is wrong. Message sequences seem to indicate that this 1153 is set *after* the notify. */ 1154 /* lphc->wState |= CBF_FOCUSED; */ 1155 1156 if( !(lphc->wState & CBF_EDIT) ) 1157 InvalidateRect(lphc->self, &lphc->textRect, TRUE); 1158 1159 CB_NOTIFY( lphc, CBN_SETFOCUS ); 1160 lphc->wState |= CBF_FOCUSED; 1161 } 1162 } 1163 1164 /*********************************************************************** 1165 * COMBO_KillFocus 1166 */ 1167 static void COMBO_KillFocus( LPHEADCOMBO lphc ) 1168 { 1169 HWND hWnd = lphc->self; 1170 1171 if( lphc->wState & CBF_FOCUSED ) 1172 { 1173 CBRollUp( lphc, FALSE, TRUE ); 1174 if( IsWindow( hWnd ) ) 1175 { 1176 if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST ) 1177 SendMessageW(lphc->hWndLBox, LB_CARETOFF, 0, 0); 1178 1179 lphc->wState &= ~CBF_FOCUSED; 1180 1181 /* redraw text */ 1182 if( !(lphc->wState & CBF_EDIT) ) 1183 InvalidateRect(lphc->self, &lphc->textRect, TRUE); 1184 1185 CB_NOTIFY( lphc, CBN_KILLFOCUS ); 1186 } 1187 } 1188 } 1189 1190 /*********************************************************************** 1191 * COMBO_Command 1192 */ 1193 static LRESULT COMBO_Command( LPHEADCOMBO lphc, WPARAM wParam, HWND hWnd ) 1194 { 1195 if ( lphc->wState & CBF_EDIT && lphc->hWndEdit == hWnd ) 1196 { 1197 /* ">> 8" makes gcc generate jump-table instead of cmp ladder */ 1198 1199 switch( HIWORD(wParam) >> 8 ) 1200 { 1201 case (EN_SETFOCUS >> 8): 1202 1203 TRACE("[%p]: edit [%p] got focus\n", lphc->self, lphc->hWndEdit ); 1204 1205 COMBO_SetFocus( lphc ); 1206 break; 1207 1208 case (EN_KILLFOCUS >> 8): 1209 1210 TRACE("[%p]: edit [%p] lost focus\n", lphc->self, lphc->hWndEdit ); 1211 1212 /* NOTE: it seems that Windows' edit control sends an 1213 * undocumented message WM_USER + 0x1B instead of this 1214 * notification (only when it happens to be a part of 1215 * the combo). ?? - AK. 1216 */ 1217 1218 COMBO_KillFocus( lphc ); 1219 break; 1220 1221 1222 case (EN_CHANGE >> 8): 1223 /* 1224 * In some circumstances (when the selection of the combobox 1225 * is changed for example) we don't want the EN_CHANGE notification 1226 * to be forwarded to the parent of the combobox. This code 1227 * checks a flag that is set in these occasions and ignores the 1228 * notification. 1229 */ 1230 if (lphc->wState & CBF_NOLBSELECT) 1231 { 1232 lphc->wState &= ~CBF_NOLBSELECT; 1233 } 1234 else 1235 { 1236 CBUpdateLBox( lphc, lphc->wState & CBF_DROPPED ); 1237 } 1238 1239 if (!(lphc->wState & CBF_NOEDITNOTIFY)) 1240 CB_NOTIFY( lphc, CBN_EDITCHANGE ); 1241 break; 1242 1243 case (EN_UPDATE >> 8): 1244 if (!(lphc->wState & CBF_NOEDITNOTIFY)) 1245 CB_NOTIFY( lphc, CBN_EDITUPDATE ); 1246 break; 1247 1248 case (EN_ERRSPACE >> 8): 1249 CB_NOTIFY( lphc, CBN_ERRSPACE ); 1250 } 1251 } 1252 else if( lphc->hWndLBox == hWnd ) 1253 { 1254 switch( (short)HIWORD(wParam) ) 1255 { 1256 case LBN_ERRSPACE: 1257 CB_NOTIFY( lphc, CBN_ERRSPACE ); 1258 break; 1259 1260 case LBN_DBLCLK: 1261 CB_NOTIFY( lphc, CBN_DBLCLK ); 1262 break; 1263 1264 case LBN_SELCHANGE: 1265 case LBN_SELCANCEL: 1266 1267 TRACE("[%p]: lbox selection change [%x]\n", lphc->self, lphc->wState ); 1268 1269 /* do not roll up if selection is being tracked 1270 * by arrow keys in the dropdown listbox */ 1271 if (!(lphc->wState & CBF_NOROLLUP)) 1272 { 1273 CBRollUp( lphc, (HIWORD(wParam) == LBN_SELCHANGE), TRUE ); 1274 } 1275 else lphc->wState &= ~CBF_NOROLLUP; 1276 1277 CB_NOTIFY( lphc, CBN_SELCHANGE ); 1278 1279 if( HIWORD(wParam) == LBN_SELCHANGE) 1280 { 1281 if( lphc->wState & CBF_EDIT ) 1282 lphc->wState |= CBF_NOLBSELECT; 1283 CBPaintText( lphc, NULL ); 1284 } 1285 break; 1286 1287 case LBN_SETFOCUS: 1288 case LBN_KILLFOCUS: 1289 /* nothing to do here since ComboLBox always resets the focus to its 1290 * combo/edit counterpart */ 1291 break; 1292 } 1293 } 1294 return 0; 1295 } 1296 1297 /*********************************************************************** 1298 * COMBO_ItemOp 1299 * 1300 * Fixup an ownerdrawn item operation and pass it up to the combobox owner. 1301 */ 1302 static LRESULT COMBO_ItemOp( LPHEADCOMBO lphc, UINT msg, LPARAM lParam ) 1303 { 1304 HWND hWnd = lphc->self; 1305 UINT id = (UINT)GetWindowLongPtrW( hWnd, GWLP_ID ); 1306 1307 TRACE("[%p]: ownerdraw op %04x\n", lphc->self, msg ); 1308 1309 switch( msg ) 1310 { 1311 case WM_DELETEITEM: 1312 { 1313 DELETEITEMSTRUCT *lpIS = (DELETEITEMSTRUCT *)lParam; 1314 lpIS->CtlType = ODT_COMBOBOX; 1315 lpIS->CtlID = id; 1316 lpIS->hwndItem = hWnd; 1317 break; 1318 } 1319 case WM_DRAWITEM: 1320 { 1321 DRAWITEMSTRUCT *lpIS = (DRAWITEMSTRUCT *)lParam; 1322 lpIS->CtlType = ODT_COMBOBOX; 1323 lpIS->CtlID = id; 1324 lpIS->hwndItem = hWnd; 1325 break; 1326 } 1327 case WM_COMPAREITEM: 1328 { 1329 COMPAREITEMSTRUCT *lpIS = (COMPAREITEMSTRUCT *)lParam; 1330 lpIS->CtlType = ODT_COMBOBOX; 1331 lpIS->CtlID = id; 1332 lpIS->hwndItem = hWnd; 1333 break; 1334 } 1335 case WM_MEASUREITEM: 1336 { 1337 MEASUREITEMSTRUCT *lpIS = (MEASUREITEMSTRUCT *)lParam; 1338 lpIS->CtlType = ODT_COMBOBOX; 1339 lpIS->CtlID = id; 1340 break; 1341 } 1342 } 1343 return SendMessageW(lphc->owner, msg, id, lParam); 1344 } 1345 1346 1347 /*********************************************************************** 1348 * COMBO_GetTextW 1349 */ 1350 static LRESULT COMBO_GetText( HEADCOMBO *lphc, INT count, LPWSTR buf ) 1351 { 1352 INT length; 1353 1354 if( lphc->wState & CBF_EDIT ) 1355 return SendMessageW( lphc->hWndEdit, WM_GETTEXT, count, (LPARAM)buf ); 1356 1357 /* get it from the listbox */ 1358 1359 if (!count || !buf) return 0; 1360 if( lphc->hWndLBox ) 1361 { 1362 INT idx = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0); 1363 if (idx == LB_ERR) goto error; 1364 length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, idx, 0 ); 1365 if (length == LB_ERR) goto error; 1366 1367 /* 'length' is without the terminating character */ 1368 if (length >= count) 1369 { 1370 WCHAR *lpBuffer = heap_alloc((length + 1) * sizeof(WCHAR)); 1371 if (!lpBuffer) goto error; 1372 length = SendMessageW(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)lpBuffer); 1373 1374 /* truncate if buffer is too short */ 1375 if (length != LB_ERR) 1376 { 1377 lstrcpynW( buf, lpBuffer, count ); 1378 length = count; 1379 } 1380 heap_free( lpBuffer ); 1381 } 1382 else length = SendMessageW(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)buf); 1383 1384 if (length == LB_ERR) return 0; 1385 return length; 1386 } 1387 1388 error: /* error - truncate string, return zero */ 1389 buf[0] = 0; 1390 return 0; 1391 } 1392 1393 /*********************************************************************** 1394 * CBResetPos 1395 * 1396 * This function sets window positions according to the updated 1397 * component placement struct. 1398 */ 1399 static void CBResetPos( 1400 LPHEADCOMBO lphc, 1401 const RECT *rectEdit, 1402 const RECT *rectLB, 1403 BOOL bRedraw) 1404 { 1405 BOOL bDrop = (CB_GETTYPE(lphc) != CBS_SIMPLE); 1406 1407 /* NOTE: logs sometimes have WM_LBUTTONUP before a cascade of 1408 * sizing messages */ 1409 1410 if( lphc->wState & CBF_EDIT ) 1411 SetWindowPos( lphc->hWndEdit, 0, 1412 rectEdit->left, rectEdit->top, 1413 rectEdit->right - rectEdit->left, 1414 rectEdit->bottom - rectEdit->top, 1415 SWP_NOZORDER | SWP_NOACTIVATE | ((bDrop) ? SWP_NOREDRAW : 0) ); 1416 1417 SetWindowPos( lphc->hWndLBox, 0, 1418 rectLB->left, rectLB->top, 1419 rectLB->right - rectLB->left, 1420 rectLB->bottom - rectLB->top, 1421 SWP_NOACTIVATE | SWP_NOZORDER | ((bDrop) ? SWP_NOREDRAW : 0) ); 1422 1423 if( bDrop ) 1424 { 1425 if( lphc->wState & CBF_DROPPED ) 1426 { 1427 lphc->wState &= ~CBF_DROPPED; 1428 ShowWindow( lphc->hWndLBox, SW_HIDE ); 1429 } 1430 1431 if( bRedraw && !(lphc->wState & CBF_NOREDRAW) ) 1432 RedrawWindow( lphc->self, NULL, 0, 1433 RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW ); 1434 } 1435 } 1436 1437 1438 /*********************************************************************** 1439 * COMBO_Size 1440 */ 1441 static void COMBO_Size( LPHEADCOMBO lphc ) 1442 { 1443 /* 1444 * Those controls are always the same height. So we have to make sure 1445 * they are not resized to another value. 1446 */ 1447 if( CB_GETTYPE(lphc) != CBS_SIMPLE ) 1448 { 1449 int newComboHeight, curComboHeight, curComboWidth; 1450 RECT rc; 1451 1452 GetWindowRect(lphc->self, &rc); 1453 curComboHeight = rc.bottom - rc.top; 1454 curComboWidth = rc.right - rc.left; 1455 newComboHeight = CBGetTextAreaHeight(lphc->self, lphc) + 2*COMBO_YBORDERSIZE(); 1456 1457 /* 1458 * Resizing a combobox has another side effect, it resizes the dropped 1459 * rectangle as well. However, it does it only if the new height for the 1460 * combobox is more than the height it should have. In other words, 1461 * if the application resizing the combobox only had the intention to resize 1462 * the actual control, for example, to do the layout of a dialog that is 1463 * resized, the height of the dropdown is not changed. 1464 */ 1465 if( curComboHeight > newComboHeight ) 1466 { 1467 TRACE("oldComboHeight=%d, newComboHeight=%d, oldDropBottom=%d, oldDropTop=%d\n", 1468 curComboHeight, newComboHeight, lphc->droppedRect.bottom, 1469 lphc->droppedRect.top); 1470 lphc->droppedRect.bottom = lphc->droppedRect.top + curComboHeight - newComboHeight; 1471 } 1472 /* 1473 * Restore original height 1474 */ 1475 if( curComboHeight != newComboHeight ) 1476 SetWindowPos(lphc->self, 0, 0, 0, curComboWidth, newComboHeight, 1477 SWP_NOZORDER|SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOREDRAW); 1478 } 1479 1480 CBCalcPlacement(lphc->self, 1481 lphc, 1482 &lphc->textRect, 1483 &lphc->buttonRect, 1484 &lphc->droppedRect); 1485 1486 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE ); 1487 } 1488 1489 1490 /*********************************************************************** 1491 * COMBO_Font 1492 */ 1493 static void COMBO_Font( LPHEADCOMBO lphc, HFONT hFont, BOOL bRedraw ) 1494 { 1495 /* 1496 * Set the font 1497 */ 1498 lphc->hFont = hFont; 1499 1500 /* 1501 * Propagate to owned windows. 1502 */ 1503 if( lphc->wState & CBF_EDIT ) 1504 SendMessageW(lphc->hWndEdit, WM_SETFONT, (WPARAM)hFont, bRedraw); 1505 SendMessageW(lphc->hWndLBox, WM_SETFONT, (WPARAM)hFont, bRedraw); 1506 1507 /* 1508 * Redo the layout of the control. 1509 */ 1510 if ( CB_GETTYPE(lphc) == CBS_SIMPLE) 1511 { 1512 CBCalcPlacement(lphc->self, 1513 lphc, 1514 &lphc->textRect, 1515 &lphc->buttonRect, 1516 &lphc->droppedRect); 1517 1518 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE ); 1519 } 1520 else 1521 { 1522 CBForceDummyResize(lphc); 1523 } 1524 } 1525 1526 1527 /*********************************************************************** 1528 * COMBO_SetItemHeight 1529 */ 1530 static LRESULT COMBO_SetItemHeight( LPHEADCOMBO lphc, INT index, INT height ) 1531 { 1532 LRESULT lRet = CB_ERR; 1533 1534 if( index == -1 ) /* set text field height */ 1535 { 1536 if( height < 32768 ) 1537 { 1538 lphc->editHeight = height + 2; /* Is the 2 for 2*EDIT_CONTROL_PADDING? */ 1539 1540 /* 1541 * Redo the layout of the control. 1542 */ 1543 if ( CB_GETTYPE(lphc) == CBS_SIMPLE) 1544 { 1545 CBCalcPlacement(lphc->self, 1546 lphc, 1547 &lphc->textRect, 1548 &lphc->buttonRect, 1549 &lphc->droppedRect); 1550 1551 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE ); 1552 } 1553 else 1554 { 1555 CBForceDummyResize(lphc); 1556 } 1557 1558 lRet = height; 1559 } 1560 } 1561 else if ( CB_OWNERDRAWN(lphc) ) /* set listbox item height */ 1562 lRet = SendMessageW(lphc->hWndLBox, LB_SETITEMHEIGHT, index, height); 1563 return lRet; 1564 } 1565 1566 /*********************************************************************** 1567 * COMBO_SelectString 1568 */ 1569 static LRESULT COMBO_SelectString( LPHEADCOMBO lphc, INT start, LPARAM pText) 1570 { 1571 INT index = SendMessageW(lphc->hWndLBox, LB_SELECTSTRING, start, pText); 1572 if( index >= 0 ) 1573 { 1574 if( lphc->wState & CBF_EDIT ) 1575 CBUpdateEdit( lphc, index ); 1576 else 1577 { 1578 InvalidateRect(lphc->self, &lphc->textRect, TRUE); 1579 } 1580 } 1581 return (LRESULT)index; 1582 } 1583 1584 /*********************************************************************** 1585 * COMBO_LButtonDown 1586 */ 1587 static void COMBO_LButtonDown( LPHEADCOMBO lphc, LPARAM lParam ) 1588 { 1589 POINT pt; 1590 BOOL bButton; 1591 HWND hWnd = lphc->self; 1592 1593 pt.x = (short)LOWORD(lParam); 1594 pt.y = (short)HIWORD(lParam); 1595 bButton = PtInRect(&lphc->buttonRect, pt); 1596 1597 if( (CB_GETTYPE(lphc) == CBS_DROPDOWNLIST) || 1598 (bButton && (CB_GETTYPE(lphc) == CBS_DROPDOWN)) ) 1599 { 1600 lphc->wState |= CBF_BUTTONDOWN; 1601 if( lphc->wState & CBF_DROPPED ) 1602 { 1603 /* got a click to cancel selection */ 1604 1605 lphc->wState &= ~CBF_BUTTONDOWN; 1606 CBRollUp( lphc, TRUE, FALSE ); 1607 if( !IsWindow( hWnd ) ) return; 1608 1609 if( lphc->wState & CBF_CAPTURE ) 1610 { 1611 lphc->wState &= ~CBF_CAPTURE; 1612 ReleaseCapture(); 1613 } 1614 } 1615 else 1616 { 1617 /* drop down the listbox and start tracking */ 1618 1619 lphc->wState |= CBF_CAPTURE; 1620 SetCapture( hWnd ); 1621 CBDropDown( lphc ); 1622 } 1623 if( bButton ) CBRepaintButton( lphc ); 1624 } 1625 } 1626 1627 /*********************************************************************** 1628 * COMBO_LButtonUp 1629 * 1630 * Release capture and stop tracking if needed. 1631 */ 1632 static void COMBO_LButtonUp( LPHEADCOMBO lphc ) 1633 { 1634 if( lphc->wState & CBF_CAPTURE ) 1635 { 1636 lphc->wState &= ~CBF_CAPTURE; 1637 if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) 1638 { 1639 INT index = CBUpdateLBox( lphc, TRUE ); 1640 /* Update edit only if item is in the list */ 1641 if(index >= 0) 1642 { 1643 lphc->wState |= CBF_NOLBSELECT; 1644 CBUpdateEdit( lphc, index ); 1645 lphc->wState &= ~CBF_NOLBSELECT; 1646 } 1647 } 1648 ReleaseCapture(); 1649 SetCapture(lphc->hWndLBox); 1650 } 1651 1652 if( lphc->wState & CBF_BUTTONDOWN ) 1653 { 1654 lphc->wState &= ~CBF_BUTTONDOWN; 1655 CBRepaintButton( lphc ); 1656 } 1657 } 1658 1659 /*********************************************************************** 1660 * COMBO_MouseMove 1661 * 1662 * Two things to do - track combo button and release capture when 1663 * pointer goes into the listbox. 1664 */ 1665 static void COMBO_MouseMove( LPHEADCOMBO lphc, WPARAM wParam, LPARAM lParam ) 1666 { 1667 POINT pt; 1668 RECT lbRect; 1669 1670 pt.x = (short)LOWORD(lParam); 1671 pt.y = (short)HIWORD(lParam); 1672 1673 if( lphc->wState & CBF_BUTTONDOWN ) 1674 { 1675 BOOL bButton; 1676 1677 bButton = PtInRect(&lphc->buttonRect, pt); 1678 1679 if( !bButton ) 1680 { 1681 lphc->wState &= ~CBF_BUTTONDOWN; 1682 CBRepaintButton( lphc ); 1683 } 1684 } 1685 1686 GetClientRect( lphc->hWndLBox, &lbRect ); 1687 MapWindowPoints( lphc->self, lphc->hWndLBox, &pt, 1 ); 1688 if( PtInRect(&lbRect, pt) ) 1689 { 1690 lphc->wState &= ~CBF_CAPTURE; 1691 ReleaseCapture(); 1692 if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) CBUpdateLBox( lphc, TRUE ); 1693 1694 /* hand over pointer tracking */ 1695 SendMessageW(lphc->hWndLBox, WM_LBUTTONDOWN, wParam, lParam); 1696 } 1697 } 1698 1699 static LRESULT COMBO_GetComboBoxInfo(const HEADCOMBO *lphc, COMBOBOXINFO *pcbi) 1700 { 1701 if (!pcbi || (pcbi->cbSize < sizeof(COMBOBOXINFO))) 1702 return FALSE; 1703 1704 pcbi->rcItem = lphc->textRect; 1705 pcbi->rcButton = lphc->buttonRect; 1706 pcbi->stateButton = 0; 1707 if (lphc->wState & CBF_BUTTONDOWN) 1708 pcbi->stateButton |= STATE_SYSTEM_PRESSED; 1709 if (IsRectEmpty(&lphc->buttonRect)) 1710 pcbi->stateButton |= STATE_SYSTEM_INVISIBLE; 1711 pcbi->hwndCombo = lphc->self; 1712 pcbi->hwndItem = lphc->hWndEdit; 1713 pcbi->hwndList = lphc->hWndLBox; 1714 return TRUE; 1715 } 1716 1717 static LRESULT CALLBACK COMBO_WindowProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) 1718 { 1719 HEADCOMBO *lphc = (HEADCOMBO *)GetWindowLongPtrW( hwnd, 0 ); 1720 HTHEME theme; 1721 1722 TRACE("[%p]: msg %#x wp %08lx lp %08lx\n", hwnd, message, wParam, lParam ); 1723 1724 if (!IsWindow(hwnd)) return 0; 1725 1726 if (lphc || message == WM_NCCREATE) 1727 switch(message) 1728 { 1729 case WM_NCCREATE: 1730 { 1731 LONG style = ((CREATESTRUCTW *)lParam)->style; 1732 return COMBO_NCCreate(hwnd, style); 1733 } 1734 1735 case WM_NCDESTROY: 1736 COMBO_NCDestroy(lphc); 1737 break;/* -> DefWindowProc */ 1738 1739 case WM_CREATE: 1740 { 1741 HWND hwndParent; 1742 LONG style; 1743 1744 hwndParent = ((CREATESTRUCTW *)lParam)->hwndParent; 1745 style = ((CREATESTRUCTW *)lParam)->style; 1746 return COMBO_Create(hwnd, lphc, hwndParent, style); 1747 } 1748 1749 case WM_DESTROY: 1750 theme = GetWindowTheme( hwnd ); 1751 CloseThemeData( theme ); 1752 break; 1753 1754 case WM_THEMECHANGED: 1755 theme = GetWindowTheme( hwnd ); 1756 CloseThemeData( theme ); 1757 OpenThemeData( hwnd, WC_COMBOBOXW ); 1758 break; 1759 1760 case WM_PRINTCLIENT: 1761 case WM_PAINT: 1762 { 1763 LRESULT ret = 0; 1764 PAINTSTRUCT ps; 1765 HDC hdc; 1766 1767 hdc = wParam ? (HDC)wParam : BeginPaint(hwnd, &ps); 1768 1769 if (hdc && !(lphc->wState & CBF_NOREDRAW)) 1770 { 1771 HTHEME theme = GetWindowTheme(hwnd); 1772 1773 if (theme) 1774 ret = COMBO_ThemedPaint(theme, lphc, hdc); 1775 else 1776 ret = COMBO_Paint(lphc, hdc); 1777 } 1778 1779 if (!wParam) 1780 EndPaint(hwnd, &ps); 1781 1782 return ret; 1783 } 1784 case WM_ERASEBKGND: 1785 /* do all painting in WM_PAINT like Windows does */ 1786 return 1; 1787 1788 case WM_GETDLGCODE: 1789 { 1790 LRESULT result = DLGC_WANTARROWS | DLGC_WANTCHARS; 1791 if (lParam && (((LPMSG)lParam)->message == WM_KEYDOWN)) 1792 { 1793 int vk = (int)((LPMSG)lParam)->wParam; 1794 1795 if ((vk == VK_RETURN || vk == VK_ESCAPE) && (lphc->wState & CBF_DROPPED)) 1796 result |= DLGC_WANTMESSAGE; 1797 } 1798 return result; 1799 } 1800 1801 case WM_SIZE: 1802 if (lphc->hWndLBox && !(lphc->wState & CBF_NORESIZE)) 1803 COMBO_Size( lphc ); 1804 return TRUE; 1805 1806 case WM_SETFONT: 1807 COMBO_Font( lphc, (HFONT)wParam, (BOOL)lParam ); 1808 return TRUE; 1809 1810 case WM_GETFONT: 1811 return (LRESULT)lphc->hFont; 1812 1813 case WM_SETFOCUS: 1814 if (lphc->wState & CBF_EDIT) 1815 { 1816 SetFocus( lphc->hWndEdit ); 1817 /* The first time focus is received, select all the text */ 1818 if (!(lphc->wState & CBF_BEENFOCUSED)) 1819 { 1820 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, -1); 1821 lphc->wState |= CBF_BEENFOCUSED; 1822 } 1823 } 1824 else 1825 COMBO_SetFocus( lphc ); 1826 return TRUE; 1827 1828 case WM_KILLFOCUS: 1829 { 1830 HWND hwndFocus = (HWND)wParam; 1831 if (!hwndFocus || (hwndFocus != lphc->hWndEdit && hwndFocus != lphc->hWndLBox)) 1832 COMBO_KillFocus( lphc ); 1833 return TRUE; 1834 } 1835 1836 case WM_COMMAND: 1837 return COMBO_Command( lphc, wParam, (HWND)lParam ); 1838 1839 case WM_GETTEXT: 1840 return COMBO_GetText( lphc, wParam, (LPWSTR)lParam ); 1841 1842 case WM_SETTEXT: 1843 case WM_GETTEXTLENGTH: 1844 case WM_CLEAR: 1845 if ((message == WM_GETTEXTLENGTH) && !ISWIN31 && !(lphc->wState & CBF_EDIT)) 1846 { 1847 int j = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0); 1848 if (j == -1) return 0; 1849 return SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, j, 0); 1850 } 1851 else if ( lphc->wState & CBF_EDIT ) 1852 { 1853 LRESULT ret; 1854 lphc->wState |= CBF_NOEDITNOTIFY; 1855 ret = SendMessageW(lphc->hWndEdit, message, wParam, lParam); 1856 lphc->wState &= ~CBF_NOEDITNOTIFY; 1857 return ret; 1858 } 1859 else 1860 return CB_ERR; 1861 1862 case WM_CUT: 1863 case WM_PASTE: 1864 case WM_COPY: 1865 if (lphc->wState & CBF_EDIT) 1866 return SendMessageW(lphc->hWndEdit, message, wParam, lParam); 1867 else return CB_ERR; 1868 1869 case WM_DRAWITEM: 1870 case WM_DELETEITEM: 1871 case WM_COMPAREITEM: 1872 case WM_MEASUREITEM: 1873 return COMBO_ItemOp(lphc, message, lParam); 1874 1875 case WM_ENABLE: 1876 if (lphc->wState & CBF_EDIT) 1877 EnableWindow( lphc->hWndEdit, (BOOL)wParam ); 1878 EnableWindow( lphc->hWndLBox, (BOOL)wParam ); 1879 1880 /* Force the control to repaint when the enabled state changes. */ 1881 InvalidateRect(lphc->self, NULL, TRUE); 1882 return TRUE; 1883 1884 case WM_SETREDRAW: 1885 if (wParam) 1886 lphc->wState &= ~CBF_NOREDRAW; 1887 else 1888 lphc->wState |= CBF_NOREDRAW; 1889 1890 if ( lphc->wState & CBF_EDIT ) 1891 SendMessageW(lphc->hWndEdit, message, wParam, lParam); 1892 SendMessageW(lphc->hWndLBox, message, wParam, lParam); 1893 return 0; 1894 1895 case WM_SYSKEYDOWN: 1896 if ( KEYDATA_ALT & HIWORD(lParam) ) 1897 if( wParam == VK_UP || wParam == VK_DOWN ) 1898 COMBO_FlipListbox( lphc, FALSE, FALSE ); 1899 return 0; 1900 1901 case WM_KEYDOWN: 1902 if ((wParam == VK_RETURN || wParam == VK_ESCAPE) && 1903 (lphc->wState & CBF_DROPPED)) 1904 { 1905 CBRollUp( lphc, wParam == VK_RETURN, FALSE ); 1906 return TRUE; 1907 } 1908 else if ((wParam == VK_F4) && !(lphc->wState & CBF_EUI)) 1909 { 1910 COMBO_FlipListbox( lphc, FALSE, FALSE ); 1911 return TRUE; 1912 } 1913 /* fall through */ 1914 case WM_CHAR: 1915 case WM_IME_CHAR: 1916 { 1917 HWND hwndTarget; 1918 1919 if ( lphc->wState & CBF_EDIT ) 1920 hwndTarget = lphc->hWndEdit; 1921 else 1922 hwndTarget = lphc->hWndLBox; 1923 1924 return SendMessageW(hwndTarget, message, wParam, lParam); 1925 } 1926 1927 case WM_LBUTTONDOWN: 1928 if ( !(lphc->wState & CBF_FOCUSED) ) SetFocus( lphc->self ); 1929 if ( lphc->wState & CBF_FOCUSED ) COMBO_LButtonDown( lphc, lParam ); 1930 return TRUE; 1931 1932 case WM_LBUTTONUP: 1933 COMBO_LButtonUp( lphc ); 1934 return TRUE; 1935 1936 case WM_MOUSEMOVE: 1937 if (!IsRectEmpty(&lphc->buttonRect)) 1938 { 1939 POINT pt; 1940 1941 pt.x = (short)LOWORD(lParam); 1942 pt.y = (short)HIWORD(lParam); 1943 1944 if (PtInRect(&lphc->buttonRect, pt)) 1945 { 1946 if (!(lphc->wState & CBF_HOT)) 1947 { 1948 lphc->wState |= CBF_HOT; 1949 RedrawWindow(hwnd, &lphc->buttonRect, 0, RDW_INVALIDATE | RDW_UPDATENOW); 1950 } 1951 } 1952 else if (lphc->wState & CBF_HOT) 1953 { 1954 lphc->wState &= ~CBF_HOT; 1955 RedrawWindow(hwnd, &lphc->buttonRect, 0, RDW_INVALIDATE | RDW_UPDATENOW); 1956 } 1957 } 1958 1959 if ( lphc->wState & CBF_CAPTURE ) 1960 COMBO_MouseMove( lphc, wParam, lParam ); 1961 return TRUE; 1962 1963 case WM_MOUSEWHEEL: 1964 if (wParam & (MK_SHIFT | MK_CONTROL)) 1965 return DefWindowProcW(hwnd, message, wParam, lParam); 1966 1967 if (GET_WHEEL_DELTA_WPARAM(wParam) > 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_UP, 0); 1968 if (GET_WHEEL_DELTA_WPARAM(wParam) < 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_DOWN, 0); 1969 return TRUE; 1970 1971 /* Combo messages */ 1972 case CB_ADDSTRING: 1973 if (lphc->dwStyle & CBS_LOWERCASE) 1974 CharLowerW((LPWSTR)lParam); 1975 else if (lphc->dwStyle & CBS_UPPERCASE) 1976 CharUpperW((LPWSTR)lParam); 1977 return SendMessageW(lphc->hWndLBox, LB_ADDSTRING, 0, lParam); 1978 1979 case CB_INSERTSTRING: 1980 if (lphc->dwStyle & CBS_LOWERCASE) 1981 CharLowerW((LPWSTR)lParam); 1982 else if (lphc->dwStyle & CBS_UPPERCASE) 1983 CharUpperW((LPWSTR)lParam); 1984 return SendMessageW(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam); 1985 1986 case CB_DELETESTRING: 1987 return SendMessageW(lphc->hWndLBox, LB_DELETESTRING, wParam, 0); 1988 1989 case CB_SELECTSTRING: 1990 return COMBO_SelectString(lphc, (INT)wParam, lParam); 1991 1992 case CB_FINDSTRING: 1993 return SendMessageW(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam); 1994 1995 case CB_FINDSTRINGEXACT: 1996 return SendMessageW(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam); 1997 1998 case CB_SETITEMHEIGHT: 1999 return COMBO_SetItemHeight( lphc, (INT)wParam, (INT)lParam); 2000 2001 case CB_GETITEMHEIGHT: 2002 if ((INT)wParam >= 0) /* listbox item */ 2003 return SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, wParam, 0); 2004 return CBGetTextAreaHeight(hwnd, lphc); 2005 2006 case CB_RESETCONTENT: 2007 SendMessageW(lphc->hWndLBox, LB_RESETCONTENT, 0, 0); 2008 2009 if ((lphc->wState & CBF_EDIT) && CB_HASSTRINGS(lphc)) 2010 { 2011 static const WCHAR empty_stringW[] = { 0 }; 2012 SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, (LPARAM)empty_stringW); 2013 } 2014 else 2015 InvalidateRect(lphc->self, NULL, TRUE); 2016 return TRUE; 2017 2018 case CB_INITSTORAGE: 2019 return SendMessageW(lphc->hWndLBox, LB_INITSTORAGE, wParam, lParam); 2020 2021 case CB_GETHORIZONTALEXTENT: 2022 return SendMessageW(lphc->hWndLBox, LB_GETHORIZONTALEXTENT, 0, 0); 2023 2024 case CB_SETHORIZONTALEXTENT: 2025 return SendMessageW(lphc->hWndLBox, LB_SETHORIZONTALEXTENT, wParam, 0); 2026 2027 case CB_GETTOPINDEX: 2028 return SendMessageW(lphc->hWndLBox, LB_GETTOPINDEX, 0, 0); 2029 2030 case CB_GETLOCALE: 2031 return SendMessageW(lphc->hWndLBox, LB_GETLOCALE, 0, 0); 2032 2033 case CB_SETLOCALE: 2034 return SendMessageW(lphc->hWndLBox, LB_SETLOCALE, wParam, 0); 2035 2036 case CB_SETDROPPEDWIDTH: 2037 if ((CB_GETTYPE(lphc) == CBS_SIMPLE) || (INT)wParam >= 32768) 2038 return CB_ERR; 2039 2040 /* new value must be higher than combobox width */ 2041 if ((INT)wParam >= lphc->droppedRect.right - lphc->droppedRect.left) 2042 lphc->droppedWidth = wParam; 2043 else if (wParam) 2044 lphc->droppedWidth = 0; 2045 2046 /* recalculate the combobox area */ 2047 CBCalcPlacement(hwnd, lphc, &lphc->textRect, &lphc->buttonRect, &lphc->droppedRect ); 2048 2049 /* fall through */ 2050 case CB_GETDROPPEDWIDTH: 2051 if (lphc->droppedWidth) 2052 return lphc->droppedWidth; 2053 return lphc->droppedRect.right - lphc->droppedRect.left; 2054 2055 case CB_GETDROPPEDCONTROLRECT: 2056 if (lParam) 2057 CBGetDroppedControlRect(lphc, (LPRECT)lParam ); 2058 return CB_OKAY; 2059 2060 case CB_GETDROPPEDSTATE: 2061 return (lphc->wState & CBF_DROPPED) != 0; 2062 2063 case CB_DIR: 2064 return SendMessageW(lphc->hWndLBox, LB_DIR, wParam, lParam); 2065 2066 case CB_SHOWDROPDOWN: 2067 if (CB_GETTYPE(lphc) != CBS_SIMPLE) 2068 { 2069 if (wParam) 2070 { 2071 if (!(lphc->wState & CBF_DROPPED)) 2072 CBDropDown( lphc ); 2073 } 2074 else if (lphc->wState & CBF_DROPPED) 2075 CBRollUp( lphc, FALSE, TRUE ); 2076 } 2077 return TRUE; 2078 2079 case CB_GETCOUNT: 2080 return SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0); 2081 2082 case CB_GETCURSEL: 2083 return SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0); 2084 2085 case CB_SETCURSEL: 2086 lParam = SendMessageW(lphc->hWndLBox, LB_SETCURSEL, wParam, 0); 2087 if (lParam >= 0) 2088 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, wParam, 0); 2089 2090 /* no LBN_SELCHANGE in this case, update manually */ 2091 CBPaintText(lphc, NULL); 2092 lphc->wState &= ~CBF_SELCHANGE; 2093 return lParam; 2094 2095 case CB_GETLBTEXT: 2096 return SendMessageW(lphc->hWndLBox, LB_GETTEXT, wParam, lParam); 2097 2098 case CB_GETLBTEXTLEN: 2099 return SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0); 2100 2101 case CB_GETITEMDATA: 2102 return SendMessageW(lphc->hWndLBox, LB_GETITEMDATA, wParam, 0); 2103 2104 case CB_SETITEMDATA: 2105 return SendMessageW(lphc->hWndLBox, LB_SETITEMDATA, wParam, lParam); 2106 2107 case CB_GETEDITSEL: 2108 /* Edit checks passed parameters itself */ 2109 if (lphc->wState & CBF_EDIT) 2110 return SendMessageW(lphc->hWndEdit, EM_GETSEL, wParam, lParam); 2111 return CB_ERR; 2112 2113 case CB_SETEDITSEL: 2114 if (lphc->wState & CBF_EDIT) 2115 return SendMessageW(lphc->hWndEdit, EM_SETSEL, (INT)(SHORT)LOWORD(lParam), (INT)(SHORT)HIWORD(lParam) ); 2116 return CB_ERR; 2117 2118 case CB_SETEXTENDEDUI: 2119 if (CB_GETTYPE(lphc) == CBS_SIMPLE ) 2120 return CB_ERR; 2121 if (wParam) 2122 lphc->wState |= CBF_EUI; 2123 else 2124 lphc->wState &= ~CBF_EUI; 2125 return CB_OKAY; 2126 2127 case CB_GETEXTENDEDUI: 2128 return (lphc->wState & CBF_EUI) != 0; 2129 2130 case CB_GETCOMBOBOXINFO: 2131 return COMBO_GetComboBoxInfo(lphc, (COMBOBOXINFO *)lParam); 2132 2133 case CB_LIMITTEXT: 2134 if (lphc->wState & CBF_EDIT) 2135 return SendMessageW(lphc->hWndEdit, EM_LIMITTEXT, wParam, lParam); 2136 return TRUE; 2137 2138 default: 2139 if (message >= WM_USER) 2140 WARN("unknown msg WM_USER+%04x wp=%04lx lp=%08lx\n", message - WM_USER, wParam, lParam ); 2141 break; 2142 } 2143 2144 return DefWindowProcW(hwnd, message, wParam, lParam); 2145 } 2146 2147 void COMBO_Register(void) 2148 { 2149 WNDCLASSW wndClass; 2150 2151 memset(&wndClass, 0, sizeof(wndClass)); 2152 wndClass.style = CS_PARENTDC | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS; 2153 wndClass.lpfnWndProc = COMBO_WindowProc; 2154 wndClass.cbClsExtra = 0; 2155 wndClass.cbWndExtra = sizeof(HEADCOMBO *); 2156 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW); 2157 wndClass.hbrBackground = NULL; 2158 wndClass.lpszClassName = WC_COMBOBOXW; 2159 RegisterClassW(&wndClass); 2160 } 2161 2162 #ifdef __REACTOS__ 2163 void COMBO_Unregister(void) 2164 { 2165 UnregisterClassW(WC_COMBOBOXW, NULL); 2166 } 2167 #endif 2168