| /* |
| * Combo controls |
| * |
| * Copyright 1997 Alex Korobka |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| * |
| * FIXME: roll up in Netscape 3.01. |
| */ |
| |
| #include <string.h> |
| |
| #include "winbase.h" |
| #include "windef.h" |
| #include "wingdi.h" |
| #include "winuser.h" |
| #include "wine/winuser16.h" |
| #include "wine/unicode.h" |
| #include "spy.h" |
| #include "user.h" |
| #include "win.h" |
| #include "controls.h" |
| #include "wine/debug.h" |
| |
| WINE_DEFAULT_DEBUG_CHANNEL(combo); |
| |
| /* bits in the dwKeyData */ |
| #define KEYDATA_ALT 0x2000 |
| #define KEYDATA_PREVSTATE 0x4000 |
| |
| /* |
| * Additional combo box definitions |
| */ |
| |
| #define CB_NOTIFY( lphc, code ) \ |
| (SendMessageW((lphc)->owner, WM_COMMAND, \ |
| MAKEWPARAM(GetWindowLongA((lphc)->self,GWL_ID), (code)), (LPARAM)(lphc)->self)) |
| |
| #define CB_DISABLED( lphc ) (!IsWindowEnabled((lphc)->self)) |
| #define CB_OWNERDRAWN( lphc ) ((lphc)->dwStyle & (CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE)) |
| #define CB_HASSTRINGS( lphc ) ((lphc)->dwStyle & CBS_HASSTRINGS) |
| #define CB_HWND( lphc ) ((lphc)->self) |
| |
| #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03) |
| |
| /* |
| * Drawing globals |
| */ |
| static HBITMAP hComboBmp = 0; |
| static UINT CBitHeight, CBitWidth; |
| |
| /* |
| * Look and feel dependant "constants" |
| */ |
| |
| #define COMBO_YBORDERGAP 5 |
| #define COMBO_XBORDERSIZE() ( (TWEAK_WineLook == WIN31_LOOK) ? 0 : 2 ) |
| #define COMBO_YBORDERSIZE() ( (TWEAK_WineLook == WIN31_LOOK) ? 0 : 2 ) |
| #define COMBO_EDITBUTTONSPACE() ( (TWEAK_WineLook == WIN31_LOOK) ? 8 : 0 ) |
| #define EDIT_CONTROL_PADDING() ( (TWEAK_WineLook == WIN31_LOOK) ? 0 : 1 ) |
| |
| static LRESULT WINAPI ComboWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ); |
| static LRESULT WINAPI ComboWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ); |
| |
| /********************************************************************* |
| * combo class descriptor |
| */ |
| const struct builtin_class_descr COMBO_builtin_class = |
| { |
| "ComboBox", /* name */ |
| CS_GLOBALCLASS | CS_PARENTDC | CS_DBLCLKS, /* style */ |
| ComboWndProcA, /* procA */ |
| ComboWndProcW, /* procW */ |
| sizeof(HEADCOMBO *), /* extra */ |
| IDC_ARROWA, /* cursor */ |
| 0 /* brush */ |
| }; |
| |
| |
| /*********************************************************************** |
| * COMBO_Init |
| * |
| * Load combo button bitmap. |
| */ |
| static BOOL COMBO_Init() |
| { |
| HDC hDC; |
| |
| if( hComboBmp ) return TRUE; |
| if( (hDC = CreateCompatibleDC(0)) ) |
| { |
| BOOL bRet = FALSE; |
| if( (hComboBmp = LoadBitmapW(0, MAKEINTRESOURCEW(OBM_COMBO))) ) |
| { |
| BITMAP bm; |
| HBITMAP hPrevB; |
| RECT r; |
| |
| GetObjectW( hComboBmp, sizeof(bm), &bm ); |
| CBitHeight = bm.bmHeight; |
| CBitWidth = bm.bmWidth; |
| |
| TRACE("combo bitmap [%i,%i]\n", CBitWidth, CBitHeight ); |
| |
| hPrevB = SelectObject( hDC, hComboBmp); |
| SetRect( &r, 0, 0, CBitWidth, CBitHeight ); |
| InvertRect( hDC, &r ); |
| SelectObject( hDC, hPrevB ); |
| bRet = TRUE; |
| } |
| DeleteDC( hDC ); |
| return bRet; |
| } |
| return FALSE; |
| } |
| |
| /*********************************************************************** |
| * COMBO_NCCreate |
| */ |
| static LRESULT COMBO_NCCreate(HWND hwnd, LONG style) |
| { |
| LPHEADCOMBO lphc; |
| |
| if (COMBO_Init() && (lphc = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(HEADCOMBO))) ) |
| { |
| lphc->self = hwnd; |
| SetWindowLongA( hwnd, 0, (LONG)lphc ); |
| |
| /* some braindead apps do try to use scrollbar/border flags */ |
| |
| lphc->dwStyle = style & ~(WS_BORDER | WS_HSCROLL | WS_VSCROLL); |
| SetWindowLongA( hwnd, GWL_STYLE, style & ~(WS_BORDER | WS_HSCROLL | WS_VSCROLL) ); |
| |
| /* |
| * We also have to remove the client edge style to make sure |
| * we don't end-up with a non client area. |
| */ |
| SetWindowLongA( hwnd, GWL_EXSTYLE, |
| GetWindowLongA( hwnd, GWL_EXSTYLE ) & ~WS_EX_CLIENTEDGE ); |
| |
| if( !(style & (CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE)) ) |
| lphc->dwStyle |= CBS_HASSTRINGS; |
| if( !(GetWindowLongA( hwnd, GWL_EXSTYLE ) & WS_EX_NOPARENTNOTIFY) ) |
| lphc->wState |= CBF_NOTIFY; |
| |
| TRACE("[%p], style = %08x\n", lphc, lphc->dwStyle ); |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| /*********************************************************************** |
| * COMBO_NCDestroy |
| */ |
| static LRESULT COMBO_NCDestroy( LPHEADCOMBO lphc ) |
| { |
| |
| if( lphc ) |
| { |
| TRACE("[%04x]: freeing storage\n", lphc->self); |
| |
| if( (CB_GETTYPE(lphc) != CBS_SIMPLE) && lphc->hWndLBox ) |
| DestroyWindow( lphc->hWndLBox ); |
| |
| SetWindowLongA( lphc->self, 0, 0 ); |
| HeapFree( GetProcessHeap(), 0, lphc ); |
| } |
| return 0; |
| } |
| |
| /*********************************************************************** |
| * CBGetTextAreaHeight |
| * |
| * This method will calculate the height of the text area of the |
| * combobox. |
| * The height of the text area is set in two ways. |
| * It can be set explicitly through a combobox message or through a |
| * WM_MEASUREITEM callback. |
| * If this is not the case, the height is set to 13 dialog units. |
| * This height was determined through experimentation. |
| */ |
| static INT CBGetTextAreaHeight( |
| HWND hwnd, |
| LPHEADCOMBO lphc) |
| { |
| INT iTextItemHeight; |
| |
| if( lphc->editHeight ) /* explicitly set height */ |
| { |
| iTextItemHeight = lphc->editHeight; |
| } |
| else |
| { |
| TEXTMETRICW tm; |
| HDC hDC = GetDC(hwnd); |
| HFONT hPrevFont = 0; |
| INT baseUnitY; |
| |
| if (lphc->hFont) |
| hPrevFont = SelectObject( hDC, lphc->hFont ); |
| |
| GetTextMetricsW(hDC, &tm); |
| |
| baseUnitY = tm.tmHeight; |
| |
| if( hPrevFont ) |
| SelectObject( hDC, hPrevFont ); |
| |
| ReleaseDC(hwnd, hDC); |
| |
| iTextItemHeight = ((13 * baseUnitY) / 8); |
| |
| /* |
| * This "formula" calculates the height of the complete control. |
| * To calculate the height of the text area, we have to remove the |
| * borders. |
| */ |
| iTextItemHeight -= 2*COMBO_YBORDERSIZE(); |
| } |
| |
| /* |
| * Check the ownerdraw case if we haven't asked the parent the size |
| * of the item yet. |
| */ |
| if ( CB_OWNERDRAWN(lphc) && |
| (lphc->wState & CBF_MEASUREITEM) ) |
| { |
| MEASUREITEMSTRUCT measureItem; |
| RECT clientRect; |
| INT originalItemHeight = iTextItemHeight; |
| UINT id = GetWindowLongA( lphc->self, GWL_ID ); |
| |
| /* |
| * We use the client rect for the width of the item. |
| */ |
| GetClientRect(hwnd, &clientRect); |
| |
| lphc->wState &= ~CBF_MEASUREITEM; |
| |
| /* |
| * Send a first one to measure the size of the text area |
| */ |
| measureItem.CtlType = ODT_COMBOBOX; |
| measureItem.CtlID = id; |
| measureItem.itemID = -1; |
| measureItem.itemWidth = clientRect.right; |
| measureItem.itemHeight = iTextItemHeight - 6; /* ownerdrawn cb is taller */ |
| measureItem.itemData = 0; |
| SendMessageW(lphc->owner, WM_MEASUREITEM, id, (LPARAM)&measureItem); |
| iTextItemHeight = 6 + measureItem.itemHeight; |
| |
| /* |
| * Send a second one in the case of a fixed ownerdraw list to calculate the |
| * size of the list items. (we basically do this on behalf of the listbox) |
| */ |
| if (lphc->dwStyle & CBS_OWNERDRAWFIXED) |
| { |
| measureItem.CtlType = ODT_COMBOBOX; |
| measureItem.CtlID = id; |
| measureItem.itemID = 0; |
| measureItem.itemWidth = clientRect.right; |
| measureItem.itemHeight = originalItemHeight; |
| measureItem.itemData = 0; |
| SendMessageW(lphc->owner, WM_MEASUREITEM, id, (LPARAM)&measureItem); |
| lphc->fixedOwnerDrawHeight = measureItem.itemHeight; |
| } |
| |
| /* |
| * Keep the size for the next time |
| */ |
| lphc->editHeight = iTextItemHeight; |
| } |
| |
| return iTextItemHeight; |
| } |
| |
| /*********************************************************************** |
| * CBForceDummyResize |
| * |
| * The dummy resize is used for listboxes that have a popup to trigger |
| * a re-arranging of the contents of the combobox and the recalculation |
| * of the size of the "real" control window. |
| */ |
| static void CBForceDummyResize( |
| LPHEADCOMBO lphc) |
| { |
| RECT windowRect; |
| int newComboHeight; |
| |
| newComboHeight = CBGetTextAreaHeight(lphc->self,lphc) + 2*COMBO_YBORDERSIZE(); |
| |
| GetWindowRect(lphc->self, &windowRect); |
| |
| /* |
| * We have to be careful, resizing a combobox also has the meaning that the |
| * dropped rect will be resized. In this case, we want to trigger a resize |
| * to recalculate layout but we don't want to change the dropped rectangle |
| * So, we pass the height of text area of control as the height. |
| * this will cancel-out in the processing of the WM_WINDOWPOSCHANGING |
| * message. |
| */ |
| SetWindowPos( lphc->self, |
| (HWND)NULL, |
| 0, 0, |
| windowRect.right - windowRect.left, |
| newComboHeight, |
| SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE ); |
| } |
| |
| /*********************************************************************** |
| * CBCalcPlacement |
| * |
| * Set up component coordinates given valid lphc->RectCombo. |
| */ |
| static void CBCalcPlacement( |
| HWND hwnd, |
| LPHEADCOMBO lphc, |
| LPRECT lprEdit, |
| LPRECT lprButton, |
| LPRECT lprLB) |
| { |
| /* |
| * Again, start with the client rectangle. |
| */ |
| GetClientRect(hwnd, lprEdit); |
| |
| /* |
| * Remove the borders |
| */ |
| InflateRect(lprEdit, -COMBO_XBORDERSIZE(), -COMBO_YBORDERSIZE()); |
| |
| /* |
| * Chop off the bottom part to fit with the height of the text area. |
| */ |
| lprEdit->bottom = lprEdit->top + CBGetTextAreaHeight(hwnd, lphc); |
| |
| /* |
| * The button starts the same vertical position as the text area. |
| */ |
| CopyRect(lprButton, lprEdit); |
| |
| /* |
| * If the combobox is "simple" there is no button. |
| */ |
| if( CB_GETTYPE(lphc) == CBS_SIMPLE ) |
| lprButton->left = lprButton->right = lprButton->bottom = 0; |
| else |
| { |
| /* |
| * Let's assume the combobox button is the same width as the |
| * scrollbar button. |
| * size the button horizontally and cut-off the text area. |
| */ |
| lprButton->left = lprButton->right - GetSystemMetrics(SM_CXVSCROLL); |
| lprEdit->right = lprButton->left; |
| } |
| |
| /* |
| * In the case of a dropdown, there is an additional spacing between the |
| * text area and the button. |
| */ |
| if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) |
| { |
| lprEdit->right -= COMBO_EDITBUTTONSPACE(); |
| } |
| |
| /* |
| * If we have an edit control, we space it away from the borders slightly. |
| */ |
| if (CB_GETTYPE(lphc) != CBS_DROPDOWNLIST) |
| { |
| InflateRect(lprEdit, -EDIT_CONTROL_PADDING(), -EDIT_CONTROL_PADDING()); |
| } |
| |
| /* |
| * Adjust the size of the listbox popup. |
| */ |
| if( CB_GETTYPE(lphc) == CBS_SIMPLE ) |
| { |
| /* |
| * Use the client rectangle to initialize the listbox rectangle |
| */ |
| GetClientRect(hwnd, lprLB); |
| |
| /* |
| * Then, chop-off the top part. |
| */ |
| lprLB->top = lprEdit->bottom + COMBO_YBORDERSIZE(); |
| } |
| else |
| { |
| /* |
| * Make sure the dropped width is as large as the combobox itself. |
| */ |
| if (lphc->droppedWidth < (lprButton->right + COMBO_XBORDERSIZE())) |
| { |
| lprLB->right = lprLB->left + (lprButton->right + COMBO_XBORDERSIZE()); |
| |
| /* |
| * In the case of a dropdown, the popup listbox is offset to the right. |
| * so, we want to make sure it's flush with the right side of the |
| * combobox |
| */ |
| if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) |
| lprLB->right -= COMBO_EDITBUTTONSPACE(); |
| } |
| else |
| lprLB->right = lprLB->left + lphc->droppedWidth; |
| } |
| |
| TRACE("\ttext\t= (%i,%i-%i,%i)\n", |
| lprEdit->left, lprEdit->top, lprEdit->right, lprEdit->bottom); |
| |
| TRACE("\tbutton\t= (%i,%i-%i,%i)\n", |
| lprButton->left, lprButton->top, lprButton->right, lprButton->bottom); |
| |
| TRACE("\tlbox\t= (%i,%i-%i,%i)\n", |
| lprLB->left, lprLB->top, lprLB->right, lprLB->bottom ); |
| } |
| |
| /*********************************************************************** |
| * CBGetDroppedControlRect |
| */ |
| static void CBGetDroppedControlRect( LPHEADCOMBO lphc, LPRECT lpRect) |
| { |
| /* In windows, CB_GETDROPPEDCONTROLRECT returns the upper left corner |
| of the combo box and the lower right corner of the listbox */ |
| |
| GetWindowRect(lphc->self, lpRect); |
| |
| lpRect->right = lpRect->left + lphc->droppedRect.right - lphc->droppedRect.left; |
| lpRect->bottom = lpRect->top + lphc->droppedRect.bottom - lphc->droppedRect.top; |
| |
| } |
| |
| /*********************************************************************** |
| * COMBO_WindowPosChanging |
| */ |
| static LRESULT COMBO_WindowPosChanging( |
| HWND hwnd, |
| LPHEADCOMBO lphc, |
| WINDOWPOS* posChanging) |
| { |
| /* |
| * We need to override the WM_WINDOWPOSCHANGING method to handle all |
| * the non-simple comboboxes. The problem is that those controls are |
| * always the same height. We have to make sure they are not resized |
| * to another value. |
| */ |
| if ( ( CB_GETTYPE(lphc) != CBS_SIMPLE ) && |
| ((posChanging->flags & SWP_NOSIZE) == 0) ) |
| { |
| int newComboHeight; |
| |
| newComboHeight = CBGetTextAreaHeight(hwnd,lphc) + |
| 2*COMBO_YBORDERSIZE(); |
| |
| /* |
| * Resizing a combobox has another side effect, it resizes the dropped |
| * rectangle as well. However, it does it only if the new height for the |
| * combobox is different from the height it should have. In other words, |
| * if the application resizing the combobox only had the intention to resize |
| * the actual control, for example, to do the layout of a dialog that is |
| * resized, the height of the dropdown is not changed. |
| */ |
| if (posChanging->cy != newComboHeight) |
| { |
| TRACE("posChanging->cy=%d, newComboHeight=%d, oldbot=%d, oldtop=%d\n", |
| posChanging->cy, newComboHeight, lphc->droppedRect.bottom, |
| lphc->droppedRect.top); |
| lphc->droppedRect.bottom = lphc->droppedRect.top + posChanging->cy - newComboHeight; |
| |
| posChanging->cy = newComboHeight; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /*********************************************************************** |
| * COMBO_Create |
| */ |
| static LRESULT COMBO_Create( HWND hwnd, LPHEADCOMBO lphc, HWND hwndParent, LONG style, |
| BOOL unicode ) |
| { |
| static const WCHAR clbName[] = {'C','o','m','b','o','L','B','o','x',0}; |
| static const WCHAR editName[] = {'E','d','i','t',0}; |
| |
| if( !CB_GETTYPE(lphc) ) lphc->dwStyle |= CBS_SIMPLE; |
| if( CB_GETTYPE(lphc) != CBS_DROPDOWNLIST ) lphc->wState |= CBF_EDIT; |
| |
| lphc->owner = hwndParent; |
| |
| /* |
| * The item height and dropped width are not set when the control |
| * is created. |
| */ |
| lphc->droppedWidth = lphc->editHeight = 0; |
| |
| /* |
| * The first time we go through, we want to measure the ownerdraw item |
| */ |
| lphc->wState |= CBF_MEASUREITEM; |
| |
| /* M$ IE 3.01 actually creates (and rapidly destroys) an ownerless combobox */ |
| |
| if( lphc->owner || !(style & WS_VISIBLE) ) |
| { |
| UINT lbeStyle = 0; |
| UINT lbeExStyle = 0; |
| |
| /* |
| * Initialize the dropped rect to the size of the client area of the |
| * control and then, force all the areas of the combobox to be |
| * recalculated. |
| */ |
| GetClientRect( hwnd, &lphc->droppedRect ); |
| CBCalcPlacement(hwnd, lphc, &lphc->textRect, &lphc->buttonRect, &lphc->droppedRect ); |
| |
| /* |
| * Adjust the position of the popup listbox if it's necessary |
| */ |
| if ( CB_GETTYPE(lphc) != CBS_SIMPLE ) |
| { |
| lphc->droppedRect.top = lphc->textRect.bottom + COMBO_YBORDERSIZE(); |
| |
| /* |
| * If it's a dropdown, the listbox is offset |
| */ |
| if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) |
| lphc->droppedRect.left += COMBO_EDITBUTTONSPACE(); |
| |
| ClientToScreen(hwnd, (LPPOINT)&lphc->droppedRect); |
| ClientToScreen(hwnd, (LPPOINT)&lphc->droppedRect.right); |
| } |
| |
| /* create listbox popup */ |
| |
| lbeStyle = (LBS_NOTIFY | WS_BORDER | WS_CLIPSIBLINGS | WS_CHILD) | |
| (style & (WS_VSCROLL | CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE)); |
| |
| if( lphc->dwStyle & CBS_SORT ) |
| lbeStyle |= LBS_SORT; |
| if( lphc->dwStyle & CBS_HASSTRINGS ) |
| lbeStyle |= LBS_HASSTRINGS; |
| if( lphc->dwStyle & CBS_NOINTEGRALHEIGHT ) |
| lbeStyle |= LBS_NOINTEGRALHEIGHT; |
| if( lphc->dwStyle & CBS_DISABLENOSCROLL ) |
| lbeStyle |= LBS_DISABLENOSCROLL; |
| |
| if( CB_GETTYPE(lphc) == CBS_SIMPLE ) /* child listbox */ |
| { |
| lbeStyle |= WS_VISIBLE; |
| |
| /* |
| * In win 95 look n feel, the listbox in the simple combobox has |
| * the WS_EXCLIENTEDGE style instead of the WS_BORDER style. |
| */ |
| if (TWEAK_WineLook > WIN31_LOOK) |
| { |
| lbeStyle &= ~WS_BORDER; |
| lbeExStyle |= WS_EX_CLIENTEDGE; |
| } |
| } |
| |
| if (unicode) |
| lphc->hWndLBox = CreateWindowExW(lbeExStyle, clbName, NULL, lbeStyle, |
| lphc->droppedRect.left, |
| lphc->droppedRect.top, |
| lphc->droppedRect.right - lphc->droppedRect.left, |
| lphc->droppedRect.bottom - lphc->droppedRect.top, |
| hwnd, (HMENU)ID_CB_LISTBOX, |
| GetWindowLongA( hwnd, GWL_HINSTANCE ), lphc ); |
| else |
| lphc->hWndLBox = CreateWindowExA(lbeExStyle, "ComboLBox", NULL, lbeStyle, |
| lphc->droppedRect.left, |
| lphc->droppedRect.top, |
| lphc->droppedRect.right - lphc->droppedRect.left, |
| lphc->droppedRect.bottom - lphc->droppedRect.top, |
| hwnd, (HMENU)ID_CB_LISTBOX, |
| GetWindowLongA( hwnd, GWL_HINSTANCE ), lphc ); |
| |
| if( lphc->hWndLBox ) |
| { |
| BOOL bEdit = TRUE; |
| lbeStyle = WS_CHILD | WS_VISIBLE | ES_NOHIDESEL | ES_LEFT | ES_COMBO; |
| |
| /* |
| * In Win95 look, the border fo the edit control is |
| * provided by the combobox |
| */ |
| if (TWEAK_WineLook == WIN31_LOOK) |
| lbeStyle |= WS_BORDER; |
| |
| if( lphc->wState & CBF_EDIT ) |
| { |
| if( lphc->dwStyle & CBS_OEMCONVERT ) |
| lbeStyle |= ES_OEMCONVERT; |
| if( lphc->dwStyle & CBS_AUTOHSCROLL ) |
| lbeStyle |= ES_AUTOHSCROLL; |
| if( lphc->dwStyle & CBS_LOWERCASE ) |
| lbeStyle |= ES_LOWERCASE; |
| else if( lphc->dwStyle & CBS_UPPERCASE ) |
| lbeStyle |= ES_UPPERCASE; |
| |
| if (!IsWindowEnabled(hwnd)) lbeStyle |= WS_DISABLED; |
| |
| if (unicode) |
| lphc->hWndEdit = CreateWindowExW(0, editName, NULL, lbeStyle, |
| lphc->textRect.left, lphc->textRect.top, |
| lphc->textRect.right - lphc->textRect.left, |
| lphc->textRect.bottom - lphc->textRect.top, |
| hwnd, (HMENU)ID_CB_EDIT, |
| GetWindowLongA( hwnd, GWL_HINSTANCE ), NULL ); |
| else |
| lphc->hWndEdit = CreateWindowExA(0, "Edit", NULL, lbeStyle, |
| lphc->textRect.left, lphc->textRect.top, |
| lphc->textRect.right - lphc->textRect.left, |
| lphc->textRect.bottom - lphc->textRect.top, |
| hwnd, (HMENU)ID_CB_EDIT, |
| GetWindowLongA( hwnd, GWL_HINSTANCE ), NULL ); |
| |
| if( !lphc->hWndEdit ) |
| bEdit = FALSE; |
| } |
| |
| if( bEdit ) |
| { |
| if( CB_GETTYPE(lphc) != CBS_SIMPLE ) |
| { |
| /* Now do the trick with parent */ |
| SetParent(lphc->hWndLBox, HWND_DESKTOP); |
| /* |
| * If the combo is a dropdown, we must resize the control |
| * to fit only the text area and button. To do this, |
| * we send a dummy resize and the WM_WINDOWPOSCHANGING message |
| * will take care of setting the height for us. |
| */ |
| CBForceDummyResize(lphc); |
| } |
| |
| TRACE("init done\n"); |
| return 0; |
| } |
| ERR("edit control failure.\n"); |
| } else ERR("listbox failure.\n"); |
| } else ERR("no owner for visible combo.\n"); |
| |
| /* CreateWindow() will send WM_NCDESTROY to cleanup */ |
| |
| return -1; |
| } |
| |
| /*********************************************************************** |
| * CBPaintButton |
| * |
| * Paint combo button (normal, pressed, and disabled states). |
| */ |
| static void CBPaintButton( |
| LPHEADCOMBO lphc, |
| HDC hdc, |
| RECT rectButton) |
| { |
| if( lphc->wState & CBF_NOREDRAW ) |
| return; |
| |
| if (TWEAK_WineLook == WIN31_LOOK) |
| { |
| UINT x, y; |
| BOOL bBool; |
| HDC hMemDC; |
| HBRUSH hPrevBrush; |
| COLORREF oldTextColor, oldBkColor; |
| |
| |
| hPrevBrush = SelectObject(hdc, GetSysColorBrush(COLOR_BTNFACE)); |
| |
| /* |
| * Draw the button background |
| */ |
| PatBlt( hdc, |
| rectButton.left, |
| rectButton.top, |
| rectButton.right-rectButton.left, |
| rectButton.bottom-rectButton.top, |
| PATCOPY ); |
| |
| if( (bBool = lphc->wState & CBF_BUTTONDOWN) ) |
| { |
| DrawEdge( hdc, &rectButton, EDGE_SUNKEN, BF_RECT ); |
| } |
| else |
| { |
| DrawEdge( hdc, &rectButton, EDGE_RAISED, BF_RECT ); |
| } |
| |
| /* |
| * Remove the edge of the button from the rectangle |
| * and calculate the position of the bitmap. |
| */ |
| InflateRect( &rectButton, -2, -2); |
| |
| x = (rectButton.left + rectButton.right - CBitWidth) >> 1; |
| y = (rectButton.top + rectButton.bottom - CBitHeight) >> 1; |
| |
| |
| hMemDC = CreateCompatibleDC( hdc ); |
| SelectObject( hMemDC, hComboBmp ); |
| oldTextColor = SetTextColor( hdc, GetSysColor(COLOR_BTNFACE) ); |
| oldBkColor = SetBkColor( hdc, CB_DISABLED(lphc) ? RGB(128,128,128) : |
| RGB(0,0,0) ); |
| BitBlt( hdc, x, y, CBitWidth, CBitHeight, hMemDC, 0, 0, SRCCOPY ); |
| SetBkColor( hdc, oldBkColor ); |
| SetTextColor( hdc, oldTextColor ); |
| DeleteDC( hMemDC ); |
| SelectObject( hdc, hPrevBrush ); |
| } |
| else |
| { |
| UINT buttonState = DFCS_SCROLLCOMBOBOX; |
| |
| if (lphc->wState & CBF_BUTTONDOWN) |
| { |
| buttonState |= DFCS_PUSHED; |
| } |
| |
| if (CB_DISABLED(lphc)) |
| { |
| buttonState |= DFCS_INACTIVE; |
| } |
| |
| DrawFrameControl(hdc, |
| &rectButton, |
| DFC_SCROLL, |
| buttonState); |
| } |
| } |
| |
| /*********************************************************************** |
| * CBPaintText |
| * |
| * Paint CBS_DROPDOWNLIST text field / update edit control contents. |
| */ |
| static void CBPaintText( |
| LPHEADCOMBO lphc, |
| HDC hdc, |
| RECT rectEdit) |
| { |
| INT id, size = 0; |
| LPWSTR pText = NULL; |
| |
| if( lphc->wState & CBF_NOREDRAW ) return; |
| |
| TRACE("\n"); |
| |
| /* follow Windows combobox that sends a bunch of text |
| * inquiries to its listbox while processing WM_PAINT. */ |
| |
| if( (id = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0) ) != LB_ERR ) |
| { |
| size = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, id, 0); |
| if (size == LB_ERR) |
| FIXME("LB_ERR probably not handled yet\n"); |
| if( (pText = HeapAlloc( GetProcessHeap(), 0, (size + 1) * sizeof(WCHAR))) ) |
| { |
| /* size from LB_GETTEXTLEN may be too large, from LB_GETTEXT is accurate */ |
| size=SendMessageW(lphc->hWndLBox, LB_GETTEXT, (WPARAM)id, (LPARAM)pText); |
| pText[size] = '\0'; /* just in case */ |
| } else return; |
| } |
| else |
| if( !CB_OWNERDRAWN(lphc) ) |
| return; |
| |
| if( lphc->wState & CBF_EDIT ) |
| { |
| static const WCHAR empty_stringW[] = { 0 }; |
| if( CB_HASSTRINGS(lphc) ) SetWindowTextW( lphc->hWndEdit, pText ? pText : empty_stringW ); |
| if( lphc->wState & CBF_FOCUSED ) |
| SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1)); |
| } |
| else /* paint text field ourselves */ |
| { |
| UINT itemState = ODS_COMBOBOXEDIT; |
| HFONT hPrevFont = (lphc->hFont) ? SelectObject(hdc, lphc->hFont) : 0; |
| |
| /* |
| * Give ourselves some space. |
| */ |
| InflateRect( &rectEdit, -1, -1 ); |
| |
| if( CB_OWNERDRAWN(lphc) ) |
| { |
| DRAWITEMSTRUCT dis; |
| HRGN clipRegion; |
| UINT ctlid = GetWindowLongA( lphc->self, GWL_ID ); |
| |
| /* setup state for DRAWITEM message. Owner will highlight */ |
| if ( (lphc->wState & CBF_FOCUSED) && |
| !(lphc->wState & CBF_DROPPED) ) |
| itemState |= ODS_SELECTED | ODS_FOCUS; |
| |
| /* |
| * Save the current clip region. |
| * To retrieve the clip region, we need to create one "dummy" |
| * clip region. |
| */ |
| clipRegion = CreateRectRgnIndirect(&rectEdit); |
| |
| if (GetClipRgn(hdc, clipRegion)!=1) |
| { |
| DeleteObject(clipRegion); |
| clipRegion=(HRGN)NULL; |
| } |
| |
| if (!IsWindowEnabled(lphc->self) & WS_DISABLED) itemState |= ODS_DISABLED; |
| |
| dis.CtlType = ODT_COMBOBOX; |
| dis.CtlID = ctlid; |
| dis.hwndItem = lphc->self; |
| dis.itemAction = ODA_DRAWENTIRE; |
| dis.itemID = id; |
| dis.itemState = itemState; |
| dis.hDC = hdc; |
| dis.rcItem = rectEdit; |
| dis.itemData = SendMessageW(lphc->hWndLBox, LB_GETITEMDATA, |
| (WPARAM)id, 0 ); |
| |
| /* |
| * Clip the DC and have the parent draw the item. |
| */ |
| IntersectClipRect(hdc, |
| rectEdit.left, rectEdit.top, |
| rectEdit.right, rectEdit.bottom); |
| |
| SendMessageW(lphc->owner, WM_DRAWITEM, ctlid, (LPARAM)&dis ); |
| |
| /* |
| * Reset the clipping region. |
| */ |
| SelectClipRgn(hdc, clipRegion); |
| } |
| else |
| { |
| static const WCHAR empty_stringW[] = { 0 }; |
| |
| if ( (lphc->wState & CBF_FOCUSED) && |
| !(lphc->wState & CBF_DROPPED) ) { |
| |
| /* highlight */ |
| FillRect( hdc, &rectEdit, GetSysColorBrush(COLOR_HIGHLIGHT) ); |
| SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) ); |
| SetTextColor( hdc, GetSysColor( COLOR_HIGHLIGHTTEXT ) ); |
| } |
| |
| ExtTextOutW( hdc, |
| rectEdit.left + 1, |
| rectEdit.top + 1, |
| ETO_OPAQUE | ETO_CLIPPED, |
| &rectEdit, |
| pText ? pText : empty_stringW , size, NULL ); |
| |
| if(lphc->wState & CBF_FOCUSED && !(lphc->wState & CBF_DROPPED)) |
| DrawFocusRect( hdc, &rectEdit ); |
| } |
| |
| if( hPrevFont ) |
| SelectObject(hdc, hPrevFont ); |
| } |
| if (pText) |
| HeapFree( GetProcessHeap(), 0, pText ); |
| } |
| |
| /*********************************************************************** |
| * CBPaintBorder |
| */ |
| static void CBPaintBorder( |
| HWND hwnd, |
| LPHEADCOMBO lphc, |
| HDC hdc) |
| { |
| RECT clientRect; |
| |
| if (CB_GETTYPE(lphc) != CBS_SIMPLE) |
| { |
| GetClientRect(hwnd, &clientRect); |
| } |
| else |
| { |
| CopyRect(&clientRect, &lphc->textRect); |
| |
| InflateRect(&clientRect, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING()); |
| InflateRect(&clientRect, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE()); |
| } |
| |
| DrawEdge(hdc, &clientRect, EDGE_SUNKEN, BF_RECT); |
| } |
| |
| /*********************************************************************** |
| * COMBO_PrepareColors |
| * |
| * This method will sent the appropriate WM_CTLCOLOR message to |
| * prepare and setup the colors for the combo's DC. |
| * |
| * It also returns the brush to use for the background. |
| */ |
| static HBRUSH COMBO_PrepareColors( |
| LPHEADCOMBO lphc, |
| HDC hDC) |
| { |
| HBRUSH hBkgBrush; |
| |
| /* |
| * Get the background brush for this control. |
| */ |
| if (CB_DISABLED(lphc)) |
| { |
| hBkgBrush = SendMessageW(lphc->owner, WM_CTLCOLORSTATIC, hDC, (LPARAM)lphc->self ); |
| |
| /* |
| * We have to change the text color since WM_CTLCOLORSTATIC will |
| * set it to the "enabled" color. This is the same behavior as the |
| * edit control |
| */ |
| SetTextColor(hDC, GetSysColor(COLOR_GRAYTEXT)); |
| } |
| else |
| { |
| if (lphc->wState & CBF_EDIT) |
| { |
| hBkgBrush = SendMessageW(lphc->owner, WM_CTLCOLOREDIT, hDC, (LPARAM)lphc->self ); |
| } |
| else |
| { |
| hBkgBrush = SendMessageW(lphc->owner, WM_CTLCOLORLISTBOX, hDC, (LPARAM)lphc->self ); |
| } |
| } |
| |
| /* |
| * Catch errors. |
| */ |
| if( !hBkgBrush ) |
| hBkgBrush = GetSysColorBrush(COLOR_WINDOW); |
| |
| return hBkgBrush; |
| } |
| |
| /*********************************************************************** |
| * COMBO_EraseBackground |
| */ |
| static LRESULT COMBO_EraseBackground( |
| HWND hwnd, |
| LPHEADCOMBO lphc, |
| HDC hParamDC) |
| { |
| HBRUSH hBkgBrush; |
| HDC hDC; |
| |
| if(lphc->wState & CBF_EDIT) |
| return TRUE; |
| |
| hDC = (hParamDC) ? hParamDC |
| : GetDC(hwnd); |
| /* |
| * Retrieve the background brush |
| */ |
| hBkgBrush = COMBO_PrepareColors(lphc, hDC); |
| |
| FillRect(hDC, &lphc->textRect, hBkgBrush); |
| |
| if (!hParamDC) |
| ReleaseDC(hwnd, hDC); |
| |
| return TRUE; |
| } |
| |
| /*********************************************************************** |
| * COMBO_Paint |
| */ |
| static LRESULT COMBO_Paint(LPHEADCOMBO lphc, HDC hParamDC) |
| { |
| PAINTSTRUCT ps; |
| HDC hDC; |
| |
| hDC = (hParamDC) ? hParamDC |
| : BeginPaint( lphc->self, &ps); |
| |
| TRACE("hdc=%04x\n", hDC); |
| |
| if( hDC && !(lphc->wState & CBF_NOREDRAW) ) |
| { |
| HBRUSH hPrevBrush, hBkgBrush; |
| |
| /* |
| * Retrieve the background brush and select it in the |
| * DC. |
| */ |
| hBkgBrush = COMBO_PrepareColors(lphc, hDC); |
| |
| hPrevBrush = SelectObject( hDC, hBkgBrush ); |
| |
| /* |
| * In non 3.1 look, there is a sunken border on the combobox |
| */ |
| if (TWEAK_WineLook != WIN31_LOOK) |
| { |
| CBPaintBorder(lphc->self, lphc, hDC); |
| } |
| |
| if( !IsRectEmpty(&lphc->buttonRect) ) |
| { |
| CBPaintButton(lphc, hDC, lphc->buttonRect); |
| } |
| |
| /* paint the edit control padding area */ |
| if (CB_GETTYPE(lphc) != CBS_DROPDOWNLIST) |
| { |
| RECT rPadEdit = lphc->textRect; |
| |
| InflateRect(&rPadEdit, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING()); |
| |
| FrameRect( hDC, &rPadEdit, GetSysColorBrush(COLOR_WINDOW) ); |
| } |
| |
| if( !(lphc->wState & CBF_EDIT) ) |
| { |
| /* |
| * The text area has a border only in Win 3.1 look. |
| */ |
| if (TWEAK_WineLook == WIN31_LOOK) |
| { |
| HPEN hPrevPen = SelectObject( hDC, SYSCOLOR_GetPen(COLOR_WINDOWFRAME) ); |
| |
| Rectangle( hDC, |
| lphc->textRect.left, lphc->textRect.top, |
| lphc->textRect.right - 1, lphc->textRect.bottom - 1); |
| |
| SelectObject( hDC, hPrevPen ); |
| } |
| |
| CBPaintText( lphc, hDC, lphc->textRect); |
| } |
| |
| if( hPrevBrush ) |
| SelectObject( hDC, hPrevBrush ); |
| } |
| |
| if( !hParamDC ) |
| EndPaint(lphc->self, &ps); |
| |
| return 0; |
| } |
| |
| /*********************************************************************** |
| * CBUpdateLBox |
| * |
| * Select listbox entry according to the contents of the edit control. |
| */ |
| static INT CBUpdateLBox( LPHEADCOMBO lphc, BOOL bSelect ) |
| { |
| INT length, idx; |
| LPWSTR pText = NULL; |
| |
| idx = LB_ERR; |
| length = SendMessageW( lphc->hWndEdit, WM_GETTEXTLENGTH, 0, 0 ); |
| |
| if( length > 0 ) |
| pText = HeapAlloc( GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR)); |
| |
| TRACE("\t edit text length %i\n", length ); |
| |
| if( pText ) |
| { |
| if( length ) GetWindowTextW( lphc->hWndEdit, pText, length + 1); |
| else pText[0] = '\0'; |
| idx = SendMessageW(lphc->hWndLBox, LB_FINDSTRING, |
| (WPARAM)(-1), (LPARAM)pText ); |
| HeapFree( GetProcessHeap(), 0, pText ); |
| } |
| |
| SendMessageW(lphc->hWndLBox, LB_SETCURSEL, (WPARAM)(bSelect ? idx : -1), 0); |
| |
| /* probably superfluous but Windows sends this too */ |
| SendMessageW(lphc->hWndLBox, LB_SETCARETINDEX, (WPARAM)(idx < 0 ? 0 : idx), 0); |
| SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, (WPARAM)(idx < 0 ? 0 : idx), 0); |
| |
| return idx; |
| } |
| |
| /*********************************************************************** |
| * CBUpdateEdit |
| * |
| * Copy a listbox entry to the edit control. |
| */ |
| static void CBUpdateEdit( LPHEADCOMBO lphc , INT index ) |
| { |
| INT length; |
| LPWSTR pText = NULL; |
| static const WCHAR empty_stringW[] = { 0 }; |
| |
| TRACE("\t %i\n", index ); |
| |
| if( index >= 0 ) /* got an entry */ |
| { |
| length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, (WPARAM)index, 0); |
| if( length != LB_ERR) |
| { |
| if( (pText = HeapAlloc( GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR))) ) |
| { |
| SendMessageW(lphc->hWndLBox, LB_GETTEXT, |
| (WPARAM)index, (LPARAM)pText ); |
| } |
| } |
| } |
| |
| lphc->wState |= (CBF_NOEDITNOTIFY | CBF_NOLBSELECT); |
| SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, pText ? (LPARAM)pText : (LPARAM)empty_stringW); |
| lphc->wState &= ~(CBF_NOEDITNOTIFY | CBF_NOLBSELECT); |
| |
| if( lphc->wState & CBF_FOCUSED ) |
| SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1)); |
| |
| if( pText ) |
| HeapFree( GetProcessHeap(), 0, pText ); |
| } |
| |
| /*********************************************************************** |
| * CBDropDown |
| * |
| * Show listbox popup. |
| */ |
| static void CBDropDown( LPHEADCOMBO lphc ) |
| { |
| RECT rect,r; |
| int nItems = 0; |
| int nDroppedHeight; |
| |
| TRACE("[%04x]: drop down\n", lphc->self); |
| |
| CB_NOTIFY( lphc, CBN_DROPDOWN ); |
| |
| /* set selection */ |
| |
| lphc->wState |= CBF_DROPPED; |
| if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) |
| { |
| lphc->droppedIndex = CBUpdateLBox( lphc, TRUE ); |
| |
| /* Update edit only if item is in the list */ |
| if( !(lphc->wState & CBF_CAPTURE) && lphc->droppedIndex >= 0) |
| CBUpdateEdit( lphc, lphc->droppedIndex ); |
| } |
| else |
| { |
| lphc->droppedIndex = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0); |
| |
| SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, |
| (WPARAM)(lphc->droppedIndex == LB_ERR ? 0 : lphc->droppedIndex), 0 ); |
| SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0); |
| } |
| |
| /* now set popup position */ |
| GetWindowRect( lphc->self, &rect ); |
| |
| /* |
| * If it's a dropdown, the listbox is offset |
| */ |
| if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) |
| rect.left += COMBO_EDITBUTTONSPACE(); |
| |
| /* if the dropped height is greater than the total height of the dropped |
| items list, then force the drop down list height to be the total height |
| of the items in the dropped list */ |
| |
| /* And Remove any extra space (Best Fit) */ |
| nDroppedHeight = lphc->droppedRect.bottom - lphc->droppedRect.top; |
| /* if listbox length has been set directly by its handle */ |
| GetWindowRect(lphc->hWndLBox, &r); |
| if (nDroppedHeight < r.bottom - r.top) |
| nDroppedHeight = r.bottom - r.top; |
| nItems = (int)SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0); |
| |
| if (nItems > 0) |
| { |
| int nHeight; |
| int nIHeight; |
| |
| nIHeight = (int)SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, 0, 0); |
| |
| nHeight = nIHeight*nItems; |
| |
| if (nHeight < nDroppedHeight - COMBO_YBORDERSIZE()) |
| nDroppedHeight = nHeight + COMBO_YBORDERSIZE(); |
| |
| if (nDroppedHeight < nIHeight) |
| { |
| if (nItems < 5) |
| nDroppedHeight = nHeight; |
| else |
| nDroppedHeight = 5*nIHeight; |
| } |
| } |
| |
| /*If height of dropped rectangle gets beyond a screen size it should go up, otherwise down.*/ |
| if( (rect.bottom + nDroppedHeight) >= GetSystemMetrics( SM_CYSCREEN ) ) |
| rect.bottom = rect.top - nDroppedHeight; |
| |
| SetWindowPos( lphc->hWndLBox, HWND_TOP, rect.left, rect.bottom, |
| lphc->droppedRect.right - lphc->droppedRect.left, |
| nDroppedHeight, |
| SWP_NOACTIVATE | SWP_SHOWWINDOW); |
| |
| |
| if( !(lphc->wState & CBF_NOREDRAW) ) |
| RedrawWindow( lphc->self, NULL, 0, RDW_INVALIDATE | |
| RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN ); |
| |
| EnableWindow( lphc->hWndLBox, TRUE ); |
| if (GetCapture() != lphc->self) |
| SetCapture(lphc->hWndLBox); |
| } |
| |
| /*********************************************************************** |
| * CBRollUp |
| * |
| * Hide listbox popup. |
| */ |
| static void CBRollUp( LPHEADCOMBO lphc, BOOL ok, BOOL bButton ) |
| { |
| HWND hWnd = lphc->self; |
| |
| TRACE("[%04x]: sel ok? [%i] dropped? [%i]\n", |
| lphc->self, (INT)ok, (INT)(lphc->wState & CBF_DROPPED)); |
| |
| CB_NOTIFY( lphc, (ok) ? CBN_SELENDOK : CBN_SELENDCANCEL ); |
| |
| if( IsWindow( hWnd ) && CB_GETTYPE(lphc) != CBS_SIMPLE ) |
| { |
| |
| if( lphc->wState & CBF_DROPPED ) |
| { |
| RECT rect; |
| |
| lphc->wState &= ~CBF_DROPPED; |
| ShowWindow( lphc->hWndLBox, SW_HIDE ); |
| |
| if(GetCapture() == lphc->hWndLBox) |
| { |
| ReleaseCapture(); |
| } |
| |
| if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) |
| { |
| rect = lphc->buttonRect; |
| } |
| else |
| { |
| if( bButton ) |
| { |
| UnionRect( &rect, |
| &lphc->buttonRect, |
| &lphc->textRect); |
| } |
| else |
| rect = lphc->textRect; |
| |
| bButton = TRUE; |
| } |
| |
| if( bButton && !(lphc->wState & CBF_NOREDRAW) ) |
| RedrawWindow( hWnd, &rect, 0, RDW_INVALIDATE | |
| RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN ); |
| CB_NOTIFY( lphc, CBN_CLOSEUP ); |
| } |
| } |
| } |
| |
| /*********************************************************************** |
| * COMBO_FlipListbox |
| * |
| * Used by the ComboLBox to show/hide itself in response to VK_F4, etc... |
| */ |
| BOOL COMBO_FlipListbox( LPHEADCOMBO lphc, BOOL ok, BOOL bRedrawButton ) |
| { |
| if( lphc->wState & CBF_DROPPED ) |
| { |
| CBRollUp( lphc, ok, bRedrawButton ); |
| return FALSE; |
| } |
| |
| CBDropDown( lphc ); |
| return TRUE; |
| } |
| |
| /*********************************************************************** |
| * CBRepaintButton |
| */ |
| static void CBRepaintButton( LPHEADCOMBO lphc ) |
| { |
| InvalidateRect(lphc->self, &lphc->buttonRect, TRUE); |
| UpdateWindow(lphc->self); |
| } |
| |
| /*********************************************************************** |
| * COMBO_SetFocus |
| */ |
| static void COMBO_SetFocus( LPHEADCOMBO lphc ) |
| { |
| if( !(lphc->wState & CBF_FOCUSED) ) |
| { |
| if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST ) |
| SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0); |
| |
| /* This is wrong. Message sequences seem to indicate that this |
| is set *after* the notify. */ |
| /* lphc->wState |= CBF_FOCUSED; */ |
| |
| if( !(lphc->wState & CBF_EDIT) ) |
| InvalidateRect(lphc->self, &lphc->textRect, TRUE); |
| |
| CB_NOTIFY( lphc, CBN_SETFOCUS ); |
| lphc->wState |= CBF_FOCUSED; |
| } |
| } |
| |
| /*********************************************************************** |
| * COMBO_KillFocus |
| */ |
| static void COMBO_KillFocus( LPHEADCOMBO lphc ) |
| { |
| HWND hWnd = lphc->self; |
| |
| if( lphc->wState & CBF_FOCUSED ) |
| { |
| CBRollUp( lphc, FALSE, TRUE ); |
| if( IsWindow( hWnd ) ) |
| { |
| if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST ) |
| SendMessageW(lphc->hWndLBox, LB_CARETOFF, 0, 0); |
| |
| lphc->wState &= ~CBF_FOCUSED; |
| |
| /* redraw text */ |
| if( !(lphc->wState & CBF_EDIT) ) |
| InvalidateRect(lphc->self, &lphc->textRect, TRUE); |
| |
| CB_NOTIFY( lphc, CBN_KILLFOCUS ); |
| } |
| } |
| } |
| |
| /*********************************************************************** |
| * COMBO_Command |
| */ |
| static LRESULT COMBO_Command( LPHEADCOMBO lphc, WPARAM wParam, HWND hWnd ) |
| { |
| if ( lphc->wState & CBF_EDIT && lphc->hWndEdit == hWnd ) |
| { |
| /* ">> 8" makes gcc generate jump-table instead of cmp ladder */ |
| |
| switch( HIWORD(wParam) >> 8 ) |
| { |
| case (EN_SETFOCUS >> 8): |
| |
| TRACE("[%04x]: edit [%04x] got focus\n", |
| lphc->self, lphc->hWndEdit ); |
| |
| COMBO_SetFocus( lphc ); |
| break; |
| |
| case (EN_KILLFOCUS >> 8): |
| |
| TRACE("[%04x]: edit [%04x] lost focus\n", |
| lphc->self, lphc->hWndEdit ); |
| |
| /* NOTE: it seems that Windows' edit control sends an |
| * undocumented message WM_USER + 0x1B instead of this |
| * notification (only when it happens to be a part of |
| * the combo). ?? - AK. |
| */ |
| |
| COMBO_KillFocus( lphc ); |
| break; |
| |
| |
| case (EN_CHANGE >> 8): |
| /* |
| * In some circumstances (when the selection of the combobox |
| * is changed for example) we don't wans the EN_CHANGE notification |
| * to be forwarded to the parent of the combobox. This code |
| * checks a flag that is set in these occasions and ignores the |
| * notification. |
| */ |
| if (lphc->wState & CBF_NOLBSELECT) |
| { |
| lphc->wState &= ~CBF_NOLBSELECT; |
| } |
| else |
| { |
| CBUpdateLBox( lphc, lphc->wState & CBF_DROPPED ); |
| } |
| |
| if (!(lphc->wState & CBF_NOEDITNOTIFY)) |
| CB_NOTIFY( lphc, CBN_EDITCHANGE ); |
| break; |
| |
| case (EN_UPDATE >> 8): |
| if (!(lphc->wState & CBF_NOEDITNOTIFY)) |
| CB_NOTIFY( lphc, CBN_EDITUPDATE ); |
| break; |
| |
| case (EN_ERRSPACE >> 8): |
| CB_NOTIFY( lphc, CBN_ERRSPACE ); |
| } |
| } |
| else if( lphc->hWndLBox == hWnd ) |
| { |
| switch( HIWORD(wParam) ) |
| { |
| case LBN_ERRSPACE: |
| CB_NOTIFY( lphc, CBN_ERRSPACE ); |
| break; |
| |
| case LBN_DBLCLK: |
| CB_NOTIFY( lphc, CBN_DBLCLK ); |
| break; |
| |
| case LBN_SELCHANGE: |
| case LBN_SELCANCEL: |
| |
| TRACE("[%04x]: lbox selection change [%04x]\n", |
| lphc->self, lphc->wState ); |
| |
| if( HIWORD(wParam) == LBN_SELCHANGE) |
| { |
| if( lphc->wState & CBF_EDIT ) |
| { |
| INT index = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0); |
| lphc->wState |= CBF_NOLBSELECT; |
| CBUpdateEdit( lphc, index ); |
| /* select text in edit, as Windows does */ |
| SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1)); |
| } |
| else |
| InvalidateRect(lphc->self, &lphc->textRect, TRUE); |
| } |
| |
| /* do not roll up if selection is being tracked |
| * by arrowkeys in the dropdown listbox */ |
| if( ((lphc->wState & CBF_DROPPED) && !(lphc->wState & CBF_NOROLLUP)) ) |
| { |
| CBRollUp( lphc, (HIWORD(wParam) == LBN_SELCHANGE), TRUE ); |
| } |
| else lphc->wState &= ~CBF_NOROLLUP; |
| |
| CB_NOTIFY( lphc, CBN_SELCHANGE ); |
| |
| /* fall through */ |
| |
| case LBN_SETFOCUS: |
| case LBN_KILLFOCUS: |
| /* nothing to do here since ComboLBox always resets the focus to its |
| * combo/edit counterpart */ |
| break; |
| } |
| } |
| return 0; |
| } |
| |
| /*********************************************************************** |
| * COMBO_ItemOp |
| * |
| * Fixup an ownerdrawn item operation and pass it up to the combobox owner. |
| */ |
| static LRESULT COMBO_ItemOp( LPHEADCOMBO lphc, UINT msg, LPARAM lParam ) |
| { |
| HWND hWnd = lphc->self; |
| UINT id = GetWindowLongA( hWnd, GWL_ID ); |
| |
| TRACE("[%04x]: ownerdraw op %04x\n", lphc->self, msg ); |
| |
| switch( msg ) |
| { |
| case WM_DELETEITEM: |
| { |
| DELETEITEMSTRUCT *lpIS = (DELETEITEMSTRUCT *)lParam; |
| lpIS->CtlType = ODT_COMBOBOX; |
| lpIS->CtlID = id; |
| lpIS->hwndItem = hWnd; |
| break; |
| } |
| case WM_DRAWITEM: |
| { |
| DRAWITEMSTRUCT *lpIS = (DRAWITEMSTRUCT *)lParam; |
| lpIS->CtlType = ODT_COMBOBOX; |
| lpIS->CtlID = id; |
| lpIS->hwndItem = hWnd; |
| break; |
| } |
| case WM_COMPAREITEM: |
| { |
| COMPAREITEMSTRUCT *lpIS = (COMPAREITEMSTRUCT *)lParam; |
| lpIS->CtlType = ODT_COMBOBOX; |
| lpIS->CtlID = id; |
| lpIS->hwndItem = hWnd; |
| break; |
| } |
| case WM_MEASUREITEM: |
| { |
| MEASUREITEMSTRUCT *lpIS = (MEASUREITEMSTRUCT *)lParam; |
| lpIS->CtlType = ODT_COMBOBOX; |
| lpIS->CtlID = id; |
| break; |
| } |
| } |
| return SendMessageW(lphc->owner, msg, id, lParam); |
| } |
| |
| |
| /*********************************************************************** |
| * COMBO_GetTextW |
| */ |
| static LRESULT COMBO_GetTextW( LPHEADCOMBO lphc, INT count, LPWSTR buf ) |
| { |
| INT length; |
| |
| if( lphc->wState & CBF_EDIT ) |
| return SendMessageW( lphc->hWndEdit, WM_GETTEXT, count, (LPARAM)buf ); |
| |
| /* get it from the listbox */ |
| |
| if (!count || !buf) return 0; |
| if( lphc->hWndLBox ) |
| { |
| INT idx = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0); |
| if (idx == LB_ERR) goto error; |
| length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, idx, 0 ); |
| if (length == LB_ERR) goto error; |
| |
| /* 'length' is without the terminating character */ |
| if (length >= count) |
| { |
| LPWSTR lpBuffer = HeapAlloc(GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR)); |
| if (!lpBuffer) goto error; |
| length = SendMessageW(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)lpBuffer); |
| |
| /* truncate if buffer is too short */ |
| if (length != LB_ERR) |
| { |
| lstrcpynW( buf, lpBuffer, count ); |
| length = count; |
| } |
| HeapFree( GetProcessHeap(), 0, lpBuffer ); |
| } |
| else length = SendMessageW(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)buf); |
| |
| if (length == LB_ERR) return 0; |
| return length; |
| } |
| |
| error: /* error - truncate string, return zero */ |
| buf[0] = 0; |
| return 0; |
| } |
| |
| |
| /*********************************************************************** |
| * COMBO_GetTextA |
| * |
| * NOTE! LB_GETTEXT does not count terminating \0, WM_GETTEXT does. |
| * also LB_GETTEXT might return values < 0, WM_GETTEXT doesn't. |
| */ |
| static LRESULT COMBO_GetTextA( LPHEADCOMBO lphc, INT count, LPSTR buf ) |
| { |
| INT length; |
| |
| if( lphc->wState & CBF_EDIT ) |
| return SendMessageA( lphc->hWndEdit, WM_GETTEXT, count, (LPARAM)buf ); |
| |
| /* get it from the listbox */ |
| |
| if (!count || !buf) return 0; |
| if( lphc->hWndLBox ) |
| { |
| INT idx = SendMessageA(lphc->hWndLBox, LB_GETCURSEL, 0, 0); |
| if (idx == LB_ERR) goto error; |
| length = SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, idx, 0 ); |
| if (length == LB_ERR) goto error; |
| |
| /* 'length' is without the terminating character */ |
| if (length >= count) |
| { |
| LPSTR lpBuffer = HeapAlloc(GetProcessHeap(), 0, (length + 1) ); |
| if (!lpBuffer) goto error; |
| length = SendMessageA(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)lpBuffer); |
| |
| /* truncate if buffer is too short */ |
| if (length != LB_ERR) |
| { |
| lstrcpynA( buf, lpBuffer, count ); |
| length = count; |
| } |
| HeapFree( GetProcessHeap(), 0, lpBuffer ); |
| } |
| else length = SendMessageA(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)buf); |
| |
| if (length == LB_ERR) return 0; |
| return length; |
| } |
| |
| error: /* error - truncate string, return zero */ |
| buf[0] = 0; |
| return 0; |
| } |
| |
| |
| /*********************************************************************** |
| * CBResetPos |
| * |
| * This function sets window positions according to the updated |
| * component placement struct. |
| */ |
| static void CBResetPos( |
| LPHEADCOMBO lphc, |
| LPRECT rectEdit, |
| LPRECT rectLB, |
| BOOL bRedraw) |
| { |
| BOOL bDrop = (CB_GETTYPE(lphc) != CBS_SIMPLE); |
| |
| /* NOTE: logs sometimes have WM_LBUTTONUP before a cascade of |
| * sizing messages */ |
| |
| if( lphc->wState & CBF_EDIT ) |
| SetWindowPos( lphc->hWndEdit, 0, |
| rectEdit->left, rectEdit->top, |
| rectEdit->right - rectEdit->left, |
| rectEdit->bottom - rectEdit->top, |
| SWP_NOZORDER | SWP_NOACTIVATE | ((bDrop) ? SWP_NOREDRAW : 0) ); |
| |
| SetWindowPos( lphc->hWndLBox, 0, |
| rectLB->left, rectLB->top, |
| rectLB->right - rectLB->left, |
| rectLB->bottom - rectLB->top, |
| SWP_NOACTIVATE | SWP_NOZORDER | ((bDrop) ? SWP_NOREDRAW : 0) ); |
| |
| if( bDrop ) |
| { |
| if( lphc->wState & CBF_DROPPED ) |
| { |
| lphc->wState &= ~CBF_DROPPED; |
| ShowWindow( lphc->hWndLBox, SW_HIDE ); |
| } |
| |
| if( bRedraw && !(lphc->wState & CBF_NOREDRAW) ) |
| RedrawWindow( lphc->self, NULL, 0, |
| RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW ); |
| } |
| } |
| |
| |
| /*********************************************************************** |
| * COMBO_Size |
| */ |
| static void COMBO_Size( LPHEADCOMBO lphc ) |
| { |
| CBCalcPlacement(lphc->self, |
| lphc, |
| &lphc->textRect, |
| &lphc->buttonRect, |
| &lphc->droppedRect); |
| |
| CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE ); |
| } |
| |
| |
| /*********************************************************************** |
| * COMBO_Font |
| */ |
| static void COMBO_Font( LPHEADCOMBO lphc, HFONT hFont, BOOL bRedraw ) |
| { |
| /* |
| * Set the font |
| */ |
| lphc->hFont = hFont; |
| |
| /* |
| * Propagate to owned windows. |
| */ |
| if( lphc->wState & CBF_EDIT ) |
| SendMessageW(lphc->hWndEdit, WM_SETFONT, (WPARAM)hFont, bRedraw); |
| SendMessageW(lphc->hWndLBox, WM_SETFONT, (WPARAM)hFont, bRedraw); |
| |
| /* |
| * Redo the layout of the control. |
| */ |
| if ( CB_GETTYPE(lphc) == CBS_SIMPLE) |
| { |
| CBCalcPlacement(lphc->self, |
| lphc, |
| &lphc->textRect, |
| &lphc->buttonRect, |
| &lphc->droppedRect); |
| |
| CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE ); |
| } |
| else |
| { |
| CBForceDummyResize(lphc); |
| } |
| } |
| |
| |
| /*********************************************************************** |
| * COMBO_SetItemHeight |
| */ |
| static LRESULT COMBO_SetItemHeight( LPHEADCOMBO lphc, INT index, INT height ) |
| { |
| LRESULT lRet = CB_ERR; |
| |
| if( index == -1 ) /* set text field height */ |
| { |
| if( height < 32768 ) |
| { |
| lphc->editHeight = height; |
| |
| /* |
| * Redo the layout of the control. |
| */ |
| if ( CB_GETTYPE(lphc) == CBS_SIMPLE) |
| { |
| CBCalcPlacement(lphc->self, |
| lphc, |
| &lphc->textRect, |
| &lphc->buttonRect, |
| &lphc->droppedRect); |
| |
| CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE ); |
| } |
| else |
| { |
| CBForceDummyResize(lphc); |
| } |
| |
| lRet = height; |
| } |
| } |
| else if ( CB_OWNERDRAWN(lphc) ) /* set listbox item height */ |
| lRet = SendMessageW(lphc->hWndLBox, LB_SETITEMHEIGHT, |
| (WPARAM)index, (LPARAM)height ); |
| return lRet; |
| } |
| |
| /*********************************************************************** |
| * COMBO_SelectString |
| */ |
| static LRESULT COMBO_SelectString( LPHEADCOMBO lphc, INT start, LPARAM pText, BOOL unicode ) |
| { |
| INT index = unicode ? SendMessageW(lphc->hWndLBox, LB_SELECTSTRING, (WPARAM)start, pText) : |
| SendMessageA(lphc->hWndLBox, LB_SELECTSTRING, (WPARAM)start, pText); |
| if( index >= 0 ) |
| { |
| if( lphc->wState & CBF_EDIT ) |
| CBUpdateEdit( lphc, index ); |
| else |
| { |
| InvalidateRect(lphc->self, &lphc->textRect, TRUE); |
| } |
| } |
| return (LRESULT)index; |
| } |
| |
| /*********************************************************************** |
| * COMBO_LButtonDown |
| */ |
| static void COMBO_LButtonDown( LPHEADCOMBO lphc, LPARAM lParam ) |
| { |
| POINT pt; |
| BOOL bButton; |
| HWND hWnd = lphc->self; |
| |
| pt.x = LOWORD(lParam); |
| pt.y = HIWORD(lParam); |
| bButton = PtInRect(&lphc->buttonRect, pt); |
| |
| if( (CB_GETTYPE(lphc) == CBS_DROPDOWNLIST) || |
| (bButton && (CB_GETTYPE(lphc) == CBS_DROPDOWN)) ) |
| { |
| lphc->wState |= CBF_BUTTONDOWN; |
| if( lphc->wState & CBF_DROPPED ) |
| { |
| /* got a click to cancel selection */ |
| |
| lphc->wState &= ~CBF_BUTTONDOWN; |
| CBRollUp( lphc, TRUE, FALSE ); |
| if( !IsWindow( hWnd ) ) return; |
| |
| if( lphc->wState & CBF_CAPTURE ) |
| { |
| lphc->wState &= ~CBF_CAPTURE; |
| ReleaseCapture(); |
| } |
| } |
| else |
| { |
| /* drop down the listbox and start tracking */ |
| |
| lphc->wState |= CBF_CAPTURE; |
| SetCapture( hWnd ); |
| CBDropDown( lphc ); |
| } |
| if( bButton ) CBRepaintButton( lphc ); |
| } |
| } |
| |
| /*********************************************************************** |
| * COMBO_LButtonUp |
| * |
| * Release capture and stop tracking if needed. |
| */ |
| static void COMBO_LButtonUp( LPHEADCOMBO lphc ) |
| { |
| if( lphc->wState & CBF_CAPTURE ) |
| { |
| lphc->wState &= ~CBF_CAPTURE; |
| if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) |
| { |
| INT index = CBUpdateLBox( lphc, TRUE ); |
| /* Update edit only if item is in the list */ |
| if(index >= 0) |
| { |
| lphc->wState |= CBF_NOLBSELECT; |
| CBUpdateEdit( lphc, index ); |
| lphc->wState &= ~CBF_NOLBSELECT; |
| } |
| } |
| ReleaseCapture(); |
| SetCapture(lphc->hWndLBox); |
| } |
| |
| if( lphc->wState & CBF_BUTTONDOWN ) |
| { |
| lphc->wState &= ~CBF_BUTTONDOWN; |
| CBRepaintButton( lphc ); |
| } |
| } |
| |
| /*********************************************************************** |
| * COMBO_MouseMove |
| * |
| * Two things to do - track combo button and release capture when |
| * pointer goes into the listbox. |
| */ |
| static void COMBO_MouseMove( LPHEADCOMBO lphc, WPARAM wParam, LPARAM lParam ) |
| { |
| POINT pt; |
| RECT lbRect; |
| |
| pt.x = LOWORD(lParam); |
| pt.y = HIWORD(lParam); |
| |
| if( lphc->wState & CBF_BUTTONDOWN ) |
| { |
| BOOL bButton; |
| |
| bButton = PtInRect(&lphc->buttonRect, pt); |
| |
| if( !bButton ) |
| { |
| lphc->wState &= ~CBF_BUTTONDOWN; |
| CBRepaintButton( lphc ); |
| } |
| } |
| |
| GetClientRect( lphc->hWndLBox, &lbRect ); |
| MapWindowPoints( lphc->self, lphc->hWndLBox, &pt, 1 ); |
| if( PtInRect(&lbRect, pt) ) |
| { |
| lphc->wState &= ~CBF_CAPTURE; |
| ReleaseCapture(); |
| if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) CBUpdateLBox( lphc, TRUE ); |
| |
| /* hand over pointer tracking */ |
| SendMessageW(lphc->hWndLBox, WM_LBUTTONDOWN, wParam, lParam); |
| } |
| } |
| |
| |
| /*********************************************************************** |
| * ComboWndProc_common |
| * |
| * http://www.microsoft.com/msdn/sdk/platforms/doc/sdk/win32/ctrl/src/combobox_15.htm |
| */ |
| static LRESULT ComboWndProc_common( HWND hwnd, UINT message, |
| WPARAM wParam, LPARAM lParam, BOOL unicode ) |
| { |
| LPHEADCOMBO lphc = (LPHEADCOMBO)GetWindowLongA( hwnd, 0 ); |
| |
| TRACE("[%04x]: msg %s wp %08x lp %08lx\n", |
| hwnd, SPY_GetMsgName(message, hwnd), wParam, lParam ); |
| |
| if( lphc || message == WM_NCCREATE ) |
| switch(message) |
| { |
| |
| /* System messages */ |
| |
| case WM_NCCREATE: |
| { |
| LONG style = unicode ? ((LPCREATESTRUCTW)lParam)->style : |
| ((LPCREATESTRUCTA)lParam)->style; |
| return COMBO_NCCreate(hwnd, style); |
| } |
| case WM_NCDESTROY: |
| COMBO_NCDestroy(lphc); |
| break;/* -> DefWindowProc */ |
| |
| case WM_CREATE: |
| { |
| HWND hwndParent; |
| LONG style; |
| if(unicode) |
| { |
| hwndParent = ((LPCREATESTRUCTW)lParam)->hwndParent; |
| style = ((LPCREATESTRUCTW)lParam)->style; |
| } |
| else |
| { |
| hwndParent = ((LPCREATESTRUCTA)lParam)->hwndParent; |
| style = ((LPCREATESTRUCTA)lParam)->style; |
| } |
| return COMBO_Create(hwnd, lphc, hwndParent, style, unicode); |
| } |
| |
| case WM_PRINTCLIENT: |
| if (lParam & PRF_ERASEBKGND) |
| COMBO_EraseBackground(hwnd, lphc, wParam); |
| |
| /* Fallthrough */ |
| case WM_PAINT: |
| /* wParam may contain a valid HDC! */ |
| return COMBO_Paint(lphc, wParam); |
| case WM_ERASEBKGND: |
| return COMBO_EraseBackground(hwnd, lphc, wParam); |
| case WM_GETDLGCODE: |
| { |
| LRESULT result = DLGC_WANTARROWS | DLGC_WANTCHARS; |
| if (lParam && (((LPMSG)lParam)->message == WM_KEYDOWN)) |
| { |
| int vk = (int)((LPMSG)lParam)->wParam; |
| |
| if ((vk == VK_RETURN || vk == VK_ESCAPE) && (lphc->wState & CBF_DROPPED)) |
| result |= DLGC_WANTMESSAGE; |
| } |
| return result; |
| } |
| case WM_WINDOWPOSCHANGING: |
| return COMBO_WindowPosChanging(hwnd, lphc, (LPWINDOWPOS)lParam); |
| case WM_WINDOWPOSCHANGED: |
| /* SetWindowPos can be called on a Combobox to resize its Listbox. |
| * In that case, the Combobox itself will not be resized, so we won't |
| * get a WM_SIZE. Since we still want to update the Listbox, we have to |
| * do it here. |
| */ |
| /* fall through */ |
| case WM_SIZE: |
| if( lphc->hWndLBox && |
| !(lphc->wState & CBF_NORESIZE) ) COMBO_Size( lphc ); |
| return TRUE; |
| case WM_SETFONT: |
| COMBO_Font( lphc, (HFONT16)wParam, (BOOL)lParam ); |
| return TRUE; |
| case WM_GETFONT: |
| return (LRESULT)lphc->hFont; |
| case WM_SETFOCUS: |
| if( lphc->wState & CBF_EDIT ) |
| SetFocus( lphc->hWndEdit ); |
| else |
| COMBO_SetFocus( lphc ); |
| return TRUE; |
| case WM_KILLFOCUS: |
| { |
| HWND hwndFocus = WIN_GetFullHandle( (HWND)wParam ); |
| if( !hwndFocus || |
| (hwndFocus != lphc->hWndEdit && hwndFocus != lphc->hWndLBox )) |
| COMBO_KillFocus( lphc ); |
| return TRUE; |
| } |
| case WM_COMMAND: |
| return COMBO_Command( lphc, wParam, WIN_GetFullHandle( (HWND)lParam ) ); |
| case WM_GETTEXT: |
| return unicode ? COMBO_GetTextW( lphc, wParam, (LPWSTR)lParam ) |
| : COMBO_GetTextA( lphc, wParam, (LPSTR)lParam ); |
| case WM_SETTEXT: |
| case WM_GETTEXTLENGTH: |
| case WM_CLEAR: |
| if ((message == WM_GETTEXTLENGTH) && !ISWIN31 && !(lphc->wState & CBF_EDIT)) |
| { |
| int j = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0); |
| if (j == -1) return 0; |
| return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, j, 0) : |
| SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, j, 0); |
| } |
| else if( lphc->wState & CBF_EDIT ) |
| { |
| LRESULT ret; |
| lphc->wState |= CBF_NOEDITNOTIFY; |
| ret = unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) : |
| SendMessageA(lphc->hWndEdit, message, wParam, lParam); |
| lphc->wState &= ~CBF_NOEDITNOTIFY; |
| return ret; |
| } |
| else return CB_ERR; |
| case WM_CUT: |
| case WM_PASTE: |
| case WM_COPY: |
| if( lphc->wState & CBF_EDIT ) |
| { |
| return unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) : |
| SendMessageA(lphc->hWndEdit, message, wParam, lParam); |
| } |
| else return CB_ERR; |
| |
| case WM_DRAWITEM: |
| case WM_DELETEITEM: |
| case WM_COMPAREITEM: |
| case WM_MEASUREITEM: |
| return COMBO_ItemOp(lphc, message, lParam); |
| case WM_ENABLE: |
| if( lphc->wState & CBF_EDIT ) |
| EnableWindow( lphc->hWndEdit, (BOOL)wParam ); |
| EnableWindow( lphc->hWndLBox, (BOOL)wParam ); |
| |
| /* Force the control to repaint when the enabled state changes. */ |
| InvalidateRect(lphc->self, NULL, TRUE); |
| return TRUE; |
| case WM_SETREDRAW: |
| if( wParam ) |
| lphc->wState &= ~CBF_NOREDRAW; |
| else |
| lphc->wState |= CBF_NOREDRAW; |
| |
| if( lphc->wState & CBF_EDIT ) |
| SendMessageW(lphc->hWndEdit, message, wParam, lParam); |
| SendMessageW(lphc->hWndLBox, message, wParam, lParam); |
| return 0; |
| case WM_SYSKEYDOWN: |
| if( KEYDATA_ALT & HIWORD(lParam) ) |
| if( wParam == VK_UP || wParam == VK_DOWN ) |
| COMBO_FlipListbox( lphc, FALSE, FALSE ); |
| return 0; |
| |
| case WM_CHAR: |
| case WM_KEYDOWN: |
| { |
| HWND hwndTarget; |
| |
| if ((wParam == VK_RETURN || wParam == VK_ESCAPE) && |
| (lphc->wState & CBF_DROPPED)) |
| { |
| CBRollUp( lphc, wParam == VK_RETURN, FALSE ); |
| return TRUE; |
| } |
| |
| if( lphc->wState & CBF_EDIT ) |
| hwndTarget = lphc->hWndEdit; |
| else |
| hwndTarget = lphc->hWndLBox; |
| |
| return unicode ? SendMessageW(hwndTarget, message, wParam, lParam) : |
| SendMessageA(hwndTarget, message, wParam, lParam); |
| } |
| case WM_LBUTTONDOWN: |
| if( !(lphc->wState & CBF_FOCUSED) ) SetFocus( lphc->self ); |
| if( lphc->wState & CBF_FOCUSED ) COMBO_LButtonDown( lphc, lParam ); |
| return TRUE; |
| case WM_LBUTTONUP: |
| COMBO_LButtonUp( lphc ); |
| return TRUE; |
| case WM_MOUSEMOVE: |
| if( lphc->wState & CBF_CAPTURE ) |
| COMBO_MouseMove( lphc, wParam, lParam ); |
| return TRUE; |
| |
| case WM_MOUSEWHEEL: |
| if (wParam & (MK_SHIFT | MK_CONTROL)) |
| return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) : |
| DefWindowProcA(hwnd, message, wParam, lParam); |
| if (SHIWORD(wParam) > 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_UP, 0); |
| if (SHIWORD(wParam) < 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_DOWN, 0); |
| return TRUE; |
| |
| /* Combo messages */ |
| |
| case CB_ADDSTRING16: |
| if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam); |
| /* fall through */ |
| case CB_ADDSTRING: |
| return unicode ? SendMessageW(lphc->hWndLBox, LB_ADDSTRING, 0, lParam) : |
| SendMessageA(lphc->hWndLBox, LB_ADDSTRING, 0, lParam); |
| case CB_INSERTSTRING16: |
| wParam = (INT)(INT16)wParam; |
| if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam); |
| /* fall through */ |
| case CB_INSERTSTRING: |
| return unicode ? SendMessageW(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam) : |
| SendMessageA(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam); |
| case CB_DELETESTRING16: |
| case CB_DELETESTRING: |
| return unicode ? SendMessageW(lphc->hWndLBox, LB_DELETESTRING, wParam, 0) : |
| SendMessageA(lphc->hWndLBox, LB_DELETESTRING, wParam, 0); |
| case CB_SELECTSTRING16: |
| wParam = (INT)(INT16)wParam; |
| if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam); |
| /* fall through */ |
| case CB_SELECTSTRING: |
| return COMBO_SelectString(lphc, (INT)wParam, lParam, unicode); |
| case CB_FINDSTRING16: |
| wParam = (INT)(INT16)wParam; |
| if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam); |
| /* fall through */ |
| case CB_FINDSTRING: |
| return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam) : |
| SendMessageA(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam); |
| case CB_FINDSTRINGEXACT16: |
| wParam = (INT)(INT16)wParam; |
| if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam); |
| /* fall through */ |
| case CB_FINDSTRINGEXACT: |
| return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam) : |
| SendMessageA(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam); |
| case CB_SETITEMHEIGHT16: |
| wParam = (INT)(INT16)wParam; /* signed integer */ |
| /* fall through */ |
| case CB_SETITEMHEIGHT: |
| return COMBO_SetItemHeight( lphc, (INT)wParam, (INT)lParam); |
| case CB_GETITEMHEIGHT16: |
| wParam = (INT)(INT16)wParam; |
| /* fall through */ |
| case CB_GETITEMHEIGHT: |
| if( (INT)wParam >= 0 ) /* listbox item */ |
| return SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, wParam, 0); |
| return CBGetTextAreaHeight(hwnd, lphc); |
| case CB_RESETCONTENT16: |
| case CB_RESETCONTENT: |
| SendMessageW(lphc->hWndLBox, LB_RESETCONTENT, 0, 0); |
| if( (lphc->wState & CBF_EDIT) && CB_HASSTRINGS(lphc) ) |
| { |
| static const WCHAR empty_stringW[] = { 0 }; |
| SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, (LPARAM)empty_stringW); |
| } |
| else |
| InvalidateRect(lphc->self, NULL, TRUE); |
| return TRUE; |
| case CB_INITSTORAGE: |
| return SendMessageW(lphc->hWndLBox, LB_INITSTORAGE, wParam, lParam); |
| case CB_GETHORIZONTALEXTENT: |
| return SendMessageW(lphc->hWndLBox, LB_GETHORIZONTALEXTENT, 0, 0); |
| case CB_SETHORIZONTALEXTENT: |
| return SendMessageW(lphc->hWndLBox, LB_SETHORIZONTALEXTENT, wParam, 0); |
| case CB_GETTOPINDEX: |
| return SendMessageW(lphc->hWndLBox, LB_GETTOPINDEX, 0, 0); |
| case CB_GETLOCALE: |
| return SendMessageW(lphc->hWndLBox, LB_GETLOCALE, 0, 0); |
| case CB_SETLOCALE: |
| return SendMessageW(lphc->hWndLBox, LB_SETLOCALE, wParam, 0); |
| case CB_GETDROPPEDWIDTH: |
| if( lphc->droppedWidth ) |
| return lphc->droppedWidth; |
| return lphc->droppedRect.right - lphc->droppedRect.left; |
| case CB_SETDROPPEDWIDTH: |
| if( (CB_GETTYPE(lphc) != CBS_SIMPLE) && |
| (INT)wParam < 32768 ) lphc->droppedWidth = (INT)wParam; |
| return CB_ERR; |
| case CB_GETDROPPEDCONTROLRECT16: |
| lParam = (LPARAM)MapSL(lParam); |
| if( lParam ) |
| { |
| RECT r; |
| CBGetDroppedControlRect( lphc, &r ); |
| CONV_RECT32TO16( &r, (LPRECT16)lParam ); |
| } |
| return CB_OKAY; |
| case CB_GETDROPPEDCONTROLRECT: |
| if( lParam ) CBGetDroppedControlRect(lphc, (LPRECT)lParam ); |
| return CB_OKAY; |
| case CB_GETDROPPEDSTATE16: |
| case CB_GETDROPPEDSTATE: |
| return (lphc->wState & CBF_DROPPED) ? TRUE : FALSE; |
| case CB_DIR16: |
| lParam = (LPARAM)MapSL(lParam); |
| message = LB_DIR16; |
| /* fall through */ |
| case CB_DIR: |
| if(message == CB_DIR) message = LB_DIR; |
| return unicode ? SendMessageW(lphc->hWndLBox, message, wParam, lParam) : |
| SendMessageA(lphc->hWndLBox, message, wParam, lParam); |
| |
| case CB_SHOWDROPDOWN16: |
| case CB_SHOWDROPDOWN: |
| if( CB_GETTYPE(lphc) != CBS_SIMPLE ) |
| { |
| if( wParam ) |
| { |
| if( !(lphc->wState & CBF_DROPPED) ) |
| CBDropDown( lphc ); |
| } |
| else |
| if( lphc->wState & CBF_DROPPED ) |
| CBRollUp( lphc, FALSE, TRUE ); |
| } |
| return TRUE; |
| case CB_GETCOUNT16: |
| case CB_GETCOUNT: |
| return SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0); |
| case CB_GETCURSEL16: |
| case CB_GETCURSEL: |
| return SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0); |
| case CB_SETCURSEL16: |
| wParam = (INT)(INT16)wParam; |
| /* fall through */ |
| case CB_SETCURSEL: |
| lParam = SendMessageW(lphc->hWndLBox, LB_SETCURSEL, wParam, 0); |
| if( lParam >= 0 ) |
| SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, wParam, 0); |
| |
| /* no LBN_SELCHANGE in this case, update manually */ |
| if( lphc->wState & CBF_EDIT ) |
| CBUpdateEdit( lphc, (INT)wParam ); |
| else |
| InvalidateRect(lphc->self, &lphc->textRect, TRUE); |
| lphc->wState &= ~CBF_SELCHANGE; |
| return lParam; |
| case CB_GETLBTEXT16: |
| wParam = (INT)(INT16)wParam; |
| lParam = (LPARAM)MapSL(lParam); |
| /* fall through */ |
| case CB_GETLBTEXT: |
| return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXT, wParam, lParam) : |
| SendMessageA(lphc->hWndLBox, LB_GETTEXT, wParam, lParam); |
| case CB_GETLBTEXTLEN16: |
| wParam = (INT)(INT16)wParam; |
| /* fall through */ |
| case CB_GETLBTEXTLEN: |
| return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0) : |
| SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0); |
| case CB_GETITEMDATA16: |
| wParam = (INT)(INT16)wParam; |
| /* fall through */ |
| case CB_GETITEMDATA: |
| return SendMessageW(lphc->hWndLBox, LB_GETITEMDATA, wParam, 0); |
| case CB_SETITEMDATA16: |
| wParam = (INT)(INT16)wParam; |
| /* fall through */ |
| case CB_SETITEMDATA: |
| return SendMessageW(lphc->hWndLBox, LB_SETITEMDATA, wParam, lParam); |
| case CB_GETEDITSEL16: |
| wParam = lParam = 0; /* just in case */ |
| /* fall through */ |
| case CB_GETEDITSEL: |
| /* Edit checks passed parameters itself */ |
| if( lphc->wState & CBF_EDIT ) |
| return SendMessageW(lphc->hWndEdit, EM_GETSEL, wParam, lParam); |
| return CB_ERR; |
| case CB_SETEDITSEL16: |
| case CB_SETEDITSEL: |
| if( lphc->wState & CBF_EDIT ) |
| return SendMessageW(lphc->hWndEdit, EM_SETSEL, |
| (INT)(INT16)LOWORD(lParam), (INT)(INT16)HIWORD(lParam) ); |
| return CB_ERR; |
| case CB_SETEXTENDEDUI16: |
| case CB_SETEXTENDEDUI: |
| if( CB_GETTYPE(lphc) == CBS_SIMPLE ) |
| return CB_ERR; |
| if( wParam ) |
| lphc->wState |= CBF_EUI; |
| else lphc->wState &= ~CBF_EUI; |
| return CB_OKAY; |
| case CB_GETEXTENDEDUI16: |
| case CB_GETEXTENDEDUI: |
| return (lphc->wState & CBF_EUI) ? TRUE : FALSE; |
| |
| default: |
| if (message >= WM_USER) |
| WARN("unknown msg WM_USER+%04x wp=%04x lp=%08lx\n", |
| message - WM_USER, wParam, lParam ); |
| break; |
| } |
| return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) : |
| DefWindowProcA(hwnd, message, wParam, lParam); |
| } |
| |
| /*********************************************************************** |
| * ComboWndProcA |
| * |
| * This is just a wrapper for the real ComboWndProc which locks/unlocks |
| * window structs. |
| */ |
| static LRESULT WINAPI ComboWndProcA( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) |
| { |
| if (!IsWindow(hwnd)) return 0; |
| return ComboWndProc_common( hwnd, message, wParam, lParam, FALSE ); |
| } |
| |
| /*********************************************************************** |
| * ComboWndProcW |
| */ |
| static LRESULT WINAPI ComboWndProcW( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) |
| { |
| if (!IsWindow(hwnd)) return 0; |
| return ComboWndProc_common( hwnd, message, wParam, lParam, TRUE ); |
| } |
| |
| /************************************************************************* |
| * GetComboBoxInfo (USER32.@) |
| */ |
| BOOL WINAPI GetComboBoxInfo(HWND hwndCombo, /* [in] handle to combo box */ |
| PCOMBOBOXINFO pcbi /* [in/out] combo box information */) |
| { |
| FIXME("\n"); |
| return FALSE; |
| |
| } |