git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@39260 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
1984 lines
52 KiB
C++
1984 lines
52 KiB
C++
/////////////////////////////////////////////////////////////////////////////
|
|
// Name: src/common/combocmn.cpp
|
|
// Purpose: wxComboCtrlBase
|
|
// Author: Jaakko Salli
|
|
// Modified by:
|
|
// Created: Apr-30-2006
|
|
// RCS-ID: $Id$
|
|
// Copyright: (c) 2005 Jaakko Salli
|
|
// Licence: wxWindows licence
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
// ============================================================================
|
|
// declarations
|
|
// ============================================================================
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// headers
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#include "wx/wxprec.h"
|
|
|
|
#ifdef __BORLANDC__
|
|
#pragma hdrstop
|
|
#endif
|
|
|
|
#if wxUSE_COMBOCTRL
|
|
|
|
#ifndef WX_PRECOMP
|
|
#include "wx/log.h"
|
|
#include "wx/combobox.h"
|
|
#include "wx/dcclient.h"
|
|
#include "wx/settings.h"
|
|
#include "wx/dialog.h"
|
|
#endif
|
|
|
|
#include "wx/dcbuffer.h"
|
|
#include "wx/tooltip.h"
|
|
#include "wx/timer.h"
|
|
|
|
#include "wx/combo.h"
|
|
|
|
|
|
|
|
// 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_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)
|
|
|
|
//#undef wxUSE_POPUPWIN
|
|
//#define wxUSE_POPUPWIN 0
|
|
|
|
#elif defined(__WXGTK__)
|
|
|
|
#define USE_TRANSIENT_POPUP 1 // Use wxPopupWindowTransient (preferred, if it works properly on platform)
|
|
|
|
#elif defined(__WXMAC__)
|
|
|
|
#define USE_TRANSIENT_POPUP 0 // Use wxPopupWindowTransient (preferred, if it works properly on platform)
|
|
|
|
#else
|
|
|
|
#define USE_TRANSIENT_POPUP 0 // Use wxPopupWindowTransient (preferred, if it works properly on platform)
|
|
|
|
#endif
|
|
|
|
|
|
// Popupwin is really only supported on wxMSW (not WINCE) and wxGTK, regardless
|
|
// what the wxUSE_POPUPWIN says.
|
|
// 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
|
|
|
|
|
|
#if USE_TRANSIENT_POPUP
|
|
|
|
#define wxComboPopupWindowBase wxPopupTransientWindow
|
|
#define INSTALL_TOPLEV_HANDLER 0
|
|
|
|
#elif wxUSE_POPUPWIN
|
|
|
|
#define wxComboPopupWindowBase wxPopupWindow
|
|
#define INSTALL_TOPLEV_HANDLER 1
|
|
|
|
#else
|
|
|
|
#define wxComboPopupWindowBase wxDialog
|
|
#define INSTALL_TOPLEV_HANDLER 0 // Doesn't need since can monitor active event
|
|
|
|
#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 );
|
|
~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();
|
|
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 wxPopupWindow customized for
|
|
// wxComboCtrl.
|
|
// ----------------------------------------------------------------------------
|
|
|
|
class wxComboPopupWindow : public wxComboPopupWindowBase
|
|
{
|
|
public:
|
|
|
|
wxComboPopupWindow( wxComboCtrlBase *parent, int style = wxBORDER_NONE );
|
|
|
|
#if USE_TRANSIENT_POPUP
|
|
virtual bool ProcessLeftDown(wxMouseEvent& event);
|
|
#endif
|
|
|
|
void OnKeyEvent(wxKeyEvent& event);
|
|
|
|
void OnMouseEvent( wxMouseEvent& event );
|
|
#if !wxUSE_POPUPWIN
|
|
void OnActivate( wxActivateEvent& event );
|
|
#endif
|
|
|
|
protected:
|
|
|
|
#if USE_TRANSIENT_POPUP
|
|
virtual void OnDismiss();
|
|
#endif
|
|
|
|
private:
|
|
DECLARE_EVENT_TABLE()
|
|
};
|
|
|
|
|
|
BEGIN_EVENT_TABLE(wxComboPopupWindow, wxComboPopupWindowBase)
|
|
EVT_MOUSE_EVENTS(wxComboPopupWindow::OnMouseEvent)
|
|
#if !wxUSE_POPUPWIN
|
|
EVT_ACTIVATE(wxComboPopupWindow::OnActivate)
|
|
#endif
|
|
EVT_KEY_DOWN(wxComboPopupWindow::OnKeyEvent)
|
|
EVT_KEY_UP(wxComboPopupWindow::OnKeyEvent)
|
|
END_EVENT_TABLE()
|
|
|
|
|
|
wxComboPopupWindow::wxComboPopupWindow( wxComboCtrlBase *parent,
|
|
int style )
|
|
#if wxUSE_POPUPWIN
|
|
: wxComboPopupWindowBase(parent,style)
|
|
#else
|
|
: wxComboPopupWindowBase(parent,
|
|
wxID_ANY,
|
|
wxEmptyString,
|
|
wxPoint(-21,-21),
|
|
wxSize(20,20),
|
|
style)
|
|
#endif
|
|
{
|
|
}
|
|
|
|
void wxComboPopupWindow::OnKeyEvent( wxKeyEvent& event )
|
|
{
|
|
// Relay keyboard event to the main child controls
|
|
// (just skipping may just cause the popup to close)
|
|
wxWindowList children = GetChildren();
|
|
wxWindowList::iterator node = children.begin();
|
|
wxWindow* child = (wxWindow*)*node;
|
|
child->AddPendingEvent(event);
|
|
}
|
|
|
|
void wxComboPopupWindow::OnMouseEvent( wxMouseEvent& event )
|
|
{
|
|
event.Skip();
|
|
}
|
|
|
|
#if !wxUSE_POPUPWIN
|
|
void wxComboPopupWindow::OnActivate( wxActivateEvent& event )
|
|
{
|
|
if ( !event.GetActive() )
|
|
{
|
|
// Tell combo control that we are dismissed.
|
|
wxComboCtrl* combo = (wxComboCtrl*) GetParent();
|
|
wxASSERT( combo );
|
|
wxASSERT( combo->IsKindOf(CLASSINFO(wxComboCtrl)) );
|
|
|
|
combo->HidePopup();
|
|
|
|
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()
|
|
{
|
|
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
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// 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->DrawFocusBackground(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 either combo box
|
|
// or its textctrl (latter if not readonly combo).
|
|
//
|
|
class wxComboBoxExtraInputHandler : public wxEvtHandler
|
|
{
|
|
public:
|
|
|
|
wxComboBoxExtraInputHandler( wxComboCtrlBase* combo )
|
|
: wxEvtHandler()
|
|
{
|
|
m_combo = combo;
|
|
}
|
|
~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)
|
|
{
|
|
int keycode = event.GetKeyCode();
|
|
|
|
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;
|
|
}
|
|
|
|
if ( m_combo->IsPopupShown() )
|
|
{
|
|
// pass it to the popped up control
|
|
m_combo->GetPopupControl()->GetControl()->AddPendingEvent(event);
|
|
}
|
|
else // no popup
|
|
{
|
|
int comboStyle = m_combo->GetWindowStyle();
|
|
wxComboPopup* popupInterface = m_combo->GetPopupControl();
|
|
|
|
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
|
|
event.Skip();
|
|
}
|
|
else
|
|
popupInterface->OnComboKeyEvent(event);
|
|
}
|
|
else
|
|
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 ( m_combo->GetTextCtrl() )
|
|
m_combo->GetTextCtrl()->SelectAll();
|
|
else
|
|
m_combo->SetSelection(-1,-1);
|
|
}
|
|
|
|
if ( event.GetId() != m_combo->GetId() )
|
|
{
|
|
// Add textctrl set focus events as combo set focus events
|
|
// NOTE: Simply changing the event and skipping didn't seem
|
|
// to do the trick.
|
|
wxFocusEvent evt2(wxEVT_SET_FOCUS,m_combo->GetId());
|
|
evt2.SetEventObject(m_combo);
|
|
m_combo->GetEventHandler()->ProcessEvent(evt2);
|
|
}
|
|
else
|
|
event.Skip();
|
|
|
|
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;
|
|
}
|
|
~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 )
|
|
{
|
|
event.Skip(false);
|
|
return;
|
|
}
|
|
}
|
|
else if ( evtType == wxEVT_LEFT_UP )
|
|
{
|
|
// Don't let left-down events in if outside
|
|
if ( evtType == wxEVT_LEFT_DOWN )
|
|
{
|
|
if ( !isInside )
|
|
return;
|
|
}
|
|
|
|
if ( !m_beenInside )
|
|
{
|
|
if ( isInside )
|
|
{
|
|
m_beenInside = true;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Some mouse events to popup that happen outside it, before cursor
|
|
// has been inside the popu, need to be ignored by it but relayed to
|
|
// the dropbutton.
|
|
//
|
|
wxWindow* btn = m_combo->GetButton();
|
|
if ( btn )
|
|
btn->GetEventHandler()->AddPendingEvent(event);
|
|
else
|
|
m_combo->GetEventHandler()->AddPendingEvent(event);
|
|
|
|
return;
|
|
}
|
|
|
|
event.Skip();
|
|
}
|
|
}
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// 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_BUTTON(wxID_ANY,wxComboCtrlBase::OnButtonClickEvent)
|
|
EVT_TEXT_ENTER(wxID_ANY,wxComboCtrlBase::OnTextCtrlEvent)
|
|
EVT_SYS_COLOUR_CHANGED(wxComboCtrlBase::OnSysColourChanged)
|
|
END_EVENT_TABLE()
|
|
|
|
|
|
IMPLEMENT_ABSTRACT_CLASS(wxComboCtrlBase, wxControl)
|
|
|
|
// Have global double buffer - should be enough for multiple combos
|
|
static wxBitmap* gs_doubleBuffer = (wxBitmap*) NULL;
|
|
|
|
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_extraEvtHandler = (wxEvtHandler*) NULL;
|
|
m_popupExtraHandler = (wxEvtHandler*) NULL;
|
|
m_textEvtHandler = (wxEvtHandler*) NULL;
|
|
|
|
#if INSTALL_TOPLEV_HANDLER
|
|
m_toplevEvtHandler = (wxEvtHandler*) NULL;
|
|
#endif
|
|
|
|
m_heightPopup = -1;
|
|
m_widthMinPopup = -1;
|
|
m_anchorSide = 0;
|
|
m_widthCustomPaint = 0;
|
|
m_widthCustomBorder = 0;
|
|
|
|
m_btnState = 0;
|
|
m_btnWidDefault = 0;
|
|
m_blankButtonBg = false;
|
|
m_btnWid = m_btnHei = 0;
|
|
m_btnSide = wxRIGHT;
|
|
m_btnSpacingX = 0;
|
|
|
|
m_extLeft = 0;
|
|
m_extRight = 0;
|
|
m_absIndent = -1;
|
|
m_iFlags = 0;
|
|
m_downReceived = false;
|
|
m_timeCanAcceptClick = 0;
|
|
}
|
|
|
|
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();
|
|
|
|
return true;
|
|
}
|
|
|
|
void wxComboCtrlBase::InstallInputHandlers( bool alsoTextCtrl )
|
|
{
|
|
if ( m_text && alsoTextCtrl )
|
|
{
|
|
m_textEvtHandler = new wxComboBoxExtraInputHandler(this);
|
|
m_text->PushEventHandler(m_textEvtHandler);
|
|
}
|
|
|
|
wxComboBoxExtraInputHandler* inputHandler = new wxComboBoxExtraInputHandler(this);
|
|
PushEventHandler(inputHandler);
|
|
m_extraEvtHandler = inputHandler;
|
|
}
|
|
|
|
void wxComboCtrlBase::CreateTextCtrl( int extraStyle, const wxValidator& validator )
|
|
{
|
|
if ( !(m_windowStyle & wxCB_READONLY) )
|
|
{
|
|
m_text = new wxTextCtrl(this,
|
|
12345,
|
|
m_valueString,
|
|
wxDefaultPosition,
|
|
wxDefaultSize,
|
|
// wxTE_PROCESS_TAB is needed because on Windows, wxTAB_TRAVERSAL is
|
|
// not used by the wxPropertyGrid and therefore the tab is
|
|
// processed by looking at ancestors to see if they have
|
|
// wxTAB_TRAVERSAL. The navigation event is then sent to
|
|
// the wrong window.
|
|
wxTE_PROCESS_TAB |
|
|
wxTE_PROCESS_ENTER |
|
|
extraStyle,
|
|
validator);
|
|
|
|
// This is required for some platforms (GTK+ atleast)
|
|
m_text->SetSizeHints(2,4);
|
|
}
|
|
}
|
|
|
|
void wxComboCtrlBase::OnThemeChange()
|
|
{
|
|
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
|
}
|
|
|
|
wxComboCtrlBase::~wxComboCtrlBase()
|
|
{
|
|
if ( HasCapture() )
|
|
ReleaseMouse();
|
|
|
|
delete gs_doubleBuffer;
|
|
gs_doubleBuffer = (wxBitmap*) NULL;
|
|
|
|
#if INSTALL_TOPLEV_HANDLER
|
|
delete ((wxComboFrameEventHandler*)m_toplevEvtHandler);
|
|
m_toplevEvtHandler = (wxEvtHandler*) NULL;
|
|
#endif
|
|
|
|
if ( m_popup )
|
|
m_popup->RemoveEventHandler(m_popupExtraHandler);
|
|
|
|
delete m_popupExtraHandler;
|
|
|
|
HidePopup();
|
|
|
|
delete m_popupInterface;
|
|
delete m_winPopup;
|
|
|
|
RemoveEventHandler(m_extraEvtHandler);
|
|
|
|
if ( m_text )
|
|
m_text->RemoveEventHandler(m_textEvtHandler);
|
|
|
|
delete m_textEvtHandler;
|
|
delete m_extraEvtHandler;
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// geometry stuff
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Recalculates button and textctrl areas
|
|
void wxComboCtrlBase::CalculateAreas( int btnWidth )
|
|
{
|
|
wxSize sz = GetClientSize();
|
|
int customBorder = m_widthCustomBorder;
|
|
bool buttonOutside;
|
|
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 )
|
|
{
|
|
buttonOutside = true;
|
|
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();
|
|
|
|
int butWidth = btnWidth;
|
|
|
|
if ( butWidth <= 0 )
|
|
butWidth = m_btnWidDefault;
|
|
else
|
|
m_btnWidDefault = butWidth;
|
|
|
|
if ( butWidth <= 0 )
|
|
return;
|
|
|
|
// Adjust button width
|
|
if ( m_btnWid < 0 )
|
|
butWidth += m_btnWid;
|
|
else if ( m_btnWid > 0 )
|
|
butWidth = m_btnWid;
|
|
|
|
int butHeight = sz.y;
|
|
|
|
butHeight -= btnBorder*2;
|
|
|
|
// Adjust button height
|
|
if ( m_btnHei < 0 )
|
|
butHeight += m_btnHei;
|
|
else if ( m_btnHei > 0 )
|
|
butHeight = m_btnHei;
|
|
|
|
// Use size of normal bitmap if...
|
|
// It is larger
|
|
// OR
|
|
// button width is set to default and blank button bg is not drawn
|
|
if ( m_bmpNormal.Ok() )
|
|
{
|
|
int bmpReqWidth = m_bmpNormal.GetWidth();
|
|
int bmpReqHeight = m_bmpNormal.GetHeight();
|
|
|
|
// If drawing blank button background, we need to add some margin.
|
|
if ( m_blankButtonBg )
|
|
{
|
|
bmpReqWidth += BMP_BUTTON_MARGIN*2;
|
|
bmpReqHeight += BMP_BUTTON_MARGIN*2;
|
|
}
|
|
|
|
if ( butWidth < bmpReqWidth || ( m_btnWid == 0 && !m_blankButtonBg ) )
|
|
butWidth = bmpReqWidth;
|
|
if ( butHeight < bmpReqHeight || ( m_btnHei == 0 && !m_blankButtonBg ) )
|
|
butHeight = bmpReqHeight;
|
|
|
|
// Need to fix height?
|
|
if ( (sz.y-(customBorder*2)) < butHeight && btnWidth == 0 )
|
|
{
|
|
int newY = butHeight+(customBorder*2);
|
|
SetClientSize(-1,newY);
|
|
sz.y = newY;
|
|
}
|
|
}
|
|
|
|
int butAreaWid = butWidth + (m_btnSpacingX*2);
|
|
|
|
m_btnSize.x = butWidth;
|
|
m_btnSize.y = butHeight;
|
|
|
|
m_btnArea.x = ( m_btnSide==wxRIGHT ? sz.x - butAreaWid - btnBorder : btnBorder );
|
|
m_btnArea.y = btnBorder;
|
|
m_btnArea.width = butAreaWid;
|
|
m_btnArea.height = sz.y - (btnBorder*2);
|
|
|
|
m_tcArea.x = ( m_btnSide==wxRIGHT ? 0 : butAreaWid ) + customBorder;
|
|
m_tcArea.y = customBorder;
|
|
m_tcArea.width = sz.x - butAreaWid - (customBorder*2);
|
|
m_tcArea.height = sz.y - (customBorder*2);
|
|
|
|
/*
|
|
if ( m_text )
|
|
{
|
|
::wxMessageBox(wxString::Format(wxT("ButtonArea (%i,%i,%i,%i)\n"),m_btnArea.x,m_btnArea.y,m_btnArea.width,m_btnArea.height) +
|
|
wxString::Format(wxT("TextCtrlArea (%i,%i,%i,%i)"),m_tcArea.x,m_tcArea.y,m_tcArea.width,m_tcArea.height));
|
|
}
|
|
*/
|
|
}
|
|
|
|
void wxComboCtrlBase::PositionTextCtrl( int textCtrlXAdjust, int textCtrlYAdjust )
|
|
{
|
|
if ( !m_text )
|
|
return;
|
|
|
|
wxSize sz = GetClientSize();
|
|
int customBorder = m_widthCustomBorder;
|
|
|
|
if ( (m_text->GetWindowStyleFlag() & wxBORDER_MASK) == wxNO_BORDER )
|
|
{
|
|
// Centre textctrl
|
|
int tcSizeY = m_text->GetBestSize().y;
|
|
int diff = sz.y - tcSizeY;
|
|
int y = textCtrlYAdjust + (diff/2);
|
|
|
|
if ( y < customBorder )
|
|
y = customBorder;
|
|
|
|
m_text->SetSize( m_tcArea.x + m_widthCustomPaint + m_absIndent + textCtrlXAdjust,
|
|
y,
|
|
m_tcArea.width - 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
|
|
{
|
|
m_text->SetSize( m_tcArea.x,
|
|
0,
|
|
sz.x - m_btnArea.x - m_widthCustomPaint - customBorder,
|
|
sz.y );
|
|
}
|
|
}
|
|
|
|
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
|
|
|
|
wxSize ret(sizeText.x + COMBO_MARGIN + DEFAULT_DROPBUTTON_WIDTH,
|
|
fhei);
|
|
|
|
CacheBestSize(ret);
|
|
return ret;
|
|
}
|
|
|
|
void wxComboCtrlBase::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 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
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// painting
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// draw focus background on area in a way typical on platform
|
|
void wxComboCtrlBase::DrawFocusBackground( wxDC& dc, const wxRect& rect, int flags )
|
|
{
|
|
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);
|
|
selRect.x += m_widthCustomPaint + focusSpacingX;
|
|
selRect.width -= m_widthCustomPaint + (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) );
|
|
bgCol = GetBackgroundColour();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dc.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT) );
|
|
bgCol = GetBackgroundColour();
|
|
}
|
|
|
|
dc.SetBrush( bgCol );
|
|
dc.SetPen( bgCol );
|
|
dc.DrawRectangle( selRect );
|
|
}
|
|
|
|
void wxComboCtrlBase::DrawButton( wxDC& dc, const wxRect& rect, bool paintBg )
|
|
{
|
|
int drawState = m_btnState;
|
|
|
|
#ifdef __WXGTK__
|
|
if ( m_isPopupShown )
|
|
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();
|
|
}
|
|
}
|
|
|
|
wxBitmap& wxComboCtrlBase::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;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// miscellaneous event handlers
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void wxComboCtrlBase::OnTextCtrlEvent(wxCommandEvent& event)
|
|
{
|
|
// Change event id and relay it forward
|
|
event.SetId(GetId());
|
|
event.Skip();
|
|
}
|
|
|
|
// call if cursor is on button area or mouse is captured for the button
|
|
bool 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 )
|
|
{
|
|
// Only accept event if it wasn't right after popup dismiss
|
|
//if ( ::wxGetLocalTimeMillis() > m_timeCanClick )
|
|
{
|
|
// Need to test this, because it might be outside.
|
|
if ( flags & 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
|
|
{
|
|
m_btnState = 0;
|
|
}*/
|
|
}
|
|
else if ( type == wxEVT_LEFT_UP )
|
|
{
|
|
|
|
// Only accept event if mouse was left-press was previously accepted
|
|
if ( HasCapture() )
|
|
ReleaseMouse();
|
|
|
|
if ( m_btnState & wxCONTROL_PRESSED )
|
|
{
|
|
// If mouse was inside, fire the click event.
|
|
if ( m_iFlags & wxCC_POPUP_ON_MOUSE_UP )
|
|
{
|
|
if ( flags & 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 ( !m_isPopupShown )
|
|
{
|
|
m_btnState &= ~(wxCONTROL_PRESSED);
|
|
Refresh();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// Conversion to double-clicks and some basic filtering
|
|
// returns true if event was consumed or filtered
|
|
//bool wxComboCtrlBase::PreprocessMouseEvent( wxMouseEvent& event, bool isOnButtonArea )
|
|
bool wxComboCtrlBase::PreprocessMouseEvent( wxMouseEvent& event,
|
|
int 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 )
|
|
{
|
|
// We'll make our own double-clicks
|
|
//evtType = 0;
|
|
event.SetEventType(0);
|
|
return true;
|
|
}
|
|
else if ( evtType == wxEVT_LEFT_UP )
|
|
{
|
|
if ( m_downReceived || m_timeLastMouseUp == 1 )
|
|
{
|
|
wxLongLong timeFromLastUp = (t-m_timeLastMouseUp);
|
|
|
|
if ( timeFromLastUp < DOUBLE_CLICK_CONVERSION_TRESHOLD )
|
|
{
|
|
//type = wxEVT_LEFT_DCLICK;
|
|
event.SetEventType(wxEVT_LEFT_DCLICK);
|
|
m_timeLastMouseUp = 1;
|
|
}
|
|
else
|
|
{
|
|
m_timeLastMouseUp = t;
|
|
}
|
|
|
|
//m_downReceived = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Filter out clicks on button immediately after popup dismiss (Windows like behaviour)
|
|
if ( evtType == wxEVT_LEFT_DOWN && t < m_timeCanAcceptClick )
|
|
{
|
|
event.SetEventType(0);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void 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 !wxUSE_POPUPWIN
|
|
// Normally do nothing - evt handler should close it for us
|
|
#elif !USE_TRANSIENT_POPUP
|
|
// Click here always hides the popup.
|
|
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 ( m_isPopupShown )
|
|
{
|
|
// relay (some) mouse events to the popup
|
|
if ( evtType == wxEVT_MOUSEWHEEL )
|
|
m_popup->AddPendingEvent(event);
|
|
}
|
|
else if ( evtType )
|
|
event.Skip();
|
|
}
|
|
|
|
void wxComboCtrlBase::OnFocusEvent( wxFocusEvent& )
|
|
{
|
|
// 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 ( m_text )
|
|
{
|
|
m_text->SetFocus();
|
|
}
|
|
else
|
|
// no need to check for m_widthCustomPaint - that
|
|
// area never gets special handling when selected
|
|
// (in writable mode, that is)
|
|
Refresh();
|
|
}
|
|
|
|
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 )
|
|
m_winPopup = new wxComboPopupWindow( this, wxNO_BORDER );
|
|
|
|
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;
|
|
}
|
|
|
|
void wxComboCtrlBase::SetPopupControl( wxComboPopup* iface )
|
|
{
|
|
wxCHECK_RET( iface, wxT("no popup interface set for wxComboCtrl") );
|
|
|
|
delete m_popupInterface;
|
|
delete m_winPopup;
|
|
|
|
iface->InitBase(this);
|
|
iface->Init();
|
|
|
|
m_popupInterface = iface;
|
|
|
|
if ( !iface->LazyCreate() || m_winPopup )
|
|
{
|
|
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( !IsPopupShown(), wxT("popup window already shown") );
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
|
|
int anchorSide = m_anchorSide;
|
|
if ( !anchorSide )
|
|
anchorSide = m_btnSide;
|
|
|
|
// Anchor popup to the side the dropbutton is on
|
|
if ( anchorSide == wxRIGHT )
|
|
popupX = scrPos.x + ctrlSz.x + m_extRight- szp.x;
|
|
else
|
|
popupX = scrPos.x - m_extLeft;
|
|
|
|
if ( spaceBelow < szp.y )
|
|
{
|
|
popupY = scrPos.y - szp.y;
|
|
}
|
|
|
|
// Move to position
|
|
//wxLogDebug(wxT("popup scheduled position1: %i,%i"),ptp.x,ptp.y);
|
|
//wxLogDebug(wxT("popup position1: %i,%i"),winPopup->GetPosition().x,winPopup->GetPosition().y);
|
|
|
|
// Some platforms (GTK) may need these two to be separate
|
|
winPopup->SetSize( szp.x, szp.y );
|
|
winPopup->Move( popupX, popupY );
|
|
|
|
//wxLogDebug(wxT("popup position2: %i,%i"),winPopup->GetPosition().x,winPopup->GetPosition().y);
|
|
|
|
m_popup = popup;
|
|
|
|
// Set string selection (must be this way instead of SetStringSelection)
|
|
if ( m_text )
|
|
{
|
|
if ( !(m_iFlags & 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_isPopupShown = true;
|
|
|
|
// Show it
|
|
#if USE_TRANSIENT_POPUP
|
|
((wxPopupTransientWindow*)winPopup)->Popup(popup);
|
|
#else
|
|
winPopup->Show();
|
|
#endif
|
|
|
|
#if INSTALL_TOPLEV_HANDLER
|
|
// Put top level window event handler into place
|
|
if ( !m_toplevEvtHandler )
|
|
m_toplevEvtHandler = new wxComboFrameEventHandler(this);
|
|
|
|
wxWindow* toplev = ::wxGetTopLevelParent( this );
|
|
wxASSERT( toplev );
|
|
((wxComboFrameEventHandler*)m_toplevEvtHandler)->OnPopup();
|
|
toplev->PushEventHandler( m_toplevEvtHandler );
|
|
#endif
|
|
|
|
}
|
|
|
|
void wxComboCtrlBase::OnPopupDismiss()
|
|
{
|
|
// Just in case, avoid double dismiss
|
|
if ( !m_isPopupShown )
|
|
return;
|
|
|
|
// *Must* set this before focus etc.
|
|
m_isPopupShown = false;
|
|
|
|
// Inform popup control itself
|
|
m_popupInterface->OnDismiss();
|
|
|
|
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() + 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())) )
|
|
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();
|
|
|
|
#if !wxUSE_POPUPWIN
|
|
SetFocus();
|
|
#endif
|
|
|
|
}
|
|
|
|
void wxComboCtrlBase::HidePopup()
|
|
{
|
|
// Should be able to call this without popup interface
|
|
//wxCHECK_RET( m_popupInterface, _T("no popup interface") );
|
|
if ( !m_isPopupShown )
|
|
return;
|
|
|
|
// transfer value and show it in textctrl, if any
|
|
SetValue( m_popupInterface->GetStringValue() );
|
|
|
|
#if USE_TRANSIENT_POPUP
|
|
((wxPopupTransientWindow*)m_winPopup)->Dismiss();
|
|
#else
|
|
m_winPopup->Hide();
|
|
#endif
|
|
|
|
OnPopupDismiss();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// customization methods
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void wxComboCtrlBase::SetButtonPosition( int width, int height,
|
|
int side, int spacingX )
|
|
{
|
|
m_btnWid = width;
|
|
m_btnHei = height;
|
|
m_btnSide = side;
|
|
m_btnSpacingX = spacingX;
|
|
|
|
RecalcAndRefresh();
|
|
}
|
|
|
|
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::SetValue(const wxString& value)
|
|
{
|
|
if ( m_text )
|
|
{
|
|
m_text->SetValue(value);
|
|
if ( !(m_iFlags & wxCC_NO_TEXT_AUTO_SELECT) )
|
|
m_text->SelectAll();
|
|
}
|
|
|
|
m_valueString = value;
|
|
|
|
Refresh();
|
|
|
|
// Since wxComboPopup may want to paint the combo as well, we need
|
|
// to set the string value here (as well as sometimes in ShowPopup).
|
|
if ( m_valueString != value && m_popupInterface )
|
|
{
|
|
m_popupInterface->SetStringValue(value);
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
|
|
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
|