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
,
79 wxBORDER_NONE
| wxBU_EXACTFIT
)
83 wxBitmap bmpNormal
, bmpFocus
, bmpPressed
, bmpDisabled
;
85 GetRenderer()->GetComboBitmaps(&bmpNormal
,
90 SetBitmapLabel(bmpNormal
);
91 SetBitmapFocus(bmpFocus
.Ok() ? bmpFocus
: bmpNormal
);
92 SetBitmapSelected(bmpPressed
.Ok() ? bmpPressed
: bmpNormal
);
93 SetBitmapDisabled(bmpDisabled
.Ok() ? bmpDisabled
: bmpNormal
);
95 SetBestSize(wxDefaultSize
);
99 void OnButton(wxCommandEvent
& event
) { m_combo
->ShowPopup(); }
101 virtual wxSize
DoGetBestClientSize() const
103 const wxBitmap
& bmp
= GetBitmapLabel();
105 return wxSize(bmp
.GetWidth(), bmp
.GetHeight());
110 wxComboControl
*m_combo
;
112 DECLARE_EVENT_TABLE()
115 // ----------------------------------------------------------------------------
116 // wxComboListBox is a listbox modified to be used as a popup window in a
118 // ----------------------------------------------------------------------------
120 class wxComboListBox
: public wxListBox
, public wxComboPopup
124 wxComboListBox(wxComboControl
*combo
, int style
= 0);
125 virtual ~wxComboListBox();
127 // implement wxComboPopup methods
128 virtual bool SetSelection(const wxString
& value
);
129 virtual wxControl
*GetControl() { return this; }
130 virtual void OnShow();
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 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();
307 return wxSize(sizeBtn
.x
+ sizeText
.x
, wxMax(sizeBtn
.y
, sizeText
.y
));
310 void wxComboControl::DoMoveWindow(int x
, int y
, int width
, int height
)
312 wxControl::DoMoveWindow(x
, y
, width
, height
);
314 // position the subcontrols inside the client area
315 wxRect rectBorders
= GetRenderer()->GetBorderDimensions(GetBorder());
318 width
-= rectBorders
.x
+ rectBorders
.width
;
319 height
-= rectBorders
.y
+ rectBorders
.height
;
321 wxSize sizeBtn
= m_btn
->GetBestSize();
323 wxCoord wText
= width
- sizeBtn
.x
;
324 wxPoint p
= GetParent() ? GetParent()->GetClientAreaOrigin() : wxPoint(0,0);
325 m_text
->SetSize(x
- p
.x
, y
- p
.y
, wText
, height
);
326 m_btn
->SetSize(x
- p
.x
+ wText
, y
- p
.y
, sizeBtn
.x
, height
);
329 // ----------------------------------------------------------------------------
331 // ----------------------------------------------------------------------------
333 bool wxComboControl::Enable(bool enable
)
335 if ( !wxControl::Enable(enable
) )
338 m_btn
->Enable(enable
);
339 m_text
->Enable(enable
);
344 bool wxComboControl::Show(bool show
)
346 if ( !wxControl::Show(show
) )
358 // ----------------------------------------------------------------------------
359 // popup window handling
360 // ----------------------------------------------------------------------------
362 void wxComboControl::SetPopupControl(wxComboPopup
*popup
)
367 void wxComboControl::ShowPopup()
369 wxCHECK_RET( m_popup
, _T("no popup to show in wxComboControl") );
370 wxCHECK_RET( !IsPopupShown(), _T("popup window already shown") );
372 wxControl
*control
= m_popup
->GetControl();
374 // size and position the popup window correctly
375 m_winPopup
->SetSize(GetSize().x
,
376 m_heightPopup
== -1 ? control
->GetBestSize().y
378 wxSize sizePopup
= m_winPopup
->GetClientSize();
379 control
->SetSize(0, 0, sizePopup
.x
, sizePopup
.y
);
381 // some controls don't accept the size we give then: e.g. a listbox may
382 // require more space to show its last row
383 wxSize sizeReal
= control
->GetSize();
384 if ( sizeReal
!= sizePopup
)
386 m_winPopup
->SetClientSize(sizeReal
);
389 m_winPopup
->PositionNearCombo();
393 m_winPopup
->Popup(m_text
);
395 m_popup
->SetSelection(m_text
->GetValue());
397 m_isPopupShown
= TRUE
;
400 void wxComboControl::HidePopup()
402 wxCHECK_RET( m_popup
, _T("no popup to hide in wxComboControl") );
403 wxCHECK_RET( IsPopupShown(), _T("popup window not shown") );
405 m_winPopup
->Dismiss();
407 m_isPopupShown
= FALSE
;
410 void wxComboControl::OnSelect(const wxString
& value
)
412 m_text
->SetValue(value
);
418 void wxComboControl::OnDismiss()
424 // ----------------------------------------------------------------------------
426 // ----------------------------------------------------------------------------
428 wxComboTextCtrl::wxComboTextCtrl(wxComboControl
*combo
,
429 const wxString
& value
,
431 const wxValidator
& validator
)
432 : wxTextCtrl(combo
->GetParent(), -1, value
,
433 wxDefaultPosition
, wxDefaultSize
,
434 wxBORDER_NONE
| style
,
440 void wxComboTextCtrl::OnText(wxCommandEvent
& event
)
442 if ( m_combo
->IsPopupShown() )
444 m_combo
->GetPopupControl()->SetSelection(GetValue());
447 // we need to make a copy of the event to have the correct originating
449 wxCommandEvent event2
= event
;
450 event2
.SetEventObject(m_combo
);
451 event2
.SetId(m_combo
->GetId());
453 // there is a small incompatibility with wxMSW here: the combobox gets the
454 // event before the text control in our case which corresponds to SMW
455 // CBN_EDITUPDATE notification and not CBN_EDITCHANGE one wxMSW currently
458 // if this is really a problem, we can play games with the event handlers
459 // to circumvent this
460 (void)m_combo
->ProcessEvent(event2
);
465 // pass the keys we don't process to the combo first
466 void wxComboTextCtrl::OnKey(wxKeyEvent
& event
)
468 switch ( event
.GetKeyCode() )
471 // the popup control gets it first but only if it is shown
472 if ( !m_combo
->IsPopupShown() )
483 (void)m_combo
->ProcessEvent(event
);
490 // ----------------------------------------------------------------------------
492 // ----------------------------------------------------------------------------
494 wxComboListBox::wxComboListBox(wxComboControl
*combo
, int style
)
495 : wxListBox(combo
->GetPopupWindow(), -1,
496 wxDefaultPosition
, wxDefaultSize
,
498 wxBORDER_SIMPLE
| wxLB_INT_HEIGHT
| style
),
501 // we don't react to the mouse events outside the window at all
505 wxComboListBox::~wxComboListBox()
509 bool wxComboListBox::SetSelection(const wxString
& value
)
511 // FindItem() would just find the current item for an empty string (it
512 // always matches), but we want to show the first one in such case
517 wxListBox::SetSelection(0);
519 //else: empty listbox - nothing to do
521 else if ( !FindItem(value
) )
530 void wxComboListBox::OnSelect(wxCommandEvent
& event
)
534 // first update the combo and close the listbox
535 m_combo
->OnSelect(event
.GetString());
537 // next let the user code have the event
539 // all fields are already filled by the listbox, just change the event
540 // type and send it to the combo
541 wxCommandEvent event2
= event
;
542 event2
.SetEventType(wxEVT_COMMAND_COMBOBOX_SELECTED
);
543 event2
.SetEventObject(m_combo
);
544 event2
.SetId(m_combo
->GetId());
545 m_combo
->ProcessEvent(event2
);
547 //else: ignore the events resultign from just moving the mouse initially
550 void wxComboListBox::OnShow()
552 // nobody clicked us yet
556 bool wxComboListBox::PerformAction(const wxControlAction
& action
,
558 const wxString
& strArg
)
561 if ( action
== wxACTION_LISTBOX_FIND
)
563 // we don't let the listbox handle this as instead of just using the
564 // single key presses, as usual, we use the text ctrl value as prefix
565 // and this is done by wxComboControl itself
569 return wxListBox::PerformAction(action
, numArg
, strArg
);
572 void wxComboListBox::OnLeftUp(wxMouseEvent
& event
)
574 // we should dismiss the combo now
580 void wxComboListBox::OnMouseMove(wxMouseEvent
& event
)
582 // while a wxComboListBox is shown, it always has capture, so if it doesn't
583 // we're about to go away anyhow (normally this shouldn't happen at all,
584 // but I don't put assert here as it just might do on other platforms and
585 // it doesn't break anythign anyhow)
586 if ( this == wxWindow::GetCapture() )
588 if ( HitTest(event
.GetPosition()) == wxHT_WINDOW_INSIDE
)
592 //else: popup shouldn't react to the mouse motions outside it, it only
593 // captures the mouse to be able to detect when it must be
594 // dismissed, so don't call Skip()
598 wxSize
wxComboListBox::DoGetBestClientSize() const
600 // don't return size too big or we risk to not fit on the screen
601 wxSize size
= wxListBox::DoGetBestClientSize();
602 wxCoord hChar
= GetCharHeight();
604 int nLines
= size
.y
/ hChar
;
606 // 10 is the same limit as used by wxMSW
615 // ----------------------------------------------------------------------------
617 // ----------------------------------------------------------------------------
619 void wxComboBox::Init()
621 m_lbox
= (wxListBox
*)NULL
;
624 bool wxComboBox::Create(wxWindow
*parent
,
626 const wxString
& value
,
630 const wxString
*choices
,
632 const wxValidator
& validator
,
633 const wxString
& name
)
635 if ( !wxComboControl::Create(parent
, id
, value
, pos
, size
, style
,
641 wxComboListBox
*combolbox
=
642 new wxComboListBox(this, style
& wxCB_SORT
? wxLB_SORT
: 0);
644 m_lbox
->Set(n
, choices
);
646 SetPopupControl(combolbox
);
651 wxComboBox::~wxComboBox()
655 // ----------------------------------------------------------------------------
656 // wxComboBox methods forwarded to wxTextCtrl
657 // ----------------------------------------------------------------------------
659 wxString
wxComboBox::GetValue() const
661 return GetText()->GetValue();
664 void wxComboBox::SetValue(const wxString
& value
)
666 GetText()->SetValue(value
);
669 void wxComboBox::Copy()
674 void wxComboBox::Cut()
679 void wxComboBox::Paste()
684 void wxComboBox::SetInsertionPoint(long pos
)
686 GetText()->SetInsertionPoint(pos
);
689 void wxComboBox::SetInsertionPointEnd()
691 GetText()->SetInsertionPointEnd();
694 long wxComboBox::GetInsertionPoint() const
696 return GetText()->GetInsertionPoint();
699 long wxComboBox::GetLastPosition() const
701 return GetText()->GetLastPosition();
704 void wxComboBox::Replace(long from
, long to
, const wxString
& value
)
706 GetText()->Replace(from
, to
, value
);
709 void wxComboBox::Remove(long from
, long to
)
711 GetText()->Remove(from
, to
);
714 void wxComboBox::SetSelection(long from
, long to
)
716 GetText()->SetSelection(from
, to
);
719 void wxComboBox::SetEditable(bool editable
)
721 GetText()->SetEditable(editable
);
724 // ----------------------------------------------------------------------------
725 // wxComboBox methods forwarded to wxListBox
726 // ----------------------------------------------------------------------------
728 void wxComboBox::Clear()
733 void wxComboBox::Delete(int n
)
735 GetLBox()->Delete(n
);
738 int wxComboBox::GetCount() const
740 return GetLBox()->GetCount();
743 wxString
wxComboBox::GetString(int n
) const
745 return GetLBox()->GetString(n
);
748 void wxComboBox::SetString(int n
, const wxString
& s
)
750 GetLBox()->SetString(n
, s
);
753 int wxComboBox::FindString(const wxString
& s
) const
755 return GetLBox()->FindString(s
);
758 void wxComboBox::Select(int n
)
760 wxCHECK_RET( (n
>= 0) && (n
< GetCount()), _T("invalid combobox index") );
762 GetLBox()->SetSelection(n
);
763 GetText()->SetValue(GetLBox()->GetString(n
));
766 int wxComboBox::GetSelection() const
768 // if the current value isn't one of the listbox strings, return -1
769 return FindString(GetText()->GetValue());
772 int wxComboBox::DoAppend(const wxString
& item
)
774 return GetLBox()->Append(item
);
777 void wxComboBox::DoSetItemClientData(int n
, void* clientData
)
779 GetLBox()->SetClientData(n
, clientData
);
782 void *wxComboBox::DoGetItemClientData(int n
) const
784 return GetLBox()->GetClientData(n
);
787 void wxComboBox::DoSetItemClientObject(int n
, wxClientData
* clientData
)
789 GetLBox()->SetClientObject(n
, clientData
);
792 wxClientData
* wxComboBox::DoGetItemClientObject(int n
) const
794 return GetLBox()->GetClientObject(n
);
797 // ----------------------------------------------------------------------------
799 // ----------------------------------------------------------------------------
801 void wxComboControl::OnKey(wxKeyEvent
& event
)
803 if ( m_isPopupShown
)
805 // pass it to the popped up control
806 (void)m_popup
->GetControl()->ProcessEvent(event
);
814 bool wxComboControl::PerformAction(const wxControlAction
& action
,
816 const wxString
& strArg
)
818 bool processed
= FALSE
;
819 if ( action
== wxACTION_COMBOBOX_POPUP
)
821 if ( !m_isPopupShown
)
828 else if ( action
== wxACTION_COMBOBOX_DISMISS
)
830 if ( m_isPopupShown
)
841 return wxControl::PerformAction(action
, numArg
, strArg
);
847 // ----------------------------------------------------------------------------
848 // wxStdComboBoxInputHandler
849 // ----------------------------------------------------------------------------
851 wxStdComboBoxInputHandler::wxStdComboBoxInputHandler(wxInputHandler
*inphand
)
852 : wxStdInputHandler(inphand
)
856 bool wxStdComboBoxInputHandler::HandleKey(wxInputConsumer
*consumer
,
857 const wxKeyEvent
& event
,
862 wxControlAction action
;
863 switch ( event
.GetKeyCode() )
866 action
= wxACTION_COMBOBOX_POPUP
;
870 action
= wxACTION_COMBOBOX_DISMISS
;
876 consumer
->PerformAction(action
);
882 return wxStdInputHandler::HandleKey(consumer
, event
, pressed
);
885 #endif // wxUSE_COMBOBOX