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