///////////////////////////////////////////////////////////////////////////// // Name: src/msw/bmpcbox.cpp // Purpose: wxBitmapComboBox // Author: Jaakko Salli // Created: 2008-04-06 // Copyright: (c) 2008 Jaakko Salli // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// // ============================================================================ // declarations // ============================================================================ // ---------------------------------------------------------------------------- // headers // ---------------------------------------------------------------------------- #include "wx/wxprec.h" #ifdef __BORLANDC__ #pragma hdrstop #endif #if wxUSE_BITMAPCOMBOBOX #include "wx/bmpcbox.h" #ifndef WX_PRECOMP #include "wx/log.h" #endif #include "wx/settings.h" #include "wx/vector.h" #include "wx/msw/dcclient.h" #include "wx/msw/private.h" // For wxODCB_XXX flags #include "wx/odcombo.h" #define IMAGE_SPACING_CTRL_VERTICAL 7 // Spacing used in control size calculation // ============================================================================ // implementation // ============================================================================ wxBEGIN_EVENT_TABLE(wxBitmapComboBox, wxComboBox) EVT_SIZE(wxBitmapComboBox::OnSize) wxEND_EVENT_TABLE() wxIMPLEMENT_DYNAMIC_CLASS(wxBitmapComboBox, wxComboBox); // ---------------------------------------------------------------------------- // wxBitmapComboBox creation // ---------------------------------------------------------------------------- void wxBitmapComboBox::Init() { m_inResize = false; } wxBitmapComboBox::wxBitmapComboBox(wxWindow *parent, wxWindowID id, const wxString& value, const wxPoint& pos, const wxSize& size, const wxArrayString& choices, long style, const wxValidator& validator, const wxString& name) : wxComboBox(), wxBitmapComboBoxBase() { Init(); Create(parent,id,value,pos,size,choices,style,validator,name); } bool wxBitmapComboBox::Create(wxWindow *parent, wxWindowID id, const wxString& value, const wxPoint& pos, const wxSize& size, const wxArrayString& choices, long style, const wxValidator& validator, const wxString& name) { wxCArrayString chs(choices); return Create(parent, id, value, pos, size, chs.GetCount(), chs.GetStrings(), style, validator, name); } bool wxBitmapComboBox::Create(wxWindow *parent, wxWindowID id, const wxString& value, const wxPoint& pos, const wxSize& size, int n, const wxString choices[], long style, const wxValidator& validator, const wxString& name) { if ( !wxComboBox::Create(parent, id, value, pos, size, n, choices, style, validator, name) ) return false; UpdateInternals(); return true; } WXDWORD wxBitmapComboBox::MSWGetStyle(long style, WXDWORD *exstyle) const { return wxComboBox::MSWGetStyle(style, exstyle) | CBS_OWNERDRAWFIXED | CBS_HASSTRINGS; } void wxBitmapComboBox::RecreateControl() { // // Recreate control so that WM_MEASUREITEM gets called again. // Can't use CBS_OWNERDRAWVARIABLE because it has odd // mouse-wheel behaviour. // wxString value = GetValue(); wxPoint pos = GetPosition(); wxSize size = GetSize(); size.y = GetBestSize().y; const wxArrayString strings = GetStrings(); const unsigned numItems = strings.size(); unsigned i; // Save the client data pointers before clearing the control, if any. const wxClientDataType clientDataType = GetClientDataType(); wxVector objectClientData; wxVector voidClientData; switch ( clientDataType ) { case wxClientData_None: break; case wxClientData_Object: objectClientData.reserve(numItems); for ( i = 0; i < numItems; ++i ) objectClientData.push_back(GetClientObject(i)); break; case wxClientData_Void: voidClientData.reserve(numItems); for ( i = 0; i < numItems; ++i ) voidClientData.push_back(GetClientData(i)); break; } wxComboBox::DoClear(); HWND hwnd = GetHwnd(); DissociateHandle(); ::DestroyWindow(hwnd); if ( !MSWCreateControl(wxT("COMBOBOX"), wxEmptyString, pos, size) ) return; // initialize the controls contents for ( i = 0; i < numItems; i++ ) { wxComboBox::Append(strings[i]); if ( !objectClientData.empty() ) SetClientObject(i, objectClientData[i]); else if ( !voidClientData.empty() ) SetClientData(i, voidClientData[i]); } // and make sure it has the same attributes as before if ( m_hasFont ) { // calling SetFont(m_font) would do nothing as the code would // notice that the font didn't change, so force it to believe // that it did wxFont font = m_font; m_font = wxNullFont; SetFont(font); } if ( m_hasFgCol ) { wxColour colFg = m_foregroundColour; m_foregroundColour = wxNullColour; SetForegroundColour(colFg); } if ( m_hasBgCol ) { wxColour colBg = m_backgroundColour; m_backgroundColour = wxNullColour; SetBackgroundColour(colBg); } else { SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); } ::SendMessage(GetHwnd(), CB_SETITEMHEIGHT, 0, MeasureItem(0)); // Revert the old string value if ( !HasFlag(wxCB_READONLY) ) ChangeValue(value); } wxBitmapComboBox::~wxBitmapComboBox() { Clear(); } wxSize wxBitmapComboBox::DoGetBestSize() const { wxSize best = wxComboBox::DoGetBestSize(); wxSize bitmapSize = GetBitmapSize(); wxCoord useHeightBitmap = EDIT_HEIGHT_FROM_CHAR_HEIGHT(bitmapSize.y); if ( best.y < useHeightBitmap ) { best.y = useHeightBitmap; CacheBestSize(best); } return best; } // ---------------------------------------------------------------------------- // Item manipulation // ---------------------------------------------------------------------------- void wxBitmapComboBox::SetItemBitmap(unsigned int n, const wxBitmap& bitmap) { OnAddBitmap(bitmap); DoSetItemBitmap(n, bitmap); if ( (int)n == GetSelection() ) Refresh(); } int wxBitmapComboBox::Append(const wxString& item, const wxBitmap& bitmap) { OnAddBitmap(bitmap); const int n = wxComboBox::Append(item); if ( n != wxNOT_FOUND ) DoSetItemBitmap(n, bitmap); return n; } int wxBitmapComboBox::Append(const wxString& item, const wxBitmap& bitmap, void *clientData) { OnAddBitmap(bitmap); const int n = wxComboBox::Append(item, clientData); if ( n != wxNOT_FOUND ) DoSetItemBitmap(n, bitmap); return n; } int wxBitmapComboBox::Append(const wxString& item, const wxBitmap& bitmap, wxClientData *clientData) { OnAddBitmap(bitmap); const int n = wxComboBox::Append(item, clientData); if ( n != wxNOT_FOUND ) DoSetItemBitmap(n, bitmap); return n; } int wxBitmapComboBox::Insert(const wxString& item, const wxBitmap& bitmap, unsigned int pos) { OnAddBitmap(bitmap); const int n = wxComboBox::Insert(item, pos); if ( n != wxNOT_FOUND ) DoSetItemBitmap(n, bitmap); return n; } int wxBitmapComboBox::Insert(const wxString& item, const wxBitmap& bitmap, unsigned int pos, void *clientData) { OnAddBitmap(bitmap); const int n = wxComboBox::Insert(item, pos, clientData); if ( n != wxNOT_FOUND ) DoSetItemBitmap(n, bitmap); return n; } int wxBitmapComboBox::Insert(const wxString& item, const wxBitmap& bitmap, unsigned int pos, wxClientData *clientData) { OnAddBitmap(bitmap); const int n = wxComboBox::Insert(item, pos, clientData); if ( n != wxNOT_FOUND ) DoSetItemBitmap(n, bitmap); return n; } int wxBitmapComboBox::DoInsertItems(const wxArrayStringsAdapter & items, unsigned int pos, void **clientData, wxClientDataType type) { const unsigned int numItems = items.GetCount(); int index; if ( HasFlag(wxCB_SORT) ) { // Since we don't know at what positions new elements will be actually inserted // we need to add them one by one, check for each one the position it was added at // and reserve the slot for corresponding bitmap at the same postion in the bitmap array. index = pos; for ( unsigned int i = 0; i < numItems; i++ ) { if ( clientData ) index = wxComboBox::DoInsertItems(items[i], pos+i, clientData+i, type); else index = wxComboBox::DoInsertItems(items[i], pos+i, NULL, wxClientData_None); wxASSERT_MSG( index != wxNOT_FOUND, wxS("Invalid wxBitmapComboBox state") ); if ( index == wxNOT_FOUND ) { continue; } // Update the bitmap array. if ( GetCount() > m_bitmaps.Count() ) { wxASSERT_MSG( GetCount() == m_bitmaps.Count() + 1, wxS("Invalid wxBitmapComboBox state") ); // Control is in the normal state. // New item has been just added. // Insert bitmap at the given index into the array. wxASSERT_MSG( (size_t)index <= m_bitmaps.Count(), wxS("wxBitmapComboBox item index out of bound") ); m_bitmaps.Insert(new wxBitmap(wxNullBitmap), index); } else { // No. of items after insertion <= No. bitmaps: // (This can happen if control is e.g. recreated with RecreateControl). // In this case existing bitmaps are reused. // Required and actual indices should be the same to assure // consistency between list of items and bitmap array. wxASSERT_MSG( (size_t)index < m_bitmaps.Count(), wxS("wxBitmapComboBox item index out of bound") ); wxASSERT_MSG( (unsigned int)index == pos+i, wxS("Invalid index for wxBitmapComboBox item") ); } } } else { const unsigned int countNew = GetCount() + numItems; m_bitmaps.Alloc(countNew); for ( unsigned int i = 0; i < numItems; i++ ) { m_bitmaps.Insert(new wxBitmap(wxNullBitmap), pos + i); } index = wxComboBox::DoInsertItems(items, pos, clientData, type); // This returns index of the last item in the inserted block. if ( index == wxNOT_FOUND ) { for ( int i = numItems-1; i >= 0; i-- ) BCBDoDeleteOneItem(pos + i); } else { // Index of the last inserted item should be consistent // with required position and number of items. wxASSERT_MSG( (unsigned int)index == pos+numItems-1, wxS("Invalid index for wxBitmapComboBox item") ); } } return index; } bool wxBitmapComboBox::OnAddBitmap(const wxBitmap& bitmap) { if ( wxBitmapComboBoxBase::OnAddBitmap(bitmap) ) { // Need to recreate control for a new measureitem call? int prevItemHeight = ::SendMessage(GetHwnd(), CB_GETITEMHEIGHT, 0, 0); if ( prevItemHeight != MeasureItem(0) ) RecreateControl(); return true; } return false; } void wxBitmapComboBox::DoClear() { wxComboBox::DoClear(); wxBitmapComboBoxBase::BCBDoClear(); } void wxBitmapComboBox::DoDeleteOneItem(unsigned int n) { wxComboBox::DoDeleteOneItem(n); wxBitmapComboBoxBase::BCBDoDeleteOneItem(n); } // ---------------------------------------------------------------------------- // wxBitmapComboBox event handlers and such // ---------------------------------------------------------------------------- void wxBitmapComboBox::OnSize(wxSizeEvent& event) { // Prevent infinite looping if ( !m_inResize ) { m_inResize = true; DetermineIndent(); m_inResize = false; } event.Skip(); } // ---------------------------------------------------------------------------- // wxBitmapComboBox miscellaneous // ---------------------------------------------------------------------------- bool wxBitmapComboBox::SetFont(const wxFont& font) { bool res = wxComboBox::SetFont(font); UpdateInternals(); return res; } // ---------------------------------------------------------------------------- // wxBitmapComboBox item drawing and measuring // ---------------------------------------------------------------------------- bool wxBitmapComboBox::MSWOnDraw(WXDRAWITEMSTRUCT *item) { LPDRAWITEMSTRUCT lpDrawItem = (LPDRAWITEMSTRUCT) item; int pos = lpDrawItem->itemID; // Draw default for item -1, which means 'focus rect only' if ( pos == -1 ) return false; int flags = 0; if ( lpDrawItem->itemState & ODS_COMBOBOXEDIT ) flags |= wxODCB_PAINTING_CONTROL; if ( lpDrawItem->itemState & ODS_SELECTED ) flags |= wxODCB_PAINTING_SELECTED; wxString text; if ( flags & wxODCB_PAINTING_CONTROL ) { text = GetValue(); if ( !HasFlag(wxCB_READONLY) ) text.clear(); } else { text = GetString(pos); } wxPaintDCEx dc(this, lpDrawItem->hDC); wxRect rect = wxRectFromRECT(lpDrawItem->rcItem); wxBitmapComboBoxBase::DrawBackground(dc, rect, pos, flags); wxBitmapComboBoxBase::DrawItem(dc, rect, pos, text, flags); // If the item has the focus, draw focus rectangle. // Commented out since regular combo box doesn't // seem to do it either. //if ( lpDrawItem->itemState & ODS_FOCUS ) // DrawFocusRect(lpDrawItem->hDC, &lpDrawItem->rcItem); return true; } bool wxBitmapComboBox::MSWOnMeasure(WXMEASUREITEMSTRUCT *item) { LPMEASUREITEMSTRUCT lpMeasureItem = (LPMEASUREITEMSTRUCT) item; int pos = lpMeasureItem->itemID; // Measure edit field height if item list is not empty, // otherwise leave default system value. if ( m_usedImgSize.y >= 0 || pos >= 0 ) { lpMeasureItem->itemHeight = wxBitmapComboBoxBase::MeasureItem(pos); } return true; } #endif // wxUSE_BITMAPCOMBOBOX