///////////////////////////////////////////////////////////////////////////// // Name: src/common/combocmn.cpp // Purpose: wxComboCtrlBase // Author: Jaakko Salli // Modified by: // Created: Apr-30-2006 // RCS-ID: $Id$ // Copyright: (c) 2005 Jaakko Salli // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// // ============================================================================ // declarations // ============================================================================ // ---------------------------------------------------------------------------- // headers // ---------------------------------------------------------------------------- #include "wx/wxprec.h" #ifdef __BORLANDC__ #pragma hdrstop #endif #if wxUSE_COMBOCTRL #include "wx/combobox.h" #ifndef WX_PRECOMP #include "wx/app.h" #include "wx/log.h" #include "wx/dcclient.h" #include "wx/settings.h" #include "wx/dialog.h" #include "wx/timer.h" #endif #include "wx/tooltip.h" #include "wx/combo.h" // constants // ---------------------------------------------------------------------------- #define DEFAULT_DROPBUTTON_WIDTH 19 #define BMP_BUTTON_MARGIN 4 #define DEFAULT_POPUP_HEIGHT 400 #define DEFAULT_TEXT_INDENT 3 #define COMBO_MARGIN 2 // spacing right of wxTextCtrl #if defined(__WXMSW__) #define USE_TRANSIENT_POPUP 1 // Use wxPopupWindowTransient (preferred, if it works properly on platform) #define TRANSIENT_POPUPWIN_IS_PERFECT 0 // wxPopupTransientWindow works, its child can have focus, and common // native controls work on it like normal. #define POPUPWIN_IS_PERFECT 0 // Same, but for non-transient popup window. #define TEXTCTRL_TEXT_CENTERED 0 // 1 if text in textctrl is vertically centered #define FOCUS_RING 0 // No focus ring on wxMSW //#undef wxUSE_POPUPWIN //#define wxUSE_POPUPWIN 0 #elif defined(__WXGTK__) // NB: It is not recommended to use wxDialog as popup on wxGTK, because of // this bug: If wxDialog is hidden, its position becomes corrupt // between hide and next show, but without internal coordinates being // reflected (or something like that - atleast commenting out ->Hide() // seemed to eliminate the position change). #define USE_TRANSIENT_POPUP 1 // Use wxPopupWindowTransient (preferred, if it works properly on platform) #define TRANSIENT_POPUPWIN_IS_PERFECT 0 // wxPopupTransientWindow works, its child can have focus, and common // native controls work on it like normal. #define POPUPWIN_IS_PERFECT 1 // Same, but for non-transient popup window. #define TEXTCTRL_TEXT_CENTERED 1 // 1 if text in textctrl is vertically centered #define FOCUS_RING 0 // No focus ring on wxGTK #elif defined(__WXMAC__) #define USE_TRANSIENT_POPUP 0 // Use wxPopupWindowTransient (preferred, if it works properly on platform) #define TRANSIENT_POPUPWIN_IS_PERFECT 0 // wxPopupTransientWindow works, its child can have focus, and common // native controls work on it like normal. #define POPUPWIN_IS_PERFECT 0 // Same, but for non-transient popup window. #define TEXTCTRL_TEXT_CENTERED 1 // 1 if text in textctrl is vertically centered #define FOCUS_RING 3 // Reserve room for the textctrl's focus ring to display #undef DEFAULT_DROPBUTTON_WIDTH #define DEFAULT_DROPBUTTON_WIDTH 22 #undef COMBO_MARGIN #define COMBO_MARGIN FOCUS_RING #else #define USE_TRANSIENT_POPUP 0 // Use wxPopupWindowTransient (preferred, if it works properly on platform) #define TRANSIENT_POPUPWIN_IS_PERFECT 0 // wxPopupTransientWindow works, its child can have focus, and common // native controls work on it like normal. #define POPUPWIN_IS_PERFECT 0 // Same, but for non-transient popup window. #define TEXTCTRL_TEXT_CENTERED 1 // 1 if text in textctrl is vertically centered #define FOCUS_RING 0 #endif // Popupwin is really only supported on wxMSW (not WINCE) and wxGTK, regardless // what the wxUSE_POPUPWIN says. // FIXME: Why isn't wxUSE_POPUPWIN reliable any longer? (it was in wxW2.6.2) #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 // Define different types of popup windows enum { POPUPWIN_NONE = 0, POPUPWIN_WXPOPUPTRANSIENTWINDOW = 1, POPUPWIN_WXPOPUPWINDOW = 2, POPUPWIN_WXDIALOG = 3 }; #if USE_TRANSIENT_POPUP // wxPopupTransientWindow is implemented #define wxComboPopupWindowBase wxPopupTransientWindow #define PRIMARY_POPUP_TYPE POPUPWIN_WXPOPUPTRANSIENTWINDOW #define USES_WXPOPUPTRANSIENTWINDOW 1 #if TRANSIENT_POPUPWIN_IS_PERFECT // #elif POPUPWIN_IS_PERFECT #define wxComboPopupWindowBase2 wxPopupWindow #define SECONDARY_POPUP_TYPE POPUPWIN_WXPOPUPWINDOW #define USES_WXPOPUPWINDOW 1 #else #define wxComboPopupWindowBase2 wxDialog #define SECONDARY_POPUP_TYPE POPUPWIN_WXDIALOG #define USES_WXDIALOG 1 #endif #elif wxUSE_POPUPWIN // wxPopupWindow (but not wxPopupTransientWindow) is properly implemented #define wxComboPopupWindowBase wxPopupWindow #define PRIMARY_POPUP_TYPE POPUPWIN_WXPOPUPWINDOW #define USES_WXPOPUPWINDOW 1 #if !POPUPWIN_IS_PERFECT #define wxComboPopupWindowBase2 wxDialog #define SECONDARY_POPUP_TYPE POPUPWIN_WXDIALOG #define USES_WXDIALOG 1 #endif #else // wxPopupWindow is not implemented #define wxComboPopupWindowBase wxDialog #define PRIMARY_POPUP_TYPE POPUPWIN_WXDIALOG #define USES_WXDIALOG 1 #endif #ifndef USES_WXPOPUPTRANSIENTWINDOW #define USES_WXPOPUPTRANSIENTWINDOW 0 #endif #ifndef USES_WXPOPUPWINDOW #define USES_WXPOPUPWINDOW 0 #endif #ifndef USES_WXDIALOG #define USES_WXDIALOG 0 #endif #if USES_WXPOPUPWINDOW #define INSTALL_TOPLEV_HANDLER 1 #else #define INSTALL_TOPLEV_HANDLER 0 #endif // // ** TODO ** // * wxComboPopupWindow for external use (ie. replace old wxUniv wxPopupComboWindow) // // ---------------------------------------------------------------------------- // wxComboFrameEventHandler 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 wxComboFrameEventHandler : public wxEvtHandler { public: wxComboFrameEventHandler( wxComboCtrlBase* pCb ); virtual ~wxComboFrameEventHandler(); 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; wxComboCtrlBase* m_combo; private: DECLARE_EVENT_TABLE() }; BEGIN_EVENT_TABLE(wxComboFrameEventHandler, wxEvtHandler) EVT_IDLE(wxComboFrameEventHandler::OnIdle) EVT_LEFT_DOWN(wxComboFrameEventHandler::OnMouseEvent) EVT_RIGHT_DOWN(wxComboFrameEventHandler::OnMouseEvent) EVT_SIZE(wxComboFrameEventHandler::OnResize) EVT_MOVE(wxComboFrameEventHandler::OnMove) EVT_MENU_HIGHLIGHT(wxID_ANY,wxComboFrameEventHandler::OnMenuEvent) EVT_MENU_OPEN(wxComboFrameEventHandler::OnMenuEvent) EVT_ACTIVATE(wxComboFrameEventHandler::OnActivate) EVT_CLOSE(wxComboFrameEventHandler::OnClose) END_EVENT_TABLE() wxComboFrameEventHandler::wxComboFrameEventHandler( wxComboCtrlBase* combo ) : wxEvtHandler() { m_combo = combo; } wxComboFrameEventHandler::~wxComboFrameEventHandler() { } void wxComboFrameEventHandler::OnPopup() { m_focusStart = ::wxWindow::FindFocus(); } void wxComboFrameEventHandler::OnIdle( wxIdleEvent& event ) { wxWindow* winFocused = ::wxWindow::FindFocus(); wxWindow* popup = m_combo->GetPopupControl()->GetControl(); 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 wxComboFrameEventHandler::OnMenuEvent( wxMenuEvent& event ) { m_combo->HidePopup(); event.Skip(); } void wxComboFrameEventHandler::OnMouseEvent( wxMouseEvent& event ) { m_combo->HidePopup(); event.Skip(); } void wxComboFrameEventHandler::OnClose( wxCloseEvent& event ) { m_combo->HidePopup(); event.Skip(); } void wxComboFrameEventHandler::OnActivate( wxActivateEvent& event ) { m_combo->HidePopup(); event.Skip(); } void wxComboFrameEventHandler::OnResize( wxSizeEvent& event ) { m_combo->HidePopup(); event.Skip(); } void wxComboFrameEventHandler::OnMove( wxMoveEvent& event ) { m_combo->HidePopup(); event.Skip(); } #endif // INSTALL_TOPLEV_HANDLER // ---------------------------------------------------------------------------- // wxComboPopupWindow is, in essence, wxPopupWindow customized for // wxComboCtrl. // ---------------------------------------------------------------------------- class wxComboPopupWindow : public wxComboPopupWindowBase { public: wxComboPopupWindow( wxComboCtrlBase *parent, int style ) #if USES_WXPOPUPWINDOW || USES_WXPOPUPTRANSIENTWINDOW : wxComboPopupWindowBase(parent,style) #else : wxComboPopupWindowBase(parent, wxID_ANY, wxEmptyString, wxPoint(-21,-21), wxSize(20,20), style) #endif { m_inShow = 0; } #if USES_WXPOPUPTRANSIENTWINDOW virtual bool Show( bool show ); virtual bool ProcessLeftDown(wxMouseEvent& event); protected: virtual void OnDismiss(); #endif private: wxByte m_inShow; }; #if USES_WXPOPUPTRANSIENTWINDOW bool wxComboPopupWindow::Show( bool show ) { // Guard against recursion if ( m_inShow ) return wxComboPopupWindowBase::Show(show); m_inShow++; wxASSERT( IsKindOf(CLASSINFO(wxPopupTransientWindow)) ); wxPopupTransientWindow* ptw = (wxPopupTransientWindow*) this; wxComboCtrlBase* combo = (wxComboCtrlBase*) GetParent(); if ( show != ptw->IsShown() ) { if ( show ) ptw->Popup(combo->GetPopupControl()->GetControl()); else ptw->Dismiss(); } m_inShow--; return true; } bool wxComboPopupWindow::ProcessLeftDown(wxMouseEvent& event) { return wxPopupTransientWindow::ProcessLeftDown(event); } // First thing that happens when a transient popup closes is that this method gets called. void wxComboPopupWindow::OnDismiss() { wxComboCtrlBase* combo = (wxComboCtrlBase*) GetParent(); wxASSERT_MSG( combo->IsKindOf(CLASSINFO(wxComboCtrlBase)), wxT("parent might not be wxComboCtrl, but check IMPLEMENT_DYNAMIC_CLASS(2) macro for correctness") ); combo->OnPopupDismiss(); } #endif // USES_WXPOPUPTRANSIENTWINDOW // ---------------------------------------------------------------------------- // wxComboPopupWindowEvtHandler does bulk of the custom event handling // of a popup window. It is separate so we can have different types // of popup windows. // ---------------------------------------------------------------------------- class wxComboPopupWindowEvtHandler : public wxEvtHandler { public: wxComboPopupWindowEvtHandler( wxComboCtrlBase *parent ) { m_combo = parent; } void OnSizeEvent( wxSizeEvent& event ); void OnKeyEvent(wxKeyEvent& event); #if USES_WXDIALOG void OnActivate( wxActivateEvent& event ); #endif private: wxComboCtrlBase* m_combo; DECLARE_EVENT_TABLE() }; BEGIN_EVENT_TABLE(wxComboPopupWindowEvtHandler, wxEvtHandler) EVT_KEY_DOWN(wxComboPopupWindowEvtHandler::OnKeyEvent) EVT_KEY_UP(wxComboPopupWindowEvtHandler::OnKeyEvent) #if USES_WXDIALOG EVT_ACTIVATE(wxComboPopupWindowEvtHandler::OnActivate) #endif EVT_SIZE(wxComboPopupWindowEvtHandler::OnSizeEvent) END_EVENT_TABLE() void wxComboPopupWindowEvtHandler::OnSizeEvent( wxSizeEvent& WXUNUSED(event) ) { // Block the event so that the popup control does not get auto-resized. } void wxComboPopupWindowEvtHandler::OnKeyEvent( wxKeyEvent& event ) { // Relay keyboard event to the main child controls wxWindowList children = m_combo->GetPopupWindow()->GetChildren(); wxWindowList::iterator node = children.begin(); wxWindow* child = (wxWindow*)*node; child->AddPendingEvent(event); } #if USES_WXDIALOG void wxComboPopupWindowEvtHandler::OnActivate( wxActivateEvent& event ) { if ( !event.GetActive() ) { // Tell combo control that we are dismissed. m_combo->HidePopup(); event.Skip(); } } #endif // ---------------------------------------------------------------------------- // wxComboPopup // // ---------------------------------------------------------------------------- wxComboPopup::~wxComboPopup() { } void wxComboPopup::OnPopup() { } void wxComboPopup::OnDismiss() { } wxSize wxComboPopup::GetAdjustedSize( int minWidth, int prefHeight, int WXUNUSED(maxHeight) ) { return wxSize(minWidth,prefHeight); } void wxComboPopup::DefaultPaintComboControl( wxComboCtrlBase* combo, wxDC& dc, const wxRect& rect ) { if ( combo->GetWindowStyle() & wxCB_READONLY ) // ie. no textctrl { combo->PrepareBackground(dc,rect,0); dc.DrawText( combo->GetValue(), rect.x + combo->GetTextIndent(), (rect.height-dc.GetCharHeight())/2 + rect.y ); } } void wxComboPopup::PaintComboControl( wxDC& dc, const wxRect& rect ) { DefaultPaintComboControl(m_combo,dc,rect); } void wxComboPopup::OnComboKeyEvent( wxKeyEvent& event ) { event.Skip(); } void wxComboPopup::OnComboDoubleClick() { } void wxComboPopup::SetStringValue( const wxString& WXUNUSED(value) ) { } bool wxComboPopup::LazyCreate() { return false; } void wxComboPopup::Dismiss() { m_combo->HidePopup(); } // ---------------------------------------------------------------------------- // input handling // ---------------------------------------------------------------------------- // // This is pushed to the event handler queue of the child textctrl. // class wxComboBoxExtraInputHandler : public wxEvtHandler { public: wxComboBoxExtraInputHandler( wxComboCtrlBase* combo ) : wxEvtHandler() { m_combo = combo; } virtual ~wxComboBoxExtraInputHandler() { } void OnKey(wxKeyEvent& event); void OnFocus(wxFocusEvent& event); protected: wxComboCtrlBase* m_combo; private: DECLARE_EVENT_TABLE() }; BEGIN_EVENT_TABLE(wxComboBoxExtraInputHandler, wxEvtHandler) EVT_KEY_DOWN(wxComboBoxExtraInputHandler::OnKey) EVT_SET_FOCUS(wxComboBoxExtraInputHandler::OnFocus) END_EVENT_TABLE() void wxComboBoxExtraInputHandler::OnKey(wxKeyEvent& event) { // Let the wxComboCtrl event handler have a go first. wxComboCtrlBase* 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 wxComboBoxExtraInputHandler::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() & wxCC_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. While this may trigger multiple // wxEVT_SET_FOCUSes (since m_text->SetFocus is called // from combo's focus event handler), they should be quite // harmless. 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 wxComboPopupExtraEventHandler : public wxEvtHandler { public: wxComboPopupExtraEventHandler( wxComboCtrlBase* combo ) : wxEvtHandler() { m_combo = combo; m_beenInside = false; } virtual ~wxComboPopupExtraEventHandler() { } void OnMouseEvent( wxMouseEvent& event ); // Called from wxComboCtrlBase::OnPopupDismiss void OnPopupDismiss() { m_beenInside = false; } protected: wxComboCtrlBase* m_combo; bool m_beenInside; private: DECLARE_EVENT_TABLE() }; BEGIN_EVENT_TABLE(wxComboPopupExtraEventHandler, wxEvtHandler) EVT_MOUSE_EVENTS(wxComboPopupExtraEventHandler::OnMouseEvent) END_EVENT_TABLE() void wxComboPopupExtraEventHandler::OnMouseEvent( wxMouseEvent& event ) { wxPoint pt = event.GetPosition(); wxSize sz = m_combo->GetPopupControl()->GetControl()->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 || !m_combo->IsPopupShown() ) { event.Skip(false); return; } } else if ( evtType == wxEVT_LEFT_UP ) { if ( !m_combo->IsPopupShown() ) { event.Skip(false); 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(); } // ---------------------------------------------------------------------------- // wxComboCtrlBase // ---------------------------------------------------------------------------- BEGIN_EVENT_TABLE(wxComboCtrlBase, wxControl) EVT_TEXT(wxID_ANY,wxComboCtrlBase::OnTextCtrlEvent) EVT_SIZE(wxComboCtrlBase::OnSizeEvent) EVT_SET_FOCUS(wxComboCtrlBase::OnFocusEvent) EVT_KILL_FOCUS(wxComboCtrlBase::OnFocusEvent) EVT_IDLE(wxComboCtrlBase::OnIdleEvent) //EVT_BUTTON(wxID_ANY,wxComboCtrlBase::OnButtonClickEvent) EVT_KEY_DOWN(wxComboCtrlBase::OnKeyEvent) EVT_TEXT_ENTER(wxID_ANY,wxComboCtrlBase::OnTextCtrlEvent) EVT_SYS_COLOUR_CHANGED(wxComboCtrlBase::OnSysColourChanged) END_EVENT_TABLE() IMPLEMENT_ABSTRACT_CLASS(wxComboCtrlBase, wxControl) void wxComboCtrlBase::Init() { m_winPopup = (wxWindow *)NULL; m_popup = (wxWindow *)NULL; m_popupWinState = Hidden; m_btn = (wxWindow*) NULL; m_text = (wxTextCtrl*) NULL; m_popupInterface = (wxComboPopup*) NULL; m_popupExtraHandler = (wxEvtHandler*) NULL; m_textEvtHandler = (wxEvtHandler*) NULL; #if INSTALL_TOPLEV_HANDLER m_toplevEvtHandler = (wxEvtHandler*) NULL; #endif m_mainCtrlWnd = this; m_heightPopup = -1; m_widthMinPopup = -1; m_anchorSide = 0; m_widthCustomPaint = 0; m_widthCustomBorder = 0; m_btnState = 0; m_btnWidDefault = 0; m_blankButtonBg = false; m_ignoreEvtText = 0; m_popupWinType = POPUPWIN_NONE; m_btnWid = m_btnHei = -1; m_btnSide = wxRIGHT; m_btnSpacingX = 0; m_extLeft = 0; m_extRight = 0; m_absIndent = -1; m_iFlags = 0; m_timeCanAcceptClick = 0; m_resetFocus = false; } bool wxComboCtrlBase::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(); m_iFlags |= wxCC_IFLAG_CREATED; // If x and y indicate valid size, wxSizeEvent won't be // emitted automatically, so we need to add artifical one. if ( size.x > 0 && size.y > 0 ) { wxSizeEvent evt(size,GetId()); GetEventHandler()->AddPendingEvent(evt); } return true; } void wxComboCtrlBase::InstallInputHandlers() { if ( m_text ) { m_textEvtHandler = new wxComboBoxExtraInputHandler(this); m_text->PushEventHandler(m_textEvtHandler); } } void wxComboCtrlBase::CreateTextCtrl(int style, const wxValidator& validator) { if ( !(m_windowStyle & wxCB_READONLY) ) { if ( m_text ) m_text->Destroy(); // 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. style |= wxTE_PROCESS_TAB; if ( HasFlag(wxTE_PROCESS_ENTER) ) style |= wxTE_PROCESS_ENTER; // Ignore EVT_TEXT generated by the constructor (but only // if the event redirector already exists) // NB: This must be " = 1" instead of "++"; if ( m_textEvtHandler ) m_ignoreEvtText = 1; else m_ignoreEvtText = 0; m_text = new wxTextCtrl(this, wxID_ANY, m_valueString, wxDefaultPosition, wxSize(10,-1), style, validator); } } void wxComboCtrlBase::OnThemeChange() { // Leave the default bg on the Mac so the area used by the focus ring will // be the correct colour and themed brush. Instead we'll use // wxSYS_COLOUR_WINDOW in the EVT_PAINT handler as needed. #ifndef __WXMAC__ SetOwnBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); #endif } wxComboCtrlBase::~wxComboCtrlBase() { if ( HasCapture() ) ReleaseMouse(); #if INSTALL_TOPLEV_HANDLER delete ((wxComboFrameEventHandler*)m_toplevEvtHandler); m_toplevEvtHandler = (wxEvtHandler*) NULL; #endif DestroyPopup(); if ( m_text ) m_text->RemoveEventHandler(m_textEvtHandler); delete m_textEvtHandler; } // ---------------------------------------------------------------------------- // geometry stuff // ---------------------------------------------------------------------------- // Recalculates button and textctrl areas void wxComboCtrlBase::CalculateAreas( int btnWidth ) { wxSize sz = GetClientSize(); int customBorder = m_widthCustomBorder; int btnBorder; // border for button only // check if button should really be outside the border: we'll do it it if // its platform default or bitmap+pushbutton background is used, but not if // there is vertical size adjustment or horizontal spacing. if ( ( (m_iFlags & wxCC_BUTTON_OUTSIDE_BORDER) || (m_bmpNormal.Ok() && m_blankButtonBg) ) && m_btnSpacingX == 0 && m_btnHei <= 0 ) { m_iFlags |= wxCC_IFLAG_BUTTON_OUTSIDE; btnBorder = 0; } else { m_iFlags &= ~(wxCC_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; int butHeight = sz.y - btnBorder*2; // Adjust button width if ( m_btnWid > 0 ) butWidth = m_btnWid; else { // Adjust button width to match aspect ratio // (but only if control is smaller than best size). int bestHeight = GetBestSize().y; int height = GetSize().y; if ( height < bestHeight ) { // Make very small buttons square, as it makes // them accommodate arrow image better and still // looks decent. if ( height > 18 ) butWidth = (height*butWidth)/bestHeight; else butWidth = butHeight; } } // Adjust button height 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(wxDefaultCoord,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 + FOCUS_RING; m_btnArea.width = butAreaWid; m_btnArea.height = sz.y - ((btnBorder+FOCUS_RING)*2); m_tcArea.x = ( m_btnSide==wxRIGHT ? 0 : butAreaWid ) + customBorder + FOCUS_RING; m_tcArea.y = customBorder + FOCUS_RING; m_tcArea.width = sz.x - butAreaWid - (customBorder*2) - (FOCUS_RING*2); m_tcArea.height = sz.y - ((customBorder+FOCUS_RING)*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 wxComboCtrlBase::PositionTextCtrl( int textCtrlXAdjust, int textCtrlYAdjust ) { if ( !m_text ) return; #if !TEXTCTRL_TEXT_CENTERED 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 - COMBO_MARGIN - (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 #else // TEXTCTRL_TEXT_CENTERED wxUnusedVar(textCtrlXAdjust); wxUnusedVar(textCtrlYAdjust); #endif // !TEXTCTRL_TEXT_CENTERED/TEXTCTRL_TEXT_CENTERED { // If it has border, have textctrl will the entire text field. m_text->SetSize( m_tcArea.x + m_widthCustomPaint, m_tcArea.y, m_tcArea.width - m_widthCustomPaint, m_tcArea.height ); } } wxSize wxComboCtrlBase::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 #ifdef __WXMAC__ // these are the numbers from the HIG: switch ( m_windowVariant ) { case wxWINDOW_VARIANT_NORMAL: default : fhei = 22; break; case wxWINDOW_VARIANT_SMALL: fhei = 19; break; case wxWINDOW_VARIANT_MINI: fhei = 15; break; } #endif fhei += 2 * FOCUS_RING; int width = sizeText.x + FOCUS_RING + COMBO_MARGIN + DEFAULT_DROPBUTTON_WIDTH; wxSize ret(width, fhei); CacheBestSize(ret); return ret; } void wxComboCtrlBase::OnSizeEvent( wxSizeEvent& event ) { if ( !IsCreated() ) return; // defined by actual wxComboCtrls OnResize(); event.Skip(); } // ---------------------------------------------------------------------------- // standard operations // ---------------------------------------------------------------------------- bool wxComboCtrlBase::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 wxComboCtrlBase::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 wxComboCtrlBase::SetFont ( const wxFont& font ) { if ( !wxControl::SetFont(font) ) return false; if (m_text) m_text->SetFont(font); return true; } #if wxUSE_TOOLTIPS void wxComboCtrlBase::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 #if wxUSE_VALIDATORS void wxComboCtrlBase::SetValidator(const wxValidator& validator) { wxTextCtrl* textCtrl = GetTextCtrl(); if ( textCtrl ) textCtrl->SetValidator( validator ); } wxValidator* wxComboCtrlBase::GetValidator() { wxTextCtrl* textCtrl = GetTextCtrl(); if ( textCtrl ) return textCtrl->GetValidator(); return wxControl::GetValidator(); } #endif // wxUSE_VALIDATORS // ---------------------------------------------------------------------------- // painting // ---------------------------------------------------------------------------- #if (!defined(__WXMSW__)) || defined(__WXUNIVERSAL__) // prepare combo box background on area in a way typical on platform void wxComboCtrlBase::PrepareBackground( wxDC& dc, const wxRect& rect, int flags ) const { 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 focusSpacingX = isEnabled ? 2 : 1; focusSpacingY = sz.y > (GetCharHeight()+2) && isEnabled ? 2 : 1; } 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); wxColour bgCol; if ( isEnabled ) { // If popup is hidden and this control is focused, // then draw the focus-indicator (selbgcolor background etc.). if ( isFocused ) { dc.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT) ); bgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT); } else { dc.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT) ); #ifndef __WXMAC__ // see note in OnThemeChange bgCol = GetBackgroundColour(); #else bgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); #endif } } else { dc.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT) ); #ifndef __WXMAC__ // see note in OnThemeChange bgCol = GetBackgroundColour(); #else bgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); #endif } dc.SetBrush( bgCol ); dc.SetPen( bgCol ); dc.DrawRectangle( selRect ); // Don't clip exactly to the selection rectangle so we can draw // to the non-selected area in front of it. wxRect clipRect(rect.x,rect.y, (selRect.x+selRect.width)-rect.x,rect.height); dc.SetClippingRegion(clipRect); } #else // Save the library size a bit for platforms that re-implement this. void wxComboCtrlBase::PrepareBackground( wxDC&, const wxRect&, int ) const { } #endif void wxComboCtrlBase::DrawButton( wxDC& dc, const wxRect& rect, int paintBg ) { int drawState = m_btnState; #ifdef __WXGTK__ if ( GetPopupWindowState() >= Animating ) drawState |= wxCONTROL_PRESSED; #endif 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() ) { // Need to clear button background even if m_btn is present if ( paintBg ) { wxColour bgCol; if ( m_iFlags & wxCC_IFLAG_BUTTON_OUTSIDE ) bgCol = GetParent()->GetBackgroundColour(); else bgCol = GetBackgroundColour(); dc.SetBrush(bgCol); dc.SetPen(bgCol); dc.DrawRectangle(rect); } // Draw standard button if (GetParent()->GetWindowVariant() == wxWINDOW_VARIANT_SMALL) drawState |= wxCONTROL_SIZE_SMALL; else if (GetParent()->GetWindowVariant() == wxWINDOW_VARIANT_MINI) drawState |= wxCONTROL_SIZE_MINI; 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 ( 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 ( paintBg ) { 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 (GetParent()->GetWindowVariant() == wxWINDOW_VARIANT_SMALL) drawState |= wxCONTROL_SIZE_SMALL; else if (GetParent()->GetWindowVariant() == wxWINDOW_VARIANT_MINI) drawState |= wxCONTROL_SIZE_MINI; wxRendererNative::Get().DrawPushButton(this, dc, drawRect, drawState); } else { // 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 ( paintBg ) 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 wxComboCtrlBase::RecalcAndRefresh() { if ( IsCreated() ) { wxSizeEvent evt(GetSize(),GetId()); GetEventHandler()->ProcessEvent(evt); Refresh(); } } // ---------------------------------------------------------------------------- // miscellaneous event handlers // ---------------------------------------------------------------------------- void wxComboCtrlBase::OnTextCtrlEvent(wxCommandEvent& event) { if ( event.GetEventType() == wxEVT_COMMAND_TEXT_UPDATED ) { if ( m_ignoreEvtText > 0 ) { m_ignoreEvtText--; return; } } // Change event id, object and string before relaying it forward event.SetId(GetId()); wxString s = event.GetString(); event.SetEventObject(this); event.SetString(s); event.Skip(); } // call if cursor is on button area or mouse is captured for the button bool wxComboCtrlBase::HandleButtonMouseEvent( wxMouseEvent& event, int flags ) { int type = event.GetEventType(); if ( type == wxEVT_MOTION ) { if ( flags & wxCC_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 || type == wxEVT_LEFT_DCLICK ) { if ( flags & (wxCC_MF_ON_CLICK_AREA|wxCC_MF_ON_BUTTON) ) { m_btnState |= wxCONTROL_PRESSED; Refresh(); if ( !(m_iFlags & wxCC_POPUP_ON_MOUSE_UP) ) OnButtonClick(); else // If showing popup now, do not capture mouse or there will be interference CaptureMouse(); } } 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 & wxCC_POPUP_ON_MOUSE_UP ) { if ( flags & (wxCC_MF_ON_CLICK_AREA|wxCC_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 ( IsPopupWindowState(Hidden) ) { m_btnState &= ~(wxCONTROL_PRESSED); Refresh(); } } } else return false; return true; } // returns true if event was consumed or filtered bool wxComboCtrlBase::PreprocessMouseEvent( wxMouseEvent& event, int WXUNUSED(flags) ) { wxLongLong t = ::wxGetLocalTimeMillis(); int evtType = event.GetEventType(); #if USES_WXPOPUPWINDOW || USES_WXDIALOG if ( m_popupWinType != POPUPWIN_WXPOPUPTRANSIENTWINDOW ) { if ( IsPopupWindowState(Visible) && ( evtType == wxEVT_LEFT_DOWN || evtType == wxEVT_RIGHT_DOWN ) ) { HidePopup(); return true; } } #endif // 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 wxComboCtrlBase::HandleNormalMouseEvent( wxMouseEvent& event ) { int evtType = event.GetEventType(); if ( (evtType == wxEVT_LEFT_DOWN || evtType == wxEVT_LEFT_DCLICK) && (m_windowStyle & wxCB_READONLY) ) { if ( GetPopupWindowState() >= Animating ) { #if USES_WXPOPUPWINDOW // Click here always hides the popup. if ( m_popupWinType == POPUPWIN_WXPOPUPWINDOW ) HidePopup(); #endif } else { if ( !(m_windowStyle & wxCC_SPECIAL_DCLICK) ) { // 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 ( IsPopupShown() ) { // relay (some) mouse events to the popup if ( evtType == wxEVT_MOUSEWHEEL ) m_popup->AddPendingEvent(event); } else if ( evtType ) event.Skip(); } void wxComboCtrlBase::OnKeyEvent(wxKeyEvent& event) { if ( IsPopupShown() ) { // pass it to the popped up control GetPopupControl()->GetControl()->AddPendingEvent(event); } else // no popup { int keycode = event.GetKeyCode(); if ( keycode == WXK_TAB ) { wxNavigationKeyEvent evt; wxWindow* mainCtrl = GetMainWindowOfCompositeControl(); evt.SetFlags(wxNavigationKeyEvent::FromTab| (!event.ShiftDown() ? wxNavigationKeyEvent::IsForward : wxNavigationKeyEvent::IsBackward)); evt.SetEventObject(mainCtrl); evt.SetCurrentFocus(mainCtrl); mainCtrl->GetParent()->GetEventHandler()->AddPendingEvent(evt); return; } if ( IsKeyPopupToggle(event) ) { OnButtonClick(); return; } int comboStyle = GetWindowStyle(); wxComboPopup* popupInterface = GetPopupControl(); if ( !popupInterface ) { event.Skip(); return; } if ( (comboStyle & wxCB_READONLY) || (keycode != WXK_RIGHT && keycode != WXK_LEFT) ) { popupInterface->OnComboKeyEvent(event); } else event.Skip(); } } void wxComboCtrlBase::OnFocusEvent( wxFocusEvent& event ) { if ( event.GetEventType() == wxEVT_SET_FOCUS ) { wxWindow* tc = GetTextCtrl(); if ( tc && tc != DoFindFocus() ) #ifdef __WXMAC__ m_resetFocus = true; #else tc->SetFocus(); #endif } Refresh(); } void wxComboCtrlBase::OnIdleEvent( wxIdleEvent& WXUNUSED(event) ) { if ( m_resetFocus ) { m_resetFocus = false; wxWindow* tc = GetTextCtrl(); if ( tc ) tc->SetFocus(); } } void wxComboCtrlBase::OnSysColourChanged(wxSysColourChangedEvent& WXUNUSED(event)) { OnThemeChange(); // indentation may also have changed if ( !(m_iFlags & wxCC_IFLAG_INDENT_SET) ) m_absIndent = GetNativeTextIndent(); RecalcAndRefresh(); } // ---------------------------------------------------------------------------- // popup handling // ---------------------------------------------------------------------------- // Create popup window and the child control void wxComboCtrlBase::CreatePopup() { wxComboPopup* popupInterface = m_popupInterface; wxWindow* popup; if ( !m_winPopup ) { #ifdef wxComboPopupWindowBase2 if ( m_iFlags & wxCC_IFLAG_USE_ALT_POPUP ) { #if !USES_WXDIALOG m_winPopup = new wxComboPopupWindowBase2( this, wxNO_BORDER ); #else m_winPopup = new wxComboPopupWindowBase2( this, wxID_ANY, wxEmptyString, wxPoint(-21,-21), wxSize(20, 20), wxNO_BORDER ); #endif m_popupWinType = SECONDARY_POPUP_TYPE; } else #endif { m_winPopup = new wxComboPopupWindow( this, wxNO_BORDER ); m_popupWinType = PRIMARY_POPUP_TYPE; } m_popupWinEvtHandler = new wxComboPopupWindowEvtHandler(this); m_winPopup->PushEventHandler(m_popupWinEvtHandler); } popupInterface->Create(m_winPopup); m_popup = popup = popupInterface->GetControl(); m_popupExtraHandler = new wxComboPopupExtraEventHandler(this); popup->PushEventHandler( m_popupExtraHandler ); // This may be helpful on some platforms // (eg. it bypasses a wxGTK popupwindow bug where // window is not initially hidden when it should be) m_winPopup->Hide(); popupInterface->m_iFlags |= wxCP_IFLAG_CREATED; } // Destroy popup window and the child control void wxComboCtrlBase::DestroyPopup() { HidePopup(); if ( m_popup ) m_popup->RemoveEventHandler(m_popupExtraHandler); delete m_popupExtraHandler; delete m_popupInterface; if ( m_winPopup ) { m_winPopup->RemoveEventHandler(m_popupWinEvtHandler); delete m_popupWinEvtHandler; m_popupWinEvtHandler = NULL; m_winPopup->Destroy(); } m_popupExtraHandler = (wxEvtHandler*) NULL; m_popupInterface = (wxComboPopup*) NULL; m_winPopup = (wxWindow*) NULL; m_popup = (wxWindow*) NULL; } void wxComboCtrlBase::DoSetPopupControl(wxComboPopup* iface) { wxCHECK_RET( iface, wxT("no popup interface set for wxComboCtrl") ); DestroyPopup(); iface->InitBase(this); iface->Init(); m_popupInterface = iface; if ( !iface->LazyCreate() ) { CreatePopup(); } else { m_popup = (wxWindow*) NULL; } // This must be done after creation if ( m_valueString.length() ) { iface->SetStringValue(m_valueString); //Refresh(); } } // Ensures there is atleast the default popup void wxComboCtrlBase::EnsurePopupControl() { if ( !m_popupInterface ) SetPopupControl(NULL); } void wxComboCtrlBase::OnButtonClick() { // Derived classes can override this method for totally custom // popup action ShowPopup(); } void wxComboCtrlBase::ShowPopup() { EnsurePopupControl(); wxCHECK_RET( !IsPopupWindowState(Visible), wxT("popup window already shown") ); if ( IsPopupWindowState(Animating) ) return; SetFocus(); // Space above and below int screenHeight; wxPoint scrPos; int spaceAbove; int spaceBelow; int maxHeightPopup; wxSize ctrlSz = GetSize(); 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 |= wxCC_IFLAG_PARENT_TAB_TRAVERSAL; } if ( !winPopup ) { CreatePopup(); winPopup = m_winPopup; popup = m_popup; } else { popup = m_popup; } winPopup->Enable(); 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(); // // Reposition and resize popup window // wxSize szp = popup->GetSize(); int popupX; int popupY = scrPos.y + ctrlSz.y; // Default anchor is wxLEFT int anchorSide = m_anchorSide; if ( !anchorSide ) anchorSide = wxLEFT; int rightX = scrPos.x + ctrlSz.x + m_extRight - szp.x; int leftX = scrPos.x - m_extLeft; if ( wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft ) leftX -= ctrlSz.x; int screenWidth = wxSystemSettings::GetMetric( wxSYS_SCREEN_X ); // If there is not enough horizontal space, anchor on the other side. // If there is no space even then, place the popup at x 0. if ( anchorSide == wxRIGHT ) { if ( rightX < 0 ) { if ( (leftX+szp.x) < screenWidth ) anchorSide = wxLEFT; else anchorSide = 0; } } else { if ( (leftX+szp.x) >= screenWidth ) { if ( rightX >= 0 ) anchorSide = wxRIGHT; else anchorSide = 0; } } // Select x coordinate according to the anchor side if ( anchorSide == wxRIGHT ) popupX = rightX; else if ( anchorSide == wxLEFT ) popupX = leftX; else popupX = 0; int showFlags = CanDeferShow; if ( spaceBelow < szp.y ) { popupY = scrPos.y - szp.y; showFlags |= ShowAbove; } #if INSTALL_TOPLEV_HANDLER // Put top level window event handler into place if ( m_popupWinType == POPUPWIN_WXPOPUPWINDOW ) { if ( !m_toplevEvtHandler ) m_toplevEvtHandler = new wxComboFrameEventHandler(this); wxWindow* toplev = ::wxGetTopLevelParent( this ); wxASSERT( toplev ); ((wxComboFrameEventHandler*)m_toplevEvtHandler)->OnPopup(); toplev->PushEventHandler( m_toplevEvtHandler ); } #endif // Set string selection (must be this way instead of SetStringSelection) if ( m_text ) { if ( !(m_iFlags & wxCC_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 Refresh(); } // This must be after SetStringValue m_popupWinState = Animating; wxRect popupWinRect( popupX, popupY, szp.x, szp.y ); m_popup = popup; if ( (m_iFlags & wxCC_IFLAG_DISABLE_POPUP_ANIM) || AnimateShow( popupWinRect, showFlags ) ) { DoShowPopup( popupWinRect, showFlags ); } } bool wxComboCtrlBase::AnimateShow( const wxRect& WXUNUSED(rect), int WXUNUSED(flags) ) { return true; } void wxComboCtrlBase::DoShowPopup( const wxRect& rect, int WXUNUSED(flags) ) { wxWindow* winPopup = m_winPopup; if ( IsPopupWindowState(Animating) ) { // Make sure the popup window is shown in the right position. // Should not matter even if animation already did this. // Some platforms (GTK) may like SetSize and Move to be separate // (though the bug was probably fixed). winPopup->SetSize( rect ); winPopup->Show(); m_popupWinState = Visible; } else if ( IsPopupWindowState(Hidden) ) { // Animation was aborted wxASSERT( !winPopup->IsShown() ); m_popupWinState = Hidden; } } void wxComboCtrlBase::OnPopupDismiss() { // Just in case, avoid double dismiss if ( IsPopupWindowState(Hidden) ) return; // This must be set before focus - otherwise there will be recursive // OnPopupDismisses. m_popupWinState = Hidden; //SetFocus(); m_winPopup->Disable(); // Inform popup control itself m_popupInterface->OnDismiss(); if ( m_popupExtraHandler ) ((wxComboPopupExtraEventHandler*)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 m_timeCanAcceptClick = ::wxGetLocalTimeMillis(); if ( m_popupWinType == POPUPWIN_WXPOPUPTRANSIENTWINDOW ) m_timeCanAcceptClick += 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.Contains(ScreenToClient(::wxGetMousePosition())) ) m_btnState = 0; // Return parent's tab traversal flag. // See ShowPopup for notes. if ( m_iFlags & wxCC_IFLAG_PARENT_TAB_TRAVERSAL ) { wxWindow* parent = GetParent(); parent->SetWindowStyle( parent->GetWindowStyle() | wxTAB_TRAVERSAL ); m_iFlags &= ~(wxCC_IFLAG_PARENT_TAB_TRAVERSAL); } // refresh control (necessary even if m_text) Refresh(); SetFocus(); } void wxComboCtrlBase::HidePopup() { // Should be able to call this without popup interface if ( IsPopupWindowState(Hidden) ) return; // transfer value and show it in textctrl, if any if ( !IsPopupWindowState(Animating) ) SetValue( m_popupInterface->GetStringValue() ); m_winPopup->Hide(); OnPopupDismiss(); } // ---------------------------------------------------------------------------- // customization methods // ---------------------------------------------------------------------------- void wxComboCtrlBase::SetButtonPosition( int width, int height, int side, int spacingX ) { m_btnWid = width; m_btnHei = height; m_btnSide = side; m_btnSpacingX = spacingX; RecalcAndRefresh(); } wxSize wxComboCtrlBase::GetButtonSize() { if ( m_btnSize.x > 0 ) return m_btnSize; wxSize retSize(m_btnWid,m_btnHei); // Need to call CalculateAreas now if button size is // is not explicitly specified. if ( retSize.x <= 0 || retSize.y <= 0) { OnResize(); retSize = m_btnSize; } return retSize; } void wxComboCtrlBase::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 wxComboCtrlBase::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 wxComboCtrlBase::SetTextIndent( int indent ) { if ( indent < 0 ) { m_absIndent = GetNativeTextIndent(); m_iFlags &= ~(wxCC_IFLAG_INDENT_SET); } else { m_absIndent = indent; m_iFlags |= wxCC_IFLAG_INDENT_SET; } RecalcAndRefresh(); } wxCoord wxComboCtrlBase::GetNativeTextIndent() const { return DEFAULT_TEXT_INDENT; } // ---------------------------------------------------------------------------- // methods forwarded to wxTextCtrl // ---------------------------------------------------------------------------- wxString wxComboCtrlBase::GetValue() const { if ( m_text ) return m_text->GetValue(); return m_valueString; } void wxComboCtrlBase::SetValueWithEvent(const wxString& value, bool withEvent) { if ( m_text ) { if ( !withEvent ) m_ignoreEvtText++; m_text->SetValue(value); if ( !(m_iFlags & wxCC_NO_TEXT_AUTO_SELECT) ) m_text->SelectAll(); } m_valueString = value; Refresh(); // Since wxComboPopup 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); } } void wxComboCtrlBase::SetValue(const wxString& value) { SetValueWithEvent(value, false); } // In this SetValue variant wxComboPopup::SetStringValue is not called void wxComboCtrlBase::SetText(const wxString& value) { // Unlike in SetValue(), this must be called here or // the behaviour will no be consistent in readonlys. EnsurePopupControl(); m_valueString = value; if ( m_text ) { m_ignoreEvtText++; m_text->SetValue( value ); } Refresh(); } void wxComboCtrlBase::Copy() { if ( m_text ) m_text->Copy(); } void wxComboCtrlBase::Cut() { if ( m_text ) m_text->Cut(); } void wxComboCtrlBase::Paste() { if ( m_text ) m_text->Paste(); } void wxComboCtrlBase::SetInsertionPoint(long pos) { if ( m_text ) m_text->SetInsertionPoint(pos); } void wxComboCtrlBase::SetInsertionPointEnd() { if ( m_text ) m_text->SetInsertionPointEnd(); } long wxComboCtrlBase::GetInsertionPoint() const { if ( m_text ) return m_text->GetInsertionPoint(); return 0; } long wxComboCtrlBase::GetLastPosition() const { if ( m_text ) return m_text->GetLastPosition(); return 0; } void wxComboCtrlBase::Replace(long from, long to, const wxString& value) { if ( m_text ) m_text->Replace(from, to, value); } void wxComboCtrlBase::Remove(long from, long to) { if ( m_text ) m_text->Remove(from, to); } void wxComboCtrlBase::SetSelection(long from, long to) { if ( m_text ) m_text->SetSelection(from, to); } void wxComboCtrlBase::Undo() { if ( m_text ) m_text->Undo(); } #endif // wxUSE_COMBOCTRL