1 /////////////////////////////////////////////////////////////////////////////
2 // Name: univ/combobox.cpp
3 // Purpose: wxComboControl and wxComboBox implementation
4 // Author: Vadim Zeitlin
8 // Copyright: (c) 2000 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
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"
50 #include "wx/validate.h"
53 #include "wx/popupwin.h"
55 #include "wx/univ/renderer.h"
56 #include "wx/univ/inphand.h"
57 #include "wx/univ/theme.h"
60 The keyboard event flow:
62 1. they always come to the text ctrl
63 2. it forwards the ones it doesn't process to the wxComboControl
64 3. which passes them to the popup window if it is popped up
67 // ----------------------------------------------------------------------------
68 // wxComboButton is just a normal button except that it sends commands to the
69 // combobox and not its parent
70 // ----------------------------------------------------------------------------
72 class wxComboButton
: public wxBitmapButton
75 wxComboButton(wxComboControl
*combo
)
76 : wxBitmapButton(combo
->GetParent(), -1, wxNullBitmap
,
77 wxDefaultPosition
, wxDefaultSize
,
82 wxBitmap bmpNormal
, bmpPressed
, bmpDisabled
;
84 GetRenderer()->GetComboBitmaps(&bmpNormal
, &bmpPressed
, &bmpDisabled
);
85 SetBitmapLabel(bmpNormal
);
86 SetBitmapFocus(bmpNormal
);
87 SetBitmapSelected(bmpPressed
);
88 SetBitmapDisabled(bmpDisabled
);
90 SetSize(bmpNormal
.GetWidth(), bmpNormal
.GetHeight());
94 void OnButton(wxCommandEvent
& event
) { m_combo
->ShowPopup(); }
96 virtual wxSize
DoGetBestSize() const { return GetSize(); }
99 wxComboControl
*m_combo
;
101 DECLARE_EVENT_TABLE()
104 // ----------------------------------------------------------------------------
105 // wxComboListBox is a listbox modified to be used as a popup window in a
107 // ----------------------------------------------------------------------------
109 class wxComboListBox
: public wxListBox
, public wxComboPopup
113 wxComboListBox(wxComboControl
*combo
, int style
= 0);
114 virtual ~wxComboListBox();
116 // implement wxComboPopup methods
117 virtual bool SetSelection(const wxString
& value
);
118 virtual wxControl
*GetControl() { return this; }
119 virtual void OnShow();
122 // we shouldn't return height too big from here
123 virtual wxSize
DoGetBestClientSize() const;
125 // filter mouse move events happening outside the list box
126 void OnMouseMove(wxMouseEvent
& event
);
128 // called whenever the user selects or activates a listbox item
129 void OnSelect(wxCommandEvent
& event
);
131 // used to process wxUniv actions
132 bool PerformAction(const wxControlAction
& action
,
134 const wxString
& strArg
);
137 DECLARE_EVENT_TABLE()
140 // ----------------------------------------------------------------------------
141 // wxComboTextCtrl is a simple text ctrl which forwards
142 // wxEVT_COMMAND_TEXT_UPDATED events and all key events to the combobox
143 // ----------------------------------------------------------------------------
145 class wxComboTextCtrl
: public wxTextCtrl
148 wxComboTextCtrl(wxComboControl
*combo
,
149 const wxString
& value
,
151 const wxValidator
& validator
);
154 void OnKey(wxKeyEvent
& event
);
155 void OnText(wxCommandEvent
& event
);
158 wxComboControl
*m_combo
;
160 DECLARE_EVENT_TABLE()
163 // ----------------------------------------------------------------------------
164 // event tables and such
165 // ----------------------------------------------------------------------------
167 BEGIN_EVENT_TABLE(wxComboButton
, wxButton
)
168 EVT_BUTTON(-1, wxComboButton::OnButton
)
171 BEGIN_EVENT_TABLE(wxComboListBox
, wxListBox
)
172 EVT_LISTBOX(-1, wxComboListBox::OnSelect
)
173 EVT_LISTBOX_DCLICK(-1, wxComboListBox::OnSelect
)
174 EVT_MOTION(wxComboListBox::OnMouseMove
)
177 BEGIN_EVENT_TABLE(wxComboControl
, wxControl
)
178 EVT_KEY_DOWN(wxComboControl::OnKey
)
179 EVT_KEY_UP(wxComboControl::OnKey
)
182 BEGIN_EVENT_TABLE(wxComboTextCtrl
, wxTextCtrl
)
183 EVT_KEY_DOWN(wxComboTextCtrl::OnKey
)
184 EVT_KEY_UP(wxComboTextCtrl::OnKey
)
185 EVT_TEXT(-1, wxComboTextCtrl::OnText
)
188 IMPLEMENT_DYNAMIC_CLASS(wxComboBox
, wxControl
);
190 // ============================================================================
192 // ============================================================================
194 // ----------------------------------------------------------------------------
195 // wxComboControl creation
196 // ----------------------------------------------------------------------------
198 void wxComboControl::Init()
200 m_popup
= (wxComboPopup
*)NULL
;
201 m_winPopup
= (wxPopupComboWindow
*)NULL
;
202 m_isPopupShown
= FALSE
;
205 bool wxComboControl::Create(wxWindow
*parent
,
207 const wxString
& value
,
211 const wxValidator
& validator
,
212 const wxString
& name
)
214 // first create our own window, i.e. the one which will contain all
216 style
&= ~wxBORDER_NONE
;
217 style
|= wxBORDER_SUNKEN
;
218 if ( !wxControl::Create(parent
, id
, pos
, size
, style
, validator
, name
) )
221 // create the text control and the button as our siblings (*not* children),
222 // don't care about size/position here - they will be set in DoMoveWindow()
223 m_btn
= new wxComboButton(this);
224 m_text
= new wxComboTextCtrl(this,
226 style
& wxCB_READONLY
? wxTE_READONLY
: 0,
229 // for compatibility with the other ports, the height specified is the
230 // combined height of the combobox itself and the popup
233 // ok, use default height for popup too
238 m_heightPopup
= size
.y
- DoGetBestSize().y
;
241 DoSetSize(pos
.x
, pos
.y
, size
.x
, size
.y
);
243 // create the popup window immediately here to allow creating the controls
244 // with parent == GetPopupWindow() from the derived class ctor
245 m_winPopup
= new wxPopupComboWindow(this);
247 // have to disable this window to avoid interfering it with message
248 // processing to the text and the button... but pretend it is enabled to
249 // make IsEnabled() return TRUE
250 wxControl::Enable(FALSE
); // don't use non virtual Disable() here!
253 CreateInputHandler(wxINP_HANDLER_COMBOBOX
);
258 wxComboControl::~wxComboControl()
260 // as the button and the text control are the parent's children and not
261 // ours, we have to delete them manually - they are not deleted
262 // automatically by wxWindows when we're deleted
269 // ----------------------------------------------------------------------------
271 // ----------------------------------------------------------------------------
273 void wxComboControl::DoSetSize(int x
, int y
,
274 int width
, int height
,
277 // combo height is always fixed
278 wxControl::DoSetSize(x
, y
, width
, DoGetBestSize().y
, sizeFlags
);
281 wxSize
wxComboControl::DoGetBestClientSize() const
283 wxSize sizeBtn
= m_btn
->GetBestSize(),
284 sizeText
= m_text
->GetBestSize();
286 return wxSize(sizeBtn
.x
+ sizeText
.x
, wxMax(sizeBtn
.y
, sizeText
.y
));
289 void wxComboControl::DoMoveWindow(int x
, int y
, int width
, int height
)
291 wxControl::DoMoveWindow(x
, y
, width
, height
);
293 // position the subcontrols inside the client area
294 wxRect rectBorders
= GetRenderer()->GetBorderDimensions(GetBorder());
297 width
-= rectBorders
.x
+ rectBorders
.width
;
298 height
-= rectBorders
.y
+ rectBorders
.height
;
300 wxSize sizeBtn
= m_btn
->GetSize(),
301 sizeText
= m_text
->GetSize();
303 wxCoord wText
= width
- sizeBtn
.x
;
304 m_text
->SetSize(x
, y
, wText
, height
);
305 m_btn
->SetSize(x
+ wText
, y
, -1, height
);
308 // ----------------------------------------------------------------------------
310 // ----------------------------------------------------------------------------
312 bool wxComboControl::Enable(bool enable
)
314 if ( !wxControl::Enable(enable
) )
317 m_btn
->Enable(enable
);
318 m_text
->Enable(enable
);
323 bool wxComboControl::Show(bool show
)
325 if ( !wxControl::Show(show
) )
334 // ----------------------------------------------------------------------------
335 // popup window handling
336 // ----------------------------------------------------------------------------
338 void wxComboControl::SetPopupControl(wxComboPopup
*popup
)
343 void wxComboControl::ShowPopup()
345 wxCHECK_RET( m_popup
, _T("no popup to show in wxComboControl") );
346 wxCHECK_RET( !IsPopupShown(), _T("popup window already shown") );
348 wxControl
*control
= m_popup
->GetControl();
350 // size and position the popup window correctly
351 m_winPopup
->SetSize(GetSize().x
,
352 m_heightPopup
== -1 ? control
->GetBestSize().y
354 wxSize sizePopup
= m_winPopup
->GetClientSize();
355 control
->SetSize(0, 0, sizePopup
.x
, sizePopup
.y
);
357 // some controls don't accept the size we give then: e.g. a listbox may
358 // require more space to show its last row
359 wxSize sizeReal
= control
->GetSize();
360 if ( sizeReal
!= sizePopup
)
362 m_winPopup
->SetClientSize(sizeReal
);
365 m_winPopup
->PositionNearCombo();
368 m_winPopup
->Popup(m_text
);
370 m_popup
->SetSelection(m_text
->GetValue());
372 m_isPopupShown
= TRUE
;
375 void wxComboControl::HidePopup()
377 wxCHECK_RET( m_popup
, _T("no popup to hide in wxComboControl") );
378 wxCHECK_RET( IsPopupShown(), _T("popup window not shown") );
380 m_winPopup
->Dismiss();
382 m_isPopupShown
= FALSE
;
385 void wxComboControl::OnSelect(const wxString
& value
)
387 m_text
->SetValue(value
);
393 void wxComboControl::OnDismiss()
399 // ----------------------------------------------------------------------------
401 // ----------------------------------------------------------------------------
403 wxComboTextCtrl::wxComboTextCtrl(wxComboControl
*combo
,
404 const wxString
& value
,
406 const wxValidator
& validator
)
407 : wxTextCtrl(combo
->GetParent(), -1, value
,
408 wxDefaultPosition
, wxDefaultSize
,
409 wxBORDER_NONE
| style
,
415 void wxComboTextCtrl::OnText(wxCommandEvent
& event
)
417 if ( m_combo
->IsPopupShown() )
419 m_combo
->GetPopupControl()->SetSelection(GetValue());
422 // we need to make a copy of the event to have the correct originating
424 wxCommandEvent event2
= event
;
425 event2
.SetEventObject(m_combo
);
426 event2
.SetId(m_combo
->GetId());
428 // there is a small incompatibility with wxMSW here: the combobox gets the
429 // event before the text control in our case which corresponds to SMW
430 // CBN_EDITUPDATE notification and not CBN_EDITCHANGE one wxMSW currently
433 // if this is really a problem, we can play games with the event handlers
434 // to circumvent this
435 (void)m_combo
->ProcessEvent(event2
);
440 // pass the keys we don't process to the combo first
441 void wxComboTextCtrl::OnKey(wxKeyEvent
& event
)
443 switch ( event
.GetKeyCode() )
446 // the popup control gets it first but only if it is shown
447 if ( !m_combo
->IsPopupShown() )
458 (void)m_combo
->ProcessEvent(event
);
465 // ----------------------------------------------------------------------------
467 // ----------------------------------------------------------------------------
469 wxComboListBox::wxComboListBox(wxComboControl
*combo
, int style
)
470 : wxListBox(combo
->GetPopupWindow(), -1,
471 wxDefaultPosition
, wxDefaultSize
,
473 wxBORDER_SIMPLE
| wxLB_INT_HEIGHT
| style
),
476 // we don't react to the mouse events outside the window at all
480 wxComboListBox::~wxComboListBox()
484 bool wxComboListBox::SetSelection(const wxString
& value
)
486 // FindItem() would just find the current item for an empty string (it
487 // always matches), but we want to show the first one in such case
492 wxListBox::SetSelection(0);
494 //else: empty listbox - nothing to do
496 else if ( !FindItem(value
) )
505 void wxComboListBox::OnSelect(wxCommandEvent
& event
)
507 // first let the user code have the event
509 // all fields are already filled by the listbox, just change the event
510 // type and send it to the combo
511 wxCommandEvent event2
= event
;
512 event2
.SetEventType(wxEVT_COMMAND_COMBOBOX_SELECTED
);
513 event2
.SetEventObject(m_combo
);
514 event2
.SetId(m_combo
->GetId());
515 m_combo
->ProcessEvent(event2
);
517 // next update the combo and close the listbox
518 m_combo
->OnSelect(event
.GetString());
521 void wxComboListBox::OnShow()
525 bool wxComboListBox::PerformAction(const wxControlAction
& action
,
527 const wxString
& strArg
)
530 if ( action
== wxACTION_LISTBOX_FIND
)
532 // we don't let the listbox handle this as instead of just using the
533 // single key presses, as usual, we use the text ctrl value as prefix
534 // and this is done by wxComboControl itself
538 return wxListBox::PerformAction(action
, numArg
, strArg
);
541 void wxComboListBox::OnMouseMove(wxMouseEvent
& event
)
543 // while a wxComboListBox is shown, it always has capture, so if it doesn't
544 // we're about to go away anyhow (normally this shouldn't happen at all,
545 // but I don't put assert here as it just might do on other platforms and
546 // it doesn't break anythign anyhow)
547 if ( this == wxWindow::GetCapture() )
549 if ( HitTest(event
.GetPosition()) == wxHT_WINDOW_INSIDE
)
553 //else: popup shouldn't react to the mouse motions outside it, it only
554 // captures the mouse to be able to detect when it must be
555 // dismissed, so don't call Skip()
559 wxSize
wxComboListBox::DoGetBestClientSize() const
561 // don't return size too big or we risk to not fit on the screen
562 wxSize size
= wxListBox::DoGetBestClientSize();
563 wxCoord hChar
= GetCharHeight();
565 int nLines
= size
.y
/ hChar
;
567 // 10 is the same limit as used by wxMSW
576 // ----------------------------------------------------------------------------
578 // ----------------------------------------------------------------------------
580 void wxComboBox::Init()
582 m_lbox
= (wxListBox
*)NULL
;
585 bool wxComboBox::Create(wxWindow
*parent
,
587 const wxString
& value
,
591 const wxString
*choices
,
593 const wxValidator
& validator
,
594 const wxString
& name
)
596 if ( !wxComboControl::Create(parent
, id
, value
, pos
, size
, style
,
602 wxComboListBox
*combolbox
=
603 new wxComboListBox(this, style
& wxCB_SORT
? wxLB_SORT
: 0);
605 m_lbox
->Set(n
, choices
);
607 SetPopupControl(combolbox
);
612 wxComboBox::~wxComboBox()
616 // ----------------------------------------------------------------------------
617 // wxComboBox methods forwarded to wxTextCtrl
618 // ----------------------------------------------------------------------------
620 wxString
wxComboBox::GetValue() const
622 return GetText()->GetValue();
625 void wxComboBox::SetValue(const wxString
& value
)
627 GetText()->SetValue(value
);
630 void wxComboBox::Copy()
635 void wxComboBox::Cut()
640 void wxComboBox::Paste()
645 void wxComboBox::SetInsertionPoint(long pos
)
647 GetText()->SetInsertionPoint(pos
);
650 void wxComboBox::SetInsertionPointEnd()
652 GetText()->SetInsertionPointEnd();
655 long wxComboBox::GetInsertionPoint() const
657 return GetText()->GetInsertionPoint();
660 long wxComboBox::GetLastPosition() const
662 return GetText()->GetLastPosition();
665 void wxComboBox::Replace(long from
, long to
, const wxString
& value
)
667 GetText()->Replace(from
, to
, value
);
670 void wxComboBox::Remove(long from
, long to
)
672 GetText()->Remove(from
, to
);
675 void wxComboBox::SetSelection(long from
, long to
)
677 GetText()->SetSelection(from
, to
);
680 void wxComboBox::SetEditable(bool editable
)
682 GetText()->SetEditable(editable
);
685 // ----------------------------------------------------------------------------
686 // wxComboBox methods forwarded to wxListBox
687 // ----------------------------------------------------------------------------
689 void wxComboBox::Clear()
694 void wxComboBox::Delete(int n
)
696 GetLBox()->Delete(n
);
699 int wxComboBox::GetCount() const
701 return GetLBox()->GetCount();
704 wxString
wxComboBox::GetString(int n
) const
706 return GetLBox()->GetString(n
);
709 void wxComboBox::SetString(int n
, const wxString
& s
)
711 GetLBox()->SetString(n
, s
);
714 int wxComboBox::FindString(const wxString
& s
) const
716 return GetLBox()->FindString(s
);
719 void wxComboBox::Select(int n
)
721 wxCHECK_RET( (n
>= 0) && (n
< GetCount()), _T("invalid combobox index") );
723 GetLBox()->SetSelection(n
);
724 GetText()->SetValue(GetLBox()->GetString(n
));
727 int wxComboBox::GetSelection() const
729 // if the current value isn't one of the listbox strings, return -1
730 return FindString(GetText()->GetValue());
733 int wxComboBox::DoAppend(const wxString
& item
)
735 return GetLBox()->Append(item
);
738 void wxComboBox::DoSetItemClientData(int n
, void* clientData
)
740 GetLBox()->SetClientData(n
, clientData
);
743 void *wxComboBox::DoGetItemClientData(int n
) const
745 return GetLBox()->GetClientData(n
);
748 void wxComboBox::DoSetItemClientObject(int n
, wxClientData
* clientData
)
750 GetLBox()->SetClientObject(n
, clientData
);
753 wxClientData
* wxComboBox::DoGetItemClientObject(int n
) const
755 return GetLBox()->GetClientObject(n
);
758 // ----------------------------------------------------------------------------
760 // ----------------------------------------------------------------------------
762 void wxComboControl::OnKey(wxCommandEvent
& event
)
764 if ( m_isPopupShown
)
766 // pass it to the popped up control
767 (void)m_popup
->GetControl()->ProcessEvent(event
);
775 bool wxComboControl::PerformAction(const wxControlAction
& action
,
777 const wxString
& strArg
)
779 bool processed
= FALSE
;
780 if ( action
== wxACTION_COMBOBOX_POPUP
)
782 if ( !m_isPopupShown
)
789 else if ( action
== wxACTION_COMBOBOX_DISMISS
)
791 if ( m_isPopupShown
)
802 return wxControl::PerformAction(action
, numArg
, strArg
);
808 // ----------------------------------------------------------------------------
809 // wxStdComboBoxInputHandler
810 // ----------------------------------------------------------------------------
812 wxStdComboBoxInputHandler::wxStdComboBoxInputHandler(wxInputHandler
*inphand
)
813 : wxStdInputHandler(inphand
)
817 bool wxStdComboBoxInputHandler::HandleKey(wxControl
*control
,
818 const wxKeyEvent
& event
,
823 wxControlAction action
;
824 switch ( event
.GetKeyCode() )
827 action
= wxACTION_COMBOBOX_POPUP
;
831 action
= wxACTION_COMBOBOX_DISMISS
;
837 control
->PerformAction(action
);
843 return wxStdInputHandler::HandleKey(control
, event
, pressed
);
846 #endif // wxUSE_COMBOBOX