So base classes will process the event too. This is need for (at least) wxGenericColourButton It has its own handler, but also needs to handle the DPI event in wxButtonImageData.
1224 lines
33 KiB
C++
1224 lines
33 KiB
C++
/////////////////////////////////////////////////////////////////////////////
|
|
// Name: src/generic/odcombo.cpp
|
|
// Purpose: wxOwnerDrawnComboBox, wxVListBoxComboPopup
|
|
// Author: Jaakko Salli
|
|
// Modified by:
|
|
// Created: Apr-30-2006
|
|
// Copyright: (c) 2005 Jaakko Salli
|
|
// Licence: wxWindows licence
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
// ============================================================================
|
|
// declarations
|
|
// ============================================================================
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// headers
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#include "wx/wxprec.h"
|
|
|
|
|
|
#if wxUSE_ODCOMBOBOX
|
|
|
|
#include "wx/odcombo.h"
|
|
|
|
#ifndef WX_PRECOMP
|
|
#include "wx/log.h"
|
|
#include "wx/combobox.h"
|
|
#include "wx/dcclient.h"
|
|
#include "wx/settings.h"
|
|
#include "wx/dialog.h"
|
|
#include "wx/textctrl.h"
|
|
#endif
|
|
|
|
#include "wx/combo.h"
|
|
|
|
// ============================================================================
|
|
// implementation
|
|
// ============================================================================
|
|
|
|
// time in milliseconds before partial completion buffer drops
|
|
#define wxODCB_PARTIAL_COMPLETION_TIME 1000
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// wxVListBoxComboPopup is a wxVListBox customized to act as a popup control
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
wxBEGIN_EVENT_TABLE(wxVListBoxComboPopup, wxVListBox)
|
|
EVT_MOTION(wxVListBoxComboPopup::OnMouseMove)
|
|
EVT_KEY_DOWN(wxVListBoxComboPopup::OnKey)
|
|
EVT_CHAR(wxVListBoxComboPopup::OnChar)
|
|
EVT_LEFT_UP(wxVListBoxComboPopup::OnLeftClick)
|
|
wxEND_EVENT_TABLE()
|
|
|
|
|
|
void wxVListBoxComboPopup::Init()
|
|
{
|
|
m_widestWidth = 0;
|
|
m_widestItem = -1;
|
|
m_widthsDirty = false;
|
|
m_findWidest = false;
|
|
m_itemHeight = 0;
|
|
m_value = -1;
|
|
m_itemHover = -1;
|
|
m_clientDataItemsType = wxClientData_None;
|
|
m_partialCompletionString.clear();
|
|
}
|
|
|
|
bool wxVListBoxComboPopup::Create(wxWindow* parent)
|
|
{
|
|
if ( !wxVListBox::Create(parent,
|
|
wxID_ANY,
|
|
wxDefaultPosition,
|
|
wxDefaultSize,
|
|
wxBORDER_SIMPLE | wxLB_INT_HEIGHT | wxWANTS_CHARS) )
|
|
return false;
|
|
|
|
m_useFont = m_combo->GetFont();
|
|
|
|
wxVListBox::SetItemCount(m_strings.GetCount());
|
|
|
|
// TODO: Move this to SetFont
|
|
m_itemHeight = m_combo->GetCharHeight();
|
|
|
|
// Bind to the DPI event of the combobox. We get our own once the popup
|
|
// is shown, but this is too late, m_itemHeight is already being used.
|
|
m_combo->Bind(wxEVT_DPI_CHANGED, &wxVListBoxComboPopup::OnDPIChanged, this);
|
|
|
|
return true;
|
|
}
|
|
|
|
wxVListBoxComboPopup::~wxVListBoxComboPopup()
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
void wxVListBoxComboPopup::SetFocus()
|
|
{
|
|
// Suppress SetFocus() warning by simply not calling it. This combo popup
|
|
// has already been designed with the assumption that SetFocus() may not
|
|
// do anything useful, so it really doesn't need to be called.
|
|
#ifdef __WXMSW__
|
|
//
|
|
#else
|
|
wxVListBox::SetFocus();
|
|
#endif
|
|
}
|
|
|
|
void wxVListBoxComboPopup::OnDPIChanged(wxDPIChangedEvent& event)
|
|
{
|
|
m_itemHeight = m_combo->GetCharHeight();
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
bool wxVListBoxComboPopup::LazyCreate()
|
|
{
|
|
// NB: There is a bug with wxVListBox that can be avoided by creating
|
|
// it later (bug causes empty space to be shown if initial selection
|
|
// is at the end of a list longer than the control can show at once).
|
|
return true;
|
|
}
|
|
|
|
// paint the control itself
|
|
void wxVListBoxComboPopup::PaintComboControl( wxDC& dc, const wxRect& rect )
|
|
{
|
|
if ( !(m_combo->GetWindowStyle() & wxODCB_STD_CONTROL_PAINT) )
|
|
{
|
|
int flags = wxODCB_PAINTING_CONTROL;
|
|
|
|
if ( m_combo->ShouldDrawFocus() )
|
|
flags |= wxODCB_PAINTING_SELECTED;
|
|
|
|
OnDrawBg(dc, rect, m_value, flags);
|
|
|
|
if ( m_value >= 0 )
|
|
{
|
|
OnDrawItem(dc,rect,m_value,flags);
|
|
return;
|
|
}
|
|
}
|
|
|
|
wxComboPopup::PaintComboControl(dc,rect);
|
|
}
|
|
|
|
void wxVListBoxComboPopup::OnDrawItem(wxDC& dc, const wxRect& rect, size_t n) const
|
|
{
|
|
// TODO: Maybe this code could be moved to wxVListBox::OnPaint?
|
|
dc.SetFont(m_useFont);
|
|
|
|
int flags = 0;
|
|
|
|
// Set correct text colour for selected items
|
|
if ( wxVListBox::GetSelection() == (int) n )
|
|
{
|
|
dc.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT) );
|
|
flags |= wxODCB_PAINTING_SELECTED;
|
|
}
|
|
else
|
|
{
|
|
dc.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT) );
|
|
}
|
|
|
|
OnDrawItem(dc,rect,(int)n,flags);
|
|
}
|
|
|
|
wxCoord wxVListBoxComboPopup::OnMeasureItem(size_t n) const
|
|
{
|
|
wxOwnerDrawnComboBox* combo = (wxOwnerDrawnComboBox*) m_combo;
|
|
|
|
wxASSERT_MSG( wxDynamicCast(combo, wxOwnerDrawnComboBox),
|
|
wxT("you must subclass wxVListBoxComboPopup for drawing and measuring methods") );
|
|
|
|
wxCoord h = combo->OnMeasureItem(n);
|
|
if ( h < 0 )
|
|
h = m_itemHeight;
|
|
return h;
|
|
}
|
|
|
|
wxCoord wxVListBoxComboPopup::OnMeasureItemWidth(size_t n) const
|
|
{
|
|
wxOwnerDrawnComboBox* combo = (wxOwnerDrawnComboBox*) m_combo;
|
|
|
|
wxASSERT_MSG( wxDynamicCast(combo, wxOwnerDrawnComboBox),
|
|
wxT("you must subclass wxVListBoxComboPopup for drawing and measuring methods") );
|
|
|
|
return combo->OnMeasureItemWidth(n);
|
|
}
|
|
|
|
void wxVListBoxComboPopup::OnDrawBg( wxDC& dc,
|
|
const wxRect& rect,
|
|
int item,
|
|
int flags ) const
|
|
{
|
|
wxOwnerDrawnComboBox* combo = (wxOwnerDrawnComboBox*) m_combo;
|
|
|
|
wxASSERT_MSG( wxDynamicCast(combo, wxOwnerDrawnComboBox),
|
|
wxT("you must subclass wxVListBoxComboPopup for drawing and measuring methods") );
|
|
|
|
if ( IsCurrent((size_t)item) && !(flags & wxODCB_PAINTING_CONTROL) )
|
|
flags |= wxODCB_PAINTING_SELECTED;
|
|
|
|
combo->OnDrawBackground(dc,rect,item,flags);
|
|
}
|
|
|
|
void wxVListBoxComboPopup::OnDrawBackground(wxDC& dc, const wxRect& rect, size_t n) const
|
|
{
|
|
OnDrawBg(dc,rect,(int)n,0);
|
|
}
|
|
|
|
// This is called from wxVListBoxComboPopup::OnDrawItem, with text colour and font prepared
|
|
void wxVListBoxComboPopup::OnDrawItem( wxDC& dc, const wxRect& rect, int item, int flags ) const
|
|
{
|
|
wxOwnerDrawnComboBox* combo = (wxOwnerDrawnComboBox*) m_combo;
|
|
|
|
wxASSERT_MSG( wxDynamicCast(combo, wxOwnerDrawnComboBox),
|
|
wxT("you must subclass wxVListBoxComboPopup for drawing and measuring methods") );
|
|
|
|
combo->OnDrawItem(dc,rect,item,flags);
|
|
}
|
|
|
|
void wxVListBoxComboPopup::DismissWithEvent()
|
|
{
|
|
StopPartialCompletion();
|
|
|
|
int selection = wxVListBox::GetSelection();
|
|
|
|
if ( selection != wxNOT_FOUND )
|
|
m_stringValue = m_strings[selection];
|
|
else
|
|
m_stringValue.clear();
|
|
|
|
m_value = selection;
|
|
|
|
Dismiss();
|
|
|
|
SendComboBoxEvent(selection);
|
|
}
|
|
|
|
void wxVListBoxComboPopup::SendComboBoxEvent( int selection )
|
|
{
|
|
// TODO: wxVListBox should be refactored to inherit from wxItemContainer
|
|
// and then we would be able to just call SendSelectionChangedEvent()
|
|
// (which, itself, should be moved down to wxItemContainer from
|
|
// wxControlWithItemsBase) instead of duplicating its code.
|
|
|
|
wxCommandEvent evt(wxEVT_COMBOBOX,m_combo->GetId());
|
|
|
|
evt.SetEventObject(m_combo);
|
|
|
|
evt.SetInt(selection);
|
|
if ( selection != wxNOT_FOUND )
|
|
evt.SetString(m_strings[selection]);
|
|
|
|
// Set client data, if any
|
|
if ( selection >= 0 && (int)m_clientDatas.size() > selection )
|
|
{
|
|
void* clientData = m_clientDatas[selection];
|
|
if ( m_clientDataItemsType == wxClientData_Object )
|
|
evt.SetClientObject((wxClientData*)clientData);
|
|
else
|
|
evt.SetClientData(clientData);
|
|
}
|
|
|
|
m_combo->GetEventHandler()->AddPendingEvent(evt);
|
|
}
|
|
|
|
// returns true if key was consumed
|
|
bool wxVListBoxComboPopup::HandleKey( int keycode, bool saturate, wxChar keychar )
|
|
{
|
|
const int itemCount = GetCount();
|
|
|
|
// keys do nothing in the empty control and returning immediately avoids
|
|
// using invalid indices below
|
|
if ( !itemCount )
|
|
return false;
|
|
|
|
int value = m_value;
|
|
int comboStyle = m_combo->GetWindowStyle();
|
|
|
|
if ( keychar > 0 )
|
|
{
|
|
// we have character equivalent of the keycode; filter out these that
|
|
// are not printable characters
|
|
if ( !wxIsprint(keychar) )
|
|
keychar = 0;
|
|
}
|
|
|
|
const bool readOnly = (comboStyle & wxCB_READONLY) != 0;
|
|
|
|
if ( keycode == WXK_DOWN || keycode == WXK_NUMPAD_DOWN || ( keycode == WXK_RIGHT && readOnly ) )
|
|
{
|
|
value++;
|
|
StopPartialCompletion();
|
|
}
|
|
else if ( keycode == WXK_UP || keycode == WXK_NUMPAD_UP || ( keycode == WXK_LEFT && readOnly ) )
|
|
{
|
|
value--;
|
|
StopPartialCompletion();
|
|
}
|
|
else if ( keycode == WXK_PAGEDOWN || keycode == WXK_NUMPAD_PAGEDOWN )
|
|
{
|
|
value+=10;
|
|
StopPartialCompletion();
|
|
}
|
|
else if ( keycode == WXK_PAGEUP || keycode == WXK_NUMPAD_PAGEUP )
|
|
{
|
|
value-=10;
|
|
StopPartialCompletion();
|
|
}
|
|
else if ( ( keycode == WXK_HOME || keycode == WXK_NUMPAD_HOME ) && readOnly )
|
|
{
|
|
value=0;
|
|
StopPartialCompletion();
|
|
}
|
|
else if ( ( keycode == WXK_END || keycode == WXK_NUMPAD_END ) && readOnly )
|
|
{
|
|
value=itemCount-1;
|
|
StopPartialCompletion();
|
|
}
|
|
else if ( keychar && readOnly )
|
|
{
|
|
// Try partial completion
|
|
|
|
// find the new partial completion string
|
|
#if wxUSE_TIMER
|
|
if (m_partialCompletionTimer.IsRunning())
|
|
m_partialCompletionString+=wxString(keychar);
|
|
else
|
|
#endif // wxUSE_TIMER
|
|
m_partialCompletionString=wxString(keychar);
|
|
|
|
// now search through the values to see if this is found
|
|
int found = -1;
|
|
unsigned int length=m_partialCompletionString.length();
|
|
int i;
|
|
for (i=0; i<itemCount; i++)
|
|
{
|
|
wxString item=GetString(i);
|
|
if (( item.length() >= length) && (! m_partialCompletionString.CmpNoCase(item.Left(length))))
|
|
{
|
|
found=i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found<0)
|
|
{
|
|
StopPartialCompletion();
|
|
::wxBell();
|
|
return true; // to stop the first value being set
|
|
}
|
|
else
|
|
{
|
|
value=i;
|
|
#if wxUSE_TIMER
|
|
m_partialCompletionTimer.Start(wxODCB_PARTIAL_COMPLETION_TIME, true);
|
|
#endif // wxUSE_TIMER
|
|
}
|
|
}
|
|
else
|
|
return false;
|
|
|
|
if ( saturate )
|
|
{
|
|
if ( value >= itemCount )
|
|
value = itemCount - 1;
|
|
else if ( value < 0 )
|
|
value = 0;
|
|
}
|
|
else
|
|
{
|
|
if ( value >= itemCount )
|
|
value -= itemCount;
|
|
else if ( value < 0 )
|
|
value += itemCount;
|
|
}
|
|
|
|
if ( value == m_value )
|
|
// Even if value was same, don't skip the event
|
|
// (good for consistency)
|
|
return true;
|
|
|
|
if ( value >= 0 )
|
|
m_combo->ChangeValue(m_strings[value]);
|
|
|
|
// The m_combo->SetValue() call above sets m_value to the index of this
|
|
// string. But if there are more identical string, the index is of the
|
|
// first occurrence, which may be wrong, so set the index explicitly here,
|
|
// _after_ the SetValue() call.
|
|
m_value = value;
|
|
|
|
SendComboBoxEvent(m_value);
|
|
|
|
return true;
|
|
}
|
|
|
|
// stop partial completion
|
|
void wxVListBoxComboPopup::StopPartialCompletion()
|
|
{
|
|
m_partialCompletionString.clear();
|
|
#if wxUSE_TIMER
|
|
m_partialCompletionTimer.Stop();
|
|
#endif // wxUSE_TIMER
|
|
}
|
|
|
|
void wxVListBoxComboPopup::OnComboDoubleClick()
|
|
{
|
|
// Cycle on dclick (disable saturation to allow true cycling).
|
|
if ( !::wxGetKeyState(WXK_SHIFT) )
|
|
HandleKey(WXK_DOWN,false);
|
|
else
|
|
HandleKey(WXK_UP,false);
|
|
}
|
|
|
|
void wxVListBoxComboPopup::OnComboKeyEvent( wxKeyEvent& event )
|
|
{
|
|
// Saturated key movement on
|
|
if ( !HandleKey(event.GetKeyCode(), true) )
|
|
event.Skip();
|
|
}
|
|
|
|
void wxVListBoxComboPopup::OnComboCharEvent( wxKeyEvent& event )
|
|
{
|
|
// unlike in OnComboKeyEvent, wxEVT_CHAR contains meaningful
|
|
// printable character information, so pass it
|
|
#if wxUSE_UNICODE
|
|
const wxChar charcode = event.GetUnicodeKey();
|
|
#else
|
|
const wxChar charcode = (wxChar)event.GetKeyCode();
|
|
#endif
|
|
|
|
if ( !HandleKey(event.GetKeyCode(), true, charcode) )
|
|
event.Skip();
|
|
}
|
|
|
|
void wxVListBoxComboPopup::OnPopup()
|
|
{
|
|
// *must* set value after size is set (this is because of a vlbox bug)
|
|
wxVListBox::SetSelection(m_value);
|
|
}
|
|
|
|
void wxVListBoxComboPopup::OnMouseMove(wxMouseEvent& event)
|
|
{
|
|
event.Skip();
|
|
|
|
// Move selection to cursor if it is inside the popup
|
|
|
|
int y = event.GetPosition().y;
|
|
int fromBottom = GetClientSize().y - y;
|
|
|
|
// Since in any case we need to find out if the last item is only
|
|
// partially visible, we might just as well replicate the HitTest
|
|
// loop here.
|
|
const size_t lineMax = GetVisibleEnd();
|
|
for ( size_t line = GetVisibleBegin(); line < lineMax; line++ )
|
|
{
|
|
y -= OnGetRowHeight(line);
|
|
if ( y < 0 )
|
|
{
|
|
// Only change selection if item is fully visible
|
|
if ( (y + fromBottom) >= 0 )
|
|
{
|
|
wxVListBox::SetSelection((int)line);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void wxVListBoxComboPopup::OnLeftClick(wxMouseEvent& WXUNUSED(event))
|
|
{
|
|
DismissWithEvent();
|
|
}
|
|
|
|
void wxVListBoxComboPopup::OnKey(wxKeyEvent& event)
|
|
{
|
|
// Hide popup if certain key or key combination was pressed
|
|
if ( m_combo->IsKeyPopupToggle(event) )
|
|
{
|
|
StopPartialCompletion();
|
|
Dismiss();
|
|
}
|
|
else if ( event.AltDown() )
|
|
{
|
|
// On both wxGTK and wxMSW, pressing Alt down seems to
|
|
// completely freeze things in popup (ie. arrow keys and
|
|
// enter won't work).
|
|
return;
|
|
}
|
|
// Select item if ENTER is pressed
|
|
else if ( event.GetKeyCode() == WXK_RETURN || event.GetKeyCode() == WXK_NUMPAD_ENTER )
|
|
{
|
|
DismissWithEvent();
|
|
}
|
|
else
|
|
{
|
|
// completion is handled in OnChar() below
|
|
event.Skip();
|
|
}
|
|
}
|
|
|
|
void wxVListBoxComboPopup::OnChar(wxKeyEvent& event)
|
|
{
|
|
if ( m_combo->GetWindowStyle() & wxCB_READONLY )
|
|
{
|
|
// Process partial completion key codes here, but not the arrow keys as
|
|
// the base class will do that for us
|
|
#if wxUSE_UNICODE
|
|
const wxChar charcode = event.GetUnicodeKey();
|
|
#else
|
|
const wxChar charcode = (wxChar)event.GetKeyCode();
|
|
#endif
|
|
if ( wxIsprint(charcode) )
|
|
{
|
|
OnComboCharEvent(event);
|
|
SetSelection(m_value); // ensure the highlight bar moves
|
|
return; // don't skip the event
|
|
}
|
|
}
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
void wxVListBoxComboPopup::Insert( const wxString& item, int pos )
|
|
{
|
|
// Need to change selection?
|
|
if ( m_combo->GetValue() == item )
|
|
{
|
|
m_value = wxMin(m_value, pos);
|
|
}
|
|
else if ( pos <= m_value )
|
|
{
|
|
m_value++;
|
|
}
|
|
|
|
m_strings.Insert(item,pos);
|
|
if ( (int)m_clientDatas.size() >= pos )
|
|
m_clientDatas.insert(m_clientDatas.begin()+pos, NULL);
|
|
|
|
m_widths.insert(m_widths.begin()+pos, -1);
|
|
m_widthsDirty = true;
|
|
|
|
if ( IsCreated() )
|
|
wxVListBox::SetItemCount( wxVListBox::GetItemCount()+1 );
|
|
}
|
|
|
|
int wxVListBoxComboPopup::Append(const wxString& item)
|
|
{
|
|
int pos = (int)m_strings.GetCount();
|
|
|
|
if ( m_combo->GetWindowStyle() & wxCB_SORT )
|
|
{
|
|
// Find position
|
|
// TODO: Could be optimized with binary search
|
|
const wxArrayString& strings = m_strings;
|
|
for ( size_t i=0; i<strings.GetCount(); i++ )
|
|
{
|
|
if ( item.CmpNoCase(strings.Item(i)) <= 0 )
|
|
{
|
|
pos = (int)i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Insert(item,pos);
|
|
|
|
return pos;
|
|
}
|
|
|
|
void wxVListBoxComboPopup::Clear()
|
|
{
|
|
wxASSERT(m_combo);
|
|
|
|
m_strings.Empty();
|
|
m_widths.clear();
|
|
|
|
m_widestWidth = 0;
|
|
m_widestItem = -1;
|
|
|
|
ClearClientDatas();
|
|
|
|
m_value = wxNOT_FOUND;
|
|
|
|
if ( IsCreated() )
|
|
wxVListBox::SetItemCount(0);
|
|
}
|
|
|
|
void wxVListBoxComboPopup::ClearClientDatas()
|
|
{
|
|
if ( m_clientDataItemsType == wxClientData_Object )
|
|
{
|
|
for ( wxVector<void*>::iterator it = m_clientDatas.begin(); it != m_clientDatas.end(); ++it )
|
|
delete (wxClientData*) *it;
|
|
}
|
|
|
|
m_clientDatas.clear();
|
|
m_clientDataItemsType = wxClientData_None;
|
|
}
|
|
|
|
void wxVListBoxComboPopup::SetItemClientData( unsigned int n,
|
|
void* clientData,
|
|
wxClientDataType clientDataItemsType )
|
|
{
|
|
// It should be sufficient to update this variable only here
|
|
m_clientDataItemsType = clientDataItemsType;
|
|
|
|
m_clientDatas[n] = clientData;
|
|
|
|
ItemWidthChanged(n);
|
|
}
|
|
|
|
void* wxVListBoxComboPopup::GetItemClientData(unsigned int n) const
|
|
{
|
|
return n < m_clientDatas.size() ? m_clientDatas[n] : NULL;
|
|
}
|
|
|
|
void wxVListBoxComboPopup::Delete( unsigned int item )
|
|
{
|
|
// Remove client data, if set
|
|
if ( !m_clientDatas.empty() )
|
|
{
|
|
if ( m_clientDataItemsType == wxClientData_Object )
|
|
delete (wxClientData*) m_clientDatas[item];
|
|
|
|
m_clientDatas.erase(m_clientDatas.begin()+item);
|
|
}
|
|
|
|
m_strings.RemoveAt(item);
|
|
m_widths.erase(m_widths.begin()+item);
|
|
|
|
if ( (int)item == m_widestItem )
|
|
m_findWidest = true;
|
|
|
|
int sel = GetSelection();
|
|
|
|
if ( IsCreated() )
|
|
wxVListBox::SetItemCount( wxVListBox::GetItemCount()-1 );
|
|
|
|
// Fix selection
|
|
if ( (int)item < sel )
|
|
SetSelection(sel-1);
|
|
else if ( (int)item == sel )
|
|
SetSelection(wxNOT_FOUND);
|
|
}
|
|
|
|
int wxVListBoxComboPopup::FindString(const wxString& s, bool bCase) const
|
|
{
|
|
return m_strings.Index(s, bCase);
|
|
}
|
|
|
|
bool wxVListBoxComboPopup::FindItem(const wxString& item, wxString* trueItem)
|
|
{
|
|
int idx = m_strings.Index(item, false);
|
|
if ( idx == wxNOT_FOUND )
|
|
return false;
|
|
if ( trueItem != NULL )
|
|
*trueItem = m_strings[idx];
|
|
return true;
|
|
}
|
|
|
|
unsigned int wxVListBoxComboPopup::GetCount() const
|
|
{
|
|
return m_strings.GetCount();
|
|
}
|
|
|
|
wxString wxVListBoxComboPopup::GetString( int item ) const
|
|
{
|
|
return m_strings[item];
|
|
}
|
|
|
|
void wxVListBoxComboPopup::SetString( int item, const wxString& str )
|
|
{
|
|
m_strings[item] = str;
|
|
ItemWidthChanged(item);
|
|
}
|
|
|
|
wxString wxVListBoxComboPopup::GetStringValue() const
|
|
{
|
|
return m_stringValue;
|
|
}
|
|
|
|
void wxVListBoxComboPopup::SetSelection( int item )
|
|
{
|
|
wxCHECK_RET( item == wxNOT_FOUND || ((unsigned int)item < GetCount()),
|
|
wxT("invalid index in wxVListBoxComboPopup::SetSelection") );
|
|
|
|
m_value = item;
|
|
|
|
if ( item >= 0 )
|
|
m_stringValue = m_strings[item];
|
|
else
|
|
m_stringValue.clear();
|
|
|
|
if ( IsCreated() )
|
|
wxVListBox::SetSelection(item);
|
|
}
|
|
|
|
int wxVListBoxComboPopup::GetSelection() const
|
|
{
|
|
return m_value;
|
|
}
|
|
|
|
void wxVListBoxComboPopup::SetStringValue( const wxString& value )
|
|
{
|
|
m_stringValue = value;
|
|
|
|
// Keep previous selection if it already corresponds to the given value
|
|
// (this is useful if there are multiple identical items in the combobox,
|
|
// we don't want to select the first one of them if another one had been
|
|
// previously selected).
|
|
if ( m_value >= 0 && m_value < (int)m_strings.size() &&
|
|
value == m_strings[m_value] )
|
|
{
|
|
return;
|
|
}
|
|
|
|
int index = m_strings.Index(value);
|
|
|
|
if ( index >= 0 && index < (int)wxVListBox::GetItemCount() )
|
|
{
|
|
wxVListBox::SetSelection(index);
|
|
m_value = index;
|
|
}
|
|
}
|
|
|
|
void wxVListBoxComboPopup::CalcWidths()
|
|
{
|
|
bool doFindWidest = m_findWidest;
|
|
|
|
// Measure items with dirty width.
|
|
if ( m_widthsDirty )
|
|
{
|
|
unsigned int n = m_widths.size();
|
|
int dirtyHandled = 0;
|
|
wxVector<int>& widths = m_widths;
|
|
|
|
// I think using wxDC::GetTextExtent is faster than
|
|
// wxWindow::GetTextExtent (assuming same dc is used
|
|
// for all calls, as we do here).
|
|
wxClientDC dc(m_combo);
|
|
if ( !m_useFont.IsOk() )
|
|
m_useFont = m_combo->GetFont();
|
|
dc.SetFont(m_useFont);
|
|
|
|
for ( unsigned int i=0; i<n; i++ )
|
|
{
|
|
if ( widths[i] < 0 )
|
|
{
|
|
wxCoord x = OnMeasureItemWidth(i);
|
|
|
|
if ( x < 0 )
|
|
{
|
|
const wxString& text = m_strings[i];
|
|
|
|
// To make sure performance won't suck in extreme scenarios,
|
|
// we'll estimate length after some arbitrary number of items
|
|
// have been checked precily.
|
|
if ( dirtyHandled < 1024 )
|
|
{
|
|
wxCoord y;
|
|
dc.GetTextExtent(text, &x, &y, 0, 0);
|
|
x += 4;
|
|
}
|
|
else
|
|
{
|
|
x = text.length() * (dc.GetCharWidth()+1);
|
|
}
|
|
}
|
|
|
|
widths[i] = x;
|
|
|
|
if ( x >= m_widestWidth )
|
|
{
|
|
m_widestWidth = x;
|
|
m_widestItem = (int)i;
|
|
}
|
|
else if ( (int)i == m_widestItem )
|
|
{
|
|
// Width of previously widest item has been decreased, so
|
|
// we'll have to check all to find current widest item.
|
|
doFindWidest = true;
|
|
}
|
|
|
|
dirtyHandled++;
|
|
}
|
|
}
|
|
|
|
m_widthsDirty = false;
|
|
}
|
|
|
|
if ( doFindWidest )
|
|
{
|
|
unsigned int n = m_widths.size();
|
|
|
|
int bestWidth = -1;
|
|
int bestIndex = -1;
|
|
|
|
for ( unsigned int i=0; i<n; i++ )
|
|
{
|
|
int w = m_widths[i];
|
|
if ( w > bestWidth )
|
|
{
|
|
bestIndex = (int)i;
|
|
bestWidth = w;
|
|
}
|
|
}
|
|
|
|
m_widestWidth = bestWidth;
|
|
m_widestItem = bestIndex;
|
|
|
|
m_findWidest = false;
|
|
}
|
|
}
|
|
|
|
wxSize wxVListBoxComboPopup::GetAdjustedSize( int minWidth, int prefHeight, int maxHeight )
|
|
{
|
|
int height = 250;
|
|
|
|
maxHeight -= 2; // Must take borders into account
|
|
|
|
if ( m_strings.GetCount() )
|
|
{
|
|
if ( prefHeight > 0 )
|
|
height = prefHeight;
|
|
|
|
if ( height > maxHeight )
|
|
height = maxHeight;
|
|
|
|
int totalHeight = GetTotalHeight(); // + 3;
|
|
|
|
// Take borders into account on Mac or scrollbars always appear
|
|
#if defined(__WXMAC__)
|
|
totalHeight += 2;
|
|
#endif
|
|
if ( height >= totalHeight )
|
|
{
|
|
height = totalHeight;
|
|
}
|
|
else
|
|
{
|
|
// Adjust height to a multiple of the height of the first item
|
|
// NB: Calculations that take variable height into account
|
|
// are unnecessary.
|
|
int fih = GetLineHeight(0);
|
|
height -= height % fih;
|
|
}
|
|
}
|
|
else
|
|
height = 50;
|
|
|
|
CalcWidths();
|
|
|
|
// Take scrollbar into account in width calculations
|
|
int widestWidth = m_widestWidth + wxSystemSettings::GetMetric(wxSYS_VSCROLL_X, this);
|
|
return wxSize(minWidth > widestWidth ? minWidth : widestWidth,
|
|
height+2);
|
|
}
|
|
|
|
//void wxVListBoxComboPopup::Populate( int n, const wxString choices[] )
|
|
void wxVListBoxComboPopup::Populate( const wxArrayString& choices )
|
|
{
|
|
int n = choices.GetCount();
|
|
|
|
for ( int i=0; i<n; i++ )
|
|
{
|
|
const wxString& item = choices.Item(i);
|
|
m_strings.Add(item);
|
|
}
|
|
|
|
m_widths.resize(n,-1);
|
|
m_widthsDirty = true;
|
|
|
|
if ( IsCreated() )
|
|
wxVListBox::SetItemCount(n);
|
|
|
|
// Sort the initial choices
|
|
if ( m_combo->GetWindowStyle() & wxCB_SORT )
|
|
m_strings.Sort();
|
|
|
|
// Find initial selection
|
|
wxString strValue = m_combo->GetValue();
|
|
if ( !strValue.empty() )
|
|
m_value = m_strings.Index(strValue);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// wxOwnerDrawnComboBox
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
wxBEGIN_EVENT_TABLE(wxOwnerDrawnComboBox, wxComboCtrl)
|
|
wxEND_EVENT_TABLE()
|
|
|
|
void wxOwnerDrawnComboBox::Init()
|
|
{
|
|
}
|
|
|
|
bool wxOwnerDrawnComboBox::Create(wxWindow *parent,
|
|
wxWindowID id,
|
|
const wxString& value,
|
|
const wxPoint& pos,
|
|
const wxSize& size,
|
|
long style,
|
|
const wxValidator& validator,
|
|
const wxString& name)
|
|
{
|
|
return wxComboCtrl::Create(parent,id,value,pos,size,style,validator,name);
|
|
}
|
|
|
|
wxOwnerDrawnComboBox::wxOwnerDrawnComboBox(wxWindow *parent,
|
|
wxWindowID id,
|
|
const wxString& value,
|
|
const wxPoint& pos,
|
|
const wxSize& size,
|
|
const wxArrayString& choices,
|
|
long style,
|
|
const wxValidator& validator,
|
|
const wxString& name)
|
|
{
|
|
Init();
|
|
|
|
Create(parent,id,value,pos,size,choices,style, validator, name);
|
|
}
|
|
|
|
bool wxOwnerDrawnComboBox::Create(wxWindow *parent,
|
|
wxWindowID id,
|
|
const wxString& value,
|
|
const wxPoint& pos,
|
|
const wxSize& size,
|
|
const wxArrayString& choices,
|
|
long style,
|
|
const wxValidator& validator,
|
|
const wxString& name)
|
|
{
|
|
m_initChs = choices;
|
|
//wxCArrayString chs(choices);
|
|
|
|
//return Create(parent, id, value, pos, size, chs.GetCount(),
|
|
// chs.GetStrings(), style, validator, name);
|
|
return Create(parent, id, value, pos, size, 0,
|
|
NULL, style, validator, name);
|
|
}
|
|
|
|
bool wxOwnerDrawnComboBox::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 ( !Create(parent, id, value, pos, size, style,
|
|
validator, name) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for ( int i=0; i<n; i++ )
|
|
m_initChs.Add(choices[i]);
|
|
|
|
return true;
|
|
}
|
|
|
|
wxOwnerDrawnComboBox::~wxOwnerDrawnComboBox()
|
|
{
|
|
if ( m_popupInterface )
|
|
GetVListBoxComboPopup()->ClearClientDatas();
|
|
}
|
|
|
|
void wxOwnerDrawnComboBox::DoSetPopupControl(wxComboPopup* popup)
|
|
{
|
|
if ( !popup )
|
|
{
|
|
popup = new wxVListBoxComboPopup();
|
|
}
|
|
|
|
wxComboCtrl::DoSetPopupControl(popup);
|
|
|
|
wxASSERT(popup);
|
|
|
|
// Add initial choices to the wxVListBox
|
|
if ( !GetVListBoxComboPopup()->GetCount() )
|
|
{
|
|
GetVListBoxComboPopup()->Populate(m_initChs);
|
|
m_initChs.Clear();
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// wxOwnerDrawnComboBox item manipulation methods
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void wxOwnerDrawnComboBox::DoClear()
|
|
{
|
|
EnsurePopupControl();
|
|
|
|
GetVListBoxComboPopup()->Clear();
|
|
|
|
// There is no text entry when using wxCB_READONLY style, so test for it.
|
|
if ( GetTextCtrl() )
|
|
wxTextEntry::Clear();
|
|
}
|
|
|
|
void wxOwnerDrawnComboBox::Clear()
|
|
{
|
|
DoClear();
|
|
SetClientDataType(wxClientData_None);
|
|
}
|
|
|
|
void wxOwnerDrawnComboBox::DoDeleteOneItem(unsigned int n)
|
|
{
|
|
wxCHECK_RET( IsValid(n), wxT("invalid index in wxOwnerDrawnComboBox::Delete") );
|
|
|
|
if ( GetSelection() == (int) n )
|
|
ChangeValue(wxEmptyString);
|
|
|
|
GetVListBoxComboPopup()->Delete(n);
|
|
}
|
|
|
|
unsigned int wxOwnerDrawnComboBox::GetCount() const
|
|
{
|
|
if ( !m_popupInterface )
|
|
return m_initChs.GetCount();
|
|
|
|
return GetVListBoxComboPopup()->GetCount();
|
|
}
|
|
|
|
wxString wxOwnerDrawnComboBox::GetString(unsigned int n) const
|
|
{
|
|
wxCHECK_MSG( IsValid(n), wxEmptyString, wxT("invalid index in wxOwnerDrawnComboBox::GetString") );
|
|
|
|
if ( !m_popupInterface )
|
|
return m_initChs.Item(n);
|
|
|
|
return GetVListBoxComboPopup()->GetString(n);
|
|
}
|
|
|
|
void wxOwnerDrawnComboBox::SetString(unsigned int n, const wxString& s)
|
|
{
|
|
EnsurePopupControl();
|
|
|
|
wxCHECK_RET( IsValid(n), wxT("invalid index in wxOwnerDrawnComboBox::SetString") );
|
|
|
|
GetVListBoxComboPopup()->SetString(n,s);
|
|
}
|
|
|
|
int wxOwnerDrawnComboBox::FindString(const wxString& s, bool bCase) const
|
|
{
|
|
if ( !m_popupInterface )
|
|
return m_initChs.Index(s, bCase);
|
|
|
|
return GetVListBoxComboPopup()->FindString(s, bCase);
|
|
}
|
|
|
|
void wxOwnerDrawnComboBox::Select(int n)
|
|
{
|
|
EnsurePopupControl();
|
|
|
|
wxCHECK_RET( (n == wxNOT_FOUND) || IsValid(n), wxT("invalid index in wxOwnerDrawnComboBox::Select") );
|
|
|
|
GetVListBoxComboPopup()->SetSelection(n);
|
|
|
|
wxString str;
|
|
if ( n >= 0 )
|
|
str = GetVListBoxComboPopup()->GetString(n);
|
|
|
|
// Refresh text portion in control
|
|
if ( m_text )
|
|
m_text->ChangeValue( str );
|
|
else
|
|
m_valueString = str;
|
|
|
|
Refresh();
|
|
}
|
|
|
|
int wxOwnerDrawnComboBox::GetSelection() const
|
|
{
|
|
if ( !m_popupInterface )
|
|
return m_initChs.Index(m_valueString);
|
|
|
|
return GetVListBoxComboPopup()->GetSelection();
|
|
}
|
|
|
|
void wxOwnerDrawnComboBox::GetSelection(long *from, long *to) const
|
|
{
|
|
wxComboCtrl::GetSelection(from, to);
|
|
}
|
|
|
|
int wxOwnerDrawnComboBox::DoInsertItems(const wxArrayStringsAdapter& items,
|
|
unsigned int pos,
|
|
void **clientData,
|
|
wxClientDataType type)
|
|
{
|
|
EnsurePopupControl();
|
|
|
|
const unsigned int count = items.GetCount();
|
|
|
|
if ( HasFlag(wxCB_SORT) )
|
|
{
|
|
int n = pos;
|
|
|
|
for ( unsigned int i = 0; i < count; ++i )
|
|
{
|
|
n = GetVListBoxComboPopup()->Append(items[i]);
|
|
AssignNewItemClientData(n, clientData, i, type);
|
|
}
|
|
|
|
return n;
|
|
}
|
|
else
|
|
{
|
|
for ( unsigned int i = 0; i < count; ++i, ++pos )
|
|
{
|
|
GetVListBoxComboPopup()->Insert(items[i], pos);
|
|
AssignNewItemClientData(pos, clientData, i, type);
|
|
}
|
|
|
|
return pos - 1;
|
|
}
|
|
}
|
|
|
|
void wxOwnerDrawnComboBox::DoSetItemClientData(unsigned int n, void* clientData)
|
|
{
|
|
EnsurePopupControl();
|
|
|
|
GetVListBoxComboPopup()->SetItemClientData(n, clientData,
|
|
GetClientDataType());
|
|
}
|
|
|
|
void* wxOwnerDrawnComboBox::DoGetItemClientData(unsigned int n) const
|
|
{
|
|
if ( !m_popupInterface )
|
|
return NULL;
|
|
|
|
return GetVListBoxComboPopup()->GetItemClientData(n);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// wxOwnerDrawnComboBox item drawing and measuring default implementations
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void wxOwnerDrawnComboBox::OnDrawItem( wxDC& dc,
|
|
const wxRect& rect,
|
|
int item,
|
|
int flags ) const
|
|
{
|
|
if ( flags & wxODCB_PAINTING_CONTROL )
|
|
{
|
|
wxString text;
|
|
|
|
if ( !ShouldUseHintText() )
|
|
{
|
|
text = GetValue();
|
|
}
|
|
else
|
|
{
|
|
text = GetHint();
|
|
wxColour col = wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT);
|
|
dc.SetTextForeground(col);
|
|
}
|
|
|
|
dc.DrawText( text,
|
|
rect.x + GetMargins().x,
|
|
(rect.height-dc.GetCharHeight())/2 + rect.y );
|
|
}
|
|
else
|
|
{
|
|
dc.DrawText( GetVListBoxComboPopup()->GetString(item), rect.x + 2, rect.y );
|
|
}
|
|
}
|
|
|
|
wxCoord wxOwnerDrawnComboBox::OnMeasureItem( size_t WXUNUSED(item) ) const
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
wxCoord wxOwnerDrawnComboBox::OnMeasureItemWidth( size_t WXUNUSED(item) ) const
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
wxSize wxOwnerDrawnComboBox::DoGetBestSize() const
|
|
{
|
|
if ( GetCount() == 0 )
|
|
return wxComboCtrlBase::DoGetBestSize();
|
|
|
|
wxOwnerDrawnComboBox* odc = const_cast<wxOwnerDrawnComboBox*>(this);
|
|
// TODO: this class may also have GetHightestItemHeight() and
|
|
// GetHightestItem() methods, and so set the whole (edit part + arrow)
|
|
// control's height according with this max height, not only max width.
|
|
return GetSizeFromTextSize(odc->GetWidestItemWidth());
|
|
}
|
|
|
|
void wxOwnerDrawnComboBox::OnDrawBackground(wxDC& dc,
|
|
const wxRect& rect,
|
|
int WXUNUSED(item),
|
|
int flags) const
|
|
{
|
|
// We need only to explicitly draw background for items
|
|
// that should have selected background. Also, call PrepareBackground
|
|
// always when painting the control so that clipping is done properly.
|
|
|
|
if ( (flags & wxODCB_PAINTING_SELECTED) ||
|
|
((flags & wxODCB_PAINTING_CONTROL) && HasFlag(wxCB_READONLY)) )
|
|
{
|
|
int bgFlags = wxCONTROL_SELECTED;
|
|
|
|
if ( !(flags & wxODCB_PAINTING_CONTROL) )
|
|
bgFlags |= wxCONTROL_ISSUBMENU;
|
|
|
|
PrepareBackground(dc, rect, bgFlags);
|
|
}
|
|
}
|
|
|
|
#endif // wxUSE_ODCOMBOBOX
|