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