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
48 //----------------------------------
52 #define ETS_SELECTED 3
53 #define ETS_DISABLED 4
55 #define ETS_READONLY 6
57 #define TMT_FILLCOLOR 3802
58 #define TMT_TEXTCOLOR 3803
59 #define TMT_BORDERCOLOR 3801
60 #define TMT_EDGEFILLCOLOR 3808
61 //----------------------------------
65 #define NATIVE_TEXT_INDENT_XP 4
66 #define NATIVE_TEXT_INDENT_CLASSIC 2
68 #define TEXTCTRLXADJUST_XP 1
69 #define TEXTCTRLYADJUST_XP 3
70 #define TEXTCTRLXADJUST_CLASSIC 1
71 #define TEXTCTRLYADJUST_CLASSIC 2
73 #define COMBOBOX_ANIMATION_RESOLUTION 10
75 #define COMBOBOX_ANIMATION_DURATION 200 // In milliseconds
77 #define wxMSW_DESKTOP_USERPREFERENCESMASK_COMBOBOXANIM (1<<2)
80 // ============================================================================
82 // ============================================================================
85 BEGIN_EVENT_TABLE(wxComboCtrl
, wxComboCtrlBase
)
86 EVT_PAINT(wxComboCtrl::OnPaintEvent
)
87 EVT_MOUSE_EVENTS(wxComboCtrl::OnMouseEvent
)
88 #if wxUSE_COMBOCTRL_POPUP_ANIMATION
89 EVT_TIMER(wxID_ANY
, wxComboCtrl::OnTimerEvent
)
94 IMPLEMENT_DYNAMIC_CLASS(wxComboCtrl
, wxComboCtrlBase
)
96 void wxComboCtrl::Init()
100 bool wxComboCtrl::Create(wxWindow
*parent
,
102 const wxString
& value
,
106 const wxValidator
& validator
,
107 const wxString
& name
)
111 long border
= style
& wxBORDER_MASK
;
113 wxUxThemeEngine
* theme
= wxUxThemeEngine::GetIfActive();
117 // For XP, have 1-width custom border, for older version use sunken
120 border
= wxBORDER_NONE
;
121 m_widthCustomBorder
= 1;
124 border
= wxBORDER_SUNKEN
;
126 style
= (style
& ~(wxBORDER_MASK
)) | border
;
129 // create main window
130 if ( !wxComboCtrlBase::Create(parent
,
135 style
| wxFULL_REPAINT_ON_RESIZE
,
140 if ( style
& wxCC_STD_BUTTON
)
141 m_iFlags
|= wxCC_POPUP_ON_MOUSE_UP
;
143 // Create textctrl, if necessary
144 CreateTextCtrl( wxNO_BORDER
, validator
);
146 // Add keyboard input handlers for main control and textctrl
147 InstallInputHandlers();
149 // Prepare background for double-buffering
150 SetBackgroundStyle( wxBG_STYLE_CUSTOM
);
152 // SetInitialSize should be called last
153 SetInitialSize(size
);
158 wxComboCtrl::~wxComboCtrl()
162 void wxComboCtrl::OnThemeChange()
164 wxUxThemeEngine
* theme
= wxUxThemeEngine::GetIfActive();
167 wxUxThemeHandle
hTheme(this, L
"COMBOBOX");
170 theme
->GetThemeColor(hTheme
,EP_EDITTEXT
,ETS_NORMAL
,TMT_FILLCOLOR
,&col
);
171 SetBackgroundColour(wxRGBToColour(col
));
172 theme
->GetThemeColor(hTheme
,EP_EDITTEXT
,ETS_NORMAL
,TMT_TEXTCOLOR
,&col
);
173 SetForegroundColour(wxRGBToColour(col
));
177 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW
));
178 SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT
));
182 void wxComboCtrl::OnResize()
185 // Recalculates button and textctrl areas
190 if ( wxUxThemeEngine::GetIfActive() )
192 textCtrlXAdjust
= TEXTCTRLXADJUST_XP
;
193 textCtrlYAdjust
= TEXTCTRLYADJUST_XP
;
197 textCtrlXAdjust
= TEXTCTRLXADJUST_CLASSIC
;
198 textCtrlYAdjust
= TEXTCTRLYADJUST_CLASSIC
;
201 // Technically Classic Windows style combo has more narrow button,
202 // but the native renderer doesn't paint it well like that.
204 CalculateAreas(btnWidth
);
206 // Position textctrl using standard routine
207 PositionTextCtrl(textCtrlXAdjust
,textCtrlYAdjust
);
210 // Draws non-XP GUI dotted line around the focus area
211 static void wxMSWDrawFocusRect( wxDC
& dc
, const wxRect
& rect
)
213 #if !defined(__WXWINCE__)
216 mswRect.left = rect.x;
217 mswRect.top = rect.y;
218 mswRect.right = rect.x + rect.width;
219 mswRect.bottom = rect.y + rect.height;
220 HDC hdc = (HDC) dc.GetHDC();
221 SetMapMode(hdc,MM_TEXT); // Just in case...
222 DrawFocusRect(hdc,&mswRect);
224 // FIXME: Use DrawFocusRect code above (currently it draws solid line
225 // for caption focus but works ok for other stuff).
226 // Also, this code below may not work in future wx versions, since
227 // it employs wxCAP_BUTT hack to have line of width 1.
228 dc
.SetLogicalFunction(wxINVERT
);
230 wxPen
pen(*wxBLACK
,1,wxDOT
);
231 pen
.SetCap(wxCAP_BUTT
);
233 dc
.SetBrush(*wxTRANSPARENT_BRUSH
);
235 dc
.DrawRectangle(rect
);
237 dc
.SetLogicalFunction(wxCOPY
);
239 dc
.SetLogicalFunction(wxINVERT
);
241 dc
.SetPen(wxPen(*wxBLACK
,1,wxDOT
));
242 dc
.SetBrush(*wxTRANSPARENT_BRUSH
);
244 dc
.DrawRectangle(rect
);
246 dc
.SetLogicalFunction(wxCOPY
);
250 // draw focus background on area in a way typical on platform
252 wxComboCtrl::PrepareBackground( wxDC
& dc
, const wxRect
& rect
, int flags
) const
254 wxUxThemeHandle
hTheme(this, L
"COMBOBOX");
257 wxSize sz
= GetClientSize();
259 bool isFocused
; // also selected
261 // For smaller size control (and for disabled background) use less spacing
265 if ( !(flags
& wxCONTROL_ISSUBMENU
) )
268 isEnabled
= IsEnabled();
269 isFocused
= ShouldDrawFocus();
271 // Windows-style: for smaller size control (and for disabled background) use less spacing
275 focusSpacingX
= isEnabled
? 2 : 1;
276 focusSpacingY
= sz
.y
> (GetCharHeight()+2) && isEnabled
? 2 : 1;
295 // Drawing a list item
296 isEnabled
= true; // they are never disabled
297 isFocused
= flags
& wxCONTROL_SELECTED
? true : false;
303 // Set the background sub-rectangle for selection, disabled etc
304 wxRect
selRect(rect
);
305 selRect
.y
+= focusSpacingY
;
306 selRect
.height
-= (focusSpacingY
*2);
310 if ( !(flags
& wxCONTROL_ISSUBMENU
) )
311 wcp
+= m_widthCustomPaint
;
313 selRect
.x
+= wcp
+ focusSpacingX
;
314 selRect
.width
-= wcp
+ (focusSpacingX
*2);
316 //wxUxThemeEngine* theme = (wxUxThemeEngine*) NULL;
318 // theme = wxUxThemeEngine::GetIfActive();
321 bool drawDottedEdge
= false;
325 // If popup is hidden and this control is focused,
326 // then draw the focus-indicator (selbgcolor background etc.).
330 // TODO: Proper theme color getting (JMS: I don't know which parts/colors to use,
331 // those below don't work)
334 theme
->GetThemeColor(hTheme
,EP_EDITTEXT
,ETS_SELECTED
,TMT_TEXTCOLOR
,&cref
);
335 dc
.SetTextForeground( wxRGBToColour(cref
) );
336 theme
->GetThemeColor(hTheme
,EP_EDITTEXT
,ETS_SELECTED
,TMT_FILLCOLOR
,&cref
);
337 bgCol
= wxRGBToColour(cref
);
342 dc
.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT
) );
343 bgCol
= wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT
);
344 if ( m_windowStyle
& wxCB_READONLY
)
345 drawDottedEdge
= true;
352 theme->GetThemeColor(hTheme,EP_EDITTEXT,ETS_NORMAL,TMT_TEXTCOLOR,&cref);
353 dc.SetTextForeground( wxRGBToColour(cref) );
354 theme->GetThemeColor(hTheme,EP_EDITTEXT,ETS_NORMAL,TMT_FILLCOLOR,&cref);
355 bgCol = wxRGBToColour(cref);
359 dc
.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT
) );
360 bgCol
= GetBackgroundColour();
368 theme->GetThemeColor(hTheme,EP_EDITTEXT,ETS_DISABLED,TMT_TEXTCOLOR,&cref);
369 dc.SetTextForeground( wxRGBToColour(cref) );
370 theme->GetThemeColor(hTheme,EP_EDITTEXT,ETS_DISABLED,TMT_EDGEFILLCOLOR,&cref);
371 bgCol = wxRGBToColour(cref);
375 dc
.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT
) );
376 bgCol
= wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE
);
382 dc
.DrawRectangle(selRect
);
383 if ( drawDottedEdge
)
384 wxMSWDrawFocusRect(dc
,selRect
);
386 // Don't clip exactly to the selection rectangle so we can draw
387 // to the non-selected area in front of it.
388 wxRect
clipRect(rect
.x
,rect
.y
,
389 (selRect
.x
+selRect
.width
)-rect
.x
-1,rect
.height
);
390 dc
.SetClippingRegion(clipRect
);
393 void wxComboCtrl::OnPaintEvent( wxPaintEvent
& WXUNUSED(event
) )
395 // TODO: Convert drawing in this function to Windows API Code
397 wxSize sz
= GetClientSize();
398 wxAutoBufferedPaintDC
dc(this);
400 const wxRect
& rectb
= m_btnArea
;
401 wxRect rect
= m_tcArea
;
402 bool isEnabled
= IsEnabled();
403 wxColour bgCol
= GetBackgroundColour();
406 wxUxThemeEngine
* theme
= NULL
;
407 wxUxThemeHandle
hTheme(this, L
"COMBOBOX");
410 // area around both controls
411 wxRect
rect2(0,0,sz
.x
,sz
.y
);
412 if ( m_iFlags
& wxCC_IFLAG_BUTTON_OUTSIDE
)
418 // Use theme to draw border on XP
421 theme
= wxUxThemeEngine::GetIfActive();
424 // Select correct border colour
426 etsState
= ETS_DISABLED
;
428 etsState
= ETS_NORMAL
;
430 if ( m_widthCustomBorder
)
432 theme
->GetThemeColor(hTheme
,EP_EDITTEXT
,etsState
,TMT_BORDERCOLOR
,&cref
);
435 dc
.SetPen( wxRGBToColour(cref
) );
437 dc
.SetBrush( *wxTRANSPARENT_BRUSH
);
438 dc
.DrawRectangle(rect2
);
441 theme
->GetThemeColor(hTheme
,EP_EDITTEXT
,etsState
,TMT_TEXTCOLOR
,&cref
);
442 fgCol
= wxRGBToColour(cref
);
446 // draw regular background
447 fgCol
= GetForegroundColour();
450 rect2
.Deflate(m_widthCustomBorder
);
455 // clear main background
456 dc
.DrawRectangle(rect
);
458 // Button background with theme?
459 bool drawButBg
= true;
460 if ( hTheme
&& m_blankButtonBg
)
463 wxCopyRectToRECT(rectb
, r
);
465 // Draw parent background if needed (since button looks like its out of
466 // the combo, this is preferred).
467 theme
->DrawThemeParentBackground(GetHwndOf(this),
474 // Standard button rendering
475 DrawButton(dc
,rectb
,drawButBg
);
477 // paint required portion on the control
478 if ( (!m_text
|| m_widthCustomPaint
) )
480 wxASSERT( m_widthCustomPaint
>= 0 );
482 // this is intentionally here to allow drawed rectangle's
483 // right edge to be hidden
485 rect
.width
= m_widthCustomPaint
;
487 dc
.SetFont( GetFont() );
489 dc
.SetClippingRegion(rect
);
490 if ( m_popupInterface
)
491 m_popupInterface
->PaintComboControl(dc
,rect
);
493 wxComboPopup::DefaultPaintComboControl(this,dc
,rect
);
497 void wxComboCtrl::OnMouseEvent( wxMouseEvent
& event
)
500 bool isOnButtonArea
= m_btnArea
.Contains(mx
,event
.m_y
);
501 int handlerFlags
= isOnButtonArea
? wxCC_MF_ON_BUTTON
: 0;
503 if ( PreprocessMouseEvent(event
,isOnButtonArea
) )
506 if ( (m_windowStyle
& (wxCC_SPECIAL_DCLICK
|wxCB_READONLY
)) == wxCB_READONLY
)
508 // if no textctrl and no special double-click, then the entire control acts
510 handlerFlags
|= wxCC_MF_ON_BUTTON
;
511 if ( HandleButtonMouseEvent(event
,handlerFlags
) )
516 if ( isOnButtonArea
|| HasCapture() ||
517 (m_widthCustomPaint
&& mx
< (m_tcArea
.x
+m_widthCustomPaint
)) )
519 handlerFlags
|= wxCC_MF_ON_CLICK_AREA
;
521 if ( HandleButtonMouseEvent(event
,handlerFlags
) )
524 else if ( m_btnState
)
526 // otherwise need to clear the hover status
528 RefreshRect(m_btnArea
);
533 // This will handle left_down and left_dclick events outside button in a Windows-like manner.
534 // See header file for further information on this method.
535 HandleNormalMouseEvent(event
);
539 #if wxUSE_COMBOCTRL_POPUP_ANIMATION
540 static wxUint32
GetUserPreferencesMask()
542 static wxUint32 userPreferencesMask
= 0;
543 static bool valueSet
= false;
546 return userPreferencesMask
;
548 wxRegKey
* pKey
= NULL
;
549 wxRegKey
key1(wxRegKey::HKCU
, wxT("Software\\Policies\\Microsoft\\Control Panel"));
550 wxRegKey
key2(wxRegKey::HKCU
, wxT("Software\\Policies\\Microsoft\\Windows\\Control Panel"));
551 wxRegKey
key3(wxRegKey::HKCU
, wxT("Control Panel\\Desktop"));
555 else if ( key2
.Exists() )
557 else if ( key3
.Exists() )
560 if ( pKey
&& pKey
->Open(wxRegKey::Read
) )
563 if ( pKey
->HasValue(wxT("UserPreferencesMask")) &&
564 pKey
->QueryValue(wxT("UserPreferencesMask"), buf
) )
566 if ( buf
.GetDataLen() >= 4 )
568 wxUint32
* p
= (wxUint32
*) buf
.GetData();
569 userPreferencesMask
= *p
;
576 return userPreferencesMask
;
580 #if wxUSE_COMBOCTRL_POPUP_ANIMATION
581 void wxComboCtrl::OnTimerEvent( wxTimerEvent
& WXUNUSED(event
) )
583 bool stopTimer
= false;
585 wxWindow
* popup
= GetPopupControl()->GetControl();
587 // Popup was hidden before it was fully shown?
588 if ( IsPopupWindowState(Hidden
) )
594 wxLongLong t
= ::wxGetLocalTimeMillis();
595 const wxRect
& rect
= m_animRect
;
596 wxWindow
* win
= GetPopupWindow();
598 int pos
= (int) (t
-m_animStart
).GetLo();
599 if ( pos
< COMBOBOX_ANIMATION_DURATION
)
601 int height
= rect
.height
;
602 //int h0 = rect.height;
603 int h
= (((pos
*256)/COMBOBOX_ANIMATION_DURATION
)*height
)/256;
604 int y
= (height
- h
);
608 if ( m_animFlags
& ShowAbove
)
610 win
->SetSize( rect
.x
, rect
.y
+ height
- h
, rect
.width
, h
);
614 popup
->Move( 0, -y
);
615 win
->SetSize( rect
.x
, rect
.y
, rect
.width
, h
);
628 DoShowPopup( m_animRect
, m_animFlags
);
633 #if wxUSE_COMBOCTRL_POPUP_ANIMATION
634 bool wxComboCtrl::AnimateShow( const wxRect
& rect
, int flags
)
636 if ( GetUserPreferencesMask() & wxMSW_DESKTOP_USERPREFERENCESMASK_COMBOBOXANIM
)
638 m_animStart
= ::wxGetLocalTimeMillis();
642 wxWindow
* win
= GetPopupWindow();
643 win
->SetSize( rect
.x
, rect
.y
, rect
.width
, 0 );
646 m_animTimer
.SetOwner( this, wxID_ANY
);
647 m_animTimer
.Start( COMBOBOX_ANIMATION_RESOLUTION
, wxTIMER_CONTINUOUS
);
649 OnTimerEvent(*((wxTimerEvent
*)NULL
)); // Event is never used, so we can give NULL
658 wxCoord
wxComboCtrl::GetNativeTextIndent() const
660 if ( wxUxThemeEngine::GetIfActive() )
661 return NATIVE_TEXT_INDENT_XP
;
662 return NATIVE_TEXT_INDENT_CLASSIC
;
665 bool wxComboCtrl::IsKeyPopupToggle(const wxKeyEvent
& event
) const
667 const bool isPopupShown
= IsPopupShown();
669 switch ( event
.GetKeyCode() )
672 // F4 toggles the popup in the native comboboxes, so emulate them
673 if ( !event
.AltDown() )
684 // On XP or with writable combo in Classic, arrows don't open the
685 // popup but Alt-arrow does
686 if ( event
.AltDown() ||
688 HasFlag(wxCB_READONLY
) &&
689 !wxUxThemeEngine::GetIfActive()
700 #endif // wxUSE_COMBOCTRL