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 // ----------------------------------------------------------------------------
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
& 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 wxControl
*GetControl() { return this; }
127 virtual void OnShow();
130 // we shouldn't return height too big from here
131 virtual wxSize
DoGetBestClientSize() const;
133 // filter mouse move events happening outside the list box
134 void OnMouseMove(wxMouseEvent
& event
);
136 // set m_clicked value from here
137 void OnLeftUp(wxMouseEvent
& event
);
139 // called whenever the user selects or activates a listbox item
140 void OnSelect(wxCommandEvent
& event
);
142 // used to process wxUniv actions
143 bool PerformAction(const wxControlAction
& action
,
145 const wxString
& strArg
);
148 // has the mouse been released on this control?
151 DECLARE_EVENT_TABLE()
154 // ----------------------------------------------------------------------------
155 // wxComboTextCtrl is a simple text ctrl which forwards
156 // wxEVT_COMMAND_TEXT_UPDATED events and all key events to the combobox
157 // ----------------------------------------------------------------------------
159 class wxComboTextCtrl
: public wxTextCtrl
162 wxComboTextCtrl(wxComboControl
*combo
,
163 const wxString
& value
,
165 const wxValidator
& validator
);
168 void OnKey(wxKeyEvent
& event
);
169 void OnText(wxCommandEvent
& event
);
172 wxComboControl
*m_combo
;
174 DECLARE_EVENT_TABLE()
177 // ----------------------------------------------------------------------------
178 // event tables and such
179 // ----------------------------------------------------------------------------
181 BEGIN_EVENT_TABLE(wxComboButton
, wxButton
)
182 EVT_BUTTON(-1, wxComboButton::OnButton
)
185 BEGIN_EVENT_TABLE(wxComboListBox
, wxListBox
)
186 EVT_LISTBOX(-1, wxComboListBox::OnSelect
)
187 EVT_LISTBOX_DCLICK(-1, wxComboListBox::OnSelect
)
188 EVT_MOTION(wxComboListBox::OnMouseMove
)
189 EVT_LEFT_UP(wxComboListBox::OnLeftUp
)
192 BEGIN_EVENT_TABLE(wxComboControl
, wxControl
)
193 EVT_KEY_DOWN(wxComboControl::OnKey
)
194 EVT_KEY_UP(wxComboControl::OnKey
)
197 BEGIN_EVENT_TABLE(wxComboTextCtrl
, wxTextCtrl
)
198 EVT_KEY_DOWN(wxComboTextCtrl::OnKey
)
199 EVT_KEY_UP(wxComboTextCtrl::OnKey
)
200 EVT_TEXT(-1, wxComboTextCtrl::OnText
)
203 IMPLEMENT_DYNAMIC_CLASS(wxComboBox
, wxControl
)
205 // ============================================================================
207 // ============================================================================
209 // ----------------------------------------------------------------------------
210 // wxComboControl creation
211 // ----------------------------------------------------------------------------
213 void wxComboControl::Init()
215 m_popup
= (wxComboPopup
*)NULL
;
216 m_winPopup
= (wxPopupComboWindow
*)NULL
;
217 m_isPopupShown
= FALSE
;
222 bool wxComboControl::Create(wxWindow
*parent
,
224 const wxString
& value
,
228 const wxValidator
& validator
,
229 const wxString
& name
)
231 // first create our own window, i.e. the one which will contain all
233 style
&= ~wxBORDER_NONE
;
234 style
|= wxBORDER_SUNKEN
;
235 if ( !wxControl::Create(parent
, id
, pos
, size
, style
, validator
, name
) )
238 // create the text control and the button as our siblings (*not* children),
239 // don't care about size/position here - they will be set in DoMoveWindow()
240 m_btn
= new wxComboButton(this);
241 m_text
= new wxComboTextCtrl(this,
243 style
& wxCB_READONLY
? wxTE_READONLY
: 0,
246 // for compatibility with the other ports, the height specified is the
247 // combined height of the combobox itself and the popup
250 // ok, use default height for popup too
255 m_heightPopup
= size
.y
- DoGetBestSize().y
;
261 // create the popup window immediately here to allow creating the controls
262 // with parent == GetPopupWindow() from the derived class ctor
263 m_winPopup
= new wxPopupComboWindow(this);
265 // have to disable this window to avoid interfering it with message
266 // processing to the text and the button... but pretend it is enabled to
267 // make IsEnabled() return TRUE
268 wxControl::Enable(FALSE
); // don't use non virtual Disable() here!
271 CreateInputHandler(wxINP_HANDLER_COMBOBOX
);
276 wxComboControl::~wxComboControl()
278 // as the button and the text control are the parent's children and not
279 // ours, we have to delete them manually - they are not deleted
280 // automatically by wxWindows when we're deleted
287 // ----------------------------------------------------------------------------
289 // ----------------------------------------------------------------------------
291 void wxComboControl::DoSetSize(int x
, int y
,
292 int width
, int height
,
295 // combo height is always fixed
296 wxControl::DoSetSize(x
, y
, width
, DoGetBestSize().y
, sizeFlags
);
299 wxSize
wxComboControl::DoGetBestClientSize() const
301 wxSize sizeBtn
= m_btn
->GetBestSize(),
302 sizeText
= m_text
->GetBestSize();
304 return wxSize(sizeText
.x
+ g_comboMargin
+ sizeBtn
.x
, wxMax(sizeBtn
.y
, sizeText
.y
));
307 void wxComboControl::DoMoveWindow(int x
, int y
, int width
, int height
)
309 wxControl::DoMoveWindow(x
, y
, width
, height
);
311 // position the subcontrols inside the client area
312 wxRect rectBorders
= GetRenderer()->GetBorderDimensions(GetBorder());
315 width
-= rectBorders
.x
+ rectBorders
.width
;
316 height
-= rectBorders
.y
+ rectBorders
.height
;
318 wxSize sizeBtn
= m_btn
->GetBestSize();
320 wxCoord wText
= width
- sizeBtn
.x
;
321 wxPoint p
= GetParent() ? GetParent()->GetClientAreaOrigin() : wxPoint(0,0);
322 m_text
->SetSize(x
- p
.x
, y
- p
.y
, wText
, height
);
323 m_btn
->SetSize(x
- p
.x
+ wText
, y
- p
.y
, sizeBtn
.x
, height
);
326 // ----------------------------------------------------------------------------
328 // ----------------------------------------------------------------------------
330 bool wxComboControl::Enable(bool enable
)
332 if ( !wxControl::Enable(enable
) )
335 m_btn
->Enable(enable
);
336 m_text
->Enable(enable
);
341 bool wxComboControl::Show(bool show
)
343 if ( !wxControl::Show(show
) )
356 void wxComboControl::DoSetToolTip(wxToolTip
*tooltip
)
358 wxControl::DoSetToolTip(tooltip
);
360 // Set tool tip for button and text box
365 const wxString
&tip
= tooltip
->GetTip();
366 m_text
->SetToolTip(tip
);
367 m_btn
->SetToolTip(tip
);
371 m_text
->SetToolTip(NULL
);
372 m_btn
->SetToolTip(NULL
);
376 #endif // wxUSE_TOOLTIPS
378 // ----------------------------------------------------------------------------
379 // popup window handling
380 // ----------------------------------------------------------------------------
382 void wxComboControl::SetPopupControl(wxComboPopup
*popup
)
387 void wxComboControl::ShowPopup()
389 wxCHECK_RET( m_popup
, _T("no popup to show in wxComboControl") );
390 wxCHECK_RET( !IsPopupShown(), _T("popup window already shown") );
392 wxControl
*control
= m_popup
->GetControl();
394 // size and position the popup window correctly
395 m_winPopup
->SetSize(GetSize().x
,
396 m_heightPopup
== -1 ? control
->GetBestSize().y
398 wxSize sizePopup
= m_winPopup
->GetClientSize();
399 control
->SetSize(0, 0, sizePopup
.x
, sizePopup
.y
);
401 // some controls don't accept the size we give then: e.g. a listbox may
402 // require more space to show its last row
403 wxSize sizeReal
= control
->GetSize();
404 if ( sizeReal
!= sizePopup
)
406 m_winPopup
->SetClientSize(sizeReal
);
409 m_winPopup
->PositionNearCombo();
413 m_winPopup
->Popup(m_text
);
415 m_popup
->SetSelection(m_text
->GetValue());
417 m_isPopupShown
= TRUE
;
420 void wxComboControl::HidePopup()
422 wxCHECK_RET( m_popup
, _T("no popup to hide in wxComboControl") );
423 wxCHECK_RET( IsPopupShown(), _T("popup window not shown") );
425 m_winPopup
->Dismiss();
427 m_isPopupShown
= FALSE
;
430 void wxComboControl::OnSelect(const wxString
& value
)
432 m_text
->SetValue(value
);
438 void wxComboControl::OnDismiss()
444 // ----------------------------------------------------------------------------
446 // ----------------------------------------------------------------------------
448 wxComboTextCtrl::wxComboTextCtrl(wxComboControl
*combo
,
449 const wxString
& value
,
451 const wxValidator
& validator
)
452 : wxTextCtrl(combo
->GetParent(), -1, value
,
453 wxDefaultPosition
, wxDefaultSize
,
454 wxBORDER_NONE
| style
,
460 void wxComboTextCtrl::OnText(wxCommandEvent
& event
)
462 if ( m_combo
->IsPopupShown() )
464 m_combo
->GetPopupControl()->SetSelection(GetValue());
467 // we need to make a copy of the event to have the correct originating
469 wxCommandEvent event2
= event
;
470 event2
.SetEventObject(m_combo
);
471 event2
.SetId(m_combo
->GetId());
473 // there is a small incompatibility with wxMSW here: the combobox gets the
474 // event before the text control in our case which corresponds to SMW
475 // CBN_EDITUPDATE notification and not CBN_EDITCHANGE one wxMSW currently
478 // if this is really a problem, we can play games with the event handlers
479 // to circumvent this
480 (void)m_combo
->ProcessEvent(event2
);
485 // pass the keys we don't process to the combo first
486 void wxComboTextCtrl::OnKey(wxKeyEvent
& event
)
488 switch ( event
.GetKeyCode() )
491 // the popup control gets it first but only if it is shown
492 if ( !m_combo
->IsPopupShown() )
503 (void)m_combo
->ProcessEvent(event
);
510 // ----------------------------------------------------------------------------
512 // ----------------------------------------------------------------------------
514 wxComboListBox::wxComboListBox(wxComboControl
*combo
, int style
)
515 : wxListBox(combo
->GetPopupWindow(), -1,
516 wxDefaultPosition
, wxDefaultSize
,
518 wxBORDER_SIMPLE
| wxLB_INT_HEIGHT
| style
),
521 // we don't react to the mouse events outside the window at all
525 wxComboListBox::~wxComboListBox()
529 bool wxComboListBox::SetSelection(const wxString
& value
)
531 // FindItem() would just find the current item for an empty string (it
532 // always matches), but we want to show the first one in such case
537 wxListBox::SetSelection(0);
539 //else: empty listbox - nothing to do
541 else if ( !FindItem(value
) )
550 void wxComboListBox::OnSelect(wxCommandEvent
& event
)
554 // first update the combo and close the listbox
555 m_combo
->OnSelect(event
.GetString());
557 // next let the user code have the event
559 // all fields are already filled by the listbox, just change the event
560 // type and send it to the combo
561 wxCommandEvent event2
= event
;
562 event2
.SetEventType(wxEVT_COMMAND_COMBOBOX_SELECTED
);
563 event2
.SetEventObject(m_combo
);
564 event2
.SetId(m_combo
->GetId());
565 m_combo
->ProcessEvent(event2
);
567 //else: ignore the events resultign from just moving the mouse initially
570 void wxComboListBox::OnShow()
572 // nobody clicked us yet
576 bool wxComboListBox::PerformAction(const wxControlAction
& action
,
578 const wxString
& strArg
)
581 if ( action
== wxACTION_LISTBOX_FIND
)
583 // we don't let the listbox handle this as instead of just using the
584 // single key presses, as usual, we use the text ctrl value as prefix
585 // and this is done by wxComboControl itself
589 return wxListBox::PerformAction(action
, numArg
, strArg
);
592 void wxComboListBox::OnLeftUp(wxMouseEvent
& event
)
594 // we should dismiss the combo now
600 void wxComboListBox::OnMouseMove(wxMouseEvent
& event
)
602 // while a wxComboListBox is shown, it always has capture, so if it doesn't
603 // we're about to go away anyhow (normally this shouldn't happen at all,
604 // but I don't put assert here as it just might do on other platforms and
605 // it doesn't break anythign anyhow)
606 if ( this == wxWindow::GetCapture() )
608 if ( HitTest(event
.GetPosition()) == wxHT_WINDOW_INSIDE
)
612 //else: popup shouldn't react to the mouse motions outside it, it only
613 // captures the mouse to be able to detect when it must be
614 // dismissed, so don't call Skip()
618 wxSize
wxComboListBox::DoGetBestClientSize() const
620 // don't return size too big or we risk to not fit on the screen
621 wxSize size
= wxListBox::DoGetBestClientSize();
622 wxCoord hChar
= GetCharHeight();
624 int nLines
= size
.y
/ hChar
;
626 // 10 is the same limit as used by wxMSW
635 // ----------------------------------------------------------------------------
637 // ----------------------------------------------------------------------------
639 void wxComboBox::Init()
641 m_lbox
= (wxListBox
*)NULL
;
644 bool wxComboBox::Create(wxWindow
*parent
,
646 const wxString
& value
,
650 const wxString
*choices
,
652 const wxValidator
& validator
,
653 const wxString
& name
)
655 if ( !wxComboControl::Create(parent
, id
, value
, pos
, size
, style
,
661 wxComboListBox
*combolbox
=
662 new wxComboListBox(this, style
& wxCB_SORT
? wxLB_SORT
: 0);
664 m_lbox
->Set(n
, choices
);
666 SetPopupControl(combolbox
);
671 wxComboBox::~wxComboBox()
675 // ----------------------------------------------------------------------------
676 // wxComboBox methods forwarded to wxTextCtrl
677 // ----------------------------------------------------------------------------
679 wxString
wxComboBox::GetValue() const
681 return GetText()->GetValue();
684 void wxComboBox::SetValue(const wxString
& value
)
686 GetText()->SetValue(value
);
689 void wxComboBox::Copy()
694 void wxComboBox::Cut()
699 void wxComboBox::Paste()
704 void wxComboBox::SetInsertionPoint(long pos
)
706 GetText()->SetInsertionPoint(pos
);
709 void wxComboBox::SetInsertionPointEnd()
711 GetText()->SetInsertionPointEnd();
714 long wxComboBox::GetInsertionPoint() const
716 return GetText()->GetInsertionPoint();
719 long wxComboBox::GetLastPosition() const
721 return GetText()->GetLastPosition();
724 void wxComboBox::Replace(long from
, long to
, const wxString
& value
)
726 GetText()->Replace(from
, to
, value
);
729 void wxComboBox::Remove(long from
, long to
)
731 GetText()->Remove(from
, to
);
734 void wxComboBox::SetSelection(long from
, long to
)
736 GetText()->SetSelection(from
, to
);
739 void wxComboBox::SetEditable(bool editable
)
741 GetText()->SetEditable(editable
);
744 // ----------------------------------------------------------------------------
745 // wxComboBox methods forwarded to wxListBox
746 // ----------------------------------------------------------------------------
748 void wxComboBox::Clear()
751 GetText()->SetValue(wxEmptyString
);
754 void wxComboBox::Delete(int n
)
756 wxCHECK_RET( (n
>= 0) && (n
< GetCount()), _T("invalid index in wxComboBox::Delete") );
758 if (GetSelection() == n
)
759 GetText()->SetValue(wxEmptyString
);
761 GetLBox()->Delete(n
);
764 int wxComboBox::GetCount() const
766 return GetLBox()->GetCount();
769 wxString
wxComboBox::GetString(int n
) const
771 wxCHECK_MSG( (n
>= 0) && (n
< GetCount()), wxEmptyString
, _T("invalid index in wxComboBox::GetString") );
773 return GetLBox()->GetString(n
);
776 void wxComboBox::SetString(int n
, const wxString
& s
)
778 wxCHECK_RET( (n
>= 0) && (n
< GetCount()), _T("invalid index in wxComboBox::SetString") );
780 GetLBox()->SetString(n
, s
);
783 int wxComboBox::FindString(const wxString
& s
) const
785 return GetLBox()->FindString(s
);
788 void wxComboBox::Select(int n
)
790 wxCHECK_RET( (n
>= 0) && (n
< GetCount()), _T("invalid index in wxComboBox::Select") );
792 GetLBox()->SetSelection(n
);
793 GetText()->SetValue(GetLBox()->GetString(n
));
796 int wxComboBox::GetSelection() const
798 #if 1 // FIXME:: What is the correct behavior?
799 // if the current value isn't one of the listbox strings, return -1
800 return GetLBox()->GetSelection();
802 // Why oh why is this done this way?
803 // It is not because the value displayed in the text can be found
804 // in the list that it is the item that is selected!
805 return FindString(GetText()->GetValue());
809 int wxComboBox::DoAppend(const wxString
& item
)
811 return GetLBox()->Append(item
);
814 void wxComboBox::DoSetItemClientData(int n
, void* clientData
)
816 GetLBox()->SetClientData(n
, clientData
);
819 void *wxComboBox::DoGetItemClientData(int n
) const
821 return GetLBox()->GetClientData(n
);
824 void wxComboBox::DoSetItemClientObject(int n
, wxClientData
* clientData
)
826 GetLBox()->SetClientObject(n
, clientData
);
829 wxClientData
* wxComboBox::DoGetItemClientObject(int n
) const
831 return GetLBox()->GetClientObject(n
);
834 // ----------------------------------------------------------------------------
836 // ----------------------------------------------------------------------------
838 void wxComboControl::OnKey(wxKeyEvent
& event
)
840 if ( m_isPopupShown
)
842 // pass it to the popped up control
843 (void)m_popup
->GetControl()->ProcessEvent(event
);
851 bool wxComboControl::PerformAction(const wxControlAction
& action
,
853 const wxString
& strArg
)
855 bool processed
= FALSE
;
856 if ( action
== wxACTION_COMBOBOX_POPUP
)
858 if ( !m_isPopupShown
)
865 else if ( action
== wxACTION_COMBOBOX_DISMISS
)
867 if ( m_isPopupShown
)
878 return wxControl::PerformAction(action
, numArg
, strArg
);
884 // ----------------------------------------------------------------------------
885 // wxStdComboBoxInputHandler
886 // ----------------------------------------------------------------------------
888 wxStdComboBoxInputHandler::wxStdComboBoxInputHandler(wxInputHandler
*inphand
)
889 : wxStdInputHandler(inphand
)
893 bool wxStdComboBoxInputHandler::HandleKey(wxInputConsumer
*consumer
,
894 const wxKeyEvent
& event
,
899 wxControlAction action
;
900 switch ( event
.GetKeyCode() )
903 action
= wxACTION_COMBOBOX_POPUP
;
907 action
= wxACTION_COMBOBOX_DISMISS
;
913 consumer
->PerformAction(action
);
919 return wxStdInputHandler::HandleKey(consumer
, event
, pressed
);
922 #endif // wxUSE_COMBOBOX