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
59 // ----------------------------------------------------------------------------
61 // the margin between the text control and the combo button
62 static const wxCoord g_comboMargin
= 2;
64 // ----------------------------------------------------------------------------
65 // wxComboButton is just a normal button except that it sends commands to the
66 // combobox and not its parent
67 // ----------------------------------------------------------------------------
69 class wxComboButton
: public wxBitmapButton
72 wxComboButton(wxComboControl
*combo
)
73 : wxBitmapButton(combo
->GetParent(), -1, wxNullBitmap
,
74 wxDefaultPosition
, wxDefaultSize
,
75 wxBORDER_NONE
| wxBU_EXACTFIT
)
79 wxBitmap bmpNormal
, bmpFocus
, bmpPressed
, bmpDisabled
;
81 GetRenderer()->GetComboBitmaps(&bmpNormal
,
86 SetBitmapLabel(bmpNormal
);
87 SetBitmapFocus(bmpFocus
.Ok() ? bmpFocus
: bmpNormal
);
88 SetBitmapSelected(bmpPressed
.Ok() ? bmpPressed
: bmpNormal
);
89 SetBitmapDisabled(bmpDisabled
.Ok() ? bmpDisabled
: bmpNormal
);
91 SetBestSize(wxDefaultSize
);
95 void OnButton(wxCommandEvent
& event
) { m_combo
->ShowPopup(); }
97 virtual wxSize
DoGetBestClientSize() const
99 const wxBitmap
& bmp
= GetBitmapLabel();
101 return wxSize(bmp
.GetWidth(), bmp
.GetHeight());
106 wxComboControl
*m_combo
;
108 DECLARE_EVENT_TABLE()
111 // ----------------------------------------------------------------------------
112 // wxComboListBox is a listbox modified to be used as a popup window in a
114 // ----------------------------------------------------------------------------
116 class wxComboListBox
: public wxListBox
, public wxComboPopup
120 wxComboListBox(wxComboControl
*combo
, int style
= 0);
121 virtual ~wxComboListBox();
123 // implement wxComboPopup methods
124 virtual bool SetSelection(const wxString
& value
);
125 virtual wxControl
*GetControl() { return this; }
126 virtual void OnShow();
129 // we shouldn't return height too big from here
130 virtual wxSize
DoGetBestClientSize() const;
132 // filter mouse move events happening outside the list box
133 void OnMouseMove(wxMouseEvent
& event
);
135 // set m_clicked value from here
136 void OnLeftUp(wxMouseEvent
& event
);
138 // called whenever the user selects or activates a listbox item
139 void OnSelect(wxCommandEvent
& event
);
141 // used to process wxUniv actions
142 bool PerformAction(const wxControlAction
& action
,
144 const wxString
& strArg
);
147 // has the mouse been released on this control?
150 DECLARE_EVENT_TABLE()
153 // ----------------------------------------------------------------------------
154 // wxComboTextCtrl is a simple text ctrl which forwards
155 // wxEVT_COMMAND_TEXT_UPDATED events and all key events to the combobox
156 // ----------------------------------------------------------------------------
158 class wxComboTextCtrl
: public wxTextCtrl
161 wxComboTextCtrl(wxComboControl
*combo
,
162 const wxString
& value
,
164 const wxValidator
& validator
);
167 void OnKey(wxKeyEvent
& event
);
168 void OnText(wxCommandEvent
& event
);
171 wxComboControl
*m_combo
;
173 DECLARE_EVENT_TABLE()
176 // ----------------------------------------------------------------------------
177 // event tables and such
178 // ----------------------------------------------------------------------------
180 BEGIN_EVENT_TABLE(wxComboButton
, wxButton
)
181 EVT_BUTTON(-1, wxComboButton::OnButton
)
184 BEGIN_EVENT_TABLE(wxComboListBox
, wxListBox
)
185 EVT_LISTBOX(-1, wxComboListBox::OnSelect
)
186 EVT_LISTBOX_DCLICK(-1, wxComboListBox::OnSelect
)
187 EVT_MOTION(wxComboListBox::OnMouseMove
)
188 EVT_LEFT_UP(wxComboListBox::OnLeftUp
)
191 BEGIN_EVENT_TABLE(wxComboControl
, wxControl
)
192 EVT_KEY_DOWN(wxComboControl::OnKey
)
193 EVT_KEY_UP(wxComboControl::OnKey
)
196 BEGIN_EVENT_TABLE(wxComboTextCtrl
, wxTextCtrl
)
197 EVT_KEY_DOWN(wxComboTextCtrl::OnKey
)
198 EVT_KEY_UP(wxComboTextCtrl::OnKey
)
199 EVT_TEXT(-1, wxComboTextCtrl::OnText
)
202 IMPLEMENT_DYNAMIC_CLASS(wxComboBox
, wxControl
)
204 // ============================================================================
206 // ============================================================================
208 // ----------------------------------------------------------------------------
209 // wxComboControl creation
210 // ----------------------------------------------------------------------------
212 void wxComboControl::Init()
214 m_popup
= (wxComboPopup
*)NULL
;
215 m_winPopup
= (wxPopupComboWindow
*)NULL
;
216 m_isPopupShown
= FALSE
;
221 bool wxComboControl::Create(wxWindow
*parent
,
223 const wxString
& value
,
227 const wxValidator
& validator
,
228 const wxString
& name
)
230 // first create our own window, i.e. the one which will contain all
232 style
&= ~wxBORDER_NONE
;
233 style
|= wxBORDER_SUNKEN
;
234 if ( !wxControl::Create(parent
, id
, pos
, size
, style
, validator
, name
) )
237 // create the text control and the button as our siblings (*not* children),
238 // don't care about size/position here - they will be set in DoMoveWindow()
239 m_btn
= new wxComboButton(this);
240 m_text
= new wxComboTextCtrl(this,
242 style
& wxCB_READONLY
? wxTE_READONLY
: 0,
245 // for compatibility with the other ports, the height specified is the
246 // combined height of the combobox itself and the popup
249 // ok, use default height for popup too
254 m_heightPopup
= size
.y
- DoGetBestSize().y
;
260 // create the popup window immediately here to allow creating the controls
261 // with parent == GetPopupWindow() from the derived class ctor
262 m_winPopup
= new wxPopupComboWindow(this);
264 // have to disable this window to avoid interfering it with message
265 // processing to the text and the button... but pretend it is enabled to
266 // make IsEnabled() return TRUE
267 wxControl::Enable(FALSE
); // don't use non virtual Disable() here!
270 CreateInputHandler(wxINP_HANDLER_COMBOBOX
);
275 wxComboControl::~wxComboControl()
277 // as the button and the text control are the parent's children and not
278 // ours, we have to delete them manually - they are not deleted
279 // automatically by wxWindows when we're deleted
286 // ----------------------------------------------------------------------------
288 // ----------------------------------------------------------------------------
290 void wxComboControl::DoSetSize(int x
, int y
,
291 int width
, int height
,
294 // combo height is always fixed
295 wxControl::DoSetSize(x
, y
, width
, DoGetBestSize().y
, sizeFlags
);
298 wxSize
wxComboControl::DoGetBestClientSize() const
300 wxSize sizeBtn
= m_btn
->GetBestSize(),
301 sizeText
= m_text
->GetBestSize();
303 return wxSize(sizeText
.x
+ g_comboMargin
+ sizeBtn
.x
, wxMax(sizeBtn
.y
, sizeText
.y
));
306 void wxComboControl::DoMoveWindow(int x
, int y
, int width
, int height
)
308 wxControl::DoMoveWindow(x
, y
, width
, height
);
310 // position the subcontrols inside the client area
311 wxRect rectBorders
= GetRenderer()->GetBorderDimensions(GetBorder());
314 width
-= rectBorders
.x
+ rectBorders
.width
;
315 height
-= rectBorders
.y
+ rectBorders
.height
;
317 wxSize sizeBtn
= m_btn
->GetBestSize();
319 wxCoord wText
= width
- sizeBtn
.x
;
320 wxPoint p
= GetParent() ? GetParent()->GetClientAreaOrigin() : wxPoint(0,0);
321 m_text
->SetSize(x
- p
.x
, y
- p
.y
, wText
, height
);
322 m_btn
->SetSize(x
- p
.x
+ wText
, y
- p
.y
, sizeBtn
.x
, height
);
325 // ----------------------------------------------------------------------------
327 // ----------------------------------------------------------------------------
329 bool wxComboControl::Enable(bool enable
)
331 if ( !wxControl::Enable(enable
) )
334 m_btn
->Enable(enable
);
335 m_text
->Enable(enable
);
340 bool wxComboControl::Show(bool show
)
342 if ( !wxControl::Show(show
) )
354 // ----------------------------------------------------------------------------
355 // popup window handling
356 // ----------------------------------------------------------------------------
358 void wxComboControl::SetPopupControl(wxComboPopup
*popup
)
363 void wxComboControl::ShowPopup()
365 wxCHECK_RET( m_popup
, _T("no popup to show in wxComboControl") );
366 wxCHECK_RET( !IsPopupShown(), _T("popup window already shown") );
368 wxControl
*control
= m_popup
->GetControl();
370 // size and position the popup window correctly
371 m_winPopup
->SetSize(GetSize().x
,
372 m_heightPopup
== -1 ? control
->GetBestSize().y
374 wxSize sizePopup
= m_winPopup
->GetClientSize();
375 control
->SetSize(0, 0, sizePopup
.x
, sizePopup
.y
);
377 // some controls don't accept the size we give then: e.g. a listbox may
378 // require more space to show its last row
379 wxSize sizeReal
= control
->GetSize();
380 if ( sizeReal
!= sizePopup
)
382 m_winPopup
->SetClientSize(sizeReal
);
385 m_winPopup
->PositionNearCombo();
389 m_winPopup
->Popup(m_text
);
391 m_popup
->SetSelection(m_text
->GetValue());
393 m_isPopupShown
= TRUE
;
396 void wxComboControl::HidePopup()
398 wxCHECK_RET( m_popup
, _T("no popup to hide in wxComboControl") );
399 wxCHECK_RET( IsPopupShown(), _T("popup window not shown") );
401 m_winPopup
->Dismiss();
403 m_isPopupShown
= FALSE
;
406 void wxComboControl::OnSelect(const wxString
& value
)
408 m_text
->SetValue(value
);
414 void wxComboControl::OnDismiss()
420 // ----------------------------------------------------------------------------
422 // ----------------------------------------------------------------------------
424 wxComboTextCtrl::wxComboTextCtrl(wxComboControl
*combo
,
425 const wxString
& value
,
427 const wxValidator
& validator
)
428 : wxTextCtrl(combo
->GetParent(), -1, value
,
429 wxDefaultPosition
, wxDefaultSize
,
430 wxBORDER_NONE
| style
,
436 void wxComboTextCtrl::OnText(wxCommandEvent
& event
)
438 if ( m_combo
->IsPopupShown() )
440 m_combo
->GetPopupControl()->SetSelection(GetValue());
443 // we need to make a copy of the event to have the correct originating
445 wxCommandEvent event2
= event
;
446 event2
.SetEventObject(m_combo
);
447 event2
.SetId(m_combo
->GetId());
449 // there is a small incompatibility with wxMSW here: the combobox gets the
450 // event before the text control in our case which corresponds to SMW
451 // CBN_EDITUPDATE notification and not CBN_EDITCHANGE one wxMSW currently
454 // if this is really a problem, we can play games with the event handlers
455 // to circumvent this
456 (void)m_combo
->ProcessEvent(event2
);
461 // pass the keys we don't process to the combo first
462 void wxComboTextCtrl::OnKey(wxKeyEvent
& event
)
464 switch ( event
.GetKeyCode() )
467 // the popup control gets it first but only if it is shown
468 if ( !m_combo
->IsPopupShown() )
479 (void)m_combo
->ProcessEvent(event
);
486 // ----------------------------------------------------------------------------
488 // ----------------------------------------------------------------------------
490 wxComboListBox::wxComboListBox(wxComboControl
*combo
, int style
)
491 : wxListBox(combo
->GetPopupWindow(), -1,
492 wxDefaultPosition
, wxDefaultSize
,
494 wxBORDER_SIMPLE
| wxLB_INT_HEIGHT
| style
),
497 // we don't react to the mouse events outside the window at all
501 wxComboListBox::~wxComboListBox()
505 bool wxComboListBox::SetSelection(const wxString
& value
)
507 // FindItem() would just find the current item for an empty string (it
508 // always matches), but we want to show the first one in such case
513 wxListBox::SetSelection(0);
515 //else: empty listbox - nothing to do
517 else if ( !FindItem(value
) )
526 void wxComboListBox::OnSelect(wxCommandEvent
& event
)
530 // first update the combo and close the listbox
531 m_combo
->OnSelect(event
.GetString());
533 // next let the user code have the event
535 // all fields are already filled by the listbox, just change the event
536 // type and send it to the combo
537 wxCommandEvent event2
= event
;
538 event2
.SetEventType(wxEVT_COMMAND_COMBOBOX_SELECTED
);
539 event2
.SetEventObject(m_combo
);
540 event2
.SetId(m_combo
->GetId());
541 m_combo
->ProcessEvent(event2
);
543 //else: ignore the events resultign from just moving the mouse initially
546 void wxComboListBox::OnShow()
548 // nobody clicked us yet
552 bool wxComboListBox::PerformAction(const wxControlAction
& action
,
554 const wxString
& strArg
)
557 if ( action
== wxACTION_LISTBOX_FIND
)
559 // we don't let the listbox handle this as instead of just using the
560 // single key presses, as usual, we use the text ctrl value as prefix
561 // and this is done by wxComboControl itself
565 return wxListBox::PerformAction(action
, numArg
, strArg
);
568 void wxComboListBox::OnLeftUp(wxMouseEvent
& event
)
570 // we should dismiss the combo now
576 void wxComboListBox::OnMouseMove(wxMouseEvent
& event
)
578 // while a wxComboListBox is shown, it always has capture, so if it doesn't
579 // we're about to go away anyhow (normally this shouldn't happen at all,
580 // but I don't put assert here as it just might do on other platforms and
581 // it doesn't break anythign anyhow)
582 if ( this == wxWindow::GetCapture() )
584 if ( HitTest(event
.GetPosition()) == wxHT_WINDOW_INSIDE
)
588 //else: popup shouldn't react to the mouse motions outside it, it only
589 // captures the mouse to be able to detect when it must be
590 // dismissed, so don't call Skip()
594 wxSize
wxComboListBox::DoGetBestClientSize() const
596 // don't return size too big or we risk to not fit on the screen
597 wxSize size
= wxListBox::DoGetBestClientSize();
598 wxCoord hChar
= GetCharHeight();
600 int nLines
= size
.y
/ hChar
;
602 // 10 is the same limit as used by wxMSW
611 // ----------------------------------------------------------------------------
613 // ----------------------------------------------------------------------------
615 void wxComboBox::Init()
617 m_lbox
= (wxListBox
*)NULL
;
620 bool wxComboBox::Create(wxWindow
*parent
,
622 const wxString
& value
,
626 const wxString
*choices
,
628 const wxValidator
& validator
,
629 const wxString
& name
)
631 if ( !wxComboControl::Create(parent
, id
, value
, pos
, size
, style
,
637 wxComboListBox
*combolbox
=
638 new wxComboListBox(this, style
& wxCB_SORT
? wxLB_SORT
: 0);
640 m_lbox
->Set(n
, choices
);
642 SetPopupControl(combolbox
);
647 wxComboBox::~wxComboBox()
651 // ----------------------------------------------------------------------------
652 // wxComboBox methods forwarded to wxTextCtrl
653 // ----------------------------------------------------------------------------
655 wxString
wxComboBox::GetValue() const
657 return GetText()->GetValue();
660 void wxComboBox::SetValue(const wxString
& value
)
662 GetText()->SetValue(value
);
665 void wxComboBox::Copy()
670 void wxComboBox::Cut()
675 void wxComboBox::Paste()
680 void wxComboBox::SetInsertionPoint(long pos
)
682 GetText()->SetInsertionPoint(pos
);
685 void wxComboBox::SetInsertionPointEnd()
687 GetText()->SetInsertionPointEnd();
690 long wxComboBox::GetInsertionPoint() const
692 return GetText()->GetInsertionPoint();
695 long wxComboBox::GetLastPosition() const
697 return GetText()->GetLastPosition();
700 void wxComboBox::Replace(long from
, long to
, const wxString
& value
)
702 GetText()->Replace(from
, to
, value
);
705 void wxComboBox::Remove(long from
, long to
)
707 GetText()->Remove(from
, to
);
710 void wxComboBox::SetSelection(long from
, long to
)
712 GetText()->SetSelection(from
, to
);
715 void wxComboBox::SetEditable(bool editable
)
717 GetText()->SetEditable(editable
);
720 // ----------------------------------------------------------------------------
721 // wxComboBox methods forwarded to wxListBox
722 // ----------------------------------------------------------------------------
724 void wxComboBox::Clear()
727 GetText()->SetValue(wxEmptyString
);
730 void wxComboBox::Delete(int n
)
732 wxCHECK_RET( (n
>= 0) && (n
< GetCount()), _T("invalid index in wxComboBox::Delete") );
734 if (GetSelection() == n
)
735 GetText()->SetValue(wxEmptyString
);
737 GetLBox()->Delete(n
);
740 int wxComboBox::GetCount() const
742 return GetLBox()->GetCount();
745 wxString
wxComboBox::GetString(int n
) const
747 wxCHECK_MSG( (n
>= 0) && (n
< GetCount()), wxEmptyString
, _T("invalid index in wxComboBox::GetString") );
749 return GetLBox()->GetString(n
);
752 void wxComboBox::SetString(int n
, const wxString
& s
)
754 wxCHECK_RET( (n
>= 0) && (n
< GetCount()), _T("invalid index in wxComboBox::SetString") );
756 GetLBox()->SetString(n
, s
);
759 int wxComboBox::FindString(const wxString
& s
) const
761 return GetLBox()->FindString(s
);
764 void wxComboBox::Select(int n
)
766 wxCHECK_RET( (n
>= 0) && (n
< GetCount()), _T("invalid index in wxComboBox::Select") );
768 GetLBox()->SetSelection(n
);
769 GetText()->SetValue(GetLBox()->GetString(n
));
772 int wxComboBox::GetSelection() const
774 #if 1 // FIXME:: What is the correct behavior?
775 // if the current value isn't one of the listbox strings, return -1
776 return GetLBox()->GetSelection();
778 // Why oh why is this done this way?
779 // It is not because the value displayed in the text can be found
780 // in the list that it is the item that is selected!
781 return FindString(GetText()->GetValue());
785 int wxComboBox::DoAppend(const wxString
& item
)
787 return GetLBox()->Append(item
);
790 void wxComboBox::DoSetItemClientData(int n
, void* clientData
)
792 GetLBox()->SetClientData(n
, clientData
);
795 void *wxComboBox::DoGetItemClientData(int n
) const
797 return GetLBox()->GetClientData(n
);
800 void wxComboBox::DoSetItemClientObject(int n
, wxClientData
* clientData
)
802 GetLBox()->SetClientObject(n
, clientData
);
805 wxClientData
* wxComboBox::DoGetItemClientObject(int n
) const
807 return GetLBox()->GetClientObject(n
);
810 // ----------------------------------------------------------------------------
812 // ----------------------------------------------------------------------------
814 void wxComboControl::OnKey(wxKeyEvent
& event
)
816 if ( m_isPopupShown
)
818 // pass it to the popped up control
819 (void)m_popup
->GetControl()->ProcessEvent(event
);
827 bool wxComboControl::PerformAction(const wxControlAction
& action
,
829 const wxString
& strArg
)
831 bool processed
= FALSE
;
832 if ( action
== wxACTION_COMBOBOX_POPUP
)
834 if ( !m_isPopupShown
)
841 else if ( action
== wxACTION_COMBOBOX_DISMISS
)
843 if ( m_isPopupShown
)
854 return wxControl::PerformAction(action
, numArg
, strArg
);
860 // ----------------------------------------------------------------------------
861 // wxStdComboBoxInputHandler
862 // ----------------------------------------------------------------------------
864 wxStdComboBoxInputHandler::wxStdComboBoxInputHandler(wxInputHandler
*inphand
)
865 : wxStdInputHandler(inphand
)
869 bool wxStdComboBoxInputHandler::HandleKey(wxInputConsumer
*consumer
,
870 const wxKeyEvent
& event
,
875 wxControlAction action
;
876 switch ( event
.GetKeyCode() )
879 action
= wxACTION_COMBOBOX_POPUP
;
883 action
= wxACTION_COMBOBOX_DISMISS
;
889 consumer
->PerformAction(action
);
895 return wxStdInputHandler::HandleKey(consumer
, event
, pressed
);
898 #endif // wxUSE_COMBOBOX