]> git.saurik.com Git - wxWidgets.git/blame_incremental - src/univ/combobox.cpp
Added project files for wxUniv under MSW
[wxWidgets.git] / src / univ / combobox.cpp
... / ...
CommitLineData
1/////////////////////////////////////////////////////////////////////////////
2// Name: univ/combobox.cpp
3// Purpose: wxComboControl and wxComboBox implementation
4// Author: Vadim Zeitlin
5// Modified by:
6// Created: 15.12.00
7// RCS-ID: $Id$
8// Copyright: (c) 2000 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9// Licence: wxWindows licence
10/////////////////////////////////////////////////////////////////////////////
11
12/*
13 TODO:
14
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?
20 */
21
22// ============================================================================
23// declarations
24// ============================================================================
25
26// ----------------------------------------------------------------------------
27// headers
28// ----------------------------------------------------------------------------
29
30#ifdef __GNUG__
31 #pragma implementation "univcombobox.h"
32#endif
33
34#include "wx/wxprec.h"
35
36#ifdef __BORLANDC__
37 #pragma hdrstop
38#endif
39
40#if wxUSE_COMBOBOX
41
42#ifndef WX_PRECOMP
43 #include "wx/log.h"
44
45 #include "wx/button.h"
46 #include "wx/combobox.h"
47 #include "wx/listbox.h"
48 #include "wx/textctrl.h"
49
50 #include "wx/validate.h"
51#endif
52
53#include "wx/popupwin.h"
54
55#include "wx/univ/renderer.h"
56#include "wx/univ/inphand.h"
57#include "wx/univ/theme.h"
58
59/*
60 The keyboard event flow:
61
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
65 */
66
67// ----------------------------------------------------------------------------
68// wxComboButton is just a normal button except that it sends commands to the
69// combobox and not its parent
70// ----------------------------------------------------------------------------
71
72class wxComboButton : public wxBitmapButton
73{
74public:
75 wxComboButton(wxComboControl *combo)
76 : wxBitmapButton(combo->GetParent(), -1, wxNullBitmap,
77 wxDefaultPosition, wxDefaultSize,
78 wxBORDER_NONE)
79 {
80 m_combo = combo;
81
82 wxBitmap bmpNormal, bmpPressed, bmpDisabled;
83
84 GetRenderer()->GetComboBitmaps(&bmpNormal, &bmpPressed, &bmpDisabled);
85 SetBitmapLabel(bmpNormal);
86 SetBitmapFocus(bmpNormal);
87 SetBitmapSelected(bmpPressed);
88 SetBitmapDisabled(bmpDisabled);
89
90 SetSize(bmpNormal.GetWidth(), bmpNormal.GetHeight());
91 }
92
93protected:
94 void OnButton(wxCommandEvent& event) { m_combo->ShowPopup(); }
95
96 virtual wxSize DoGetBestSize() const { return GetSize(); }
97
98private:
99 wxComboControl *m_combo;
100
101 DECLARE_EVENT_TABLE()
102};
103
104// ----------------------------------------------------------------------------
105// wxComboListBox is a listbox modified to be used as a popup window in a
106// combobox
107// ----------------------------------------------------------------------------
108
109class wxComboListBox : public wxListBox, public wxComboPopup
110{
111public:
112 // ctor and dtor
113 wxComboListBox(wxComboControl *combo, int style = 0);
114 virtual ~wxComboListBox();
115
116 // implement wxComboPopup methods
117 virtual bool SetSelection(const wxString& value);
118 virtual wxControl *GetControl() { return this; }
119 virtual void OnShow();
120
121protected:
122 // we shouldn't return height too big from here
123 virtual wxSize DoGetBestClientSize() const;
124
125 // filter mouse move events happening outside the list box
126 void OnMouseMove(wxMouseEvent& event);
127
128 // called whenever the user selects or activates a listbox item
129 void OnSelect(wxCommandEvent& event);
130
131 // used to process wxUniv actions
132 bool PerformAction(const wxControlAction& action,
133 long numArg,
134 const wxString& strArg);
135
136private:
137 DECLARE_EVENT_TABLE()
138};
139
140// ----------------------------------------------------------------------------
141// wxComboTextCtrl is a simple text ctrl which forwards
142// wxEVT_COMMAND_TEXT_UPDATED events and all key events to the combobox
143// ----------------------------------------------------------------------------
144
145class wxComboTextCtrl : public wxTextCtrl
146{
147public:
148 wxComboTextCtrl(wxComboControl *combo,
149 const wxString& value,
150 long style,
151 const wxValidator& validator);
152
153protected:
154 void OnKey(wxKeyEvent& event);
155 void OnText(wxCommandEvent& event);
156
157private:
158 wxComboControl *m_combo;
159
160 DECLARE_EVENT_TABLE()
161};
162
163// ----------------------------------------------------------------------------
164// event tables and such
165// ----------------------------------------------------------------------------
166
167BEGIN_EVENT_TABLE(wxComboButton, wxButton)
168 EVT_BUTTON(-1, wxComboButton::OnButton)
169END_EVENT_TABLE()
170
171BEGIN_EVENT_TABLE(wxComboListBox, wxListBox)
172 EVT_LISTBOX(-1, wxComboListBox::OnSelect)
173 EVT_LISTBOX_DCLICK(-1, wxComboListBox::OnSelect)
174 EVT_MOTION(wxComboListBox::OnMouseMove)
175END_EVENT_TABLE()
176
177BEGIN_EVENT_TABLE(wxComboControl, wxControl)
178 EVT_KEY_DOWN(wxComboControl::OnKey)
179 EVT_KEY_UP(wxComboControl::OnKey)
180END_EVENT_TABLE()
181
182BEGIN_EVENT_TABLE(wxComboTextCtrl, wxTextCtrl)
183 EVT_KEY_DOWN(wxComboTextCtrl::OnKey)
184 EVT_KEY_UP(wxComboTextCtrl::OnKey)
185 EVT_TEXT(-1, wxComboTextCtrl::OnText)
186END_EVENT_TABLE()
187
188IMPLEMENT_DYNAMIC_CLASS(wxComboBox, wxControl);
189
190// ============================================================================
191// implementation
192// ============================================================================
193
194// ----------------------------------------------------------------------------
195// wxComboControl creation
196// ----------------------------------------------------------------------------
197
198void wxComboControl::Init()
199{
200 m_popup = (wxComboPopup *)NULL;
201 m_winPopup = (wxPopupComboWindow *)NULL;
202 m_isPopupShown = FALSE;
203}
204
205bool wxComboControl::Create(wxWindow *parent,
206 wxWindowID id,
207 const wxString& value,
208 const wxPoint& pos,
209 const wxSize& size,
210 long style,
211 const wxValidator& validator,
212 const wxString& name)
213{
214 // first create our own window, i.e. the one which will contain all
215 // subcontrols
216 style &= ~wxBORDER_NONE;
217 style |= wxBORDER_SUNKEN;
218 if ( !wxControl::Create(parent, id, pos, size, style, validator, name) )
219 return FALSE;
220
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,
225 value,
226 style & wxCB_READONLY ? wxTE_READONLY : 0,
227 validator);
228
229 // for compatibility with the other ports, the height specified is the
230 // combined height of the combobox itself and the popup
231 if ( size.y == -1 )
232 {
233 // ok, use default height for popup too
234 m_heightPopup = -1;
235 }
236 else
237 {
238 m_heightPopup = size.y - DoGetBestSize().y;
239 }
240
241 DoSetSize(pos.x, pos.y, size.x, size.y);
242
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);
246
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!
251 m_isEnabled = TRUE;
252
253 CreateInputHandler(wxINP_HANDLER_COMBOBOX);
254
255 return TRUE;
256}
257
258wxComboControl::~wxComboControl()
259{
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
263 delete m_btn;
264 delete m_text;
265
266 delete m_winPopup;
267}
268
269// ----------------------------------------------------------------------------
270// geometry stuff
271// ----------------------------------------------------------------------------
272
273void wxComboControl::DoSetSize(int x, int y,
274 int width, int height,
275 int sizeFlags)
276{
277 // combo height is always fixed
278 wxControl::DoSetSize(x, y, width, DoGetBestSize().y, sizeFlags);
279}
280
281wxSize wxComboControl::DoGetBestClientSize() const
282{
283 wxSize sizeBtn = m_btn->GetBestSize(),
284 sizeText = m_text->GetBestSize();
285
286 return wxSize(sizeBtn.x + sizeText.x, wxMax(sizeBtn.y, sizeText.y));
287}
288
289void wxComboControl::DoMoveWindow(int x, int y, int width, int height)
290{
291 wxControl::DoMoveWindow(x, y, width, height);
292
293 // position the subcontrols inside the client area
294 wxRect rectBorders = GetRenderer()->GetBorderDimensions(GetBorder());
295 x += rectBorders.x;
296 y += rectBorders.y;
297 width -= rectBorders.x + rectBorders.width;
298 height -= rectBorders.y + rectBorders.height;
299
300 wxSize sizeBtn = m_btn->GetSize(),
301 sizeText = m_text->GetSize();
302
303 wxCoord wText = width - sizeBtn.x;
304 m_text->SetSize(x, y, wText, height);
305 m_btn->SetSize(x + wText, y, -1, height);
306}
307
308// ----------------------------------------------------------------------------
309// operations
310// ----------------------------------------------------------------------------
311
312bool wxComboControl::Enable(bool enable)
313{
314 if ( !wxControl::Enable(enable) )
315 return FALSE;
316
317 m_btn->Enable(enable);
318 m_text->Enable(enable);
319
320 return TRUE;
321}
322
323bool wxComboControl::Show(bool show)
324{
325 if ( !wxControl::Show(show) )
326 return FALSE;
327
328 m_btn->Show(show);
329 m_text->Show(show);
330
331 return TRUE;
332}
333
334// ----------------------------------------------------------------------------
335// popup window handling
336// ----------------------------------------------------------------------------
337
338void wxComboControl::SetPopupControl(wxComboPopup *popup)
339{
340 m_popup = popup;
341}
342
343void wxComboControl::ShowPopup()
344{
345 wxCHECK_RET( m_popup, _T("no popup to show in wxComboControl") );
346 wxCHECK_RET( !IsPopupShown(), _T("popup window already shown") );
347
348 wxControl *control = m_popup->GetControl();
349
350 // size and position the popup window correctly
351 m_winPopup->SetSize(GetSize().x,
352 m_heightPopup == -1 ? control->GetBestSize().y
353 : m_heightPopup);
354 wxSize sizePopup = m_winPopup->GetClientSize();
355 control->SetSize(0, 0, sizePopup.x, sizePopup.y);
356
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 )
361 {
362 m_winPopup->SetClientSize(sizeReal);
363 }
364
365 m_winPopup->PositionNearCombo();
366
367 // show it
368 m_winPopup->Popup(m_text);
369 m_text->SelectAll();
370 m_popup->SetSelection(m_text->GetValue());
371
372 m_isPopupShown = TRUE;
373}
374
375void wxComboControl::HidePopup()
376{
377 wxCHECK_RET( m_popup, _T("no popup to hide in wxComboControl") );
378 wxCHECK_RET( IsPopupShown(), _T("popup window not shown") );
379
380 m_winPopup->Dismiss();
381
382 m_isPopupShown = FALSE;
383}
384
385void wxComboControl::OnSelect(const wxString& value)
386{
387 m_text->SetValue(value);
388 m_text->SelectAll();
389
390 OnDismiss();
391}
392
393void wxComboControl::OnDismiss()
394{
395 HidePopup();
396 m_text->SetFocus();
397}
398
399// ----------------------------------------------------------------------------
400// wxComboTextCtrl
401// ----------------------------------------------------------------------------
402
403wxComboTextCtrl::wxComboTextCtrl(wxComboControl *combo,
404 const wxString& value,
405 long style,
406 const wxValidator& validator)
407 : wxTextCtrl(combo->GetParent(), -1, value,
408 wxDefaultPosition, wxDefaultSize,
409 wxBORDER_NONE | style,
410 validator)
411{
412 m_combo = combo;
413}
414
415void wxComboTextCtrl::OnText(wxCommandEvent& event)
416{
417 if ( m_combo->IsPopupShown() )
418 {
419 m_combo->GetPopupControl()->SetSelection(GetValue());
420 }
421
422 // we need to make a copy of the event to have the correct originating
423 // object and id
424 wxCommandEvent event2 = event;
425 event2.SetEventObject(m_combo);
426 event2.SetId(m_combo->GetId());
427
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
431 // uses
432 //
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);
436
437 event.Skip();
438}
439
440// pass the keys we don't process to the combo first
441void wxComboTextCtrl::OnKey(wxKeyEvent& event)
442{
443 switch ( event.GetKeyCode() )
444 {
445 case WXK_RETURN:
446 // the popup control gets it first but only if it is shown
447 if ( !m_combo->IsPopupShown() )
448 break;
449 //else: fall through
450
451 case WXK_UP:
452 case WXK_DOWN:
453 case WXK_ESCAPE:
454 case WXK_PAGEDOWN:
455 case WXK_PAGEUP:
456 case WXK_PRIOR:
457 case WXK_NEXT:
458 (void)m_combo->ProcessEvent(event);
459 return;
460 }
461
462 event.Skip();
463}
464
465// ----------------------------------------------------------------------------
466// wxComboListBox
467// ----------------------------------------------------------------------------
468
469wxComboListBox::wxComboListBox(wxComboControl *combo, int style)
470 : wxListBox(combo->GetPopupWindow(), -1,
471 wxDefaultPosition, wxDefaultSize,
472 0, NULL,
473 wxBORDER_SIMPLE | wxLB_INT_HEIGHT | style),
474 wxComboPopup(combo)
475{
476 // we don't react to the mouse events outside the window at all
477 StopAutoScrolling();
478}
479
480wxComboListBox::~wxComboListBox()
481{
482}
483
484bool wxComboListBox::SetSelection(const wxString& value)
485{
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
488 if ( value.empty() )
489 {
490 if ( GetCount() )
491 {
492 wxListBox::SetSelection(0);
493 }
494 //else: empty listbox - nothing to do
495 }
496 else if ( !FindItem(value) )
497 {
498 // no match att all
499 return FALSE;
500 }
501
502 return TRUE;
503}
504
505void wxComboListBox::OnSelect(wxCommandEvent& event)
506{
507 // first let the user code have the event
508
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);
516
517 // next update the combo and close the listbox
518 m_combo->OnSelect(event.GetString());
519}
520
521void wxComboListBox::OnShow()
522{
523}
524
525bool wxComboListBox::PerformAction(const wxControlAction& action,
526 long numArg,
527 const wxString& strArg)
528
529{
530 if ( action == wxACTION_LISTBOX_FIND )
531 {
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
535 return TRUE;
536 }
537
538 return wxListBox::PerformAction(action, numArg, strArg);
539}
540
541void wxComboListBox::OnMouseMove(wxMouseEvent& event)
542{
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() )
548 {
549 if ( HitTest(event.GetPosition()) == wxHT_WINDOW_INSIDE )
550 {
551 event.Skip();
552 }
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()
556 }
557}
558
559wxSize wxComboListBox::DoGetBestClientSize() const
560{
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();
564
565 int nLines = size.y / hChar;
566
567 // 10 is the same limit as used by wxMSW
568 if ( nLines > 10 )
569 {
570 size.y = 10*hChar;
571 }
572
573 return size;
574}
575
576// ----------------------------------------------------------------------------
577// wxComboBox
578// ----------------------------------------------------------------------------
579
580void wxComboBox::Init()
581{
582 m_lbox = (wxListBox *)NULL;
583}
584
585bool wxComboBox::Create(wxWindow *parent,
586 wxWindowID id,
587 const wxString& value,
588 const wxPoint& pos,
589 const wxSize& size,
590 int n,
591 const wxString *choices,
592 long style,
593 const wxValidator& validator,
594 const wxString& name)
595{
596 if ( !wxComboControl::Create(parent, id, value, pos, size, style,
597 validator, name) )
598 {
599 return FALSE;
600 }
601
602 wxComboListBox *combolbox =
603 new wxComboListBox(this, style & wxCB_SORT ? wxLB_SORT : 0);
604 m_lbox = combolbox;
605 m_lbox->Set(n, choices);
606
607 SetPopupControl(combolbox);
608
609 return TRUE;
610}
611
612wxComboBox::~wxComboBox()
613{
614}
615
616// ----------------------------------------------------------------------------
617// wxComboBox methods forwarded to wxTextCtrl
618// ----------------------------------------------------------------------------
619
620wxString wxComboBox::GetValue() const
621{
622 return GetText()->GetValue();
623}
624
625void wxComboBox::SetValue(const wxString& value)
626{
627 GetText()->SetValue(value);
628}
629
630void wxComboBox::Copy()
631{
632 GetText()->Copy();
633}
634
635void wxComboBox::Cut()
636{
637 GetText()->Cut();
638}
639
640void wxComboBox::Paste()
641{
642 GetText()->Paste();
643}
644
645void wxComboBox::SetInsertionPoint(long pos)
646{
647 GetText()->SetInsertionPoint(pos);
648}
649
650void wxComboBox::SetInsertionPointEnd()
651{
652 GetText()->SetInsertionPointEnd();
653}
654
655long wxComboBox::GetInsertionPoint() const
656{
657 return GetText()->GetInsertionPoint();
658}
659
660long wxComboBox::GetLastPosition() const
661{
662 return GetText()->GetLastPosition();
663}
664
665void wxComboBox::Replace(long from, long to, const wxString& value)
666{
667 GetText()->Replace(from, to, value);
668}
669
670void wxComboBox::Remove(long from, long to)
671{
672 GetText()->Remove(from, to);
673}
674
675void wxComboBox::SetSelection(long from, long to)
676{
677 GetText()->SetSelection(from, to);
678}
679
680void wxComboBox::SetEditable(bool editable)
681{
682 GetText()->SetEditable(editable);
683}
684
685// ----------------------------------------------------------------------------
686// wxComboBox methods forwarded to wxListBox
687// ----------------------------------------------------------------------------
688
689void wxComboBox::Clear()
690{
691 GetLBox()->Clear();
692}
693
694void wxComboBox::Delete(int n)
695{
696 GetLBox()->Delete(n);
697}
698
699int wxComboBox::GetCount() const
700{
701 return GetLBox()->GetCount();
702}
703
704wxString wxComboBox::GetString(int n) const
705{
706 return GetLBox()->GetString(n);
707}
708
709void wxComboBox::SetString(int n, const wxString& s)
710{
711 GetLBox()->SetString(n, s);
712}
713
714int wxComboBox::FindString(const wxString& s) const
715{
716 return GetLBox()->FindString(s);
717}
718
719void wxComboBox::Select(int n)
720{
721 wxCHECK_RET( (n >= 0) && (n < GetCount()), _T("invalid combobox index") );
722
723 GetLBox()->SetSelection(n);
724 GetText()->SetValue(GetLBox()->GetString(n));
725}
726
727int wxComboBox::GetSelection() const
728{
729 // if the current value isn't one of the listbox strings, return -1
730 return FindString(GetText()->GetValue());
731}
732
733int wxComboBox::DoAppend(const wxString& item)
734{
735 return GetLBox()->Append(item);
736}
737
738void wxComboBox::DoSetItemClientData(int n, void* clientData)
739{
740 GetLBox()->SetClientData(n, clientData);
741}
742
743void *wxComboBox::DoGetItemClientData(int n) const
744{
745 return GetLBox()->GetClientData(n);
746}
747
748void wxComboBox::DoSetItemClientObject(int n, wxClientData* clientData)
749{
750 GetLBox()->SetClientObject(n, clientData);
751}
752
753wxClientData* wxComboBox::DoGetItemClientObject(int n) const
754{
755 return GetLBox()->GetClientObject(n);
756}
757
758// ----------------------------------------------------------------------------
759// input handling
760// ----------------------------------------------------------------------------
761
762void wxComboControl::OnKey(wxCommandEvent& event)
763{
764 if ( m_isPopupShown )
765 {
766 // pass it to the popped up control
767 (void)m_popup->GetControl()->ProcessEvent(event);
768 }
769 else // no popup
770 {
771 event.Skip();
772 }
773}
774
775bool wxComboControl::PerformAction(const wxControlAction& action,
776 long numArg,
777 const wxString& strArg)
778{
779 bool processed = FALSE;
780 if ( action == wxACTION_COMBOBOX_POPUP )
781 {
782 if ( !m_isPopupShown )
783 {
784 ShowPopup();
785
786 processed = TRUE;
787 }
788 }
789 else if ( action == wxACTION_COMBOBOX_DISMISS )
790 {
791 if ( m_isPopupShown )
792 {
793 HidePopup();
794
795 processed = TRUE;
796 }
797 }
798
799 if ( !processed )
800 {
801 // pass along
802 return wxControl::PerformAction(action, numArg, strArg);
803 }
804
805 return TRUE;
806}
807
808// ----------------------------------------------------------------------------
809// wxStdComboBoxInputHandler
810// ----------------------------------------------------------------------------
811
812wxStdComboBoxInputHandler::wxStdComboBoxInputHandler(wxInputHandler *inphand)
813 : wxStdInputHandler(inphand)
814{
815}
816
817bool wxStdComboBoxInputHandler::HandleKey(wxControl *control,
818 const wxKeyEvent& event,
819 bool pressed)
820{
821 if ( pressed )
822 {
823 wxControlAction action;
824 switch ( event.GetKeyCode() )
825 {
826 case WXK_DOWN:
827 action = wxACTION_COMBOBOX_POPUP;
828 break;
829
830 case WXK_ESCAPE:
831 action = wxACTION_COMBOBOX_DISMISS;
832 break;
833 }
834
835 if ( !!action )
836 {
837 control->PerformAction(action);
838
839 return TRUE;
840 }
841 }
842
843 return wxStdInputHandler::HandleKey(control, event, pressed);
844}
845
846#endif // wxUSE_COMBOBOX