Files
wxWidgets/src/univ/combobox.cpp
Julian Smart 48aa18c000 Applied patch [ 650078 ] Add better checking to wxComboBox
This patch adds extra checks, checks to see and report
if the provided item index is valid.

An other thing:
- IMHO wxComboBox::GetSelection() was wrong, I
corrected this (now it behaves the same as wxMSW,
don't know about wxGTK and others...).

Hans Van Leemputten (hansvl)


git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@18142 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
2002-12-09 10:26:21 +00:00

899 lines
24 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// Name: univ/combobox.cpp
// Purpose: wxComboControl and wxComboBox implementation
// Author: Vadim Zeitlin
// Modified by:
// Created: 15.12.00
// RCS-ID: $Id$
// Copyright: (c) 2000 SciTech Software, Inc. (www.scitechsoft.com)
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
// ============================================================================
// declarations
// ============================================================================
// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------
#ifdef __GNUG__
#pragma implementation "univcombobox.h"
#endif
#include "wx/wxprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#if wxUSE_COMBOBOX
#ifndef WX_PRECOMP
#include "wx/log.h"
#include "wx/button.h"
#include "wx/combobox.h"
#include "wx/listbox.h"
#include "wx/textctrl.h"
#include "wx/bmpbuttn.h"
#include "wx/validate.h"
#endif
#include "wx/popupwin.h"
#include "wx/univ/renderer.h"
#include "wx/univ/inphand.h"
#include "wx/univ/theme.h"
/*
The keyboard event flow:
1. they always come to the text ctrl
2. it forwards the ones it doesn't process to the wxComboControl
3. which passes them to the popup window if it is popped up
*/
// constants
// ----------------------------------------------------------------------------
// the margin between the text control and the combo button
static const wxCoord g_comboMargin = 2;
// ----------------------------------------------------------------------------
// wxComboButton is just a normal button except that it sends commands to the
// combobox and not its parent
// ----------------------------------------------------------------------------
class wxComboButton : public wxBitmapButton
{
public:
wxComboButton(wxComboControl *combo)
: wxBitmapButton(combo->GetParent(), -1, wxNullBitmap,
wxDefaultPosition, wxDefaultSize,
wxBORDER_NONE | wxBU_EXACTFIT)
{
m_combo = combo;
wxBitmap bmpNormal, bmpFocus, bmpPressed, bmpDisabled;
GetRenderer()->GetComboBitmaps(&bmpNormal,
&bmpFocus,
&bmpPressed,
&bmpDisabled);
SetBitmapLabel(bmpNormal);
SetBitmapFocus(bmpFocus.Ok() ? bmpFocus : bmpNormal);
SetBitmapSelected(bmpPressed.Ok() ? bmpPressed : bmpNormal);
SetBitmapDisabled(bmpDisabled.Ok() ? bmpDisabled : bmpNormal);
SetBestSize(wxDefaultSize);
}
protected:
void OnButton(wxCommandEvent& event) { m_combo->ShowPopup(); }
virtual wxSize DoGetBestClientSize() const
{
const wxBitmap& bmp = GetBitmapLabel();
return wxSize(bmp.GetWidth(), bmp.GetHeight());
}
private:
wxComboControl *m_combo;
DECLARE_EVENT_TABLE()
};
// ----------------------------------------------------------------------------
// wxComboListBox is a listbox modified to be used as a popup window in a
// combobox
// ----------------------------------------------------------------------------
class wxComboListBox : public wxListBox, public wxComboPopup
{
public:
// ctor and dtor
wxComboListBox(wxComboControl *combo, int style = 0);
virtual ~wxComboListBox();
// implement wxComboPopup methods
virtual bool SetSelection(const wxString& value);
virtual wxControl *GetControl() { return this; }
virtual void OnShow();
protected:
// we shouldn't return height too big from here
virtual wxSize DoGetBestClientSize() const;
// filter mouse move events happening outside the list box
void OnMouseMove(wxMouseEvent& event);
// set m_clicked value from here
void OnLeftUp(wxMouseEvent& event);
// called whenever the user selects or activates a listbox item
void OnSelect(wxCommandEvent& event);
// used to process wxUniv actions
bool PerformAction(const wxControlAction& action,
long numArg,
const wxString& strArg);
private:
// has the mouse been released on this control?
bool m_clicked;
DECLARE_EVENT_TABLE()
};
// ----------------------------------------------------------------------------
// wxComboTextCtrl is a simple text ctrl which forwards
// wxEVT_COMMAND_TEXT_UPDATED events and all key events to the combobox
// ----------------------------------------------------------------------------
class wxComboTextCtrl : public wxTextCtrl
{
public:
wxComboTextCtrl(wxComboControl *combo,
const wxString& value,
long style,
const wxValidator& validator);
protected:
void OnKey(wxKeyEvent& event);
void OnText(wxCommandEvent& event);
private:
wxComboControl *m_combo;
DECLARE_EVENT_TABLE()
};
// ----------------------------------------------------------------------------
// event tables and such
// ----------------------------------------------------------------------------
BEGIN_EVENT_TABLE(wxComboButton, wxButton)
EVT_BUTTON(-1, wxComboButton::OnButton)
END_EVENT_TABLE()
BEGIN_EVENT_TABLE(wxComboListBox, wxListBox)
EVT_LISTBOX(-1, wxComboListBox::OnSelect)
EVT_LISTBOX_DCLICK(-1, wxComboListBox::OnSelect)
EVT_MOTION(wxComboListBox::OnMouseMove)
EVT_LEFT_UP(wxComboListBox::OnLeftUp)
END_EVENT_TABLE()
BEGIN_EVENT_TABLE(wxComboControl, wxControl)
EVT_KEY_DOWN(wxComboControl::OnKey)
EVT_KEY_UP(wxComboControl::OnKey)
END_EVENT_TABLE()
BEGIN_EVENT_TABLE(wxComboTextCtrl, wxTextCtrl)
EVT_KEY_DOWN(wxComboTextCtrl::OnKey)
EVT_KEY_UP(wxComboTextCtrl::OnKey)
EVT_TEXT(-1, wxComboTextCtrl::OnText)
END_EVENT_TABLE()
IMPLEMENT_DYNAMIC_CLASS(wxComboBox, wxControl)
// ============================================================================
// implementation
// ============================================================================
// ----------------------------------------------------------------------------
// wxComboControl creation
// ----------------------------------------------------------------------------
void wxComboControl::Init()
{
m_popup = (wxComboPopup *)NULL;
m_winPopup = (wxPopupComboWindow *)NULL;
m_isPopupShown = FALSE;
m_btn = NULL;
m_text = NULL;
}
bool wxComboControl::Create(wxWindow *parent,
wxWindowID id,
const wxString& value,
const wxPoint& pos,
const wxSize& size,
long style,
const wxValidator& validator,
const wxString& name)
{
// first create our own window, i.e. the one which will contain all
// subcontrols
style &= ~wxBORDER_NONE;
style |= wxBORDER_SUNKEN;
if ( !wxControl::Create(parent, id, pos, size, style, validator, name) )
return FALSE;
// create the text control and the button as our siblings (*not* children),
// don't care about size/position here - they will be set in DoMoveWindow()
m_btn = new wxComboButton(this);
m_text = new wxComboTextCtrl(this,
value,
style & wxCB_READONLY ? wxTE_READONLY : 0,
validator);
// for compatibility with the other ports, the height specified is the
// combined height of the combobox itself and the popup
if ( size.y == -1 )
{
// ok, use default height for popup too
m_heightPopup = -1;
}
else
{
m_heightPopup = size.y - DoGetBestSize().y;
}
SetBestSize(size);
Move(pos);
// create the popup window immediately here to allow creating the controls
// with parent == GetPopupWindow() from the derived class ctor
m_winPopup = new wxPopupComboWindow(this);
// have to disable this window to avoid interfering it with message
// processing to the text and the button... but pretend it is enabled to
// make IsEnabled() return TRUE
wxControl::Enable(FALSE); // don't use non virtual Disable() here!
m_isEnabled = TRUE;
CreateInputHandler(wxINP_HANDLER_COMBOBOX);
return TRUE;
}
wxComboControl::~wxComboControl()
{
// as the button and the text control are the parent's children and not
// ours, we have to delete them manually - they are not deleted
// automatically by wxWindows when we're deleted
delete m_btn;
delete m_text;
delete m_winPopup;
}
// ----------------------------------------------------------------------------
// geometry stuff
// ----------------------------------------------------------------------------
void wxComboControl::DoSetSize(int x, int y,
int width, int height,
int sizeFlags)
{
// combo height is always fixed
wxControl::DoSetSize(x, y, width, DoGetBestSize().y, sizeFlags);
}
wxSize wxComboControl::DoGetBestClientSize() const
{
wxSize sizeBtn = m_btn->GetBestSize(),
sizeText = m_text->GetBestSize();
return wxSize(sizeText.x + g_comboMargin + sizeBtn.x, wxMax(sizeBtn.y, sizeText.y));
}
void wxComboControl::DoMoveWindow(int x, int y, int width, int height)
{
wxControl::DoMoveWindow(x, y, width, height);
// position the subcontrols inside the client area
wxRect rectBorders = GetRenderer()->GetBorderDimensions(GetBorder());
x += rectBorders.x;
y += rectBorders.y;
width -= rectBorders.x + rectBorders.width;
height -= rectBorders.y + rectBorders.height;
wxSize sizeBtn = m_btn->GetBestSize();
wxCoord wText = width - sizeBtn.x;
wxPoint p = GetParent() ? GetParent()->GetClientAreaOrigin() : wxPoint(0,0);
m_text->SetSize(x - p.x, y - p.y, wText, height);
m_btn->SetSize(x - p.x + wText, y - p.y, sizeBtn.x, height);
}
// ----------------------------------------------------------------------------
// operations
// ----------------------------------------------------------------------------
bool wxComboControl::Enable(bool enable)
{
if ( !wxControl::Enable(enable) )
return FALSE;
m_btn->Enable(enable);
m_text->Enable(enable);
return TRUE;
}
bool wxComboControl::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;
}
// ----------------------------------------------------------------------------
// popup window handling
// ----------------------------------------------------------------------------
void wxComboControl::SetPopupControl(wxComboPopup *popup)
{
m_popup = popup;
}
void wxComboControl::ShowPopup()
{
wxCHECK_RET( m_popup, _T("no popup to show in wxComboControl") );
wxCHECK_RET( !IsPopupShown(), _T("popup window already shown") );
wxControl *control = m_popup->GetControl();
// size and position the popup window correctly
m_winPopup->SetSize(GetSize().x,
m_heightPopup == -1 ? control->GetBestSize().y
: m_heightPopup);
wxSize sizePopup = m_winPopup->GetClientSize();
control->SetSize(0, 0, sizePopup.x, sizePopup.y);
// some controls don't accept the size we give then: e.g. a listbox may
// require more space to show its last row
wxSize sizeReal = control->GetSize();
if ( sizeReal != sizePopup )
{
m_winPopup->SetClientSize(sizeReal);
}
m_winPopup->PositionNearCombo();
// show it
m_popup->OnShow();
m_winPopup->Popup(m_text);
m_text->SelectAll();
m_popup->SetSelection(m_text->GetValue());
m_isPopupShown = TRUE;
}
void wxComboControl::HidePopup()
{
wxCHECK_RET( m_popup, _T("no popup to hide in wxComboControl") );
wxCHECK_RET( IsPopupShown(), _T("popup window not shown") );
m_winPopup->Dismiss();
m_isPopupShown = FALSE;
}
void wxComboControl::OnSelect(const wxString& value)
{
m_text->SetValue(value);
m_text->SelectAll();
OnDismiss();
}
void wxComboControl::OnDismiss()
{
HidePopup();
m_text->SetFocus();
}
// ----------------------------------------------------------------------------
// wxComboTextCtrl
// ----------------------------------------------------------------------------
wxComboTextCtrl::wxComboTextCtrl(wxComboControl *combo,
const wxString& value,
long style,
const wxValidator& validator)
: wxTextCtrl(combo->GetParent(), -1, value,
wxDefaultPosition, wxDefaultSize,
wxBORDER_NONE | style,
validator)
{
m_combo = combo;
}
void wxComboTextCtrl::OnText(wxCommandEvent& event)
{
if ( m_combo->IsPopupShown() )
{
m_combo->GetPopupControl()->SetSelection(GetValue());
}
// we need to make a copy of the event to have the correct originating
// object and id
wxCommandEvent event2 = event;
event2.SetEventObject(m_combo);
event2.SetId(m_combo->GetId());
// there is a small incompatibility with wxMSW here: the combobox gets the
// event before the text control in our case which corresponds to SMW
// CBN_EDITUPDATE notification and not CBN_EDITCHANGE one wxMSW currently
// uses
//
// if this is really a problem, we can play games with the event handlers
// to circumvent this
(void)m_combo->ProcessEvent(event2);
event.Skip();
}
// pass the keys we don't process to the combo first
void wxComboTextCtrl::OnKey(wxKeyEvent& event)
{
switch ( event.GetKeyCode() )
{
case WXK_RETURN:
// the popup control gets it first but only if it is shown
if ( !m_combo->IsPopupShown() )
break;
//else: fall through
case WXK_UP:
case WXK_DOWN:
case WXK_ESCAPE:
case WXK_PAGEDOWN:
case WXK_PAGEUP:
case WXK_PRIOR:
case WXK_NEXT:
(void)m_combo->ProcessEvent(event);
return;
}
event.Skip();
}
// ----------------------------------------------------------------------------
// wxComboListBox
// ----------------------------------------------------------------------------
wxComboListBox::wxComboListBox(wxComboControl *combo, int style)
: wxListBox(combo->GetPopupWindow(), -1,
wxDefaultPosition, wxDefaultSize,
0, NULL,
wxBORDER_SIMPLE | wxLB_INT_HEIGHT | style),
wxComboPopup(combo)
{
// we don't react to the mouse events outside the window at all
StopAutoScrolling();
}
wxComboListBox::~wxComboListBox()
{
}
bool wxComboListBox::SetSelection(const wxString& value)
{
// FindItem() would just find the current item for an empty string (it
// always matches), but we want to show the first one in such case
if ( value.empty() )
{
if ( GetCount() )
{
wxListBox::SetSelection(0);
}
//else: empty listbox - nothing to do
}
else if ( !FindItem(value) )
{
// no match att all
return FALSE;
}
return TRUE;
}
void wxComboListBox::OnSelect(wxCommandEvent& event)
{
if ( m_clicked )
{
// first update the combo and close the listbox
m_combo->OnSelect(event.GetString());
// next let the user code have the event
// all fields are already filled by the listbox, just change the event
// type and send it to the combo
wxCommandEvent event2 = event;
event2.SetEventType(wxEVT_COMMAND_COMBOBOX_SELECTED);
event2.SetEventObject(m_combo);
event2.SetId(m_combo->GetId());
m_combo->ProcessEvent(event2);
}
//else: ignore the events resultign from just moving the mouse initially
}
void wxComboListBox::OnShow()
{
// nobody clicked us yet
m_clicked = FALSE;
}
bool wxComboListBox::PerformAction(const wxControlAction& action,
long numArg,
const wxString& strArg)
{
if ( action == wxACTION_LISTBOX_FIND )
{
// we don't let the listbox handle this as instead of just using the
// single key presses, as usual, we use the text ctrl value as prefix
// and this is done by wxComboControl itself
return TRUE;
}
return wxListBox::PerformAction(action, numArg, strArg);
}
void wxComboListBox::OnLeftUp(wxMouseEvent& event)
{
// we should dismiss the combo now
m_clicked = TRUE;
event.Skip();
}
void wxComboListBox::OnMouseMove(wxMouseEvent& event)
{
// while a wxComboListBox is shown, it always has capture, so if it doesn't
// we're about to go away anyhow (normally this shouldn't happen at all,
// but I don't put assert here as it just might do on other platforms and
// it doesn't break anythign anyhow)
if ( this == wxWindow::GetCapture() )
{
if ( HitTest(event.GetPosition()) == wxHT_WINDOW_INSIDE )
{
event.Skip();
}
//else: popup shouldn't react to the mouse motions outside it, it only
// captures the mouse to be able to detect when it must be
// dismissed, so don't call Skip()
}
}
wxSize wxComboListBox::DoGetBestClientSize() const
{
// don't return size too big or we risk to not fit on the screen
wxSize size = wxListBox::DoGetBestClientSize();
wxCoord hChar = GetCharHeight();
int nLines = size.y / hChar;
// 10 is the same limit as used by wxMSW
if ( nLines > 10 )
{
size.y = 10*hChar;
}
return size;
}
// ----------------------------------------------------------------------------
// wxComboBox
// ----------------------------------------------------------------------------
void wxComboBox::Init()
{
m_lbox = (wxListBox *)NULL;
}
bool wxComboBox::Create(wxWindow *parent,
wxWindowID id,
const wxString& value,
const wxPoint& pos,
const wxSize& size,
int n,
const wxString *choices,
long style,
const wxValidator& validator,
const wxString& name)
{
if ( !wxComboControl::Create(parent, id, value, pos, size, style,
validator, name) )
{
return FALSE;
}
wxComboListBox *combolbox =
new wxComboListBox(this, style & wxCB_SORT ? wxLB_SORT : 0);
m_lbox = combolbox;
m_lbox->Set(n, choices);
SetPopupControl(combolbox);
return TRUE;
}
wxComboBox::~wxComboBox()
{
}
// ----------------------------------------------------------------------------
// wxComboBox methods forwarded to wxTextCtrl
// ----------------------------------------------------------------------------
wxString wxComboBox::GetValue() const
{
return GetText()->GetValue();
}
void wxComboBox::SetValue(const wxString& value)
{
GetText()->SetValue(value);
}
void wxComboBox::Copy()
{
GetText()->Copy();
}
void wxComboBox::Cut()
{
GetText()->Cut();
}
void wxComboBox::Paste()
{
GetText()->Paste();
}
void wxComboBox::SetInsertionPoint(long pos)
{
GetText()->SetInsertionPoint(pos);
}
void wxComboBox::SetInsertionPointEnd()
{
GetText()->SetInsertionPointEnd();
}
long wxComboBox::GetInsertionPoint() const
{
return GetText()->GetInsertionPoint();
}
long wxComboBox::GetLastPosition() const
{
return GetText()->GetLastPosition();
}
void wxComboBox::Replace(long from, long to, const wxString& value)
{
GetText()->Replace(from, to, value);
}
void wxComboBox::Remove(long from, long to)
{
GetText()->Remove(from, to);
}
void wxComboBox::SetSelection(long from, long to)
{
GetText()->SetSelection(from, to);
}
void wxComboBox::SetEditable(bool editable)
{
GetText()->SetEditable(editable);
}
// ----------------------------------------------------------------------------
// wxComboBox methods forwarded to wxListBox
// ----------------------------------------------------------------------------
void wxComboBox::Clear()
{
GetLBox()->Clear();
GetText()->SetValue(wxEmptyString);
}
void wxComboBox::Delete(int n)
{
wxCHECK_RET( (n >= 0) && (n < GetCount()), _T("invalid index in wxComboBox::Delete") );
if (GetSelection() == n)
GetText()->SetValue(wxEmptyString);
GetLBox()->Delete(n);
}
int wxComboBox::GetCount() const
{
return GetLBox()->GetCount();
}
wxString wxComboBox::GetString(int n) const
{
wxCHECK_MSG( (n >= 0) && (n < GetCount()), wxEmptyString, _T("invalid index in wxComboBox::GetString") );
return GetLBox()->GetString(n);
}
void wxComboBox::SetString(int n, const wxString& s)
{
wxCHECK_RET( (n >= 0) && (n < GetCount()), _T("invalid index in wxComboBox::SetString") );
GetLBox()->SetString(n, s);
}
int wxComboBox::FindString(const wxString& s) const
{
return GetLBox()->FindString(s);
}
void wxComboBox::Select(int n)
{
wxCHECK_RET( (n >= 0) && (n < GetCount()), _T("invalid index in wxComboBox::Select") );
GetLBox()->SetSelection(n);
GetText()->SetValue(GetLBox()->GetString(n));
}
int wxComboBox::GetSelection() const
{
#if 1 // FIXME:: What is the correct behavior?
// if the current value isn't one of the listbox strings, return -1
return GetLBox()->GetSelection();
#else
// Why oh why is this done this way?
// It is not because the value displayed in the text can be found
// in the list that it is the item that is selected!
return FindString(GetText()->GetValue());
#endif
}
int wxComboBox::DoAppend(const wxString& item)
{
return GetLBox()->Append(item);
}
void wxComboBox::DoSetItemClientData(int n, void* clientData)
{
GetLBox()->SetClientData(n, clientData);
}
void *wxComboBox::DoGetItemClientData(int n) const
{
return GetLBox()->GetClientData(n);
}
void wxComboBox::DoSetItemClientObject(int n, wxClientData* clientData)
{
GetLBox()->SetClientObject(n, clientData);
}
wxClientData* wxComboBox::DoGetItemClientObject(int n) const
{
return GetLBox()->GetClientObject(n);
}
// ----------------------------------------------------------------------------
// input handling
// ----------------------------------------------------------------------------
void wxComboControl::OnKey(wxKeyEvent& event)
{
if ( m_isPopupShown )
{
// pass it to the popped up control
(void)m_popup->GetControl()->ProcessEvent(event);
}
else // no popup
{
event.Skip();
}
}
bool wxComboControl::PerformAction(const wxControlAction& action,
long numArg,
const wxString& strArg)
{
bool processed = FALSE;
if ( action == wxACTION_COMBOBOX_POPUP )
{
if ( !m_isPopupShown )
{
ShowPopup();
processed = TRUE;
}
}
else if ( action == wxACTION_COMBOBOX_DISMISS )
{
if ( m_isPopupShown )
{
HidePopup();
processed = TRUE;
}
}
if ( !processed )
{
// pass along
return wxControl::PerformAction(action, numArg, strArg);
}
return TRUE;
}
// ----------------------------------------------------------------------------
// wxStdComboBoxInputHandler
// ----------------------------------------------------------------------------
wxStdComboBoxInputHandler::wxStdComboBoxInputHandler(wxInputHandler *inphand)
: wxStdInputHandler(inphand)
{
}
bool wxStdComboBoxInputHandler::HandleKey(wxInputConsumer *consumer,
const wxKeyEvent& event,
bool pressed)
{
if ( pressed )
{
wxControlAction action;
switch ( event.GetKeyCode() )
{
case WXK_DOWN:
action = wxACTION_COMBOBOX_POPUP;
break;
case WXK_ESCAPE:
action = wxACTION_COMBOBOX_DISMISS;
break;
}
if ( !!action )
{
consumer->PerformAction(action);
return TRUE;
}
}
return wxStdInputHandler::HandleKey(consumer, event, pressed);
}
#endif // wxUSE_COMBOBOX