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