1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/combo.cpp
3 // Purpose: wxMSW wxComboCtrl
4 // Author: Jaakko Salli
6 // Created: Apr-30-2006
8 // Copyright: (c) 2005 Jaakko Salli
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
12 // ============================================================================
14 // ============================================================================
16 // ----------------------------------------------------------------------------
18 // ----------------------------------------------------------------------------
20 #include "wx/wxprec.h"
30 #include "wx/combobox.h"
31 #include "wx/dcclient.h"
32 #include "wx/settings.h"
33 #include "wx/dialog.h"
34 #include "wx/stopwatch.h"
37 #include "wx/dcbuffer.h"
40 #include "wx/msw/registry.h"
41 #include "wx/msw/uxtheme.h"
43 // Change to #if 1 to include tmschema.h for easier testing of theme
49 //----------------------------------
53 #define ETS_SELECTED 3
54 #define ETS_DISABLED 4
56 #define ETS_READONLY 6
58 #define TMT_FILLCOLOR 3802
59 #define TMT_TEXTCOLOR 3803
60 #define TMT_BORDERCOLOR 3801
61 #define TMT_EDGEFILLCOLOR 3808
62 #define TMT_BGTYPE 4001
64 #define BT_IMAGEFILE 0
65 #define BT_BORDERFILL 1
67 #define CP_DROPDOWNBUTTON 1
68 #define CP_BACKGROUND 2 // This and above are Vista and later only
69 #define CP_TRANSPARENTBACKGROUND 3
72 #define CP_DROPDOWNBUTTONRIGHT 6
73 #define CP_DROPDOWNBUTTONLEFT 7
74 #define CP_CUEBANNER 8
78 #define CBXS_PRESSED 3
79 #define CBXS_DISABLED 4
81 #define CBXSR_NORMAL 1
83 #define CBXSR_PRESSED 3
84 #define CBXSR_DISABLED 4
86 #define CBXSL_NORMAL 1
88 #define CBXSL_PRESSED 3
89 #define CBXSL_DISABLED 4
91 #define CBTBS_NORMAL 1
93 #define CBTBS_DISABLED 3
94 #define CBTBS_FOCUSED 4
99 #define CBB_DISABLED 4
101 #define CBRO_NORMAL 1
103 #define CBRO_PRESSED 3
104 #define CBRO_DISABLED 4
106 #define CBCB_NORMAL 1
108 #define CBCB_PRESSED 3
109 #define CBCB_DISABLED 4
114 #define NATIVE_TEXT_INDENT_XP 4
115 #define NATIVE_TEXT_INDENT_CLASSIC 2
117 #define TEXTCTRLXADJUST_XP 1
118 #define TEXTCTRLYADJUST_XP 3
119 #define TEXTCTRLXADJUST_CLASSIC 1
120 #define TEXTCTRLYADJUST_CLASSIC 3
122 #define COMBOBOX_ANIMATION_RESOLUTION 10
124 #define COMBOBOX_ANIMATION_DURATION 200 // In milliseconds
126 #define wxMSW_DESKTOP_USERPREFERENCESMASK_COMBOBOXANIM (1<<2)
129 // ============================================================================
131 // ============================================================================
134 BEGIN_EVENT_TABLE(wxComboCtrl
, wxComboCtrlBase
)
135 EVT_PAINT(wxComboCtrl::OnPaintEvent
)
136 EVT_MOUSE_EVENTS(wxComboCtrl::OnMouseEvent
)
137 #if wxUSE_COMBOCTRL_POPUP_ANIMATION
138 EVT_TIMER(wxID_ANY
, wxComboCtrl::OnTimerEvent
)
143 IMPLEMENT_DYNAMIC_CLASS(wxComboCtrl
, wxComboCtrlBase
)
145 void wxComboCtrl::Init()
149 bool wxComboCtrl::Create(wxWindow
*parent
,
151 const wxString
& value
,
155 const wxValidator
& validator
,
156 const wxString
& name
)
160 long border
= style
& wxBORDER_MASK
;
162 wxUxThemeEngine
* theme
= wxUxThemeEngine::GetIfActive();
168 // For XP, have 1-width custom border, for older version use sunken
169 border
= wxBORDER_NONE
;
170 m_widthCustomBorder
= 1;
173 border
= wxBORDER_SUNKEN
;
175 style
= (style
& ~(wxBORDER_MASK
)) | border
;
178 // create main window
179 if ( !wxComboCtrlBase::Create(parent
,
184 style
| wxFULL_REPAINT_ON_RESIZE
,
191 if ( ::wxGetWinVersion() >= wxWinVersion_Vista
)
192 m_iFlags
|= wxCC_BUTTON_STAYS_DOWN
|wxCC_BUTTON_COVERS_BORDER
;
195 if ( style
& wxCC_STD_BUTTON
)
196 m_iFlags
|= wxCC_POPUP_ON_MOUSE_UP
;
198 // Create textctrl, if necessary
199 CreateTextCtrl( wxNO_BORDER
, validator
);
201 // Add keyboard input handlers for main control and textctrl
202 InstallInputHandlers();
204 // Prepare background for double-buffering
205 SetBackgroundStyle( wxBG_STYLE_CUSTOM
);
207 // SetInitialSize should be called last
208 SetInitialSize(size
);
213 wxComboCtrl::~wxComboCtrl()
217 void wxComboCtrl::OnThemeChange()
219 wxUxThemeEngine
* theme
= wxUxThemeEngine::GetIfActive();
222 wxUxThemeHandle
hTheme(this, L
"EDIT");
225 HRESULT hr
= theme
->GetThemeColor(hTheme
,EP_EDITTEXT
,ETS_NORMAL
,TMT_FILLCOLOR
,&col
);
227 wxLogApiError(_T("GetThemeColor(EDIT, EP_EDITTEXT, ETS_NORMAL, TMT_FILLCOLOR)"), hr
);
229 SetBackgroundColour(wxRGBToColour(col
));
231 hr
= theme
->GetThemeColor(hTheme
,EP_EDITTEXT
,ETS_NORMAL
,TMT_TEXTCOLOR
,&col
);
233 wxLogApiError(_T("GetThemeColor(EDIT, EP_EDITTEXT, ETS_NORMAL, TMT_TEXTCOLOR)"), hr
);
235 SetForegroundColour(wxRGBToColour(col
));
239 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW
));
240 SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT
));
244 void wxComboCtrl::OnResize()
247 // Recalculates button and textctrl areas
252 if ( wxUxThemeEngine::GetIfActive() )
254 textCtrlXAdjust
= TEXTCTRLXADJUST_XP
;
255 textCtrlYAdjust
= TEXTCTRLYADJUST_XP
;
259 textCtrlXAdjust
= TEXTCTRLXADJUST_CLASSIC
;
260 textCtrlYAdjust
= TEXTCTRLYADJUST_CLASSIC
;
263 // Technically Classic Windows style combo has more narrow button,
264 // but the native renderer doesn't paint it well like that.
266 CalculateAreas(btnWidth
);
268 // Position textctrl using standard routine
269 PositionTextCtrl(textCtrlXAdjust
,textCtrlYAdjust
);
272 // Draws non-XP GUI dotted line around the focus area
273 static void wxMSWDrawFocusRect( wxDC
& dc
, const wxRect
& rect
)
275 #if !defined(__WXWINCE__)
278 mswRect.left = rect.x;
279 mswRect.top = rect.y;
280 mswRect.right = rect.x + rect.width;
281 mswRect.bottom = rect.y + rect.height;
282 HDC hdc = (HDC) dc.GetHDC();
283 SetMapMode(hdc,MM_TEXT); // Just in case...
284 DrawFocusRect(hdc,&mswRect);
286 // FIXME: Use DrawFocusRect code above (currently it draws solid line
287 // for caption focus but works ok for other stuff).
288 // Also, this code below may not work in future wx versions, since
289 // it employs wxCAP_BUTT hack to have line of width 1.
290 dc
.SetLogicalFunction(wxINVERT
);
292 wxPen
pen(*wxBLACK
,1,wxDOT
);
293 pen
.SetCap(wxCAP_BUTT
);
295 dc
.SetBrush(*wxTRANSPARENT_BRUSH
);
297 dc
.DrawRectangle(rect
);
299 dc
.SetLogicalFunction(wxCOPY
);
301 dc
.SetLogicalFunction(wxINVERT
);
303 dc
.SetPen(wxPen(*wxBLACK
,1,wxDOT
));
304 dc
.SetBrush(*wxTRANSPARENT_BRUSH
);
306 dc
.DrawRectangle(rect
);
308 dc
.SetLogicalFunction(wxCOPY
);
312 // draw focus background on area in a way typical on platform
314 wxComboCtrl::PrepareBackground( wxDC
& dc
, const wxRect
& rect
, int flags
) const
316 wxUxThemeHandle
hTheme(this, L
"COMBOBOX");
318 wxSize sz
= GetClientSize();
320 bool doDrawFocusRect
; // also selected
322 // For smaller size control (and for disabled background) use less spacing
326 if ( !(flags
& wxCONTROL_ISSUBMENU
) )
329 isEnabled
= IsEnabled();
330 doDrawFocusRect
= ShouldDrawFocus();
332 // Windows-style: for smaller size control (and for disabled background) use less spacing
336 focusSpacingX
= isEnabled
? 2 : 1;
337 focusSpacingY
= sz
.y
> (GetCharHeight()+2) && isEnabled
? 2 : 1;
356 // Drawing a list item
357 isEnabled
= true; // they are never disabled
358 doDrawFocusRect
= flags
& wxCONTROL_SELECTED
? true : false;
364 // Set the background sub-rectangle for selection, disabled etc
365 wxRect
selRect(rect
);
366 selRect
.y
+= focusSpacingY
;
367 selRect
.height
-= (focusSpacingY
*2);
371 if ( !(flags
& wxCONTROL_ISSUBMENU
) )
372 wcp
+= m_widthCustomPaint
;
374 selRect
.x
+= wcp
+ focusSpacingX
;
375 selRect
.width
-= wcp
+ (focusSpacingX
*2);
377 //wxUxThemeEngine* theme = (wxUxThemeEngine*) NULL;
379 // theme = wxUxThemeEngine::GetIfActive();
382 bool doDrawDottedEdge
= false;
383 bool doDrawSelRect
= true;
385 // TODO: doDrawDottedEdge = true when focus has arrived to control via tab.
386 // (and other cases which are not that apparent).
390 // If popup is hidden and this control is focused,
391 // then draw the focus-indicator (selbgcolor background etc.).
392 if ( doDrawFocusRect
)
394 // NB: We can't really use XP visual styles to get TMT_TEXTCOLOR since
395 // it is not properly defined for combo boxes. Instead, they expect
396 // you to use DrawThemeText.
398 // Here is, however, sample code how to get theme colours:
401 // theme->GetThemeColor(hTheme,EP_EDITTEXT,ETS_NORMAL,TMT_TEXTCOLOR,&cref);
402 // dc.SetTextForeground( wxRGBToColour(cref) );
403 if ( (m_iFlags
& wxCC_FULL_BUTTON
) && !(flags
& wxCONTROL_ISSUBMENU
) )
405 // Vista style read-only combo
406 doDrawSelRect
= false;
407 doDrawDottedEdge
= true;
411 dc
.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT
) );
412 bgCol
= wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT
);
417 dc
.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT
) );
418 bgCol
= GetBackgroundColour();
419 doDrawSelRect
= false;
424 dc
.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT
) );
425 bgCol
= wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE
);
432 dc
.DrawRectangle(selRect
);
435 if ( doDrawDottedEdge
)
436 wxMSWDrawFocusRect(dc
, selRect
);
438 // Don't clip exactly to the selection rectangle so we can draw
439 // to the non-selected area in front of it.
440 wxRect
clipRect(rect
.x
,rect
.y
,
441 (selRect
.x
+selRect
.width
)-rect
.x
-1,rect
.height
);
442 dc
.SetClippingRegion(clipRect
);
445 void wxComboCtrl::OnPaintEvent( wxPaintEvent
& WXUNUSED(event
) )
447 // TODO: Convert drawing in this function to Windows API Code
449 wxSize sz
= GetClientSize();
450 wxAutoBufferedPaintDC
dc(this);
452 const wxRect
& rectButton
= m_btnArea
;
453 wxRect rectTextField
= m_tcArea
;
454 const bool isEnabled
= IsEnabled();
455 wxColour bgCol
= GetBackgroundColour();
457 HDC hDc
= GetHdcOf(dc
);
458 HWND hWnd
= GetHwndOf(this);
460 wxUxThemeEngine
* theme
= NULL
;
461 wxUxThemeHandle
hTheme(this, L
"COMBOBOX");
464 theme
= wxUxThemeEngine::GetIfActive();
466 wxRect
borderRect(0,0,sz
.x
,sz
.y
);
468 if ( m_iFlags
& wxCC_IFLAG_BUTTON_OUTSIDE
)
470 borderRect
= m_tcArea
;
471 borderRect
.Inflate(1);
474 int drawButFlags
= 0;
478 const bool useVistaComboBox
= ::wxGetWinVersion() >= wxWinVersion_Vista
;
481 wxCopyRectToRECT(borderRect
, rFull
);
484 wxCopyRectToRECT(rectButton
, rButton
);
487 wxCopyRectToRECT(borderRect
, rBorder
);
489 bool isNonStdButton
= (m_iFlags
& wxCC_IFLAG_BUTTON_OUTSIDE
) ||
490 (m_iFlags
& wxCC_IFLAG_HAS_NONSTANDARD_BUTTON
);
493 // Get some states for themed drawing
498 butState
= CBXS_DISABLED
;
500 // Vista will display the drop-button as depressed always
501 // when the popup window is visilbe
502 else if ( (m_btnState
& wxCONTROL_PRESSED
) ||
503 (useVistaComboBox
&& !IsPopupWindowState(Hidden
)) )
505 butState
= CBXS_PRESSED
;
507 else if ( m_btnState
& wxCONTROL_CURRENT
)
513 butState
= CBXS_NORMAL
;
516 int comboBoxPart
= 0; // For XP, use the 'default' part
517 RECT
* rUseForBg
= &rBorder
;
519 bool drawFullButton
= false;
520 int bgState
= butState
;
521 const bool isFocused
= (FindFocus() == GetMainWindowOfCompositeControl()) ? true : false;
523 if ( useVistaComboBox
)
525 // FIXME: Either SetBackgroundColour or GetBackgroundColour
526 // doesn't work under Vista, so here's a temporary
528 bgCol
= wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW
);
530 // Draw the entire control as a single button?
531 if ( !isNonStdButton
)
533 if ( HasFlag(wxCB_READONLY
) )
534 drawFullButton
= true;
537 if ( drawFullButton
)
539 comboBoxPart
= CP_READONLY
;
542 // It should be safe enough to update this flag here.
543 m_iFlags
|= wxCC_FULL_BUTTON
;
547 comboBoxPart
= CP_BORDER
;
548 m_iFlags
&= ~wxCC_FULL_BUTTON
;
551 bgState
= CBB_FOCUSED
;
553 bgState
= CBB_NORMAL
;
558 // Draw parent's background, if necessary
559 RECT
* rUseForTb
= NULL
;
561 if ( theme
->IsThemeBackgroundPartiallyTransparent( hTheme
, comboBoxPart
, bgState
) )
563 else if ( m_iFlags
& wxCC_IFLAG_BUTTON_OUTSIDE
)
564 rUseForTb
= &rButton
;
567 theme
->DrawThemeParentBackground( hWnd
, hDc
, rUseForTb
);
570 // Draw the control background (including the border)
571 if ( m_widthCustomBorder
> 0 )
573 theme
->DrawThemeBackground( hTheme
, hDc
, comboBoxPart
, bgState
, rUseForBg
, NULL
);
577 // No border. We can't use theme, since it cannot be relied on
578 // to deliver borderless drawing, even with DrawThemeBackgroundEx.
581 dc
.DrawRectangle(borderRect
);
585 // Draw the drop-button
586 if ( !isNonStdButton
)
588 drawButFlags
= Button_BitmapOnly
;
590 int butPart
= CP_DROPDOWNBUTTON
;
592 if ( useVistaComboBox
)
594 if ( drawFullButton
)
596 // We need to alter the button style slightly before
597 // drawing the actual button (but it was good above
598 // when background etc was done).
599 if ( butState
== CBXS_HOT
|| butState
== CBXS_PRESSED
)
600 butState
= CBXS_NORMAL
;
603 if ( m_btnSide
== wxRIGHT
)
604 butPart
= CP_DROPDOWNBUTTONRIGHT
;
606 butPart
= CP_DROPDOWNBUTTONLEFT
;
609 theme
->DrawThemeBackground( hTheme
, hDc
, butPart
, butState
, &rButton
, NULL
);
611 else if ( useVistaComboBox
&&
612 (m_iFlags
& wxCC_IFLAG_BUTTON_OUTSIDE
) )
614 // We'll do this, because DrawThemeParentBackground
615 // doesn't seem to be reliable on Vista.
616 drawButFlags
|= Button_PaintBackground
;
621 // Windows 2000 and earlier
622 drawButFlags
= Button_PaintBackground
;
626 dc
.DrawRectangle(borderRect
);
629 // Button rendering (may only do the bitmap on button, depending on the flags)
630 DrawButton( dc
, rectButton
, drawButFlags
);
632 // Paint required portion of the custom image on the control
633 if ( (!m_text
|| m_widthCustomPaint
) )
635 wxASSERT( m_widthCustomPaint
>= 0 );
637 // this is intentionally here to allow drawed rectangle's
638 // right edge to be hidden
640 rectTextField
.width
= m_widthCustomPaint
;
642 dc
.SetFont( GetFont() );
644 dc
.SetClippingRegion(rectTextField
);
645 if ( m_popupInterface
)
646 m_popupInterface
->PaintComboControl(dc
,rectTextField
);
648 wxComboPopup::DefaultPaintComboControl(this,dc
,rectTextField
);
652 void wxComboCtrl::OnMouseEvent( wxMouseEvent
& event
)
655 bool isOnButtonArea
= m_btnArea
.Contains(mx
,event
.m_y
);
656 int handlerFlags
= isOnButtonArea
? wxCC_MF_ON_BUTTON
: 0;
658 if ( PreprocessMouseEvent(event
,isOnButtonArea
) )
661 if ( (m_windowStyle
& (wxCC_SPECIAL_DCLICK
|wxCB_READONLY
)) == wxCB_READONLY
)
663 // if no textctrl and no special double-click, then the entire control acts
665 handlerFlags
|= wxCC_MF_ON_BUTTON
;
666 if ( HandleButtonMouseEvent(event
,handlerFlags
) )
671 if ( isOnButtonArea
|| HasCapture() ||
672 (m_widthCustomPaint
&& mx
< (m_tcArea
.x
+m_widthCustomPaint
)) )
674 handlerFlags
|= wxCC_MF_ON_CLICK_AREA
;
676 if ( HandleButtonMouseEvent(event
,handlerFlags
) )
679 else if ( m_btnState
)
681 // otherwise need to clear the hover status
683 RefreshRect(m_btnArea
);
688 // This will handle left_down and left_dclick events outside button in a Windows-like manner.
689 // See header file for further information on this method.
690 HandleNormalMouseEvent(event
);
694 #if wxUSE_COMBOCTRL_POPUP_ANIMATION
695 static wxUint32
GetUserPreferencesMask()
697 static wxUint32 userPreferencesMask
= 0;
698 static bool valueSet
= false;
701 return userPreferencesMask
;
703 wxRegKey
* pKey
= NULL
;
704 wxRegKey
key1(wxRegKey::HKCU
, wxT("Software\\Policies\\Microsoft\\Control Panel"));
705 wxRegKey
key2(wxRegKey::HKCU
, wxT("Software\\Policies\\Microsoft\\Windows\\Control Panel"));
706 wxRegKey
key3(wxRegKey::HKCU
, wxT("Control Panel\\Desktop"));
710 else if ( key2
.Exists() )
712 else if ( key3
.Exists() )
715 if ( pKey
&& pKey
->Open(wxRegKey::Read
) )
718 if ( pKey
->HasValue(wxT("UserPreferencesMask")) &&
719 pKey
->QueryValue(wxT("UserPreferencesMask"), buf
) )
721 if ( buf
.GetDataLen() >= 4 )
723 wxUint32
* p
= (wxUint32
*) buf
.GetData();
724 userPreferencesMask
= *p
;
731 return userPreferencesMask
;
735 #if wxUSE_COMBOCTRL_POPUP_ANIMATION
736 void wxComboCtrl::OnTimerEvent( wxTimerEvent
& WXUNUSED(event
) )
738 bool stopTimer
= false;
740 wxWindow
* popup
= GetPopupControl()->GetControl();
742 // Popup was hidden before it was fully shown?
743 if ( IsPopupWindowState(Hidden
) )
749 wxLongLong t
= ::wxGetLocalTimeMillis();
750 const wxRect
& rect
= m_animRect
;
751 wxWindow
* win
= GetPopupWindow();
753 int pos
= (int) (t
-m_animStart
).GetLo();
754 if ( pos
< COMBOBOX_ANIMATION_DURATION
)
756 int height
= rect
.height
;
757 //int h0 = rect.height;
758 int h
= (((pos
*256)/COMBOBOX_ANIMATION_DURATION
)*height
)/256;
759 int y
= (height
- h
);
763 if ( m_animFlags
& ShowAbove
)
765 win
->SetSize( rect
.x
, rect
.y
+ height
- h
, rect
.width
, h
);
769 popup
->Move( 0, -y
);
770 win
->SetSize( rect
.x
, rect
.y
, rect
.width
, h
);
783 DoShowPopup( m_animRect
, m_animFlags
);
788 #if wxUSE_COMBOCTRL_POPUP_ANIMATION
789 bool wxComboCtrl::AnimateShow( const wxRect
& rect
, int flags
)
791 if ( GetUserPreferencesMask() & wxMSW_DESKTOP_USERPREFERENCESMASK_COMBOBOXANIM
)
793 m_animStart
= ::wxGetLocalTimeMillis();
797 wxWindow
* win
= GetPopupWindow();
798 win
->SetSize( rect
.x
, rect
.y
, rect
.width
, 0 );
801 m_animTimer
.SetOwner( this, wxID_ANY
);
802 m_animTimer
.Start( COMBOBOX_ANIMATION_RESOLUTION
, wxTIMER_CONTINUOUS
);
804 OnTimerEvent(*((wxTimerEvent
*)NULL
)); // Event is never used, so we can give NULL
813 wxCoord
wxComboCtrl::GetNativeTextIndent() const
815 if ( wxUxThemeEngine::GetIfActive() )
816 return NATIVE_TEXT_INDENT_XP
;
817 return NATIVE_TEXT_INDENT_CLASSIC
;
820 bool wxComboCtrl::IsKeyPopupToggle(const wxKeyEvent
& event
) const
822 const bool isPopupShown
= IsPopupShown();
824 switch ( event
.GetKeyCode() )
827 // F4 toggles the popup in the native comboboxes, so emulate them
828 if ( !event
.AltDown() )
839 // On XP or with writable combo in Classic, arrows don't open the
840 // popup but Alt-arrow does
841 if ( event
.AltDown() ||
843 HasFlag(wxCB_READONLY
) &&
844 !wxUxThemeEngine::GetIfActive()
855 #endif // wxUSE_COMBOCTRL