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 | RDW_ERASE | RDW_UPDATENOW ); 1050 1051 EnableWindow( lphc->hWndLBox, TRUE ); 1052 if (GetCapture() != lphc->self) 1053 SetCapture(lphc->hWndLBox); 1054 } 1055 1056 /*********************************************************************** 1057 * CBRollUp 1058 * 1059 * Hide listbox popup. 1060 */ 1061 static void CBRollUp( LPHEADCOMBO lphc, BOOL ok, BOOL bButton ) 1062 { 1063 HWND hWnd = lphc->self; 1064 1065 TRACE("[%p]: sel ok? [%i] dropped? [%i]\n", 1066 lphc->self, ok, (INT)(lphc->wState & CBF_DROPPED)); 1067 1068 CB_NOTIFY( lphc, (ok) ? CBN_SELENDOK : CBN_SELENDCANCEL ); 1069 1070 if( IsWindow( hWnd ) && CB_GETTYPE(lphc) != CBS_SIMPLE ) 1071 { 1072 1073 if( lphc->wState & CBF_DROPPED ) 1074 { 1075 RECT rect; 1076 1077 lphc->wState &= ~CBF_DROPPED; 1078 ShowWindow( lphc->hWndLBox, SW_HIDE ); 1079 1080 if(GetCapture() == lphc->hWndLBox) 1081 { 1082 ReleaseCapture(); 1083 } 1084 1085 if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) 1086 { 1087 rect = lphc->buttonRect; 1088 } 1089 else 1090 { 1091 if( bButton ) 1092 { 1093 UnionRect( &rect, 1094 &lphc->buttonRect, 1095 &lphc->textRect); 1096 } 1097 else 1098 rect = lphc->textRect; 1099 1100 bButton = TRUE; 1101 } 1102 1103 if( bButton && !(lphc->wState & CBF_NOREDRAW) ) 1104 RedrawWindow( hWnd, &rect, 0, RDW_INVALIDATE | 1105 RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN ); 1106 CB_NOTIFY( lphc, CBN_CLOSEUP ); 1107 } 1108 } 1109 } 1110 1111 /*********************************************************************** 1112 * COMBO_FlipListbox 1113 * 1114 * Used by the ComboLBox to show/hide itself in response to VK_F4, etc... 1115 */ 1116 BOOL COMBO_FlipListbox( LPHEADCOMBO lphc, BOOL ok, BOOL bRedrawButton ) 1117 { 1118 if( lphc->wState & CBF_DROPPED ) 1119 { 1120 CBRollUp( lphc, ok, bRedrawButton ); 1121 return FALSE; 1122 } 1123 1124 CBDropDown( lphc ); 1125 return TRUE; 1126 } 1127 1128 /*********************************************************************** 1129 * CBRepaintButton 1130 */ 1131 static void CBRepaintButton( LPHEADCOMBO lphc ) 1132 { 1133 InvalidateRect(lphc->self, &lphc->buttonRect, TRUE); 1134 UpdateWindow(lphc->self); 1135 } 1136 1137 /*********************************************************************** 1138 * COMBO_SetFocus 1139 */ 1140 static void COMBO_SetFocus( LPHEADCOMBO lphc ) 1141 { 1142 if( !(lphc->wState & CBF_FOCUSED) ) 1143 { 1144 if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST ) 1145 SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0); 1146 1147 /* This is wrong. Message sequences seem to indicate that this 1148 is set *after* the notify. */ 1149 /* lphc->wState |= CBF_FOCUSED; */ 1150 1151 if( !(lphc->wState & CBF_EDIT) ) 1152 InvalidateRect(lphc->self, &lphc->textRect, TRUE); 1153 1154 CB_NOTIFY( lphc, CBN_SETFOCUS ); 1155 lphc->wState |= CBF_FOCUSED; 1156 } 1157 } 1158 1159 /*********************************************************************** 1160 * COMBO_KillFocus 1161 */ 1162 static void COMBO_KillFocus( LPHEADCOMBO lphc ) 1163 { 1164 HWND hWnd = lphc->self; 1165 1166 if( lphc->wState & CBF_FOCUSED ) 1167 { 1168 CBRollUp( lphc, FALSE, TRUE ); 1169 if( IsWindow( hWnd ) ) 1170 { 1171 if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST ) 1172 SendMessageW(lphc->hWndLBox, LB_CARETOFF, 0, 0); 1173 1174 lphc->wState &= ~CBF_FOCUSED; 1175 1176 /* redraw text */ 1177 if( !(lphc->wState & CBF_EDIT) ) 1178 InvalidateRect(lphc->self, &lphc->textRect, TRUE); 1179 1180 CB_NOTIFY( lphc, CBN_KILLFOCUS ); 1181 } 1182 } 1183 } 1184 1185 /*********************************************************************** 1186 * COMBO_Command 1187 */ 1188 static LRESULT COMBO_Command( LPHEADCOMBO lphc, WPARAM wParam, HWND hWnd ) 1189 { 1190 if ( lphc->wState & CBF_EDIT && lphc->hWndEdit == hWnd ) 1191 { 1192 /* ">> 8" makes gcc generate jump-table instead of cmp ladder */ 1193 1194 switch( HIWORD(wParam) >> 8 ) 1195 { 1196 case (EN_SETFOCUS >> 8): 1197 1198 TRACE("[%p]: edit [%p] got focus\n", lphc->self, lphc->hWndEdit ); 1199 1200 COMBO_SetFocus( lphc ); 1201 break; 1202 1203 case (EN_KILLFOCUS >> 8): 1204 1205 TRACE("[%p]: edit [%p] lost focus\n", lphc->self, lphc->hWndEdit ); 1206 1207 /* NOTE: it seems that Windows' edit control sends an 1208 * undocumented message WM_USER + 0x1B instead of this 1209 * notification (only when it happens to be a part of 1210 * the combo). ?? - AK. 1211 */ 1212 1213 COMBO_KillFocus( lphc ); 1214 break; 1215 1216 1217 case (EN_CHANGE >> 8): 1218 /* 1219 * In some circumstances (when the selection of the combobox 1220 * is changed for example) we don't want the EN_CHANGE notification 1221 * to be forwarded to the parent of the combobox. This code 1222 * checks a flag that is set in these occasions and ignores the 1223 * notification. 1224 */ 1225 if (lphc->wState & CBF_NOLBSELECT) 1226 { 1227 lphc->wState &= ~CBF_NOLBSELECT; 1228 } 1229 else 1230 { 1231 CBUpdateLBox( lphc, lphc->wState & CBF_DROPPED ); 1232 } 1233 1234 if (!(lphc->wState & CBF_NOEDITNOTIFY)) 1235 CB_NOTIFY( lphc, CBN_EDITCHANGE ); 1236 break; 1237 1238 case (EN_UPDATE >> 8): 1239 if (!(lphc->wState & CBF_NOEDITNOTIFY)) 1240 CB_NOTIFY( lphc, CBN_EDITUPDATE ); 1241 break; 1242 1243 case (EN_ERRSPACE >> 8): 1244 CB_NOTIFY( lphc, CBN_ERRSPACE ); 1245 } 1246 } 1247 else if( lphc->hWndLBox == hWnd ) 1248 { 1249 switch( (short)HIWORD(wParam) ) 1250 { 1251 case LBN_ERRSPACE: 1252 CB_NOTIFY( lphc, CBN_ERRSPACE ); 1253 break; 1254 1255 case LBN_DBLCLK: 1256 CB_NOTIFY( lphc, CBN_DBLCLK ); 1257 break; 1258 1259 case LBN_SELCHANGE: 1260 case LBN_SELCANCEL: 1261 1262 TRACE("[%p]: lbox selection change [%x]\n", lphc->self, lphc->wState ); 1263 1264 /* do not roll up if selection is being tracked 1265 * by arrow keys in the dropdown listbox */ 1266 if (!(lphc->wState & CBF_NOROLLUP)) 1267 { 1268 CBRollUp( lphc, (HIWORD(wParam) == LBN_SELCHANGE), TRUE ); 1269 } 1270 else lphc->wState &= ~CBF_NOROLLUP; 1271 1272 CB_NOTIFY( lphc, CBN_SELCHANGE ); 1273 1274 if( HIWORD(wParam) == LBN_SELCHANGE) 1275 { 1276 if( lphc->wState & CBF_EDIT ) 1277 lphc->wState |= CBF_NOLBSELECT; 1278 CBPaintText( lphc, NULL ); 1279 } 1280 break; 1281 1282 case LBN_SETFOCUS: 1283 case LBN_KILLFOCUS: 1284 /* nothing to do here since ComboLBox always resets the focus to its 1285 * combo/edit counterpart */ 1286 break; 1287 } 1288 } 1289 return 0; 1290 } 1291 1292 /*********************************************************************** 1293 * COMBO_ItemOp 1294 * 1295 * Fixup an ownerdrawn item operation and pass it up to the combobox owner. 1296 */ 1297 static LRESULT COMBO_ItemOp( LPHEADCOMBO lphc, UINT msg, LPARAM lParam ) 1298 { 1299 HWND hWnd = lphc->self; 1300 UINT id = (UINT)GetWindowLongPtrW( hWnd, GWLP_ID ); 1301 1302 TRACE("[%p]: ownerdraw op %04x\n", lphc->self, msg ); 1303 1304 switch( msg ) 1305 { 1306 case WM_DELETEITEM: 1307 { 1308 DELETEITEMSTRUCT *lpIS = (DELETEITEMSTRUCT *)lParam; 1309 lpIS->CtlType = ODT_COMBOBOX; 1310 lpIS->CtlID = id; 1311 lpIS->hwndItem = hWnd; 1312 break; 1313 } 1314 case WM_DRAWITEM: 1315 { 1316 DRAWITEMSTRUCT *lpIS = (DRAWITEMSTRUCT *)lParam; 1317 lpIS->CtlType = ODT_COMBOBOX; 1318 lpIS->CtlID = id; 1319 lpIS->hwndItem = hWnd; 1320 break; 1321 } 1322 case WM_COMPAREITEM: 1323 { 1324 COMPAREITEMSTRUCT *lpIS = (COMPAREITEMSTRUCT *)lParam; 1325 lpIS->CtlType = ODT_COMBOBOX; 1326 lpIS->CtlID = id; 1327 lpIS->hwndItem = hWnd; 1328 break; 1329 } 1330 case WM_MEASUREITEM: 1331 { 1332 MEASUREITEMSTRUCT *lpIS = (MEASUREITEMSTRUCT *)lParam; 1333 lpIS->CtlType = ODT_COMBOBOX; 1334 lpIS->CtlID = id; 1335 break; 1336 } 1337 } 1338 return SendMessageW(lphc->owner, msg, id, lParam); 1339 } 1340 1341 1342 /*********************************************************************** 1343 * COMBO_GetTextW 1344 */ 1345 static LRESULT COMBO_GetText( HEADCOMBO *lphc, INT count, LPWSTR buf ) 1346 { 1347 INT length; 1348 1349 if( lphc->wState & CBF_EDIT ) 1350 return SendMessageW( lphc->hWndEdit, WM_GETTEXT, count, (LPARAM)buf ); 1351 1352 /* get it from the listbox */ 1353 1354 if (!count || !buf) return 0; 1355 if( lphc->hWndLBox ) 1356 { 1357 INT idx = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0); 1358 if (idx == LB_ERR) goto error; 1359 length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, idx, 0 ); 1360 if (length == LB_ERR) goto error; 1361 1362 /* 'length' is without the terminating character */ 1363 if (length >= count) 1364 { 1365 WCHAR *lpBuffer = heap_alloc((length + 1) * sizeof(WCHAR)); 1366 if (!lpBuffer) goto error; 1367 length = SendMessageW(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)lpBuffer); 1368 1369 /* truncate if buffer is too short */ 1370 if (length != LB_ERR) 1371 { 1372 lstrcpynW( buf, lpBuffer, count ); 1373 length = count; 1374 } 1375 heap_free( lpBuffer ); 1376 } 1377 else length = SendMessageW(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)buf); 1378 1379 if (length == LB_ERR) return 0; 1380 return length; 1381 } 1382 1383 error: /* error - truncate string, return zero */ 1384 buf[0] = 0; 1385 return 0; 1386 } 1387 1388 /*********************************************************************** 1389 * CBResetPos 1390 * 1391 * This function sets window positions according to the updated 1392 * component placement struct. 1393 */ 1394 static void CBResetPos( 1395 LPHEADCOMBO lphc, 1396 const RECT *rectEdit, 1397 const RECT *rectLB, 1398 BOOL bRedraw) 1399 { 1400 BOOL bDrop = (CB_GETTYPE(lphc) != CBS_SIMPLE); 1401 1402 /* NOTE: logs sometimes have WM_LBUTTONUP before a cascade of 1403 * sizing messages */ 1404 1405 if( lphc->wState & CBF_EDIT ) 1406 SetWindowPos( lphc->hWndEdit, 0, 1407 rectEdit->left, rectEdit->top, 1408 rectEdit->right - rectEdit->left, 1409 rectEdit->bottom - rectEdit->top, 1410 SWP_NOZORDER | SWP_NOACTIVATE | ((bDrop) ? SWP_NOREDRAW : 0) ); 1411 1412 SetWindowPos( lphc->hWndLBox, 0, 1413 rectLB->left, rectLB->top, 1414 rectLB->right - rectLB->left, 1415 rectLB->bottom - rectLB->top, 1416 SWP_NOACTIVATE | SWP_NOZORDER | ((bDrop) ? SWP_NOREDRAW : 0) ); 1417 1418 if( bDrop ) 1419 { 1420 if( lphc->wState & CBF_DROPPED ) 1421 { 1422 lphc->wState &= ~CBF_DROPPED; 1423 ShowWindow( lphc->hWndLBox, SW_HIDE ); 1424 } 1425 1426 if( bRedraw && !(lphc->wState & CBF_NOREDRAW) ) 1427 RedrawWindow( lphc->self, NULL, 0, 1428 RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW ); 1429 } 1430 } 1431 1432 1433 /*********************************************************************** 1434 * COMBO_Size 1435 */ 1436 static void COMBO_Size( LPHEADCOMBO lphc ) 1437 { 1438 /* 1439 * Those controls are always the same height. So we have to make sure 1440 * they are not resized to another value. 1441 */ 1442 if( CB_GETTYPE(lphc) != CBS_SIMPLE ) 1443 { 1444 int newComboHeight, curComboHeight, curComboWidth; 1445 RECT rc; 1446 1447 GetWindowRect(lphc->self, &rc); 1448 curComboHeight = rc.bottom - rc.top; 1449 curComboWidth = rc.right - rc.left; 1450 newComboHeight = CBGetTextAreaHeight(lphc->self, lphc) + 2*COMBO_YBORDERSIZE(); 1451 1452 /* 1453 * Resizing a combobox has another side effect, it resizes the dropped 1454 * rectangle as well. However, it does it only if the new height for the 1455 * combobox is more than the height it should have. In other words, 1456 * if the application resizing the combobox only had the intention to resize 1457 * the actual control, for example, to do the layout of a dialog that is 1458 * resized, the height of the dropdown is not changed. 1459 */ 1460 if( curComboHeight > newComboHeight ) 1461 { 1462 TRACE("oldComboHeight=%d, newComboHeight=%d, oldDropBottom=%d, oldDropTop=%d\n", 1463 curComboHeight, newComboHeight, lphc->droppedRect.bottom, 1464 lphc->droppedRect.top); 1465 lphc->droppedRect.bottom = lphc->droppedRect.top + curComboHeight - newComboHeight; 1466 } 1467 /* 1468 * Restore original height 1469 */ 1470 if( curComboHeight != newComboHeight ) 1471 SetWindowPos(lphc->self, 0, 0, 0, curComboWidth, newComboHeight, 1472 SWP_NOZORDER|SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOREDRAW); 1473 } 1474 1475 CBCalcPlacement(lphc->self, 1476 lphc, 1477 &lphc->textRect, 1478 &lphc->buttonRect, 1479 &lphc->droppedRect); 1480 1481 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE ); 1482 } 1483 1484 1485 /*********************************************************************** 1486 * COMBO_Font 1487 */ 1488 static void COMBO_Font( LPHEADCOMBO lphc, HFONT hFont, BOOL bRedraw ) 1489 { 1490 /* 1491 * Set the font 1492 */ 1493 lphc->hFont = hFont; 1494 1495 /* 1496 * Propagate to owned windows. 1497 */ 1498 if( lphc->wState & CBF_EDIT ) 1499 SendMessageW(lphc->hWndEdit, WM_SETFONT, (WPARAM)hFont, bRedraw); 1500 SendMessageW(lphc->hWndLBox, WM_SETFONT, (WPARAM)hFont, bRedraw); 1501 1502 /* 1503 * Redo the layout of the control. 1504 */ 1505 if ( CB_GETTYPE(lphc) == CBS_SIMPLE) 1506 { 1507 CBCalcPlacement(lphc->self, 1508 lphc, 1509 &lphc->textRect, 1510 &lphc->buttonRect, 1511 &lphc->droppedRect); 1512 1513 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE ); 1514 } 1515 else 1516 { 1517 CBForceDummyResize(lphc); 1518 } 1519 } 1520 1521 1522 /*********************************************************************** 1523 * COMBO_SetItemHeight 1524 */ 1525 static LRESULT COMBO_SetItemHeight( LPHEADCOMBO lphc, INT index, INT height ) 1526 { 1527 LRESULT lRet = CB_ERR; 1528 1529 if( index == -1 ) /* set text field height */ 1530 { 1531 if( height < 32768 ) 1532 { 1533 lphc->editHeight = height + 2; /* Is the 2 for 2*EDIT_CONTROL_PADDING? */ 1534 1535 /* 1536 * Redo the layout of the control. 1537 */ 1538 if ( CB_GETTYPE(lphc) == CBS_SIMPLE) 1539 { 1540 CBCalcPlacement(lphc->self, 1541 lphc, 1542 &lphc->textRect, 1543 &lphc->buttonRect, 1544 &lphc->droppedRect); 1545 1546 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE ); 1547 } 1548 else 1549 { 1550 CBForceDummyResize(lphc); 1551 } 1552 1553 lRet = height; 1554 } 1555 } 1556 else if ( CB_OWNERDRAWN(lphc) ) /* set listbox item height */ 1557 lRet = SendMessageW(lphc->hWndLBox, LB_SETITEMHEIGHT, index, height); 1558 return lRet; 1559 } 1560 1561 /*********************************************************************** 1562 * COMBO_SelectString 1563 */ 1564 static LRESULT COMBO_SelectString( LPHEADCOMBO lphc, INT start, LPARAM pText) 1565 { 1566 INT index = SendMessageW(lphc->hWndLBox, LB_SELECTSTRING, start, pText); 1567 if( index >= 0 ) 1568 { 1569 if( lphc->wState & CBF_EDIT ) 1570 CBUpdateEdit( lphc, index ); 1571 else 1572 { 1573 InvalidateRect(lphc->self, &lphc->textRect, TRUE); 1574 } 1575 } 1576 return (LRESULT)index; 1577 } 1578 1579 /*********************************************************************** 1580 * COMBO_LButtonDown 1581 */ 1582 static void COMBO_LButtonDown( LPHEADCOMBO lphc, LPARAM lParam ) 1583 { 1584 POINT pt; 1585 BOOL bButton; 1586 HWND hWnd = lphc->self; 1587 1588 pt.x = (short)LOWORD(lParam); 1589 pt.y = (short)HIWORD(lParam); 1590 bButton = PtInRect(&lphc->buttonRect, pt); 1591 1592 if( (CB_GETTYPE(lphc) == CBS_DROPDOWNLIST) || 1593 (bButton && (CB_GETTYPE(lphc) == CBS_DROPDOWN)) ) 1594 { 1595 lphc->wState |= CBF_BUTTONDOWN; 1596 if( lphc->wState & CBF_DROPPED ) 1597 { 1598 /* got a click to cancel selection */ 1599 1600 lphc->wState &= ~CBF_BUTTONDOWN; 1601 CBRollUp( lphc, TRUE, FALSE ); 1602 if( !IsWindow( hWnd ) ) return; 1603 1604 if( lphc->wState & CBF_CAPTURE ) 1605 { 1606 lphc->wState &= ~CBF_CAPTURE; 1607 ReleaseCapture(); 1608 } 1609 } 1610 else 1611 { 1612 /* drop down the listbox and start tracking */ 1613 1614 lphc->wState |= CBF_CAPTURE; 1615 SetCapture( hWnd ); 1616 CBDropDown( lphc ); 1617 } 1618 if( bButton ) CBRepaintButton( lphc ); 1619 } 1620 } 1621 1622 /*********************************************************************** 1623 * COMBO_LButtonUp 1624 * 1625 * Release capture and stop tracking if needed. 1626 */ 1627 static void COMBO_LButtonUp( LPHEADCOMBO lphc ) 1628 { 1629 if( lphc->wState & CBF_CAPTURE ) 1630 { 1631 lphc->wState &= ~CBF_CAPTURE; 1632 if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) 1633 { 1634 INT index = CBUpdateLBox( lphc, TRUE ); 1635 /* Update edit only if item is in the list */ 1636 if(index >= 0) 1637 { 1638 lphc->wState |= CBF_NOLBSELECT; 1639 CBUpdateEdit( lphc, index ); 1640 lphc->wState &= ~CBF_NOLBSELECT; 1641 } 1642 } 1643 ReleaseCapture(); 1644 SetCapture(lphc->hWndLBox); 1645 } 1646 1647 if( lphc->wState & CBF_BUTTONDOWN ) 1648 { 1649 lphc->wState &= ~CBF_BUTTONDOWN; 1650 CBRepaintButton( lphc ); 1651 } 1652 } 1653 1654 /*********************************************************************** 1655 * COMBO_MouseMove 1656 * 1657 * Two things to do - track combo button and release capture when 1658 * pointer goes into the listbox. 1659 */ 1660 static void COMBO_MouseMove( LPHEADCOMBO lphc, WPARAM wParam, LPARAM lParam ) 1661 { 1662 POINT pt; 1663 RECT lbRect; 1664 1665 pt.x = (short)LOWORD(lParam); 1666 pt.y = (short)HIWORD(lParam); 1667 1668 if( lphc->wState & CBF_BUTTONDOWN ) 1669 { 1670 BOOL bButton; 1671 1672 bButton = PtInRect(&lphc->buttonRect, pt); 1673 1674 if( !bButton ) 1675 { 1676 lphc->wState &= ~CBF_BUTTONDOWN; 1677 CBRepaintButton( lphc ); 1678 } 1679 } 1680 1681 GetClientRect( lphc->hWndLBox, &lbRect ); 1682 MapWindowPoints( lphc->self, lphc->hWndLBox, &pt, 1 ); 1683 if( PtInRect(&lbRect, pt) ) 1684 { 1685 lphc->wState &= ~CBF_CAPTURE; 1686 ReleaseCapture(); 1687 if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) CBUpdateLBox( lphc, TRUE ); 1688 1689 /* hand over pointer tracking */ 1690 SendMessageW(lphc->hWndLBox, WM_LBUTTONDOWN, wParam, lParam); 1691 } 1692 } 1693 1694 static LRESULT COMBO_GetComboBoxInfo(const HEADCOMBO *lphc, COMBOBOXINFO *pcbi) 1695 { 1696 if (!pcbi || (pcbi->cbSize < sizeof(COMBOBOXINFO))) 1697 return FALSE; 1698 1699 pcbi->rcItem = lphc->textRect; 1700 pcbi->rcButton = lphc->buttonRect; 1701 pcbi->stateButton = 0; 1702 if (lphc->wState & CBF_BUTTONDOWN) 1703 pcbi->stateButton |= STATE_SYSTEM_PRESSED; 1704 if (IsRectEmpty(&lphc->buttonRect)) 1705 pcbi->stateButton |= STATE_SYSTEM_INVISIBLE; 1706 pcbi->hwndCombo = lphc->self; 1707 pcbi->hwndItem = lphc->hWndEdit; 1708 pcbi->hwndList = lphc->hWndLBox; 1709 return TRUE; 1710 } 1711 1712 static LRESULT CALLBACK COMBO_WindowProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) 1713 { 1714 HEADCOMBO *lphc = (HEADCOMBO *)GetWindowLongPtrW( hwnd, 0 ); 1715 HTHEME theme; 1716 1717 TRACE("[%p]: msg %#x wp %08lx lp %08lx\n", hwnd, message, wParam, lParam ); 1718 1719 if (!IsWindow(hwnd)) return 0; 1720 1721 if (lphc || message == WM_NCCREATE) 1722 switch(message) 1723 { 1724 case WM_NCCREATE: 1725 { 1726 LONG style = ((CREATESTRUCTW *)lParam)->style; 1727 return COMBO_NCCreate(hwnd, style); 1728 } 1729 1730 case WM_NCDESTROY: 1731 COMBO_NCDestroy(lphc); 1732 break;/* -> DefWindowProc */ 1733 1734 case WM_CREATE: 1735 { 1736 HWND hwndParent; 1737 LONG style; 1738 1739 hwndParent = ((CREATESTRUCTW *)lParam)->hwndParent; 1740 style = ((CREATESTRUCTW *)lParam)->style; 1741 return COMBO_Create(hwnd, lphc, hwndParent, style); 1742 } 1743 1744 case WM_DESTROY: 1745 theme = GetWindowTheme( hwnd ); 1746 CloseThemeData( theme ); 1747 break; 1748 1749 case WM_THEMECHANGED: 1750 theme = GetWindowTheme( hwnd ); 1751 CloseThemeData( theme ); 1752 OpenThemeData( hwnd, WC_COMBOBOXW ); 1753 break; 1754 1755 case WM_PRINTCLIENT: 1756 case WM_PAINT: 1757 { 1758 LRESULT ret = 0; 1759 PAINTSTRUCT ps; 1760 HDC hdc; 1761 1762 hdc = wParam ? (HDC)wParam : BeginPaint(hwnd, &ps); 1763 1764 if (hdc && !(lphc->wState & CBF_NOREDRAW)) 1765 { 1766 HTHEME theme = GetWindowTheme(hwnd); 1767 1768 if (theme) 1769 ret = COMBO_ThemedPaint(theme, lphc, hdc); 1770 else 1771 ret = COMBO_Paint(lphc, hdc); 1772 } 1773 1774 if (!wParam) 1775 EndPaint(hwnd, &ps); 1776 1777 return ret; 1778 } 1779 case WM_ERASEBKGND: 1780 /* do all painting in WM_PAINT like Windows does */ 1781 return 1; 1782 1783 case WM_GETDLGCODE: 1784 { 1785 LRESULT result = DLGC_WANTARROWS | DLGC_WANTCHARS; 1786 if (lParam && (((LPMSG)lParam)->message == WM_KEYDOWN)) 1787 { 1788 int vk = (int)((LPMSG)lParam)->wParam; 1789 1790 if ((vk == VK_RETURN || vk == VK_ESCAPE) && (lphc->wState & CBF_DROPPED)) 1791 result |= DLGC_WANTMESSAGE; 1792 } 1793 return result; 1794 } 1795 1796 case WM_SIZE: 1797 if (lphc->hWndLBox && !(lphc->wState & CBF_NORESIZE)) 1798 COMBO_Size( lphc ); 1799 return TRUE; 1800 1801 case WM_SETFONT: 1802 COMBO_Font( lphc, (HFONT)wParam, (BOOL)lParam ); 1803 return TRUE; 1804 1805 case WM_GETFONT: 1806 return (LRESULT)lphc->hFont; 1807 1808 case WM_SETFOCUS: 1809 if (lphc->wState & CBF_EDIT) 1810 { 1811 SetFocus( lphc->hWndEdit ); 1812 /* The first time focus is received, select all the text */ 1813 if (!(lphc->wState & CBF_BEENFOCUSED)) 1814 { 1815 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, -1); 1816 lphc->wState |= CBF_BEENFOCUSED; 1817 } 1818 } 1819 else 1820 COMBO_SetFocus( lphc ); 1821 return TRUE; 1822 1823 case WM_KILLFOCUS: 1824 { 1825 HWND hwndFocus = (HWND)wParam; 1826 if (!hwndFocus || (hwndFocus != lphc->hWndEdit && hwndFocus != lphc->hWndLBox)) 1827 COMBO_KillFocus( lphc ); 1828 return TRUE; 1829 } 1830 1831 case WM_COMMAND: 1832 return COMBO_Command( lphc, wParam, (HWND)lParam ); 1833 1834 case WM_GETTEXT: 1835 return COMBO_GetText( lphc, wParam, (LPWSTR)lParam ); 1836 1837 case WM_SETTEXT: 1838 case WM_GETTEXTLENGTH: 1839 case WM_CLEAR: 1840 if ((message == WM_GETTEXTLENGTH) && !ISWIN31 && !(lphc->wState & CBF_EDIT)) 1841 { 1842 int j = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0); 1843 if (j == -1) return 0; 1844 return SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, j, 0); 1845 } 1846 else if ( lphc->wState & CBF_EDIT ) 1847 { 1848 LRESULT ret; 1849 lphc->wState |= CBF_NOEDITNOTIFY; 1850 ret = SendMessageW(lphc->hWndEdit, message, wParam, lParam); 1851 lphc->wState &= ~CBF_NOEDITNOTIFY; 1852 return ret; 1853 } 1854 else 1855 return CB_ERR; 1856 1857 case WM_CUT: 1858 case WM_PASTE: 1859 case WM_COPY: 1860 if (lphc->wState & CBF_EDIT) 1861 return SendMessageW(lphc->hWndEdit, message, wParam, lParam); 1862 else return CB_ERR; 1863 1864 case WM_DRAWITEM: 1865 case WM_DELETEITEM: 1866 case WM_COMPAREITEM: 1867 case WM_MEASUREITEM: 1868 return COMBO_ItemOp(lphc, message, lParam); 1869 1870 case WM_ENABLE: 1871 if (lphc->wState & CBF_EDIT) 1872 EnableWindow( lphc->hWndEdit, (BOOL)wParam ); 1873 EnableWindow( lphc->hWndLBox, (BOOL)wParam ); 1874 1875 /* Force the control to repaint when the enabled state changes. */ 1876 InvalidateRect(lphc->self, NULL, TRUE); 1877 return TRUE; 1878 1879 case WM_SETREDRAW: 1880 if (wParam) 1881 lphc->wState &= ~CBF_NOREDRAW; 1882 else 1883 lphc->wState |= CBF_NOREDRAW; 1884 1885 if ( lphc->wState & CBF_EDIT ) 1886 SendMessageW(lphc->hWndEdit, message, wParam, lParam); 1887 SendMessageW(lphc->hWndLBox, message, wParam, lParam); 1888 return 0; 1889 1890 case WM_SYSKEYDOWN: 1891 if ( KEYDATA_ALT & HIWORD(lParam) ) 1892 if( wParam == VK_UP || wParam == VK_DOWN ) 1893 COMBO_FlipListbox( lphc, FALSE, FALSE ); 1894 return 0; 1895 1896 case WM_KEYDOWN: 1897 if ((wParam == VK_RETURN || wParam == VK_ESCAPE) && 1898 (lphc->wState & CBF_DROPPED)) 1899 { 1900 CBRollUp( lphc, wParam == VK_RETURN, FALSE ); 1901 return TRUE; 1902 } 1903 else if ((wParam == VK_F4) && !(lphc->wState & CBF_EUI)) 1904 { 1905 COMBO_FlipListbox( lphc, FALSE, FALSE ); 1906 return TRUE; 1907 } 1908 /* fall through */ 1909 case WM_CHAR: 1910 case WM_IME_CHAR: 1911 { 1912 HWND hwndTarget; 1913 1914 if ( lphc->wState & CBF_EDIT ) 1915 hwndTarget = lphc->hWndEdit; 1916 else 1917 hwndTarget = lphc->hWndLBox; 1918 1919 return SendMessageW(hwndTarget, message, wParam, lParam); 1920 } 1921 1922 case WM_LBUTTONDOWN: 1923 if ( !(lphc->wState & CBF_FOCUSED) ) SetFocus( lphc->self ); 1924 if ( lphc->wState & CBF_FOCUSED ) COMBO_LButtonDown( lphc, lParam ); 1925 return TRUE; 1926 1927 case WM_LBUTTONUP: 1928 COMBO_LButtonUp( lphc ); 1929 return TRUE; 1930 1931 case WM_MOUSEMOVE: 1932 if (!IsRectEmpty(&lphc->buttonRect)) 1933 { 1934 POINT pt; 1935 1936 pt.x = (short)LOWORD(lParam); 1937 pt.y = (short)HIWORD(lParam); 1938 1939 if (PtInRect(&lphc->buttonRect, pt)) 1940 { 1941 if (!(lphc->wState & CBF_HOT)) 1942 { 1943 lphc->wState |= CBF_HOT; 1944 RedrawWindow(hwnd, &lphc->buttonRect, 0, RDW_INVALIDATE | RDW_UPDATENOW); 1945 } 1946 } 1947 else if (lphc->wState & CBF_HOT) 1948 { 1949 lphc->wState &= ~CBF_HOT; 1950 RedrawWindow(hwnd, &lphc->buttonRect, 0, RDW_INVALIDATE | RDW_UPDATENOW); 1951 } 1952 } 1953 1954 if ( lphc->wState & CBF_CAPTURE ) 1955 COMBO_MouseMove( lphc, wParam, lParam ); 1956 return TRUE; 1957 1958 case WM_MOUSEWHEEL: 1959 if (wParam & (MK_SHIFT | MK_CONTROL)) 1960 return DefWindowProcW(hwnd, message, wParam, lParam); 1961 1962 if (GET_WHEEL_DELTA_WPARAM(wParam) > 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_UP, 0); 1963 if (GET_WHEEL_DELTA_WPARAM(wParam) < 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_DOWN, 0); 1964 return TRUE; 1965 1966 case WM_CTLCOLOR: 1967 case WM_CTLCOLORMSGBOX: 1968 case WM_CTLCOLOREDIT: 1969 case WM_CTLCOLORLISTBOX: 1970 case WM_CTLCOLORBTN: 1971 case WM_CTLCOLORDLG: 1972 case WM_CTLCOLORSCROLLBAR: 1973 case WM_CTLCOLORSTATIC: 1974 return SendMessageW(lphc->owner, message, wParam, lParam); 1975 1976 /* Combo messages */ 1977 case CB_ADDSTRING: 1978 if (lphc->dwStyle & CBS_LOWERCASE) 1979 CharLowerW((LPWSTR)lParam); 1980 else if (lphc->dwStyle & CBS_UPPERCASE) 1981 CharUpperW((LPWSTR)lParam); 1982 return SendMessageW(lphc->hWndLBox, LB_ADDSTRING, 0, lParam); 1983 1984 case CB_INSERTSTRING: 1985 if (lphc->dwStyle & CBS_LOWERCASE) 1986 CharLowerW((LPWSTR)lParam); 1987 else if (lphc->dwStyle & CBS_UPPERCASE) 1988 CharUpperW((LPWSTR)lParam); 1989 return SendMessageW(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam); 1990 1991 case CB_DELETESTRING: 1992 return SendMessageW(lphc->hWndLBox, LB_DELETESTRING, wParam, 0); 1993 1994 case CB_SELECTSTRING: 1995 return COMBO_SelectString(lphc, (INT)wParam, lParam); 1996 1997 case CB_FINDSTRING: 1998 return SendMessageW(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam); 1999 2000 case CB_FINDSTRINGEXACT: 2001 return SendMessageW(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam); 2002 2003 case CB_SETITEMHEIGHT: 2004 return COMBO_SetItemHeight( lphc, (INT)wParam, (INT)lParam); 2005 2006 case CB_GETITEMHEIGHT: 2007 if ((INT)wParam >= 0) /* listbox item */ 2008 return SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, wParam, 0); 2009 return CBGetTextAreaHeight(hwnd, lphc); 2010 2011 case CB_RESETCONTENT: 2012 SendMessageW(lphc->hWndLBox, LB_RESETCONTENT, 0, 0); 2013 2014 if ((lphc->wState & CBF_EDIT) && CB_HASSTRINGS(lphc)) 2015 { 2016 static const WCHAR empty_stringW[] = { 0 }; 2017 SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, (LPARAM)empty_stringW); 2018 } 2019 else 2020 InvalidateRect(lphc->self, NULL, TRUE); 2021 return TRUE; 2022 2023 case CB_INITSTORAGE: 2024 return SendMessageW(lphc->hWndLBox, LB_INITSTORAGE, wParam, lParam); 2025 2026 case CB_GETHORIZONTALEXTENT: 2027 return SendMessageW(lphc->hWndLBox, LB_GETHORIZONTALEXTENT, 0, 0); 2028 2029 case CB_SETHORIZONTALEXTENT: 2030 return SendMessageW(lphc->hWndLBox, LB_SETHORIZONTALEXTENT, wParam, 0); 2031 2032 case CB_GETTOPINDEX: 2033 return SendMessageW(lphc->hWndLBox, LB_GETTOPINDEX, 0, 0); 2034 2035 case CB_GETLOCALE: 2036 return SendMessageW(lphc->hWndLBox, LB_GETLOCALE, 0, 0); 2037 2038 case CB_SETLOCALE: 2039 return SendMessageW(lphc->hWndLBox, LB_SETLOCALE, wParam, 0); 2040 2041 case CB_SETDROPPEDWIDTH: 2042 if ((CB_GETTYPE(lphc) == CBS_SIMPLE) || (INT)wParam >= 32768) 2043 return CB_ERR; 2044 2045 /* new value must be higher than combobox width */ 2046 if ((INT)wParam >= lphc->droppedRect.right - lphc->droppedRect.left) 2047 lphc->droppedWidth = wParam; 2048 else if (wParam) 2049 lphc->droppedWidth = 0; 2050 2051 /* recalculate the combobox area */ 2052 CBCalcPlacement(hwnd, lphc, &lphc->textRect, &lphc->buttonRect, &lphc->droppedRect ); 2053 2054 /* fall through */ 2055 case CB_GETDROPPEDWIDTH: 2056 if (lphc->droppedWidth) 2057 return lphc->droppedWidth; 2058 return lphc->droppedRect.right - lphc->droppedRect.left; 2059 2060 case CB_GETDROPPEDCONTROLRECT: 2061 if (lParam) 2062 CBGetDroppedControlRect(lphc, (LPRECT)lParam ); 2063 return CB_OKAY; 2064 2065 case CB_GETDROPPEDSTATE: 2066 return (lphc->wState & CBF_DROPPED) != 0; 2067 2068 case CB_DIR: 2069 return SendMessageW(lphc->hWndLBox, LB_DIR, wParam, lParam); 2070 2071 case CB_SHOWDROPDOWN: 2072 if (CB_GETTYPE(lphc) != CBS_SIMPLE) 2073 { 2074 if (wParam) 2075 { 2076 if (!(lphc->wState & CBF_DROPPED)) 2077 CBDropDown( lphc ); 2078 } 2079 else if (lphc->wState & CBF_DROPPED) 2080 CBRollUp( lphc, FALSE, TRUE ); 2081 } 2082 return TRUE; 2083 2084 case CB_GETCOUNT: 2085 return SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0); 2086 2087 case CB_GETCURSEL: 2088 return SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0); 2089 2090 case CB_SETCURSEL: 2091 lParam = SendMessageW(lphc->hWndLBox, LB_SETCURSEL, wParam, 0); 2092 if (lParam >= 0) 2093 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, wParam, 0); 2094 2095 /* no LBN_SELCHANGE in this case, update manually */ 2096 CBPaintText(lphc, NULL); 2097 lphc->wState &= ~CBF_SELCHANGE; 2098 return lParam; 2099 2100 case CB_GETLBTEXT: 2101 return SendMessageW(lphc->hWndLBox, LB_GETTEXT, wParam, lParam); 2102 2103 case CB_GETLBTEXTLEN: 2104 return SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0); 2105 2106 case CB_GETITEMDATA: 2107 return SendMessageW(lphc->hWndLBox, LB_GETITEMDATA, wParam, 0); 2108 2109 case CB_SETITEMDATA: 2110 return SendMessageW(lphc->hWndLBox, LB_SETITEMDATA, wParam, lParam); 2111 2112 case CB_GETEDITSEL: 2113 /* Edit checks passed parameters itself */ 2114 if (lphc->wState & CBF_EDIT) 2115 return SendMessageW(lphc->hWndEdit, EM_GETSEL, wParam, lParam); 2116 return CB_ERR; 2117 2118 case CB_SETEDITSEL: 2119 if (lphc->wState & CBF_EDIT) 2120 return SendMessageW(lphc->hWndEdit, EM_SETSEL, (INT)(SHORT)LOWORD(lParam), (INT)(SHORT)HIWORD(lParam) ); 2121 return CB_ERR; 2122 2123 case CB_SETEXTENDEDUI: 2124 if (CB_GETTYPE(lphc) == CBS_SIMPLE ) 2125 return CB_ERR; 2126 if (wParam) 2127 lphc->wState |= CBF_EUI; 2128 else 2129 lphc->wState &= ~CBF_EUI; 2130 return CB_OKAY; 2131 2132 case CB_GETEXTENDEDUI: 2133 return (lphc->wState & CBF_EUI) != 0; 2134 2135 case CB_GETCOMBOBOXINFO: 2136 return COMBO_GetComboBoxInfo(lphc, (COMBOBOXINFO *)lParam); 2137 2138 case CB_LIMITTEXT: 2139 if (lphc->wState & CBF_EDIT) 2140 return SendMessageW(lphc->hWndEdit, EM_LIMITTEXT, wParam, lParam); 2141 return TRUE; 2142 2143 case CB_GETMINVISIBLE: 2144 return lphc->visibleItems; 2145 2146 case CB_SETMINVISIBLE: 2147 lphc->visibleItems = (INT)wParam; 2148 return TRUE; 2149 2150 default: 2151 if (message >= WM_USER) 2152 WARN("unknown msg WM_USER+%04x wp=%04lx lp=%08lx\n", message - WM_USER, wParam, lParam ); 2153 break; 2154 } 2155 2156 return DefWindowProcW(hwnd, message, wParam, lParam); 2157 } 2158 2159 void COMBO_Register(void) 2160 { 2161 WNDCLASSW wndClass; 2162 2163 memset(&wndClass, 0, sizeof(wndClass)); 2164 wndClass.style = CS_PARENTDC | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS; 2165 wndClass.lpfnWndProc = COMBO_WindowProc; 2166 wndClass.cbClsExtra = 0; 2167 wndClass.cbWndExtra = sizeof(HEADCOMBO *); 2168 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW); 2169 wndClass.hbrBackground = NULL; 2170 wndClass.lpszClassName = WC_COMBOBOXW; 2171 RegisterClassW(&wndClass); 2172 } 2173 2174 #ifdef __REACTOS__ 2175 void COMBO_Unregister(void) 2176 { 2177 UnregisterClassW(WC_COMBOBOXW, NULL); 2178 } 2179 #endif 2180