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