///////////////////////////////////////////////////////////////////////////// // Name: odcombo.cpp // Purpose: wxPGOwnerDrawnComboBox and related classes implementation // Author: Jaakko Salli // Modified by: // Created: Jan-25-2005 // RCS-ID: $Id: // Copyright: (c) 2005 Jaakko Salli // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// // ============================================================================ // declarations // ============================================================================ // ---------------------------------------------------------------------------- // headers // ---------------------------------------------------------------------------- #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) #pragma implementation "odcombo.h" #endif #include "wx/wxprec.h" #ifdef __BORLANDC__ #pragma hdrstop #endif #if wxUSE_COMBOBOX #ifndef WX_PRECOMP #include "wx/app.h" #include "wx/log.h" #include "wx/button.h" #include "wx/combobox.h" #include "wx/textctrl.h" #include "wx/dcclient.h" #include "wx/settings.h" #include "wx/dialog.h" #endif #include "wx/dcbuffer.h" #include "wx/tooltip.h" #include "wx/timer.h" #if defined(__WXMSW__) && !defined(__WXUNIVERSAL__) #include "wx/msw/uxtheme.h" #endif #include "wx/propgrid/odcombo.h" // // THESE GO TO BASE FILE // #define BMP_BUTTON_MARGIN 4 //#define DEFAULT_POPUP_HEIGHT -1 #define DEFAULT_POPUP_HEIGHT 300 #define DEFAULT_TEXT_INDENT 3 #if defined(__WXMSW__) #define ALLOW_FAKE_POPUP 0 // Use only on plats with problems with wxPopupWindow #define USE_TRANSIENT_POPUP 1 // Use wxPopupWindowTransient (preferred, if it works properly on platform) //#undef wxUSE_POPUPWIN //#define wxUSE_POPUPWIN 0 #elif defined(__WXGTK__) // Fake popup windows cause focus problems on GTK2 (but enable on GTK1.2, just in case) #if defined(__WXGTK20__) #define ALLOW_FAKE_POPUP 0 // Use only on plats with problems with wxPopupWindow #else #define ALLOW_FAKE_POPUP 1 // Use only on plats with problems with wxPopupWindow #endif #define USE_TRANSIENT_POPUP 1 // Use wxPopupWindowTransient (preferred, if it works properly on platform) #elif defined(__WXMAC__) #define ALLOW_FAKE_POPUP 1 // Use only on plats with problems with wxPopupWindow #define USE_TRANSIENT_POPUP 0 // Use wxPopupWindowTransient (preferred, if it works properly on platform) #else #define ALLOW_FAKE_POPUP 1 // Use only on plats with problems with wxPopupWindow #define USE_TRANSIENT_POPUP 0 // Use wxPopupWindowTransient (preferred, if it works properly on platform) #endif // Popupwin is really only supported on wxMSW (not WINCE) and wxGTK, regardless // what the wxUSE_POPUPWIN says. #if (!defined(__WXMSW__) && !defined(__WXGTK__)) || defined(__WXWINCE__) #undef wxUSE_POPUPWIN #define wxUSE_POPUPWIN 0 #endif #if wxUSE_POPUPWIN #include "wx/popupwin.h" #else #undef USE_TRANSIENT_POPUP #define USE_TRANSIENT_POPUP 0 #endif // For versions < 2.6.2, don't enable transient popup. There may be // problems I don't have time to test properly. #if !wxCHECK_VERSION(2, 6, 2) #undef USE_TRANSIENT_POPUP #define USE_TRANSIENT_POPUP 0 #endif #if USE_TRANSIENT_POPUP #undef ALLOW_FAKE_POPUP #define ALLOW_FAKE_POPUP 0 #define wxPGComboPopupWindowBase wxPopupTransientWindow #define INSTALL_TOPLEV_HANDLER 0 #elif wxUSE_POPUPWIN #define wxPGComboPopupWindowBase wxPopupWindow #define INSTALL_TOPLEV_HANDLER 1 #else #define wxPGComboPopupWindowBase wxDialog #if !ALLOW_FAKE_POPUP #define INSTALL_TOPLEV_HANDLER 0 // Doesn't need since can monitor active event #else #define INSTALL_TOPLEV_HANDLER 1 #endif #endif // // THESE GO TO GENERIC FILE // // Some adjustments to make the generic more bearable #if defined(__WXUNIVERSAL__) #define TEXTCTRLXADJUST 0 // position adjustment for wxTextCtrl, with zero indent #define TEXTCTRLYADJUST 0 #define TEXTXADJUST 0 // how much is read-only text's x adjusted #define DEFAULT_DROPBUTTON_WIDTH 19 #elif defined(__WXMSW__) #define TEXTCTRLXADJUST 2 // position adjustment for wxTextCtrl, with zero indent #define TEXTCTRLYADJUST 3 #define TEXTXADJUST 0 // how much is read-only text's x adjusted #define DEFAULT_DROPBUTTON_WIDTH 17 #elif defined(__WXGTK__) #define TEXTCTRLXADJUST -1 // position adjustment for wxTextCtrl, with zero indent #define TEXTCTRLYADJUST 0 #define TEXTXADJUST 1 // how much is read-only text's x adjusted #define DEFAULT_DROPBUTTON_WIDTH 23 #elif defined(__WXMAC__) #define TEXTCTRLXADJUST 0 // position adjustment for wxTextCtrl, with zero indent #define TEXTCTRLYADJUST 0 #define TEXTXADJUST 0 // how much is read-only text's x adjusted #define DEFAULT_DROPBUTTON_WIDTH 19 #else #define TEXTCTRLXADJUST 0 // position adjustment for wxTextCtrl, with zero indent #define TEXTCTRLYADJUST 0 #define TEXTXADJUST 0 // how much is read-only text's x adjusted #define DEFAULT_DROPBUTTON_WIDTH 19 #endif // constants // ---------------------------------------------------------------------------- // TO BASE // the margin between the text control and the combo button static const wxCoord g_comboMargin = 2; // ---------------------------------------------------------------------------- // wxPGComboFrameEventHandler takes care of hiding the popup when events happen // in its top level parent. // ---------------------------------------------------------------------------- #if INSTALL_TOPLEV_HANDLER // // This will no longer be necessary after wxTransientPopupWindow // works well on all platforms. // class wxPGComboFrameEventHandler : public wxEvtHandler { public: wxPGComboFrameEventHandler( wxPGComboControlBase* pCb ); ~wxPGComboFrameEventHandler(); void OnPopup(); void OnIdle( wxIdleEvent& event ); void OnMouseEvent( wxMouseEvent& event ); void OnActivate( wxActivateEvent& event ); void OnResize( wxSizeEvent& event ); void OnMove( wxMoveEvent& event ); void OnMenuEvent( wxMenuEvent& event ); void OnClose( wxCloseEvent& event ); protected: wxWindow* m_focusStart; wxPGComboControlBase* m_combo; private: DECLARE_EVENT_TABLE() }; BEGIN_EVENT_TABLE(wxPGComboFrameEventHandler, wxEvtHandler) EVT_IDLE(wxPGComboFrameEventHandler::OnIdle) EVT_LEFT_DOWN(wxPGComboFrameEventHandler::OnMouseEvent) EVT_RIGHT_DOWN(wxPGComboFrameEventHandler::OnMouseEvent) EVT_SIZE(wxPGComboFrameEventHandler::OnResize) EVT_MOVE(wxPGComboFrameEventHandler::OnMove) EVT_MENU_HIGHLIGHT(wxID_ANY,wxPGComboFrameEventHandler::OnMenuEvent) EVT_MENU_OPEN(wxPGComboFrameEventHandler::OnMenuEvent) EVT_ACTIVATE(wxPGComboFrameEventHandler::OnActivate) EVT_CLOSE(wxPGComboFrameEventHandler::OnClose) END_EVENT_TABLE() wxPGComboFrameEventHandler::wxPGComboFrameEventHandler( wxPGComboControlBase* combo ) : wxEvtHandler() { m_combo = combo; } wxPGComboFrameEventHandler::~wxPGComboFrameEventHandler() { } void wxPGComboFrameEventHandler::OnPopup() { m_focusStart = ::wxWindow::FindFocus(); } void wxPGComboFrameEventHandler::OnIdle( wxIdleEvent& event ) { wxWindow* winFocused = ::wxWindow::FindFocus(); wxWindow* popup = m_combo->GetPopupControl(); wxWindow* winpopup = m_combo->GetPopupWindow(); if ( winFocused != m_focusStart && winFocused != popup && winFocused->GetParent() != popup && winFocused != winpopup && winFocused->GetParent() != winpopup && winFocused != m_combo && winFocused != m_combo->GetButton() // GTK (atleast) requires this ) { m_combo->HidePopup(); } event.Skip(); } void wxPGComboFrameEventHandler::OnMenuEvent( wxMenuEvent& event ) { m_combo->HidePopup(); event.Skip(); } void wxPGComboFrameEventHandler::OnMouseEvent( wxMouseEvent& event ) { m_combo->HidePopup(); event.Skip(); } void wxPGComboFrameEventHandler::OnClose( wxCloseEvent& event ) { m_combo->HidePopup(); event.Skip(); } void wxPGComboFrameEventHandler::OnActivate( wxActivateEvent& event ) { m_combo->HidePopup(); event.Skip(); } void wxPGComboFrameEventHandler::OnResize( wxSizeEvent& event ) { m_combo->HidePopup(); event.Skip(); } void wxPGComboFrameEventHandler::OnMove( wxMoveEvent& event ) { m_combo->HidePopup(); event.Skip(); } #endif // INSTALL_TOPLEV_HANDLER // ---------------------------------------------------------------------------- // wxPGComboPopupWindow is wxPopupWindow customized for // wxComboControl. // ---------------------------------------------------------------------------- class wxPGComboPopupWindow : public wxPGComboPopupWindowBase { public: wxPGComboPopupWindow( wxPGComboControlBase *parent, int style = wxBORDER_NONE ); #if USE_TRANSIENT_POPUP virtual bool ProcessLeftDown(wxMouseEvent& event); #endif void OnKeyEvent(wxKeyEvent& event); void OnMouseEvent( wxMouseEvent& event ); #if !wxUSE_POPUPWIN void OnActivate( wxActivateEvent& event ); #endif protected: #if USE_TRANSIENT_POPUP virtual void OnDismiss(); #endif private: DECLARE_EVENT_TABLE() }; BEGIN_EVENT_TABLE(wxPGComboPopupWindow, wxPGComboPopupWindowBase) EVT_MOUSE_EVENTS(wxPGComboPopupWindow::OnMouseEvent) #if !wxUSE_POPUPWIN EVT_ACTIVATE(wxPGComboPopupWindow::OnActivate) #endif EVT_KEY_DOWN(wxPGComboPopupWindow::OnKeyEvent) EVT_KEY_UP(wxPGComboPopupWindow::OnKeyEvent) END_EVENT_TABLE() wxPGComboPopupWindow::wxPGComboPopupWindow( wxPGComboControlBase *parent, int style ) #if wxUSE_POPUPWIN : wxPGComboPopupWindowBase(parent,style) #else : wxPGComboPopupWindowBase(parent, wxID_ANY, wxEmptyString, wxPoint(-21,-21), wxSize(20,20), style) #endif { } void wxPGComboPopupWindow::OnKeyEvent( wxKeyEvent& event ) { // Relay keyboard event to the main child controls // (just skipping may just cause the popup to close) wxWindowList children = GetChildren(); wxWindowList::iterator node = children.begin(); wxWindow* child = (wxWindow*)*node; child->AddPendingEvent(event); } void wxPGComboPopupWindow::OnMouseEvent( wxMouseEvent& event ) { event.Skip(); } #if !wxUSE_POPUPWIN void wxPGComboPopupWindow::OnActivate( wxActivateEvent& event ) { if ( !event.GetActive() ) { // Tell combo control that we are dismissed. wxPGComboControl* combo = (wxPGComboControl*) GetParent(); wxASSERT( combo ); wxASSERT( combo->IsKindOf(CLASSINFO(wxPGComboControl)) ); combo->HidePopup(); event.Skip(); } } #endif #if USE_TRANSIENT_POPUP bool wxPGComboPopupWindow::ProcessLeftDown(wxMouseEvent& event ) { return wxPGComboPopupWindowBase::ProcessLeftDown(event); } #endif #if USE_TRANSIENT_POPUP // First thing that happens when a transient popup closes is that this method gets called. void wxPGComboPopupWindow::OnDismiss() { wxPGComboControlBase* combo = (wxPGComboControlBase*) GetParent(); wxASSERT ( combo->IsKindOf(CLASSINFO(wxPGComboControlBase)) ); combo->OnPopupDismiss(); } #endif // ---------------------------------------------------------------------------- // wxPGComboPopup methods // // ---------------------------------------------------------------------------- wxPGComboPopup::~wxPGComboPopup() { } void wxPGComboPopup::OnPopup() { } void wxPGComboPopup::OnDismiss() { } wxSize wxPGComboPopup::GetAdjustedSize( int minWidth, int prefHeight, int WXUNUSED(maxHeight) ) { return wxSize(minWidth,prefHeight); } void wxPGComboPopup::PaintComboControl( wxDC& dc, const wxRect& rect ) { if ( m_combo->GetWindowStyle() & wxCB_READONLY ) // ie. no textctrl { m_combo->DrawFocusBackground(dc,rect,0); dc.DrawText( GetStringValue(), rect.x + m_combo->GetTextIndent(), (rect.height-dc.GetCharHeight())/2 + m_combo->m_widthCustomBorder ); } } void wxPGComboPopup::OnComboKeyEvent( wxKeyEvent& event ) { event.Skip(); } void wxPGComboPopup::OnComboDoubleClick() { } void wxPGComboPopup::SetStringValue( const wxString& WXUNUSED(value) ) { } bool wxPGComboPopup::LazyCreate() { return false; } void wxPGComboPopup::Dismiss() { m_combo->HidePopup(); } // ---------------------------------------------------------------------------- // wxPGVListBoxComboPopup is a wxVListBox customized to act as a popup control // // ---------------------------------------------------------------------------- BEGIN_EVENT_TABLE(wxPGVListBoxComboPopup, wxVListBox) EVT_MOTION(wxPGVListBoxComboPopup::OnMouseMove) EVT_KEY_DOWN(wxPGVListBoxComboPopup::OnKey) EVT_LEFT_UP(wxPGVListBoxComboPopup::OnLeftClick) END_EVENT_TABLE() wxPGVListBoxComboPopup::wxPGVListBoxComboPopup(wxPGComboControl* combo) : wxVListBox(), wxPGComboPopup(combo) { //m_callback = callback; m_widestWidth = 0; m_avgCharWidth = 0; m_baseImageWidth = 0; m_itemHeight = 0; m_value = -1; m_itemHover = -1; m_clientDataItemsType = wxClientData_None; } bool wxPGVListBoxComboPopup::Create(wxWindow* parent) { if ( !wxVListBox::Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_SIMPLE | wxLB_INT_HEIGHT | wxWANTS_CHARS) ) return false; m_font = m_combo->GetFont(); wxVListBox::SetItemCount(m_strings.GetCount()); // TODO: Move this to SetFont m_itemHeight = GetCharHeight() + 0; return true; } wxPGVListBoxComboPopup::~wxPGVListBoxComboPopup() { Clear(); } bool wxPGVListBoxComboPopup::LazyCreate() { // NB: There is a bug with wxVListBox that can be avoided by creating // it later (bug causes empty space to be shown if initial selection // is at the end of a list longer than the control can show at once). return true; } // paint the control itself void wxPGVListBoxComboPopup::PaintComboControl( wxDC& dc, const wxRect& rect ) { if ( !(m_combo->GetWindowStyle() & wxODCB_STD_CONTROL_PAINT) ) { m_combo->DrawFocusBackground(dc,rect,0); if ( m_combo->OnDrawListItem(dc,rect,m_value,wxPGCC_PAINTING_CONTROL) ) return; } wxPGComboPopup::PaintComboControl(dc,rect); } void wxPGVListBoxComboPopup::OnDrawItem( wxDC& dc, const wxRect& rect, size_t n) const { dc.SetFont(m_font); bool isHilited = wxVListBox::GetSelection() == (int) n; // Set correct text colour for selected items // (must always set the correct colour - atleast GTK may have lost it // in between calls). if ( isHilited ) dc.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT) ); else dc.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT) ); if ( !m_combo->OnDrawListItem(dc,rect,(int)n,0) ) dc.DrawText( GetString(n), rect.x + 2, rect.y ); } wxCoord wxPGVListBoxComboPopup::OnMeasureItem(size_t n) const { int itemHeight = m_combo->OnMeasureListItem(n); if ( itemHeight < 0 ) itemHeight = m_itemHeight; return itemHeight; } void wxPGVListBoxComboPopup::OnDrawBackground(wxDC& dc, const wxRect& rect, size_t n) const { // we need to render selected and current items differently if ( IsCurrent(n) ) { m_combo->DrawFocusBackground( dc, rect, wxCONTROL_ISSUBMENU|wxCONTROL_SELECTED ); } //else: do nothing for the normal items } void wxPGVListBoxComboPopup::DismissWithEvent() { int selection = wxVListBox::GetSelection(); Dismiss(); if ( selection != wxNOT_FOUND ) m_stringValue = m_strings[selection]; else m_stringValue = wxEmptyString; if ( m_stringValue != m_combo->GetValue() ) m_combo->SetValue(m_stringValue); m_value = selection; SendComboBoxEvent(selection); } void wxPGVListBoxComboPopup::SendComboBoxEvent( int selection ) { wxCommandEvent evt(wxEVT_COMMAND_COMBOBOX_SELECTED,m_combo->GetId()); evt.SetEventObject(m_combo); evt.SetInt(selection); // Set client data, if any if ( selection >= 0 && (int)m_clientDatas.GetCount() > selection ) { void* clientData = m_clientDatas[selection]; if ( m_clientDataItemsType == wxClientData_Object ) evt.SetClientObject((wxClientData*)clientData); else evt.SetClientData(clientData); } m_combo->GetEventHandler()->AddPendingEvent(evt); } // returns true if key was consumed bool wxPGVListBoxComboPopup::HandleKey( int keycode, bool saturate ) { int value = m_value; int itemCount = GetCount(); if ( keycode == WXK_DOWN || keycode == WXK_RIGHT ) { value++; } else if ( keycode == WXK_UP || keycode == WXK_LEFT ) { value--; } else if ( keycode == WXK_PAGEDOWN ) { value+=10; } else if ( keycode == WXK_PAGEUP ) { value-=10; } /* else if ( keycode == WXK_END ) { value = itemCount-1; } else if ( keycode == WXK_HOME ) { value = 0; } */ else return false; if ( saturate ) { if ( value >= itemCount ) value = itemCount - 1; else if ( value < 0 ) value = 0; } else { if ( value >= itemCount ) value -= itemCount; else if ( value < 0 ) value += itemCount; } if ( value == m_value ) // Even if value was same, don't skip the event // (good for consistency) return true; m_value = value; wxString valStr; if ( value >= 0 ) m_combo->SetValue(m_strings[value]); SendComboBoxEvent(m_value); return true; } void wxPGVListBoxComboPopup::OnComboDoubleClick() { // Cycle on dclick (disable saturation to allow true cycling). if ( !::wxGetKeyState(WXK_SHIFT) ) HandleKey(WXK_DOWN,false); else HandleKey(WXK_UP,false); } void wxPGVListBoxComboPopup::OnComboKeyEvent( wxKeyEvent& event ) { // Saturated key movement on if ( !HandleKey(event.GetKeyCode(),true) ) event.Skip(); } void wxPGVListBoxComboPopup::OnPopup() { // *must* set value after size is set (this is because of a vlbox bug) wxVListBox::SetSelection(m_value); } void wxPGVListBoxComboPopup::OnMouseMove(wxMouseEvent& event) { // Move selection to cursor if it is inside the popup int itemHere = GetItemAtPosition(event.GetPosition()); if ( itemHere >= 0 ) wxVListBox::SetSelection(itemHere); event.Skip(); } void wxPGVListBoxComboPopup::OnLeftClick(wxMouseEvent& WXUNUSED(event)) { DismissWithEvent(); } void wxPGVListBoxComboPopup::OnKey(wxKeyEvent& event) { // Select item if ENTER is pressed if ( event.GetKeyCode() == WXK_RETURN || event.GetKeyCode() == WXK_NUMPAD_ENTER ) { DismissWithEvent(); } // Hide popup if ESC is pressed else if ( event.GetKeyCode() == WXK_ESCAPE ) Dismiss(); else event.Skip(); } void wxPGVListBoxComboPopup::CheckWidth( int pos ) { wxCoord x = m_combo->OnMeasureListItemWidth(pos); if ( x < 0 ) { if ( !m_font.Ok() ) m_font = m_combo->GetFont(); wxCoord y; m_combo->GetTextExtent(m_strings[pos], &x, &y, 0, 0, &m_font); x += 4; } if ( m_widestWidth < x ) { m_widestWidth = x; } } void wxPGVListBoxComboPopup::Insert( const wxString& item, int pos ) { // Need to change selection? wxString strValue; if ( !(m_combo->GetWindowStyle() & wxCB_READONLY) && m_combo->GetValue() == item ) m_value = pos; else if ( m_value >= pos ) m_value++; m_strings.Insert(item,pos); if ( IsCreated() ) wxVListBox::SetItemCount( wxVListBox::GetItemCount()+1 ); // Calculate width CheckWidth(pos); } int wxPGVListBoxComboPopup::Append(const wxString& item) { int pos = (int)m_strings.GetCount(); if ( m_combo->GetWindowStyle() & wxCB_SORT ) { // Find position // TODO: Could be optimized with binary search wxArrayString strings = m_strings; unsigned int i; for ( i=0; i n ) return m_clientDatas[n]; return NULL; } void wxPGVListBoxComboPopup::Delete( wxODCIndex item ) { // Remove client data, if set if ( m_clientDatas.GetCount() ) { if ( m_clientDataItemsType == wxClientData_Object ) delete (wxClientData*) m_clientDatas[item]; m_clientDatas.RemoveAt(item); } m_strings.RemoveAt(item); int sel = GetSelection(); if ( IsCreated() ) wxVListBox::SetItemCount( wxVListBox::GetItemCount()-1 ); if ( (int)item < sel ) SetSelection(sel-1); else if ( (int)item == sel ) SetSelection(wxNOT_FOUND); } int wxPGVListBoxComboPopup::FindString(const wxString& s) const { return m_strings.Index(s); } wxODCCount wxPGVListBoxComboPopup::GetCount() const { return m_strings.GetCount(); } wxString wxPGVListBoxComboPopup::GetString( int item ) const { return m_strings[item]; } void wxPGVListBoxComboPopup::SetString( int item, const wxString& str ) { m_strings[item] = str; } wxString wxPGVListBoxComboPopup::GetStringValue() const { return m_stringValue; } void wxPGVListBoxComboPopup::SetSelection( int item ) { // This seems to be necessary (2.5.3 w/ MingW atleast) if ( item < -1 || item >= (int)m_strings.GetCount() ) item = -1; m_value = item; if ( item >= 0 ) m_stringValue = m_strings[item]; else m_stringValue = wxEmptyString; if ( IsCreated() ) wxVListBox::SetSelection(item); } int wxPGVListBoxComboPopup::GetSelection() const { return m_value; } void wxPGVListBoxComboPopup::SetStringValue( const wxString& value ) { int index = m_strings.Index(value); m_stringValue = value; if ( index >= 0 && index < (int)wxVListBox::GetItemCount() ) { wxVListBox::SetSelection(index); m_value = index; } } wxSize wxPGVListBoxComboPopup::GetAdjustedSize( int minWidth, int prefHeight, int maxHeight ) { int height = 250; if ( m_strings.GetCount() ) { if ( prefHeight > 0 ) height = prefHeight; if ( height > maxHeight ) height = maxHeight; int totalHeight = GetTotalHeight(); // + 3; if ( height >= totalHeight ) { height = totalHeight; } else { // Adjust height to a multiple of the height of the first item // NB: Calculations that take variable height into account // are unnecessary. int fih = GetLineHeight(0); int shown = height/fih; height = shown * fih; } } else height = 50; #if defined(__WXMAC__) // Set a minimum height since otherwise scrollbars won't draw properly height = wxMax(50, height); #endif // Take scrollbar into account in width calculations int widestWidth = m_widestWidth + wxSystemSettings::GetMetric(wxSYS_VSCROLL_X); return wxSize(minWidth > widestWidth ? minWidth : widestWidth, height+2); } void wxPGVListBoxComboPopup::Populate( int n, const wxString choices[] ) { int i; for ( i=0; iGetWindowStyle() & wxCB_SORT ) m_strings.Sort(); // Find initial selection wxString strValue = m_combo->GetValue(); if ( strValue.Length() ) m_value = m_strings.Index(strValue); } // ---------------------------------------------------------------------------- // input handling // ---------------------------------------------------------------------------- // // This is pushed to the event handler queue of either combo box // or its textctrl (latter if not readonly combo). // class wxPGComboBoxTextCtrlHandler : public wxEvtHandler { public: wxPGComboBoxTextCtrlHandler( wxPGComboControlBase* combo ) : wxEvtHandler() { m_combo = combo; } ~wxPGComboBoxTextCtrlHandler() { } void OnKey(wxKeyEvent& event); void OnFocus(wxFocusEvent& event); protected: wxPGComboControlBase* m_combo; private: DECLARE_EVENT_TABLE() }; BEGIN_EVENT_TABLE(wxPGComboBoxTextCtrlHandler, wxEvtHandler) EVT_KEY_DOWN(wxPGComboBoxTextCtrlHandler::OnKey) EVT_SET_FOCUS(wxPGComboBoxTextCtrlHandler::OnFocus) END_EVENT_TABLE() void wxPGComboBoxTextCtrlHandler::OnKey(wxKeyEvent& event) { // Let the wxComboCtrl event handler have a go first. wxPGComboControlBase* combo = m_combo; wxObject* prevObj = event.GetEventObject(); event.SetId(combo->GetId()); event.SetEventObject(combo); combo->GetEventHandler()->ProcessEvent(event); event.SetId(((wxWindow*)prevObj)->GetId()); event.SetEventObject(prevObj); } void wxPGComboBoxTextCtrlHandler::OnFocus(wxFocusEvent& event) { // FIXME: This code does run when control is clicked, // yet on Windows it doesn't select all the text. if ( !(m_combo->GetInternalFlags() & wxPGCC_NO_TEXT_AUTO_SELECT) ) { if ( m_combo->GetTextCtrl() ) m_combo->GetTextCtrl()->SelectAll(); else m_combo->SetSelection(-1,-1); } // Send focus indication to parent. // NB: This is needed for cases where the textctrl gets focus // instead of its parent. We'll check if the focus came from // in order to prevent a possible infinite recursion. if ( m_combo->ConsumingTextCtrlFocusEvent() ) { wxFocusEvent evt2(wxEVT_SET_FOCUS,m_combo->GetId()); evt2.SetEventObject(m_combo); m_combo->GetEventHandler()->ProcessEvent(evt2); } event.Skip(); } // // This is pushed to the event handler queue of the control in popup. // class wxPGComboPopupExtraEventHandler : public wxEvtHandler { public: wxPGComboPopupExtraEventHandler( wxPGComboControlBase* combo ) : wxEvtHandler() { m_combo = combo; m_beenInside = false; } ~wxPGComboPopupExtraEventHandler() { } void OnMouseEvent( wxMouseEvent& event ); // Called from wxPGComboControlBase::OnPopupDismiss void OnPopupDismiss() { m_beenInside = false; } protected: wxPGComboControlBase* m_combo; bool m_beenInside; private: DECLARE_EVENT_TABLE() }; BEGIN_EVENT_TABLE(wxPGComboPopupExtraEventHandler, wxEvtHandler) EVT_MOUSE_EVENTS(wxPGComboPopupExtraEventHandler::OnMouseEvent) END_EVENT_TABLE() void wxPGComboPopupExtraEventHandler::OnMouseEvent( wxMouseEvent& event ) { wxPoint pt = event.GetPosition(); wxSize sz = m_combo->GetPopupControl()->GetClientSize(); int evtType = event.GetEventType(); bool isInside = pt.x >= 0 && pt.y >= 0 && pt.x < sz.x && pt.y < sz.y; if ( evtType == wxEVT_MOTION || evtType == wxEVT_LEFT_DOWN || evtType == wxEVT_RIGHT_DOWN ) { // Block motion and click events outside the popup if ( !isInside ) { event.Skip(false); return; } } else if ( evtType == wxEVT_LEFT_UP ) { // Don't let left-down events in if outside if ( evtType == wxEVT_LEFT_DOWN ) { if ( !isInside ) return; } if ( !m_beenInside ) { if ( isInside ) { m_beenInside = true; } else { // // Some mouse events to popup that happen outside it, before cursor // has been inside the popu, need to be ignored by it but relayed to // the dropbutton. // wxWindow* btn = m_combo->GetButton(); if ( btn ) btn->GetEventHandler()->AddPendingEvent(event); else m_combo->GetEventHandler()->AddPendingEvent(event); return; } event.Skip(); } } event.Skip(); } // ---------------------------------------------------------------------------- // wxPGComboControlBase // ---------------------------------------------------------------------------- BEGIN_EVENT_TABLE(wxPGComboControlBase, wxControl) EVT_TEXT(wxID_ANY,wxPGComboControlBase::OnTextCtrlEvent) EVT_SIZE(wxPGComboControlBase::OnSizeEvent) EVT_KEY_DOWN(wxPGComboControlBase::OnKeyEvent) EVT_SET_FOCUS(wxPGComboControlBase::OnFocusEvent) EVT_KILL_FOCUS(wxPGComboControlBase::OnFocusEvent) //EVT_BUTTON(wxID_ANY,wxPGComboControlBase::OnButtonClickEvent) EVT_TEXT_ENTER(wxID_ANY,wxPGComboControlBase::OnTextCtrlEvent) EVT_SYS_COLOUR_CHANGED(wxPGComboControlBase::OnSysColourChanged) END_EVENT_TABLE() IMPLEMENT_ABSTRACT_CLASS(wxPGComboControlBase, wxControl) // Have global double buffer - should be enough for multiple combos static wxBitmap* gs_doubleBuffer = (wxBitmap*) NULL; void wxPGComboControlBase::Init() { m_winPopup = (wxWindow *)NULL; m_popup = (wxWindow *)NULL; m_isPopupShown = false; m_btn = (wxWindow*) NULL; m_text = (wxTextCtrl*) NULL; m_popupInterface = (wxPGComboPopup*) NULL; m_popupExtraHandler = (wxEvtHandler*) NULL; m_textEvtHandler = (wxEvtHandler*) NULL; #if INSTALL_TOPLEV_HANDLER m_toplevEvtHandler = (wxEvtHandler*) NULL; #endif m_heightPopup = -1; m_widthMinPopup = -1; m_widthCustomPaint = 0; m_widthCustomBorder = 0; m_btnState = 0; m_btnWidDefault = 0; m_blankButtonBg = false; m_btnWid = m_btnHei = 0; m_btnSide = wxRIGHT; m_btnSpacingX = 0; m_extLeft = 0; m_extRight = 0; m_absIndent = -1; m_iFlags = 0; m_fakePopupUsage = 0; m_skipTextCtrlFocusEvents = 0; m_timeCanAcceptClick = 0; } bool wxPGComboControlBase::Create(wxWindow *parent, wxWindowID id, const wxString& value, const wxPoint& pos, const wxSize& size, long style, const wxValidator& validator, const wxString& name) { if ( !wxControl::Create(parent, id, pos, size, style | wxWANTS_CHARS, validator, name) ) return false; m_valueString = value; // Get colours OnThemeChange(); m_absIndent = GetNativeTextIndent(); return true; } void wxPGComboControlBase::InstallInputHandlers() { if ( m_text ) { m_textEvtHandler = new wxPGComboBoxTextCtrlHandler(this); m_text->PushEventHandler(m_textEvtHandler); } } void wxPGComboControlBase::CreateTextCtrl( int extraStyle, const wxValidator& validator ) { if ( !(m_windowStyle & wxCB_READONLY) ) { m_text = new wxTextCtrl(this, 12345, m_valueString, wxDefaultPosition, wxDefaultSize, // wxTE_PROCESS_TAB is needed because on Windows, wxTAB_TRAVERSAL is // not used by the wxPropertyGrid and therefore the tab is // processed by looking at ancestors to see if they have // wxTAB_TRAVERSAL. The navigation event is then sent to // the wrong window. wxTE_PROCESS_TAB | wxTE_PROCESS_ENTER | //wxWANTS_CHARS | extraStyle, validator); #if defined(__WXMSW__) && !defined(__WXWINCE__) ::SendMessage(GetHwndOf(m_text), EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(3, 3)); #endif // This is required for some platforms (GTK+ atleast) m_text->SetSizeHints(2,4); } } void wxPGComboControlBase::OnThemeChange() { SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); } bool wxPGComboControlBase::Destroy() { return wxControl::Destroy(); } wxPGComboControlBase::~wxPGComboControlBase() { if ( HasCapture() ) ReleaseMouse(); HidePopup(); delete gs_doubleBuffer; gs_doubleBuffer = (wxBitmap*) NULL; #if INSTALL_TOPLEV_HANDLER delete ((wxPGComboFrameEventHandler*)m_toplevEvtHandler); m_toplevEvtHandler = (wxEvtHandler*) NULL; #endif if ( m_popup ) m_popup->RemoveEventHandler(m_popupExtraHandler); delete m_popupExtraHandler; delete m_popupInterface; delete m_winPopup; if ( m_text ) m_text->RemoveEventHandler(m_textEvtHandler); delete m_textEvtHandler; } // ---------------------------------------------------------------------------- // geometry stuff // ---------------------------------------------------------------------------- // Recalculates button and textctrl areas void wxPGComboControlBase::CalculateAreas( int btnWidth ) { wxSize sz = GetClientSize(); int customBorder = m_widthCustomBorder; bool buttonOutside; int btnBorder; // border for button only if ( ( (m_iFlags & wxPGCC_BUTTON_OUTSIDE_BORDER) || m_blankButtonBg ) && m_btnSpacingX == 0 && m_btnWid == 0 && m_btnHei == 0 && (!m_bmpNormal.Ok() || m_blankButtonBg) ) { buttonOutside = true; m_iFlags |= wxPGCC_IFLAG_BUTTON_OUTSIDE; btnBorder = 0; } else { buttonOutside = false; m_iFlags &= ~(wxPGCC_IFLAG_BUTTON_OUTSIDE); btnBorder = customBorder; } // Defaul indentation if ( m_absIndent < 0 ) m_absIndent = GetNativeTextIndent(); int butWidth = btnWidth; if ( butWidth <= 0 ) butWidth = m_btnWidDefault; else m_btnWidDefault = butWidth; if ( butWidth <= 0 ) return; // Adjust button width if ( m_btnWid < 0 ) butWidth += m_btnWid; else if ( m_btnWid > 0 ) butWidth = m_btnWid; int butHeight = sz.y; butHeight -= btnBorder*2; // Adjust button height if ( m_btnHei < 0 ) butHeight += m_btnHei; else if ( m_btnHei > 0 ) butHeight = m_btnHei; // Use size of normal bitmap if... // It is larger // OR // button width is set to default and blank button bg is not drawn if ( m_bmpNormal.Ok() ) { int bmpReqWidth = m_bmpNormal.GetWidth(); int bmpReqHeight = m_bmpNormal.GetHeight(); // If drawing blank button background, we need to add some margin. if ( m_blankButtonBg ) { bmpReqWidth += BMP_BUTTON_MARGIN*2; bmpReqHeight += BMP_BUTTON_MARGIN*2; } if ( butWidth < bmpReqWidth || ( m_btnWid == 0 && !m_blankButtonBg ) ) butWidth = bmpReqWidth; if ( butHeight < bmpReqHeight || ( m_btnHei == 0 && !m_blankButtonBg ) ) butHeight = bmpReqHeight; // Need to fix height? if ( (sz.y-(customBorder*2)) < butHeight && btnWidth == 0 ) { int newY = butHeight+(customBorder*2); SetClientSize(-1,newY); sz.y = newY; } } int butAreaWid = butWidth + (m_btnSpacingX*2); m_btnSize.x = butWidth; m_btnSize.y = butHeight; m_btnArea.x = ( m_btnSide==wxRIGHT ? sz.x - butAreaWid - btnBorder : btnBorder ); m_btnArea.y = btnBorder; m_btnArea.width = butAreaWid; m_btnArea.height = sz.y - (btnBorder*2); if ( m_bmpNormal.Ok() || m_btnArea.width != butWidth || m_btnArea.height != butHeight ) m_iFlags |= wxPGCC_IFLAG_HAS_NONSTANDARD_BUTTON; else m_iFlags &= ~wxPGCC_IFLAG_HAS_NONSTANDARD_BUTTON; m_tcArea.x = ( m_btnSide==wxRIGHT ? 0 : butAreaWid ) + customBorder; m_tcArea.y = customBorder; m_tcArea.width = sz.x - butAreaWid - (customBorder*2); m_tcArea.height = sz.y - (customBorder*2); /* if ( m_text ) { ::wxMessageBox(wxString::Format(wxT("ButtonArea (%i,%i,%i,%i)\n"),m_btnArea.x,m_btnArea.y,m_btnArea.width,m_btnArea.height) + wxString::Format(wxT("TextCtrlArea (%i,%i,%i,%i)"),m_tcArea.x,m_tcArea.y,m_tcArea.width,m_tcArea.height)); } */ } void wxPGComboControlBase::PositionTextCtrl( int textCtrlXAdjust, int textCtrlYAdjust ) { if ( !m_text ) return; wxSize sz = GetClientSize(); int customBorder = m_widthCustomBorder; if ( (m_text->GetWindowStyleFlag() & wxBORDER_MASK) == wxNO_BORDER ) { // Centre textctrl int tcSizeY = m_text->GetBestSize().y; int diff = sz.y - tcSizeY; int y = textCtrlYAdjust + (diff/2); if ( y < customBorder ) y = customBorder; m_text->SetSize( m_tcArea.x + m_widthCustomPaint + m_absIndent + textCtrlXAdjust, y, m_tcArea.width - g_comboMargin - (textCtrlXAdjust + m_widthCustomPaint + m_absIndent), -1 ); // Make sure textctrl doesn't exceed the bottom custom border wxSize tsz = m_text->GetSize(); diff = (y + tsz.y) - (sz.y - customBorder); if ( diff >= 0 ) { tsz.y = tsz.y - diff - 1; m_text->SetSize(tsz); } } else { m_text->SetSize( m_tcArea.x, 0, sz.x - m_btnArea.x - m_widthCustomPaint - customBorder, sz.y ); } } wxSize wxPGComboControlBase::DoGetBestSize() const { wxSize sizeText(150,0); if ( m_text ) sizeText = m_text->GetBestSize(); // TODO: Better method to calculate close-to-native control height. int fhei; if ( m_font.Ok() ) fhei = (m_font.GetPointSize()*2) + 5; else if ( wxNORMAL_FONT->Ok() ) fhei = (wxNORMAL_FONT->GetPointSize()*2) + 5; else fhei = sizeText.y + 4; // Need to force height to accomodate bitmap? int btnSizeY = m_btnSize.y; if ( m_bmpNormal.Ok() && fhei < btnSizeY ) fhei = btnSizeY; // Control height doesn't depend on border /* // Add border int border = m_windowStyle & wxBORDER_MASK; if ( border == wxSIMPLE_BORDER ) fhei += 2; else if ( border == wxNO_BORDER ) fhei += (m_widthCustomBorder*2); else // Sunken etc. fhei += 4; */ // Final adjustments #ifdef __WXGTK__ fhei += 1; #endif wxSize ret(sizeText.x + g_comboMargin + DEFAULT_DROPBUTTON_WIDTH, fhei); CacheBestSize(ret); return ret; } void wxPGComboControlBase::DoMoveWindow(int x, int y, int width, int height) { // SetSize is called last in create, so it marks the end of creation m_iFlags |= wxPGCC_IFLAG_CREATED; wxControl::DoMoveWindow(x, y, width, height); } void wxPGComboControlBase::OnSizeEvent( wxSizeEvent& event ) { if ( !IsCreated() ) return; // defined by actual wxComboControls OnResize(); event.Skip(); } // ---------------------------------------------------------------------------- // standard operations // ---------------------------------------------------------------------------- bool wxPGComboControlBase::Enable(bool enable) { if ( !wxControl::Enable(enable) ) return false; if ( m_btn ) m_btn->Enable(enable); if ( m_text ) m_text->Enable(enable); return true; } bool wxPGComboControlBase::Show(bool show) { if ( !wxControl::Show(show) ) return false; if (m_btn) m_btn->Show(show); if (m_text) m_text->Show(show); return true; } bool wxPGComboControlBase::SetFont ( const wxFont& font ) { if ( !wxControl::SetFont(font) ) return false; if (m_text) m_text->SetFont(font); return true; } #if wxUSE_TOOLTIPS void wxPGComboControlBase::DoSetToolTip(wxToolTip *tooltip) { wxControl::DoSetToolTip(tooltip); // Set tool tip for button and text box if ( tooltip ) { const wxString &tip = tooltip->GetTip(); if ( m_text ) m_text->SetToolTip(tip); if ( m_btn ) m_btn->SetToolTip(tip); } else { if ( m_text ) m_text->SetToolTip( (wxToolTip*) NULL ); if ( m_btn ) m_btn->SetToolTip( (wxToolTip*) NULL ); } } #endif // wxUSE_TOOLTIPS // ---------------------------------------------------------------------------- // painting // ---------------------------------------------------------------------------- // draw focus background on area in a way typical on platform void wxPGComboControlBase::DrawFocusBackground( wxDC& dc, const wxRect& rect, int flags ) { wxSize sz = GetClientSize(); bool isEnabled; bool doDrawFocusRect; // also selected // For smaller size control (and for disabled background) use less spacing int focusSpacingX; int focusSpacingY; if ( !(flags & wxCONTROL_ISSUBMENU) ) { // Drawing control isEnabled = IsEnabled(); doDrawFocusRect = ShouldDrawFocus(); // Windows-style: for smaller size control (and for disabled background) use less spacing //focusSpacingX = isEnabled ? 2 : 1; focusSpacingX = 1; focusSpacingY = sz.y > (GetCharHeight()+500) && isEnabled ? 2 : 1; } else { // Drawing a list item isEnabled = true; // they are never disabled doDrawFocusRect = flags & wxCONTROL_SELECTED ? true : false; focusSpacingX = 0; focusSpacingY = 0; } // Set the background sub-rectangle for selection, disabled etc wxRect selRect(rect); selRect.y += focusSpacingY; selRect.height -= (focusSpacingY*2); int wcp = 0; if ( !(flags & wxCONTROL_ISSUBMENU) ) wcp += m_widthCustomPaint; selRect.x += wcp + focusSpacingX; selRect.width -= wcp + (focusSpacingX*2); wxColour bgCol; bool doDrawSelRect = true; if ( isEnabled ) { // If popup is hidden and this control is focused, // then draw the focus-indicator (selbgcolor background etc.). if ( doDrawFocusRect ) { dc.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT) ); bgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT); } else { dc.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT) ); bgCol = GetBackgroundColour(); doDrawSelRect = false; } } else { dc.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT) ); bgCol = GetBackgroundColour(); } dc.SetBrush( bgCol ); if ( doDrawSelRect ) { dc.SetPen( bgCol ); dc.DrawRectangle( selRect ); } } void wxPGComboControlBase::DrawButton( wxDC& dc, const wxRect& rect, int flags ) { int drawState = m_btnState; if ( (m_iFlags & wxPGCC_BUTTON_STAYS_DOWN) && IsPopupShown() ) drawState |= wxCONTROL_PRESSED; wxRect drawRect(rect.x+m_btnSpacingX, rect.y+((rect.height-m_btnSize.y)/2), m_btnSize.x, m_btnSize.y); // Make sure area is not larger than the control if ( drawRect.y < rect.y ) drawRect.y = rect.y; if ( drawRect.height > rect.height ) drawRect.height = rect.height; bool enabled = IsEnabled(); if ( !enabled ) drawState |= wxCONTROL_DISABLED; if ( !m_bmpNormal.Ok() ) { if ( flags & Button_BitmapOnly ) return; // Need to clear button background even if m_btn is present if ( flags & Button_PaintBackground ) { wxColour bgCol; if ( m_iFlags & wxPGCC_IFLAG_BUTTON_OUTSIDE ) bgCol = GetParent()->GetBackgroundColour(); else bgCol = GetBackgroundColour(); dc.SetBrush(bgCol); dc.SetPen(bgCol); dc.DrawRectangle(rect); } // Draw standard button wxRendererNative::Get().DrawComboBoxDropButton(this, dc, drawRect, drawState); } else { // Draw bitmap wxBitmap* pBmp; if ( !enabled ) pBmp = &m_bmpDisabled; else if ( m_btnState & wxCONTROL_PRESSED ) pBmp = &m_bmpPressed; else if ( m_btnState & wxCONTROL_CURRENT ) pBmp = &m_bmpHover; else pBmp = &m_bmpNormal; #if wxCHECK_VERSION(2, 7, 0) if ( m_blankButtonBg ) { // If using blank button background, we need to clear its background // with button face colour instead of colour for rest of the control. if ( flags & Button_PaintBackground ) { wxColour bgCol = GetParent()->GetBackgroundColour(); //wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE); //wxColour bgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); dc.SetPen(bgCol); dc.SetBrush(bgCol); dc.DrawRectangle(rect); } if ( !(flags & Button_BitmapOnly) ) { wxRendererNative::Get().DrawPushButton(this, dc, drawRect, drawState); } } else #endif { // Need to clear button background even if m_btn is present // (assume non-button background was cleared just before this call so brushes are good) if ( flags & Button_PaintBackground ) dc.DrawRectangle(rect); } // Draw bitmap centered in drawRect dc.DrawBitmap(*pBmp, drawRect.x + (drawRect.width-pBmp->GetWidth())/2, drawRect.y + (drawRect.height-pBmp->GetHeight())/2, true); } } void wxPGComboControlBase::RecalcAndRefresh() { if ( IsCreated() ) { wxSizeEvent evt(GetSize(),GetId()); GetEventHandler()->ProcessEvent(evt); Refresh(); } } bool wxPGComboControlBase::OnDrawListItem( wxDC& WXUNUSED(dc), const wxRect& WXUNUSED(rect), int WXUNUSED(item), int WXUNUSED(flags) ) { return false; // signals caller to make default drawing } wxCoord wxPGComboControlBase::OnMeasureListItem( int WXUNUSED(item) ) { return -1; // signals caller to use default } wxCoord wxPGComboControlBase::OnMeasureListItemWidth( int WXUNUSED(item) ) { return -1; // signals caller to use default } // ---------------------------------------------------------------------------- // miscellaneous event handlers // ---------------------------------------------------------------------------- void wxPGComboControlBase::OnTextCtrlEvent(wxCommandEvent& event) { // Change event id and relay it forward event.SetId(GetId()); event.Skip(); } // call if cursor is on button area or mouse is captured for the button bool wxPGComboControlBase::HandleButtonMouseEvent( wxMouseEvent& event, int flags ) { int type = event.GetEventType(); if ( type == wxEVT_MOTION ) { if ( flags & wxPGCC_MF_ON_BUTTON ) { if ( !(m_btnState & wxCONTROL_CURRENT) ) { // Mouse hover begins m_btnState |= wxCONTROL_CURRENT; if ( HasCapture() ) // Retain pressed state. m_btnState |= wxCONTROL_PRESSED; Refresh(); } } else if ( (m_btnState & wxCONTROL_CURRENT) ) { // Mouse hover ends m_btnState &= ~(wxCONTROL_CURRENT|wxCONTROL_PRESSED); Refresh(); } } else if ( type == wxEVT_LEFT_DOWN ) { // Only accept event if it wasn't right after popup dismiss //if ( ::wxGetLocalTimeMillis() > m_timeCanClick ) { // Need to test this, because it might be outside. if ( flags & wxPGCC_MF_ON_BUTTON ) { m_btnState |= wxCONTROL_PRESSED; Refresh(); if ( !(m_iFlags & wxPGCC_POPUP_ON_MOUSE_UP) ) OnButtonClick(); else // If showing popup now, do not capture mouse or there will be interference CaptureMouse(); } } /*else { m_btnState = 0; }*/ } else if ( type == wxEVT_LEFT_UP ) { // Only accept event if mouse was left-press was previously accepted if ( HasCapture() ) ReleaseMouse(); if ( m_btnState & wxCONTROL_PRESSED ) { // If mouse was inside, fire the click event. if ( m_iFlags & wxPGCC_POPUP_ON_MOUSE_UP ) { if ( flags & wxPGCC_MF_ON_BUTTON ) OnButtonClick(); } m_btnState &= ~(wxCONTROL_PRESSED); Refresh(); } } else if ( type == wxEVT_LEAVE_WINDOW ) { if ( m_btnState & (wxCONTROL_CURRENT|wxCONTROL_PRESSED) ) { m_btnState &= ~(wxCONTROL_CURRENT); // Mouse hover ends if ( !m_isPopupShown ) { m_btnState &= ~(wxCONTROL_PRESSED); Refresh(); } } } else return false; return true; } // Conversion to double-clicks and some basic filtering // returns true if event was consumed or filtered bool wxPGComboControlBase::PreprocessMouseEvent( wxMouseEvent& event, int WXUNUSED(flags) ) { wxLongLong t = ::wxGetLocalTimeMillis(); int evtType = event.GetEventType(); if ( m_isPopupShown && ( evtType == wxEVT_LEFT_DOWN || evtType == wxEVT_RIGHT_DOWN ) ) { HidePopup(); return true; } // // Generate our own double-clicks // (to allow on-focus dc-event on double-clicks instead of triple-clicks) /*if ( (m_windowStyle & wxPGCC_DCLICK_CYCLES) && !m_isPopupShown && //!(handlerFlags & wxPGCC_MF_ON_BUTTON) ) !(flags & wxPGCC_MF_ON_BUTTON) ) { if ( evtType == wxEVT_LEFT_DOWN ) { // Set value to avoid up-events without corresponding downs m_downReceived = true; } else if ( evtType == wxEVT_LEFT_DCLICK ) { // We'll make our own double-clicks //evtType = 0; event.SetEventType(0); return true; } else if ( evtType == wxEVT_LEFT_UP ) { if ( m_downReceived || m_timeLastMouseUp == 1 ) { wxLongLong timeFromLastUp = (t-m_timeLastMouseUp); if ( timeFromLastUp < DOUBLE_CLICK_CONVERSION_TRESHOLD ) { //type = wxEVT_LEFT_DCLICK; event.SetEventType(wxEVT_LEFT_DCLICK); m_timeLastMouseUp = 1; } else { m_timeLastMouseUp = t; } //m_downReceived = false; } } }*/ // Filter out clicks on button immediately after popup dismiss (Windows like behaviour) if ( evtType == wxEVT_LEFT_DOWN && t < m_timeCanAcceptClick ) { event.SetEventType(0); return true; } return false; } void wxPGComboControlBase::HandleNormalMouseEvent( wxMouseEvent& event ) { int evtType = event.GetEventType(); if ( (evtType == wxEVT_LEFT_DOWN || evtType == wxEVT_LEFT_DCLICK) && (m_windowStyle & wxCB_READONLY) ) { if ( m_isPopupShown ) { #if !wxUSE_POPUPWIN // Normally do nothing - evt handler should close it for us #if ALLOW_FAKE_POPUP if ( m_fakePopupUsage == 2 ) HidePopup(); #endif #elif !USE_TRANSIENT_POPUP // Click here always hides the popup. HidePopup(); #endif } else { if ( !(m_windowStyle & wxPGCC_DCLICK_CYCLES) ) { // In read-only mode, clicking the text is the // same as clicking the button. OnButtonClick(); } else if ( /*evtType == wxEVT_LEFT_UP || */evtType == wxEVT_LEFT_DCLICK ) { //if ( m_popupInterface->CycleValue() ) // Refresh(); if ( m_popupInterface ) m_popupInterface->OnComboDoubleClick(); } } } else if ( m_isPopupShown ) { // relay (some) mouse events to the popup if ( evtType == wxEVT_MOUSEWHEEL ) m_popup->AddPendingEvent(event); } else if ( evtType ) event.Skip(); } void wxPGComboControlBase::OnKeyEvent( wxKeyEvent& event ) { int keycode = event.GetKeyCode(); if ( keycode == WXK_TAB && !IsPopupShown() ) { wxNavigationKeyEvent evt; evt.SetFlags(wxNavigationKeyEvent::FromTab| (!event.ShiftDown()?wxNavigationKeyEvent::IsForward: wxNavigationKeyEvent::IsBackward)); evt.SetEventObject(this); GetParent()->GetEventHandler()->AddPendingEvent(evt); return; } if ( IsPopupShown() ) { // pass it to the popped up control GetPopupControl()->AddPendingEvent(event); } else // no popup { int comboStyle = GetWindowStyle(); wxPGComboPopup* popupInterface = GetPopup(); if ( !popupInterface ) { event.Skip(); return; } if ( (comboStyle & wxCB_READONLY) || ( keycode != WXK_RIGHT && keycode != WXK_LEFT ) ) { // Alternate keys: UP and DOWN show the popup instead of cycling if ( (comboStyle & wxPGCC_ALT_KEYS) ) { if ( keycode == WXK_UP || keycode == WXK_DOWN ) { OnButtonClick(); return; } else event.Skip(); } else popupInterface->OnComboKeyEvent(event); } else event.Skip(); } } void wxPGComboControlBase::OnFocusEvent( wxFocusEvent& event ) { if ( event.GetEventType() == wxEVT_SET_FOCUS ) { if ( m_text && m_text != ::wxWindow::FindFocus() ) { m_skipTextCtrlFocusEvents++; m_text->SetFocus(); } } Refresh(); } void wxPGComboControlBase::OnSysColourChanged(wxSysColourChangedEvent& WXUNUSED(event)) { OnThemeChange(); // indentation may also have changed if ( !(m_iFlags & wxPGCC_IFLAG_INDENT_SET) ) m_absIndent = GetNativeTextIndent(); RecalcAndRefresh(); } // ---------------------------------------------------------------------------- // popup handling // ---------------------------------------------------------------------------- // Create popup window and the child control void wxPGComboControlBase::CreatePopup() { wxPGComboPopup* popupInterface = m_popupInterface; wxWindow* popup; if ( !m_winPopup ) m_winPopup = new wxPGComboPopupWindow( this, wxNO_BORDER ); popupInterface->Create(m_winPopup); m_popup = popup = popupInterface->GetControl(); m_popupExtraHandler = new wxPGComboPopupExtraEventHandler(this); popup->PushEventHandler( m_popupExtraHandler ); popupInterface->m_iFlags |= wxPGCP_IFLAG_CREATED; } void wxPGComboControlBase::SetPopup( wxPGComboPopup* iface ) { delete m_popupInterface; delete m_winPopup; m_popupInterface = iface; #if ALLOW_FAKE_POPUP m_fakePopupUsage = 0; #endif if ( !iface->LazyCreate() || m_winPopup ) { CreatePopup(); /* m_winPopup = new wxPGComboPopupWindow( this, wxNO_BORDER ); // Create popup right away iface->Create(m_winPopup); m_popup = iface->GetControl(); m_popupExtraHandler = new wxPGComboPopupExtraEventHandler(this); m_popup->PushEventHandler( m_popupExtraHandler ); // Add interface as event handler //m_popup->PushEventHandler( iface ); */ // FIXME: This bypasses wxGTK popupwindow bug // (i.e. window is not initially hidden when it should be) m_winPopup->Hide(); #if ALLOW_FAKE_POPUP m_fakePopupUsage = 1; #endif } else { m_popup = (wxWindow*) NULL; } // This must be after creation if ( m_valueString.length() ) iface->SetStringValue(m_valueString); } void wxPGComboControlBase::OnButtonClick() { // Derived classes can override this method for totally custom // popup action ShowPopup(); } void wxPGComboControlBase::ShowPopup() { wxCHECK_RET( m_popupInterface, wxT("no popup interface set for wxComboControl") ); wxCHECK_RET( !IsPopupShown(), wxT("popup window already shown") ); SetFocus(); // Space above and below int screenHeight; wxPoint scrPos; int spaceAbove; int spaceBelow; int maxHeightPopup; wxSize ctrlSz = GetSize(); #if ALLOW_FAKE_POPUP int existingHeight = 200; if ( m_popup ) existingHeight = m_popup->GetSize().y; int screenWidth; GetParent()->GetClientSize(&screenWidth,&screenHeight); screenWidth -= 2; scrPos = GetPosition(); spaceAbove = scrPos.y - 2; spaceBelow = screenHeight - spaceAbove - ctrlSz.y - 4; maxHeightPopup = spaceBelow; if ( spaceAbove > spaceBelow ) maxHeightPopup = spaceAbove; if ( maxHeightPopup >= existingHeight ) { if ( m_winPopup && m_fakePopupUsage!=2 ) { delete m_winPopup; m_winPopup = (wxWindow*) NULL; m_popup = (wxWindow*) NULL; } m_fakePopupUsage = 2; } else { if ( m_winPopup && m_fakePopupUsage!=1 ) { delete m_winPopup; m_winPopup = (wxWindow*) NULL; m_popup = (wxWindow*) NULL; } m_fakePopupUsage = 1; #else { #endif screenHeight = wxSystemSettings::GetMetric( wxSYS_SCREEN_Y ); scrPos = GetParent()->ClientToScreen(GetPosition()); spaceAbove = scrPos.y; spaceBelow = screenHeight - spaceAbove - ctrlSz.y; maxHeightPopup = spaceBelow; if ( spaceAbove > spaceBelow ) maxHeightPopup = spaceAbove; } // Width int widthPopup = ctrlSz.x + m_extLeft + m_extRight; if ( widthPopup < m_widthMinPopup ) widthPopup = m_widthMinPopup; wxWindow* winPopup = m_winPopup; wxWindow* popup; // Need to disable tab traversal of parent // // NB: This is to fix a bug in wxMSW. In theory it could also be fixed // by, for instance, adding check to window.cpp:wxWindowMSW::MSWProcessMessage // that if transient popup is open, then tab traversal is to be ignored. // However, I think this code would still be needed for cases where // transient popup doesn't work yet (wxWINCE?). wxWindow* parent = GetParent(); int parentFlags = parent->GetWindowStyle(); if ( parentFlags & wxTAB_TRAVERSAL ) { parent->SetWindowStyle( parentFlags & ~(wxTAB_TRAVERSAL) ); m_iFlags |= wxPGCC_IFLAG_PARENT_TAB_TRAVERSAL; } if ( !winPopup ) { #if ALLOW_FAKE_POPUP if ( m_fakePopupUsage == 2 ) { winPopup = new wxWindow(); #ifdef __WXMSW__ // Only wxMSW supports this winPopup->Hide(); #endif winPopup->Create( GetParent(), -1 ); m_winPopup = winPopup; } #endif CreatePopup(); winPopup = m_winPopup; popup = m_popup; } else { popup = m_popup; } wxASSERT( !m_popup || m_popup == popup ); // Consistency check. wxSize adjustedSize = m_popupInterface->GetAdjustedSize(widthPopup, m_heightPopup<=0?DEFAULT_POPUP_HEIGHT:m_heightPopup, maxHeightPopup); popup->SetSize(adjustedSize); popup->Move(0,0); m_popupInterface->OnPopup(); #if ALLOW_FAKE_POPUP // Make sure fake popup didn't get too big if ( m_fakePopupUsage == 2 && popup->GetSize().x > screenWidth ) { popup->SetSize(screenWidth-2,popup->GetSize().y); } #endif // // Reposition and resize popup window // wxSize szp = popup->GetSize(); int popupX; int popupY = scrPos.y + ctrlSz.y; // Anchor popup to the side the dropbutton is on if ( m_btnSide == wxRIGHT ) popupX = scrPos.x + ctrlSz.x + m_extRight- szp.x; else popupX = scrPos.x - m_extLeft; #if ALLOW_FAKE_POPUP if ( m_fakePopupUsage == 2 ) { if ( spaceBelow < szp.y ) { if ( spaceAbove > spaceBelow ) { if ( szp.y > spaceAbove ) { popup->SetSize(szp.x,spaceAbove); szp.y = spaceAbove; } popupY = scrPos.y - szp.y; } else { if ( szp.y > spaceBelow ) { popup->SetSize(szp.x,spaceBelow); szp.y = spaceBelow; } } } } else #endif if ( spaceBelow < szp.y ) { popupY = scrPos.y - szp.y; } // Move to position //wxLogDebug(wxT("popup scheduled position1: %i,%i"),ptp.x,ptp.y); //wxLogDebug(wxT("popup position1: %i,%i"),winPopup->GetPosition().x,winPopup->GetPosition().y); // Some platforms (GTK) may need these two to be separate winPopup->SetSize( szp.x, szp.y ); winPopup->Move( popupX, popupY ); //wxLogDebug(wxT("popup position2: %i,%i"),winPopup->GetPosition().x,winPopup->GetPosition().y); m_popup = popup; // Set string selection (must be this way instead of SetStringSelection) if ( m_text ) { if ( !(m_iFlags & wxPGCC_NO_TEXT_AUTO_SELECT) ) m_text->SelectAll(); m_popupInterface->SetStringValue( m_text->GetValue() ); } else { // This is neede since focus/selection indication may change when popup is shown // FIXME: But in that case, would m_isPopupShown need to go before this? Refresh(); } // This must be after SetStringValue m_isPopupShown = true; // Show it #if USE_TRANSIENT_POPUP ((wxPopupTransientWindow*)winPopup)->Popup(popup); #else winPopup->Show(); #endif #if INSTALL_TOPLEV_HANDLER // If our real popup is wxDialog, then only install handler // incase of fake popup. #if !wxUSE_POPUPWIN if ( m_fakePopupUsage != 2 ) { if ( m_toplevEvtHandler ) { delete m_toplevEvtHandler; m_toplevEvtHandler = (wxEvtHandler*) NULL; } } else #endif { // Put top level window event handler into place if ( !m_toplevEvtHandler ) m_toplevEvtHandler = new wxPGComboFrameEventHandler(this); wxWindow* toplev = ::wxGetTopLevelParent( this ); wxASSERT( toplev ); ((wxPGComboFrameEventHandler*)m_toplevEvtHandler)->OnPopup(); toplev->PushEventHandler( m_toplevEvtHandler ); } #endif } void wxPGComboControlBase::OnPopupDismiss() { // Just in case, avoid double dismiss if ( !m_isPopupShown ) return; // *Must* set this before focus etc. m_isPopupShown = false; // Inform popup control itself m_popupInterface->OnDismiss(); //((wxComboDropButton*)m_btn)->SetPopup( (wxWindow*) NULL ); if ( m_popupExtraHandler ) ((wxPGComboPopupExtraEventHandler*)m_popupExtraHandler)->OnPopupDismiss(); #if INSTALL_TOPLEV_HANDLER // Remove top level window event handler if ( m_toplevEvtHandler ) { wxWindow* toplev = ::wxGetTopLevelParent( this ); if ( toplev ) toplev->RemoveEventHandler( m_toplevEvtHandler ); } #endif #if !wxUSE_POPUPWIN if ( m_fakePopupUsage != 2 ) GetParent()->SetFocus(); #endif m_timeCanAcceptClick = ::wxGetLocalTimeMillis() + 150; // If cursor not on dropdown button, then clear its state // (technically not required by all ports, but do it for all just in case) if ( !m_btnArea.wxPGRectContains(ScreenToClient(::wxGetMousePosition())) ) m_btnState = 0; // Return parent's tab traversal flag. // See ShowPopup for notes. if ( m_iFlags & wxPGCC_IFLAG_PARENT_TAB_TRAVERSAL ) { wxWindow* parent = GetParent(); parent->SetWindowStyle( parent->GetWindowStyle() | wxTAB_TRAVERSAL ); m_iFlags &= ~(wxPGCC_IFLAG_PARENT_TAB_TRAVERSAL); } // refresh control (necessary even if m_text) Refresh(); } void wxPGComboControlBase::HidePopup() { // Should be able to call this without popup interface //wxCHECK_RET( m_popupInterface, _T("no popup interface") ); if ( !m_isPopupShown ) return; // transfer value and show it in textctrl, if any SetValue( m_popupInterface->GetStringValue() ); #if USE_TRANSIENT_POPUP ((wxPopupTransientWindow*)m_winPopup)->Dismiss(); #else m_winPopup->Hide(); #endif OnPopupDismiss(); } // ---------------------------------------------------------------------------- // customization methods // ---------------------------------------------------------------------------- void wxPGComboControlBase::SetButtonPosition( int width, int height, int side, int spacingX ) { m_btnWid = width; m_btnHei = height; m_btnSide = side; m_btnSpacingX = spacingX; RecalcAndRefresh(); } void wxPGComboControlBase::SetButtonBitmaps( const wxBitmap& bmpNormal, bool blankButtonBg, const wxBitmap& bmpPressed, const wxBitmap& bmpHover, const wxBitmap& bmpDisabled ) { m_bmpNormal = bmpNormal; m_blankButtonBg = blankButtonBg; if ( bmpPressed.Ok() ) m_bmpPressed = bmpPressed; else m_bmpPressed = bmpNormal; if ( bmpHover.Ok() ) m_bmpHover = bmpHover; else m_bmpHover = bmpNormal; if ( bmpDisabled.Ok() ) m_bmpDisabled = bmpDisabled; else m_bmpDisabled = bmpNormal; RecalcAndRefresh(); } void wxPGComboControlBase::SetCustomPaintWidth( int width ) { if ( m_text ) { // move textctrl accordingly wxRect r = m_text->GetRect(); int inc = width - m_widthCustomPaint; r.x += inc; r.width -= inc; m_text->SetSize( r ); } m_widthCustomPaint = width; RecalcAndRefresh(); } void wxPGComboControlBase::SetTextIndent( int indent ) { if ( indent < 0 ) { m_absIndent = GetNativeTextIndent(); m_iFlags &= ~(wxPGCC_IFLAG_INDENT_SET); } else { m_absIndent = indent; m_iFlags |= wxPGCC_IFLAG_INDENT_SET; } RecalcAndRefresh(); } wxCoord wxPGComboControlBase::GetNativeTextIndent() const { return DEFAULT_TEXT_INDENT; } // ---------------------------------------------------------------------------- // methods forwarded to wxTextCtrl // ---------------------------------------------------------------------------- wxString wxPGComboControlBase::GetValue() const { if ( m_text ) return m_text->GetValue(); return m_valueString; } void wxPGComboControlBase::SetValue(const wxString& value) { if ( m_text ) { m_text->SetValue(value); if ( !(m_iFlags & wxPGCC_NO_TEXT_AUTO_SELECT) ) m_text->SelectAll(); } // Since wxPGComboPopup may want to paint the combo as well, we need // to set the string value here (as well as sometimes in ShowPopup). if ( m_valueString != value && m_popupInterface ) { m_popupInterface->SetStringValue(value); } m_valueString = value; Refresh(); } void wxPGComboControlBase::Copy() { if ( m_text ) m_text->Copy(); } void wxPGComboControlBase::Cut() { if ( m_text ) m_text->Cut(); } void wxPGComboControlBase::Paste() { if ( m_text ) m_text->Paste(); } void wxPGComboControlBase::SetInsertionPoint(long pos) { if ( m_text ) m_text->SetInsertionPoint(pos); } void wxPGComboControlBase::SetInsertionPointEnd() { if ( m_text ) m_text->SetInsertionPointEnd(); } long wxPGComboControlBase::GetInsertionPoint() const { if ( m_text ) return m_text->GetInsertionPoint(); return 0; } long wxPGComboControlBase::GetLastPosition() const { if ( m_text ) return m_text->GetLastPosition(); return 0; } void wxPGComboControlBase::Replace(long from, long to, const wxString& value) { if ( m_text ) m_text->Replace(from, to, value); } void wxPGComboControlBase::Remove(long from, long to) { if ( m_text ) m_text->Remove(from, to); } void wxPGComboControlBase::SetSelection(long from, long to) { if ( m_text ) m_text->SetSelection(from, to); } void wxPGComboControlBase::Undo() { if ( m_text ) m_text->Undo(); } // ---------------------------------------------------------------------------- // wxPGGenericComboControl // ---------------------------------------------------------------------------- BEGIN_EVENT_TABLE(wxPGGenericComboControl, wxPGComboControlBase) //EVT_SIZE(wxPGGenericComboControl::OnSizeEvent) EVT_PAINT(wxPGGenericComboControl::OnPaintEvent) EVT_MOUSE_EVENTS(wxPGGenericComboControl::OnMouseEvent) END_EVENT_TABLE() IMPLEMENT_DYNAMIC_CLASS(wxPGGenericComboControl, wxPGComboControlBase) void wxPGGenericComboControl::Init() { } bool wxPGGenericComboControl::Create(wxWindow *parent, wxWindowID id, const wxString& value, const wxPoint& pos, const wxSize& size, long style, const wxValidator& validator, const wxString& name) { // Set border long border = style & wxBORDER_MASK; #if defined(__WXUNIVERSAL__) if ( !border ) border = wxBORDER_SIMPLE; #elif defined(__WXMSW__) if ( !border ) border = wxBORDER_SIMPLE; #else if ( !border ) { border = wxBORDER_NONE; m_widthCustomBorder = 1; } Customize( wxPGCC_BUTTON_OUTSIDE_BORDER | wxPGCC_NO_TEXT_AUTO_SELECT | wxPGCC_BUTTON_STAYS_DOWN ); #endif style = (style & ~(wxBORDER_MASK)) | border; // create main window if ( !wxPGComboControlBase::Create(parent, id, value, wxDefaultPosition, wxDefaultSize, style | wxFULL_REPAINT_ON_RESIZE, wxDefaultValidator, name) ) return false; // Create textctrl, if necessary CreateTextCtrl( wxBORDER_NONE, validator ); // Add keyboard input handlers for main control and textctrl InstallInputHandlers(); // Set background SetBackgroundStyle( wxBG_STYLE_CUSTOM ); // for double-buffering // SetSize should be called last SetSize(pos.x,pos.y,size.x,size.y); return true; } wxPGGenericComboControl::~wxPGGenericComboControl() { } void wxPGGenericComboControl::OnResize() { // Recalculates button and textctrl areas CalculateAreas(DEFAULT_DROPBUTTON_WIDTH); #if 0 // Move separate button control, if any, to correct position if ( m_btn ) { wxSize sz = GetClientSize(); m_btn->SetSize( m_btnArea.x + m_btnSpacingX, (sz.y-m_btnSize.y)/2, m_btnSize.x, m_btnSize.y ); } #endif // Move textctrl, if any, accordingly PositionTextCtrl( TEXTCTRLXADJUST, TEXTCTRLYADJUST ); } void wxPGGenericComboControl::OnPaintEvent( wxPaintEvent& WXUNUSED(event) ) { wxSize sz = GetClientSize(); #if !wxCHECK_VERSION(2, 7, 1) // If size is larger, recalculate double buffer bitmap if ( !gs_doubleBuffer || sz.x > gs_doubleBuffer->GetWidth() || sz.y > gs_doubleBuffer->GetHeight() ) { delete gs_doubleBuffer; gs_doubleBuffer = new wxBitmap(sz.x+25,sz.y); } wxBufferedPaintDC dc(this,*gs_doubleBuffer); #else wxAutoBufferedPaintDC dc(this); #endif const wxRect& rectb = m_btnArea; wxRect rect = m_tcArea; // artificial simple border if ( m_widthCustomBorder ) { int customBorder = m_widthCustomBorder; // Set border colour wxPen pen1( wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT), customBorder, wxSOLID ); dc.SetPen( pen1 ); // area around both controls wxRect rect2(0,0,sz.x,sz.y); if ( m_iFlags & wxPGCC_IFLAG_BUTTON_OUTSIDE ) { rect2 = m_tcArea; if ( customBorder == 1 ) { rect2.Inflate(1); } else { #ifdef __WXGTK__ rect2.x -= 1; rect2.y -= 1; #else rect2.x -= customBorder; rect2.y -= customBorder; #endif rect2.width += 1 + customBorder; rect2.height += 1 + customBorder; } } dc.SetBrush( *wxTRANSPARENT_BRUSH ); dc.DrawRectangle(rect2); } wxColour winCol = GetBackgroundColour(); dc.SetBrush(winCol); dc.SetPen(winCol); //wxLogDebug(wxT("hei: %i tcy: %i tchei: %i"),GetClientSize().y,m_tcArea.y,m_tcArea.height); //wxLogDebug(wxT("btnx: %i tcx: %i tcwid: %i"),m_btnArea.x,m_tcArea.x,m_tcArea.width); // clear main background dc.DrawRectangle(rect); if ( !m_btn ) // Standard button rendering DrawButton(dc, rectb); // paint required portion on the control if ( !m_text || m_widthCustomPaint ) { wxASSERT( m_widthCustomPaint >= 0 ); // this is intentionally here to allow drawed rectangle's // right edge to be hidden if ( m_text ) rect.width = m_widthCustomPaint; dc.SetFont( GetFont() ); dc.SetClippingRegion(rect); m_popupInterface->PaintComboControl(dc, rect); } } void wxPGGenericComboControl::OnMouseEvent( wxMouseEvent& event ) { bool isOnButtonArea = m_btnArea.wxPGRectContains(event.m_x,event.m_y); int handlerFlags = isOnButtonArea ? wxPGCC_MF_ON_BUTTON : 0; // Preprocessing fabricates double-clicks and prevents // (it may also do other common things in future) if ( PreprocessMouseEvent(event,handlerFlags) ) return; #ifdef __WXMSW__ const bool ctrlIsButton = true; #else const bool ctrlIsButton = false; #endif if ( ctrlIsButton && (m_windowStyle & (wxPGCC_DCLICK_CYCLES|wxCB_READONLY)) == wxCB_READONLY ) { // if no textctrl and no special double-click, then the entire control acts // as a button handlerFlags |= wxPGCC_MF_ON_BUTTON; if ( HandleButtonMouseEvent(event,handlerFlags) ) return; } else { if ( isOnButtonArea || m_btnState & wxCONTROL_PRESSED ) { if ( HandleButtonMouseEvent(event,handlerFlags) ) return; } else if ( m_btnState ) { // otherwise need to clear the hover status m_btnState = 0; RefreshRect(m_btnArea); } } // // This will handle left_down and left_dclick events outside button in a Windows/GTK-like manner. // See header file for further information on this method. HandleNormalMouseEvent(event); } // ---------------------------------------------------------------------------- // wxComboControl // ---------------------------------------------------------------------------- #if defined(__WXMSW__) && !defined(__WXUNIVERSAL__) // Change to #if 1 to include tmschema.h for easier testing of theme // parameters. #if 0 #include #include #else //---------------------------------- #define EP_EDITTEXT 1 #define ETS_NORMAL 1 #define ETS_HOT 2 #define ETS_SELECTED 3 #define ETS_DISABLED 4 #define ETS_FOCUSED 5 #define ETS_READONLY 6 #define ETS_ASSIST 7 #define TMT_FILLCOLOR 3802 #define TMT_TEXTCOLOR 3803 #define TMT_BORDERCOLOR 3801 #define TMT_EDGEFILLCOLOR 3808 #define TMT_BGTYPE 4001 #define BT_IMAGEFILE 0 #define BT_BORDERFILL 1 #define CP_DROPDOWNBUTTON 1 #define CP_BACKGROUND 2 // This and above are Vista and later only #define CP_TRANSPARENTBACKGROUND 3 #define CP_BORDER 4 #define CP_READONLY 5 #define CP_DROPDOWNBUTTONRIGHT 6 #define CP_DROPDOWNBUTTONLEFT 7 #define CP_CUEBANNER 8 #define CBXS_NORMAL 1 #define CBXS_HOT 2 #define CBXS_PRESSED 3 #define CBXS_DISABLED 4 #define CBXSR_NORMAL 1 #define CBXSR_HOT 2 #define CBXSR_PRESSED 3 #define CBXSR_DISABLED 4 #define CBXSL_NORMAL 1 #define CBXSL_HOT 2 #define CBXSL_PRESSED 3 #define CBXSL_DISABLED 4 #define CBTBS_NORMAL 1 #define CBTBS_HOT 2 #define CBTBS_DISABLED 3 #define CBTBS_FOCUSED 4 #define CBB_NORMAL 1 #define CBB_HOT 2 #define CBB_FOCUSED 3 #define CBB_DISABLED 4 #define CBRO_NORMAL 1 #define CBRO_HOT 2 #define CBRO_PRESSED 3 #define CBRO_DISABLED 4 #define CBCB_NORMAL 1 #define CBCB_HOT 2 #define CBCB_PRESSED 3 #define CBCB_DISABLED 4 #endif #define NATIVE_TEXT_INDENT_XP 4 #define NATIVE_TEXT_INDENT_CLASSIC 2 #define TEXTCTRLXADJUST_XP 0 #define TEXTCTRLYADJUST_XP 4 #define TEXTCTRLXADJUST_CLASSIC 0 #define TEXTCTRLYADJUST_CLASSIC 4 BEGIN_EVENT_TABLE(wxPGComboControl, wxPGComboControlBase) EVT_PAINT(wxPGComboControl::OnPaintEvent) EVT_MOUSE_EVENTS(wxPGComboControl::OnMouseEvent) END_EVENT_TABLE() IMPLEMENT_DYNAMIC_CLASS(wxPGComboControl, wxPGComboControlBase) void wxPGComboControl::Init() { } bool wxPGComboControl::Create(wxWindow *parent, wxWindowID id, const wxString& value, const wxPoint& pos, const wxSize& size, long style, const wxValidator& validator, const wxString& name) { // Set border long border = style & wxBORDER_MASK; wxUxThemeEngine* theme = wxUxThemeEngine::GetIfActive(); if ( !border ) { // For XP, have 1-width custom border, for older version use sunken if ( theme ) { border = wxBORDER_NONE; m_widthCustomBorder = 1; } else border = wxBORDER_SUNKEN; style = (style & ~(wxBORDER_MASK)) | border; } //Customize( wxPGCC_BUTTON_OUTSIDE_BORDER ); // create main window if ( !wxPGComboControlBase::Create(parent, id, value, wxDefaultPosition, wxDefaultSize, style | wxFULL_REPAINT_ON_RESIZE, wxDefaultValidator, name) ) return false; if ( theme ) { #if wxCHECK_VERSION(2, 8, 0) const bool isVista = (::wxGetWinVersion() >= wxWinVersion_6); #else int Major = 0; int family = wxGetOsVersion(&Major, NULL); const bool isVista = ((family == wxWINDOWS_NT) && (Major >= 6)); #endif if ( isVista ) m_iFlags |= wxPGCC_BUTTON_STAYS_DOWN; } // Create textctrl, if necessary CreateTextCtrl( wxNO_BORDER, validator ); // Add keyboard input handlers for main control and textctrl InstallInputHandlers(); // Prepare background for double-buffering SetBackgroundStyle( wxBG_STYLE_CUSTOM ); // SetSize should be called last SetSize(pos.x,pos.y,size.x,size.y); return true; } wxPGComboControl::~wxPGComboControl() { } void wxPGComboControl::OnThemeChange() { wxUxThemeEngine* theme = wxUxThemeEngine::GetIfActive(); if ( theme ) { wxUxThemeHandle hTheme(this, L"COMBOBOX"); COLORREF col; theme->GetThemeColor(hTheme,EP_EDITTEXT,ETS_NORMAL,TMT_FILLCOLOR,&col); SetBackgroundColour(wxRGBToColour(col)); theme->GetThemeColor(hTheme,EP_EDITTEXT,ETS_NORMAL,TMT_TEXTCOLOR,&col); SetForegroundColour(wxRGBToColour(col)); } else { SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); } } //void wxPGComboControl::OnSizeEvent( wxSizeEvent& event ) void wxPGComboControl::OnResize() { // // Recalculates button and textctrl areas int textCtrlXAdjust; int textCtrlYAdjust; if ( wxUxThemeEngine::GetIfActive() ) { textCtrlXAdjust = TEXTCTRLXADJUST_XP; textCtrlYAdjust = TEXTCTRLYADJUST_XP; } else { textCtrlXAdjust = TEXTCTRLXADJUST_CLASSIC; textCtrlYAdjust = TEXTCTRLYADJUST_CLASSIC; } // Technically Classic Windows style combo has more narrow button, // but the native renderer doesn't paint it well like that. int btnWidth = 17; CalculateAreas(btnWidth); // Position textctrl using standard routine PositionTextCtrl(textCtrlXAdjust,textCtrlYAdjust); } /* // Draws non-XP GUI dotted line around the focus area static void wxMSWDrawFocusRect( wxDC& dc, const wxRect& rect ) { #if !defined(__WXWINCE__) dc.SetLogicalFunction(wxINVERT); wxPen pen(*wxBLACK,1,wxDOT); pen.SetCap(wxCAP_BUTT); dc.SetPen(pen); dc.SetBrush(*wxTRANSPARENT_BRUSH); dc.DrawRectangle(rect); dc.SetLogicalFunction(wxCOPY); #else dc.SetLogicalFunction(wxINVERT); dc.SetPen(wxPen(*wxBLACK,1,wxDOT)); dc.SetBrush(*wxTRANSPARENT_BRUSH); dc.DrawRectangle(rect); dc.SetLogicalFunction(wxCOPY); #endif } */ /* // draw focus background on area in a way typical on platform void wxPGComboControl::DrawFocusBackground( wxDC& dc, const wxRect& rect, int flags ) { wxUxThemeEngine* theme = (wxUxThemeEngine*) NULL; wxUxThemeHandle hTheme(this, L"COMBOBOX"); //COLORREF cref; wxSize sz = GetClientSize(); bool isEnabled; bool isFocused; // also selected // For smaller size control (and for disabled background) use less spacing int focusSpacingX; int focusSpacingY; if ( !(flags & wxCONTROL_ISSUBMENU) ) { // Drawing control isEnabled = IsEnabled(); isFocused = ShouldDrawFocus(); // Windows-style: for smaller size control (and for disabled background) use less spacing if ( hTheme ) { // WinXP Theme focusSpacingX = isEnabled ? 2 : 1; focusSpacingY = sz.y > (GetCharHeight()+2) && isEnabled ? 2 : 1; } else { // Classic Theme if ( isEnabled ) { focusSpacingX = 1; focusSpacingY = 1; } else { focusSpacingX = 0; focusSpacingY = 0; } } } else { // Drawing a list item isEnabled = true; // they are never disabled isFocused = flags & wxCONTROL_SELECTED ? true : false; focusSpacingX = 0; focusSpacingY = 0; } // Set the background sub-rectangle for selection, disabled etc wxRect selRect(rect); selRect.y += focusSpacingY; selRect.height -= (focusSpacingY*2); int wcp = 0; if ( !(flags & wxCONTROL_ISSUBMENU) ) wcp += m_widthCustomPaint; selRect.x += wcp + focusSpacingX; selRect.width -= wcp + (focusSpacingX*2); if ( hTheme ) theme = wxUxThemeEngine::GetIfActive(); wxColour bgCol; bool drawDottedEdge = false; if ( isEnabled ) { // If popup is hidden and this control is focused, // then draw the focus-indicator (selbgcolor background etc.). if ( isFocused ) { #if 0 // TODO: Proper theme color getting (JMS: I don't know which parts/colors to use, // those below don't work) if ( hTheme ) { theme->GetThemeColor(hTheme,EP_EDITTEXT,ETS_SELECTED,TMT_TEXTCOLOR,&cref); dc.SetTextForeground( wxRGBToColour(cref) ); theme->GetThemeColor(hTheme,EP_EDITTEXT,ETS_SELECTED,TMT_FILLCOLOR,&cref); bgCol = wxRGBToColour(cref); } else #endif { dc.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT) ); bgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT); if ( m_windowStyle & wxCB_READONLY ) drawDottedEdge = true; } } else { bgCol = GetBackgroundColour(); } } else { dc.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT) ); bgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE); } dc.SetBrush(bgCol); dc.SetPen(bgCol); dc.DrawRectangle(selRect); //if ( drawDottedEdge ) // wxMSWDrawFocusRect(dc,selRect); } */ void wxPGComboControl::OnPaintEvent( wxPaintEvent& WXUNUSED(event) ) { // TODO: Convert drawing in this function to Windows API Code wxSize sz = GetClientSize(); #if !wxCHECK_VERSION(2, 7, 1) // If size is larger, recalculate double buffer bitmap if ( !gs_doubleBuffer || sz.x > gs_doubleBuffer->GetWidth() || sz.y > gs_doubleBuffer->GetHeight() ) { delete gs_doubleBuffer; gs_doubleBuffer = new wxBitmap(sz.x+25,sz.y); } wxBufferedPaintDC dc(this,*gs_doubleBuffer); #else wxAutoBufferedPaintDC dc(this); #endif const wxRect& rectButton = m_btnArea; wxRect rectTextField = m_tcArea; const bool isEnabled = IsEnabled(); wxColour bgCol = GetBackgroundColour(); HDC hDc = GetHdcOf(dc); HWND hWnd = GetHwndOf(this); wxUxThemeEngine* theme = NULL; wxUxThemeHandle hTheme(this, L"COMBOBOX"); if ( hTheme ) theme = wxUxThemeEngine::GetIfActive(); wxRect borderRect(0,0,sz.x,sz.y); if ( m_iFlags & wxPGCC_IFLAG_BUTTON_OUTSIDE ) { borderRect = m_tcArea; borderRect.Inflate(1); } int drawButFlags = 0; if ( hTheme ) { #if wxCHECK_VERSION(2, 8, 0) const bool useVistaComboBox = (::wxGetWinVersion() >= wxWinVersion_6); #else int Major = 0; int family = wxGetOsVersion(&Major, NULL); const bool useVistaComboBox = ((family == wxWINDOWS_NT) && (Major >= 6)); #endif RECT rFull; wxCopyRectToRECT(borderRect, rFull); RECT rButton; wxCopyRectToRECT(rectButton, rButton); RECT rBorder; wxCopyRectToRECT(borderRect, rBorder); bool isNonStdButton = (m_iFlags & wxPGCC_IFLAG_BUTTON_OUTSIDE) || (m_iFlags & wxPGCC_IFLAG_HAS_NONSTANDARD_BUTTON); // // Get some states for themed drawing int butState; if ( !isEnabled ) { butState = CBXS_DISABLED; } // Vista will display the drop-button as depressed always // when the popup window is visilbe else if ( (m_btnState & wxCONTROL_PRESSED) || (useVistaComboBox && IsPopupShown()) ) { butState = CBXS_PRESSED; } else if ( m_btnState & wxCONTROL_CURRENT ) { butState = CBXS_HOT; } else { butState = CBXS_NORMAL; } int comboBoxPart = 0; // For XP, use the 'default' part RECT* rUseForBg = &rBorder; bool drawFullButton = false; int bgState = butState; const bool isFocused = IsFocused(); if ( useVistaComboBox ) { // FIXME: Either SetBackgroundColour or GetBackgroundColour // doesn't work under Vista, so here's a temporary // workaround. bgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); // Draw the entire control as a single button? /* if ( !isNonStdButton ) { if ( HasFlag(wxCB_READONLY) ) drawFullButton = true; } */ if ( drawFullButton ) { comboBoxPart = CP_READONLY; rUseForBg = &rFull; // It should be safe enough to update this flag here. m_iFlags |= wxPGCC_FULL_BUTTON; } else { comboBoxPart = CP_BORDER; m_iFlags &= ~wxPGCC_FULL_BUTTON; if ( isFocused ) bgState = CBB_FOCUSED; else bgState = CBB_NORMAL; } } // // Draw parent's background, if necessary RECT* rUseForTb = NULL; if ( theme->IsThemeBackgroundPartiallyTransparent( hTheme, comboBoxPart, bgState ) ) rUseForTb = &rFull; else if ( m_iFlags & wxPGCC_IFLAG_BUTTON_OUTSIDE ) rUseForTb = &rButton; if ( rUseForTb ) theme->DrawThemeParentBackground( hWnd, hDc, rUseForTb ); // // Draw the control background (including the border) if ( m_widthCustomBorder > 0 ) { theme->DrawThemeBackground( hTheme, hDc, comboBoxPart, bgState, rUseForBg, NULL ); } else { // No border. We can't use theme, since it cannot be relied on // to deliver borderless drawing, even with DrawThemeBackgroundEx. dc.SetBrush(bgCol); dc.SetPen(bgCol); dc.DrawRectangle(borderRect); } // // Draw the drop-button if ( !isNonStdButton ) { drawButFlags = Button_BitmapOnly; int butPart = CP_DROPDOWNBUTTON; if ( useVistaComboBox && m_widthCustomBorder > 0 ) { if ( drawFullButton ) { // We need to alter the button style slightly before // drawing the actual button (but it was good above // when background etc was done). if ( butState == CBXS_HOT || butState == CBXS_PRESSED ) butState = CBXS_NORMAL; } if ( m_btnSide == wxRIGHT ) butPart = CP_DROPDOWNBUTTONRIGHT; else butPart = CP_DROPDOWNBUTTONLEFT; } theme->DrawThemeBackground( hTheme, hDc, butPart, butState, &rButton, NULL ); } else if ( useVistaComboBox && (m_iFlags & wxPGCC_IFLAG_BUTTON_OUTSIDE) ) { // We'll do this, because DrawThemeParentBackground // doesn't seem to be reliable on Vista. drawButFlags |= Button_PaintBackground; } } else { // Windows 2000 and earlier drawButFlags = Button_PaintBackground; dc.SetBrush(bgCol); dc.SetPen(bgCol); dc.DrawRectangle(borderRect); } // Button rendering (may only do the bitmap on button, depending on the flags) DrawButton( dc, rectButton, drawButFlags ); // Paint required portion of the custom image on the control if ( (!m_text || m_widthCustomPaint) ) { wxASSERT( m_widthCustomPaint >= 0 ); // this is intentionally here to allow drawed rectangle's // right edge to be hidden if ( m_text ) rectTextField.width = m_widthCustomPaint; dc.SetFont( GetFont() ); dc.SetClippingRegion(rectTextField); m_popupInterface->PaintComboControl(dc,rectTextField); } } void wxPGComboControl::OnMouseEvent( wxMouseEvent& event ) { bool isOnButtonArea = m_btnArea.wxPGRectContains(event.m_x,event.m_y); int handlerFlags = isOnButtonArea ? wxPGCC_MF_ON_BUTTON : 0; // Preprocessing fabricates double-clicks and prevents // (it may also do other common things in future) if ( PreprocessMouseEvent(event,isOnButtonArea) ) return; if ( (m_windowStyle & (wxPGCC_DCLICK_CYCLES|wxCB_READONLY)) == wxCB_READONLY ) { // if no textctrl and no special double-click, then the entire control acts // as a button handlerFlags |= wxPGCC_MF_ON_BUTTON; if ( HandleButtonMouseEvent(event,handlerFlags) ) return; } else { if ( isOnButtonArea || m_btnState & wxCONTROL_PRESSED ) { if ( HandleButtonMouseEvent(event,handlerFlags) ) return; } else if ( m_btnState ) { // otherwise need to clear the hover status m_btnState = 0; RefreshRect(m_btnArea); } } // // This will handle left_down and left_dclick events outside button in a Windows-like manner. // See header file for further information on this method. HandleNormalMouseEvent(event); } wxCoord wxPGComboControl::GetNativeTextIndent() const { if ( wxUxThemeEngine::GetIfActive() ) return NATIVE_TEXT_INDENT_XP; return NATIVE_TEXT_INDENT_CLASSIC; } #else IMPLEMENT_DYNAMIC_CLASS(wxPGComboControl, wxPGComboControlBase) #endif // #if defined(__WXMSW__) && !defined(__WXUNIVERSAL__) // ---------------------------------------------------------------------------- // wxPGOwnerDrawnComboBox // ---------------------------------------------------------------------------- IMPLEMENT_DYNAMIC_CLASS(wxPGOwnerDrawnComboBox, wxPGComboControl) BEGIN_EVENT_TABLE(wxPGOwnerDrawnComboBox, wxPGComboControl) END_EVENT_TABLE() void wxPGOwnerDrawnComboBox::Init() { } bool wxPGOwnerDrawnComboBox::Create(wxWindow *parent, wxWindowID id, const wxString& value, const wxPoint& pos, const wxSize& size, long style, const wxValidator& validator, const wxString& name) { return wxPGComboControl::Create(parent,id,value,pos,size,style,validator,name); } wxPGOwnerDrawnComboBox::wxPGOwnerDrawnComboBox(wxWindow *parent, wxWindowID id, const wxString& value, const wxPoint& pos, const wxSize& size, const wxArrayString& choices, long style, const wxValidator& validator, const wxString& name) : wxPGComboControl() { Init(); Create(parent,id,value,pos,size,choices,style, validator, name); } bool wxPGOwnerDrawnComboBox::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(), /*callback,*/ style, validator, name); } bool wxPGOwnerDrawnComboBox::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 ( !Create(parent, id, value, pos, size, style, validator, name) ) { return false; } wxPGVListBoxComboPopup* iface = new wxPGVListBoxComboPopup(this); SetPopup(iface); m_popupInterface = iface; // Add initial choices to the interface iface->Populate(n,choices); return true; } wxPGOwnerDrawnComboBox::~wxPGOwnerDrawnComboBox() { if ( m_popupInterface ) m_popupInterface->ClearClientDatas(); } // ---------------------------------------------------------------------------- // wxPGOwnerDrawnComboBox item manipulation methods // ---------------------------------------------------------------------------- void wxPGOwnerDrawnComboBox::Clear() { wxASSERT( m_popupInterface ); m_popupInterface->Clear(); GetTextCtrl()->SetValue(wxEmptyString); } void wxPGOwnerDrawnComboBox::Delete(wxODCIndex n) { wxCHECK_RET( (n >= 0) && (n < GetCount()), _T("invalid index in wxPGOwnerDrawnComboBox::Delete") ); if ( GetSelection() == (int) n ) SetValue(wxEmptyString); m_popupInterface->Delete(n); } wxODCCount wxPGOwnerDrawnComboBox::GetCount() const { wxASSERT( m_popupInterface ); return m_popupInterface->GetCount(); } wxString wxPGOwnerDrawnComboBox::GetString(wxODCIndex n) const { wxCHECK_MSG( (n >= 0) && (n < GetCount()), wxEmptyString, _T("invalid index in wxPGOwnerDrawnComboBox::GetString") ); return m_popupInterface->GetString(n); } void wxPGOwnerDrawnComboBox::SetString(wxODCIndex n, const wxString& s) { wxCHECK_RET( (n >= 0) && (n < GetCount()), _T("invalid index in wxPGOwnerDrawnComboBox::SetString") ); m_popupInterface->SetString(n,s); } int wxPGOwnerDrawnComboBox::FindString(const wxString& s) const { wxASSERT( m_popupInterface ); return m_popupInterface->FindString(s); } void wxPGOwnerDrawnComboBox::Select(int n) { wxCHECK_RET( (n >= -1) && (n < (int)GetCount()), _T("invalid index in wxPGOwnerDrawnComboBox::Select") ); wxASSERT( m_popupInterface ); m_popupInterface->SetSelection(n); wxString str; if ( n >= 0 ) str = m_popupInterface->GetString(n); // Refresh text portion in control if ( m_text ) m_text->SetValue( str ); else m_valueString = str; Refresh(); } int wxPGOwnerDrawnComboBox::GetSelection() const { wxASSERT( m_popupInterface ); return m_popupInterface->GetSelection(); } int wxPGOwnerDrawnComboBox::DoAppend(const wxString& item) { wxASSERT( m_popupInterface ); return m_popupInterface->Append(item); } int wxPGOwnerDrawnComboBox::DoInsert(const wxString& item, wxODCIndex pos) { wxCHECK_MSG(!(GetWindowStyle() & wxCB_SORT), -1, wxT("can't insert into sorted list")); wxCHECK_MSG((pos>=0) && (pos<=GetCount()), -1, wxT("invalid index")); m_popupInterface->Insert(item,pos); return pos; } #if wxCHECK_VERSION(2,9,0) int wxPGOwnerDrawnComboBox::DoInsertItems(const wxArrayStringsAdapter& items, unsigned int pos, void **clientData, wxClientDataType type) { unsigned int i; for ( i=0; iSetItemClientData(n,clientData, #if wxCHECK_VERSION(2,9,0) GetClientDataType() #else m_clientDataItemsType #endif ); } void* wxPGOwnerDrawnComboBox::DoGetItemClientData(wxODCIndex n) const { wxASSERT(m_popupInterface); return m_popupInterface->GetItemClientData(n); } void wxPGOwnerDrawnComboBox::DoSetItemClientObject(wxODCIndex n, wxClientData* clientData) { DoSetItemClientData(n, (void*) clientData); } wxClientData* wxPGOwnerDrawnComboBox::DoGetItemClientObject(wxODCIndex n) const { return (wxClientData*) DoGetItemClientData(n); } #endif // wxUSE_COMBOBOX