/////////////////////////////////////////////////////////////////////////////
// Name:        src/common/combocmn.cpp
// Purpose:     wxComboCtrlBase
// Author:      Jaakko Salli
// Modified by:
// Created:     Apr-30-2006
// RCS-ID:      $Id: combocmn.cpp,v 1.56.2.4 2007/04/16 20:51:27 RD Exp $
// 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
        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);
            }

            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();
    }

    // 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_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 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
