]> git.saurik.com Git - wxWidgets.git/blob - src/common/combocmn.cpp
add alt popup window style allowing to use keyboard and comboboxes in the popup windo...
[wxWidgets.git] / src / common / combocmn.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/common/combocmn.cpp
3 // Purpose: wxComboCtrlBase
4 // Author: Jaakko Salli
5 // Modified by:
6 // Created: Apr-30-2006
7 // RCS-ID: $Id$
8 // Copyright: (c) 2005 Jaakko Salli
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 #include "wx/wxprec.h"
21
22 #ifdef __BORLANDC__
23 #pragma hdrstop
24 #endif
25
26 #if wxUSE_COMBOCTRL
27
28 #include "wx/combobox.h"
29
30 #ifndef WX_PRECOMP
31 #include "wx/log.h"
32 #include "wx/dcclient.h"
33 #include "wx/settings.h"
34 #include "wx/dialog.h"
35 #include "wx/timer.h"
36 #endif
37
38 #include "wx/tooltip.h"
39
40 #include "wx/combo.h"
41
42
43
44 // constants
45 // ----------------------------------------------------------------------------
46
47 // Milliseconds to wait for two mouse-ups after focus inorder
48 // to trigger a double-click.
49 #define DOUBLE_CLICK_CONVERSION_TRESHOLD 500
50
51 #define DEFAULT_DROPBUTTON_WIDTH 19
52
53 #define BMP_BUTTON_MARGIN 4
54
55 #define DEFAULT_POPUP_HEIGHT 400
56
57 #define DEFAULT_TEXT_INDENT 3
58
59 #define COMBO_MARGIN 2 // spacing right of wxTextCtrl
60
61
62 #if defined(__WXMSW__)
63
64 #define USE_TRANSIENT_POPUP 1 // Use wxPopupWindowTransient (preferred, if it works properly on platform)
65 #define TRANSIENT_POPUPWIN_IS_PERFECT 0 // wxPopupTransientWindow works, its child can have focus, and common
66 // native controls work on it like normal.
67 #define POPUPWIN_IS_PERFECT 0 // Same, but for non-transient popup window.
68 #define TEXTCTRL_TEXT_CENTERED 0 // 1 if text in textctrl is vertically centered
69
70 //#undef wxUSE_POPUPWIN
71 //#define wxUSE_POPUPWIN 0
72
73 #elif defined(__WXGTK__)
74
75 // NB: It is not recommended to use wxDialog as popup on wxGTK, because of
76 // this bug: If wxDialog is hidden, its position becomes corrupt
77 // between hide and next show, but without internal coordinates being
78 // reflected (or something like that - atleast commenting out ->Hide()
79 // seemed to eliminate the position change).
80
81 #define USE_TRANSIENT_POPUP 1 // Use wxPopupWindowTransient (preferred, if it works properly on platform)
82 #define TRANSIENT_POPUPWIN_IS_PERFECT 0 // wxPopupTransientWindow works, its child can have focus, and common
83 // native controls work on it like normal.
84 #define POPUPWIN_IS_PERFECT 1 // Same, but for non-transient popup window.
85 #define TEXTCTRL_TEXT_CENTERED 1 // 1 if text in textctrl is vertically centered
86
87 #elif defined(__WXMAC__)
88
89 #define USE_TRANSIENT_POPUP 0 // Use wxPopupWindowTransient (preferred, if it works properly on platform)
90 #define TRANSIENT_POPUPWIN_IS_PERFECT 0 // wxPopupTransientWindow works, its child can have focus, and common
91 // native controls work on it like normal.
92 #define POPUPWIN_IS_PERFECT 0 // Same, but for non-transient popup window.
93 #define TEXTCTRL_TEXT_CENTERED 1 // 1 if text in textctrl is vertically centered
94
95 #else
96
97 #define USE_TRANSIENT_POPUP 0 // Use wxPopupWindowTransient (preferred, if it works properly on platform)
98 #define TRANSIENT_POPUPWIN_IS_PERFECT 0 // wxPopupTransientWindow works, its child can have focus, and common
99 // native controls work on it like normal.
100 #define POPUPWIN_IS_PERFECT 0 // Same, but for non-transient popup window.
101 #define TEXTCTRL_TEXT_CENTERED 1 // 1 if text in textctrl is vertically centered
102
103 #endif
104
105
106 // Popupwin is really only supported on wxMSW (not WINCE) and wxGTK, regardless
107 // what the wxUSE_POPUPWIN says.
108 // FIXME: Why isn't wxUSE_POPUPWIN reliable any longer? (it was in wxW2.6.2)
109 #if (!defined(__WXMSW__) && !defined(__WXGTK__)) || defined(__WXWINCE__)
110 #undef wxUSE_POPUPWIN
111 #define wxUSE_POPUPWIN 0
112 #endif
113
114
115 #if wxUSE_POPUPWIN
116 #include "wx/popupwin.h"
117 #else
118 #undef USE_TRANSIENT_POPUP
119 #define USE_TRANSIENT_POPUP 0
120 #endif
121
122
123 // Define different types of popup windows
124 enum
125 {
126 POPUPWIN_NONE = 0,
127 POPUPWIN_WXPOPUPTRANSIENTWINDOW = 1,
128 POPUPWIN_WXPOPUPWINDOW = 2,
129 POPUPWIN_WXDIALOG = 3
130 };
131
132
133 #if USE_TRANSIENT_POPUP
134 // wxPopupTransientWindow is implemented
135
136 #define wxComboPopupWindowBase wxPopupTransientWindow
137 #define PRIMARY_POPUP_TYPE POPUPWIN_WXPOPUPTRANSIENTWINDOW
138 #define USES_WXPOPUPTRANSIENTWINDOW 1
139
140 #if TRANSIENT_POPUPWIN_IS_PERFECT
141 //
142 #elif POPUPWIN_IS_PERFECT
143 #define wxComboPopupWindowBase2 wxPopupWindow
144 #define SECONDARY_POPUP_TYPE POPUPWIN_WXPOPUPWINDOW
145 #define USES_WXPOPUPWINDOW 1
146 #else
147 #define wxComboPopupWindowBase2 wxDialog
148 #define SECONDARY_POPUP_TYPE POPUPWIN_WXDIALOG
149 #define USES_WXDIALOG 1
150 #endif
151
152 #elif wxUSE_POPUPWIN
153 // wxPopupWindow (but not wxPopupTransientWindow) is properly implemented
154
155 #define wxComboPopupWindowBase wxPopupWindow
156 #define PRIMARY_POPUP_TYPE POPUPWIN_WXPOPUPWINDOW
157 #define USES_WXPOPUPWINDOW 1
158
159 #if !POPUPWIN_IS_PERFECT
160 #define wxComboPopupWindowBase2 wxDialog
161 #define SECONDARY_POPUP_TYPE POPUPWIN_WXDIALOG
162 #define USES_WXDIALOG 1
163 #endif
164
165 #else
166 // wxPopupWindow is not implemented
167
168 #define wxComboPopupWindowBase wxDialog
169 #define PRIMARY_POPUP_TYPE POPUPWIN_WXDIALOG
170 #define USES_WXDIALOG 1
171
172 #endif
173
174
175 #ifndef USES_WXPOPUPTRANSIENTWINDOW
176 #define USES_WXPOPUPTRANSIENTWINDOW 0
177 #endif
178
179 #ifndef USES_WXPOPUPWINDOW
180 #define USES_WXPOPUPWINDOW 0
181 #endif
182
183 #ifndef USES_WXDIALOG
184 #define USES_WXDIALOG 0
185 #endif
186
187
188 #if USES_WXPOPUPWINDOW
189 #define INSTALL_TOPLEV_HANDLER 1
190 #else
191 #define INSTALL_TOPLEV_HANDLER 0
192 #endif
193
194
195 //
196 // ** TODO **
197 // * wxComboPopupWindow for external use (ie. replace old wxUniv wxPopupComboWindow)
198 //
199
200
201 // ----------------------------------------------------------------------------
202 // wxComboFrameEventHandler takes care of hiding the popup when events happen
203 // in its top level parent.
204 // ----------------------------------------------------------------------------
205
206 #if INSTALL_TOPLEV_HANDLER
207
208 //
209 // This will no longer be necessary after wxTransientPopupWindow
210 // works well on all platforms.
211 //
212
213 class wxComboFrameEventHandler : public wxEvtHandler
214 {
215 public:
216 wxComboFrameEventHandler( wxComboCtrlBase* pCb );
217 virtual ~wxComboFrameEventHandler();
218
219 void OnPopup();
220
221 void OnIdle( wxIdleEvent& event );
222 void OnMouseEvent( wxMouseEvent& event );
223 void OnActivate( wxActivateEvent& event );
224 void OnResize( wxSizeEvent& event );
225 void OnMove( wxMoveEvent& event );
226 void OnMenuEvent( wxMenuEvent& event );
227 void OnClose( wxCloseEvent& event );
228
229 protected:
230 wxWindow* m_focusStart;
231 wxComboCtrlBase* m_combo;
232
233 private:
234 DECLARE_EVENT_TABLE()
235 };
236
237 BEGIN_EVENT_TABLE(wxComboFrameEventHandler, wxEvtHandler)
238 EVT_IDLE(wxComboFrameEventHandler::OnIdle)
239 EVT_LEFT_DOWN(wxComboFrameEventHandler::OnMouseEvent)
240 EVT_RIGHT_DOWN(wxComboFrameEventHandler::OnMouseEvent)
241 EVT_SIZE(wxComboFrameEventHandler::OnResize)
242 EVT_MOVE(wxComboFrameEventHandler::OnMove)
243 EVT_MENU_HIGHLIGHT(wxID_ANY,wxComboFrameEventHandler::OnMenuEvent)
244 EVT_MENU_OPEN(wxComboFrameEventHandler::OnMenuEvent)
245 EVT_ACTIVATE(wxComboFrameEventHandler::OnActivate)
246 EVT_CLOSE(wxComboFrameEventHandler::OnClose)
247 END_EVENT_TABLE()
248
249 wxComboFrameEventHandler::wxComboFrameEventHandler( wxComboCtrlBase* combo )
250 : wxEvtHandler()
251 {
252 m_combo = combo;
253 }
254
255 wxComboFrameEventHandler::~wxComboFrameEventHandler()
256 {
257 }
258
259 void wxComboFrameEventHandler::OnPopup()
260 {
261 m_focusStart = ::wxWindow::FindFocus();
262 }
263
264 void wxComboFrameEventHandler::OnIdle( wxIdleEvent& event )
265 {
266 wxWindow* winFocused = ::wxWindow::FindFocus();
267
268 wxWindow* popup = m_combo->GetPopupControl()->GetControl();
269 wxWindow* winpopup = m_combo->GetPopupWindow();
270
271 if (
272 winFocused != m_focusStart &&
273 winFocused != popup &&
274 winFocused->GetParent() != popup &&
275 winFocused != winpopup &&
276 winFocused->GetParent() != winpopup &&
277 winFocused != m_combo &&
278 winFocused != m_combo->GetButton() // GTK (atleast) requires this
279 )
280 {
281 m_combo->HidePopup();
282 }
283
284 event.Skip();
285 }
286
287 void wxComboFrameEventHandler::OnMenuEvent( wxMenuEvent& event )
288 {
289 m_combo->HidePopup();
290 event.Skip();
291 }
292
293 void wxComboFrameEventHandler::OnMouseEvent( wxMouseEvent& event )
294 {
295 m_combo->HidePopup();
296 event.Skip();
297 }
298
299 void wxComboFrameEventHandler::OnClose( wxCloseEvent& event )
300 {
301 m_combo->HidePopup();
302 event.Skip();
303 }
304
305 void wxComboFrameEventHandler::OnActivate( wxActivateEvent& event )
306 {
307 m_combo->HidePopup();
308 event.Skip();
309 }
310
311 void wxComboFrameEventHandler::OnResize( wxSizeEvent& event )
312 {
313 m_combo->HidePopup();
314 event.Skip();
315 }
316
317 void wxComboFrameEventHandler::OnMove( wxMoveEvent& event )
318 {
319 m_combo->HidePopup();
320 event.Skip();
321 }
322
323 #endif // INSTALL_TOPLEV_HANDLER
324
325 // ----------------------------------------------------------------------------
326 // wxComboPopupWindow is, in essence, wxPopupWindow customized for
327 // wxComboCtrl.
328 // ----------------------------------------------------------------------------
329
330 class wxComboPopupWindow : public wxComboPopupWindowBase
331 {
332 public:
333
334 wxComboPopupWindow( wxComboCtrlBase *parent,
335 int style )
336 #if USES_WXPOPUPWINDOW || USES_WXPOPUPTRANSIENTWINDOW
337 : wxComboPopupWindowBase(parent,style)
338 #else
339 : wxComboPopupWindowBase(parent,
340 wxID_ANY,
341 wxEmptyString,
342 wxPoint(-21,-21),
343 wxSize(20,20),
344 style)
345 #endif
346 {
347 }
348
349 #if USES_WXPOPUPTRANSIENTWINDOW
350 virtual bool ProcessLeftDown(wxMouseEvent& event);
351 virtual void OnDismiss();
352 #endif
353
354 };
355
356
357 #if USES_WXPOPUPTRANSIENTWINDOW
358 bool wxComboPopupWindow::ProcessLeftDown(wxMouseEvent& event )
359 {
360 return wxComboPopupWindowBase::ProcessLeftDown(event);
361 }
362
363 // First thing that happens when a transient popup closes is that this method gets called.
364 void wxComboPopupWindow::OnDismiss()
365 {
366 wxComboCtrlBase* combo = (wxComboCtrlBase*) GetParent();
367 wxASSERT_MSG( combo->IsKindOf(CLASSINFO(wxComboCtrlBase)),
368 wxT("parent might not be wxComboCtrl, but check IMPLEMENT_DYNAMIC_CLASS(2) macro for correctness") );
369
370 combo->OnPopupDismiss();
371 }
372 #endif // USES_WXPOPUPTRANSIENTWINDOW
373
374
375 // ----------------------------------------------------------------------------
376 // wxComboPopupWindowEvtHandler does bulk of the custom event handling
377 // of a popup window. It is separate so we can have different types
378 // of popup windows.
379 // ----------------------------------------------------------------------------
380
381 class wxComboPopupWindowEvtHandler : public wxEvtHandler
382 {
383 public:
384
385 wxComboPopupWindowEvtHandler( wxComboCtrlBase *parent )
386 {
387 m_combo = parent;
388 }
389
390 void OnKeyEvent(wxKeyEvent& event);
391 #if USES_WXDIALOG
392 void OnActivate( wxActivateEvent& event );
393 #endif
394
395 private:
396 wxComboCtrlBase* m_combo;
397
398 DECLARE_EVENT_TABLE()
399 };
400
401
402 BEGIN_EVENT_TABLE(wxComboPopupWindowEvtHandler, wxEvtHandler)
403 EVT_KEY_DOWN(wxComboPopupWindowEvtHandler::OnKeyEvent)
404 EVT_KEY_UP(wxComboPopupWindowEvtHandler::OnKeyEvent)
405 #if USES_WXDIALOG
406 EVT_ACTIVATE(wxComboPopupWindowEvtHandler::OnActivate)
407 #endif
408 END_EVENT_TABLE()
409
410
411 void wxComboPopupWindowEvtHandler::OnKeyEvent( wxKeyEvent& event )
412 {
413 // Relay keyboard event to the main child controls
414 wxWindowList children = m_combo->GetPopupWindow()->GetChildren();
415 wxWindowList::iterator node = children.begin();
416 wxWindow* child = (wxWindow*)*node;
417 child->AddPendingEvent(event);
418 }
419
420 #if USES_WXDIALOG
421 void wxComboPopupWindowEvtHandler::OnActivate( wxActivateEvent& event )
422 {
423 if ( !event.GetActive() )
424 {
425 // Tell combo control that we are dismissed.
426 m_combo->HidePopup();
427
428 event.Skip();
429 }
430 }
431 #endif
432
433
434 // ----------------------------------------------------------------------------
435 // wxComboPopup
436 //
437 // ----------------------------------------------------------------------------
438
439 wxComboPopup::~wxComboPopup()
440 {
441 }
442
443 void wxComboPopup::OnPopup()
444 {
445 }
446
447 void wxComboPopup::OnDismiss()
448 {
449 }
450
451 wxSize wxComboPopup::GetAdjustedSize( int minWidth,
452 int prefHeight,
453 int WXUNUSED(maxHeight) )
454 {
455 return wxSize(minWidth,prefHeight);
456 }
457
458 void wxComboPopup::DefaultPaintComboControl( wxComboCtrlBase* combo,
459 wxDC& dc, const wxRect& rect )
460 {
461 if ( combo->GetWindowStyle() & wxCB_READONLY ) // ie. no textctrl
462 {
463 combo->PrepareBackground(dc,rect,0);
464
465 dc.DrawText( combo->GetValue(),
466 rect.x + combo->GetTextIndent(),
467 (rect.height-dc.GetCharHeight())/2 + rect.y );
468 }
469 }
470
471 void wxComboPopup::PaintComboControl( wxDC& dc, const wxRect& rect )
472 {
473 DefaultPaintComboControl(m_combo,dc,rect);
474 }
475
476 void wxComboPopup::OnComboKeyEvent( wxKeyEvent& event )
477 {
478 event.Skip();
479 }
480
481 void wxComboPopup::OnComboDoubleClick()
482 {
483 }
484
485 void wxComboPopup::SetStringValue( const wxString& WXUNUSED(value) )
486 {
487 }
488
489 bool wxComboPopup::LazyCreate()
490 {
491 return false;
492 }
493
494 void wxComboPopup::Dismiss()
495 {
496 m_combo->HidePopup();
497 }
498
499 // ----------------------------------------------------------------------------
500 // input handling
501 // ----------------------------------------------------------------------------
502
503 //
504 // This is pushed to the event handler queue of the child textctrl.
505 //
506 class wxComboBoxExtraInputHandler : public wxEvtHandler
507 {
508 public:
509
510 wxComboBoxExtraInputHandler( wxComboCtrlBase* combo )
511 : wxEvtHandler()
512 {
513 m_combo = combo;
514 }
515 virtual ~wxComboBoxExtraInputHandler() { }
516 void OnKey(wxKeyEvent& event);
517 void OnFocus(wxFocusEvent& event);
518
519 protected:
520 wxComboCtrlBase* m_combo;
521
522 private:
523 DECLARE_EVENT_TABLE()
524 };
525
526
527 BEGIN_EVENT_TABLE(wxComboBoxExtraInputHandler, wxEvtHandler)
528 EVT_KEY_DOWN(wxComboBoxExtraInputHandler::OnKey)
529 EVT_SET_FOCUS(wxComboBoxExtraInputHandler::OnFocus)
530 END_EVENT_TABLE()
531
532
533 void wxComboBoxExtraInputHandler::OnKey(wxKeyEvent& event)
534 {
535 // Let the wxComboCtrl event handler have a go first.
536 wxComboCtrlBase* combo = m_combo;
537 wxObject* prevObj = event.GetEventObject();
538
539 event.SetId(combo->GetId());
540 event.SetEventObject(combo);
541 combo->GetEventHandler()->ProcessEvent(event);
542
543 event.SetId(((wxWindow*)prevObj)->GetId());
544 event.SetEventObject(prevObj);
545 }
546
547 void wxComboBoxExtraInputHandler::OnFocus(wxFocusEvent& event)
548 {
549 // FIXME: This code does run when control is clicked,
550 // yet on Windows it doesn't select all the text.
551 if ( !(m_combo->GetInternalFlags() & wxCC_NO_TEXT_AUTO_SELECT) )
552 {
553 if ( m_combo->GetTextCtrl() )
554 m_combo->GetTextCtrl()->SelectAll();
555 else
556 m_combo->SetSelection(-1,-1);
557 }
558
559 // Send focus indication to parent.
560 // NB: This is needed for cases where the textctrl gets focus
561 // instead of its parent. While this may trigger multiple
562 // wxEVT_SET_FOCUSes (since m_text->SetFocus is called
563 // from combo's focus event handler), they should be quite
564 // harmless.
565 wxFocusEvent evt2(wxEVT_SET_FOCUS,m_combo->GetId());
566 evt2.SetEventObject(m_combo);
567 m_combo->GetEventHandler()->ProcessEvent(evt2);
568
569 event.Skip();
570 }
571
572
573 //
574 // This is pushed to the event handler queue of the control in popup.
575 //
576
577 class wxComboPopupExtraEventHandler : public wxEvtHandler
578 {
579 public:
580
581 wxComboPopupExtraEventHandler( wxComboCtrlBase* combo )
582 : wxEvtHandler()
583 {
584 m_combo = combo;
585 m_beenInside = false;
586 }
587 virtual ~wxComboPopupExtraEventHandler() { }
588
589 void OnMouseEvent( wxMouseEvent& event );
590
591 // Called from wxComboCtrlBase::OnPopupDismiss
592 void OnPopupDismiss()
593 {
594 m_beenInside = false;
595 }
596
597 protected:
598 wxComboCtrlBase* m_combo;
599
600 bool m_beenInside;
601
602 private:
603 DECLARE_EVENT_TABLE()
604 };
605
606
607 BEGIN_EVENT_TABLE(wxComboPopupExtraEventHandler, wxEvtHandler)
608 EVT_MOUSE_EVENTS(wxComboPopupExtraEventHandler::OnMouseEvent)
609 END_EVENT_TABLE()
610
611
612 void wxComboPopupExtraEventHandler::OnMouseEvent( wxMouseEvent& event )
613 {
614 wxPoint pt = event.GetPosition();
615 wxSize sz = m_combo->GetPopupControl()->GetControl()->GetClientSize();
616 int evtType = event.GetEventType();
617 bool isInside = pt.x >= 0 && pt.y >= 0 && pt.x < sz.x && pt.y < sz.y;
618
619 if ( evtType == wxEVT_MOTION ||
620 evtType == wxEVT_LEFT_DOWN ||
621 evtType == wxEVT_RIGHT_DOWN )
622 {
623 // Block motion and click events outside the popup
624 if ( !isInside )
625 {
626 event.Skip(false);
627 return;
628 }
629 }
630 else if ( evtType == wxEVT_LEFT_UP )
631 {
632 // Don't let left-down events in if outside
633 if ( evtType == wxEVT_LEFT_DOWN )
634 {
635 if ( !isInside )
636 return;
637 }
638
639 if ( !m_beenInside )
640 {
641 if ( isInside )
642 {
643 m_beenInside = true;
644 }
645 else
646 {
647 //
648 // Some mouse events to popup that happen outside it, before cursor
649 // has been inside the popu, need to be ignored by it but relayed to
650 // the dropbutton.
651 //
652 wxWindow* btn = m_combo->GetButton();
653 if ( btn )
654 btn->GetEventHandler()->AddPendingEvent(event);
655 else
656 m_combo->GetEventHandler()->AddPendingEvent(event);
657
658 return;
659 }
660
661 event.Skip();
662 }
663 }
664
665 event.Skip();
666 }
667
668 // ----------------------------------------------------------------------------
669 // wxComboCtrlBase
670 // ----------------------------------------------------------------------------
671
672
673 BEGIN_EVENT_TABLE(wxComboCtrlBase, wxControl)
674 EVT_TEXT(wxID_ANY,wxComboCtrlBase::OnTextCtrlEvent)
675 EVT_SIZE(wxComboCtrlBase::OnSizeEvent)
676 EVT_SET_FOCUS(wxComboCtrlBase::OnFocusEvent)
677 EVT_KILL_FOCUS(wxComboCtrlBase::OnFocusEvent)
678 //EVT_BUTTON(wxID_ANY,wxComboCtrlBase::OnButtonClickEvent)
679 EVT_KEY_DOWN(wxComboCtrlBase::OnKeyEvent)
680 EVT_TEXT_ENTER(wxID_ANY,wxComboCtrlBase::OnTextCtrlEvent)
681 EVT_SYS_COLOUR_CHANGED(wxComboCtrlBase::OnSysColourChanged)
682 END_EVENT_TABLE()
683
684
685 IMPLEMENT_ABSTRACT_CLASS(wxComboCtrlBase, wxControl)
686
687 void wxComboCtrlBase::Init()
688 {
689 m_winPopup = (wxWindow *)NULL;
690 m_popup = (wxWindow *)NULL;
691 m_isPopupShown = false;
692 m_btn = (wxWindow*) NULL;
693 m_text = (wxTextCtrl*) NULL;
694 m_popupInterface = (wxComboPopup*) NULL;
695
696 m_popupExtraHandler = (wxEvtHandler*) NULL;
697 m_textEvtHandler = (wxEvtHandler*) NULL;
698
699 #if INSTALL_TOPLEV_HANDLER
700 m_toplevEvtHandler = (wxEvtHandler*) NULL;
701 #endif
702
703 m_heightPopup = -1;
704 m_widthMinPopup = -1;
705 m_anchorSide = 0;
706 m_widthCustomPaint = 0;
707 m_widthCustomBorder = 0;
708
709 m_btnState = 0;
710 m_btnWidDefault = 0;
711 m_blankButtonBg = false;
712 m_ignoreEvtText = 0;
713 m_popupWinType = POPUPWIN_NONE;
714 m_btnWid = m_btnHei = -1;
715 m_btnSide = wxRIGHT;
716 m_btnSpacingX = 0;
717
718 m_extLeft = 0;
719 m_extRight = 0;
720 m_absIndent = -1;
721 m_iFlags = 0;
722 m_timeCanAcceptClick = 0;
723 }
724
725 bool wxComboCtrlBase::Create(wxWindow *parent,
726 wxWindowID id,
727 const wxString& value,
728 const wxPoint& pos,
729 const wxSize& size,
730 long style,
731 const wxValidator& validator,
732 const wxString& name)
733 {
734 if ( !wxControl::Create(parent,
735 id,
736 pos,
737 size,
738 style | wxWANTS_CHARS,
739 validator,
740 name) )
741 return false;
742
743 m_valueString = value;
744
745 // Get colours
746 OnThemeChange();
747 m_absIndent = GetNativeTextIndent();
748
749 m_iFlags |= wxCC_IFLAG_CREATED;
750
751 // If x and y indicate valid size, wxSizeEvent won't be
752 // emitted automatically, so we need to add artifical one.
753 if ( size.x > 0 && size.y > 0 )
754 {
755 wxSizeEvent evt(size,GetId());
756 GetEventHandler()->AddPendingEvent(evt);
757 }
758
759 return true;
760 }
761
762 void wxComboCtrlBase::InstallInputHandlers()
763 {
764 if ( m_text )
765 {
766 m_textEvtHandler = new wxComboBoxExtraInputHandler(this);
767 m_text->PushEventHandler(m_textEvtHandler);
768 }
769 }
770
771 void
772 wxComboCtrlBase::CreateTextCtrl(int style, const wxValidator& validator)
773 {
774 if ( !(m_windowStyle & wxCB_READONLY) )
775 {
776 if ( m_text )
777 m_text->Destroy();
778
779 // wxTE_PROCESS_TAB is needed because on Windows, wxTAB_TRAVERSAL is
780 // not used by the wxPropertyGrid and therefore the tab is processed by
781 // looking at ancestors to see if they have wxTAB_TRAVERSAL. The
782 // navigation event is then sent to the wrong window.
783 style |= wxTE_PROCESS_TAB;
784
785 if ( HasFlag(wxTE_PROCESS_ENTER) )
786 style |= wxTE_PROCESS_ENTER;
787
788 // Ignore EVT_TEXT generated by the constructor (but only
789 // if the event redirector already exists)
790 // NB: This must be " = 1" instead of "++";
791 if ( m_textEvtHandler )
792 m_ignoreEvtText = 1;
793 else
794 m_ignoreEvtText = 0;
795
796 m_text = new wxTextCtrl(this, wxID_ANY, m_valueString,
797 wxDefaultPosition, wxDefaultSize,
798 style, validator);
799
800 // This is required for some platforms (GTK+ atleast)
801 m_text->SetSizeHints(2,4);
802 }
803 }
804
805 void wxComboCtrlBase::OnThemeChange()
806 {
807 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
808 }
809
810 wxComboCtrlBase::~wxComboCtrlBase()
811 {
812 if ( HasCapture() )
813 ReleaseMouse();
814
815 #if INSTALL_TOPLEV_HANDLER
816 delete ((wxComboFrameEventHandler*)m_toplevEvtHandler);
817 m_toplevEvtHandler = (wxEvtHandler*) NULL;
818 #endif
819
820 DestroyPopup();
821
822 if ( m_text )
823 m_text->RemoveEventHandler(m_textEvtHandler);
824
825 delete m_textEvtHandler;
826 }
827
828
829 // ----------------------------------------------------------------------------
830 // geometry stuff
831 // ----------------------------------------------------------------------------
832
833 // Recalculates button and textctrl areas
834 void wxComboCtrlBase::CalculateAreas( int btnWidth )
835 {
836 wxSize sz = GetClientSize();
837 int customBorder = m_widthCustomBorder;
838 int btnBorder; // border for button only
839
840 // check if button should really be outside the border: we'll do it it if
841 // its platform default or bitmap+pushbutton background is used, but not if
842 // there is vertical size adjustment or horizontal spacing.
843 if ( ( (m_iFlags & wxCC_BUTTON_OUTSIDE_BORDER) ||
844 (m_bmpNormal.Ok() && m_blankButtonBg) ) &&
845 m_btnSpacingX == 0 &&
846 m_btnHei <= 0 )
847 {
848 m_iFlags |= wxCC_IFLAG_BUTTON_OUTSIDE;
849 btnBorder = 0;
850 }
851 else
852 {
853 m_iFlags &= ~(wxCC_IFLAG_BUTTON_OUTSIDE);
854 btnBorder = customBorder;
855 }
856
857 // Defaul indentation
858 if ( m_absIndent < 0 )
859 m_absIndent = GetNativeTextIndent();
860
861 int butWidth = btnWidth;
862
863 if ( butWidth <= 0 )
864 butWidth = m_btnWidDefault;
865 else
866 m_btnWidDefault = butWidth;
867
868 if ( butWidth <= 0 )
869 return;
870
871 int butHeight = sz.y - btnBorder*2;
872
873 // Adjust button width
874 if ( m_btnWid > 0 )
875 butWidth = m_btnWid;
876 else
877 {
878 // Adjust button width to match aspect ratio
879 // (but only if control is smaller than best size).
880 int bestHeight = GetBestSize().y;
881 int height = GetSize().y;
882
883 if ( height < bestHeight )
884 {
885 // Make very small buttons square, as it makes
886 // them accommodate arrow image better and still
887 // looks decent.
888 if ( height > 18 )
889 butWidth = (height*butWidth)/bestHeight;
890 else
891 butWidth = butHeight;
892 }
893 }
894
895 // Adjust button height
896 if ( m_btnHei > 0 )
897 butHeight = m_btnHei;
898
899 // Use size of normal bitmap if...
900 // It is larger
901 // OR
902 // button width is set to default and blank button bg is not drawn
903 if ( m_bmpNormal.Ok() )
904 {
905 int bmpReqWidth = m_bmpNormal.GetWidth();
906 int bmpReqHeight = m_bmpNormal.GetHeight();
907
908 // If drawing blank button background, we need to add some margin.
909 if ( m_blankButtonBg )
910 {
911 bmpReqWidth += BMP_BUTTON_MARGIN*2;
912 bmpReqHeight += BMP_BUTTON_MARGIN*2;
913 }
914
915 if ( butWidth < bmpReqWidth || ( m_btnWid == 0 && !m_blankButtonBg ) )
916 butWidth = bmpReqWidth;
917 if ( butHeight < bmpReqHeight || ( m_btnHei == 0 && !m_blankButtonBg ) )
918 butHeight = bmpReqHeight;
919
920 // Need to fix height?
921 if ( (sz.y-(customBorder*2)) < butHeight && btnWidth == 0 )
922 {
923 int newY = butHeight+(customBorder*2);
924 SetClientSize(wxDefaultCoord,newY);
925 sz.y = newY;
926 }
927 }
928
929 int butAreaWid = butWidth + (m_btnSpacingX*2);
930
931 m_btnSize.x = butWidth;
932 m_btnSize.y = butHeight;
933
934 m_btnArea.x = ( m_btnSide==wxRIGHT ? sz.x - butAreaWid - btnBorder : btnBorder );
935 m_btnArea.y = btnBorder;
936 m_btnArea.width = butAreaWid;
937 m_btnArea.height = sz.y - (btnBorder*2);
938
939 m_tcArea.x = ( m_btnSide==wxRIGHT ? 0 : butAreaWid ) + customBorder;
940 m_tcArea.y = customBorder;
941 m_tcArea.width = sz.x - butAreaWid - (customBorder*2);
942 m_tcArea.height = sz.y - (customBorder*2);
943
944 /*
945 if ( m_text )
946 {
947 ::wxMessageBox(wxString::Format(wxT("ButtonArea (%i,%i,%i,%i)\n"),m_btnArea.x,m_btnArea.y,m_btnArea.width,m_btnArea.height) +
948 wxString::Format(wxT("TextCtrlArea (%i,%i,%i,%i)"),m_tcArea.x,m_tcArea.y,m_tcArea.width,m_tcArea.height));
949 }
950 */
951 }
952
953 void wxComboCtrlBase::PositionTextCtrl( int textCtrlXAdjust, int textCtrlYAdjust )
954 {
955 if ( !m_text )
956 return;
957
958 wxSize sz = GetClientSize();
959 int customBorder = m_widthCustomBorder;
960
961 #if !TEXTCTRL_TEXT_CENTERED
962 if ( (m_text->GetWindowStyleFlag() & wxBORDER_MASK) == wxNO_BORDER )
963 {
964 // Centre textctrl
965 int tcSizeY = m_text->GetBestSize().y;
966 int diff = sz.y - tcSizeY;
967 int y = textCtrlYAdjust + (diff/2);
968
969 if ( y < customBorder )
970 y = customBorder;
971
972 m_text->SetSize( m_tcArea.x + m_widthCustomPaint + m_absIndent + textCtrlXAdjust,
973 y,
974 m_tcArea.width - COMBO_MARGIN -
975 (textCtrlXAdjust + m_widthCustomPaint + m_absIndent),
976 -1 );
977
978 // Make sure textctrl doesn't exceed the bottom custom border
979 wxSize tsz = m_text->GetSize();
980 diff = (y + tsz.y) - (sz.y - customBorder);
981 if ( diff >= 0 )
982 {
983 tsz.y = tsz.y - diff - 1;
984 m_text->SetSize(tsz);
985 }
986 }
987 else
988 #else
989 wxUnusedVar(textCtrlXAdjust);
990 wxUnusedVar(textCtrlYAdjust);
991 #endif
992 {
993 // If it has border, have textctrl will the entire text field.
994 m_text->SetSize( m_tcArea.x + m_widthCustomPaint,
995 customBorder,
996 sz.x - m_btnArea.width - m_widthCustomPaint - customBorder,
997 sz.y-(customBorder*2) );
998 }
999 }
1000
1001 wxSize wxComboCtrlBase::DoGetBestSize() const
1002 {
1003 wxSize sizeText(150,0);
1004
1005 if ( m_text )
1006 sizeText = m_text->GetBestSize();
1007
1008 // TODO: Better method to calculate close-to-native control height.
1009
1010 int fhei;
1011 if ( m_font.Ok() )
1012 fhei = (m_font.GetPointSize()*2) + 5;
1013 else if ( wxNORMAL_FONT->Ok() )
1014 fhei = (wxNORMAL_FONT->GetPointSize()*2) + 5;
1015 else
1016 fhei = sizeText.y + 4;
1017
1018 // Need to force height to accomodate bitmap?
1019 int btnSizeY = m_btnSize.y;
1020 if ( m_bmpNormal.Ok() && fhei < btnSizeY )
1021 fhei = btnSizeY;
1022
1023 // Control height doesn't depend on border
1024 /*
1025 // Add border
1026 int border = m_windowStyle & wxBORDER_MASK;
1027 if ( border == wxSIMPLE_BORDER )
1028 fhei += 2;
1029 else if ( border == wxNO_BORDER )
1030 fhei += (m_widthCustomBorder*2);
1031 else
1032 // Sunken etc.
1033 fhei += 4;
1034 */
1035
1036 // Final adjustments
1037 #ifdef __WXGTK__
1038 fhei += 1;
1039 #endif
1040
1041 wxSize ret(sizeText.x + COMBO_MARGIN + DEFAULT_DROPBUTTON_WIDTH,
1042 fhei);
1043
1044 CacheBestSize(ret);
1045 return ret;
1046 }
1047
1048 void wxComboCtrlBase::OnSizeEvent( wxSizeEvent& event )
1049 {
1050 if ( !IsCreated() )
1051 return;
1052
1053 // defined by actual wxComboCtrls
1054 OnResize();
1055
1056 event.Skip();
1057 }
1058
1059 // ----------------------------------------------------------------------------
1060 // standard operations
1061 // ----------------------------------------------------------------------------
1062
1063 bool wxComboCtrlBase::Enable(bool enable)
1064 {
1065 if ( !wxControl::Enable(enable) )
1066 return false;
1067
1068 if ( m_btn )
1069 m_btn->Enable(enable);
1070 if ( m_text )
1071 m_text->Enable(enable);
1072
1073 return true;
1074 }
1075
1076 bool wxComboCtrlBase::Show(bool show)
1077 {
1078 if ( !wxControl::Show(show) )
1079 return false;
1080
1081 if (m_btn)
1082 m_btn->Show(show);
1083
1084 if (m_text)
1085 m_text->Show(show);
1086
1087 return true;
1088 }
1089
1090 bool wxComboCtrlBase::SetFont ( const wxFont& font )
1091 {
1092 if ( !wxControl::SetFont(font) )
1093 return false;
1094
1095 if (m_text)
1096 m_text->SetFont(font);
1097
1098 return true;
1099 }
1100
1101 #if wxUSE_TOOLTIPS
1102 void wxComboCtrlBase::DoSetToolTip(wxToolTip *tooltip)
1103 {
1104 wxControl::DoSetToolTip(tooltip);
1105
1106 // Set tool tip for button and text box
1107 if ( tooltip )
1108 {
1109 const wxString &tip = tooltip->GetTip();
1110 if ( m_text ) m_text->SetToolTip(tip);
1111 if ( m_btn ) m_btn->SetToolTip(tip);
1112 }
1113 else
1114 {
1115 if ( m_text ) m_text->SetToolTip( (wxToolTip*) NULL );
1116 if ( m_btn ) m_btn->SetToolTip( (wxToolTip*) NULL );
1117 }
1118 }
1119 #endif // wxUSE_TOOLTIPS
1120
1121 // ----------------------------------------------------------------------------
1122 // painting
1123 // ----------------------------------------------------------------------------
1124
1125 #if (!defined(__WXMSW__)) || defined(__WXUNIVERSAL__)
1126 // prepare combo box background on area in a way typical on platform
1127 void wxComboCtrlBase::PrepareBackground( wxDC& dc, const wxRect& rect, int flags ) const
1128 {
1129 wxSize sz = GetClientSize();
1130 bool isEnabled;
1131 bool isFocused; // also selected
1132
1133 // For smaller size control (and for disabled background) use less spacing
1134 int focusSpacingX;
1135 int focusSpacingY;
1136
1137 if ( !(flags & wxCONTROL_ISSUBMENU) )
1138 {
1139 // Drawing control
1140 isEnabled = IsEnabled();
1141 isFocused = ShouldDrawFocus();
1142
1143 // Windows-style: for smaller size control (and for disabled background) use less spacing
1144 focusSpacingX = isEnabled ? 2 : 1;
1145 focusSpacingY = sz.y > (GetCharHeight()+2) && isEnabled ? 2 : 1;
1146 }
1147 else
1148 {
1149 // Drawing a list item
1150 isEnabled = true; // they are never disabled
1151 isFocused = flags & wxCONTROL_SELECTED ? true : false;
1152
1153 focusSpacingX = 0;
1154 focusSpacingY = 0;
1155 }
1156
1157 // Set the background sub-rectangle for selection, disabled etc
1158 wxRect selRect(rect);
1159 selRect.y += focusSpacingY;
1160 selRect.height -= (focusSpacingY*2);
1161
1162 int wcp = 0;
1163
1164 if ( !(flags & wxCONTROL_ISSUBMENU) )
1165 wcp += m_widthCustomPaint;
1166
1167 selRect.x += wcp + focusSpacingX;
1168 selRect.width -= wcp + (focusSpacingX*2);
1169
1170 wxColour bgCol;
1171
1172 if ( isEnabled )
1173 {
1174 // If popup is hidden and this control is focused,
1175 // then draw the focus-indicator (selbgcolor background etc.).
1176 if ( isFocused )
1177 {
1178 dc.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT) );
1179 bgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT);
1180 }
1181 else
1182 {
1183 dc.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT) );
1184 bgCol = GetBackgroundColour();
1185 }
1186 }
1187 else
1188 {
1189 dc.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT) );
1190 bgCol = GetBackgroundColour();
1191 }
1192
1193 dc.SetBrush( bgCol );
1194 dc.SetPen( bgCol );
1195 dc.DrawRectangle( selRect );
1196
1197 // Don't clip exactly to the selection rectangle so we can draw
1198 // to the non-selected area in front of it.
1199 wxRect clipRect(rect.x,rect.y,
1200 (selRect.x+selRect.width)-rect.x,rect.height);
1201 dc.SetClippingRegion(clipRect);
1202 }
1203 #else
1204 // Save the library size a bit for platforms that re-implement this.
1205 void wxComboCtrlBase::PrepareBackground( wxDC&, const wxRect&, int ) const
1206 {
1207 }
1208 #endif
1209
1210 void wxComboCtrlBase::DrawButton( wxDC& dc, const wxRect& rect, bool paintBg )
1211 {
1212 int drawState = m_btnState;
1213
1214 #ifdef __WXGTK__
1215 if ( m_isPopupShown )
1216 drawState |= wxCONTROL_PRESSED;
1217 #endif
1218
1219 wxRect drawRect(rect.x+m_btnSpacingX,
1220 rect.y+((rect.height-m_btnSize.y)/2),
1221 m_btnSize.x,
1222 m_btnSize.y);
1223
1224 // Make sure area is not larger than the control
1225 if ( drawRect.y < rect.y )
1226 drawRect.y = rect.y;
1227 if ( drawRect.height > rect.height )
1228 drawRect.height = rect.height;
1229
1230 bool enabled = IsEnabled();
1231
1232 if ( !enabled )
1233 drawState |= wxCONTROL_DISABLED;
1234
1235 if ( !m_bmpNormal.Ok() )
1236 {
1237 // Need to clear button background even if m_btn is present
1238 if ( paintBg )
1239 {
1240 wxColour bgCol;
1241
1242 if ( m_iFlags & wxCC_IFLAG_BUTTON_OUTSIDE )
1243 bgCol = GetParent()->GetBackgroundColour();
1244 else
1245 bgCol = GetBackgroundColour();
1246
1247 dc.SetBrush(bgCol);
1248 dc.SetPen(bgCol);
1249 dc.DrawRectangle(rect);
1250 }
1251
1252 // Draw standard button
1253 wxRendererNative::Get().DrawComboBoxDropButton(this,
1254 dc,
1255 drawRect,
1256 drawState);
1257 }
1258 else
1259 {
1260 // Draw bitmap
1261
1262 wxBitmap* pBmp;
1263
1264 if ( !enabled )
1265 pBmp = &m_bmpDisabled;
1266 else if ( m_btnState & wxCONTROL_PRESSED )
1267 pBmp = &m_bmpPressed;
1268 else if ( m_btnState & wxCONTROL_CURRENT )
1269 pBmp = &m_bmpHover;
1270 else
1271 pBmp = &m_bmpNormal;
1272
1273 if ( m_blankButtonBg )
1274 {
1275 // If using blank button background, we need to clear its background
1276 // with button face colour instead of colour for rest of the control.
1277 if ( paintBg )
1278 {
1279 wxColour bgCol = GetParent()->GetBackgroundColour(); //wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE);
1280 //wxColour bgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
1281 dc.SetPen(bgCol);
1282 dc.SetBrush(bgCol);
1283 dc.DrawRectangle(rect);
1284 }
1285
1286 wxRendererNative::Get().DrawPushButton(this,
1287 dc,
1288 drawRect,
1289 drawState);
1290
1291 }
1292 else
1293
1294 {
1295 // Need to clear button background even if m_btn is present
1296 // (assume non-button background was cleared just before this call so brushes are good)
1297 if ( paintBg )
1298 dc.DrawRectangle(rect);
1299 }
1300
1301 // Draw bitmap centered in drawRect
1302 dc.DrawBitmap(*pBmp,
1303 drawRect.x + (drawRect.width-pBmp->GetWidth())/2,
1304 drawRect.y + (drawRect.height-pBmp->GetHeight())/2,
1305 true);
1306 }
1307 }
1308
1309 void wxComboCtrlBase::RecalcAndRefresh()
1310 {
1311 if ( IsCreated() )
1312 {
1313 wxSizeEvent evt(GetSize(),GetId());
1314 GetEventHandler()->ProcessEvent(evt);
1315 Refresh();
1316 }
1317 }
1318
1319 // ----------------------------------------------------------------------------
1320 // miscellaneous event handlers
1321 // ----------------------------------------------------------------------------
1322
1323 void wxComboCtrlBase::OnTextCtrlEvent(wxCommandEvent& event)
1324 {
1325 if ( event.GetEventType() == wxEVT_COMMAND_TEXT_UPDATED )
1326 {
1327 if ( m_ignoreEvtText > 0 )
1328 {
1329 m_ignoreEvtText--;
1330 return;
1331 }
1332 }
1333
1334 // Change event id, object and string before relaying it forward
1335 event.SetId(GetId());
1336 wxString s = event.GetString();
1337 event.SetEventObject(this);
1338 event.SetString(s);
1339 event.Skip();
1340 }
1341
1342 // call if cursor is on button area or mouse is captured for the button
1343 bool wxComboCtrlBase::HandleButtonMouseEvent( wxMouseEvent& event,
1344 int flags )
1345 {
1346 int type = event.GetEventType();
1347
1348 if ( type == wxEVT_MOTION )
1349 {
1350 if ( flags & wxCC_MF_ON_BUTTON )
1351 {
1352 if ( !(m_btnState & wxCONTROL_CURRENT) )
1353 {
1354 // Mouse hover begins
1355 m_btnState |= wxCONTROL_CURRENT;
1356 if ( HasCapture() ) // Retain pressed state.
1357 m_btnState |= wxCONTROL_PRESSED;
1358 Refresh();
1359 }
1360 }
1361 else if ( (m_btnState & wxCONTROL_CURRENT) )
1362 {
1363 // Mouse hover ends
1364 m_btnState &= ~(wxCONTROL_CURRENT|wxCONTROL_PRESSED);
1365 Refresh();
1366 }
1367 }
1368 else if ( type == wxEVT_LEFT_DOWN )
1369 {
1370 if ( flags & (wxCC_MF_ON_CLICK_AREA|wxCC_MF_ON_BUTTON) )
1371 {
1372 m_btnState |= wxCONTROL_PRESSED;
1373 Refresh();
1374
1375 if ( !(m_iFlags & wxCC_POPUP_ON_MOUSE_UP) )
1376 OnButtonClick();
1377 else
1378 // If showing popup now, do not capture mouse or there will be interference
1379 CaptureMouse();
1380 }
1381 }
1382 else if ( type == wxEVT_LEFT_UP )
1383 {
1384
1385 // Only accept event if mouse was left-press was previously accepted
1386 if ( HasCapture() )
1387 ReleaseMouse();
1388
1389 if ( m_btnState & wxCONTROL_PRESSED )
1390 {
1391 // If mouse was inside, fire the click event.
1392 if ( m_iFlags & wxCC_POPUP_ON_MOUSE_UP )
1393 {
1394 if ( flags & (wxCC_MF_ON_CLICK_AREA|wxCC_MF_ON_BUTTON) )
1395 OnButtonClick();
1396 }
1397
1398 m_btnState &= ~(wxCONTROL_PRESSED);
1399 Refresh();
1400 }
1401 }
1402 else if ( type == wxEVT_LEAVE_WINDOW )
1403 {
1404 if ( m_btnState & (wxCONTROL_CURRENT|wxCONTROL_PRESSED) )
1405 {
1406 m_btnState &= ~(wxCONTROL_CURRENT);
1407
1408 // Mouse hover ends
1409 if ( !m_isPopupShown )
1410 {
1411 m_btnState &= ~(wxCONTROL_PRESSED);
1412 Refresh();
1413 }
1414 }
1415 }
1416 else
1417 return false;
1418
1419 return true;
1420 }
1421
1422 // returns true if event was consumed or filtered
1423 bool wxComboCtrlBase::PreprocessMouseEvent( wxMouseEvent& event,
1424 int WXUNUSED(flags) )
1425 {
1426 wxLongLong t = ::wxGetLocalTimeMillis();
1427 int evtType = event.GetEventType();
1428
1429 #if USES_WXPOPUPWINDOW || USES_WXDIALOG
1430 if ( m_popupWinType != POPUPWIN_WXPOPUPTRANSIENTWINDOW )
1431 {
1432 if ( m_isPopupShown &&
1433 ( evtType == wxEVT_LEFT_DOWN || evtType == wxEVT_RIGHT_DOWN ) )
1434 {
1435 HidePopup();
1436 return true;
1437 }
1438 }
1439 #endif
1440
1441 // Filter out clicks on button immediately after popup dismiss (Windows like behaviour)
1442 if ( evtType == wxEVT_LEFT_DOWN && t < m_timeCanAcceptClick )
1443 {
1444 event.SetEventType(0);
1445 return true;
1446 }
1447
1448 return false;
1449 }
1450
1451 void wxComboCtrlBase::HandleNormalMouseEvent( wxMouseEvent& event )
1452 {
1453 int evtType = event.GetEventType();
1454
1455 if ( (evtType == wxEVT_LEFT_DOWN || evtType == wxEVT_LEFT_DCLICK) &&
1456 (m_windowStyle & wxCB_READONLY) )
1457 {
1458 if ( m_isPopupShown )
1459 {
1460 #if USES_WXPOPUPWINDOW
1461 // Click here always hides the popup.
1462 if ( m_popupWinType == POPUPWIN_WXPOPUPWINDOW )
1463 HidePopup();
1464 #endif
1465 }
1466 else
1467 {
1468 if ( !(m_windowStyle & wxCC_SPECIAL_DCLICK) )
1469 {
1470 // In read-only mode, clicking the text is the
1471 // same as clicking the button.
1472 OnButtonClick();
1473 }
1474 else if ( /*evtType == wxEVT_LEFT_UP || */evtType == wxEVT_LEFT_DCLICK )
1475 {
1476 //if ( m_popupInterface->CycleValue() )
1477 // Refresh();
1478 if ( m_popupInterface )
1479 m_popupInterface->OnComboDoubleClick();
1480 }
1481 }
1482 }
1483 else
1484 if ( m_isPopupShown )
1485 {
1486 // relay (some) mouse events to the popup
1487 if ( evtType == wxEVT_MOUSEWHEEL )
1488 m_popup->AddPendingEvent(event);
1489 }
1490 else if ( evtType )
1491 event.Skip();
1492 }
1493
1494 void wxComboCtrlBase::OnKeyEvent(wxKeyEvent& event)
1495 {
1496 if ( IsPopupShown() )
1497 {
1498 // pass it to the popped up control
1499 GetPopupControl()->GetControl()->AddPendingEvent(event);
1500 }
1501 else // no popup
1502 {
1503 int keycode = event.GetKeyCode();
1504
1505 if ( keycode == WXK_TAB )
1506 {
1507 wxNavigationKeyEvent evt;
1508 evt.SetFlags(wxNavigationKeyEvent::FromTab|
1509 (!event.ShiftDown() ? wxNavigationKeyEvent::IsForward
1510 : wxNavigationKeyEvent::IsBackward));
1511 evt.SetEventObject(this);
1512 GetParent()->GetEventHandler()->AddPendingEvent(evt);
1513 return;
1514 }
1515
1516 if ( IsKeyPopupToggle(event) )
1517 {
1518 OnButtonClick();
1519 return;
1520 }
1521
1522 int comboStyle = GetWindowStyle();
1523 wxComboPopup* popupInterface = GetPopupControl();
1524
1525 if ( !popupInterface )
1526 {
1527 event.Skip();
1528 return;
1529 }
1530
1531 if ( (comboStyle & wxCB_READONLY) ||
1532 (keycode != WXK_RIGHT && keycode != WXK_LEFT) )
1533 {
1534 popupInterface->OnComboKeyEvent(event);
1535 }
1536 else
1537 event.Skip();
1538 }
1539 }
1540
1541 void wxComboCtrlBase::OnFocusEvent( wxFocusEvent& event )
1542 {
1543 if ( event.GetEventType() == wxEVT_SET_FOCUS )
1544 {
1545 if ( m_text && m_text != ::wxWindow::FindFocus() )
1546 m_text->SetFocus();
1547 }
1548
1549 Refresh();
1550 }
1551
1552 void wxComboCtrlBase::OnSysColourChanged(wxSysColourChangedEvent& WXUNUSED(event))
1553 {
1554 OnThemeChange();
1555 // indentation may also have changed
1556 if ( !(m_iFlags & wxCC_IFLAG_INDENT_SET) )
1557 m_absIndent = GetNativeTextIndent();
1558 RecalcAndRefresh();
1559 }
1560
1561 // ----------------------------------------------------------------------------
1562 // popup handling
1563 // ----------------------------------------------------------------------------
1564
1565 // Create popup window and the child control
1566 void wxComboCtrlBase::CreatePopup()
1567 {
1568 wxComboPopup* popupInterface = m_popupInterface;
1569 wxWindow* popup;
1570
1571 if ( !m_winPopup )
1572 {
1573 #ifdef wxComboPopupWindowBase2
1574 if ( m_iFlags & wxCC_IFLAG_USE_ALT_POPUP )
1575 {
1576 #if !USES_WXDIALOG
1577 m_winPopup = new wxComboPopupWindowBase2( this, wxNO_BORDER );
1578 #else
1579 m_winPopup = new wxComboPopupWindowBase2( this, wxID_ANY, wxEmptyString,
1580 wxPoint(-21,-21), wxSize(20, 20),
1581 wxNO_BORDER );
1582 #endif
1583 m_popupWinType = SECONDARY_POPUP_TYPE;
1584 }
1585 else
1586 #endif
1587 {
1588 m_winPopup = new wxComboPopupWindow( this, wxNO_BORDER );
1589 m_popupWinType = PRIMARY_POPUP_TYPE;
1590 }
1591 m_popupWinEvtHandler = new wxComboPopupWindowEvtHandler(this);
1592 m_winPopup->PushEventHandler(m_popupWinEvtHandler);
1593 }
1594
1595 popupInterface->Create(m_winPopup);
1596 m_popup = popup = popupInterface->GetControl();
1597
1598 m_popupExtraHandler = new wxComboPopupExtraEventHandler(this);
1599 popup->PushEventHandler( m_popupExtraHandler );
1600
1601 // This may be helpful on some platforms
1602 // (eg. it bypasses a wxGTK popupwindow bug where
1603 // window is not initially hidden when it should be)
1604 m_winPopup->Hide();
1605
1606 popupInterface->m_iFlags |= wxCP_IFLAG_CREATED;
1607 }
1608
1609 // Destroy popup window and the child control
1610 void wxComboCtrlBase::DestroyPopup()
1611 {
1612 HidePopup();
1613
1614 if ( m_popup )
1615 m_popup->RemoveEventHandler(m_popupExtraHandler);
1616
1617 delete m_popupExtraHandler;
1618
1619 delete m_popupInterface;
1620
1621 if ( m_winPopup )
1622 {
1623 m_winPopup->RemoveEventHandler(m_popupWinEvtHandler);
1624 delete m_popupWinEvtHandler;
1625 m_popupWinEvtHandler = NULL;
1626 m_winPopup->Destroy();
1627 }
1628
1629 m_popupExtraHandler = (wxEvtHandler*) NULL;
1630 m_popupInterface = (wxComboPopup*) NULL;
1631 m_winPopup = (wxWindow*) NULL;
1632 m_popup = (wxWindow*) NULL;
1633 }
1634
1635 void wxComboCtrlBase::DoSetPopupControl(wxComboPopup* iface)
1636 {
1637 wxCHECK_RET( iface, wxT("no popup interface set for wxComboCtrl") );
1638
1639 DestroyPopup();
1640
1641 iface->InitBase(this);
1642 iface->Init();
1643
1644 m_popupInterface = iface;
1645
1646 if ( !iface->LazyCreate() )
1647 {
1648 CreatePopup();
1649 }
1650 else
1651 {
1652 m_popup = (wxWindow*) NULL;
1653 }
1654
1655 // This must be done after creation
1656 if ( m_valueString.length() )
1657 {
1658 iface->SetStringValue(m_valueString);
1659 //Refresh();
1660 }
1661 }
1662
1663 // Ensures there is atleast the default popup
1664 void wxComboCtrlBase::EnsurePopupControl()
1665 {
1666 if ( !m_popupInterface )
1667 SetPopupControl(NULL);
1668 }
1669
1670 void wxComboCtrlBase::OnButtonClick()
1671 {
1672 // Derived classes can override this method for totally custom
1673 // popup action
1674 ShowPopup();
1675 }
1676
1677 void wxComboCtrlBase::ShowPopup()
1678 {
1679 EnsurePopupControl();
1680 wxCHECK_RET( !IsPopupShown(), wxT("popup window already shown") );
1681
1682 SetFocus();
1683
1684 // Space above and below
1685 int screenHeight;
1686 wxPoint scrPos;
1687 int spaceAbove;
1688 int spaceBelow;
1689 int maxHeightPopup;
1690 wxSize ctrlSz = GetSize();
1691
1692 screenHeight = wxSystemSettings::GetMetric( wxSYS_SCREEN_Y );
1693 scrPos = GetParent()->ClientToScreen(GetPosition());
1694
1695 spaceAbove = scrPos.y;
1696 spaceBelow = screenHeight - spaceAbove - ctrlSz.y;
1697
1698 maxHeightPopup = spaceBelow;
1699 if ( spaceAbove > spaceBelow )
1700 maxHeightPopup = spaceAbove;
1701
1702 // Width
1703 int widthPopup = ctrlSz.x + m_extLeft + m_extRight;
1704
1705 if ( widthPopup < m_widthMinPopup )
1706 widthPopup = m_widthMinPopup;
1707
1708 wxWindow* winPopup = m_winPopup;
1709 wxWindow* popup;
1710
1711 // Need to disable tab traversal of parent
1712 //
1713 // NB: This is to fix a bug in wxMSW. In theory it could also be fixed
1714 // by, for instance, adding check to window.cpp:wxWindowMSW::MSWProcessMessage
1715 // that if transient popup is open, then tab traversal is to be ignored.
1716 // However, I think this code would still be needed for cases where
1717 // transient popup doesn't work yet (wxWinCE?).
1718 wxWindow* parent = GetParent();
1719 int parentFlags = parent->GetWindowStyle();
1720 if ( parentFlags & wxTAB_TRAVERSAL )
1721 {
1722 parent->SetWindowStyle( parentFlags & ~(wxTAB_TRAVERSAL) );
1723 m_iFlags |= wxCC_IFLAG_PARENT_TAB_TRAVERSAL;
1724 }
1725
1726 if ( !winPopup )
1727 {
1728 CreatePopup();
1729 winPopup = m_winPopup;
1730 popup = m_popup;
1731 }
1732 else
1733 {
1734 popup = m_popup;
1735 }
1736
1737 wxASSERT( !m_popup || m_popup == popup ); // Consistency check.
1738
1739 wxSize adjustedSize = m_popupInterface->GetAdjustedSize(widthPopup,
1740 m_heightPopup<=0?DEFAULT_POPUP_HEIGHT:m_heightPopup,
1741 maxHeightPopup);
1742
1743 popup->SetSize(adjustedSize);
1744 popup->Move(0,0);
1745 m_popupInterface->OnPopup();
1746
1747 //
1748 // Reposition and resize popup window
1749 //
1750
1751 wxSize szp = popup->GetSize();
1752
1753 int popupX;
1754 int popupY = scrPos.y + ctrlSz.y;
1755
1756 // Default anchor is wxLEFT
1757 int anchorSide = m_anchorSide;
1758 if ( !anchorSide )
1759 anchorSide = wxLEFT;
1760
1761 int rightX = scrPos.x + ctrlSz.x + m_extRight - szp.x;
1762 int leftX = scrPos.x - m_extLeft;
1763 int screenWidth = wxSystemSettings::GetMetric( wxSYS_SCREEN_X );
1764
1765 // If there is not enough horizontal space, anchor on the other side.
1766 // If there is no space even then, place the popup at x 0.
1767 if ( anchorSide == wxRIGHT )
1768 {
1769 if ( rightX < 0 )
1770 {
1771 if ( (leftX+szp.x) < screenWidth )
1772 anchorSide = wxLEFT;
1773 else
1774 anchorSide = 0;
1775 }
1776 }
1777 else
1778 {
1779 if ( (leftX+szp.x) >= screenWidth )
1780 {
1781 if ( rightX >= 0 )
1782 anchorSide = wxRIGHT;
1783 else
1784 anchorSide = 0;
1785 }
1786 }
1787
1788 // Select x coordinate according to the anchor side
1789 if ( anchorSide == wxRIGHT )
1790 popupX = rightX;
1791 else if ( anchorSide == wxLEFT )
1792 popupX = leftX;
1793 else
1794 popupX = 0;
1795
1796 if ( spaceBelow < szp.y )
1797 {
1798 popupY = scrPos.y - szp.y;
1799 }
1800
1801 // Move to position
1802 //wxLogDebug(wxT("popup scheduled position1: %i,%i"),ptp.x,ptp.y);
1803 //wxLogDebug(wxT("popup position1: %i,%i"),winPopup->GetPosition().x,winPopup->GetPosition().y);
1804
1805 // Some platforms (GTK) may need these two to be separate
1806 winPopup->SetSize( szp.x, szp.y );
1807 winPopup->Move( popupX, popupY );
1808
1809 //wxLogDebug(wxT("popup position2: %i,%i"),winPopup->GetPosition().x,winPopup->GetPosition().y);
1810
1811 m_popup = popup;
1812
1813 // Set string selection (must be this way instead of SetStringSelection)
1814 if ( m_text )
1815 {
1816 if ( !(m_iFlags & wxCC_NO_TEXT_AUTO_SELECT) )
1817 m_text->SelectAll();
1818
1819 m_popupInterface->SetStringValue( m_text->GetValue() );
1820 }
1821 else
1822 {
1823 // This is neede since focus/selection indication may change when popup is shown
1824 Refresh();
1825 }
1826
1827 // This must be after SetStringValue
1828 m_isPopupShown = true;
1829
1830 // Show it
1831 #if USES_WXPOPUPTRANSIENTWINDOW
1832 if ( m_popupWinType == POPUPWIN_WXPOPUPTRANSIENTWINDOW )
1833 ((wxPopupTransientWindow*)winPopup)->Popup(popup);
1834 else
1835 #endif
1836 winPopup->Show();
1837
1838 #if INSTALL_TOPLEV_HANDLER
1839 // Put top level window event handler into place
1840 if ( m_popupWinType == POPUPWIN_WXPOPUPWINDOW )
1841 {
1842 if ( !m_toplevEvtHandler )
1843 m_toplevEvtHandler = new wxComboFrameEventHandler(this);
1844
1845 wxWindow* toplev = ::wxGetTopLevelParent( this );
1846 wxASSERT( toplev );
1847 ((wxComboFrameEventHandler*)m_toplevEvtHandler)->OnPopup();
1848 toplev->PushEventHandler( m_toplevEvtHandler );
1849 }
1850 #endif
1851
1852 }
1853
1854 void wxComboCtrlBase::OnPopupDismiss()
1855 {
1856 // Just in case, avoid double dismiss
1857 if ( !m_isPopupShown )
1858 return;
1859
1860 // NB: Focus setting is really funny, atleast on wxMSW. First of all,
1861 // we need to have SetFocus at the end. Otherwise wxTextCtrl may
1862 // freeze until focus goes somewhere else. Second, wxTreeCtrl as
1863 // popup, when dismissing, "steals" focus back to itself unless
1864 // SetFocus is called also here, exactly before m_isPopupShown
1865 // is set to false. Which is truly weird since SetFocus is just
1866 // wxWindowMSW method and does not call event handler or anything like
1867 // that (ie. does not care about m_isPopupShown).
1868
1869 SetFocus();
1870
1871 // This should preferably be set before focus.
1872 m_isPopupShown = false;
1873
1874 // Inform popup control itself
1875 m_popupInterface->OnDismiss();
1876
1877 if ( m_popupExtraHandler )
1878 ((wxComboPopupExtraEventHandler*)m_popupExtraHandler)->OnPopupDismiss();
1879
1880 #if INSTALL_TOPLEV_HANDLER
1881 // Remove top level window event handler
1882 if ( m_toplevEvtHandler )
1883 {
1884 wxWindow* toplev = ::wxGetTopLevelParent( this );
1885 if ( toplev )
1886 toplev->RemoveEventHandler( m_toplevEvtHandler );
1887 }
1888 #endif
1889
1890 m_timeCanAcceptClick = ::wxGetLocalTimeMillis() + 150;
1891
1892 // If cursor not on dropdown button, then clear its state
1893 // (technically not required by all ports, but do it for all just in case)
1894 if ( !m_btnArea.Contains(ScreenToClient(::wxGetMousePosition())) )
1895 m_btnState = 0;
1896
1897 // Return parent's tab traversal flag.
1898 // See ShowPopup for notes.
1899 if ( m_iFlags & wxCC_IFLAG_PARENT_TAB_TRAVERSAL )
1900 {
1901 wxWindow* parent = GetParent();
1902 parent->SetWindowStyle( parent->GetWindowStyle() | wxTAB_TRAVERSAL );
1903 m_iFlags &= ~(wxCC_IFLAG_PARENT_TAB_TRAVERSAL);
1904 }
1905
1906 // refresh control (necessary even if m_text)
1907 Refresh();
1908
1909 SetFocus();
1910 }
1911
1912 void wxComboCtrlBase::HidePopup()
1913 {
1914 // Should be able to call this without popup interface
1915 //wxCHECK_RET( m_popupInterface, _T("no popup interface") );
1916 if ( !m_isPopupShown )
1917 return;
1918
1919 // transfer value and show it in textctrl, if any
1920 SetValue( m_popupInterface->GetStringValue() );
1921
1922 #if USES_WXPOPUPTRANSIENTWINDOW
1923 if ( m_popupWinType == POPUPWIN_WXPOPUPTRANSIENTWINDOW )
1924 ((wxPopupTransientWindow*)m_winPopup)->Dismiss();
1925 else
1926 #endif
1927 m_winPopup->Hide();
1928
1929 OnPopupDismiss();
1930 }
1931
1932 // ----------------------------------------------------------------------------
1933 // customization methods
1934 // ----------------------------------------------------------------------------
1935
1936 void wxComboCtrlBase::SetButtonPosition( int width, int height,
1937 int side, int spacingX )
1938 {
1939 m_btnWid = width;
1940 m_btnHei = height;
1941 m_btnSide = side;
1942 m_btnSpacingX = spacingX;
1943
1944 RecalcAndRefresh();
1945 }
1946
1947 wxSize wxComboCtrlBase::GetButtonSize()
1948 {
1949 if ( m_btnSize.x > 0 )
1950 return m_btnSize;
1951
1952 wxSize retSize(m_btnWid,m_btnHei);
1953
1954 // Need to call CalculateAreas now if button size is
1955 // is not explicitly specified.
1956 if ( retSize.x <= 0 || retSize.y <= 0)
1957 {
1958 OnResize();
1959
1960 retSize = m_btnSize;
1961 }
1962
1963 return retSize;
1964 }
1965
1966 void wxComboCtrlBase::SetButtonBitmaps( const wxBitmap& bmpNormal,
1967 bool blankButtonBg,
1968 const wxBitmap& bmpPressed,
1969 const wxBitmap& bmpHover,
1970 const wxBitmap& bmpDisabled )
1971 {
1972 m_bmpNormal = bmpNormal;
1973 m_blankButtonBg = blankButtonBg;
1974
1975 if ( bmpPressed.Ok() )
1976 m_bmpPressed = bmpPressed;
1977 else
1978 m_bmpPressed = bmpNormal;
1979
1980 if ( bmpHover.Ok() )
1981 m_bmpHover = bmpHover;
1982 else
1983 m_bmpHover = bmpNormal;
1984
1985 if ( bmpDisabled.Ok() )
1986 m_bmpDisabled = bmpDisabled;
1987 else
1988 m_bmpDisabled = bmpNormal;
1989
1990 RecalcAndRefresh();
1991 }
1992
1993 void wxComboCtrlBase::SetCustomPaintWidth( int width )
1994 {
1995 if ( m_text )
1996 {
1997 // move textctrl accordingly
1998 wxRect r = m_text->GetRect();
1999 int inc = width - m_widthCustomPaint;
2000 r.x += inc;
2001 r.width -= inc;
2002 m_text->SetSize( r );
2003 }
2004
2005 m_widthCustomPaint = width;
2006
2007 RecalcAndRefresh();
2008 }
2009
2010 void wxComboCtrlBase::SetTextIndent( int indent )
2011 {
2012 if ( indent < 0 )
2013 {
2014 m_absIndent = GetNativeTextIndent();
2015 m_iFlags &= ~(wxCC_IFLAG_INDENT_SET);
2016 }
2017 else
2018 {
2019 m_absIndent = indent;
2020 m_iFlags |= wxCC_IFLAG_INDENT_SET;
2021 }
2022
2023 RecalcAndRefresh();
2024 }
2025
2026 wxCoord wxComboCtrlBase::GetNativeTextIndent() const
2027 {
2028 return DEFAULT_TEXT_INDENT;
2029 }
2030
2031 // ----------------------------------------------------------------------------
2032 // methods forwarded to wxTextCtrl
2033 // ----------------------------------------------------------------------------
2034
2035 wxString wxComboCtrlBase::GetValue() const
2036 {
2037 if ( m_text )
2038 return m_text->GetValue();
2039 return m_valueString;
2040 }
2041
2042 void wxComboCtrlBase::SetValueWithEvent(const wxString& value, bool withEvent)
2043 {
2044 if ( m_text )
2045 {
2046 if ( !withEvent )
2047 m_ignoreEvtText++;
2048
2049 m_text->SetValue(value);
2050 if ( !(m_iFlags & wxCC_NO_TEXT_AUTO_SELECT) )
2051 m_text->SelectAll();
2052 }
2053
2054 m_valueString = value;
2055
2056 Refresh();
2057
2058 // Since wxComboPopup may want to paint the combo as well, we need
2059 // to set the string value here (as well as sometimes in ShowPopup).
2060 if ( m_valueString != value && m_popupInterface )
2061 {
2062 m_popupInterface->SetStringValue(value);
2063 }
2064 }
2065
2066 void wxComboCtrlBase::SetValue(const wxString& value)
2067 {
2068 SetValueWithEvent(value, false);
2069 }
2070
2071 // In this SetValue variant wxComboPopup::SetStringValue is not called
2072 void wxComboCtrlBase::SetText(const wxString& value)
2073 {
2074 // Unlike in SetValue(), this must be called here or
2075 // the behaviour will no be consistent in readonlys.
2076 EnsurePopupControl();
2077
2078 m_valueString = value;
2079
2080 if ( m_text )
2081 {
2082 m_ignoreEvtText++;
2083 m_text->SetValue( value );
2084 }
2085
2086 Refresh();
2087 }
2088
2089 void wxComboCtrlBase::Copy()
2090 {
2091 if ( m_text )
2092 m_text->Copy();
2093 }
2094
2095 void wxComboCtrlBase::Cut()
2096 {
2097 if ( m_text )
2098 m_text->Cut();
2099 }
2100
2101 void wxComboCtrlBase::Paste()
2102 {
2103 if ( m_text )
2104 m_text->Paste();
2105 }
2106
2107 void wxComboCtrlBase::SetInsertionPoint(long pos)
2108 {
2109 if ( m_text )
2110 m_text->SetInsertionPoint(pos);
2111 }
2112
2113 void wxComboCtrlBase::SetInsertionPointEnd()
2114 {
2115 if ( m_text )
2116 m_text->SetInsertionPointEnd();
2117 }
2118
2119 long wxComboCtrlBase::GetInsertionPoint() const
2120 {
2121 if ( m_text )
2122 return m_text->GetInsertionPoint();
2123
2124 return 0;
2125 }
2126
2127 long wxComboCtrlBase::GetLastPosition() const
2128 {
2129 if ( m_text )
2130 return m_text->GetLastPosition();
2131
2132 return 0;
2133 }
2134
2135 void wxComboCtrlBase::Replace(long from, long to, const wxString& value)
2136 {
2137 if ( m_text )
2138 m_text->Replace(from, to, value);
2139 }
2140
2141 void wxComboCtrlBase::Remove(long from, long to)
2142 {
2143 if ( m_text )
2144 m_text->Remove(from, to);
2145 }
2146
2147 void wxComboCtrlBase::SetSelection(long from, long to)
2148 {
2149 if ( m_text )
2150 m_text->SetSelection(from, to);
2151 }
2152
2153 void wxComboCtrlBase::Undo()
2154 {
2155 if ( m_text )
2156 m_text->Undo();
2157 }
2158
2159 #endif // wxUSE_COMBOCTRL