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 /////////////////////////////////////////////////////////////////////////////
12 // ============================================================================
14 // ============================================================================
16 // ----------------------------------------------------------------------------
18 // ----------------------------------------------------------------------------
21 #pragma implementation "univcombobox.h"
24 #include "wx/wxprec.h"
35 #include "wx/button.h"
36 #include "wx/combobox.h"
37 #include "wx/listbox.h"
38 #include "wx/textctrl.h"
39 #include "wx/bmpbuttn.h"
41 #include "wx/validate.h"
44 #include "wx/popupwin.h"
46 #include "wx/univ/renderer.h"
47 #include "wx/univ/inphand.h"
48 #include "wx/univ/theme.h"
51 The keyboard event flow:
53 1. they always come to the text ctrl
54 2. it forwards the ones it doesn't process to the wxComboControl
55 3. which passes them to the popup window if it is popped up
58 // ----------------------------------------------------------------------------
59 // wxComboButton is just a normal button except that it sends commands to the
60 // combobox and not its parent
61 // ----------------------------------------------------------------------------
63 class wxComboButton
: public wxBitmapButton
66 wxComboButton(wxComboControl
*combo
)
67 : wxBitmapButton(combo
->GetParent(), -1, wxNullBitmap
,
68 wxDefaultPosition
, wxDefaultSize
,
69 wxBORDER_NONE
| wxBU_EXACTFIT
)
73 wxBitmap bmpNormal
, bmpFocus
, bmpPressed
, bmpDisabled
;
75 GetRenderer()->GetComboBitmaps(&bmpNormal
,
80 SetBitmapLabel(bmpNormal
);
81 SetBitmapFocus(bmpFocus
.Ok() ? bmpFocus
: bmpNormal
);
82 SetBitmapSelected(bmpPressed
.Ok() ? bmpPressed
: bmpNormal
);
83 SetBitmapDisabled(bmpDisabled
.Ok() ? bmpDisabled
: bmpNormal
);
85 SetBestSize(wxDefaultSize
);
89 void OnButton(wxCommandEvent
& event
) { m_combo
->ShowPopup(); }
91 virtual wxSize
DoGetBestClientSize() const
93 const wxBitmap
& bmp
= GetBitmapLabel();
95 return wxSize(bmp
.GetWidth(), bmp
.GetHeight());
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 // set m_clicked value from here
130 void OnLeftUp(wxMouseEvent
& event
);
132 // called whenever the user selects or activates a listbox item
133 void OnSelect(wxCommandEvent
& event
);
135 // used to process wxUniv actions
136 bool PerformAction(const wxControlAction
& action
,
138 const wxString
& strArg
);
141 // has the mouse been released on this control?
144 DECLARE_EVENT_TABLE()
147 // ----------------------------------------------------------------------------
148 // wxComboTextCtrl is a simple text ctrl which forwards
149 // wxEVT_COMMAND_TEXT_UPDATED events and all key events to the combobox
150 // ----------------------------------------------------------------------------
152 class wxComboTextCtrl
: public wxTextCtrl
155 wxComboTextCtrl(wxComboControl
*combo
,
156 const wxString
& value
,
158 const wxValidator
& validator
);
161 void OnKey(wxKeyEvent
& event
);
162 void OnText(wxCommandEvent
& event
);
165 wxComboControl
*m_combo
;
167 DECLARE_EVENT_TABLE()
170 // ----------------------------------------------------------------------------
171 // event tables and such
172 // ----------------------------------------------------------------------------
174 BEGIN_EVENT_TABLE(wxComboButton
, wxButton
)
175 EVT_BUTTON(-1, wxComboButton::OnButton
)
178 BEGIN_EVENT_TABLE(wxComboListBox
, wxListBox
)
179 EVT_LISTBOX(-1, wxComboListBox::OnSelect
)
180 EVT_LISTBOX_DCLICK(-1, wxComboListBox::OnSelect
)
181 EVT_MOTION(wxComboListBox::OnMouseMove
)
182 EVT_LEFT_UP(wxComboListBox::OnLeftUp
)
185 BEGIN_EVENT_TABLE(wxComboControl
, wxControl
)
186 EVT_KEY_DOWN(wxComboControl::OnKey
)
187 EVT_KEY_UP(wxComboControl::OnKey
)
190 BEGIN_EVENT_TABLE(wxComboTextCtrl
, wxTextCtrl
)
191 EVT_KEY_DOWN(wxComboTextCtrl::OnKey
)
192 EVT_KEY_UP(wxComboTextCtrl::OnKey
)
193 EVT_TEXT(-1, wxComboTextCtrl::OnText
)
196 IMPLEMENT_DYNAMIC_CLASS(wxComboBox
, wxControl
);
198 // ============================================================================
200 // ============================================================================
202 // ----------------------------------------------------------------------------
203 // wxComboControl creation
204 // ----------------------------------------------------------------------------
206 void wxComboControl::Init()
208 m_popup
= (wxComboPopup
*)NULL
;
209 m_winPopup
= (wxPopupComboWindow
*)NULL
;
210 m_isPopupShown
= FALSE
;
215 bool wxComboControl::Create(wxWindow
*parent
,
217 const wxString
& value
,
221 const wxValidator
& validator
,
222 const wxString
& name
)
224 // first create our own window, i.e. the one which will contain all
226 style
&= ~wxBORDER_NONE
;
227 style
|= wxBORDER_SUNKEN
;
228 if ( !wxControl::Create(parent
, id
, pos
, size
, style
, validator
, name
) )
231 // create the text control and the button as our siblings (*not* children),
232 // don't care about size/position here - they will be set in DoMoveWindow()
233 m_btn
= new wxComboButton(this);
234 m_text
= new wxComboTextCtrl(this,
236 style
& wxCB_READONLY
? wxTE_READONLY
: 0,
239 // for compatibility with the other ports, the height specified is the
240 // combined height of the combobox itself and the popup
243 // ok, use default height for popup too
248 m_heightPopup
= size
.y
- DoGetBestSize().y
;
254 // create the popup window immediately here to allow creating the controls
255 // with parent == GetPopupWindow() from the derived class ctor
256 m_winPopup
= new wxPopupComboWindow(this);
258 // have to disable this window to avoid interfering it with message
259 // processing to the text and the button... but pretend it is enabled to
260 // make IsEnabled() return TRUE
261 wxControl::Enable(FALSE
); // don't use non virtual Disable() here!
264 CreateInputHandler(wxINP_HANDLER_COMBOBOX
);
269 wxComboControl::~wxComboControl()
271 // as the button and the text control are the parent's children and not
272 // ours, we have to delete them manually - they are not deleted
273 // automatically by wxWindows when we're deleted
280 // ----------------------------------------------------------------------------
282 // ----------------------------------------------------------------------------
284 void wxComboControl::DoSetSize(int x
, int y
,
285 int width
, int height
,
288 // combo height is always fixed
289 wxControl::DoSetSize(x
, y
, width
, DoGetBestSize().y
, sizeFlags
);
292 wxSize
wxComboControl::DoGetBestClientSize() const
294 wxSize sizeBtn
= m_btn
->GetBestSize(),
295 sizeText
= m_text
->GetBestSize();
297 return wxSize(sizeBtn
.x
+ sizeText
.x
, wxMax(sizeBtn
.y
, sizeText
.y
));
300 void wxComboControl::DoMoveWindow(int x
, int y
, int width
, int height
)
302 wxControl::DoMoveWindow(x
, y
, width
, height
);
304 // position the subcontrols inside the client area
305 wxRect rectBorders
= GetRenderer()->GetBorderDimensions(GetBorder());
308 width
-= rectBorders
.x
+ rectBorders
.width
;
309 height
-= rectBorders
.y
+ rectBorders
.height
;
311 wxSize sizeBtn
= m_btn
->GetBestSize();
313 wxCoord wText
= width
- sizeBtn
.x
;
314 wxPoint p
= GetParent() ? GetParent()->GetClientAreaOrigin() : wxPoint(0,0);
315 m_text
->SetSize(x
- p
.x
, y
- p
.y
, wText
, height
);
316 m_btn
->SetSize(x
- p
.x
+ wText
, y
- p
.y
, sizeBtn
.x
, height
);
319 // ----------------------------------------------------------------------------
321 // ----------------------------------------------------------------------------
323 bool wxComboControl::Enable(bool enable
)
325 if ( !wxControl::Enable(enable
) )
328 m_btn
->Enable(enable
);
329 m_text
->Enable(enable
);
334 bool wxComboControl::Show(bool show
)
336 if ( !wxControl::Show(show
) )
348 // ----------------------------------------------------------------------------
349 // popup window handling
350 // ----------------------------------------------------------------------------
352 void wxComboControl::SetPopupControl(wxComboPopup
*popup
)
357 void wxComboControl::ShowPopup()
359 wxCHECK_RET( m_popup
, _T("no popup to show in wxComboControl") );
360 wxCHECK_RET( !IsPopupShown(), _T("popup window already shown") );
362 wxControl
*control
= m_popup
->GetControl();
364 // size and position the popup window correctly
365 m_winPopup
->SetSize(GetSize().x
,
366 m_heightPopup
== -1 ? control
->GetBestSize().y
368 wxSize sizePopup
= m_winPopup
->GetClientSize();
369 control
->SetSize(0, 0, sizePopup
.x
, sizePopup
.y
);
371 // some controls don't accept the size we give then: e.g. a listbox may
372 // require more space to show its last row
373 wxSize sizeReal
= control
->GetSize();
374 if ( sizeReal
!= sizePopup
)
376 m_winPopup
->SetClientSize(sizeReal
);
379 m_winPopup
->PositionNearCombo();
383 m_winPopup
->Popup(m_text
);
385 m_popup
->SetSelection(m_text
->GetValue());
387 m_isPopupShown
= TRUE
;
390 void wxComboControl::HidePopup()
392 wxCHECK_RET( m_popup
, _T("no popup to hide in wxComboControl") );
393 wxCHECK_RET( IsPopupShown(), _T("popup window not shown") );
395 m_winPopup
->Dismiss();
397 m_isPopupShown
= FALSE
;
400 void wxComboControl::OnSelect(const wxString
& value
)
402 m_text
->SetValue(value
);
408 void wxComboControl::OnDismiss()
414 // ----------------------------------------------------------------------------
416 // ----------------------------------------------------------------------------
418 wxComboTextCtrl::wxComboTextCtrl(wxComboControl
*combo
,
419 const wxString
& value
,
421 const wxValidator
& validator
)
422 : wxTextCtrl(combo
->GetParent(), -1, value
,
423 wxDefaultPosition
, wxDefaultSize
,
424 wxBORDER_NONE
| style
,
430 void wxComboTextCtrl::OnText(wxCommandEvent
& event
)
432 if ( m_combo
->IsPopupShown() )
434 m_combo
->GetPopupControl()->SetSelection(GetValue());
437 // we need to make a copy of the event to have the correct originating
439 wxCommandEvent event2
= event
;
440 event2
.SetEventObject(m_combo
);
441 event2
.SetId(m_combo
->GetId());
443 // there is a small incompatibility with wxMSW here: the combobox gets the
444 // event before the text control in our case which corresponds to SMW
445 // CBN_EDITUPDATE notification and not CBN_EDITCHANGE one wxMSW currently
448 // if this is really a problem, we can play games with the event handlers
449 // to circumvent this
450 (void)m_combo
->ProcessEvent(event2
);
455 // pass the keys we don't process to the combo first
456 void wxComboTextCtrl::OnKey(wxKeyEvent
& event
)
458 switch ( event
.GetKeyCode() )
461 // the popup control gets it first but only if it is shown
462 if ( !m_combo
->IsPopupShown() )
473 (void)m_combo
->ProcessEvent(event
);
480 // ----------------------------------------------------------------------------
482 // ----------------------------------------------------------------------------
484 wxComboListBox::wxComboListBox(wxComboControl
*combo
, int style
)
485 : wxListBox(combo
->GetPopupWindow(), -1,
486 wxDefaultPosition
, wxDefaultSize
,
488 wxBORDER_SIMPLE
| wxLB_INT_HEIGHT
| style
),
491 // we don't react to the mouse events outside the window at all
495 wxComboListBox::~wxComboListBox()
499 bool wxComboListBox::SetSelection(const wxString
& value
)
501 // FindItem() would just find the current item for an empty string (it
502 // always matches), but we want to show the first one in such case
507 wxListBox::SetSelection(0);
509 //else: empty listbox - nothing to do
511 else if ( !FindItem(value
) )
520 void wxComboListBox::OnSelect(wxCommandEvent
& event
)
524 // first update the combo and close the listbox
525 m_combo
->OnSelect(event
.GetString());
527 // next let the user code have the event
529 // all fields are already filled by the listbox, just change the event
530 // type and send it to the combo
531 wxCommandEvent event2
= event
;
532 event2
.SetEventType(wxEVT_COMMAND_COMBOBOX_SELECTED
);
533 event2
.SetEventObject(m_combo
);
534 event2
.SetId(m_combo
->GetId());
535 m_combo
->ProcessEvent(event2
);
537 //else: ignore the events resultign from just moving the mouse initially
540 void wxComboListBox::OnShow()
542 // nobody clicked us yet
546 bool wxComboListBox::PerformAction(const wxControlAction
& action
,
548 const wxString
& strArg
)
551 if ( action
== wxACTION_LISTBOX_FIND
)
553 // we don't let the listbox handle this as instead of just using the
554 // single key presses, as usual, we use the text ctrl value as prefix
555 // and this is done by wxComboControl itself
559 return wxListBox::PerformAction(action
, numArg
, strArg
);
562 void wxComboListBox::OnLeftUp(wxMouseEvent
& event
)
564 // we should dismiss the combo now
570 void wxComboListBox::OnMouseMove(wxMouseEvent
& event
)
572 // while a wxComboListBox is shown, it always has capture, so if it doesn't
573 // we're about to go away anyhow (normally this shouldn't happen at all,
574 // but I don't put assert here as it just might do on other platforms and
575 // it doesn't break anythign anyhow)
576 if ( this == wxWindow::GetCapture() )
578 if ( HitTest(event
.GetPosition()) == wxHT_WINDOW_INSIDE
)
582 //else: popup shouldn't react to the mouse motions outside it, it only
583 // captures the mouse to be able to detect when it must be
584 // dismissed, so don't call Skip()
588 wxSize
wxComboListBox::DoGetBestClientSize() const
590 // don't return size too big or we risk to not fit on the screen
591 wxSize size
= wxListBox::DoGetBestClientSize();
592 wxCoord hChar
= GetCharHeight();
594 int nLines
= size
.y
/ hChar
;
596 // 10 is the same limit as used by wxMSW
605 // ----------------------------------------------------------------------------
607 // ----------------------------------------------------------------------------
609 void wxComboBox::Init()
611 m_lbox
= (wxListBox
*)NULL
;
614 bool wxComboBox::Create(wxWindow
*parent
,
616 const wxString
& value
,
620 const wxString
*choices
,
622 const wxValidator
& validator
,
623 const wxString
& name
)
625 if ( !wxComboControl::Create(parent
, id
, value
, pos
, size
, style
,
631 wxComboListBox
*combolbox
=
632 new wxComboListBox(this, style
& wxCB_SORT
? wxLB_SORT
: 0);
634 m_lbox
->Set(n
, choices
);
636 SetPopupControl(combolbox
);
641 wxComboBox::~wxComboBox()
645 // ----------------------------------------------------------------------------
646 // wxComboBox methods forwarded to wxTextCtrl
647 // ----------------------------------------------------------------------------
649 wxString
wxComboBox::GetValue() const
651 return GetText()->GetValue();
654 void wxComboBox::SetValue(const wxString
& value
)
656 GetText()->SetValue(value
);
659 void wxComboBox::Copy()
664 void wxComboBox::Cut()
669 void wxComboBox::Paste()
674 void wxComboBox::SetInsertionPoint(long pos
)
676 GetText()->SetInsertionPoint(pos
);
679 void wxComboBox::SetInsertionPointEnd()
681 GetText()->SetInsertionPointEnd();
684 long wxComboBox::GetInsertionPoint() const
686 return GetText()->GetInsertionPoint();
689 long wxComboBox::GetLastPosition() const
691 return GetText()->GetLastPosition();
694 void wxComboBox::Replace(long from
, long to
, const wxString
& value
)
696 GetText()->Replace(from
, to
, value
);
699 void wxComboBox::Remove(long from
, long to
)
701 GetText()->Remove(from
, to
);
704 void wxComboBox::SetSelection(long from
, long to
)
706 GetText()->SetSelection(from
, to
);
709 void wxComboBox::SetEditable(bool editable
)
711 GetText()->SetEditable(editable
);
714 // ----------------------------------------------------------------------------
715 // wxComboBox methods forwarded to wxListBox
716 // ----------------------------------------------------------------------------
718 void wxComboBox::Clear()
723 void wxComboBox::Delete(int n
)
725 GetLBox()->Delete(n
);
728 int wxComboBox::GetCount() const
730 return GetLBox()->GetCount();
733 wxString
wxComboBox::GetString(int n
) const
735 return GetLBox()->GetString(n
);
738 void wxComboBox::SetString(int n
, const wxString
& s
)
740 GetLBox()->SetString(n
, s
);
743 int wxComboBox::FindString(const wxString
& s
) const
745 return GetLBox()->FindString(s
);
748 void wxComboBox::Select(int n
)
750 wxCHECK_RET( (n
>= 0) && (n
< GetCount()), _T("invalid combobox index") );
752 GetLBox()->SetSelection(n
);
753 GetText()->SetValue(GetLBox()->GetString(n
));
756 int wxComboBox::GetSelection() const
758 // if the current value isn't one of the listbox strings, return -1
759 return FindString(GetText()->GetValue());
762 int wxComboBox::DoAppend(const wxString
& item
)
764 return GetLBox()->Append(item
);
767 void wxComboBox::DoSetItemClientData(int n
, void* clientData
)
769 GetLBox()->SetClientData(n
, clientData
);
772 void *wxComboBox::DoGetItemClientData(int n
) const
774 return GetLBox()->GetClientData(n
);
777 void wxComboBox::DoSetItemClientObject(int n
, wxClientData
* clientData
)
779 GetLBox()->SetClientObject(n
, clientData
);
782 wxClientData
* wxComboBox::DoGetItemClientObject(int n
) const
784 return GetLBox()->GetClientObject(n
);
787 // ----------------------------------------------------------------------------
789 // ----------------------------------------------------------------------------
791 void wxComboControl::OnKey(wxKeyEvent
& event
)
793 if ( m_isPopupShown
)
795 // pass it to the popped up control
796 (void)m_popup
->GetControl()->ProcessEvent(event
);
804 bool wxComboControl::PerformAction(const wxControlAction
& action
,
806 const wxString
& strArg
)
808 bool processed
= FALSE
;
809 if ( action
== wxACTION_COMBOBOX_POPUP
)
811 if ( !m_isPopupShown
)
818 else if ( action
== wxACTION_COMBOBOX_DISMISS
)
820 if ( m_isPopupShown
)
831 return wxControl::PerformAction(action
, numArg
, strArg
);
837 // ----------------------------------------------------------------------------
838 // wxStdComboBoxInputHandler
839 // ----------------------------------------------------------------------------
841 wxStdComboBoxInputHandler::wxStdComboBoxInputHandler(wxInputHandler
*inphand
)
842 : wxStdInputHandler(inphand
)
846 bool wxStdComboBoxInputHandler::HandleKey(wxInputConsumer
*consumer
,
847 const wxKeyEvent
& event
,
852 wxControlAction action
;
853 switch ( event
.GetKeyCode() )
856 action
= wxACTION_COMBOBOX_POPUP
;
860 action
= wxACTION_COMBOBOX_DISMISS
;
866 consumer
->PerformAction(action
);
872 return wxStdInputHandler::HandleKey(consumer
, event
, pressed
);
875 #endif // wxUSE_COMBOBOX