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()
62 m_widthsDirty
= false;
67 m_clientDataItemsType
= wxClientData_None
;
70 bool wxVListBoxComboPopup
::Create(wxWindow
* parent
)
72 if ( !wxVListBox
::Create(parent
,
76 wxBORDER_SIMPLE
| wxLB_INT_HEIGHT
| wxWANTS_CHARS
) )
79 m_useFont
= m_combo
->GetFont();
81 wxVListBox
::SetItemCount(m_strings
.GetCount());
83 // TODO: Move this to SetFont
84 m_itemHeight
= GetCharHeight() + 0;
89 wxVListBoxComboPopup
::~wxVListBoxComboPopup()
94 bool wxVListBoxComboPopup
::LazyCreate()
96 // NB: There is a bug with wxVListBox that can be avoided by creating
97 // it later (bug causes empty space to be shown if initial selection
98 // is at the end of a list longer than the control can show at once).
102 // paint the control itself
103 void wxVListBoxComboPopup
::PaintComboControl( wxDC
& dc
, const wxRect
& rect
)
105 if ( !(m_combo
->GetWindowStyle() & wxODCB_STD_CONTROL_PAINT
) )
107 OnDrawBg(dc
,rect
,m_value
,wxODCB_PAINTING_CONTROL
);
110 OnDrawItem(dc
,rect
,m_value
,wxODCB_PAINTING_CONTROL
);
115 wxComboPopup
::PaintComboControl(dc
,rect
);
118 void wxVListBoxComboPopup
::OnDrawItem(wxDC
& dc
, const wxRect
& rect
, size_t n
) const
120 // TODO: Maybe this code could be moved to wxVListBox::OnPaint?
121 dc
.SetFont(m_useFont
);
123 // Set correct text colour for selected items
124 if ( wxVListBox
::GetSelection() == (int) n
)
125 dc
.SetTextForeground( wxSystemSettings
::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT
) );
127 dc
.SetTextForeground( wxSystemSettings
::GetColour(wxSYS_COLOUR_WINDOWTEXT
) );
129 OnDrawItem(dc
,rect
,(int)n
,0);
132 wxCoord wxVListBoxComboPopup
::OnMeasureItem(size_t n
) const
134 wxOwnerDrawnComboBox
* combo
= (wxOwnerDrawnComboBox
*) m_combo
;
136 wxASSERT_MSG( combo
->IsKindOf(CLASSINFO(wxOwnerDrawnComboBox
)),
137 wxT("you must subclass wxVListBoxComboPopup for drawing and measuring methods") );
139 wxCoord h
= combo
->OnMeasureItem(n
);
145 wxCoord wxVListBoxComboPopup
::OnMeasureItemWidth(size_t n
) const
147 wxOwnerDrawnComboBox
* combo
= (wxOwnerDrawnComboBox
*) m_combo
;
149 wxASSERT_MSG( combo
->IsKindOf(CLASSINFO(wxOwnerDrawnComboBox
)),
150 wxT("you must subclass wxVListBoxComboPopup for drawing and measuring methods") );
152 return combo
->OnMeasureItemWidth(n
);
155 void wxVListBoxComboPopup
::OnDrawBg( wxDC
& dc
,
160 wxOwnerDrawnComboBox
* combo
= (wxOwnerDrawnComboBox
*) m_combo
;
162 wxASSERT_MSG( combo
->IsKindOf(CLASSINFO(wxOwnerDrawnComboBox
)),
163 wxT("you must subclass wxVListBoxComboPopup for drawing and measuring methods") );
165 combo
->OnDrawBackground(dc
,rect
,item
,flags
);
168 void wxVListBoxComboPopup
::OnDrawBackground(wxDC
& dc
, const wxRect
& rect
, size_t n
) const
170 OnDrawBg(dc
,rect
,(int)n
,0);
173 // This is called from wxVListBoxComboPopup::OnDrawItem, with text colour and font prepared
174 void wxVListBoxComboPopup
::OnDrawItem( wxDC
& dc
, const wxRect
& rect
, int item
, int flags
) const
176 wxOwnerDrawnComboBox
* combo
= (wxOwnerDrawnComboBox
*) m_combo
;
178 wxASSERT_MSG( combo
->IsKindOf(CLASSINFO(wxOwnerDrawnComboBox
)),
179 wxT("you must subclass wxVListBoxComboPopup for drawing and measuring methods") );
181 combo
->OnDrawItem(dc
,rect
,item
,flags
);
184 void wxVListBoxComboPopup
::DismissWithEvent()
186 int selection
= wxVListBox
::GetSelection();
191 if ( selection
!= wxNOT_FOUND
)
192 valStr
= m_strings
[selection
];
194 valStr
= wxEmptyString
;
198 if ( valStr
!= m_combo
->GetValue() )
199 m_combo
->SetValue(valStr
);
201 SendComboBoxEvent(selection
);
204 void wxVListBoxComboPopup
::SendComboBoxEvent( int selection
)
206 wxCommandEvent
evt(wxEVT_COMMAND_COMBOBOX_SELECTED
,m_combo
->GetId());
208 evt
.SetEventObject(m_combo
);
210 evt
.SetInt(selection
);
212 // Set client data, if any
213 if ( selection
>= 0 && (int)m_clientDatas
.GetCount() > selection
)
215 void* clientData
= m_clientDatas
[selection
];
216 if ( m_clientDataItemsType
== wxClientData_Object
)
217 evt
.SetClientObject((wxClientData
*)clientData
);
219 evt
.SetClientData(clientData
);
222 m_combo
->GetEventHandler()->AddPendingEvent(evt
);
225 // returns true if key was consumed
226 bool wxVListBoxComboPopup
::HandleKey( int keycode
, bool saturate
)
229 int itemCount
= GetCount();
231 if ( keycode
== WXK_DOWN
|| keycode
== WXK_RIGHT
)
235 else if ( keycode
== WXK_UP
|| keycode
== WXK_LEFT
)
239 else if ( keycode
== WXK_PAGEDOWN
)
243 else if ( keycode
== WXK_PAGEUP
)
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
)
306 // Move selection to cursor if it is inside the popup
308 int y
= event
.GetPosition().y
;
309 int fromBottom
= GetClientSize().y
- y
;
311 // Since in any case we need to find out if the last item is only
312 // partially visible, we might just as well replicate the HitTest
314 const size_t lineMax
= GetVisibleEnd();
315 for ( size_t line
= GetVisibleBegin(); line
< lineMax
; line
++ )
317 y
-= OnGetLineHeight(line
);
320 // Only change selection if item is fully visible
321 if ( (y
+ fromBottom
) >= 0 )
323 wxVListBox
::SetSelection((int)line
);
330 void wxVListBoxComboPopup
::OnLeftClick(wxMouseEvent
& WXUNUSED(event
))
335 void wxVListBoxComboPopup
::OnKey(wxKeyEvent
& event
)
337 // Select item if ENTER is pressed
338 if ( event
.GetKeyCode() == WXK_RETURN
|| event
.GetKeyCode() == WXK_NUMPAD_ENTER
)
342 // Hide popup if ESC is pressed
343 else if ( event
.GetKeyCode() == WXK_ESCAPE
)
349 void wxVListBoxComboPopup
::Insert( const wxString
& item
, int pos
)
351 // Need to change selection?
353 if ( !(m_combo
->GetWindowStyle() & wxCB_READONLY
) &&
354 m_combo
->GetValue() == item
)
359 m_strings
.Insert(item
,pos
);
360 m_widths
.Insert(-1,pos
);
361 m_widthsDirty
= true;
364 wxVListBox
::SetItemCount( wxVListBox
::GetItemCount()+1 );
367 int wxVListBoxComboPopup
::Append(const wxString
& item
)
369 int pos
= (int)m_strings
.GetCount();
371 if ( m_combo
->GetWindowStyle() & wxCB_SORT
)
374 // TODO: Could be optimized with binary search
375 wxArrayString strings
= m_strings
;
378 for ( i
=0; i
<strings
.GetCount(); i
++ )
380 if ( item
.Cmp(strings
.Item(i
)) < 0 )
393 void wxVListBoxComboPopup
::Clear()
405 m_value
= wxNOT_FOUND
;
408 wxVListBox
::SetItemCount(0);
411 void wxVListBoxComboPopup
::ClearClientDatas()
413 if ( m_clientDataItemsType
== wxClientData_Object
)
416 for ( i
=0; i
<m_clientDatas
.GetCount(); i
++ )
417 delete (wxClientData
*) m_clientDatas
[i
];
420 m_clientDatas
.Empty();
423 void wxVListBoxComboPopup
::SetItemClientData( unsigned int n
,
425 wxClientDataType clientDataItemsType
)
427 // It should be sufficient to update this variable only here
428 m_clientDataItemsType
= clientDataItemsType
;
430 m_clientDatas
.SetCount(n
+1,NULL
);
431 m_clientDatas
[n
] = clientData
;
436 void* wxVListBoxComboPopup
::GetItemClientData(unsigned int n
) const
438 if ( m_clientDatas
.GetCount() > n
)
439 return m_clientDatas
[n
];
444 void wxVListBoxComboPopup
::Delete( unsigned int item
)
446 // Remove client data, if set
447 if ( m_clientDatas
.GetCount() )
449 if ( m_clientDataItemsType
== wxClientData_Object
)
450 delete (wxClientData
*) m_clientDatas
[item
];
452 m_clientDatas
.RemoveAt(item
);
455 m_strings
.RemoveAt(item
);
456 m_widths
.RemoveAt(item
);
458 if ( (int)item
== m_widestItem
)
462 wxVListBox
::SetItemCount( wxVListBox
::GetItemCount()-1 );
465 int wxVListBoxComboPopup
::FindString(const wxString
& s
, bool bCase
) const
467 return m_strings
.Index(s
, bCase
);
470 unsigned int wxVListBoxComboPopup
::GetCount() const
472 return m_strings
.GetCount();
475 wxString wxVListBoxComboPopup
::GetString( int item
) const
477 return m_strings
[item
];
480 void wxVListBoxComboPopup
::SetString( int item
, const wxString
& str
)
482 m_strings
[item
] = str
;
483 ItemWidthChanged(item
);
486 wxString wxVListBoxComboPopup
::GetStringValue() const
489 return m_strings
[m_value
];
490 return wxEmptyString
;
493 void wxVListBoxComboPopup
::SetSelection( int item
)
495 wxCHECK_RET( item
== wxNOT_FOUND
|| ((unsigned int)item
< GetCount()),
496 wxT("invalid index in wxVListBoxComboPopup::SetSelection") );
501 wxVListBox
::SetSelection(item
);
504 int wxVListBoxComboPopup
::GetSelection() const
509 void wxVListBoxComboPopup
::SetStringValue( const wxString
& value
)
511 int index
= m_strings
.Index(value
);
515 if ( index
>= -1 && index
< (int)wxVListBox
::GetItemCount() )
516 wxVListBox
::SetSelection(index
);
519 wxSize wxVListBoxComboPopup
::GetAdjustedSize( int minWidth
, int prefHeight
, int maxHeight
)
523 if ( m_strings
.GetCount() )
525 if ( prefHeight
> 0 )
528 if ( height
> maxHeight
)
531 int totalHeight
= GetTotalHeight(); // + 3;
532 if ( height
>= totalHeight
)
534 height
= totalHeight
;
538 // Adjust height to a multiple of the height of the first item
539 // NB: Calculations that take variable height into account
541 int fih
= GetLineHeight(0);
542 int shown
= height
/fih
;
543 height
= shown
* fih
;
549 bool doFindWidest
= m_findWidest
;
551 // Measure items with dirty width.
555 unsigned int n
= m_widths
.GetCount();
556 int dirtyHandled
= 0;
557 wxArrayInt
& widths
= m_widths
;
559 // I think using wxDC::GetTextExtent is faster than
560 // wxWindow::GetTextExtent (assuming same dc is used
561 // for all calls, as we do here).
562 wxClientDC
dc(m_combo
);
563 dc
.SetFont(m_useFont
);
565 for ( i
=0; i
<n
; i
++ )
569 wxCoord x
= OnMeasureItemWidth(i
);
573 const wxString
& text
= m_strings
[i
];
575 // To make sure performance won't suck in extreme scenarios,
576 // we'll estimate length after some arbitrary number of items
577 // have been checked precily.
578 if ( dirtyHandled
< 1024 )
581 dc
.GetTextExtent(text
, &x
, &y
, 0, 0);
586 x
= text
.length() * (dc
.GetCharWidth()+1);
592 if ( x
>= m_widestWidth
)
595 m_widestItem
= (int)i
;
597 else if ( (int)i
== m_widestItem
)
599 // Width of previously widest item has been decreased, so
600 // we'll have to check all to find current widest item.
608 m_widthsDirty
= false;
614 unsigned int n
= m_widths
.GetCount();
619 for ( i
=0; i
<n
; i
++ )
629 m_widestWidth
= bestWidth
;
630 m_widestItem
= bestIndex
;
632 m_findWidest
= false;
635 // Take scrollbar into account in width calculations
636 int widestWidth
= m_widestWidth
+ wxSystemSettings
::GetMetric(wxSYS_VSCROLL_X
);
637 return wxSize(minWidth
> widestWidth ? minWidth
: widestWidth
,
641 //void wxVListBoxComboPopup::Populate( int n, const wxString choices[] )
642 void wxVListBoxComboPopup
::Populate( const wxArrayString
& choices
)
646 int n
= choices
.GetCount();
648 for ( i
=0; i
<n
; i
++ )
650 const wxString
& item
= choices
.Item(i
);
654 m_widths
.SetCount(n
,-1);
655 m_widthsDirty
= true;
658 wxVListBox
::SetItemCount(n
);
660 // Sort the initial choices
661 if ( m_combo
->GetWindowStyle() & wxCB_SORT
)
664 // Find initial selection
665 wxString strValue
= m_combo
->GetValue();
666 if ( strValue
.length() )
667 m_value
= m_strings
.Index(strValue
);
670 // ----------------------------------------------------------------------------
671 // wxOwnerDrawnComboBox
672 // ----------------------------------------------------------------------------
675 BEGIN_EVENT_TABLE(wxOwnerDrawnComboBox
, wxComboCtrl
)
679 IMPLEMENT_DYNAMIC_CLASS2(wxOwnerDrawnComboBox
, wxComboCtrl
, wxControlWithItems
)
681 void wxOwnerDrawnComboBox
::Init()
685 bool wxOwnerDrawnComboBox
::Create(wxWindow
*parent
,
687 const wxString
& value
,
691 const wxValidator
& validator
,
692 const wxString
& name
)
694 return wxComboCtrl
::Create(parent
,id
,value
,pos
,size
,style
,validator
,name
);
697 wxOwnerDrawnComboBox
::wxOwnerDrawnComboBox(wxWindow
*parent
,
699 const wxString
& value
,
702 const wxArrayString
& choices
,
704 const wxValidator
& validator
,
705 const wxString
& name
)
710 Create(parent
,id
,value
,pos
,size
,choices
,style
, validator
, name
);
713 bool wxOwnerDrawnComboBox
::Create(wxWindow
*parent
,
715 const wxString
& value
,
718 const wxArrayString
& choices
,
720 const wxValidator
& validator
,
721 const wxString
& name
)
724 //wxCArrayString chs(choices);
726 //return Create(parent, id, value, pos, size, chs.GetCount(),
727 // chs.GetStrings(), style, validator, name);
728 return Create(parent
, id
, value
, pos
, size
, 0,
729 NULL
, style
, validator
, name
);
732 bool wxOwnerDrawnComboBox
::Create(wxWindow
*parent
,
734 const wxString
& value
,
738 const wxString choices
[],
740 const wxValidator
& validator
,
741 const wxString
& name
)
744 if ( !Create(parent
, id
, value
, pos
, size
, style
,
751 for ( i
=0; i
<n
; i
++ )
752 m_initChs
.Add(choices
[i
]);
757 wxOwnerDrawnComboBox
::~wxOwnerDrawnComboBox()
759 if ( m_popupInterface
)
760 GetVListBoxComboPopup()->ClearClientDatas();
763 void wxOwnerDrawnComboBox
::DoSetPopupControl(wxComboPopup
* popup
)
767 popup
= new wxVListBoxComboPopup();
770 wxComboCtrl
::DoSetPopupControl(popup
);
774 // Add initial choices to the wxVListBox
775 if ( !GetVListBoxComboPopup()->GetCount() )
777 GetVListBoxComboPopup()->Populate(m_initChs
);
782 // ----------------------------------------------------------------------------
783 // wxOwnerDrawnComboBox item manipulation methods
784 // ----------------------------------------------------------------------------
786 void wxOwnerDrawnComboBox
::Clear()
788 EnsurePopupControl();
790 GetVListBoxComboPopup()->Clear();
792 SetValue(wxEmptyString
);
795 void wxOwnerDrawnComboBox
::Delete(unsigned int n
)
797 wxCHECK_RET( IsValid(n
), _T("invalid index in wxOwnerDrawnComboBox::Delete") );
799 if ( GetSelection() == (int) n
)
800 SetValue(wxEmptyString
);
802 GetVListBoxComboPopup()->Delete(n
);
805 unsigned int wxOwnerDrawnComboBox
::GetCount() const
807 if ( !m_popupInterface
)
808 return m_initChs
.GetCount();
810 return GetVListBoxComboPopup()->GetCount();
813 wxString wxOwnerDrawnComboBox
::GetString(unsigned int n
) const
815 wxCHECK_MSG( IsValid(n
), wxEmptyString
, _T("invalid index in wxOwnerDrawnComboBox::GetString") );
817 if ( !m_popupInterface
)
818 return m_initChs
.Item(n
);
820 return GetVListBoxComboPopup()->GetString(n
);
823 void wxOwnerDrawnComboBox
::SetString(unsigned int n
, const wxString
& s
)
825 EnsurePopupControl();
827 wxCHECK_RET( IsValid(n
), _T("invalid index in wxOwnerDrawnComboBox::SetString") );
829 GetVListBoxComboPopup()->SetString(n
,s
);
832 int wxOwnerDrawnComboBox
::FindString(const wxString
& s
, bool bCase
) const
834 if ( !m_popupInterface
)
835 return m_initChs
.Index(s
, bCase
);
837 return GetVListBoxComboPopup()->FindString(s
, bCase
);
840 void wxOwnerDrawnComboBox
::Select(int n
)
842 EnsurePopupControl();
844 wxCHECK_RET( (n
== wxNOT_FOUND
) || IsValid(n
), _T("invalid index in wxOwnerDrawnComboBox::Select") );
846 GetVListBoxComboPopup()->SetSelection(n
);
850 str
= GetVListBoxComboPopup()->GetString(n
);
852 // Refresh text portion in control
854 m_text
->SetValue( str
);
861 int wxOwnerDrawnComboBox
::GetSelection() const
863 if ( !m_popupInterface
)
864 return m_initChs
.Index(m_valueString
);
866 return GetVListBoxComboPopup()->GetSelection();
869 int wxOwnerDrawnComboBox
::DoAppend(const wxString
& item
)
871 EnsurePopupControl();
872 wxASSERT(m_popupInterface
);
874 return GetVListBoxComboPopup()->Append(item
);
877 int wxOwnerDrawnComboBox
::DoInsert(const wxString
& item
, unsigned int pos
)
879 EnsurePopupControl();
881 wxCHECK_MSG(!(GetWindowStyle() & wxCB_SORT
), -1, wxT("can't insert into sorted list"));
882 wxCHECK_MSG(IsValidInsert(pos
), -1, wxT("invalid index"));
884 GetVListBoxComboPopup()->Insert(item
,pos
);
889 void wxOwnerDrawnComboBox
::DoSetItemClientData(unsigned int n
, void* clientData
)
891 EnsurePopupControl();
893 GetVListBoxComboPopup()->SetItemClientData(n
,clientData
,m_clientDataItemsType
);
896 void* wxOwnerDrawnComboBox
::DoGetItemClientData(unsigned int n
) const
898 if ( !m_popupInterface
)
901 return GetVListBoxComboPopup()->GetItemClientData(n
);
904 void wxOwnerDrawnComboBox
::DoSetItemClientObject(unsigned int n
, wxClientData
* clientData
)
906 DoSetItemClientData(n
, (void*) clientData
);
909 wxClientData
* wxOwnerDrawnComboBox
::DoGetItemClientObject(unsigned int n
) const
911 return (wxClientData
*) DoGetItemClientData(n
);
914 // ----------------------------------------------------------------------------
915 // wxOwnerDrawnComboBox item drawing and measuring default implementations
916 // ----------------------------------------------------------------------------
918 void wxOwnerDrawnComboBox
::OnDrawItem( wxDC
& dc
,
923 if ( flags
& wxODCB_PAINTING_CONTROL
)
925 dc
.DrawText( GetValue(),
926 rect
.x
+ GetTextIndent(),
927 (rect
.height
-dc
.GetCharHeight())/2 + rect
.y
);
931 dc
.DrawText( GetVListBoxComboPopup()->GetString(item
), rect
.x
+ 2, rect
.y
);
935 wxCoord wxOwnerDrawnComboBox
::OnMeasureItem( size_t WXUNUSED(item
) ) const
940 wxCoord wxOwnerDrawnComboBox
::OnMeasureItemWidth( size_t WXUNUSED(item
) ) const
945 void wxOwnerDrawnComboBox
::OnDrawBackground(wxDC
& dc
, const wxRect
& rect
, int item
, int flags
) const
947 // we need to render selected and current items differently
948 if ( GetVListBoxComboPopup()->IsCurrent((size_t)item
) )
950 DrawFocusBackground(dc
,
952 (flags
&wxODCB_PAINTING_CONTROL?
0:wxCONTROL_ISSUBMENU
) |
955 //else: do nothing for the normal items
958 #endif // wxUSE_ODCOMBOBOX