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 wxComboControl::wxComboControl()
221 wxComboControl::wxComboControl(wxWindow
*parent
,
223 const wxString
& value
,
227 const wxValidator
& validator
,
228 const wxString
& name
)
232 (void)Create(parent
, id
, value
, pos
, size
, style
, validator
, name
);
235 void wxComboControl::Init()
237 m_popup
= (wxComboPopup
*)NULL
;
238 m_winPopup
= (wxPopupComboWindow
*)NULL
;
239 m_isPopupShown
= FALSE
;
244 bool wxComboControl::Create(wxWindow
*parent
,
246 const wxString
& value
,
250 const wxValidator
& validator
,
251 const wxString
& name
)
253 // first create our own window, i.e. the one which will contain all
255 style
&= ~wxBORDER_NONE
;
256 style
|= wxBORDER_SUNKEN
;
257 if ( !wxControl::Create(parent
, id
, pos
, size
, style
, validator
, name
) )
260 // create the text control and the button as our siblings (*not* children),
261 // don't care about size/position here - they will be set in DoMoveWindow()
262 m_btn
= new wxComboButton(this);
263 m_text
= new wxComboTextCtrl(this,
265 style
& wxCB_READONLY
? wxTE_READONLY
: 0,
268 // for compatibility with the other ports, the height specified is the
269 // combined height of the combobox itself and the popup
272 // ok, use default height for popup too
277 m_heightPopup
= size
.y
- DoGetBestSize().y
;
283 // create the popup window immediately here to allow creating the controls
284 // with parent == GetPopupWindow() from the derived class ctor
285 m_winPopup
= new wxPopupComboWindow(this);
287 // have to disable this window to avoid interfering it with message
288 // processing to the text and the button... but pretend it is enabled to
289 // make IsEnabled() return TRUE
290 wxControl::Enable(FALSE
); // don't use non virtual Disable() here!
293 CreateInputHandler(wxINP_HANDLER_COMBOBOX
);
298 wxComboControl::~wxComboControl()
300 // as the button and the text control are the parent's children and not
301 // ours, we have to delete them manually - they are not deleted
302 // automatically by wxWindows when we're deleted
309 // ----------------------------------------------------------------------------
311 // ----------------------------------------------------------------------------
313 void wxComboControl::DoSetSize(int x
, int y
,
314 int width
, int WXUNUSED(height
),
317 // combo height is always fixed
318 wxControl::DoSetSize(x
, y
, width
, DoGetBestSize().y
, sizeFlags
);
321 wxSize
wxComboControl::DoGetBestClientSize() const
323 wxSize sizeBtn
= m_btn
->GetBestSize(),
324 sizeText
= m_text
->GetBestSize();
325 wxCoord widthPopup
= 0;
329 widthPopup
= m_popup
->GetBestWidth();
332 return wxSize(wxMax(sizeText
.x
+ g_comboMargin
+ sizeBtn
.x
, widthPopup
),
333 wxMax(sizeBtn
.y
, sizeText
.y
));
336 void wxComboControl::DoMoveWindow(int x
, int y
, int width
, int height
)
338 wxControl::DoMoveWindow(x
, y
, width
, height
);
340 // position the subcontrols inside the client area
341 wxRect rectBorders
= GetRenderer()->GetBorderDimensions(GetBorder());
344 width
-= rectBorders
.x
+ rectBorders
.width
;
345 height
-= rectBorders
.y
+ rectBorders
.height
;
347 wxSize sizeBtn
= m_btn
->GetBestSize();
349 wxCoord wText
= width
- sizeBtn
.x
;
350 wxPoint p
= GetParent() ? GetParent()->GetClientAreaOrigin() : wxPoint(0,0);
351 m_text
->SetSize(x
- p
.x
, y
- p
.y
, wText
, height
);
352 m_btn
->SetSize(x
- p
.x
+ wText
, y
- p
.y
, sizeBtn
.x
, height
);
355 // ----------------------------------------------------------------------------
357 // ----------------------------------------------------------------------------
359 bool wxComboControl::Enable(bool enable
)
361 if ( !wxControl::Enable(enable
) )
364 m_btn
->Enable(enable
);
365 m_text
->Enable(enable
);
370 bool wxComboControl::Show(bool show
)
372 if ( !wxControl::Show(show
) )
385 void wxComboControl::DoSetToolTip(wxToolTip
*tooltip
)
387 wxControl::DoSetToolTip(tooltip
);
389 // Set tool tip for button and text box
394 const wxString
&tip
= tooltip
->GetTip();
395 m_text
->SetToolTip(tip
);
396 m_btn
->SetToolTip(tip
);
400 m_text
->SetToolTip(NULL
);
401 m_btn
->SetToolTip(NULL
);
405 #endif // wxUSE_TOOLTIPS
407 // ----------------------------------------------------------------------------
408 // popup window handling
409 // ----------------------------------------------------------------------------
411 void wxComboControl::SetPopupControl(wxComboPopup
*popup
)
416 void wxComboControl::ShowPopup()
418 wxCHECK_RET( m_popup
, _T("no popup to show in wxComboControl") );
419 wxCHECK_RET( !IsPopupShown(), _T("popup window already shown") );
421 wxControl
*control
= m_popup
->GetControl();
423 // size and position the popup window correctly
424 m_winPopup
->SetSize(GetSize().x
,
425 m_heightPopup
== -1 ? control
->GetBestSize().y
427 wxSize sizePopup
= m_winPopup
->GetClientSize();
428 control
->SetSize(0, 0, sizePopup
.x
, sizePopup
.y
);
430 // some controls don't accept the size we give then: e.g. a listbox may
431 // require more space to show its last row
432 wxSize sizeReal
= control
->GetSize();
433 if ( sizeReal
!= sizePopup
)
435 m_winPopup
->SetClientSize(sizeReal
);
438 m_winPopup
->PositionNearCombo();
442 m_winPopup
->Popup(m_text
);
444 m_popup
->SetSelection(m_text
->GetValue());
446 m_isPopupShown
= TRUE
;
449 void wxComboControl::HidePopup()
451 wxCHECK_RET( m_popup
, _T("no popup to hide in wxComboControl") );
452 wxCHECK_RET( IsPopupShown(), _T("popup window not shown") );
454 m_winPopup
->Dismiss();
456 m_isPopupShown
= FALSE
;
459 void wxComboControl::OnSelect(const wxString
& value
)
461 m_text
->SetValue(value
);
467 void wxComboControl::OnDismiss()
473 // ----------------------------------------------------------------------------
475 // ----------------------------------------------------------------------------
477 wxComboTextCtrl::wxComboTextCtrl(wxComboControl
*combo
,
478 const wxString
& value
,
480 const wxValidator
& validator
)
481 : wxTextCtrl(combo
->GetParent(), -1, value
,
482 wxDefaultPosition
, wxDefaultSize
,
483 wxBORDER_NONE
| style
,
489 void wxComboTextCtrl::OnText(wxCommandEvent
& event
)
491 if ( m_combo
->IsPopupShown() )
493 m_combo
->GetPopupControl()->SetSelection(GetValue());
496 // we need to make a copy of the event to have the correct originating
498 wxCommandEvent event2
= event
;
499 event2
.SetEventObject(m_combo
);
500 event2
.SetId(m_combo
->GetId());
502 // there is a small incompatibility with wxMSW here: the combobox gets the
503 // event before the text control in our case which corresponds to SMW
504 // CBN_EDITUPDATE notification and not CBN_EDITCHANGE one wxMSW currently
507 // if this is really a problem, we can play games with the event handlers
508 // to circumvent this
509 (void)m_combo
->ProcessEvent(event2
);
514 // pass the keys we don't process to the combo first
515 void wxComboTextCtrl::OnKey(wxKeyEvent
& event
)
517 switch ( event
.GetKeyCode() )
520 // the popup control gets it first but only if it is shown
521 if ( !m_combo
->IsPopupShown() )
532 (void)m_combo
->ProcessEvent(event
);
539 // ----------------------------------------------------------------------------
541 // ----------------------------------------------------------------------------
543 wxComboListBox::wxComboListBox(wxComboControl
*combo
, int style
)
544 : wxListBox(combo
->GetPopupWindow(), -1,
545 wxDefaultPosition
, wxDefaultSize
,
547 wxBORDER_SIMPLE
| wxLB_INT_HEIGHT
| style
),
550 // we don't react to the mouse events outside the window at all
554 wxComboListBox::~wxComboListBox()
558 bool wxComboListBox::SetSelection(const wxString
& value
)
560 // FindItem() would just find the current item for an empty string (it
561 // always matches), but we want to show the first one in such case
566 wxListBox::SetSelection(0);
568 //else: empty listbox - nothing to do
570 else if ( !FindItem(value
) )
579 void wxComboListBox::OnSelect(wxCommandEvent
& event
)
583 // first update the combo and close the listbox
584 m_combo
->OnSelect(event
.GetString());
586 // next let the user code have the event
588 // all fields are already filled by the listbox, just change the event
589 // type and send it to the combo
590 wxCommandEvent event2
= event
;
591 event2
.SetEventType(wxEVT_COMMAND_COMBOBOX_SELECTED
);
592 event2
.SetEventObject(m_combo
);
593 event2
.SetId(m_combo
->GetId());
594 m_combo
->ProcessEvent(event2
);
596 //else: ignore the events resultign from just moving the mouse initially
599 void wxComboListBox::OnShow()
601 // nobody clicked us yet
605 bool wxComboListBox::PerformAction(const wxControlAction
& action
,
607 const wxString
& strArg
)
610 if ( action
== wxACTION_LISTBOX_FIND
)
612 // we don't let the listbox handle this as instead of just using the
613 // single key presses, as usual, we use the text ctrl value as prefix
614 // and this is done by wxComboControl itself
618 return wxListBox::PerformAction(action
, numArg
, strArg
);
621 void wxComboListBox::OnLeftUp(wxMouseEvent
& event
)
623 // we should dismiss the combo now
629 void wxComboListBox::OnMouseMove(wxMouseEvent
& event
)
631 // while a wxComboListBox is shown, it always has capture, so if it doesn't
632 // we're about to go away anyhow (normally this shouldn't happen at all,
633 // but I don't put assert here as it just might do on other platforms and
634 // it doesn't break anythign anyhow)
635 if ( this == wxWindow::GetCapture() )
637 if ( HitTest(event
.GetPosition()) == wxHT_WINDOW_INSIDE
)
641 //else: popup shouldn't react to the mouse motions outside it, it only
642 // captures the mouse to be able to detect when it must be
643 // dismissed, so don't call Skip()
647 wxCoord
wxComboListBox::GetBestWidth() const
649 wxSize size
= wxListBox::GetBestSize();
653 wxSize
wxComboListBox::DoGetBestClientSize() const
655 // don't return size too big or we risk to not fit on the screen
656 wxSize size
= wxListBox::DoGetBestClientSize();
657 wxCoord hChar
= GetCharHeight();
659 int nLines
= size
.y
/ hChar
;
661 // 10 is the same limit as used by wxMSW
670 // ----------------------------------------------------------------------------
672 // ----------------------------------------------------------------------------
674 wxComboBox::wxComboBox()
679 wxComboBox::wxComboBox(wxWindow
*parent
,
681 const wxString
& value
,
685 const wxString
*choices
,
687 const wxValidator
& validator
,
688 const wxString
& name
)
692 (void)Create(parent
, id
, value
, pos
, size
, n
, choices
,
693 style
, validator
, name
);
696 void wxComboBox::Init()
698 m_lbox
= (wxListBox
*)NULL
;
701 bool wxComboBox::Create(wxWindow
*parent
,
703 const wxString
& value
,
707 const wxString
*choices
,
709 const wxValidator
& validator
,
710 const wxString
& name
)
712 if ( !wxComboControl::Create(parent
, id
, value
, pos
, size
, style
,
718 wxComboListBox
*combolbox
=
719 new wxComboListBox(this, style
& wxCB_SORT
? wxLB_SORT
: 0);
721 m_lbox
->Set(n
, choices
);
723 SetPopupControl(combolbox
);
728 wxComboBox::~wxComboBox()
732 // ----------------------------------------------------------------------------
733 // wxComboBox methods forwarded to wxTextCtrl
734 // ----------------------------------------------------------------------------
736 wxString
wxComboBox::GetValue() const
738 return GetText()->GetValue();
741 void wxComboBox::SetValue(const wxString
& value
)
743 GetText()->SetValue(value
);
746 void wxComboBox::Copy()
751 void wxComboBox::Cut()
756 void wxComboBox::Paste()
761 void wxComboBox::SetInsertionPoint(long pos
)
763 GetText()->SetInsertionPoint(pos
);
766 void wxComboBox::SetInsertionPointEnd()
768 GetText()->SetInsertionPointEnd();
771 long wxComboBox::GetInsertionPoint() const
773 return GetText()->GetInsertionPoint();
776 long wxComboBox::GetLastPosition() const
778 return GetText()->GetLastPosition();
781 void wxComboBox::Replace(long from
, long to
, const wxString
& value
)
783 GetText()->Replace(from
, to
, value
);
786 void wxComboBox::Remove(long from
, long to
)
788 GetText()->Remove(from
, to
);
791 void wxComboBox::SetSelection(long from
, long to
)
793 GetText()->SetSelection(from
, to
);
796 void wxComboBox::SetEditable(bool editable
)
798 GetText()->SetEditable(editable
);
801 // ----------------------------------------------------------------------------
802 // wxComboBox methods forwarded to wxListBox
803 // ----------------------------------------------------------------------------
805 void wxComboBox::Clear()
808 GetText()->SetValue(wxEmptyString
);
811 void wxComboBox::Delete(int n
)
813 wxCHECK_RET( (n
>= 0) && (n
< GetCount()), _T("invalid index in wxComboBox::Delete") );
815 if (GetSelection() == n
)
816 GetText()->SetValue(wxEmptyString
);
818 GetLBox()->Delete(n
);
821 int wxComboBox::GetCount() const
823 return GetLBox()->GetCount();
826 wxString
wxComboBox::GetString(int n
) const
828 wxCHECK_MSG( (n
>= 0) && (n
< GetCount()), wxEmptyString
, _T("invalid index in wxComboBox::GetString") );
830 return GetLBox()->GetString(n
);
833 void wxComboBox::SetString(int n
, const wxString
& s
)
835 wxCHECK_RET( (n
>= 0) && (n
< GetCount()), _T("invalid index in wxComboBox::SetString") );
837 GetLBox()->SetString(n
, s
);
840 int wxComboBox::FindString(const wxString
& s
) const
842 return GetLBox()->FindString(s
);
845 void wxComboBox::Select(int n
)
847 wxCHECK_RET( (n
>= 0) && (n
< GetCount()), _T("invalid index in wxComboBox::Select") );
849 GetLBox()->SetSelection(n
);
850 GetText()->SetValue(GetLBox()->GetString(n
));
853 int wxComboBox::GetSelection() const
855 #if 1 // FIXME:: What is the correct behavior?
856 // if the current value isn't one of the listbox strings, return -1
857 return GetLBox()->GetSelection();
859 // Why oh why is this done this way?
860 // It is not because the value displayed in the text can be found
861 // in the list that it is the item that is selected!
862 return FindString(GetText()->GetValue());
866 int wxComboBox::DoAppend(const wxString
& item
)
868 return GetLBox()->Append(item
);
871 int wxComboBox::DoInsert(const wxString
& item
, int pos
)
873 wxCHECK_MSG(!(GetWindowStyle() & wxCB_SORT
), -1, wxT("can't insert into sorted list"));
874 wxCHECK_MSG((pos
>=0) && (pos
<=GetCount()), -1, wxT("invalid index"));
876 if (pos
== GetCount())
877 return DoAppend(item
);
879 GetLBox()->Insert(item
, pos
);
883 void wxComboBox::DoSetItemClientData(int n
, void* clientData
)
885 GetLBox()->SetClientData(n
, clientData
);
888 void *wxComboBox::DoGetItemClientData(int n
) const
890 return GetLBox()->GetClientData(n
);
893 void wxComboBox::DoSetItemClientObject(int n
, wxClientData
* clientData
)
895 GetLBox()->SetClientObject(n
, clientData
);
898 wxClientData
* wxComboBox::DoGetItemClientObject(int n
) const
900 return GetLBox()->GetClientObject(n
);
903 // ----------------------------------------------------------------------------
905 // ----------------------------------------------------------------------------
907 void wxComboControl::OnKey(wxKeyEvent
& event
)
909 if ( m_isPopupShown
)
911 // pass it to the popped up control
912 (void)m_popup
->GetControl()->ProcessEvent(event
);
920 bool wxComboControl::PerformAction(const wxControlAction
& action
,
922 const wxString
& strArg
)
924 bool processed
= FALSE
;
925 if ( action
== wxACTION_COMBOBOX_POPUP
)
927 if ( !m_isPopupShown
)
934 else if ( action
== wxACTION_COMBOBOX_DISMISS
)
936 if ( m_isPopupShown
)
947 return wxControl::PerformAction(action
, numArg
, strArg
);
953 // ----------------------------------------------------------------------------
954 // wxStdComboBoxInputHandler
955 // ----------------------------------------------------------------------------
957 wxStdComboBoxInputHandler::wxStdComboBoxInputHandler(wxInputHandler
*inphand
)
958 : wxStdInputHandler(inphand
)
962 bool wxStdComboBoxInputHandler::HandleKey(wxInputConsumer
*consumer
,
963 const wxKeyEvent
& event
,
968 wxControlAction action
;
969 switch ( event
.GetKeyCode() )
972 action
= wxACTION_COMBOBOX_POPUP
;
976 action
= wxACTION_COMBOBOX_DISMISS
;
982 consumer
->PerformAction(action
);
988 return wxStdInputHandler::HandleKey(consumer
, event
, pressed
);
991 #endif // wxUSE_COMBOBOX