1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/generic/odcombo.cpp
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"
28 #include "wx/odcombo.h"
32 #include "wx/combobox.h"
33 #include "wx/dcclient.h"
34 #include "wx/settings.h"
35 #include "wx/dialog.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()
66 m_clientDataItemsType
= wxClientData_None
;
69 bool wxVListBoxComboPopup::Create(wxWindow
* parent
)
71 if ( !wxVListBox::Create(parent
,
75 wxBORDER_SIMPLE
| wxLB_INT_HEIGHT
| wxWANTS_CHARS
) )
78 m_useFont
= m_combo
->GetFont();
80 wxVListBox::SetItemCount(m_strings
.GetCount());
82 // TODO: Move this to SetFont
83 m_itemHeight
= GetCharHeight() + 0;
88 wxVListBoxComboPopup::~wxVListBoxComboPopup()
93 bool wxVListBoxComboPopup::LazyCreate()
95 // NB: There is a bug with wxVListBox that can be avoided by creating
96 // it later (bug causes empty space to be shown if initial selection
97 // is at the end of a list longer than the control can show at once).
101 // paint the control itself
102 void wxVListBoxComboPopup::PaintComboControl( wxDC
& dc
, const wxRect
& rect
)
104 if ( !(m_combo
->GetWindowStyle() & wxODCB_STD_CONTROL_PAINT
) )
106 OnDrawBg(dc
,rect
,m_value
,wxODCB_PAINTING_CONTROL
);
109 OnDrawItem(dc
,rect
,m_value
,wxODCB_PAINTING_CONTROL
);
114 wxComboPopup::PaintComboControl(dc
,rect
);
117 void wxVListBoxComboPopup::OnDrawItem(wxDC
& dc
, const wxRect
& rect
, size_t n
) const
119 // TODO: Maybe this code could be moved to wxVListBox::OnPaint?
120 dc
.SetFont(m_useFont
);
122 // Set correct text colour for selected items
123 if ( wxVListBox::GetSelection() == (int) n
)
124 dc
.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT
) );
126 dc
.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT
) );
128 OnDrawItem(dc
,rect
,(int)n
,0);
131 wxCoord
wxVListBoxComboPopup::OnMeasureItem(size_t n
) const
133 wxOwnerDrawnComboBox
* combo
= (wxOwnerDrawnComboBox
*) m_combo
;
135 wxASSERT_MSG( combo
->IsKindOf(CLASSINFO(wxOwnerDrawnComboBox
)),
136 wxT("you must subclass wxVListBoxComboPopup for drawing and measuring methods") );
138 wxCoord h
= combo
->OnMeasureItem(n
);
144 wxCoord
wxVListBoxComboPopup::OnMeasureItemWidth(size_t n
) const
146 wxOwnerDrawnComboBox
* combo
= (wxOwnerDrawnComboBox
*) m_combo
;
148 wxASSERT_MSG( combo
->IsKindOf(CLASSINFO(wxOwnerDrawnComboBox
)),
149 wxT("you must subclass wxVListBoxComboPopup for drawing and measuring methods") );
151 return combo
->OnMeasureItemWidth(n
);
154 void wxVListBoxComboPopup::OnDrawBg( wxDC
& dc
,
159 wxOwnerDrawnComboBox
* combo
= (wxOwnerDrawnComboBox
*) m_combo
;
161 wxASSERT_MSG( combo
->IsKindOf(CLASSINFO(wxOwnerDrawnComboBox
)),
162 wxT("you must subclass wxVListBoxComboPopup for drawing and measuring methods") );
164 combo
->OnDrawBackground(dc
,rect
,item
,flags
);
167 void wxVListBoxComboPopup::OnDrawBackground(wxDC
& dc
, const wxRect
& rect
, size_t n
) const
169 OnDrawBg(dc
,rect
,(int)n
,0);
172 // This is called from wxVListBoxComboPopup::OnDrawItem, with text colour and font prepared
173 void wxVListBoxComboPopup::OnDrawItem( wxDC
& dc
, const wxRect
& rect
, int item
, int flags
) const
175 wxOwnerDrawnComboBox
* combo
= (wxOwnerDrawnComboBox
*) m_combo
;
177 wxASSERT_MSG( combo
->IsKindOf(CLASSINFO(wxOwnerDrawnComboBox
)),
178 wxT("you must subclass wxVListBoxComboPopup for drawing and measuring methods") );
180 combo
->OnDrawItem(dc
,rect
,item
,flags
);
183 void wxVListBoxComboPopup::DismissWithEvent()
185 int selection
= wxVListBox::GetSelection();
190 if ( selection
!= wxNOT_FOUND
)
191 valStr
= m_strings
[selection
];
193 valStr
= wxEmptyString
;
197 if ( valStr
!= m_combo
->GetValue() )
198 m_combo
->SetValue(valStr
);
200 SendComboBoxEvent(selection
);
203 void wxVListBoxComboPopup::SendComboBoxEvent( int selection
)
205 wxCommandEvent
evt(wxEVT_COMMAND_COMBOBOX_SELECTED
,m_combo
->GetId());
207 evt
.SetEventObject(m_combo
);
209 evt
.SetInt(selection
);
211 // Set client data, if any
212 if ( selection
>= 0 && (int)m_clientDatas
.GetCount() > selection
)
214 void* clientData
= m_clientDatas
[selection
];
215 if ( m_clientDataItemsType
== wxClientData_Object
)
216 evt
.SetClientObject((wxClientData
*)clientData
);
218 evt
.SetClientData(clientData
);
221 m_combo
->GetEventHandler()->AddPendingEvent(evt
);
224 // returns true if key was consumed
225 bool wxVListBoxComboPopup::HandleKey( int keycode
, bool saturate
)
228 int itemCount
= GetCount();
230 if ( keycode
== WXK_DOWN
|| keycode
== WXK_RIGHT
)
234 else if ( keycode
== WXK_UP
|| keycode
== WXK_LEFT
)
238 else if ( keycode
== WXK_PAGEDOWN
)
242 else if ( keycode
== WXK_PAGEUP
)
251 if ( value
>= itemCount
)
252 value
= itemCount
- 1;
253 else if ( value
< 0 )
258 if ( value
>= itemCount
)
260 else if ( value
< 0 )
264 if ( value
== m_value
)
265 // Even if value was same, don't skip the event
266 // (good for consistency)
272 m_combo
->SetValue(m_strings
[value
]);
274 SendComboBoxEvent(m_value
);
279 void wxVListBoxComboPopup::OnComboDoubleClick()
281 // Cycle on dclick (disable saturation to allow true cycling).
282 if ( !::wxGetKeyState(WXK_SHIFT
) )
283 HandleKey(WXK_DOWN
,false);
285 HandleKey(WXK_UP
,false);
288 void wxVListBoxComboPopup::OnComboKeyEvent( wxKeyEvent
& event
)
290 // Saturated key movement on
291 if ( !HandleKey(event
.GetKeyCode(),true) )
295 void wxVListBoxComboPopup::OnPopup()
297 // *must* set value after size is set (this is because of a vlbox bug)
298 wxVListBox::SetSelection(m_value
);
301 void wxVListBoxComboPopup::OnMouseMove(wxMouseEvent
& event
)
303 // Move selection to cursor if it is inside the popup
304 int itemHere
= GetItemAtPosition(event
.GetPosition());
306 wxVListBox::SetSelection(itemHere
);
311 void wxVListBoxComboPopup::OnLeftClick(wxMouseEvent
& WXUNUSED(event
))
316 void wxVListBoxComboPopup::OnKey(wxKeyEvent
& event
)
318 // Select item if ENTER is pressed
319 if ( event
.GetKeyCode() == WXK_RETURN
|| event
.GetKeyCode() == WXK_NUMPAD_ENTER
)
323 // Hide popup if ESC is pressed
324 else if ( event
.GetKeyCode() == WXK_ESCAPE
)
330 void wxVListBoxComboPopup::CheckWidth( int pos
)
332 wxCoord x
= OnMeasureItemWidth(pos
);
336 if ( !m_useFont
.Ok() )
337 m_useFont
= m_combo
->GetFont();
340 m_combo
->GetTextExtent(m_strings
[pos
], &x
, &y
, 0, 0, &m_useFont
);
344 if ( m_widestWidth
< x
)
350 void wxVListBoxComboPopup::Insert( const wxString
& item
, int pos
)
352 // Need to change selection?
354 if ( !(m_combo
->GetWindowStyle() & wxCB_READONLY
) &&
355 m_combo
->GetValue() == item
)
360 m_strings
.Insert(item
,pos
);
363 wxVListBox::SetItemCount( wxVListBox::GetItemCount()+1 );
369 int wxVListBoxComboPopup::Append(const wxString
& item
)
371 int pos
= (int)m_strings
.GetCount();
373 if ( m_combo
->GetWindowStyle() & wxCB_SORT
)
376 // TODO: Could be optimized with binary search
377 wxArrayString strings
= m_strings
;
380 for ( i
=0; i
<strings
.GetCount(); i
++ )
382 if ( item
.Cmp(strings
.Item(i
)) < 0 )
395 void wxVListBoxComboPopup::Clear()
403 m_value
= wxNOT_FOUND
;
406 wxVListBox::SetItemCount(0);
409 void wxVListBoxComboPopup::ClearClientDatas()
411 if ( m_clientDataItemsType
== wxClientData_Object
)
414 for ( i
=0; i
<m_clientDatas
.GetCount(); i
++ )
415 delete (wxClientData
*) m_clientDatas
[i
];
418 m_clientDatas
.Empty();
421 void wxVListBoxComboPopup::SetItemClientData( unsigned int n
,
423 wxClientDataType clientDataItemsType
)
425 // It should be sufficient to update this variable only here
426 m_clientDataItemsType
= clientDataItemsType
;
428 m_clientDatas
.SetCount(n
+1,NULL
);
429 m_clientDatas
[n
] = clientData
;
432 void* wxVListBoxComboPopup::GetItemClientData(unsigned int n
) const
434 if ( m_clientDatas
.GetCount() > n
)
435 return m_clientDatas
[n
];
440 void wxVListBoxComboPopup::Delete( unsigned int item
)
442 // Remove client data, if set
443 if ( m_clientDatas
.GetCount() )
445 if ( m_clientDataItemsType
== wxClientData_Object
)
446 delete (wxClientData
*) m_clientDatas
[item
];
448 m_clientDatas
.RemoveAt(item
);
451 m_strings
.RemoveAt(item
);
454 wxVListBox::SetItemCount( wxVListBox::GetItemCount()-1 );
457 int wxVListBoxComboPopup::FindString(const wxString
& s
, bool bCase
) const
459 return m_strings
.Index(s
, bCase
);
462 unsigned int wxVListBoxComboPopup::GetCount() const
464 return m_strings
.GetCount();
467 wxString
wxVListBoxComboPopup::GetString( int item
) const
469 return m_strings
[item
];
472 void wxVListBoxComboPopup::SetString( int item
, const wxString
& str
)
474 m_strings
[item
] = str
;
477 wxString
wxVListBoxComboPopup::GetStringValue() const
480 return m_strings
[m_value
];
481 return wxEmptyString
;
484 void wxVListBoxComboPopup::SetSelection( int item
)
486 wxCHECK_RET( item
== wxNOT_FOUND
|| ((unsigned int)item
< GetCount()),
487 wxT("invalid index in wxVListBoxComboPopup::SetSelection") );
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
, wxComboCtrl
)
582 IMPLEMENT_DYNAMIC_CLASS2(wxOwnerDrawnComboBox
, wxComboCtrl
, 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 wxComboCtrl::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::DoSetPopupControl(wxComboPopup
* popup
)
671 popup
= new wxVListBoxComboPopup();
674 wxComboCtrl::DoSetPopupControl(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 SetValue(wxEmptyString
);
701 void wxOwnerDrawnComboBox::Delete(unsigned int n
)
703 wxCHECK_RET( IsValid(n
), _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 if ( !m_popupInterface
)
714 return m_initChs
.GetCount();
716 return m_popupInterface
->GetCount();
719 wxString
wxOwnerDrawnComboBox::GetString(unsigned int n
) const
721 wxCHECK_MSG( IsValid(n
), wxEmptyString
, _T("invalid index in wxOwnerDrawnComboBox::GetString") );
723 if ( !m_popupInterface
)
724 return m_initChs
.Item(n
);
726 return m_popupInterface
->GetString(n
);
729 void wxOwnerDrawnComboBox::SetString(unsigned int n
, const wxString
& s
)
731 EnsurePopupControl();
733 wxCHECK_RET( IsValid(n
), _T("invalid index in wxOwnerDrawnComboBox::SetString") );
735 m_popupInterface
->SetString(n
,s
);
738 int wxOwnerDrawnComboBox::FindString(const wxString
& s
, bool bCase
) const
740 if ( !m_popupInterface
)
741 return m_initChs
.Index(s
, bCase
);
743 return m_popupInterface
->FindString(s
, bCase
);
746 void wxOwnerDrawnComboBox::Select(int n
)
748 EnsurePopupControl();
750 wxCHECK_RET( (n
== wxNOT_FOUND
) || IsValid(n
), _T("invalid index in wxOwnerDrawnComboBox::Select") );
752 m_popupInterface
->SetSelection(n
);
756 str
= m_popupInterface
->GetString(n
);
758 // Refresh text portion in control
760 m_text
->SetValue( str
);
767 int wxOwnerDrawnComboBox::GetSelection() const
769 if ( !m_popupInterface
)
770 return m_initChs
.Index(m_valueString
);
772 return m_popupInterface
->GetSelection();
775 int wxOwnerDrawnComboBox::DoAppend(const wxString
& item
)
777 EnsurePopupControl();
778 wxASSERT(m_popupInterface
);
779 return m_popupInterface
->Append(item
);
782 int wxOwnerDrawnComboBox::DoInsert(const wxString
& item
, unsigned int pos
)
784 EnsurePopupControl();
786 wxCHECK_MSG(!(GetWindowStyle() & wxCB_SORT
), -1, wxT("can't insert into sorted list"));
787 wxCHECK_MSG(IsValidInsert(pos
), -1, wxT("invalid index"));
789 m_popupInterface
->Insert(item
,pos
);
794 void wxOwnerDrawnComboBox::DoSetItemClientData(unsigned int n
, void* clientData
)
796 EnsurePopupControl();
797 m_popupInterface
->SetItemClientData(n
,clientData
,m_clientDataItemsType
);
800 void* wxOwnerDrawnComboBox::DoGetItemClientData(unsigned int n
) const
802 if ( !m_popupInterface
)
805 return m_popupInterface
->GetItemClientData(n
);
808 void wxOwnerDrawnComboBox::DoSetItemClientObject(unsigned int n
, wxClientData
* clientData
)
810 DoSetItemClientData(n
, (void*) clientData
);
813 wxClientData
* wxOwnerDrawnComboBox::DoGetItemClientObject(unsigned int n
) const
815 return (wxClientData
*) DoGetItemClientData(n
);
818 // ----------------------------------------------------------------------------
819 // wxOwnerDrawnComboBox item drawing and measuring default implementations
820 // ----------------------------------------------------------------------------
822 void wxOwnerDrawnComboBox::OnDrawItem( wxDC
& dc
,
827 if ( flags
& wxODCB_PAINTING_CONTROL
)
829 dc
.DrawText( GetValue(),
830 rect
.x
+ GetTextIndent(),
831 (rect
.height
-dc
.GetCharHeight())/2 + rect
.y
);
835 dc
.DrawText( m_popupInterface
->GetString(item
), rect
.x
+ 2, rect
.y
);
839 wxCoord
wxOwnerDrawnComboBox::OnMeasureItem( size_t WXUNUSED(item
) ) const
844 wxCoord
wxOwnerDrawnComboBox::OnMeasureItemWidth( size_t WXUNUSED(item
) ) const
849 void wxOwnerDrawnComboBox::OnDrawBackground(wxDC
& dc
, const wxRect
& rect
, int item
, int flags
) const
851 // we need to render selected and current items differently
852 if ( m_popupInterface
->IsCurrent((size_t)item
) )
854 DrawFocusBackground(dc
,
856 (flags
&wxODCB_PAINTING_CONTROL
?0:wxCONTROL_ISSUBMENU
) |
859 //else: do nothing for the normal items
862 #endif // wxUSE_ODCOMBOBOX