1 /////////////////////////////////////////////////////////////////////////////
3 // Purpose: wxOwnerDrawnComboBox, wxVListBoxComboPopup
4 // Author: Jaakko Salli
6 // Created: Apr-30-2006
8 // Copyright: (c) 2005 Jaakko Salli
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
12 // ============================================================================
14 // ============================================================================
16 // ----------------------------------------------------------------------------
18 // ----------------------------------------------------------------------------
20 #include "wx/wxprec.h"
26 #if wxUSE_OWNERDRAWNCOMBOBOX
30 #include "wx/combobox.h"
31 #include "wx/dcclient.h"
32 #include "wx/settings.h"
33 #include "wx/dialog.h"
37 #include "wx/odcombo.h"
40 // ============================================================================
42 // ============================================================================
45 // ----------------------------------------------------------------------------
46 // wxVListBoxComboPopup is a wxVListBox customized to act as a popup control
48 // ----------------------------------------------------------------------------
51 BEGIN_EVENT_TABLE(wxVListBoxComboPopup
, wxVListBox
)
52 EVT_MOTION(wxVListBoxComboPopup::OnMouseMove
)
53 EVT_KEY_DOWN(wxVListBoxComboPopup::OnKey
)
54 EVT_LEFT_UP(wxVListBoxComboPopup::OnLeftClick
)
58 void wxVListBoxComboPopup::Init()
68 m_clientDataItemsType
= wxClientData_None
;
71 bool wxVListBoxComboPopup::Create(wxWindow
* parent
)
73 if ( !wxVListBox::Create(parent
,
77 wxBORDER_SIMPLE
| wxLB_INT_HEIGHT
| wxWANTS_CHARS
) )
80 m_useFont
= m_combo
->GetFont();
82 wxVListBox::SetItemCount(m_strings
.GetCount());
84 // TODO: Move this to SetFont
85 m_itemHeight
= GetCharHeight() + 0;
90 wxVListBoxComboPopup::~wxVListBoxComboPopup()
95 bool wxVListBoxComboPopup::LazyCreate()
97 // NB: There is a bug with wxVListBox that can be avoided by creating
98 // it later (bug causes empty space to be shown if initial selection
99 // is at the end of a list longer than the control can show at once).
103 // paint the control itself
104 void wxVListBoxComboPopup::PaintComboControl( wxDC
& dc
, const wxRect
& rect
)
106 if ( !(m_combo
->GetWindowStyle() & wxODCB_STD_CONTROL_PAINT
) )
108 m_combo
->DrawFocusBackground(dc
,rect
,0);
111 OnDrawItem(dc
,rect
,m_value
,wxCP_PAINTING_CONTROL
);
116 wxComboPopup::PaintComboControl(dc
,rect
);
119 void wxVListBoxComboPopup::OnDrawItem(wxDC
& dc
, const wxRect
& rect
, size_t n
) const
121 // TODO: Maybe this code could be moved to wxVListBox::OnPaint?
122 dc
.SetFont(m_useFont
);
124 // Set correct text colour for selected items
125 if ( wxVListBox::GetSelection() == (int) n
)
126 dc
.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT
) );
128 dc
.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT
) );
130 OnDrawItem(dc
,rect
,(int)n
,0);
133 wxCoord
wxVListBoxComboPopup::OnMeasureItem(size_t WXUNUSED(n
)) const
136 int itemHeight = m_combo->OnMeasureListItem(n);
137 if ( itemHeight < 0 )
138 itemHeight = m_itemHeight;
143 wxCoord
wxVListBoxComboPopup::OnMeasureItemWidth(size_t WXUNUSED(n
)) const
145 //return OnMeasureListItemWidth(n);
149 void wxVListBoxComboPopup::OnDrawBackground(wxDC
& dc
, const wxRect
& rect
, size_t n
) const
151 // we need to render selected and current items differently
154 m_combo
->DrawFocusBackground( dc
, rect
, wxCONTROL_ISSUBMENU
|wxCONTROL_SELECTED
);
156 //else: do nothing for the normal items
159 // This is called from wxVListBoxComboPopup::OnDrawItem, with text colour and font prepared
160 void wxVListBoxComboPopup::OnDrawItem( wxDC
& dc
, const wxRect
& rect
, int item
, int flags
) const
162 if ( flags
& wxCP_PAINTING_CONTROL
)
164 dc
.DrawText( m_combo
->GetValue(),
165 rect
.x
+ m_combo
->GetTextIndent(),
166 (rect
.height
-dc
.GetCharHeight())/2 + rect
.y
);
170 dc
.DrawText( GetString(item
), rect
.x
+ 2, rect
.y
);
174 void wxVListBoxComboPopup::DismissWithEvent()
176 int selection
= wxVListBox::GetSelection();
181 if ( selection
!= wxNOT_FOUND
)
182 valStr
= m_strings
[selection
];
184 valStr
= wxEmptyString
;
188 if ( valStr
!= m_combo
->GetValue() )
189 m_combo
->SetValue(valStr
);
191 SendComboBoxEvent(selection
);
194 void wxVListBoxComboPopup::SendComboBoxEvent( int selection
)
196 wxCommandEvent
evt(wxEVT_COMMAND_COMBOBOX_SELECTED
,m_combo
->GetId());
198 evt
.SetEventObject(m_combo
);
200 evt
.SetInt(selection
);
202 // Set client data, if any
203 if ( selection
>= 0 && (int)m_clientDatas
.GetCount() > selection
)
205 void* clientData
= m_clientDatas
[selection
];
206 if ( m_clientDataItemsType
== wxClientData_Object
)
207 evt
.SetClientObject((wxClientData
*)clientData
);
209 evt
.SetClientData(clientData
);
212 m_combo
->GetEventHandler()->AddPendingEvent(evt
);
215 // returns true if key was consumed
216 bool wxVListBoxComboPopup::HandleKey( int keycode
, bool saturate
)
219 int itemCount
= GetCount();
221 if ( keycode
== WXK_DOWN
|| keycode
== WXK_RIGHT
)
225 else if ( keycode
== WXK_UP
|| keycode
== WXK_LEFT
)
229 else if ( keycode
== WXK_PAGEDOWN
)
233 else if ( keycode
== WXK_PAGEUP
)
238 else if ( keycode == WXK_END )
242 else if ( keycode == WXK_HOME )
252 if ( value
>= itemCount
)
253 value
= itemCount
- 1;
254 else if ( value
< 0 )
259 if ( value
>= itemCount
)
261 else if ( value
< 0 )
265 if ( value
== m_value
)
266 // Even if value was same, don't skip the event
267 // (good for consistency)
273 m_combo
->SetValue(m_strings
[value
]);
275 SendComboBoxEvent(m_value
);
280 void wxVListBoxComboPopup::OnComboDoubleClick()
282 // Cycle on dclick (disable saturation to allow true cycling).
283 if ( !::wxGetKeyState(WXK_SHIFT
) )
284 HandleKey(WXK_DOWN
,false);
286 HandleKey(WXK_UP
,false);
289 void wxVListBoxComboPopup::OnComboKeyEvent( wxKeyEvent
& event
)
291 // Saturated key movement on
292 if ( !HandleKey(event
.GetKeyCode(),true) )
296 void wxVListBoxComboPopup::OnPopup()
298 // *must* set value after size is set (this is because of a vlbox bug)
299 wxVListBox::SetSelection(m_value
);
302 void wxVListBoxComboPopup::OnMouseMove(wxMouseEvent
& event
)
304 // Move selection to cursor if it is inside the popup
305 int itemHere
= GetItemAtPosition(event
.GetPosition());
307 wxVListBox::SetSelection(itemHere
);
312 void wxVListBoxComboPopup::OnLeftClick(wxMouseEvent
& WXUNUSED(event
))
317 void wxVListBoxComboPopup::OnKey(wxKeyEvent
& event
)
319 // Select item if ENTER is pressed
320 if ( event
.GetKeyCode() == WXK_RETURN
|| event
.GetKeyCode() == WXK_NUMPAD_ENTER
)
324 // Hide popup if ESC is pressed
325 else if ( event
.GetKeyCode() == WXK_ESCAPE
)
331 void wxVListBoxComboPopup::CheckWidth( int pos
)
333 wxCoord x
= OnMeasureItemWidth(pos
);
337 if ( !m_useFont
.Ok() )
338 m_useFont
= m_combo
->GetFont();
341 m_combo
->GetTextExtent(m_strings
[pos
], &x
, &y
, 0, 0, &m_useFont
);
345 if ( m_widestWidth
< x
)
351 void wxVListBoxComboPopup::Insert( const wxString
& item
, int pos
)
353 // Need to change selection?
355 if ( !(m_combo
->GetWindowStyle() & wxCB_READONLY
) &&
356 m_combo
->GetValue() == item
)
361 m_strings
.Insert(item
,pos
);
364 wxVListBox::SetItemCount( wxVListBox::GetItemCount()+1 );
370 int wxVListBoxComboPopup::Append(const wxString
& item
)
372 int pos
= (int)m_strings
.GetCount();
374 if ( m_combo
->GetWindowStyle() & wxCB_SORT
)
377 // TODO: Could be optimized with binary search
378 wxArrayString strings
= m_strings
;
381 for ( i
=0; i
<strings
.GetCount(); i
++ )
383 if ( item
.Cmp(strings
.Item(i
)) < 0 )
396 void wxVListBoxComboPopup::Clear()
405 wxVListBox::SetItemCount(0);
408 void wxVListBoxComboPopup::ClearClientDatas()
410 if ( m_clientDataItemsType
== wxClientData_Object
)
413 for ( i
=0; i
<m_clientDatas
.GetCount(); i
++ )
414 delete (wxClientData
*) m_clientDatas
[i
];
417 m_clientDatas
.Empty();
420 void wxVListBoxComboPopup::SetItemClientData( unsigned int n
,
422 wxClientDataType clientDataItemsType
)
424 // It should be sufficient to update this variable only here
425 m_clientDataItemsType
= clientDataItemsType
;
427 m_clientDatas
.SetCount(n
+1,NULL
);
428 m_clientDatas
[n
] = clientData
;
431 void* wxVListBoxComboPopup::GetItemClientData(unsigned int n
) const
433 if ( m_clientDatas
.GetCount() > n
)
434 return m_clientDatas
[n
];
439 void wxVListBoxComboPopup::Delete( unsigned int item
)
441 // Remove client data, if set
442 if ( m_clientDatas
.GetCount() )
444 if ( m_clientDataItemsType
== wxClientData_Object
)
445 delete (wxClientData
*) m_clientDatas
[item
];
447 m_clientDatas
.RemoveAt(item
);
450 m_strings
.RemoveAt(item
);
453 wxVListBox::SetItemCount( wxVListBox::GetItemCount()-1 );
456 int wxVListBoxComboPopup::FindString(const wxString
& s
) const
458 return m_strings
.Index(s
);
461 unsigned int wxVListBoxComboPopup::GetCount() const
463 return m_strings
.GetCount();
466 wxString
wxVListBoxComboPopup::GetString( int item
) const
468 return m_strings
[item
];
471 void wxVListBoxComboPopup::SetString( int item
, const wxString
& str
)
473 m_strings
[item
] = str
;
476 wxString
wxVListBoxComboPopup::GetStringValue() const
479 return m_strings
[m_value
];
480 return wxEmptyString
;
483 void wxVListBoxComboPopup::SetSelection( int item
)
485 // This seems to be necessary (2.5.3 w/ MingW atleast)
486 if ( item
< -1 || item
>= (int)m_strings
.GetCount() )
492 wxVListBox::SetSelection(item
);
495 int wxVListBoxComboPopup::GetSelection() const
500 void wxVListBoxComboPopup::SetStringValue( const wxString
& value
)
502 int index
= m_strings
.Index(value
);
506 if ( index
>= -1 && index
< (int)wxVListBox::GetItemCount() )
507 wxVListBox::SetSelection(index
);
510 wxSize
wxVListBoxComboPopup::GetAdjustedSize( int minWidth
, int prefHeight
, int maxHeight
)
514 if ( m_strings
.GetCount() )
516 if ( prefHeight
> 0 )
519 if ( height
> maxHeight
)
522 int totalHeight
= GetTotalHeight(); // + 3;
523 if ( height
>= totalHeight
)
525 height
= totalHeight
;
529 // Adjust height to a multiple of the height of the first item
530 // NB: Calculations that take variable height into account
532 int fih
= GetLineHeight(0);
533 int shown
= height
/fih
;
534 height
= shown
* fih
;
540 // Take scrollbar into account in width calculations
541 int widestWidth
= m_widestWidth
+ wxSystemSettings::GetMetric(wxSYS_VSCROLL_X
);
542 return wxSize(minWidth
> widestWidth
? minWidth
: widestWidth
,
546 //void wxVListBoxComboPopup::Populate( int n, const wxString choices[] )
547 void wxVListBoxComboPopup::Populate( const wxArrayString
& choices
)
551 int n
= choices
.GetCount();
553 for ( i
=0; i
<n
; i
++ )
555 const wxString
& item
= choices
.Item(i
);
561 wxVListBox::SetItemCount(n
);
563 // Sort the initial choices
564 if ( m_combo
->GetWindowStyle() & wxCB_SORT
)
567 // Find initial selection
568 wxString strValue
= m_combo
->GetValue();
569 if ( strValue
.Length() )
570 m_value
= m_strings
.Index(strValue
);
573 // ----------------------------------------------------------------------------
574 // wxOwnerDrawnComboBox
575 // ----------------------------------------------------------------------------
578 BEGIN_EVENT_TABLE(wxOwnerDrawnComboBox
, wxComboControl
)
582 IMPLEMENT_DYNAMIC_CLASS2(wxOwnerDrawnComboBox
, wxComboControl
, wxControlWithItems
)
584 void wxOwnerDrawnComboBox::Init()
586 m_popupInterface
= NULL
;
589 bool wxOwnerDrawnComboBox::Create(wxWindow
*parent
,
591 const wxString
& value
,
595 const wxValidator
& validator
,
596 const wxString
& name
)
598 return wxComboControl::Create(parent
,id
,value
,pos
,size
,style
,validator
,name
);
601 wxOwnerDrawnComboBox::wxOwnerDrawnComboBox(wxWindow
*parent
,
603 const wxString
& value
,
606 const wxArrayString
& choices
,
608 const wxValidator
& validator
,
609 const wxString
& name
)
614 Create(parent
,id
,value
,pos
,size
,choices
,style
, validator
, name
);
617 bool wxOwnerDrawnComboBox::Create(wxWindow
*parent
,
619 const wxString
& value
,
622 const wxArrayString
& choices
,
624 const wxValidator
& validator
,
625 const wxString
& name
)
628 //wxCArrayString chs(choices);
630 //return Create(parent, id, value, pos, size, chs.GetCount(),
631 // chs.GetStrings(), style, validator, name);
632 return Create(parent
, id
, value
, pos
, size
, 0,
633 NULL
, style
, validator
, name
);
636 bool wxOwnerDrawnComboBox::Create(wxWindow
*parent
,
638 const wxString
& value
,
642 const wxString choices
[],
644 const wxValidator
& validator
,
645 const wxString
& name
)
648 if ( !Create(parent
, id
, value
, pos
, size
, style
,
655 for ( i
=0; i
<n
; i
++ )
656 m_initChs
.Add(choices
[i
]);
661 wxOwnerDrawnComboBox::~wxOwnerDrawnComboBox()
663 if ( m_popupInterface
)
664 m_popupInterface
->ClearClientDatas();
667 void wxOwnerDrawnComboBox::SetPopupControl( wxComboPopup
* popup
)
671 popup
= new wxVListBoxComboPopup();
674 wxComboControl::SetPopupControl(popup
);
677 m_popupInterface
= (wxVListBoxComboPopup
*) popup
;
679 // Add initial choices to the wxVListBox
680 if ( !m_popupInterface
->GetCount() )
682 //m_popupInterface->Populate(m_initChs.GetCount(),m_initChs.GetStrings());
683 m_popupInterface
->Populate(m_initChs
);
688 // ----------------------------------------------------------------------------
689 // wxOwnerDrawnComboBox item manipulation methods
690 // ----------------------------------------------------------------------------
692 void wxOwnerDrawnComboBox::Clear()
694 EnsurePopupControl();
696 m_popupInterface
->Clear();
698 GetTextCtrl()->SetValue(wxEmptyString
);
701 void wxOwnerDrawnComboBox::Delete(unsigned int n
)
703 wxCHECK_RET( (n
>= 0) && (n
< GetCount()), _T("invalid index in wxOwnerDrawnComboBox::Delete") );
705 if ( GetSelection() == (int) n
)
706 SetValue(wxEmptyString
);
708 m_popupInterface
->Delete(n
);
711 unsigned int wxOwnerDrawnComboBox::GetCount() const
713 wxASSERT_MSG( m_popupInterface
, wxT("no popup interface") );
714 return m_popupInterface
->GetCount();
717 wxString
wxOwnerDrawnComboBox::GetString(unsigned int n
) const
719 wxCHECK_MSG( (n
>= 0) && (n
< GetCount()), wxEmptyString
, _T("invalid index in wxOwnerDrawnComboBox::GetString") );
720 return m_popupInterface
->GetString(n
);
723 void wxOwnerDrawnComboBox::SetString(unsigned int n
, const wxString
& s
)
725 wxCHECK_RET( (n
>= 0) && (n
< GetCount()), _T("invalid index in wxOwnerDrawnComboBox::SetString") );
726 m_popupInterface
->SetString(n
,s
);
729 int wxOwnerDrawnComboBox::FindString(const wxString
& s
) const
731 wxASSERT_MSG( m_popupInterface
, wxT("no popup interface") );
732 return m_popupInterface
->FindString(s
);
735 void wxOwnerDrawnComboBox::Select(int n
)
737 wxCHECK_RET( (n
>= -1) && (n
< (int)GetCount()), _T("invalid index in wxOwnerDrawnComboBox::Select") );
738 EnsurePopupControl();
740 m_popupInterface
->SetSelection(n
);
744 str
= m_popupInterface
->GetString(n
);
746 // Refresh text portion in control
748 m_text
->SetValue( str
);
755 int wxOwnerDrawnComboBox::GetSelection() const
757 wxASSERT_MSG( m_popupInterface
, wxT("no popup interface") );
758 return m_popupInterface
->GetSelection();
761 int wxOwnerDrawnComboBox::DoAppend(const wxString
& item
)
763 EnsurePopupControl();
764 wxASSERT(m_popupInterface
);
765 return m_popupInterface
->Append(item
);
768 int wxOwnerDrawnComboBox::DoInsert(const wxString
& item
, unsigned int pos
)
770 wxCHECK_MSG(!(GetWindowStyle() & wxCB_SORT
), -1, wxT("can't insert into sorted list"));
771 wxCHECK_MSG((pos
>=0) && (pos
<=GetCount()), -1, wxT("invalid index"));
773 EnsurePopupControl();
774 m_popupInterface
->Insert(item
,pos
);
779 void wxOwnerDrawnComboBox::DoSetItemClientData(unsigned int n
, void* clientData
)
781 EnsurePopupControl();
782 m_popupInterface
->SetItemClientData(n
,clientData
,m_clientDataItemsType
);
785 void* wxOwnerDrawnComboBox::DoGetItemClientData(unsigned int n
) const
787 wxASSERT_MSG( m_popupInterface
, wxT("no popup interface") );
788 return m_popupInterface
->GetItemClientData(n
);
791 void wxOwnerDrawnComboBox::DoSetItemClientObject(unsigned int n
, wxClientData
* clientData
)
793 DoSetItemClientData(n
, (void*) clientData
);
796 wxClientData
* wxOwnerDrawnComboBox::DoGetItemClientObject(unsigned int n
) const
798 return (wxClientData
*) DoGetItemClientData(n
);
801 #endif // wxUSE_OWNERDRAWNCOMBOBOX