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 // 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 DECLARE_EVENT_TABLE()
151 // ----------------------------------------------------------------------------
152 // wxComboTextCtrl is a simple text ctrl which forwards
153 // wxEVT_COMMAND_TEXT_UPDATED events and all key events to the combobox
154 // ----------------------------------------------------------------------------
156 class wxComboTextCtrl
: public wxTextCtrl
159 wxComboTextCtrl(wxComboControl
*combo
,
160 const wxString
& value
,
162 const wxValidator
& validator
);
165 void OnKey(wxKeyEvent
& event
);
166 void OnText(wxCommandEvent
& event
);
169 wxComboControl
*m_combo
;
171 DECLARE_EVENT_TABLE()
174 // ----------------------------------------------------------------------------
175 // event tables and such
176 // ----------------------------------------------------------------------------
178 BEGIN_EVENT_TABLE(wxComboButton
, wxButton
)
179 EVT_BUTTON(-1, wxComboButton::OnButton
)
182 BEGIN_EVENT_TABLE(wxComboListBox
, wxListBox
)
183 EVT_LISTBOX(-1, wxComboListBox::OnSelect
)
184 EVT_LISTBOX_DCLICK(-1, wxComboListBox::OnSelect
)
185 EVT_MOTION(wxComboListBox::OnMouseMove
)
188 BEGIN_EVENT_TABLE(wxComboControl
, wxControl
)
189 EVT_KEY_DOWN(wxComboControl::OnKey
)
190 EVT_KEY_UP(wxComboControl::OnKey
)
193 BEGIN_EVENT_TABLE(wxComboTextCtrl
, wxTextCtrl
)
194 EVT_KEY_DOWN(wxComboTextCtrl::OnKey
)
195 EVT_KEY_UP(wxComboTextCtrl::OnKey
)
196 EVT_TEXT(-1, wxComboTextCtrl::OnText
)
199 IMPLEMENT_DYNAMIC_CLASS(wxComboBox
, wxControl
);
201 // ============================================================================
203 // ============================================================================
205 // ----------------------------------------------------------------------------
206 // wxComboControl creation
207 // ----------------------------------------------------------------------------
209 void wxComboControl::Init()
211 m_popup
= (wxComboPopup
*)NULL
;
212 m_winPopup
= (wxPopupComboWindow
*)NULL
;
213 m_isPopupShown
= FALSE
;
218 bool wxComboControl::Create(wxWindow
*parent
,
220 const wxString
& value
,
224 const wxValidator
& validator
,
225 const wxString
& name
)
227 // first create our own window, i.e. the one which will contain all
229 style
&= ~wxBORDER_NONE
;
230 style
|= wxBORDER_SUNKEN
;
231 if ( !wxControl::Create(parent
, id
, pos
, size
, style
, validator
, name
) )
234 // create the text control and the button as our siblings (*not* children),
235 // don't care about size/position here - they will be set in DoMoveWindow()
236 m_btn
= new wxComboButton(this);
237 m_text
= new wxComboTextCtrl(this,
239 style
& wxCB_READONLY
? wxTE_READONLY
: 0,
242 // for compatibility with the other ports, the height specified is the
243 // combined height of the combobox itself and the popup
246 // ok, use default height for popup too
251 m_heightPopup
= size
.y
- DoGetBestSize().y
;
257 // create the popup window immediately here to allow creating the controls
258 // with parent == GetPopupWindow() from the derived class ctor
259 m_winPopup
= new wxPopupComboWindow(this);
261 // have to disable this window to avoid interfering it with message
262 // processing to the text and the button... but pretend it is enabled to
263 // make IsEnabled() return TRUE
264 wxControl::Enable(FALSE
); // don't use non virtual Disable() here!
267 CreateInputHandler(wxINP_HANDLER_COMBOBOX
);
272 wxComboControl::~wxComboControl()
274 // as the button and the text control are the parent's children and not
275 // ours, we have to delete them manually - they are not deleted
276 // automatically by wxWindows when we're deleted
283 // ----------------------------------------------------------------------------
285 // ----------------------------------------------------------------------------
287 void wxComboControl::DoSetSize(int x
, int y
,
288 int width
, int height
,
291 // combo height is always fixed
292 wxControl::DoSetSize(x
, y
, width
, DoGetBestSize().y
, sizeFlags
);
295 wxSize
wxComboControl::DoGetBestClientSize() const
297 wxSize sizeBtn
= m_btn
->GetBestSize(),
298 sizeText
= m_text
->GetBestSize();
300 return wxSize(sizeBtn
.x
+ sizeText
.x
, wxMax(sizeBtn
.y
, sizeText
.y
));
303 void wxComboControl::DoMoveWindow(int x
, int y
, int width
, int height
)
305 wxControl::DoMoveWindow(x
, y
, width
, height
);
307 // position the subcontrols inside the client area
308 wxRect rectBorders
= GetRenderer()->GetBorderDimensions(GetBorder());
311 width
-= rectBorders
.x
+ rectBorders
.width
;
312 height
-= rectBorders
.y
+ rectBorders
.height
;
314 wxSize sizeBtn
= m_btn
->GetBestSize();
316 wxCoord wText
= width
- sizeBtn
.x
;
317 m_text
->SetSize(x
, y
, wText
, height
);
318 m_btn
->SetSize(x
+ wText
, y
, sizeBtn
.x
, height
);
321 // ----------------------------------------------------------------------------
323 // ----------------------------------------------------------------------------
325 bool wxComboControl::Enable(bool enable
)
327 if ( !wxControl::Enable(enable
) )
330 m_btn
->Enable(enable
);
331 m_text
->Enable(enable
);
336 bool wxComboControl::Show(bool show
)
338 if ( !wxControl::Show(show
) )
350 // ----------------------------------------------------------------------------
351 // popup window handling
352 // ----------------------------------------------------------------------------
354 void wxComboControl::SetPopupControl(wxComboPopup
*popup
)
359 void wxComboControl::ShowPopup()
361 wxCHECK_RET( m_popup
, _T("no popup to show in wxComboControl") );
362 wxCHECK_RET( !IsPopupShown(), _T("popup window already shown") );
364 wxControl
*control
= m_popup
->GetControl();
366 // size and position the popup window correctly
367 m_winPopup
->SetSize(GetSize().x
,
368 m_heightPopup
== -1 ? control
->GetBestSize().y
370 wxSize sizePopup
= m_winPopup
->GetClientSize();
371 control
->SetSize(0, 0, sizePopup
.x
, sizePopup
.y
);
373 // some controls don't accept the size we give then: e.g. a listbox may
374 // require more space to show its last row
375 wxSize sizeReal
= control
->GetSize();
376 if ( sizeReal
!= sizePopup
)
378 m_winPopup
->SetClientSize(sizeReal
);
381 m_winPopup
->PositionNearCombo();
384 m_winPopup
->Popup(m_text
);
386 m_popup
->SetSelection(m_text
->GetValue());
388 m_isPopupShown
= TRUE
;
391 void wxComboControl::HidePopup()
393 wxCHECK_RET( m_popup
, _T("no popup to hide in wxComboControl") );
394 wxCHECK_RET( IsPopupShown(), _T("popup window not shown") );
396 m_winPopup
->Dismiss();
398 m_isPopupShown
= FALSE
;
401 void wxComboControl::OnSelect(const wxString
& value
)
403 m_text
->SetValue(value
);
409 void wxComboControl::OnDismiss()
415 // ----------------------------------------------------------------------------
417 // ----------------------------------------------------------------------------
419 wxComboTextCtrl::wxComboTextCtrl(wxComboControl
*combo
,
420 const wxString
& value
,
422 const wxValidator
& validator
)
423 : wxTextCtrl(combo
->GetParent(), -1, value
,
424 wxDefaultPosition
, wxDefaultSize
,
425 wxBORDER_NONE
| style
,
431 void wxComboTextCtrl::OnText(wxCommandEvent
& event
)
433 if ( m_combo
->IsPopupShown() )
435 m_combo
->GetPopupControl()->SetSelection(GetValue());
438 // we need to make a copy of the event to have the correct originating
440 wxCommandEvent event2
= event
;
441 event2
.SetEventObject(m_combo
);
442 event2
.SetId(m_combo
->GetId());
444 // there is a small incompatibility with wxMSW here: the combobox gets the
445 // event before the text control in our case which corresponds to SMW
446 // CBN_EDITUPDATE notification and not CBN_EDITCHANGE one wxMSW currently
449 // if this is really a problem, we can play games with the event handlers
450 // to circumvent this
451 (void)m_combo
->ProcessEvent(event2
);
456 // pass the keys we don't process to the combo first
457 void wxComboTextCtrl::OnKey(wxKeyEvent
& event
)
459 switch ( event
.GetKeyCode() )
462 // the popup control gets it first but only if it is shown
463 if ( !m_combo
->IsPopupShown() )
474 (void)m_combo
->ProcessEvent(event
);
481 // ----------------------------------------------------------------------------
483 // ----------------------------------------------------------------------------
485 wxComboListBox::wxComboListBox(wxComboControl
*combo
, int style
)
486 : wxListBox(combo
->GetPopupWindow(), -1,
487 wxDefaultPosition
, wxDefaultSize
,
489 wxBORDER_SIMPLE
| wxLB_INT_HEIGHT
| style
),
492 // we don't react to the mouse events outside the window at all
496 wxComboListBox::~wxComboListBox()
500 bool wxComboListBox::SetSelection(const wxString
& value
)
502 // FindItem() would just find the current item for an empty string (it
503 // always matches), but we want to show the first one in such case
508 wxListBox::SetSelection(0);
510 //else: empty listbox - nothing to do
512 else if ( !FindItem(value
) )
521 void wxComboListBox::OnSelect(wxCommandEvent
& event
)
523 // first let the user code have the event
525 // all fields are already filled by the listbox, just change the event
526 // type and send it to the combo
527 wxCommandEvent event2
= event
;
528 event2
.SetEventType(wxEVT_COMMAND_COMBOBOX_SELECTED
);
529 event2
.SetEventObject(m_combo
);
530 event2
.SetId(m_combo
->GetId());
531 m_combo
->ProcessEvent(event2
);
533 // next update the combo and close the listbox
534 m_combo
->OnSelect(event
.GetString());
537 void wxComboListBox::OnShow()
541 bool wxComboListBox::PerformAction(const wxControlAction
& action
,
543 const wxString
& strArg
)
546 if ( action
== wxACTION_LISTBOX_FIND
)
548 // we don't let the listbox handle this as instead of just using the
549 // single key presses, as usual, we use the text ctrl value as prefix
550 // and this is done by wxComboControl itself
554 return wxListBox::PerformAction(action
, numArg
, strArg
);
557 void wxComboListBox::OnMouseMove(wxMouseEvent
& event
)
559 // while a wxComboListBox is shown, it always has capture, so if it doesn't
560 // we're about to go away anyhow (normally this shouldn't happen at all,
561 // but I don't put assert here as it just might do on other platforms and
562 // it doesn't break anythign anyhow)
563 if ( this == wxWindow::GetCapture() )
565 if ( HitTest(event
.GetPosition()) == wxHT_WINDOW_INSIDE
)
569 //else: popup shouldn't react to the mouse motions outside it, it only
570 // captures the mouse to be able to detect when it must be
571 // dismissed, so don't call Skip()
575 wxSize
wxComboListBox::DoGetBestClientSize() const
577 // don't return size too big or we risk to not fit on the screen
578 wxSize size
= wxListBox::DoGetBestClientSize();
579 wxCoord hChar
= GetCharHeight();
581 int nLines
= size
.y
/ hChar
;
583 // 10 is the same limit as used by wxMSW
592 // ----------------------------------------------------------------------------
594 // ----------------------------------------------------------------------------
596 void wxComboBox::Init()
598 m_lbox
= (wxListBox
*)NULL
;
601 bool wxComboBox::Create(wxWindow
*parent
,
603 const wxString
& value
,
607 const wxString
*choices
,
609 const wxValidator
& validator
,
610 const wxString
& name
)
612 if ( !wxComboControl::Create(parent
, id
, value
, pos
, size
, style
,
618 wxComboListBox
*combolbox
=
619 new wxComboListBox(this, style
& wxCB_SORT
? wxLB_SORT
: 0);
621 m_lbox
->Set(n
, choices
);
623 SetPopupControl(combolbox
);
628 wxComboBox::~wxComboBox()
632 // ----------------------------------------------------------------------------
633 // wxComboBox methods forwarded to wxTextCtrl
634 // ----------------------------------------------------------------------------
636 wxString
wxComboBox::GetValue() const
638 return GetText()->GetValue();
641 void wxComboBox::SetValue(const wxString
& value
)
643 GetText()->SetValue(value
);
646 void wxComboBox::Copy()
651 void wxComboBox::Cut()
656 void wxComboBox::Paste()
661 void wxComboBox::SetInsertionPoint(long pos
)
663 GetText()->SetInsertionPoint(pos
);
666 void wxComboBox::SetInsertionPointEnd()
668 GetText()->SetInsertionPointEnd();
671 long wxComboBox::GetInsertionPoint() const
673 return GetText()->GetInsertionPoint();
676 long wxComboBox::GetLastPosition() const
678 return GetText()->GetLastPosition();
681 void wxComboBox::Replace(long from
, long to
, const wxString
& value
)
683 GetText()->Replace(from
, to
, value
);
686 void wxComboBox::Remove(long from
, long to
)
688 GetText()->Remove(from
, to
);
691 void wxComboBox::SetSelection(long from
, long to
)
693 GetText()->SetSelection(from
, to
);
696 void wxComboBox::SetEditable(bool editable
)
698 GetText()->SetEditable(editable
);
701 // ----------------------------------------------------------------------------
702 // wxComboBox methods forwarded to wxListBox
703 // ----------------------------------------------------------------------------
705 void wxComboBox::Clear()
710 void wxComboBox::Delete(int n
)
712 GetLBox()->Delete(n
);
715 int wxComboBox::GetCount() const
717 return GetLBox()->GetCount();
720 wxString
wxComboBox::GetString(int n
) const
722 return GetLBox()->GetString(n
);
725 void wxComboBox::SetString(int n
, const wxString
& s
)
727 GetLBox()->SetString(n
, s
);
730 int wxComboBox::FindString(const wxString
& s
) const
732 return GetLBox()->FindString(s
);
735 void wxComboBox::Select(int n
)
737 wxCHECK_RET( (n
>= 0) && (n
< GetCount()), _T("invalid combobox index") );
739 GetLBox()->SetSelection(n
);
740 GetText()->SetValue(GetLBox()->GetString(n
));
743 int wxComboBox::GetSelection() const
745 // if the current value isn't one of the listbox strings, return -1
746 return FindString(GetText()->GetValue());
749 int wxComboBox::DoAppend(const wxString
& item
)
751 return GetLBox()->Append(item
);
754 void wxComboBox::DoSetItemClientData(int n
, void* clientData
)
756 GetLBox()->SetClientData(n
, clientData
);
759 void *wxComboBox::DoGetItemClientData(int n
) const
761 return GetLBox()->GetClientData(n
);
764 void wxComboBox::DoSetItemClientObject(int n
, wxClientData
* clientData
)
766 GetLBox()->SetClientObject(n
, clientData
);
769 wxClientData
* wxComboBox::DoGetItemClientObject(int n
) const
771 return GetLBox()->GetClientObject(n
);
774 // ----------------------------------------------------------------------------
776 // ----------------------------------------------------------------------------
778 void wxComboControl::OnKey(wxKeyEvent
& event
)
780 if ( m_isPopupShown
)
782 // pass it to the popped up control
783 (void)m_popup
->GetControl()->ProcessEvent(event
);
791 bool wxComboControl::PerformAction(const wxControlAction
& action
,
793 const wxString
& strArg
)
795 bool processed
= FALSE
;
796 if ( action
== wxACTION_COMBOBOX_POPUP
)
798 if ( !m_isPopupShown
)
805 else if ( action
== wxACTION_COMBOBOX_DISMISS
)
807 if ( m_isPopupShown
)
818 return wxControl::PerformAction(action
, numArg
, strArg
);
824 // ----------------------------------------------------------------------------
825 // wxStdComboBoxInputHandler
826 // ----------------------------------------------------------------------------
828 wxStdComboBoxInputHandler::wxStdComboBoxInputHandler(wxInputHandler
*inphand
)
829 : wxStdInputHandler(inphand
)
833 bool wxStdComboBoxInputHandler::HandleKey(wxInputConsumer
*consumer
,
834 const wxKeyEvent
& event
,
839 wxControlAction action
;
840 switch ( event
.GetKeyCode() )
843 action
= wxACTION_COMBOBOX_POPUP
;
847 action
= wxACTION_COMBOBOX_DISMISS
;
853 consumer
->PerformAction(action
);
859 return wxStdInputHandler::HandleKey(consumer
, event
, pressed
);
862 #endif // wxUSE_COMBOBOX