1 /////////////////////////////////////////////////////////////////////////////
2 // Name: univ/combobox.cpp
3 // Purpose: wxComboControl and wxComboBox implementation
4 // Author: Vadim Zeitlin
8 // Copyright: (c) 2000 SciTech Software, Inc. (www.scitechsoft.com)
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
12 // ============================================================================
14 // ============================================================================
16 // ----------------------------------------------------------------------------
18 // ----------------------------------------------------------------------------
20 #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
21 #pragma implementation "univcombobox.h"
24 #include "wx/wxprec.h"
35 #include "wx/button.h"
36 #include "wx/combobox.h"
37 #include "wx/listbox.h"
38 #include "wx/textctrl.h"
39 #include "wx/bmpbuttn.h"
41 #include "wx/validate.h"
44 #include "wx/tooltip.h"
45 #include "wx/popupwin.h"
47 #include "wx/univ/renderer.h"
48 #include "wx/univ/inphand.h"
49 #include "wx/univ/theme.h"
52 The keyboard event flow:
54 1. they always come to the text ctrl
55 2. it forwards the ones it doesn't process to the wxComboControl
56 3. which passes them to the popup window if it is popped up
60 // ----------------------------------------------------------------------------
62 // the margin between the text control and the combo button
63 static const wxCoord g_comboMargin
= 2;
65 // ----------------------------------------------------------------------------
66 // wxComboButton is just a normal button except that it sends commands to the
67 // combobox and not its parent
68 // ----------------------------------------------------------------------------
70 class wxComboButton
: public wxBitmapButton
73 wxComboButton(wxComboControl
*combo
)
74 : wxBitmapButton(combo
->GetParent(), -1, wxNullBitmap
,
75 wxDefaultPosition
, wxDefaultSize
,
76 wxBORDER_NONE
| wxBU_EXACTFIT
)
80 wxBitmap bmpNormal
, bmpFocus
, bmpPressed
, bmpDisabled
;
82 GetRenderer()->GetComboBitmaps(&bmpNormal
,
87 SetBitmapLabel(bmpNormal
);
88 SetBitmapFocus(bmpFocus
.Ok() ? bmpFocus
: bmpNormal
);
89 SetBitmapSelected(bmpPressed
.Ok() ? bmpPressed
: bmpNormal
);
90 SetBitmapDisabled(bmpDisabled
.Ok() ? bmpDisabled
: bmpNormal
);
92 SetBestSize(wxDefaultSize
);
96 void OnButton(wxCommandEvent
& WXUNUSED(event
)) { m_combo
->ShowPopup(); }
98 virtual wxSize
DoGetBestClientSize() const
100 const wxBitmap
& bmp
= GetBitmapLabel();
102 return wxSize(bmp
.GetWidth(), bmp
.GetHeight());
107 wxComboControl
*m_combo
;
109 DECLARE_EVENT_TABLE()
112 // ----------------------------------------------------------------------------
113 // wxComboListBox is a listbox modified to be used as a popup window in a
115 // ----------------------------------------------------------------------------
117 class wxComboListBox
: public wxListBox
, public wxComboPopup
121 wxComboListBox(wxComboControl
*combo
, int style
= 0);
122 virtual ~wxComboListBox();
124 // implement wxComboPopup methods
125 virtual bool SetSelection(const wxString
& value
);
126 virtual void SetSelection(int n
, bool select
)
127 { wxListBox::SetSelection( n
, select
); };
128 virtual wxControl
*GetControl() { return this; }
129 virtual void OnShow();
130 virtual wxCoord
GetBestWidth() const;
133 // we shouldn't return height too big from here
134 virtual wxSize
DoGetBestClientSize() const;
136 // filter mouse move events happening outside the list box
137 void OnMouseMove(wxMouseEvent
& event
);
139 // set m_clicked value from here
140 void OnLeftUp(wxMouseEvent
& event
);
142 // called whenever the user selects or activates a listbox item
143 void OnSelect(wxCommandEvent
& event
);
145 // used to process wxUniv actions
146 bool PerformAction(const wxControlAction
& action
,
148 const wxString
& strArg
);
151 // has the mouse been released on this control?
154 DECLARE_EVENT_TABLE()
157 // ----------------------------------------------------------------------------
158 // wxComboTextCtrl is a simple text ctrl which forwards
159 // wxEVT_COMMAND_TEXT_UPDATED events and all key events to the combobox
160 // ----------------------------------------------------------------------------
162 class wxComboTextCtrl
: public wxTextCtrl
165 wxComboTextCtrl(wxComboControl
*combo
,
166 const wxString
& value
,
168 const wxValidator
& validator
);
171 void OnKey(wxKeyEvent
& event
);
172 void OnText(wxCommandEvent
& event
);
175 wxComboControl
*m_combo
;
177 DECLARE_EVENT_TABLE()
180 // ----------------------------------------------------------------------------
181 // event tables and such
182 // ----------------------------------------------------------------------------
184 BEGIN_EVENT_TABLE(wxComboButton
, wxButton
)
185 EVT_BUTTON(-1, wxComboButton::OnButton
)
188 BEGIN_EVENT_TABLE(wxComboListBox
, wxListBox
)
189 EVT_LISTBOX(-1, wxComboListBox::OnSelect
)
190 EVT_LISTBOX_DCLICK(-1, wxComboListBox::OnSelect
)
191 EVT_MOTION(wxComboListBox::OnMouseMove
)
192 EVT_LEFT_UP(wxComboListBox::OnLeftUp
)
195 BEGIN_EVENT_TABLE(wxComboControl
, wxControl
)
196 EVT_KEY_DOWN(wxComboControl::OnKey
)
197 EVT_KEY_UP(wxComboControl::OnKey
)
200 BEGIN_EVENT_TABLE(wxComboTextCtrl
, wxTextCtrl
)
201 EVT_KEY_DOWN(wxComboTextCtrl::OnKey
)
202 EVT_KEY_UP(wxComboTextCtrl::OnKey
)
203 EVT_TEXT(-1, wxComboTextCtrl::OnText
)
206 IMPLEMENT_DYNAMIC_CLASS(wxComboBox
, wxControl
)
208 // ============================================================================
210 // ============================================================================
212 // ----------------------------------------------------------------------------
213 // wxComboControl creation
214 // ----------------------------------------------------------------------------
216 void wxComboControl::Init()
218 m_popup
= (wxComboPopup
*)NULL
;
219 m_winPopup
= (wxPopupComboWindow
*)NULL
;
220 m_isPopupShown
= FALSE
;
225 bool wxComboControl::Create(wxWindow
*parent
,
227 const wxString
& value
,
231 const wxValidator
& validator
,
232 const wxString
& name
)
234 // first create our own window, i.e. the one which will contain all
236 style
&= ~wxBORDER_NONE
;
237 style
|= wxBORDER_SUNKEN
;
238 if ( !wxControl::Create(parent
, id
, pos
, size
, style
, validator
, name
) )
241 // create the text control and the button as our siblings (*not* children),
242 // don't care about size/position here - they will be set in DoMoveWindow()
243 m_btn
= new wxComboButton(this);
244 m_text
= new wxComboTextCtrl(this,
246 style
& wxCB_READONLY
? wxTE_READONLY
: 0,
249 // for compatibility with the other ports, the height specified is the
250 // combined height of the combobox itself and the popup
253 // ok, use default height for popup too
258 m_heightPopup
= size
.y
- DoGetBestSize().y
;
264 // create the popup window immediately here to allow creating the controls
265 // with parent == GetPopupWindow() from the derived class ctor
266 m_winPopup
= new wxPopupComboWindow(this);
268 // have to disable this window to avoid interfering it with message
269 // processing to the text and the button... but pretend it is enabled to
270 // make IsEnabled() return TRUE
271 wxControl::Enable(FALSE
); // don't use non virtual Disable() here!
274 CreateInputHandler(wxINP_HANDLER_COMBOBOX
);
279 wxComboControl::~wxComboControl()
281 // as the button and the text control are the parent's children and not
282 // ours, we have to delete them manually - they are not deleted
283 // automatically by wxWindows when we're deleted
290 // ----------------------------------------------------------------------------
292 // ----------------------------------------------------------------------------
294 void wxComboControl::DoSetSize(int x
, int y
,
295 int width
, int WXUNUSED(height
),
298 // combo height is always fixed
299 wxControl::DoSetSize(x
, y
, width
, DoGetBestSize().y
, sizeFlags
);
302 wxSize
wxComboControl::DoGetBestClientSize() const
304 wxSize sizeBtn
= m_btn
->GetBestSize(),
305 sizeText
= m_text
->GetBestSize();
306 wxCoord widthPopup
= 0;
310 widthPopup
= m_popup
->GetBestWidth();
313 return wxSize(wxMax(sizeText
.x
+ g_comboMargin
+ sizeBtn
.x
, widthPopup
),
314 wxMax(sizeBtn
.y
, sizeText
.y
));
317 void wxComboControl::DoMoveWindow(int x
, int y
, int width
, int height
)
319 wxControl::DoMoveWindow(x
, y
, width
, height
);
321 // position the subcontrols inside the client area
322 wxRect rectBorders
= GetRenderer()->GetBorderDimensions(GetBorder());
325 width
-= rectBorders
.x
+ rectBorders
.width
;
326 height
-= rectBorders
.y
+ rectBorders
.height
;
328 wxSize sizeBtn
= m_btn
->GetBestSize();
330 wxCoord wText
= width
- sizeBtn
.x
;
331 wxPoint p
= GetParent() ? GetParent()->GetClientAreaOrigin() : wxPoint(0,0);
332 m_text
->SetSize(x
- p
.x
, y
- p
.y
, wText
, height
);
333 m_btn
->SetSize(x
- p
.x
+ wText
, y
- p
.y
, sizeBtn
.x
, height
);
336 // ----------------------------------------------------------------------------
338 // ----------------------------------------------------------------------------
340 bool wxComboControl::Enable(bool enable
)
342 if ( !wxControl::Enable(enable
) )
345 m_btn
->Enable(enable
);
346 m_text
->Enable(enable
);
351 bool wxComboControl::Show(bool show
)
353 if ( !wxControl::Show(show
) )
366 void wxComboControl::DoSetToolTip(wxToolTip
*tooltip
)
368 wxControl::DoSetToolTip(tooltip
);
370 // Set tool tip for button and text box
375 const wxString
&tip
= tooltip
->GetTip();
376 m_text
->SetToolTip(tip
);
377 m_btn
->SetToolTip(tip
);
381 m_text
->SetToolTip(NULL
);
382 m_btn
->SetToolTip(NULL
);
386 #endif // wxUSE_TOOLTIPS
388 // ----------------------------------------------------------------------------
389 // popup window handling
390 // ----------------------------------------------------------------------------
392 void wxComboControl::SetPopupControl(wxComboPopup
*popup
)
397 void wxComboControl::ShowPopup()
399 wxCHECK_RET( m_popup
, _T("no popup to show in wxComboControl") );
400 wxCHECK_RET( !IsPopupShown(), _T("popup window already shown") );
402 wxControl
*control
= m_popup
->GetControl();
404 // size and position the popup window correctly
405 m_winPopup
->SetSize(GetSize().x
,
406 m_heightPopup
== -1 ? control
->GetBestSize().y
408 wxSize sizePopup
= m_winPopup
->GetClientSize();
409 control
->SetSize(0, 0, sizePopup
.x
, sizePopup
.y
);
411 // some controls don't accept the size we give then: e.g. a listbox may
412 // require more space to show its last row
413 wxSize sizeReal
= control
->GetSize();
414 if ( sizeReal
!= sizePopup
)
416 m_winPopup
->SetClientSize(sizeReal
);
419 m_winPopup
->PositionNearCombo();
423 m_winPopup
->Popup(m_text
);
425 m_popup
->SetSelection(m_text
->GetValue());
427 m_isPopupShown
= TRUE
;
430 void wxComboControl::HidePopup()
432 wxCHECK_RET( m_popup
, _T("no popup to hide in wxComboControl") );
433 wxCHECK_RET( IsPopupShown(), _T("popup window not shown") );
435 m_winPopup
->Dismiss();
437 m_isPopupShown
= FALSE
;
440 void wxComboControl::OnSelect(const wxString
& value
)
442 m_text
->SetValue(value
);
448 void wxComboControl::OnDismiss()
454 // ----------------------------------------------------------------------------
456 // ----------------------------------------------------------------------------
458 wxComboTextCtrl::wxComboTextCtrl(wxComboControl
*combo
,
459 const wxString
& value
,
461 const wxValidator
& validator
)
462 : wxTextCtrl(combo
->GetParent(), -1, value
,
463 wxDefaultPosition
, wxDefaultSize
,
464 wxBORDER_NONE
| style
,
470 void wxComboTextCtrl::OnText(wxCommandEvent
& event
)
472 if ( m_combo
->IsPopupShown() )
474 m_combo
->GetPopupControl()->SetSelection(GetValue());
477 // we need to make a copy of the event to have the correct originating
479 wxCommandEvent event2
= event
;
480 event2
.SetEventObject(m_combo
);
481 event2
.SetId(m_combo
->GetId());
483 // there is a small incompatibility with wxMSW here: the combobox gets the
484 // event before the text control in our case which corresponds to SMW
485 // CBN_EDITUPDATE notification and not CBN_EDITCHANGE one wxMSW currently
488 // if this is really a problem, we can play games with the event handlers
489 // to circumvent this
490 (void)m_combo
->ProcessEvent(event2
);
495 // pass the keys we don't process to the combo first
496 void wxComboTextCtrl::OnKey(wxKeyEvent
& event
)
498 switch ( event
.GetKeyCode() )
501 // the popup control gets it first but only if it is shown
502 if ( !m_combo
->IsPopupShown() )
513 (void)m_combo
->ProcessEvent(event
);
520 // ----------------------------------------------------------------------------
522 // ----------------------------------------------------------------------------
524 wxComboListBox::wxComboListBox(wxComboControl
*combo
, int style
)
525 : wxListBox(combo
->GetPopupWindow(), -1,
526 wxDefaultPosition
, wxDefaultSize
,
528 wxBORDER_SIMPLE
| wxLB_INT_HEIGHT
| style
),
531 // we don't react to the mouse events outside the window at all
535 wxComboListBox::~wxComboListBox()
539 bool wxComboListBox::SetSelection(const wxString
& value
)
541 // FindItem() would just find the current item for an empty string (it
542 // always matches), but we want to show the first one in such case
547 wxListBox::SetSelection(0);
549 //else: empty listbox - nothing to do
551 else if ( !FindItem(value
) )
560 void wxComboListBox::OnSelect(wxCommandEvent
& event
)
564 // first update the combo and close the listbox
565 m_combo
->OnSelect(event
.GetString());
567 // next let the user code have the event
569 // all fields are already filled by the listbox, just change the event
570 // type and send it to the combo
571 wxCommandEvent event2
= event
;
572 event2
.SetEventType(wxEVT_COMMAND_COMBOBOX_SELECTED
);
573 event2
.SetEventObject(m_combo
);
574 event2
.SetId(m_combo
->GetId());
575 m_combo
->ProcessEvent(event2
);
577 //else: ignore the events resultign from just moving the mouse initially
580 void wxComboListBox::OnShow()
582 // nobody clicked us yet
586 bool wxComboListBox::PerformAction(const wxControlAction
& action
,
588 const wxString
& strArg
)
591 if ( action
== wxACTION_LISTBOX_FIND
)
593 // we don't let the listbox handle this as instead of just using the
594 // single key presses, as usual, we use the text ctrl value as prefix
595 // and this is done by wxComboControl itself
599 return wxListBox::PerformAction(action
, numArg
, strArg
);
602 void wxComboListBox::OnLeftUp(wxMouseEvent
& event
)
604 // we should dismiss the combo now
610 void wxComboListBox::OnMouseMove(wxMouseEvent
& event
)
612 // while a wxComboListBox is shown, it always has capture, so if it doesn't
613 // we're about to go away anyhow (normally this shouldn't happen at all,
614 // but I don't put assert here as it just might do on other platforms and
615 // it doesn't break anythign anyhow)
616 if ( this == wxWindow::GetCapture() )
618 if ( HitTest(event
.GetPosition()) == wxHT_WINDOW_INSIDE
)
622 //else: popup shouldn't react to the mouse motions outside it, it only
623 // captures the mouse to be able to detect when it must be
624 // dismissed, so don't call Skip()
628 wxCoord
wxComboListBox::GetBestWidth() const
630 wxSize size
= wxListBox::GetBestSize();
634 wxSize
wxComboListBox::DoGetBestClientSize() const
636 // don't return size too big or we risk to not fit on the screen
637 wxSize size
= wxListBox::DoGetBestClientSize();
638 wxCoord hChar
= GetCharHeight();
640 int nLines
= size
.y
/ hChar
;
642 // 10 is the same limit as used by wxMSW
651 // ----------------------------------------------------------------------------
653 // ----------------------------------------------------------------------------
655 void wxComboBox::Init()
657 m_lbox
= (wxListBox
*)NULL
;
660 wxComboBox::wxComboBox(wxWindow
*parent
,
662 const wxString
& value
,
665 const wxArrayString
& choices
,
667 const wxValidator
& validator
,
668 const wxString
& name
)
672 Create(parent
, id
, value
, pos
, size
, choices
, style
, validator
, name
);
675 bool wxComboBox::Create(wxWindow
*parent
,
677 const wxString
& value
,
680 const wxArrayString
& choices
,
682 const wxValidator
& validator
,
683 const wxString
& name
)
685 wxCArrayString
chs(choices
);
687 return Create(parent
, id
, value
, pos
, size
, chs
.GetCount(),
688 chs
.GetStrings(), style
, validator
, name
);
691 bool wxComboBox::Create(wxWindow
*parent
,
693 const wxString
& value
,
697 const wxString
*choices
,
699 const wxValidator
& validator
,
700 const wxString
& name
)
702 if ( !wxComboControl::Create(parent
, id
, value
, pos
, size
, style
,
708 wxComboListBox
*combolbox
=
709 new wxComboListBox(this, style
& wxCB_SORT
? wxLB_SORT
: 0);
711 m_lbox
->Set(n
, choices
);
713 SetPopupControl(combolbox
);
718 wxComboBox::~wxComboBox()
722 // ----------------------------------------------------------------------------
723 // wxComboBox methods forwarded to wxTextCtrl
724 // ----------------------------------------------------------------------------
726 wxString
wxComboBox::GetValue() const
728 return GetText()->GetValue();
731 void wxComboBox::SetValue(const wxString
& value
)
733 GetText()->SetValue(value
);
736 void wxComboBox::Copy()
741 void wxComboBox::Cut()
746 void wxComboBox::Paste()
751 void wxComboBox::SetInsertionPoint(long pos
)
753 GetText()->SetInsertionPoint(pos
);
756 void wxComboBox::SetInsertionPointEnd()
758 GetText()->SetInsertionPointEnd();
761 long wxComboBox::GetInsertionPoint() const
763 return GetText()->GetInsertionPoint();
766 long wxComboBox::GetLastPosition() const
768 return GetText()->GetLastPosition();
771 void wxComboBox::Replace(long from
, long to
, const wxString
& value
)
773 GetText()->Replace(from
, to
, value
);
776 void wxComboBox::Remove(long from
, long to
)
778 GetText()->Remove(from
, to
);
781 void wxComboBox::SetSelection(long from
, long to
)
783 GetText()->SetSelection(from
, to
);
786 void wxComboBox::SetEditable(bool editable
)
788 GetText()->SetEditable(editable
);
791 // ----------------------------------------------------------------------------
792 // wxComboBox methods forwarded to wxListBox
793 // ----------------------------------------------------------------------------
795 void wxComboBox::Clear()
798 GetText()->SetValue(wxEmptyString
);
801 void wxComboBox::Delete(int n
)
803 wxCHECK_RET( (n
>= 0) && (n
< GetCount()), _T("invalid index in wxComboBox::Delete") );
805 if (GetSelection() == n
)
806 GetText()->SetValue(wxEmptyString
);
808 GetLBox()->Delete(n
);
811 int wxComboBox::GetCount() const
813 return GetLBox()->GetCount();
816 wxString
wxComboBox::GetString(int n
) const
818 wxCHECK_MSG( (n
>= 0) && (n
< GetCount()), wxEmptyString
, _T("invalid index in wxComboBox::GetString") );
820 return GetLBox()->GetString(n
);
823 void wxComboBox::SetString(int n
, const wxString
& s
)
825 wxCHECK_RET( (n
>= 0) && (n
< GetCount()), _T("invalid index in wxComboBox::SetString") );
827 GetLBox()->SetString(n
, s
);
830 int wxComboBox::FindString(const wxString
& s
) const
832 return GetLBox()->FindString(s
);
835 void wxComboBox::Select(int n
)
837 wxCHECK_RET( (n
>= 0) && (n
< GetCount()), _T("invalid index in wxComboBox::Select") );
839 GetLBox()->SetSelection(n
);
840 GetText()->SetValue(GetLBox()->GetString(n
));
843 int wxComboBox::GetSelection() const
845 #if 1 // FIXME:: What is the correct behavior?
846 // if the current value isn't one of the listbox strings, return -1
847 return GetLBox()->GetSelection();
849 // Why oh why is this done this way?
850 // It is not because the value displayed in the text can be found
851 // in the list that it is the item that is selected!
852 return FindString(GetText()->GetValue());
856 int wxComboBox::DoAppend(const wxString
& item
)
858 return GetLBox()->Append(item
);
861 int wxComboBox::DoInsert(const wxString
& item
, int pos
)
863 wxCHECK_MSG(!(GetWindowStyle() & wxCB_SORT
), -1, wxT("can't insert into sorted list"));
864 wxCHECK_MSG((pos
>=0) && (pos
<=GetCount()), -1, wxT("invalid index"));
866 if (pos
== GetCount())
867 return DoAppend(item
);
869 GetLBox()->Insert(item
, pos
);
873 void wxComboBox::DoSetItemClientData(int n
, void* clientData
)
875 GetLBox()->SetClientData(n
, clientData
);
878 void *wxComboBox::DoGetItemClientData(int n
) const
880 return GetLBox()->GetClientData(n
);
883 void wxComboBox::DoSetItemClientObject(int n
, wxClientData
* clientData
)
885 GetLBox()->SetClientObject(n
, clientData
);
888 wxClientData
* wxComboBox::DoGetItemClientObject(int n
) const
890 return GetLBox()->GetClientObject(n
);
893 // ----------------------------------------------------------------------------
895 // ----------------------------------------------------------------------------
897 void wxComboControl::OnKey(wxKeyEvent
& event
)
899 if ( m_isPopupShown
)
901 // pass it to the popped up control
902 (void)m_popup
->GetControl()->ProcessEvent(event
);
910 bool wxComboControl::PerformAction(const wxControlAction
& action
,
912 const wxString
& strArg
)
914 bool processed
= FALSE
;
915 if ( action
== wxACTION_COMBOBOX_POPUP
)
917 if ( !m_isPopupShown
)
924 else if ( action
== wxACTION_COMBOBOX_DISMISS
)
926 if ( m_isPopupShown
)
937 return wxControl::PerformAction(action
, numArg
, strArg
);
943 // ----------------------------------------------------------------------------
944 // wxStdComboBoxInputHandler
945 // ----------------------------------------------------------------------------
947 wxStdComboBoxInputHandler::wxStdComboBoxInputHandler(wxInputHandler
*inphand
)
948 : wxStdInputHandler(inphand
)
952 bool wxStdComboBoxInputHandler::HandleKey(wxInputConsumer
*consumer
,
953 const wxKeyEvent
& event
,
958 wxControlAction action
;
959 switch ( event
.GetKeyCode() )
962 action
= wxACTION_COMBOBOX_POPUP
;
966 action
= wxACTION_COMBOBOX_DISMISS
;
972 consumer
->PerformAction(action
);
978 return wxStdInputHandler::HandleKey(consumer
, event
, pressed
);
981 #endif // wxUSE_COMBOBOX