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
;
206 bool wxComboControl::Create(wxWindow
*parent
,
208 const wxString
& value
,
212 const wxValidator
& validator
,
213 const wxString
& name
)
215 // first create our own window, i.e. the one which will contain all
217 style
&= ~wxBORDER_NONE
;
218 style
|= wxBORDER_SUNKEN
;
219 if ( !wxControl::Create(parent
, id
, pos
, size
, style
, validator
, name
) )
222 // create the text control and the button as our siblings (*not* children),
223 // don't care about size/position here - they will be set in DoMoveWindow()
224 m_btn
= new wxComboButton(this);
225 m_text
= new wxComboTextCtrl(this,
227 style
& wxCB_READONLY
? wxTE_READONLY
: 0,
230 // for compatibility with the other ports, the height specified is the
231 // combined height of the combobox itself and the popup
234 // ok, use default height for popup too
239 m_heightPopup
= size
.y
- DoGetBestSize().y
;
242 DoSetSize(pos
.x
, pos
.y
, size
.x
, size
.y
);
244 // create the popup window immediately here to allow creating the controls
245 // with parent == GetPopupWindow() from the derived class ctor
246 m_winPopup
= new wxPopupComboWindow(this);
248 // have to disable this window to avoid interfering it with message
249 // processing to the text and the button... but pretend it is enabled to
250 // make IsEnabled() return TRUE
251 wxControl::Enable(FALSE
); // don't use non virtual Disable() here!
254 CreateInputHandler(wxINP_HANDLER_COMBOBOX
);
259 wxComboControl::~wxComboControl()
261 // as the button and the text control are the parent's children and not
262 // ours, we have to delete them manually - they are not deleted
263 // automatically by wxWindows when we're deleted
270 // ----------------------------------------------------------------------------
272 // ----------------------------------------------------------------------------
274 void wxComboControl::DoSetSize(int x
, int y
,
275 int width
, int height
,
278 // combo height is always fixed
279 wxControl::DoSetSize(x
, y
, width
, DoGetBestSize().y
, sizeFlags
);
282 wxSize
wxComboControl::DoGetBestClientSize() const
284 wxSize sizeBtn
= m_btn
->GetBestSize(),
285 sizeText
= m_text
->GetBestSize();
287 return wxSize(sizeBtn
.x
+ sizeText
.x
, wxMax(sizeBtn
.y
, sizeText
.y
));
290 void wxComboControl::DoMoveWindow(int x
, int y
, int width
, int height
)
292 wxControl::DoMoveWindow(x
, y
, width
, height
);
294 // position the subcontrols inside the client area
295 wxRect rectBorders
= GetRenderer()->GetBorderDimensions(GetBorder());
298 width
-= rectBorders
.x
+ rectBorders
.width
;
299 height
-= rectBorders
.y
+ rectBorders
.height
;
301 wxSize sizeBtn
= m_btn
->GetSize(),
302 sizeText
= m_text
->GetSize();
304 wxCoord wText
= width
- sizeBtn
.x
;
305 m_text
->SetSize(x
, y
, wText
, height
);
306 m_btn
->SetSize(x
+ wText
, y
, -1, height
);
309 // ----------------------------------------------------------------------------
311 // ----------------------------------------------------------------------------
313 bool wxComboControl::Enable(bool enable
)
315 if ( !wxControl::Enable(enable
) )
318 m_btn
->Enable(enable
);
319 m_text
->Enable(enable
);
324 bool wxComboControl::Show(bool show
)
326 if ( !wxControl::Show(show
) )
335 // ----------------------------------------------------------------------------
336 // popup window handling
337 // ----------------------------------------------------------------------------
339 void wxComboControl::SetPopupControl(wxComboPopup
*popup
)
344 void wxComboControl::ShowPopup()
346 wxCHECK_RET( m_popup
, _T("no popup to show in wxComboControl") );
347 wxCHECK_RET( !IsPopupShown(), _T("popup window already shown") );
349 wxControl
*control
= m_popup
->GetControl();
351 // size and position the popup window correctly
352 m_winPopup
->SetSize(GetSize().x
,
353 m_heightPopup
== -1 ? control
->GetBestSize().y
355 wxSize sizePopup
= m_winPopup
->GetClientSize();
356 control
->SetSize(0, 0, sizePopup
.x
, sizePopup
.y
);
358 // some controls don't accept the size we give then: e.g. a listbox may
359 // require more space to show its last row
360 wxSize sizeReal
= control
->GetSize();
361 if ( sizeReal
!= sizePopup
)
363 m_winPopup
->SetClientSize(sizeReal
);
366 m_winPopup
->PositionNearCombo();
369 m_winPopup
->Popup(m_text
);
371 m_popup
->SetSelection(m_text
->GetValue());
373 m_isPopupShown
= TRUE
;
376 void wxComboControl::HidePopup()
378 wxCHECK_RET( m_popup
, _T("no popup to hide in wxComboControl") );
379 wxCHECK_RET( IsPopupShown(), _T("popup window not shown") );
381 m_winPopup
->Dismiss();
383 m_isPopupShown
= FALSE
;
386 void wxComboControl::OnSelect(const wxString
& value
)
388 m_text
->SetValue(value
);
394 void wxComboControl::OnDismiss()
400 // ----------------------------------------------------------------------------
402 // ----------------------------------------------------------------------------
404 wxComboTextCtrl::wxComboTextCtrl(wxComboControl
*combo
,
405 const wxString
& value
,
407 const wxValidator
& validator
)
408 : wxTextCtrl(combo
->GetParent(), -1, value
,
409 wxDefaultPosition
, wxDefaultSize
,
410 wxBORDER_NONE
| style
,
416 void wxComboTextCtrl::OnText(wxCommandEvent
& event
)
418 if ( m_combo
->IsPopupShown() )
420 m_combo
->GetPopupControl()->SetSelection(GetValue());
423 // we need to make a copy of the event to have the correct originating
425 wxCommandEvent event2
= event
;
426 event2
.SetEventObject(m_combo
);
427 event2
.SetId(m_combo
->GetId());
429 // there is a small incompatibility with wxMSW here: the combobox gets the
430 // event before the text control in our case which corresponds to SMW
431 // CBN_EDITUPDATE notification and not CBN_EDITCHANGE one wxMSW currently
434 // if this is really a problem, we can play games with the event handlers
435 // to circumvent this
436 (void)m_combo
->ProcessEvent(event2
);
441 // pass the keys we don't process to the combo first
442 void wxComboTextCtrl::OnKey(wxKeyEvent
& event
)
444 switch ( event
.GetKeyCode() )
447 // the popup control gets it first but only if it is shown
448 if ( !m_combo
->IsPopupShown() )
459 (void)m_combo
->ProcessEvent(event
);
466 // ----------------------------------------------------------------------------
468 // ----------------------------------------------------------------------------
470 wxComboListBox::wxComboListBox(wxComboControl
*combo
, int style
)
471 : wxListBox(combo
->GetPopupWindow(), -1,
472 wxDefaultPosition
, wxDefaultSize
,
474 wxBORDER_SIMPLE
| wxLB_INT_HEIGHT
| style
),
477 // we don't react to the mouse events outside the window at all
481 wxComboListBox::~wxComboListBox()
485 bool wxComboListBox::SetSelection(const wxString
& value
)
487 // FindItem() would just find the current item for an empty string (it
488 // always matches), but we want to show the first one in such case
493 wxListBox::SetSelection(0);
495 //else: empty listbox - nothing to do
497 else if ( !FindItem(value
) )
506 void wxComboListBox::OnSelect(wxCommandEvent
& event
)
508 // first let the user code have the event
510 // all fields are already filled by the listbox, just change the event
511 // type and send it to the combo
512 wxCommandEvent event2
= event
;
513 event2
.SetEventType(wxEVT_COMMAND_COMBOBOX_SELECTED
);
514 event2
.SetEventObject(m_combo
);
515 event2
.SetId(m_combo
->GetId());
516 m_combo
->ProcessEvent(event2
);
518 // next update the combo and close the listbox
519 m_combo
->OnSelect(event
.GetString());
522 void wxComboListBox::OnShow()
526 bool wxComboListBox::PerformAction(const wxControlAction
& action
,
528 const wxString
& strArg
)
531 if ( action
== wxACTION_LISTBOX_FIND
)
533 // we don't let the listbox handle this as instead of just using the
534 // single key presses, as usual, we use the text ctrl value as prefix
535 // and this is done by wxComboControl itself
539 return wxListBox::PerformAction(action
, numArg
, strArg
);
542 void wxComboListBox::OnMouseMove(wxMouseEvent
& event
)
544 // while a wxComboListBox is shown, it always has capture, so if it doesn't
545 // we're about to go away anyhow (normally this shouldn't happen at all,
546 // but I don't put assert here as it just might do on other platforms and
547 // it doesn't break anythign anyhow)
548 if ( this == wxWindow::GetCapture() )
550 if ( HitTest(event
.GetPosition()) == wxHT_WINDOW_INSIDE
)
554 //else: popup shouldn't react to the mouse motions outside it, it only
555 // captures the mouse to be able to detect when it must be
556 // dismissed, so don't call Skip()
560 wxSize
wxComboListBox::DoGetBestClientSize() const
562 // don't return size too big or we risk to not fit on the screen
563 wxSize size
= wxListBox::DoGetBestClientSize();
564 wxCoord hChar
= GetCharHeight();
566 int nLines
= size
.y
/ hChar
;
568 // 10 is the same limit as used by wxMSW
577 // ----------------------------------------------------------------------------
579 // ----------------------------------------------------------------------------
581 void wxComboBox::Init()
583 m_lbox
= (wxListBox
*)NULL
;
586 bool wxComboBox::Create(wxWindow
*parent
,
588 const wxString
& value
,
592 const wxString
*choices
,
594 const wxValidator
& validator
,
595 const wxString
& name
)
597 if ( !wxComboControl::Create(parent
, id
, value
, pos
, size
, style
,
603 wxComboListBox
*combolbox
=
604 new wxComboListBox(this, style
& wxCB_SORT
? wxLB_SORT
: 0);
606 m_lbox
->Set(n
, choices
);
608 SetPopupControl(combolbox
);
613 wxComboBox::~wxComboBox()
617 // ----------------------------------------------------------------------------
618 // wxComboBox methods forwarded to wxTextCtrl
619 // ----------------------------------------------------------------------------
621 wxString
wxComboBox::GetValue() const
623 return GetText()->GetValue();
626 void wxComboBox::SetValue(const wxString
& value
)
628 GetText()->SetValue(value
);
631 void wxComboBox::Copy()
636 void wxComboBox::Cut()
641 void wxComboBox::Paste()
646 void wxComboBox::SetInsertionPoint(long pos
)
648 GetText()->SetInsertionPoint(pos
);
651 void wxComboBox::SetInsertionPointEnd()
653 GetText()->SetInsertionPointEnd();
656 long wxComboBox::GetInsertionPoint() const
658 return GetText()->GetInsertionPoint();
661 long wxComboBox::GetLastPosition() const
663 return GetText()->GetLastPosition();
666 void wxComboBox::Replace(long from
, long to
, const wxString
& value
)
668 GetText()->Replace(from
, to
, value
);
671 void wxComboBox::Remove(long from
, long to
)
673 GetText()->Remove(from
, to
);
676 void wxComboBox::SetSelection(long from
, long to
)
678 GetText()->SetSelection(from
, to
);
681 void wxComboBox::SetEditable(bool editable
)
683 GetText()->SetEditable(editable
);
686 // ----------------------------------------------------------------------------
687 // wxComboBox methods forwarded to wxListBox
688 // ----------------------------------------------------------------------------
690 void wxComboBox::Clear()
695 void wxComboBox::Delete(int n
)
697 GetLBox()->Delete(n
);
700 int wxComboBox::GetCount() const
702 return GetLBox()->GetCount();
705 wxString
wxComboBox::GetString(int n
) const
707 return GetLBox()->GetString(n
);
710 void wxComboBox::SetString(int n
, const wxString
& s
)
712 GetLBox()->SetString(n
, s
);
715 int wxComboBox::FindString(const wxString
& s
) const
717 return GetLBox()->FindString(s
);
720 void wxComboBox::Select(int n
)
722 wxCHECK_RET( (n
>= 0) && (n
< GetCount()), _T("invalid combobox index") );
724 GetLBox()->SetSelection(n
);
725 GetText()->SetValue(GetLBox()->GetString(n
));
728 int wxComboBox::GetSelection() const
730 // if the current value isn't one of the listbox strings, return -1
731 return FindString(GetText()->GetValue());
734 int wxComboBox::DoAppend(const wxString
& item
)
736 return GetLBox()->Append(item
);
739 void wxComboBox::DoSetItemClientData(int n
, void* clientData
)
741 GetLBox()->SetClientData(n
, clientData
);
744 void *wxComboBox::DoGetItemClientData(int n
) const
746 return GetLBox()->GetClientData(n
);
749 void wxComboBox::DoSetItemClientObject(int n
, wxClientData
* clientData
)
751 GetLBox()->SetClientObject(n
, clientData
);
754 wxClientData
* wxComboBox::DoGetItemClientObject(int n
) const
756 return GetLBox()->GetClientObject(n
);
759 // ----------------------------------------------------------------------------
761 // ----------------------------------------------------------------------------
763 void wxComboControl::OnKey(wxCommandEvent
& event
)
765 if ( m_isPopupShown
)
767 // pass it to the popped up control
768 (void)m_popup
->GetControl()->ProcessEvent(event
);
776 bool wxComboControl::PerformAction(const wxControlAction
& action
,
778 const wxString
& strArg
)
780 bool processed
= FALSE
;
781 if ( action
== wxACTION_COMBOBOX_POPUP
)
783 if ( !m_isPopupShown
)
790 else if ( action
== wxACTION_COMBOBOX_DISMISS
)
792 if ( m_isPopupShown
)
803 return wxControl::PerformAction(action
, numArg
, strArg
);
809 // ----------------------------------------------------------------------------
810 // wxStdComboBoxInputHandler
811 // ----------------------------------------------------------------------------
813 wxStdComboBoxInputHandler::wxStdComboBoxInputHandler(wxInputHandler
*inphand
)
814 : wxStdInputHandler(inphand
)
818 bool wxStdComboBoxInputHandler::HandleKey(wxControl
*control
,
819 const wxKeyEvent
& event
,
824 wxControlAction action
;
825 switch ( event
.GetKeyCode() )
828 action
= wxACTION_COMBOBOX_POPUP
;
832 action
= wxACTION_COMBOBOX_DISMISS
;
838 control
->PerformAction(action
);
844 return wxStdInputHandler::HandleKey(control
, event
, pressed
);
847 #endif // wxUSE_COMBOBOX