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 wxPoint p
= GetParent() ? GetParent()->GetClientAreaOrigin() : wxPoint(0,0);
318 m_text
->SetSize(x
- p
.x
, y
- p
.y
, wText
, height
);
319 m_btn
->SetSize(x
- p
.x
+ wText
, y
- p
.y
, sizeBtn
.x
, height
);
322 // ----------------------------------------------------------------------------
324 // ----------------------------------------------------------------------------
326 bool wxComboControl::Enable(bool enable
)
328 if ( !wxControl::Enable(enable
) )
331 m_btn
->Enable(enable
);
332 m_text
->Enable(enable
);
337 bool wxComboControl::Show(bool show
)
339 if ( !wxControl::Show(show
) )
351 // ----------------------------------------------------------------------------
352 // popup window handling
353 // ----------------------------------------------------------------------------
355 void wxComboControl::SetPopupControl(wxComboPopup
*popup
)
360 void wxComboControl::ShowPopup()
362 wxCHECK_RET( m_popup
, _T("no popup to show in wxComboControl") );
363 wxCHECK_RET( !IsPopupShown(), _T("popup window already shown") );
365 wxControl
*control
= m_popup
->GetControl();
367 // size and position the popup window correctly
368 m_winPopup
->SetSize(GetSize().x
,
369 m_heightPopup
== -1 ? control
->GetBestSize().y
371 wxSize sizePopup
= m_winPopup
->GetClientSize();
372 control
->SetSize(0, 0, sizePopup
.x
, sizePopup
.y
);
374 // some controls don't accept the size we give then: e.g. a listbox may
375 // require more space to show its last row
376 wxSize sizeReal
= control
->GetSize();
377 if ( sizeReal
!= sizePopup
)
379 m_winPopup
->SetClientSize(sizeReal
);
382 m_winPopup
->PositionNearCombo();
385 m_winPopup
->Popup(m_text
);
387 m_popup
->SetSelection(m_text
->GetValue());
389 m_isPopupShown
= TRUE
;
392 void wxComboControl::HidePopup()
394 wxCHECK_RET( m_popup
, _T("no popup to hide in wxComboControl") );
395 wxCHECK_RET( IsPopupShown(), _T("popup window not shown") );
397 m_winPopup
->Dismiss();
399 m_isPopupShown
= FALSE
;
402 void wxComboControl::OnSelect(const wxString
& value
)
404 m_text
->SetValue(value
);
410 void wxComboControl::OnDismiss()
416 // ----------------------------------------------------------------------------
418 // ----------------------------------------------------------------------------
420 wxComboTextCtrl::wxComboTextCtrl(wxComboControl
*combo
,
421 const wxString
& value
,
423 const wxValidator
& validator
)
424 : wxTextCtrl(combo
->GetParent(), -1, value
,
425 wxDefaultPosition
, wxDefaultSize
,
426 wxBORDER_NONE
| style
,
432 void wxComboTextCtrl::OnText(wxCommandEvent
& event
)
434 if ( m_combo
->IsPopupShown() )
436 m_combo
->GetPopupControl()->SetSelection(GetValue());
439 // we need to make a copy of the event to have the correct originating
441 wxCommandEvent event2
= event
;
442 event2
.SetEventObject(m_combo
);
443 event2
.SetId(m_combo
->GetId());
445 // there is a small incompatibility with wxMSW here: the combobox gets the
446 // event before the text control in our case which corresponds to SMW
447 // CBN_EDITUPDATE notification and not CBN_EDITCHANGE one wxMSW currently
450 // if this is really a problem, we can play games with the event handlers
451 // to circumvent this
452 (void)m_combo
->ProcessEvent(event2
);
457 // pass the keys we don't process to the combo first
458 void wxComboTextCtrl::OnKey(wxKeyEvent
& event
)
460 switch ( event
.GetKeyCode() )
463 // the popup control gets it first but only if it is shown
464 if ( !m_combo
->IsPopupShown() )
475 (void)m_combo
->ProcessEvent(event
);
482 // ----------------------------------------------------------------------------
484 // ----------------------------------------------------------------------------
486 wxComboListBox::wxComboListBox(wxComboControl
*combo
, int style
)
487 : wxListBox(combo
->GetPopupWindow(), -1,
488 wxDefaultPosition
, wxDefaultSize
,
490 wxBORDER_SIMPLE
| wxLB_INT_HEIGHT
| style
),
493 // we don't react to the mouse events outside the window at all
497 wxComboListBox::~wxComboListBox()
501 bool wxComboListBox::SetSelection(const wxString
& value
)
503 // FindItem() would just find the current item for an empty string (it
504 // always matches), but we want to show the first one in such case
509 wxListBox::SetSelection(0);
511 //else: empty listbox - nothing to do
513 else if ( !FindItem(value
) )
522 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
);
538 void wxComboListBox::OnShow()
542 bool wxComboListBox::PerformAction(const wxControlAction
& action
,
544 const wxString
& strArg
)
547 if ( action
== wxACTION_LISTBOX_FIND
)
549 // we don't let the listbox handle this as instead of just using the
550 // single key presses, as usual, we use the text ctrl value as prefix
551 // and this is done by wxComboControl itself
555 return wxListBox::PerformAction(action
, numArg
, strArg
);
558 void wxComboListBox::OnMouseMove(wxMouseEvent
& event
)
560 // while a wxComboListBox is shown, it always has capture, so if it doesn't
561 // we're about to go away anyhow (normally this shouldn't happen at all,
562 // but I don't put assert here as it just might do on other platforms and
563 // it doesn't break anythign anyhow)
564 if ( this == wxWindow::GetCapture() )
566 if ( HitTest(event
.GetPosition()) == wxHT_WINDOW_INSIDE
)
570 //else: popup shouldn't react to the mouse motions outside it, it only
571 // captures the mouse to be able to detect when it must be
572 // dismissed, so don't call Skip()
576 wxSize
wxComboListBox::DoGetBestClientSize() const
578 // don't return size too big or we risk to not fit on the screen
579 wxSize size
= wxListBox::DoGetBestClientSize();
580 wxCoord hChar
= GetCharHeight();
582 int nLines
= size
.y
/ hChar
;
584 // 10 is the same limit as used by wxMSW
593 // ----------------------------------------------------------------------------
595 // ----------------------------------------------------------------------------
597 void wxComboBox::Init()
599 m_lbox
= (wxListBox
*)NULL
;
602 bool wxComboBox::Create(wxWindow
*parent
,
604 const wxString
& value
,
608 const wxString
*choices
,
610 const wxValidator
& validator
,
611 const wxString
& name
)
613 if ( !wxComboControl::Create(parent
, id
, value
, pos
, size
, style
,
619 wxComboListBox
*combolbox
=
620 new wxComboListBox(this, style
& wxCB_SORT
? wxLB_SORT
: 0);
622 m_lbox
->Set(n
, choices
);
624 SetPopupControl(combolbox
);
629 wxComboBox::~wxComboBox()
633 // ----------------------------------------------------------------------------
634 // wxComboBox methods forwarded to wxTextCtrl
635 // ----------------------------------------------------------------------------
637 wxString
wxComboBox::GetValue() const
639 return GetText()->GetValue();
642 void wxComboBox::SetValue(const wxString
& value
)
644 GetText()->SetValue(value
);
647 void wxComboBox::Copy()
652 void wxComboBox::Cut()
657 void wxComboBox::Paste()
662 void wxComboBox::SetInsertionPoint(long pos
)
664 GetText()->SetInsertionPoint(pos
);
667 void wxComboBox::SetInsertionPointEnd()
669 GetText()->SetInsertionPointEnd();
672 long wxComboBox::GetInsertionPoint() const
674 return GetText()->GetInsertionPoint();
677 long wxComboBox::GetLastPosition() const
679 return GetText()->GetLastPosition();
682 void wxComboBox::Replace(long from
, long to
, const wxString
& value
)
684 GetText()->Replace(from
, to
, value
);
687 void wxComboBox::Remove(long from
, long to
)
689 GetText()->Remove(from
, to
);
692 void wxComboBox::SetSelection(long from
, long to
)
694 GetText()->SetSelection(from
, to
);
697 void wxComboBox::SetEditable(bool editable
)
699 GetText()->SetEditable(editable
);
702 // ----------------------------------------------------------------------------
703 // wxComboBox methods forwarded to wxListBox
704 // ----------------------------------------------------------------------------
706 void wxComboBox::Clear()
711 void wxComboBox::Delete(int n
)
713 GetLBox()->Delete(n
);
716 int wxComboBox::GetCount() const
718 return GetLBox()->GetCount();
721 wxString
wxComboBox::GetString(int n
) const
723 return GetLBox()->GetString(n
);
726 void wxComboBox::SetString(int n
, const wxString
& s
)
728 GetLBox()->SetString(n
, s
);
731 int wxComboBox::FindString(const wxString
& s
) const
733 return GetLBox()->FindString(s
);
736 void wxComboBox::Select(int n
)
738 wxCHECK_RET( (n
>= 0) && (n
< GetCount()), _T("invalid combobox index") );
740 GetLBox()->SetSelection(n
);
741 GetText()->SetValue(GetLBox()->GetString(n
));
744 int wxComboBox::GetSelection() const
746 // if the current value isn't one of the listbox strings, return -1
747 return FindString(GetText()->GetValue());
750 int wxComboBox::DoAppend(const wxString
& item
)
752 return GetLBox()->Append(item
);
755 void wxComboBox::DoSetItemClientData(int n
, void* clientData
)
757 GetLBox()->SetClientData(n
, clientData
);
760 void *wxComboBox::DoGetItemClientData(int n
) const
762 return GetLBox()->GetClientData(n
);
765 void wxComboBox::DoSetItemClientObject(int n
, wxClientData
* clientData
)
767 GetLBox()->SetClientObject(n
, clientData
);
770 wxClientData
* wxComboBox::DoGetItemClientObject(int n
) const
772 return GetLBox()->GetClientObject(n
);
775 // ----------------------------------------------------------------------------
777 // ----------------------------------------------------------------------------
779 void wxComboControl::OnKey(wxKeyEvent
& event
)
781 if ( m_isPopupShown
)
783 // pass it to the popped up control
784 (void)m_popup
->GetControl()->ProcessEvent(event
);
792 bool wxComboControl::PerformAction(const wxControlAction
& action
,
794 const wxString
& strArg
)
796 bool processed
= FALSE
;
797 if ( action
== wxACTION_COMBOBOX_POPUP
)
799 if ( !m_isPopupShown
)
806 else if ( action
== wxACTION_COMBOBOX_DISMISS
)
808 if ( m_isPopupShown
)
819 return wxControl::PerformAction(action
, numArg
, strArg
);
825 // ----------------------------------------------------------------------------
826 // wxStdComboBoxInputHandler
827 // ----------------------------------------------------------------------------
829 wxStdComboBoxInputHandler::wxStdComboBoxInputHandler(wxInputHandler
*inphand
)
830 : wxStdInputHandler(inphand
)
834 bool wxStdComboBoxInputHandler::HandleKey(wxInputConsumer
*consumer
,
835 const wxKeyEvent
& event
,
840 wxControlAction action
;
841 switch ( event
.GetKeyCode() )
844 action
= wxACTION_COMBOBOX_POPUP
;
848 action
= wxACTION_COMBOBOX_DISMISS
;
854 consumer
->PerformAction(action
);
860 return wxStdInputHandler::HandleKey(consumer
, event
, pressed
);
863 #endif // wxUSE_COMBOBOX