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 /////////////////////////////////////////////////////////////////////////////
15 +1. typing in the text should select the string in listbox
16 +2. scrollbars in listbox are unusable
17 +3. the initially selected item is not selected
18 ?4. kbd interface (what does GTK do?)
19 5. there is still autoscrolling without scrollbars - but is it bad?
22 // ============================================================================
24 // ============================================================================
26 // ----------------------------------------------------------------------------
28 // ----------------------------------------------------------------------------
31 #pragma implementation "univcombobox.h"
34 #include "wx/wxprec.h"
45 #include "wx/button.h"
46 #include "wx/combobox.h"
47 #include "wx/listbox.h"
48 #include "wx/textctrl.h"
49 #include "wx/bmpbuttn.h"
51 #include "wx/validate.h"
54 #include "wx/popupwin.h"
56 #include "wx/univ/renderer.h"
57 #include "wx/univ/inphand.h"
58 #include "wx/univ/theme.h"
61 The keyboard event flow:
63 1. they always come to the text ctrl
64 2. it forwards the ones it doesn't process to the wxComboControl
65 3. which passes them to the popup window if it is popped up
68 // ----------------------------------------------------------------------------
69 // wxComboButton is just a normal button except that it sends commands to the
70 // combobox and not its parent
71 // ----------------------------------------------------------------------------
73 class wxComboButton
: public wxBitmapButton
76 wxComboButton(wxComboControl
*combo
)
77 : wxBitmapButton(combo
->GetParent(), -1, wxNullBitmap
,
78 wxDefaultPosition
, wxDefaultSize
,
83 wxBitmap bmpNormal
, bmpPressed
, bmpDisabled
;
85 GetRenderer()->GetComboBitmaps(&bmpNormal
, &bmpPressed
, &bmpDisabled
);
86 SetBitmapLabel(bmpNormal
);
87 SetBitmapFocus(bmpNormal
);
88 SetBitmapSelected(bmpPressed
);
89 SetBitmapDisabled(bmpDisabled
);
91 SetSize(bmpNormal
.GetWidth(), bmpNormal
.GetHeight());
95 void OnButton(wxCommandEvent
& event
) { m_combo
->ShowPopup(); }
97 virtual wxSize
DoGetBestSize() const { return GetSize(); }
100 wxComboControl
*m_combo
;
102 DECLARE_EVENT_TABLE()
105 // ----------------------------------------------------------------------------
106 // wxComboListBox is a listbox modified to be used as a popup window in a
108 // ----------------------------------------------------------------------------
110 class wxComboListBox
: public wxListBox
, public wxComboPopup
114 wxComboListBox(wxComboControl
*combo
, int style
= 0);
115 virtual ~wxComboListBox();
117 // implement wxComboPopup methods
118 virtual bool SetSelection(const wxString
& value
);
119 virtual wxControl
*GetControl() { return this; }
120 virtual void OnShow();
123 // we shouldn't return height too big from here
124 virtual wxSize
DoGetBestClientSize() const;
126 // filter mouse move events happening outside the list box
127 void OnMouseMove(wxMouseEvent
& event
);
129 // called whenever the user selects or activates a listbox item
130 void OnSelect(wxCommandEvent
& event
);
132 // used to process wxUniv actions
133 bool PerformAction(const wxControlAction
& action
,
135 const wxString
& strArg
);
138 DECLARE_EVENT_TABLE()
141 // ----------------------------------------------------------------------------
142 // wxComboTextCtrl is a simple text ctrl which forwards
143 // wxEVT_COMMAND_TEXT_UPDATED events and all key events to the combobox
144 // ----------------------------------------------------------------------------
146 class wxComboTextCtrl
: public wxTextCtrl
149 wxComboTextCtrl(wxComboControl
*combo
,
150 const wxString
& value
,
152 const wxValidator
& validator
);
155 void OnKey(wxKeyEvent
& event
);
156 void OnText(wxCommandEvent
& event
);
159 wxComboControl
*m_combo
;
161 DECLARE_EVENT_TABLE()
164 // ----------------------------------------------------------------------------
165 // event tables and such
166 // ----------------------------------------------------------------------------
168 BEGIN_EVENT_TABLE(wxComboButton
, wxButton
)
169 EVT_BUTTON(-1, wxComboButton::OnButton
)
172 BEGIN_EVENT_TABLE(wxComboListBox
, wxListBox
)
173 EVT_LISTBOX(-1, wxComboListBox::OnSelect
)
174 EVT_LISTBOX_DCLICK(-1, wxComboListBox::OnSelect
)
175 EVT_MOTION(wxComboListBox::OnMouseMove
)
178 BEGIN_EVENT_TABLE(wxComboControl
, wxControl
)
179 EVT_KEY_DOWN(wxComboControl::OnKey
)
180 EVT_KEY_UP(wxComboControl::OnKey
)
183 BEGIN_EVENT_TABLE(wxComboTextCtrl
, wxTextCtrl
)
184 EVT_KEY_DOWN(wxComboTextCtrl::OnKey
)
185 EVT_KEY_UP(wxComboTextCtrl::OnKey
)
186 EVT_TEXT(-1, wxComboTextCtrl::OnText
)
189 IMPLEMENT_DYNAMIC_CLASS(wxComboBox
, wxControl
);
191 // ============================================================================
193 // ============================================================================
195 // ----------------------------------------------------------------------------
196 // wxComboControl creation
197 // ----------------------------------------------------------------------------
199 void wxComboControl::Init()
201 m_popup
= (wxComboPopup
*)NULL
;
202 m_winPopup
= (wxPopupComboWindow
*)NULL
;
203 m_isPopupShown
= FALSE
;
208 bool wxComboControl::Create(wxWindow
*parent
,
210 const wxString
& value
,
214 const wxValidator
& validator
,
215 const wxString
& name
)
217 // first create our own window, i.e. the one which will contain all
219 style
&= ~wxBORDER_NONE
;
220 style
|= wxBORDER_SUNKEN
;
221 if ( !wxControl::Create(parent
, id
, pos
, size
, style
, validator
, name
) )
224 // create the text control and the button as our siblings (*not* children),
225 // don't care about size/position here - they will be set in DoMoveWindow()
226 m_btn
= new wxComboButton(this);
227 m_text
= new wxComboTextCtrl(this,
229 style
& wxCB_READONLY
? wxTE_READONLY
: 0,
232 // for compatibility with the other ports, the height specified is the
233 // combined height of the combobox itself and the popup
236 // ok, use default height for popup too
241 m_heightPopup
= size
.y
- DoGetBestSize().y
;
244 DoSetSize(pos
.x
, pos
.y
, size
.x
, size
.y
);
246 // create the popup window immediately here to allow creating the controls
247 // with parent == GetPopupWindow() from the derived class ctor
248 m_winPopup
= new wxPopupComboWindow(this);
250 // have to disable this window to avoid interfering it with message
251 // processing to the text and the button... but pretend it is enabled to
252 // make IsEnabled() return TRUE
253 wxControl::Enable(FALSE
); // don't use non virtual Disable() here!
256 CreateInputHandler(wxINP_HANDLER_COMBOBOX
);
261 wxComboControl::~wxComboControl()
263 // as the button and the text control are the parent's children and not
264 // ours, we have to delete them manually - they are not deleted
265 // automatically by wxWindows when we're deleted
272 // ----------------------------------------------------------------------------
274 // ----------------------------------------------------------------------------
276 void wxComboControl::DoSetSize(int x
, int y
,
277 int width
, int height
,
280 // combo height is always fixed
281 wxControl::DoSetSize(x
, y
, width
, DoGetBestSize().y
, sizeFlags
);
284 wxSize
wxComboControl::DoGetBestClientSize() const
286 wxSize sizeBtn
= m_btn
->GetBestSize(),
287 sizeText
= m_text
->GetBestSize();
289 return wxSize(sizeBtn
.x
+ sizeText
.x
, wxMax(sizeBtn
.y
, sizeText
.y
));
292 void wxComboControl::DoMoveWindow(int x
, int y
, int width
, int height
)
294 wxControl::DoMoveWindow(x
, y
, width
, height
);
296 // position the subcontrols inside the client area
297 wxRect rectBorders
= GetRenderer()->GetBorderDimensions(GetBorder());
300 width
-= rectBorders
.x
+ rectBorders
.width
;
301 height
-= rectBorders
.y
+ rectBorders
.height
;
303 wxSize sizeBtn
= m_btn
->GetSize();
305 wxCoord wText
= width
- sizeBtn
.x
;
306 m_text
->SetSize(x
, y
, wText
, height
);
307 m_btn
->SetSize(x
+ wText
, y
, sizeBtn
.x
, height
);
310 // ----------------------------------------------------------------------------
312 // ----------------------------------------------------------------------------
314 bool wxComboControl::Enable(bool enable
)
316 if ( !wxControl::Enable(enable
) )
319 m_btn
->Enable(enable
);
320 m_text
->Enable(enable
);
325 bool wxComboControl::Show(bool show
)
327 if ( !wxControl::Show(show
) )
339 // ----------------------------------------------------------------------------
340 // popup window handling
341 // ----------------------------------------------------------------------------
343 void wxComboControl::SetPopupControl(wxComboPopup
*popup
)
348 void wxComboControl::ShowPopup()
350 wxCHECK_RET( m_popup
, _T("no popup to show in wxComboControl") );
351 wxCHECK_RET( !IsPopupShown(), _T("popup window already shown") );
353 wxControl
*control
= m_popup
->GetControl();
355 // size and position the popup window correctly
356 m_winPopup
->SetSize(GetSize().x
,
357 m_heightPopup
== -1 ? control
->GetBestSize().y
359 wxSize sizePopup
= m_winPopup
->GetClientSize();
360 control
->SetSize(0, 0, sizePopup
.x
, sizePopup
.y
);
362 // some controls don't accept the size we give then: e.g. a listbox may
363 // require more space to show its last row
364 wxSize sizeReal
= control
->GetSize();
365 if ( sizeReal
!= sizePopup
)
367 m_winPopup
->SetClientSize(sizeReal
);
370 m_winPopup
->PositionNearCombo();
373 m_winPopup
->Popup(m_text
);
375 m_popup
->SetSelection(m_text
->GetValue());
377 m_isPopupShown
= TRUE
;
380 void wxComboControl::HidePopup()
382 wxCHECK_RET( m_popup
, _T("no popup to hide in wxComboControl") );
383 wxCHECK_RET( IsPopupShown(), _T("popup window not shown") );
385 m_winPopup
->Dismiss();
387 m_isPopupShown
= FALSE
;
390 void wxComboControl::OnSelect(const wxString
& value
)
392 m_text
->SetValue(value
);
398 void wxComboControl::OnDismiss()
404 // ----------------------------------------------------------------------------
406 // ----------------------------------------------------------------------------
408 wxComboTextCtrl::wxComboTextCtrl(wxComboControl
*combo
,
409 const wxString
& value
,
411 const wxValidator
& validator
)
412 : wxTextCtrl(combo
->GetParent(), -1, value
,
413 wxDefaultPosition
, wxDefaultSize
,
414 wxBORDER_NONE
| style
,
420 void wxComboTextCtrl::OnText(wxCommandEvent
& event
)
422 if ( m_combo
->IsPopupShown() )
424 m_combo
->GetPopupControl()->SetSelection(GetValue());
427 // we need to make a copy of the event to have the correct originating
429 wxCommandEvent event2
= event
;
430 event2
.SetEventObject(m_combo
);
431 event2
.SetId(m_combo
->GetId());
433 // there is a small incompatibility with wxMSW here: the combobox gets the
434 // event before the text control in our case which corresponds to SMW
435 // CBN_EDITUPDATE notification and not CBN_EDITCHANGE one wxMSW currently
438 // if this is really a problem, we can play games with the event handlers
439 // to circumvent this
440 (void)m_combo
->ProcessEvent(event2
);
445 // pass the keys we don't process to the combo first
446 void wxComboTextCtrl::OnKey(wxKeyEvent
& event
)
448 switch ( event
.GetKeyCode() )
451 // the popup control gets it first but only if it is shown
452 if ( !m_combo
->IsPopupShown() )
463 (void)m_combo
->ProcessEvent(event
);
470 // ----------------------------------------------------------------------------
472 // ----------------------------------------------------------------------------
474 wxComboListBox::wxComboListBox(wxComboControl
*combo
, int style
)
475 : wxListBox(combo
->GetPopupWindow(), -1,
476 wxDefaultPosition
, wxDefaultSize
,
478 wxBORDER_SIMPLE
| wxLB_INT_HEIGHT
| style
),
481 // we don't react to the mouse events outside the window at all
485 wxComboListBox::~wxComboListBox()
489 bool wxComboListBox::SetSelection(const wxString
& value
)
491 // FindItem() would just find the current item for an empty string (it
492 // always matches), but we want to show the first one in such case
497 wxListBox::SetSelection(0);
499 //else: empty listbox - nothing to do
501 else if ( !FindItem(value
) )
510 void wxComboListBox::OnSelect(wxCommandEvent
& event
)
512 // first let the user code have the event
514 // all fields are already filled by the listbox, just change the event
515 // type and send it to the combo
516 wxCommandEvent event2
= event
;
517 event2
.SetEventType(wxEVT_COMMAND_COMBOBOX_SELECTED
);
518 event2
.SetEventObject(m_combo
);
519 event2
.SetId(m_combo
->GetId());
520 m_combo
->ProcessEvent(event2
);
522 // next update the combo and close the listbox
523 m_combo
->OnSelect(event
.GetString());
526 void wxComboListBox::OnShow()
530 bool wxComboListBox::PerformAction(const wxControlAction
& action
,
532 const wxString
& strArg
)
535 if ( action
== wxACTION_LISTBOX_FIND
)
537 // we don't let the listbox handle this as instead of just using the
538 // single key presses, as usual, we use the text ctrl value as prefix
539 // and this is done by wxComboControl itself
543 return wxListBox::PerformAction(action
, numArg
, strArg
);
546 void wxComboListBox::OnMouseMove(wxMouseEvent
& event
)
548 // while a wxComboListBox is shown, it always has capture, so if it doesn't
549 // we're about to go away anyhow (normally this shouldn't happen at all,
550 // but I don't put assert here as it just might do on other platforms and
551 // it doesn't break anythign anyhow)
552 if ( this == wxWindow::GetCapture() )
554 if ( HitTest(event
.GetPosition()) == wxHT_WINDOW_INSIDE
)
558 //else: popup shouldn't react to the mouse motions outside it, it only
559 // captures the mouse to be able to detect when it must be
560 // dismissed, so don't call Skip()
564 wxSize
wxComboListBox::DoGetBestClientSize() const
566 // don't return size too big or we risk to not fit on the screen
567 wxSize size
= wxListBox::DoGetBestClientSize();
568 wxCoord hChar
= GetCharHeight();
570 int nLines
= size
.y
/ hChar
;
572 // 10 is the same limit as used by wxMSW
581 // ----------------------------------------------------------------------------
583 // ----------------------------------------------------------------------------
585 void wxComboBox::Init()
587 m_lbox
= (wxListBox
*)NULL
;
590 bool wxComboBox::Create(wxWindow
*parent
,
592 const wxString
& value
,
596 const wxString
*choices
,
598 const wxValidator
& validator
,
599 const wxString
& name
)
601 if ( !wxComboControl::Create(parent
, id
, value
, pos
, size
, style
,
607 wxComboListBox
*combolbox
=
608 new wxComboListBox(this, style
& wxCB_SORT
? wxLB_SORT
: 0);
610 m_lbox
->Set(n
, choices
);
612 SetPopupControl(combolbox
);
617 wxComboBox::~wxComboBox()
621 // ----------------------------------------------------------------------------
622 // wxComboBox methods forwarded to wxTextCtrl
623 // ----------------------------------------------------------------------------
625 wxString
wxComboBox::GetValue() const
627 return GetText()->GetValue();
630 void wxComboBox::SetValue(const wxString
& value
)
632 GetText()->SetValue(value
);
635 void wxComboBox::Copy()
640 void wxComboBox::Cut()
645 void wxComboBox::Paste()
650 void wxComboBox::SetInsertionPoint(long pos
)
652 GetText()->SetInsertionPoint(pos
);
655 void wxComboBox::SetInsertionPointEnd()
657 GetText()->SetInsertionPointEnd();
660 long wxComboBox::GetInsertionPoint() const
662 return GetText()->GetInsertionPoint();
665 long wxComboBox::GetLastPosition() const
667 return GetText()->GetLastPosition();
670 void wxComboBox::Replace(long from
, long to
, const wxString
& value
)
672 GetText()->Replace(from
, to
, value
);
675 void wxComboBox::Remove(long from
, long to
)
677 GetText()->Remove(from
, to
);
680 void wxComboBox::SetSelection(long from
, long to
)
682 GetText()->SetSelection(from
, to
);
685 void wxComboBox::SetEditable(bool editable
)
687 GetText()->SetEditable(editable
);
690 // ----------------------------------------------------------------------------
691 // wxComboBox methods forwarded to wxListBox
692 // ----------------------------------------------------------------------------
694 void wxComboBox::Clear()
699 void wxComboBox::Delete(int n
)
701 GetLBox()->Delete(n
);
704 int wxComboBox::GetCount() const
706 return GetLBox()->GetCount();
709 wxString
wxComboBox::GetString(int n
) const
711 return GetLBox()->GetString(n
);
714 void wxComboBox::SetString(int n
, const wxString
& s
)
716 GetLBox()->SetString(n
, s
);
719 int wxComboBox::FindString(const wxString
& s
) const
721 return GetLBox()->FindString(s
);
724 void wxComboBox::Select(int n
)
726 wxCHECK_RET( (n
>= 0) && (n
< GetCount()), _T("invalid combobox index") );
728 GetLBox()->SetSelection(n
);
729 GetText()->SetValue(GetLBox()->GetString(n
));
732 int wxComboBox::GetSelection() const
734 // if the current value isn't one of the listbox strings, return -1
735 return FindString(GetText()->GetValue());
738 int wxComboBox::DoAppend(const wxString
& item
)
740 return GetLBox()->Append(item
);
743 void wxComboBox::DoSetItemClientData(int n
, void* clientData
)
745 GetLBox()->SetClientData(n
, clientData
);
748 void *wxComboBox::DoGetItemClientData(int n
) const
750 return GetLBox()->GetClientData(n
);
753 void wxComboBox::DoSetItemClientObject(int n
, wxClientData
* clientData
)
755 GetLBox()->SetClientObject(n
, clientData
);
758 wxClientData
* wxComboBox::DoGetItemClientObject(int n
) const
760 return GetLBox()->GetClientObject(n
);
763 // ----------------------------------------------------------------------------
765 // ----------------------------------------------------------------------------
767 void wxComboControl::OnKey(wxCommandEvent
& event
)
769 if ( m_isPopupShown
)
771 // pass it to the popped up control
772 (void)m_popup
->GetControl()->ProcessEvent(event
);
780 bool wxComboControl::PerformAction(const wxControlAction
& action
,
782 const wxString
& strArg
)
784 bool processed
= FALSE
;
785 if ( action
== wxACTION_COMBOBOX_POPUP
)
787 if ( !m_isPopupShown
)
794 else if ( action
== wxACTION_COMBOBOX_DISMISS
)
796 if ( m_isPopupShown
)
807 return wxControl::PerformAction(action
, numArg
, strArg
);
813 // ----------------------------------------------------------------------------
814 // wxStdComboBoxInputHandler
815 // ----------------------------------------------------------------------------
817 wxStdComboBoxInputHandler::wxStdComboBoxInputHandler(wxInputHandler
*inphand
)
818 : wxStdInputHandler(inphand
)
822 bool wxStdComboBoxInputHandler::HandleKey(wxControl
*control
,
823 const wxKeyEvent
& event
,
828 wxControlAction action
;
829 switch ( event
.GetKeyCode() )
832 action
= wxACTION_COMBOBOX_POPUP
;
836 action
= wxACTION_COMBOBOX_DISMISS
;
842 control
->PerformAction(action
);
848 return wxStdInputHandler::HandleKey(control
, event
, pressed
);
851 #endif // wxUSE_COMBOBOX