X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/a340b80d8a692f5308d26c1c1b95fd689a1d7eb3..133506215101bd8cffbf81e6c36944f7d5e2f8db:/src/common/combocmn.cpp diff --git a/src/common/combocmn.cpp b/src/common/combocmn.cpp index 3ae8b2c22d..0b5b37d372 100644 --- a/src/common/combocmn.cpp +++ b/src/common/combocmn.cpp @@ -1,6 +1,6 @@ ///////////////////////////////////////////////////////////////////////////// -// Name: combocmn.cpp -// Purpose: wxComboControlBase +// Name: src/common/combocmn.cpp +// Purpose: wxComboCtrlBase // Author: Jaakko Salli // Modified by: // Created: Apr-30-2006 @@ -23,20 +23,20 @@ #pragma hdrstop #endif -#if wxUSE_COMBOCONTROL +#if wxUSE_COMBOCTRL + +#include "wx/combobox.h" #ifndef WX_PRECOMP - #include "wx/defs.h" + #include "wx/app.h" #include "wx/log.h" - #include "wx/combobox.h" #include "wx/dcclient.h" #include "wx/settings.h" - #include "wx/dialog.h" + #include "wx/timer.h" + #include "wx/textctrl.h" #endif -#include "wx/dcbuffer.h" #include "wx/tooltip.h" -#include "wx/timer.h" #include "wx/combo.h" @@ -45,15 +45,11 @@ // constants // ---------------------------------------------------------------------------- -// Milliseconds to wait for two mouse-ups after focus inorder -// to trigger a double-click. -#define DOUBLE_CLICK_CONVERSION_TRESHOLD 500 - #define DEFAULT_DROPBUTTON_WIDTH 19 #define BMP_BUTTON_MARGIN 4 -#define DEFAULT_POPUP_HEIGHT 200 +#define DEFAULT_POPUP_HEIGHT 400 #define DEFAULT_TEXT_INDENT 3 @@ -62,22 +58,76 @@ #if defined(__WXMSW__) +// Let's use wxFrame as a fall-back solution until wxMSW gets wxNonOwnedWindow +#include "wx/frame.h" +#define wxCC_GENERIC_TLW_IS_FRAME +#define wxComboCtrlGenericTLW wxFrame + #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). + +#include "wx/dialog.h" +#define wxCC_GENERIC_TLW_IS_DIALOG +#define wxComboCtrlGenericTLW wxDialog + +#include "wx/gtk/private.h" + +// NB: Let's not be afraid to use wxGTK's wxPopupTransientWindow as a +// 'perfect' popup, as it can succesfully host child controls even in +// popups that are shown in modal dialogs. + #define USE_TRANSIENT_POPUP 1 // Use wxPopupWindowTransient (preferred, if it works properly on platform) +#define TRANSIENT_POPUPWIN_IS_PERFECT 1 // 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) +#include "wx/nonownedwnd.h" +#define wxCC_GENERIC_TLW_IS_NONOWNEDWINDOW +#define wxComboCtrlGenericTLW wxNonOwnedWindow + +#define USE_TRANSIENT_POPUP 1 // Use wxPopupWindowTransient (preferred, if it works properly on platform) +#define TRANSIENT_POPUPWIN_IS_PERFECT 1 // 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 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 +#include "wx/dialog.h" +#define wxCC_GENERIC_TLW_IS_DIALOG +#define wxComboCtrlGenericTLW wxDialog + #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 @@ -85,7 +135,7 @@ // 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__) +#if (!defined(__WXMSW__) && !defined(__WXGTK__) && !defined(__WXMAC__)) || defined(__WXWINCE__) #undef wxUSE_POPUPWIN #define wxUSE_POPUPWIN 0 #endif @@ -99,24 +149,97 @@ #endif -#if USE_TRANSIENT_POPUP +// Define different types of popup windows +enum +{ + POPUPWIN_NONE = 0, + POPUPWIN_WXPOPUPTRANSIENTWINDOW = 1, + POPUPWIN_WXPOPUPWINDOW = 2, + POPUPWIN_GENERICTLW = 3 +}; + - #define wxComboPopupWindowBase wxPopupTransientWindow - #define INSTALL_TOPLEV_HANDLER 0 +#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 wxComboCtrlGenericTLW + #define SECONDARY_POPUP_TYPE POPUPWIN_GENERICTLW + #define USES_GENERICTLW 1 + #endif #elif wxUSE_POPUPWIN + // wxPopupWindow (but not wxPopupTransientWindow) is properly implemented - #define wxComboPopupWindowBase wxPopupWindow - #define INSTALL_TOPLEV_HANDLER 1 + #define wxComboPopupWindowBase wxPopupWindow + #define PRIMARY_POPUP_TYPE POPUPWIN_WXPOPUPWINDOW + #define USES_WXPOPUPWINDOW 1 + + #if !POPUPWIN_IS_PERFECT + #define wxComboPopupWindowBase2 wxComboCtrlGenericTLW + #define SECONDARY_POPUP_TYPE POPUPWIN_GENERICTLW + #define USES_GENERICTLW 1 + #endif #else + // wxPopupWindow is not implemented + + #define wxComboPopupWindowBase wxComboCtrlGenericTLW + #define PRIMARY_POPUP_TYPE POPUPWIN_GENERICTLW + #define USES_GENERICTLW 1 + +#endif + + +#ifndef USES_WXPOPUPTRANSIENTWINDOW + #define USES_WXPOPUPTRANSIENTWINDOW 0 +#endif + +#ifndef USES_WXPOPUPWINDOW + #define USES_WXPOPUPWINDOW 0 +#endif + +#ifndef USES_GENERICTLW + #define USES_GENERICTLW 0 +#endif - #define wxComboPopupWindowBase wxDialog - #define INSTALL_TOPLEV_HANDLER 0 // Doesn't need since can monitor active event +#if USES_WXPOPUPWINDOW + #define INSTALL_TOPLEV_HANDLER 1 +#else + #define INSTALL_TOPLEV_HANDLER 0 #endif +// Returns true if given popup window type can be classified as perfect +// on this platform. +static inline bool IsPopupWinTypePerfect( wxByte popupWinType ) +{ +#if POPUPWIN_IS_PERFECT && TRANSIENT_POPUPWIN_IS_PERFECT + wxUnusedVar(popupWinType); + return true; +#else + return ( popupWinType == POPUPWIN_GENERICTLW + #if POPUPWIN_IS_PERFECT + || popupWinType == POPUPWIN_WXPOPUPWINDOW + #endif + #if TRANSIENT_POPUPWIN_IS_PERFECT + || popupWinType == POPUPWIN_WXPOPUPTRANSIENTWINDOW + #endif + ); +#endif +} + // // ** TODO ** @@ -139,8 +262,8 @@ class wxComboFrameEventHandler : public wxEvtHandler { public: - wxComboFrameEventHandler( wxComboControlBase* pCb ); - ~wxComboFrameEventHandler(); + wxComboFrameEventHandler( wxComboCtrlBase* pCb ); + virtual ~wxComboFrameEventHandler(); void OnPopup(); @@ -154,7 +277,7 @@ public: protected: wxWindow* m_focusStart; - wxComboControlBase* m_combo; + wxComboCtrlBase* m_combo; private: DECLARE_EVENT_TABLE() @@ -172,7 +295,7 @@ BEGIN_EVENT_TABLE(wxComboFrameEventHandler, wxEvtHandler) EVT_CLOSE(wxComboFrameEventHandler::OnClose) END_EVENT_TABLE() -wxComboFrameEventHandler::wxComboFrameEventHandler( wxComboControlBase* combo ) +wxComboFrameEventHandler::wxComboFrameEventHandler( wxComboCtrlBase* combo ) : wxEvtHandler() { m_combo = combo; @@ -191,7 +314,7 @@ void wxComboFrameEventHandler::OnIdle( wxIdleEvent& event ) { wxWindow* winFocused = ::wxWindow::FindFocus(); - wxWindow* popup = m_combo->GetPopupControl(); + wxWindow* popup = m_combo->GetPopupControl()->GetControl(); wxWindow* winpopup = m_combo->GetPopupWindow(); if ( @@ -204,7 +327,7 @@ void wxComboFrameEventHandler::OnIdle( wxIdleEvent& event ) winFocused != m_combo->GetButton() // GTK (atleast) requires this ) { - m_combo->HidePopup(); + m_combo->HidePopup(true); } event.Skip(); @@ -212,150 +335,191 @@ void wxComboFrameEventHandler::OnIdle( wxIdleEvent& event ) void wxComboFrameEventHandler::OnMenuEvent( wxMenuEvent& event ) { - m_combo->HidePopup(); + m_combo->HidePopup(true); event.Skip(); } void wxComboFrameEventHandler::OnMouseEvent( wxMouseEvent& event ) { - m_combo->HidePopup(); + m_combo->HidePopup(true); event.Skip(); } void wxComboFrameEventHandler::OnClose( wxCloseEvent& event ) { - m_combo->HidePopup(); + m_combo->HidePopup(true); event.Skip(); } void wxComboFrameEventHandler::OnActivate( wxActivateEvent& event ) { - m_combo->HidePopup(); + m_combo->HidePopup(true); event.Skip(); } void wxComboFrameEventHandler::OnResize( wxSizeEvent& event ) { - m_combo->HidePopup(); + m_combo->HidePopup(true); event.Skip(); } void wxComboFrameEventHandler::OnMove( wxMoveEvent& event ) { - m_combo->HidePopup(); + m_combo->HidePopup(true); event.Skip(); } #endif // INSTALL_TOPLEV_HANDLER // ---------------------------------------------------------------------------- -// wxComboPopupWindow is wxPopupWindow customized for -// wxComboControl. +// wxComboPopupWindow is, in essence, wxPopupWindow customized for +// wxComboCtrl. // ---------------------------------------------------------------------------- class wxComboPopupWindow : public wxComboPopupWindowBase { public: - wxComboPopupWindow( wxComboControlBase *parent, int style = wxBORDER_NONE ); + 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 USE_TRANSIENT_POPUP +#if USES_WXPOPUPTRANSIENTWINDOW + virtual bool Show( bool show ); virtual bool ProcessLeftDown(wxMouseEvent& event); +protected: + virtual void OnDismiss(); #endif - void OnKeyEvent(wxKeyEvent& event); +private: + wxByte m_inShow; +}; - void OnMouseEvent( wxMouseEvent& event ); -#if !wxUSE_POPUPWIN - void OnActivate( wxActivateEvent& event ); -#endif -protected: +#if USES_WXPOPUPTRANSIENTWINDOW +bool wxComboPopupWindow::Show( bool show ) +{ + // Guard against recursion + if ( m_inShow ) + return wxComboPopupWindowBase::Show(show); -#if USE_TRANSIENT_POPUP - virtual void OnDismiss(); + m_inShow++; + + wxASSERT( IsKindOf(CLASSINFO(wxPopupTransientWindow)) ); + + wxPopupTransientWindow* ptw = (wxPopupTransientWindow*) this; + + if ( show != ptw->IsShown() ) + { + if ( show ) + // We used to do wxPopupTransientWindow::Popup here, + // but this would hide normal Show, which we are + // also going to need. + ptw->Show(); + 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(true); +} +#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_GENERICTLW + void OnActivate( wxActivateEvent& event ); #endif private: + wxComboCtrlBase* m_combo; + DECLARE_EVENT_TABLE() }; -BEGIN_EVENT_TABLE(wxComboPopupWindow, wxComboPopupWindowBase) - EVT_MOUSE_EVENTS(wxComboPopupWindow::OnMouseEvent) -#if !wxUSE_POPUPWIN - EVT_ACTIVATE(wxComboPopupWindow::OnActivate) +BEGIN_EVENT_TABLE(wxComboPopupWindowEvtHandler, wxEvtHandler) + EVT_KEY_DOWN(wxComboPopupWindowEvtHandler::OnKeyEvent) + EVT_KEY_UP(wxComboPopupWindowEvtHandler::OnKeyEvent) + EVT_CHAR(wxComboPopupWindowEvtHandler::OnKeyEvent) +#if USES_GENERICTLW + EVT_ACTIVATE(wxComboPopupWindowEvtHandler::OnActivate) #endif - EVT_KEY_DOWN(wxComboPopupWindow::OnKeyEvent) - EVT_KEY_UP(wxComboPopupWindow::OnKeyEvent) + EVT_SIZE(wxComboPopupWindowEvtHandler::OnSizeEvent) END_EVENT_TABLE() -wxComboPopupWindow::wxComboPopupWindow( wxComboControlBase *parent, - int style ) -#if wxUSE_POPUPWIN - : wxComboPopupWindowBase(parent,style) -#else - : wxComboPopupWindowBase(parent, - wxID_ANY, - wxEmptyString, - wxPoint(-21,-21), - wxSize(20,20), - style) -#endif +void wxComboPopupWindowEvtHandler::OnSizeEvent( wxSizeEvent& WXUNUSED(event) ) { + // Block the event so that the popup control does not get auto-resized. } -void wxComboPopupWindow::OnKeyEvent( wxKeyEvent& event ) +void wxComboPopupWindowEvtHandler::OnKeyEvent( wxKeyEvent& event ) { // Relay keyboard event to the main child controls - // (just skipping may just cause the popup to close) - wxWindowList children = GetChildren(); + wxWindowList children = m_combo->GetPopupWindow()->GetChildren(); wxWindowList::iterator node = children.begin(); wxWindow* child = (wxWindow*)*node; - child->AddPendingEvent(event); -} - -void wxComboPopupWindow::OnMouseEvent( wxMouseEvent& event ) -{ - event.Skip(); + child->GetEventHandler()->ProcessEvent(event); } -#if !wxUSE_POPUPWIN -void wxComboPopupWindow::OnActivate( wxActivateEvent& event ) +#if USES_GENERICTLW +void wxComboPopupWindowEvtHandler::OnActivate( wxActivateEvent& event ) { if ( !event.GetActive() ) { // Tell combo control that we are dismissed. - wxComboControl* combo = (wxComboControl*) GetParent(); - wxASSERT( combo ); - wxASSERT( combo->IsKindOf(CLASSINFO(wxComboControl)) ); - - combo->HidePopup(); + m_combo->HidePopup(true); event.Skip(); } } #endif -#if USE_TRANSIENT_POPUP -bool wxComboPopupWindow::ProcessLeftDown(wxMouseEvent& event ) -{ - return wxComboPopupWindowBase::ProcessLeftDown(event); -} -#endif - -#if USE_TRANSIENT_POPUP -// First thing that happens when a transient popup closes is that this method gets called. -void wxComboPopupWindow::OnDismiss() -{ - wxComboControlBase* combo = (wxComboControlBase*) GetParent(); - wxASSERT_MSG( combo->IsKindOf(CLASSINFO(wxComboControlBase)), - wxT("parent might not be wxComboControl, but check IMPLEMENT_DYNAMIC_CLASS(2) macro for correctness") ); - - combo->OnPopupDismiss(); -} -#endif // ---------------------------------------------------------------------------- // wxComboPopup @@ -374,6 +538,11 @@ void wxComboPopup::OnDismiss() { } +wxComboCtrl* wxComboPopup::GetComboCtrl() const +{ + return wxStaticCast(m_combo, wxComboCtrl); +} + wxSize wxComboPopup::GetAdjustedSize( int minWidth, int prefHeight, int WXUNUSED(maxHeight) ) @@ -381,23 +550,34 @@ wxSize wxComboPopup::GetAdjustedSize( int minWidth, return wxSize(minWidth,prefHeight); } -void wxComboPopup::PaintComboControl( wxDC& dc, const wxRect& rect ) +void wxComboPopup::DefaultPaintComboControl( wxComboCtrlBase* combo, + wxDC& dc, const wxRect& rect ) { - if ( m_combo->GetWindowStyle() & wxCB_READONLY ) // ie. no textctrl + if ( combo->GetWindowStyle() & wxCB_READONLY ) // ie. no textctrl { - m_combo->DrawFocusBackground(dc,rect,0); + combo->PrepareBackground(dc,rect,0); - dc.DrawText( GetStringValue(), - rect.x + m_combo->GetTextIndent(), - (rect.height-dc.GetCharHeight())/2 + m_combo->m_widthCustomBorder ); + dc.DrawText( combo->GetValue(), + rect.x + combo->m_marginLeft, + (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::OnComboCharEvent( wxKeyEvent& event ) +{ + event.Skip(); +} + void wxComboPopup::OnComboDoubleClick() { } @@ -413,7 +593,7 @@ bool wxComboPopup::LazyCreate() void wxComboPopup::Dismiss() { - m_combo->HidePopup(); + m_combo->HidePopup(true); } // ---------------------------------------------------------------------------- @@ -421,24 +601,23 @@ void wxComboPopup::Dismiss() // ---------------------------------------------------------------------------- // -// This is pushed to the event handler queue of either combo box -// or its textctrl (latter if not readonly combo). +// This is pushed to the event handler queue of the child textctrl. // class wxComboBoxExtraInputHandler : public wxEvtHandler { public: - wxComboBoxExtraInputHandler( wxComboControlBase* combo ) + wxComboBoxExtraInputHandler( wxComboCtrlBase* combo ) : wxEvtHandler() { m_combo = combo; } - ~wxComboBoxExtraInputHandler() { } + virtual ~wxComboBoxExtraInputHandler() { } void OnKey(wxKeyEvent& event); void OnFocus(wxFocusEvent& event); protected: - wxComboControlBase* m_combo; + wxComboCtrlBase* m_combo; private: DECLARE_EVENT_TABLE() @@ -447,68 +626,36 @@ private: BEGIN_EVENT_TABLE(wxComboBoxExtraInputHandler, wxEvtHandler) EVT_KEY_DOWN(wxComboBoxExtraInputHandler::OnKey) + EVT_KEY_UP(wxComboBoxExtraInputHandler::OnKey) + EVT_CHAR(wxComboBoxExtraInputHandler::OnKey) EVT_SET_FOCUS(wxComboBoxExtraInputHandler::OnFocus) + EVT_KILL_FOCUS(wxComboBoxExtraInputHandler::OnFocus) END_EVENT_TABLE() void wxComboBoxExtraInputHandler::OnKey(wxKeyEvent& event) { - int keycode = event.GetKeyCode(); + // Let the wxComboCtrl event handler have a go first. + wxComboCtrlBase* combo = m_combo; - if ( keycode == WXK_TAB ) - { - wxNavigationKeyEvent evt; - evt.SetFlags(wxNavigationKeyEvent::FromTab| - (!event.ShiftDown()?wxNavigationKeyEvent::IsForward: - wxNavigationKeyEvent::IsBackward)); - evt.SetEventObject(m_combo); - m_combo->GetParent()->GetEventHandler()->AddPendingEvent(evt); - return; - } + wxKeyEvent redirectedEvent(event); + redirectedEvent.SetId(combo->GetId()); + redirectedEvent.SetEventObject(combo); - if ( m_combo->IsPopupShown() ) - { - // pass it to the popped up control - m_combo->GetPopupControl()->AddPendingEvent(event); - } - else // no popup + if ( !combo->GetEventHandler()->ProcessEvent(redirectedEvent) ) { - int comboStyle = m_combo->GetWindowStyle(); - wxComboPopup* popupInterface = m_combo->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 & wxCC_ALT_KEYS) ) - { - if ( keycode == WXK_UP || keycode == WXK_DOWN ) - { - m_combo->OnButtonClick(); - return; - } - } - else - popupInterface->OnComboKeyEvent(event); - } - else + // Don't let TAB through to the text ctrl - looks ugly + if ( event.GetKeyCode() != WXK_TAB ) event.Skip(); } } - 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 ( event.GetEventType() == wxEVT_SET_FOCUS && + !(m_combo->GetInternalFlags() & wxCC_NO_TEXT_AUTO_SELECT) ) { if ( m_combo->GetTextCtrl() ) m_combo->GetTextCtrl()->SelectAll(); @@ -516,6 +663,16 @@ void wxComboBoxExtraInputHandler::OnFocus(wxFocusEvent& event) 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(event.GetEventType(),m_combo->GetId()); + evt2.SetEventObject(m_combo); + m_combo->GetEventHandler()->ProcessEvent(evt2); + event.Skip(); } @@ -524,135 +681,211 @@ void wxComboBoxExtraInputHandler::OnFocus(wxFocusEvent& event) // This is pushed to the event handler queue of the control in popup. // -class wxComboPopupExtraEventHandler : public wxEvtHandler +class wxComboPopupEvtHandler : public wxEvtHandler { public: - wxComboPopupExtraEventHandler( wxComboControlBase* combo ) + wxComboPopupEvtHandler( wxComboCtrlBase* combo ) : wxEvtHandler() { m_combo = combo; m_beenInside = false; + + // Let's make it so that the popup control will not receive mouse + // events until mouse left button has been up. + m_blockEventsToPopup = true; } - ~wxComboPopupExtraEventHandler() { } + virtual ~wxComboPopupEvtHandler() { } void OnMouseEvent( wxMouseEvent& event ); - // Called from wxPGComboControlBase::OnPopupDismiss + // Called from wxComboCtrlBase::OnPopupDismiss void OnPopupDismiss() { m_beenInside = false; + m_blockEventsToPopup = true; } protected: - wxComboControlBase* m_combo; + wxComboCtrlBase* m_combo; - bool m_beenInside; + bool m_beenInside; + bool m_blockEventsToPopup; private: DECLARE_EVENT_TABLE() }; -BEGIN_EVENT_TABLE(wxComboPopupExtraEventHandler, wxEvtHandler) - EVT_MOUSE_EVENTS(wxComboPopupExtraEventHandler::OnMouseEvent) +BEGIN_EVENT_TABLE(wxComboPopupEvtHandler, wxEvtHandler) + EVT_MOUSE_EVENTS(wxComboPopupEvtHandler::OnMouseEvent) END_EVENT_TABLE() -void wxComboPopupExtraEventHandler::OnMouseEvent( wxMouseEvent& event ) +void wxComboPopupEvtHandler::OnMouseEvent( wxMouseEvent& event ) { wxPoint pt = event.GetPosition(); - wxSize sz = m_combo->GetPopupControl()->GetClientSize(); + 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; + bool relayToButton = false; + + event.Skip(); - if ( evtType == wxEVT_MOTION || - evtType == wxEVT_LEFT_DOWN || - evtType == wxEVT_RIGHT_DOWN ) + if ( !isInside || !m_combo->IsPopupShown() ) { - // Block motion and click events outside the popup - if ( !isInside ) + // Mouse is outside the popup or popup is not actually shown (yet) + + if ( evtType == wxEVT_MOTION || + evtType == wxEVT_LEFT_DOWN || + evtType == wxEVT_LEFT_UP || + evtType == wxEVT_RIGHT_DOWN ) { + // Block motion and click events outside the popup event.Skip(false); - return; } } - else if ( evtType == wxEVT_LEFT_UP ) + else { - // Don't let left-down events in if outside - if ( evtType == wxEVT_LEFT_DOWN ) - { - if ( !isInside ) - return; - } + // Mouse is inside the popup, which is fully shown - if ( !m_beenInside ) + m_beenInside = true; + + // Do not let the popup control respond to mouse events until + // mouse press used to display the popup has been lifted. This + // is important for users with slower mouse fingers or mouse + // drivers. Note that we have some redundancy here, just in + // case the popup is some native control that does not emit all + // mouse event types. + if ( evtType == wxEVT_MOTION ) { - if ( isInside ) + if ( m_blockEventsToPopup ) { - m_beenInside = true; + if ( event.LeftIsDown() ) + event.Skip(false); + else + m_blockEventsToPopup = false; } - else + } + else if ( evtType == wxEVT_LEFT_DOWN ) + { + if ( m_blockEventsToPopup ) + m_blockEventsToPopup = false; + } + else if ( evtType == wxEVT_LEFT_UP ) + { + if ( m_blockEventsToPopup ) { - // - // 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; + // On first left up, stop blocking mouse events (but still + // block this one) + m_blockEventsToPopup = false; + event.Skip(false); + + // Also, this button press was (probably) used to display + // the popup, so relay it back to the drop-down button + // (which supposedly originated it). This is necessary to + // refresh it properly. + relayToButton = true; } + } + else if ( m_blockEventsToPopup ) + { + event.Skip(false); + } + } - event.Skip(); + // + // Some mouse events to popup that happen outside it, before cursor + // has been inside the popup, need to be ignored by it but relayed to + // the dropbutton. + // + if ( evtType == wxEVT_LEFT_UP ) + { + if ( !m_combo->IsPopupShown() ) + { + event.Skip(false); + relayToButton = true; + } + else if ( !isInside && !m_beenInside ) + { + // Popup is shown but the cursor is not inside, nor it has been + relayToButton = true; } } - event.Skip(); + if ( relayToButton ) + { + wxWindow* btn = m_combo->GetButton(); + if ( btn ) + btn->GetEventHandler()->ProcessEvent(event); + else + // Bypass the event handling mechanism. Using it would be + // confusing for the platform-specific wxComboCtrl + // implementations. + m_combo->HandleButtonMouseEvent(event, 0); + } } // ---------------------------------------------------------------------------- -// wxComboControlBase +// wxComboCtrlTextCtrl // ---------------------------------------------------------------------------- +class wxComboCtrlTextCtrl : public wxTextCtrl +{ +public: + wxComboCtrlTextCtrl() : wxTextCtrl() { } + virtual ~wxComboCtrlTextCtrl() { } + + virtual wxWindow *GetMainWindowOfCompositeControl() + { + wxComboCtrl* combo = (wxComboCtrl*) GetParent(); -BEGIN_EVENT_TABLE(wxComboControlBase, wxControl) - EVT_TEXT(wxID_ANY,wxComboControlBase::OnTextCtrlEvent) - EVT_SIZE(wxComboControlBase::OnSizeEvent) - EVT_SET_FOCUS(wxComboControlBase::OnFocusEvent) - EVT_KILL_FOCUS(wxComboControlBase::OnFocusEvent) - //EVT_BUTTON(wxID_ANY,wxComboControlBase::OnButtonClickEvent) - EVT_TEXT_ENTER(wxID_ANY,wxComboControlBase::OnTextCtrlEvent) - EVT_SYS_COLOUR_CHANGED(wxComboControlBase::OnSysColourChanged) -END_EVENT_TABLE() + // Returning this instead of just 'parent' lets FindFocus work + // correctly even when parent control is a child of a composite + // generic control (as is case with wxGenericDatePickerCtrl). + return combo->GetMainWindowOfCompositeControl(); + } +}; + +// ---------------------------------------------------------------------------- +// wxComboCtrlBase +// ---------------------------------------------------------------------------- -IMPLEMENT_ABSTRACT_CLASS(wxComboControlBase, wxControl) +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_CHAR(wxComboCtrlBase::OnCharEvent) + EVT_TEXT_ENTER(wxID_ANY,wxComboCtrlBase::OnTextCtrlEvent) + EVT_SYS_COLOUR_CHANGED(wxComboCtrlBase::OnSysColourChanged) +END_EVENT_TABLE() + -// Have global double buffer - should be enough for multiple combos -static wxBitmap* gs_doubleBuffer = (wxBitmap*) NULL; +IMPLEMENT_ABSTRACT_CLASS(wxComboCtrlBase, wxControl) -void wxComboControlBase::Init() +void wxComboCtrlBase::Init() { - m_winPopup = (wxWindow *)NULL; - m_popup = (wxWindow *)NULL; - m_isPopupShown = false; - m_btn = (wxWindow*) NULL; - m_text = (wxTextCtrl*) NULL; - m_popupInterface = (wxComboPopup*) NULL; + m_winPopup = NULL; + m_popup = NULL; + m_popupWinState = Hidden; + m_btn = NULL; + m_text = NULL; + m_popupInterface = NULL; - m_extraEvtHandler = (wxEvtHandler*) NULL; - m_popupExtraHandler = (wxEvtHandler*) NULL; - m_textEvtHandler = (wxEvtHandler*) NULL; + m_popupEvtHandler = NULL; + m_textEvtHandler = NULL; #if INSTALL_TOPLEV_HANDLER - m_toplevEvtHandler = (wxEvtHandler*) NULL; + m_toplevEvtHandler = NULL; #endif + m_mainCtrlWnd = this; + m_heightPopup = -1; m_widthMinPopup = -1; m_anchorSide = 0; @@ -662,26 +895,30 @@ void wxComboControlBase::Init() m_btnState = 0; m_btnWidDefault = 0; m_blankButtonBg = false; - m_btnWid = m_btnHei = 0; + 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_marginLeft = -1; m_iFlags = 0; - m_downReceived = false; + m_textCtrlStyle = 0; m_timeCanAcceptClick = 0; + + m_resetFocus = false; } -bool wxComboControlBase::Create(wxWindow *parent, - wxWindowID id, - const wxString& value, - const wxPoint& pos, - const wxSize& size, - long style, - const wxValidator& validator, - const wxString& name) +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, @@ -696,82 +933,108 @@ bool wxComboControlBase::Create(wxWindow *parent, // Get colours OnThemeChange(); - m_absIndent = GetNativeTextIndent(); + m_marginLeft = 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 wxComboControlBase::InstallInputHandlers( bool alsoTextCtrl ) +void wxComboCtrlBase::InstallInputHandlers() { - if ( m_text && alsoTextCtrl ) + if ( m_text ) { m_textEvtHandler = new wxComboBoxExtraInputHandler(this); m_text->PushEventHandler(m_textEvtHandler); } - - wxComboBoxExtraInputHandler* inputHandler = new wxComboBoxExtraInputHandler(this); - PushEventHandler(inputHandler); - m_extraEvtHandler = inputHandler; } -void wxComboControlBase::CreateTextCtrl( int extraStyle, const wxValidator& validator ) +void +wxComboCtrlBase::CreateTextCtrl(int style, 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 | - extraStyle, - validator); + 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 | m_textCtrlStyle; + + 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; - // This is required for some platforms (GTK+ atleast) - m_text->SetSizeHints(2,4); + m_text = new wxComboCtrlTextCtrl(); + m_text->Create(this, wxID_ANY, m_valueString, + wxDefaultPosition, wxSize(10,-1), + style, validator); + m_text->SetHint(m_hintText); } } -void wxComboControlBase::OnThemeChange() +void wxComboCtrlBase::OnThemeChange() { - SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + // 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__ + #if defined(__WXMSW__) || defined(__WXGTK__) + wxVisualAttributes vattrs = wxComboBox::GetClassDefaultAttributes(); + #else + wxVisualAttributes vattrs; + vattrs.colFg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); + vattrs.colBg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); + #endif + + // Only change the colours if application has not specified + // custom ones. + if ( !m_hasFgCol ) + { + SetOwnForegroundColour(vattrs.colFg); + m_hasFgCol = false; + } + if ( !m_hasBgCol ) + { + SetOwnBackgroundColour(vattrs.colBg); + m_hasBgCol = false; + } +#endif // !__WXMAC__ } -wxComboControlBase::~wxComboControlBase() +wxComboCtrlBase::~wxComboCtrlBase() { if ( HasCapture() ) ReleaseMouse(); - delete gs_doubleBuffer; - gs_doubleBuffer = (wxBitmap*) NULL; - #if INSTALL_TOPLEV_HANDLER delete ((wxComboFrameEventHandler*)m_toplevEvtHandler); - m_toplevEvtHandler = (wxEvtHandler*) NULL; + m_toplevEvtHandler = NULL; #endif - if ( m_popup ) - m_popup->RemoveEventHandler(m_popupExtraHandler); - - delete m_popupExtraHandler; - - HidePopup(); - - delete m_popupInterface; - delete m_winPopup; - - RemoveEventHandler(m_extraEvtHandler); + DestroyPopup(); if ( m_text ) m_text->RemoveEventHandler(m_textEvtHandler); delete m_textEvtHandler; - delete m_extraEvtHandler; } @@ -780,31 +1043,38 @@ wxComboControlBase::~wxComboControlBase() // ---------------------------------------------------------------------------- // Recalculates button and textctrl areas -void wxComboControlBase::CalculateAreas( int btnWidth ) +void wxComboCtrlBase::CalculateAreas( int btnWidth ) { wxSize sz = GetClientSize(); int customBorder = m_widthCustomBorder; - bool buttonOutside; int btnBorder; // border for button only - if ( ( (m_iFlags & wxCC_BUTTON_OUTSIDE_BORDER) || m_blankButtonBg ) && - m_btnSpacingX == 0 && m_btnWid == 0 && m_btnHei == 0 && - (!m_bmpNormal.Ok() || m_blankButtonBg) ) + // 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 ) { - buttonOutside = true; m_iFlags |= wxCC_IFLAG_BUTTON_OUTSIDE; btnBorder = 0; } + else if ( (m_iFlags & wxCC_BUTTON_COVERS_BORDER) && + m_btnSpacingX == 0 && !m_bmpNormal.Ok() ) + { + m_iFlags &= ~(wxCC_IFLAG_BUTTON_OUTSIDE); + btnBorder = 0; + } else { - buttonOutside = false; m_iFlags &= ~(wxCC_IFLAG_BUTTON_OUTSIDE); btnBorder = customBorder; } // Defaul indentation - if ( m_absIndent < 0 ) - m_absIndent = GetNativeTextIndent(); + if ( m_marginLeft < 0 ) + m_marginLeft = GetNativeTextIndent(); int butWidth = btnWidth; @@ -816,20 +1086,32 @@ void wxComboControlBase::CalculateAreas( int btnWidth ) if ( butWidth <= 0 ) return; + int butHeight = sz.y - btnBorder*2; + // Adjust button width - if ( m_btnWid < 0 ) - butWidth += m_btnWid; - else if ( m_btnWid > 0 ) + 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; - int butHeight = sz.y; - - butHeight -= btnBorder*2; + 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; - else if ( m_btnHei > 0 ) + if ( m_btnHei > 0 ) butHeight = m_btnHei; // Use size of normal bitmap if... @@ -857,7 +1139,12 @@ void wxComboControlBase::CalculateAreas( int btnWidth ) if ( (sz.y-(customBorder*2)) < butHeight && btnWidth == 0 ) { int newY = butHeight+(customBorder*2); - SetClientSize(-1,newY); + SetClientSize(wxDefaultCoord,newY); + if ( m_bmpNormal.Ok() || m_btnArea.width != butWidth || m_btnArea.height != butHeight ) + m_iFlags |= wxCC_IFLAG_HAS_NONSTANDARD_BUTTON; + else + m_iFlags &= ~wxCC_IFLAG_HAS_NONSTANDARD_BUTTON; + sz.y = newY; } } @@ -868,14 +1155,14 @@ void wxComboControlBase::CalculateAreas( int btnWidth ) m_btnSize.y = butHeight; m_btnArea.x = ( m_btnSide==wxRIGHT ? sz.x - butAreaWid - btnBorder : btnBorder ); - m_btnArea.y = btnBorder; + m_btnArea.y = btnBorder + FOCUS_RING; m_btnArea.width = butAreaWid; - m_btnArea.height = sz.y - (btnBorder*2); + m_btnArea.height = sz.y - ((btnBorder+FOCUS_RING)*2); - 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); + 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 ) @@ -886,49 +1173,73 @@ void wxComboControlBase::CalculateAreas( int btnWidth ) */ } -void wxComboControlBase::PositionTextCtrl( int textCtrlXAdjust, int textCtrlYAdjust ) +void wxComboCtrlBase::PositionTextCtrl( int textCtrlXAdjust, int textCtrlYAdjust ) { if ( !m_text ) return; wxSize sz = GetClientSize(); - int customBorder = m_widthCustomBorder; + int customBorder = m_widthCustomBorder; if ( (m_text->GetWindowStyleFlag() & wxBORDER_MASK) == wxNO_BORDER ) { - // Centre textctrl + int x; + + if ( !m_widthCustomPaint ) + { + // No special custom paint area - we can use 0 left margin + // with wxTextCtrl. + if ( m_text->SetMargins(0) ) + textCtrlXAdjust = 0; + x = m_tcArea.x + m_marginLeft + textCtrlXAdjust; + } + else + { + // There is special custom paint area - it is better to + // use some margin with the wxTextCtrl. + m_text->SetMargins(m_marginLeft); + x = m_tcArea.x + m_widthCustomPaint + + m_marginLeft + textCtrlXAdjust; + } + + // Centre textctrl vertically, if needed +#if !TEXTCTRL_TEXT_CENTERED int tcSizeY = m_text->GetBestSize().y; - int diff = sz.y - tcSizeY; - int y = textCtrlYAdjust + (diff/2); + int diff0 = sz.y - tcSizeY; + int y = textCtrlYAdjust + (diff0/2); +#else + wxUnusedVar(textCtrlYAdjust); + int y = 0; +#endif 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 ); + m_text->SetSize(x, + y, + m_tcArea.width - m_tcArea.x - x, + -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 ) + int diff1 = (y + tsz.y) - (sz.y - customBorder); + if ( diff1 >= 0 ) { - tsz.y = tsz.y - diff - 1; + tsz.y = tsz.y - diff1 - 1; m_text->SetSize(tsz); } } else { - m_text->SetSize( m_tcArea.x, - 0, - sz.x - m_btnArea.x - m_widthCustomPaint - customBorder, - sz.y ); + // If it has border, have textctrl fill the entire text field. + m_text->SetSize( m_tcArea.x + m_widthCustomPaint, + m_tcArea.y, + m_tcArea.width - m_widthCustomPaint, + m_tcArea.height ); } } -wxSize wxComboControlBase::DoGetBestSize() const +wxSize wxComboCtrlBase::DoGetBestSize() const { wxSize sizeText(150,0); @@ -968,27 +1279,37 @@ wxSize wxComboControlBase::DoGetBestSize() const fhei += 1; #endif - wxSize ret(sizeText.x + COMBO_MARGIN + DEFAULT_DROPBUTTON_WIDTH, - fhei); +#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 wxComboControlBase::DoMoveWindow(int x, int y, int width, int height) -{ - // SetSize is called last in create, so it marks the end of creation - m_iFlags |= wxCC_IFLAG_CREATED; - - wxControl::DoMoveWindow(x, y, width, height); -} - -void wxComboControlBase::OnSizeEvent( wxSizeEvent& event ) +void wxComboCtrlBase::OnSizeEvent( wxSizeEvent& event ) { if ( !IsCreated() ) return; - // defined by actual wxComboControls + // defined by actual wxComboCtrls OnResize(); event.Skip(); @@ -998,7 +1319,7 @@ void wxComboControlBase::OnSizeEvent( wxSizeEvent& event ) // standard operations // ---------------------------------------------------------------------------- -bool wxComboControlBase::Enable(bool enable) +bool wxComboCtrlBase::Enable(bool enable) { if ( !wxControl::Enable(enable) ) return false; @@ -1008,10 +1329,12 @@ bool wxComboControlBase::Enable(bool enable) if ( m_text ) m_text->Enable(enable); + Refresh(); + return true; } -bool wxComboControlBase::Show(bool show) +bool wxComboCtrlBase::Show(bool show) { if ( !wxControl::Show(show) ) return false; @@ -1025,19 +1348,26 @@ bool wxComboControlBase::Show(bool show) return true; } -bool wxComboControlBase::SetFont ( const wxFont& font ) +bool wxComboCtrlBase::SetFont ( const wxFont& font ) { if ( !wxControl::SetFont(font) ) return false; - if (m_text) + if ( m_text ) + { + // Without hiding the wxTextCtrl there would be some + // visible 'flicker' (at least on Windows XP). + m_text->Hide(); m_text->SetFont(font); + OnResize(); + m_text->Show(); + } return true; } #if wxUSE_TOOLTIPS -void wxComboControlBase::DoSetToolTip(wxToolTip *tooltip) +void wxComboCtrlBase::DoSetToolTip(wxToolTip *tooltip) { wxControl::DoSetToolTip(tooltip); @@ -1050,22 +1380,63 @@ void wxComboControlBase::DoSetToolTip(wxToolTip *tooltip) } else { - if ( m_text ) m_text->SetToolTip( (wxToolTip*) NULL ); - if ( m_btn ) m_btn->SetToolTip( (wxToolTip*) NULL ); + if ( m_text ) m_text->SetToolTip( NULL ); + if ( m_btn ) m_btn->SetToolTip( NULL ); } } #endif // wxUSE_TOOLTIPS -// ---------------------------------------------------------------------------- -// painting -// ---------------------------------------------------------------------------- - -// draw focus background on area in a way typical on platform -void wxComboControlBase::DrawFocusBackground( wxDC& dc, const wxRect& rect, int flags ) +#if wxUSE_VALIDATORS +void wxComboCtrlBase::SetValidator(const wxValidator& validator) { - wxSize sz = GetClientSize(); + wxTextCtrl* textCtrl = GetTextCtrl(); + + if ( textCtrl ) + textCtrl->SetValidator( validator ); + else + wxControl::SetValidator( validator ); +} + +wxValidator* wxComboCtrlBase::GetValidator() +{ + wxTextCtrl* textCtrl = GetTextCtrl(); + + return textCtrl ? textCtrl->GetValidator() : wxControl::GetValidator(); +} +#endif // wxUSE_VALIDATORS + +bool wxComboCtrlBase::SetForegroundColour(const wxColour& colour) +{ + if ( wxControl::SetForegroundColour(colour) ) + { + if ( m_text ) + m_text->SetForegroundColour(colour); + return true; + } + return false; +} + +bool wxComboCtrlBase::SetBackgroundColour(const wxColour& colour) +{ + if ( wxControl::SetBackgroundColour(colour) ) + { + if ( m_text ) + m_text->SetBackgroundColour(colour); + return true; + } + return false; +} +// ---------------------------------------------------------------------------- +// 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 + bool doDrawFocusRect; // also selected // For smaller size control (and for disabled background) use less spacing int focusSpacingX; @@ -1075,7 +1446,7 @@ void wxComboControlBase::DrawFocusBackground( wxDC& dc, const wxRect& rect, int { // Drawing control isEnabled = IsEnabled(); - isFocused = ShouldDrawFocus(); + doDrawFocusRect = ShouldDrawFocus() && !(m_iFlags & wxCC_FULL_BUTTON); // Windows-style: for smaller size control (and for disabled background) use less spacing focusSpacingX = isEnabled ? 2 : 1; @@ -1085,7 +1456,7 @@ void wxComboControlBase::DrawFocusBackground( wxDC& dc, const wxRect& rect, int { // Drawing a list item isEnabled = true; // they are never disabled - isFocused = flags & wxCONTROL_SELECTED ? true : false; + doDrawFocusRect = (flags & wxCONTROL_SELECTED) != 0; focusSpacingX = 0; focusSpacingY = 0; @@ -1095,45 +1466,101 @@ void wxComboControlBase::DrawFocusBackground( wxDC& dc, const wxRect& rect, int wxRect selRect(rect); selRect.y += focusSpacingY; selRect.height -= (focusSpacingY*2); - selRect.x += m_widthCustomPaint + focusSpacingX; - selRect.width -= m_widthCustomPaint + (focusSpacingX*2); + + int wcp = 0; + + if ( !(flags & wxCONTROL_ISSUBMENU) ) + wcp += m_widthCustomPaint; + + selRect.x += wcp + focusSpacingX; + selRect.width -= wcp + (focusSpacingX*2); wxColour bgCol; + wxColour fgCol; + + bool doDrawSelRect = true; + + // Determine foreground colour + if ( isEnabled ) + { + if ( doDrawFocusRect ) + { + fgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT); + } + else if ( m_hasFgCol ) + { + // Honour the custom foreground colour + fgCol = GetForegroundColour(); + } + else + { + fgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); + } + } + else + { + fgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT); + } + // Determine background colour if ( isEnabled ) { - // If popup is hidden and this control is focused, - // then draw the focus-indicator (selbgcolor background etc.). - if ( isFocused ) + if ( doDrawFocusRect ) { - dc.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT) ); bgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT); } + else if ( m_hasBgCol ) + { + // Honour the custom background colour + bgCol = GetBackgroundColour(); + } else { - dc.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT) ); +#ifndef __WXMAC__ // see note in OnThemeChange + doDrawSelRect = false; 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.SetTextForeground( fgCol ); dc.SetBrush( bgCol ); - dc.SetPen( bgCol ); - dc.DrawRectangle( selRect ); + if ( doDrawSelRect ) + { + 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 wxComboControlBase::DrawButton( wxDC& dc, const wxRect& rect, bool paintBg ) +void wxComboCtrlBase::DrawButton( wxDC& dc, const wxRect& rect, int flags ) { int drawState = m_btnState; -#ifdef __WXGTK__ - if ( m_isPopupShown ) + if ( (m_iFlags & wxCC_BUTTON_STAYS_DOWN) && + GetPopupWindowState() >= Animating ) drawState |= wxCONTROL_PRESSED; -#endif wxRect drawRect(rect.x+m_btnSpacingX, rect.y+((rect.height-m_btnSize.y)/2), @@ -1153,10 +1580,23 @@ void wxComboControlBase::DrawButton( wxDC& dc, const wxRect& rect, bool paintBg if ( !m_bmpNormal.Ok() ) { + if ( flags & Button_BitmapOnly ) + return; + // 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 ) + if ( flags & Button_PaintBackground ) + { + 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 wxRendererNative::Get().DrawComboBoxDropButton(this, @@ -1183,7 +1623,7 @@ void wxComboControlBase::DrawButton( wxDC& dc, const wxRect& rect, bool paintBg { // 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 ) + if ( flags & Button_PaintBackground ) { wxColour bgCol = GetParent()->GetBackgroundColour(); //wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE); //wxColour bgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); @@ -1192,18 +1632,20 @@ void wxComboControlBase::DrawButton( wxDC& dc, const wxRect& rect, bool paintBg dc.DrawRectangle(rect); } - wxRendererNative::Get().DrawPushButton(this, - dc, - drawRect, - drawState); - + if ( !(flags & Button_BitmapOnly) ) + { + 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 ) + if ( flags & Button_PaintBackground ) dc.DrawRectangle(rect); } @@ -1215,7 +1657,7 @@ void wxComboControlBase::DrawButton( wxDC& dc, const wxRect& rect, bool paintBg } } -void wxComboControlBase::RecalcAndRefresh() +void wxComboCtrlBase::RecalcAndRefresh() { if ( IsCreated() ) { @@ -1225,58 +1667,39 @@ void wxComboControlBase::RecalcAndRefresh() } } -wxBitmap& wxComboControlBase::GetBufferBitmap( const wxSize& sz ) const -{ - // 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); - } - return *gs_doubleBuffer; -} - - -bool wxComboControlBase::OnDrawListItem( wxDC& WXUNUSED(dc), - const wxRect& WXUNUSED(rect), - int WXUNUSED(item), - int WXUNUSED(flags) ) -{ - return false; // signals caller to make default drawing -} - -wxCoord wxComboControlBase::OnMeasureListItem( int WXUNUSED(item) ) -{ - return -1; // signals caller to use default -} - -wxCoord wxComboControlBase::OnMeasureListItemWidth( int WXUNUSED(item) ) -{ - return -1; // signals caller to use default -} - // ---------------------------------------------------------------------------- // miscellaneous event handlers // ---------------------------------------------------------------------------- -void wxComboControlBase::OnTextCtrlEvent(wxCommandEvent& event) +void wxComboCtrlBase::OnTextCtrlEvent(wxCommandEvent& event) { - // Change event id and relay it forward + 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 wxComboControlBase::HandleButtonMouseEvent( wxMouseEvent& event, - int flags ) +bool wxComboCtrlBase::HandleButtonMouseEvent( wxMouseEvent& event, + int flags ) { int type = event.GetEventType(); if ( type == wxEVT_MOTION ) { - if ( flags & wxCC_MF_ON_BUTTON ) + if ( (flags & wxCC_MF_ON_BUTTON) && + IsPopupWindowState(Hidden) ) { if ( !(m_btnState & wxCONTROL_CURRENT) ) { @@ -1294,28 +1717,19 @@ bool wxComboControlBase::HandleButtonMouseEvent( wxMouseEvent& event, Refresh(); } } - else if ( type == wxEVT_LEFT_DOWN ) + else if ( type == wxEVT_LEFT_DOWN || type == wxEVT_LEFT_DCLICK ) { - // Only accept event if it wasn't right after popup dismiss - //if ( ::wxGetLocalTimeMillis() > m_timeCanClick ) + if ( flags & (wxCC_MF_ON_CLICK_AREA|wxCC_MF_ON_BUTTON) ) { - // Need to test this, because it might be outside. - if ( flags & wxCC_MF_ON_BUTTON ) - { - m_btnState |= wxCONTROL_PRESSED; - Refresh(); + 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(); - } + 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 - { - m_btnState = 0; - }*/ } else if ( type == wxEVT_LEFT_UP ) { @@ -1329,7 +1743,7 @@ bool wxComboControlBase::HandleButtonMouseEvent( wxMouseEvent& event, // If mouse was inside, fire the click event. if ( m_iFlags & wxCC_POPUP_ON_MOUSE_UP ) { - if ( flags & wxCC_MF_ON_BUTTON ) + if ( flags & (wxCC_MF_ON_CLICK_AREA|wxCC_MF_ON_BUTTON) ) OnButtonClick(); } @@ -1344,7 +1758,7 @@ bool wxComboControlBase::HandleButtonMouseEvent( wxMouseEvent& event, m_btnState &= ~(wxCONTROL_CURRENT); // Mouse hover ends - if ( !m_isPopupShown ) + if ( IsPopupWindowState(Hidden) ) { m_btnState &= ~(wxCONTROL_PRESSED); Refresh(); @@ -1354,61 +1768,34 @@ bool wxComboControlBase::HandleButtonMouseEvent( wxMouseEvent& event, else return false; + // Never have 'hot' state when popup is being shown + // (this is mostly needed because of the animation). + if ( !IsPopupWindowState(Hidden) ) + m_btnState &= ~wxCONTROL_CURRENT; + return true; } -// Conversion to double-clicks and some basic filtering // returns true if event was consumed or filtered -//bool wxComboControlBase::PreprocessMouseEvent( wxMouseEvent& event, bool isOnButtonArea ) -bool wxComboControlBase::PreprocessMouseEvent( wxMouseEvent& event, - int flags ) +bool wxComboCtrlBase::PreprocessMouseEvent( wxMouseEvent& event, + int WXUNUSED(flags) ) { wxLongLong t = ::wxGetLocalTimeMillis(); int evtType = event.GetEventType(); - // - // Generate our own double-clicks - // (to allow on-focus dc-event on double-clicks instead of triple-clicks) - if ( (m_windowStyle & wxCC_SPECIAL_DCLICK) && - !m_isPopupShown && - //!(handlerFlags & wxCC_MF_ON_BUTTON) ) - !(flags & wxCC_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 ) +#if USES_WXPOPUPWINDOW || USES_GENERICTLW + if ( m_popupWinType != POPUPWIN_WXPOPUPTRANSIENTWINDOW ) + { + if ( IsPopupWindowState(Visible) && + ( evtType == wxEVT_LEFT_DOWN || evtType == wxEVT_RIGHT_DOWN ) ) { - // We'll make our own double-clicks - //evtType = 0; - event.SetEventType(0); + HidePopup(true); 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; - } - } } +#endif - // Filter out clicks on button immediately after popup dismiss (Windows like behaviour) + // Filter out clicks on button immediately after popup dismiss if ( evtType == wxEVT_LEFT_DOWN && t < m_timeCanAcceptClick ) { event.SetEventType(0); @@ -1418,20 +1805,19 @@ bool wxComboControlBase::PreprocessMouseEvent( wxMouseEvent& event, return false; } -void wxComboControlBase::HandleNormalMouseEvent( wxMouseEvent& event ) +void wxComboCtrlBase::HandleNormalMouseEvent( wxMouseEvent& event ) { int evtType = event.GetEventType(); if ( (evtType == wxEVT_LEFT_DOWN || evtType == wxEVT_LEFT_DCLICK) && (m_windowStyle & wxCB_READONLY) ) { - if ( m_isPopupShown ) + if ( GetPopupWindowState() >= Animating ) { - #if !wxUSE_POPUPWIN - // Normally do nothing - evt handler should close it for us - #elif !USE_TRANSIENT_POPUP + #if USES_WXPOPUPWINDOW // Click here always hides the popup. - HidePopup(); + if ( m_popupWinType == POPUPWIN_WXPOPUPWINDOW ) + HidePopup(true); #endif } else @@ -1451,42 +1837,133 @@ void wxComboControlBase::HandleNormalMouseEvent( wxMouseEvent& event ) } } } - else - if ( m_isPopupShown ) + else if ( evtType == wxEVT_MOUSEWHEEL ) { - // relay (some) mouse events to the popup - if ( evtType == wxEVT_MOUSEWHEEL ) - m_popup->AddPendingEvent(event); + if ( IsPopupShown() ) + { + // relay (some) mouse events to the popup + m_popup->GetEventHandler()->ProcessEvent(event); + } + else if ( event.GetWheelAxis() == 0 && + event.GetWheelRotation() != 0 && + event.GetModifiers() == 0 ) + { + // Translate mousewheel actions into key up/down. This is + // the simplest way of getting native behaviour: scrolling the + // wheel moves selection up/down by one item. + wxKeyEvent kevent(wxEVT_KEY_DOWN); + kevent.m_keyCode = event.GetWheelRotation() > 0 + ? WXK_UP + : WXK_DOWN; + GetEventHandler()->ProcessEvent(kevent); + } + else + { + event.Skip(); + } } else if ( evtType ) + { event.Skip(); + } } -void wxComboControlBase::OnFocusEvent( wxFocusEvent& ) +void wxComboCtrlBase::OnKeyEvent(wxKeyEvent& event) { - // First click is the first part of double-click - // Some platforms don't generate down-less mouse up-event - // (Windows does, GTK+2 doesn't), so that's why we have - // to do this. - m_timeLastMouseUp = ::wxGetLocalTimeMillis(); + if ( IsPopupShown() ) + { + // pass it to the popped up control + GetPopupControl()->GetControl()->GetEventHandler()->ProcessEvent(event); + } + else // no popup + { + wxWindow* mainCtrl = GetMainWindowOfCompositeControl(); - if ( m_text ) + if ( mainCtrl->GetParent()->HasFlag(wxTAB_TRAVERSAL) ) + { + if ( mainCtrl->HandleAsNavigationKey(event) ) + return; + } + + if ( IsKeyPopupToggle(event) ) + { + OnButtonClick(); + return; + } + + int comboStyle = GetWindowStyle(); + wxComboPopup* popupInterface = GetPopupControl(); + + if ( !popupInterface ) + { + event.Skip(); + return; + } + + int keycode = event.GetKeyCode(); + + if ( (comboStyle & wxCB_READONLY) || + (keycode != WXK_RIGHT && keycode != WXK_LEFT) ) + { + popupInterface->OnComboKeyEvent(event); + } + else + event.Skip(); + } +} + +void wxComboCtrlBase::OnCharEvent(wxKeyEvent& event) +{ + if ( IsPopupShown() ) { - m_text->SetFocus(); + // pass it to the popped up control + GetPopupControl()->GetControl()->GetEventHandler()->ProcessEvent(event); + } + else // no popup + { + wxComboPopup* popupInterface = GetPopupControl(); + if ( popupInterface ) + { + popupInterface->OnComboCharEvent(event); + } + else + { + event.Skip(); + } + } +} + +void wxComboCtrlBase::OnFocusEvent( wxFocusEvent& event ) +{ + if ( event.GetEventType() == wxEVT_SET_FOCUS ) + { + wxWindow* tc = GetTextCtrl(); + if ( tc && tc != DoFindFocus() ) + { + tc->SetFocus(); + } + } + + Refresh(); +} + +void wxComboCtrlBase::OnIdleEvent( wxIdleEvent& WXUNUSED(event) ) +{ + if ( m_resetFocus ) + { + m_resetFocus = false; + wxWindow* tc = GetTextCtrl(); + if ( tc ) + tc->SetFocus(); } - else - // no need to check for m_widthCustomPaint - that - // area never gets special handling when selected - // (in writable mode, that is) - Refresh(); } -void wxComboControlBase::OnSysColourChanged(wxSysColourChangedEvent& WXUNUSED(event)) +void wxComboCtrlBase::OnSysColourChanged(wxSysColourChangedEvent& WXUNUSED(event)) { OnThemeChange(); - // indentation may also have changed - if ( !(m_iFlags & wxCC_IFLAG_INDENT_SET) ) - m_absIndent = GetNativeTextIndent(); + // left margin may also have changed + if ( !(m_iFlags & wxCC_IFLAG_LEFT_MARGIN_SET) ) + m_marginLeft = GetNativeTextIndent(); RecalcAndRefresh(); } @@ -1495,19 +1972,51 @@ void wxComboControlBase::OnSysColourChanged(wxSysColourChangedEvent& WXUNUSED(ev // ---------------------------------------------------------------------------- // Create popup window and the child control -void wxComboControlBase::CreatePopup() +void wxComboCtrlBase::CreatePopup() { wxComboPopup* popupInterface = m_popupInterface; wxWindow* popup; if ( !m_winPopup ) - m_winPopup = new wxComboPopupWindow( this, wxNO_BORDER ); + { +#ifdef wxComboPopupWindowBase2 + if ( m_iFlags & wxCC_IFLAG_USE_ALT_POPUP ) + { + #if !USES_GENERICTLW + m_winPopup = new wxComboPopupWindowBase2( this, wxNO_BORDER ); + #else + int tlwFlags = wxNO_BORDER; + #ifdef wxCC_GENERIC_TLW_IS_FRAME + tlwFlags |= wxFRAME_NO_TASKBAR; + #endif + + #ifdef wxCC_GENERIC_TLW_IS_NONOWNEDWINDOW + m_winPopup = new wxComboPopupWindowBase2( this, wxID_ANY, + wxPoint(-21,-21), wxSize(20, 20), + tlwFlags ); + #else + m_winPopup = new wxComboPopupWindowBase2( this, wxID_ANY, wxEmptyString, + wxPoint(-21,-21), wxSize(20, 20), + tlwFlags ); + #endif + #endif + m_popupWinType = SECONDARY_POPUP_TYPE; + } + else +#endif // wxComboPopupWindowBase2 + { + 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 ); + m_popupEvtHandler = new wxComboPopupEvtHandler(this); + popup->PushEventHandler( m_popupEvtHandler ); // This may be helpful on some platforms // (eg. it bypasses a wxGTK popupwindow bug where @@ -1517,39 +2026,89 @@ void wxComboControlBase::CreatePopup() popupInterface->m_iFlags |= wxCP_IFLAG_CREATED; } -void wxComboControlBase::SetPopupControl( wxComboPopup* iface ) +// Destroy popup window and the child control +void wxComboCtrlBase::DestroyPopup() { - delete m_popupInterface; - delete m_winPopup; + HidePopup(true); + + if ( m_popup ) + m_popup->RemoveEventHandler(m_popupEvtHandler); + + wxDELETE(m_popupEvtHandler); + + wxDELETE(m_popupInterface); + + if ( m_winPopup ) + { + m_winPopup->RemoveEventHandler(m_popupWinEvtHandler); + wxDELETE(m_popupWinEvtHandler); + m_winPopup->Destroy(); + m_winPopup = NULL; + } + + m_popup = 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() || m_winPopup ) + if ( !iface->LazyCreate() ) { CreatePopup(); } else { - m_popup = (wxWindow*) NULL; + m_popup = NULL; } - // This must be after creation - if ( m_valueString ) + // 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 wxComboControlBase::OnButtonClick() +void wxComboCtrlBase::OnButtonClick() { // Derived classes can override this method for totally custom // popup action - ShowPopup(); + if ( !IsPopupWindowState(Visible) ) + { + wxCommandEvent event(wxEVT_COMMAND_COMBOBOX_DROPDOWN, GetId()); + event.SetEventObject(this); + HandleWindowEvent(event); + + ShowPopup(); + } + else + { + HidePopup(true); + } } -void wxComboControlBase::ShowPopup() +void wxComboCtrlBase::ShowPopup() { - wxCHECK_RET( m_popupInterface, wxT("no popup interface set for wxComboControl") ); - wxCHECK_RET( !IsPopupShown(), wxT("popup window already shown") ); + EnsurePopupControl(); + wxCHECK_RET( !IsPopupWindowState(Visible), wxT("popup window already shown") ); + + if ( IsPopupWindowState(Animating) ) + return; SetFocus(); @@ -1587,7 +2146,8 @@ void wxComboControlBase::ShowPopup() // 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(); + wxWindow* mainCtrl = GetMainWindowOfCompositeControl(); + wxWindow* parent = mainCtrl->GetParent(); int parentFlags = parent->GetWindowStyle(); if ( parentFlags & wxTAB_TRAVERSAL ) { @@ -1606,6 +2166,8 @@ void wxComboControlBase::ShowPopup() popup = m_popup; } + winPopup->Enable(); + wxASSERT( !m_popup || m_popup == popup ); // Consistency check. wxSize adjustedSize = m_popupInterface->GetAdjustedSize(widthPopup, @@ -1625,32 +2187,71 @@ void wxComboControlBase::ShowPopup() int popupX; int popupY = scrPos.y + ctrlSz.y; + // Default anchor is wxLEFT int anchorSide = m_anchorSide; if ( !anchorSide ) - anchorSide = m_btnSide; + 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; + } + } - // Anchor popup to the side the dropbutton is on + // Select x coordinate according to the anchor side if ( anchorSide == wxRIGHT ) - popupX = scrPos.x + ctrlSz.x + m_extRight- szp.x; + popupX = rightX; + else if ( anchorSide == wxLEFT ) + popupX = leftX; else - popupX = scrPos.x - m_extLeft; + popupX = 0; + + int showFlags = CanDeferShow; if ( spaceBelow < szp.y ) { popupY = scrPos.y - szp.y; + showFlags |= ShowAbove; } - // 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); +#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); - m_popup = popup; + 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 ) @@ -1663,47 +2264,86 @@ void wxComboControlBase::ShowPopup() 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; + m_popupWinState = Animating; - // Show it -#if USE_TRANSIENT_POPUP - ((wxPopupTransientWindow*)winPopup)->Popup(popup); -#else - winPopup->Show(); -#endif + wxRect popupWinRect( popupX, popupY, szp.x, szp.y ); -#if INSTALL_TOPLEV_HANDLER - // Put top level window event handler into place - if ( !m_toplevEvtHandler ) - m_toplevEvtHandler = new wxComboFrameEventHandler(this); + m_popup = popup; + if ( (m_iFlags & wxCC_IFLAG_DISABLE_POPUP_ANIM) || + AnimateShow( popupWinRect, showFlags ) ) + { + DoShowPopup( popupWinRect, showFlags ); + } +} - wxWindow* toplev = ::wxGetTopLevelParent( this ); - wxASSERT( toplev ); - ((wxComboFrameEventHandler*)m_toplevEvtHandler)->OnPopup(); - toplev->PushEventHandler( m_toplevEvtHandler ); +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 ); + +#if USES_WXPOPUPTRANSIENTWINDOW + if ( m_popupWinType == POPUPWIN_WXPOPUPTRANSIENTWINDOW ) + ((wxPopupTransientWindow*)winPopup)->Popup(m_popup); + else #endif + winPopup->Show(); + + m_popupWinState = Visible; + + // If popup window was a generic top-level window, or the + // wxPopupWindow implemenation on this platform is classified as + // perfect, then we should be able to safely set focus to the popup + // control. + if ( IsPopupWinTypePerfect(m_popupWinType) ) + m_popup->SetFocus(); + } + else if ( IsPopupWindowState(Hidden) ) + { + // Animation was aborted + + wxASSERT( !winPopup->IsShown() ); + + m_popupWinState = Hidden; + } + Refresh(); } -void wxComboControlBase::OnPopupDismiss() +void wxComboCtrlBase::OnPopupDismiss(bool generateEvent) { // Just in case, avoid double dismiss - if ( !m_isPopupShown ) + if ( IsPopupWindowState(Hidden) ) return; - // *Must* set this before focus etc. - m_isPopupShown = false; + // 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 ( m_popupEvtHandler ) + ((wxComboPopupEvtHandler*)m_popupEvtHandler)->OnPopupDismiss(); #if INSTALL_TOPLEV_HANDLER // Remove top level window event handler @@ -1715,11 +2355,14 @@ void wxComboControlBase::OnPopupDismiss() } #endif - m_timeCanAcceptClick = ::wxGetLocalTimeMillis() + 150; + 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.Inside(ScreenToClient(::wxGetMousePosition())) ) + if ( !m_btnArea.Contains(ScreenToClient(::wxGetMousePosition())) ) m_btnState = 0; // Return parent's tab traversal flag. @@ -1734,47 +2377,69 @@ void wxComboControlBase::OnPopupDismiss() // refresh control (necessary even if m_text) Refresh(); -#if !wxUSE_POPUPWIN SetFocus(); -#endif + if ( generateEvent ) + { + wxCommandEvent event(wxEVT_COMMAND_COMBOBOX_CLOSEUP, GetId()); + event.SetEventObject(this); + HandleWindowEvent(event); + } } -void wxComboControlBase::HidePopup() +void wxComboCtrlBase::HidePopup(bool generateEvent) { // Should be able to call this without popup interface - //wxCHECK_RET( m_popupInterface, _T("no popup interface") ); - if ( !m_isPopupShown ) + if ( IsPopupWindowState(Hidden) ) return; // transfer value and show it in textctrl, if any - SetValue( m_popupInterface->GetStringValue() ); + if ( !IsPopupWindowState(Animating) ) + SetValue( m_popupInterface->GetStringValue() ); -#if USE_TRANSIENT_POPUP - ((wxPopupTransientWindow*)m_winPopup)->Dismiss(); -#else m_winPopup->Hide(); -#endif - OnPopupDismiss(); + OnPopupDismiss(generateEvent); } // ---------------------------------------------------------------------------- // customization methods // ---------------------------------------------------------------------------- -void wxComboControlBase::SetButtonPosition( int width, int height, - int side, int spacingX ) +void wxComboCtrlBase::SetButtonPosition( int width, int height, + int side, int spacingX ) { m_btnWid = width; m_btnHei = height; m_btnSide = side; m_btnSpacingX = spacingX; + if ( width > 0 || height > 0 || spacingX ) + m_iFlags |= wxCC_IFLAG_HAS_NONSTANDARD_BUTTON; + RecalcAndRefresh(); } -void wxComboControlBase::SetButtonBitmaps( const wxBitmap& bmpNormal, +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, @@ -1801,7 +2466,7 @@ void wxComboControlBase::SetButtonBitmaps( const wxBitmap& bmpNormal, RecalcAndRefresh(); } -void wxComboControlBase::SetCustomPaintWidth( int width ) +void wxComboCtrlBase::SetCustomPaintWidth( int width ) { if ( m_text ) { @@ -1818,90 +2483,167 @@ void wxComboControlBase::SetCustomPaintWidth( int width ) RecalcAndRefresh(); } -void wxComboControlBase::SetTextIndent( int indent ) +bool wxComboCtrlBase::DoSetMargins(const wxPoint& margins) +{ + // For general sanity's sake, we ignore top margin. Instead + // we will always try to center the text vertically. + bool res = true; + + if ( margins.x != -1 ) + { + m_marginLeft = margins.x; + m_iFlags |= wxCC_IFLAG_LEFT_MARGIN_SET; + } + else + { + m_marginLeft = GetNativeTextIndent(); + m_iFlags &= ~(wxCC_IFLAG_LEFT_MARGIN_SET); + } + + if ( margins.y != -1 ) + { + res = false; + } + + RecalcAndRefresh(); + + return res; +} + +wxPoint wxComboCtrlBase::DoGetMargins() const +{ + return wxPoint(m_marginLeft, -1); +} + +#if WXWIN_COMPATIBILITY_2_8 +void wxComboCtrlBase::SetTextIndent( int indent ) { if ( indent < 0 ) { - m_absIndent = GetNativeTextIndent(); - m_iFlags &= ~(wxCC_IFLAG_INDENT_SET); + m_marginLeft = GetNativeTextIndent(); + m_iFlags &= ~(wxCC_IFLAG_LEFT_MARGIN_SET); } else { - m_absIndent = indent; - m_iFlags |= wxCC_IFLAG_INDENT_SET; + m_marginLeft = indent; + m_iFlags |= wxCC_IFLAG_LEFT_MARGIN_SET; } RecalcAndRefresh(); } -wxCoord wxComboControlBase::GetNativeTextIndent() const +wxCoord wxComboCtrlBase::GetTextIndent() const +{ + return m_marginLeft; +} +#endif + +wxCoord wxComboCtrlBase::GetNativeTextIndent() const { return DEFAULT_TEXT_INDENT; } +void wxComboCtrlBase::SetTextCtrlStyle( int style ) +{ + m_textCtrlStyle = style; + + if ( m_text ) + m_text->SetWindowStyle(style); +} + // ---------------------------------------------------------------------------- // methods forwarded to wxTextCtrl // ---------------------------------------------------------------------------- -wxString wxComboControlBase::GetValue() const +wxString wxComboCtrlBase::GetValue() const { if ( m_text ) return m_text->GetValue(); return m_valueString; } -void wxComboControlBase::SetValue(const wxString& value) +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(); } // 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 ) + if ( m_valueString != value ) { - m_popupInterface->SetStringValue(value); + m_valueString = value; + + EnsurePopupControl(); + + if (m_popupInterface) + m_popupInterface->SetStringValue(value); } + Refresh(); +} + +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 wxComboControlBase::Copy() +void wxComboCtrlBase::Copy() { if ( m_text ) m_text->Copy(); } -void wxComboControlBase::Cut() +void wxComboCtrlBase::Cut() { if ( m_text ) m_text->Cut(); } -void wxComboControlBase::Paste() +void wxComboCtrlBase::Paste() { if ( m_text ) m_text->Paste(); } -void wxComboControlBase::SetInsertionPoint(long pos) +void wxComboCtrlBase::SetInsertionPoint(long pos) { if ( m_text ) m_text->SetInsertionPoint(pos); } -void wxComboControlBase::SetInsertionPointEnd() +void wxComboCtrlBase::SetInsertionPointEnd() { if ( m_text ) m_text->SetInsertionPointEnd(); } -long wxComboControlBase::GetInsertionPoint() const +long wxComboCtrlBase::GetInsertionPoint() const { if ( m_text ) return m_text->GetInsertionPoint(); @@ -1909,7 +2651,7 @@ long wxComboControlBase::GetInsertionPoint() const return 0; } -long wxComboControlBase::GetLastPosition() const +long wxComboCtrlBase::GetLastPosition() const { if ( m_text ) return m_text->GetLastPosition(); @@ -1917,28 +2659,43 @@ long wxComboControlBase::GetLastPosition() const return 0; } -void wxComboControlBase::Replace(long from, long to, const wxString& value) +void wxComboCtrlBase::Replace(long from, long to, const wxString& value) { if ( m_text ) m_text->Replace(from, to, value); } -void wxComboControlBase::Remove(long from, long to) +void wxComboCtrlBase::Remove(long from, long to) { if ( m_text ) m_text->Remove(from, to); } -void wxComboControlBase::SetSelection(long from, long to) +void wxComboCtrlBase::SetSelection(long from, long to) { if ( m_text ) m_text->SetSelection(from, to); } -void wxComboControlBase::Undo() +void wxComboCtrlBase::Undo() { if ( m_text ) m_text->Undo(); } -#endif // wxUSE_COMBOCONTROL +bool wxComboCtrlBase::SetHint(const wxString& hint) +{ + m_hintText = hint; + bool res = true; + if ( m_text ) + res = m_text->SetHint(hint); + Refresh(); + return res; +} + +wxString wxComboCtrlBase::GetHint() const +{ + return m_hintText; +} + +#endif // wxUSE_COMBOCTRL