1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/combo.cpp
3 // Purpose: wxMSW wxComboCtrl
4 // Author: Jaakko Salli
6 // Created: Apr-30-2006
7 // Copyright: (c) 2005 Jaakko Salli
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
11 // ============================================================================
13 // ============================================================================
15 // ----------------------------------------------------------------------------
17 // ----------------------------------------------------------------------------
19 #include "wx/wxprec.h"
29 #include "wx/combobox.h"
30 #include "wx/dcclient.h"
31 #include "wx/settings.h"
32 #include "wx/dialog.h"
33 #include "wx/stopwatch.h"
36 #include "wx/dcbuffer.h"
39 #include "wx/msw/registry.h"
41 #include "wx/msw/uxtheme.h"
43 #include "wx/msw/dc.h"
45 // Change to #if 1 to include tmschema.h for easier testing of theme
51 //----------------------------------
55 #define ETS_SELECTED 3
56 #define ETS_DISABLED 4
58 #define ETS_READONLY 6
60 #define TMT_FILLCOLOR 3802
61 #define TMT_TEXTCOLOR 3803
62 #define TMT_BORDERCOLOR 3801
63 #define TMT_EDGEFILLCOLOR 3808
64 #define TMT_BGTYPE 4001
66 #define BT_IMAGEFILE 0
67 #define BT_BORDERFILL 1
69 #define CP_DROPDOWNBUTTON 1
70 #define CP_BACKGROUND 2 // This and above are Vista and later only
71 #define CP_TRANSPARENTBACKGROUND 3
74 #define CP_DROPDOWNBUTTONRIGHT 6
75 #define CP_DROPDOWNBUTTONLEFT 7
76 #define CP_CUEBANNER 8
80 #define CBXS_PRESSED 3
81 #define CBXS_DISABLED 4
83 #define CBXSR_NORMAL 1
85 #define CBXSR_PRESSED 3
86 #define CBXSR_DISABLED 4
88 #define CBXSL_NORMAL 1
90 #define CBXSL_PRESSED 3
91 #define CBXSL_DISABLED 4
93 #define CBTBS_NORMAL 1
95 #define CBTBS_DISABLED 3
96 #define CBTBS_FOCUSED 4
100 #define CBB_FOCUSED 3
101 #define CBB_DISABLED 4
103 #define CBRO_NORMAL 1
105 #define CBRO_PRESSED 3
106 #define CBRO_DISABLED 4
108 #define CBCB_NORMAL 1
110 #define CBCB_PRESSED 3
111 #define CBCB_DISABLED 4
116 #define NATIVE_TEXT_INDENT_XP 4
117 #define NATIVE_TEXT_INDENT_CLASSIC 2
119 #define COMBOBOX_ANIMATION_RESOLUTION 10
121 #define COMBOBOX_ANIMATION_DURATION 200 // In milliseconds
123 #define wxMSW_DESKTOP_USERPREFERENCESMASK_COMBOBOXANIM (1<<2)
126 // ============================================================================
128 // ============================================================================
131 BEGIN_EVENT_TABLE(wxComboCtrl
, wxComboCtrlBase
)
132 EVT_PAINT(wxComboCtrl::OnPaintEvent
)
133 EVT_MOUSE_EVENTS(wxComboCtrl::OnMouseEvent
)
134 #if wxUSE_COMBOCTRL_POPUP_ANIMATION
135 EVT_TIMER(wxID_ANY
, wxComboCtrl::OnTimerEvent
)
140 IMPLEMENT_DYNAMIC_CLASS(wxComboCtrl
, wxComboCtrlBase
)
142 void wxComboCtrl::Init()
146 bool wxComboCtrl::Create(wxWindow
*parent
,
148 const wxString
& value
,
152 const wxValidator
& validator
,
153 const wxString
& name
)
157 long border
= style
& wxBORDER_MASK
;
160 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;
174 border
= wxBORDER_SUNKEN
;
176 style
= (style
& ~(wxBORDER_MASK
)) | border
;
179 // create main window
180 if ( !wxComboCtrlBase::Create(parent
,
185 style
| wxFULL_REPAINT_ON_RESIZE
,
193 if ( ::wxGetWinVersion() >= wxWinVersion_Vista
)
194 m_iFlags
|= wxCC_BUTTON_STAYS_DOWN
|wxCC_BUTTON_COVERS_BORDER
;
198 if ( style
& wxCC_STD_BUTTON
)
199 m_iFlags
|= wxCC_POPUP_ON_MOUSE_UP
;
201 // Prepare background for double-buffering or better background theme
202 // support, whichever is possible.
203 SetDoubleBuffered(true);
204 if ( !IsDoubleBuffered() )
205 SetBackgroundStyle( wxBG_STYLE_PAINT
);
207 // Create textctrl, if necessary
208 CreateTextCtrl( wxNO_BORDER
);
210 // Add keyboard input handlers for main control and textctrl
211 InstallInputHandlers();
213 // SetInitialSize should be called last
214 SetInitialSize(size
);
219 wxComboCtrl::~wxComboCtrl()
223 void wxComboCtrl::OnResize()
226 // Recalculates button and textctrl areas
228 // Technically Classic Windows style combo has more narrow button,
229 // but the native renderer doesn't paint it well like that.
231 CalculateAreas(btnWidth
);
233 // Position textctrl using standard routine
237 // Draws non-XP GUI dotted line around the focus area
238 static void wxMSWDrawFocusRect( wxDC
& dc
, const wxRect
& rect
)
240 #if !defined(__WXWINCE__)
243 mswRect.left = rect.x;
244 mswRect.top = rect.y;
245 mswRect.right = rect.x + rect.width;
246 mswRect.bottom = rect.y + rect.height;
247 HDC hdc = (HDC) dc.GetHDC();
248 SetMapMode(hdc,MM_TEXT); // Just in case...
249 DrawFocusRect(hdc,&mswRect);
251 // FIXME: Use DrawFocusRect code above (currently it draws solid line
252 // for caption focus but works ok for other stuff).
253 // Also, this code below may not work in future wx versions, since
254 // it employs wxCAP_BUTT hack to have line of width 1.
255 dc
.SetLogicalFunction(wxINVERT
);
257 wxPen
pen(*wxBLACK
, 1, wxPENSTYLE_DOT
);
258 pen
.SetCap(wxCAP_BUTT
);
260 dc
.SetBrush(*wxTRANSPARENT_BRUSH
);
262 dc
.DrawRectangle(rect
);
264 dc
.SetLogicalFunction(wxCOPY
);
266 dc
.SetLogicalFunction(wxINVERT
);
268 dc
.SetPen(wxPen(*wxBLACK
,1,wxDOT
));
269 dc
.SetBrush(*wxTRANSPARENT_BRUSH
);
271 dc
.DrawRectangle(rect
);
273 dc
.SetLogicalFunction(wxCOPY
);
277 // draw focus background on area in a way typical on platform
279 wxComboCtrl::PrepareBackground( wxDC
& dc
, const wxRect
& rect
, int flags
) const
282 wxUxThemeHandle
hTheme(this, L
"COMBOBOX");
285 wxSize sz
= GetClientSize();
287 bool doDrawFocusRect
; // also selected
289 // For smaller size control (and for disabled background) use less spacing
293 if ( !(flags
& wxCONTROL_ISSUBMENU
) )
296 isEnabled
= IsThisEnabled();
297 doDrawFocusRect
= ShouldDrawFocus();
300 // Windows-style: for smaller size control (and for disabled background) use less spacing
304 focusSpacingX
= isEnabled
? 2 : 1;
305 focusSpacingY
= sz
.y
> (GetCharHeight()+2) && isEnabled
? 2 : 1;
325 // Drawing a list item
326 isEnabled
= true; // they are never disabled
327 doDrawFocusRect
= flags
& wxCONTROL_SELECTED
? true : false;
333 // Set the background sub-rectangle for selection, disabled etc
334 wxRect
selRect(rect
);
335 selRect
.y
+= focusSpacingY
;
336 selRect
.height
-= (focusSpacingY
*2);
340 if ( !(flags
& wxCONTROL_ISSUBMENU
) )
341 wcp
+= m_widthCustomPaint
;
343 selRect
.x
+= wcp
+ focusSpacingX
;
344 selRect
.width
-= wcp
+ (focusSpacingX
*2);
346 //wxUxThemeEngine* theme = NULL;
348 // theme = wxUxThemeEngine::GetIfActive();
352 bool doDrawDottedEdge
= false;
353 bool doDrawSelRect
= true;
355 // TODO: doDrawDottedEdge = true when focus has arrived to control via tab.
356 // (and other cases which are not that apparent).
360 // If popup is hidden and this control is focused,
361 // then draw the focus-indicator (selbgcolor background etc.).
362 if ( doDrawFocusRect
)
364 // NB: We can't really use XP visual styles to get TMT_TEXTCOLOR since
365 // it is not properly defined for combo boxes. Instead, they expect
366 // you to use DrawThemeText.
368 // Here is, however, sample code how to get theme colours:
371 // theme->GetThemeColor(hTheme,EP_EDITTEXT,ETS_NORMAL,TMT_TEXTCOLOR,&cref);
372 // dc.SetTextForeground( wxRGBToColour(cref) );
373 if ( (m_iFlags
& wxCC_FULL_BUTTON
) && !(flags
& wxCONTROL_ISSUBMENU
) )
375 // Vista style read-only combo
376 fgCol
= GetForegroundColour();
377 bgCol
= GetBackgroundColour();
378 doDrawSelRect
= false;
379 doDrawDottedEdge
= true;
383 fgCol
= wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT
);
384 bgCol
= wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT
);
389 fgCol
= GetForegroundColour();
390 bgCol
= GetBackgroundColour();
391 doDrawSelRect
= false;
396 fgCol
= wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT
);
397 bgCol
= wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE
);
400 dc
.SetTextForeground(fgCol
);
405 dc
.DrawRectangle(selRect
);
408 if ( doDrawDottedEdge
)
409 wxMSWDrawFocusRect(dc
, selRect
);
411 // Don't clip exactly to the selection rectangle so we can draw
412 // to the non-selected area in front of it.
413 wxRect
clipRect(rect
.x
,rect
.y
,
414 (selRect
.x
+selRect
.width
)-rect
.x
-1,rect
.height
);
415 dc
.SetClippingRegion(clipRect
);
418 void wxComboCtrl::OnPaintEvent( wxPaintEvent
& WXUNUSED(event
) )
420 // TODO: Convert drawing in this function to Windows API Code
422 wxSize sz
= GetClientSize();
423 wxDC
* dcPtr
= wxAutoBufferedPaintDCFactory(this);
426 const wxRect
& rectButton
= m_btnArea
;
427 wxRect rectTextField
= m_tcArea
;
429 // FIXME: Either SetBackgroundColour or GetBackgroundColour
430 // doesn't work under Vista, so here's a temporary
432 // In the theme-less rendering code below, this fixes incorrect
433 // background on read-only comboboxes (they are gray, but should be
435 wxColour bgCol
= wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW
);
438 const bool isEnabled
= IsThisEnabled();
440 wxMSWDCImpl
*impl
= (wxMSWDCImpl
*) dc
.GetImpl();
441 HDC hDc
= GetHdcOf(*impl
);
442 HWND hWnd
= GetHwndOf(this);
444 wxUxThemeEngine
* theme
= NULL
;
445 wxUxThemeHandle
hTheme(this, L
"COMBOBOX");
448 theme
= wxUxThemeEngine::GetIfActive();
449 #endif // wxUSE_UXTHEME
451 wxRect
borderRect(0,0,sz
.x
,sz
.y
);
453 if ( m_iFlags
& wxCC_IFLAG_BUTTON_OUTSIDE
)
455 borderRect
= m_tcArea
;
456 borderRect
.Inflate(1);
459 int drawButFlags
= 0;
464 const bool useVistaComboBox
= ::wxGetWinVersion() >= wxWinVersion_Vista
;
467 wxCopyRectToRECT(borderRect
, rFull
);
470 wxCopyRectToRECT(rectButton
, rButton
);
473 wxCopyRectToRECT(borderRect
, rBorder
);
475 bool isNonStdButton
= (m_iFlags
& wxCC_IFLAG_BUTTON_OUTSIDE
) ||
476 (m_iFlags
& wxCC_IFLAG_HAS_NONSTANDARD_BUTTON
);
479 // Get some states for themed drawing
484 butState
= CBXS_DISABLED
;
486 // Vista will display the drop-button as depressed always
487 // when the popup window is visilbe
488 else if ( (m_btnState
& wxCONTROL_PRESSED
) ||
489 (useVistaComboBox
&& !IsPopupWindowState(Hidden
)) )
491 butState
= CBXS_PRESSED
;
493 else if ( m_btnState
& wxCONTROL_CURRENT
)
499 butState
= CBXS_NORMAL
;
502 int comboBoxPart
= 0; // For XP, use the 'default' part
503 RECT
* rUseForBg
= &rBorder
;
505 bool drawFullButton
= false;
506 int bgState
= butState
;
507 const bool isFocused
= (FindFocus() == GetMainWindowOfCompositeControl()) ? true : false;
509 if ( useVistaComboBox
)
511 // Draw the entire control as a single button?
512 if ( !isNonStdButton
)
514 if ( HasFlag(wxCB_READONLY
) )
515 drawFullButton
= true;
518 if ( drawFullButton
)
520 comboBoxPart
= CP_READONLY
;
523 // It should be safe enough to update this flag here.
524 m_iFlags
|= wxCC_FULL_BUTTON
;
528 comboBoxPart
= CP_BORDER
;
529 m_iFlags
&= ~wxCC_FULL_BUTTON
;
532 bgState
= CBB_FOCUSED
;
534 bgState
= CBB_NORMAL
;
539 // Draw parent's background, if necessary
540 RECT
* rUseForTb
= NULL
;
542 if ( theme
->IsThemeBackgroundPartiallyTransparent( hTheme
, comboBoxPart
, bgState
) )
544 else if ( m_iFlags
& wxCC_IFLAG_BUTTON_OUTSIDE
)
545 rUseForTb
= &rButton
;
548 theme
->DrawThemeParentBackground( hWnd
, hDc
, rUseForTb
);
551 // Draw the control background (including the border)
552 if ( m_widthCustomBorder
> 0 )
554 theme
->DrawThemeBackground( hTheme
, hDc
, comboBoxPart
, bgState
, rUseForBg
, NULL
);
558 // No border. We can't use theme, since it cannot be relied on
559 // to deliver borderless drawing, even with DrawThemeBackgroundEx.
562 dc
.DrawRectangle(borderRect
);
566 // Draw the drop-button
567 if ( !isNonStdButton
)
569 drawButFlags
= Button_BitmapOnly
;
571 int butPart
= CP_DROPDOWNBUTTON
;
573 if ( useVistaComboBox
)
575 if ( drawFullButton
)
577 // We need to alter the button style slightly before
578 // drawing the actual button (but it was good above
579 // when background etc was done).
580 if ( butState
== CBXS_HOT
|| butState
== CBXS_PRESSED
)
581 butState
= CBXS_NORMAL
;
584 if ( m_btnSide
== wxRIGHT
)
585 butPart
= CP_DROPDOWNBUTTONRIGHT
;
587 butPart
= CP_DROPDOWNBUTTONLEFT
;
590 theme
->DrawThemeBackground( hTheme
, hDc
, butPart
, butState
, &rButton
, NULL
);
592 else if ( useVistaComboBox
&&
593 (m_iFlags
& wxCC_IFLAG_BUTTON_OUTSIDE
) )
595 // We'll do this, because DrawThemeParentBackground
596 // doesn't seem to be reliable on Vista.
597 drawButFlags
|= Button_PaintBackground
;
603 // Windows 2000 and earlier
604 drawButFlags
= Button_PaintBackground
;
608 dc
.DrawRectangle(borderRect
);
611 // Button rendering (may only do the bitmap on button, depending on the flags)
612 DrawButton( dc
, rectButton
, drawButFlags
);
614 // Paint required portion of the custom image on the control
615 if ( (!m_text
|| m_widthCustomPaint
) )
617 wxASSERT( m_widthCustomPaint
>= 0 );
619 // this is intentionally here to allow drawed rectangle's
620 // right edge to be hidden
622 rectTextField
.width
= m_widthCustomPaint
;
624 dc
.SetFont( GetFont() );
626 dc
.SetClippingRegion(rectTextField
);
627 if ( m_popupInterface
)
628 m_popupInterface
->PaintComboControl(dc
,rectTextField
);
630 wxComboPopup::DefaultPaintComboControl(this,dc
,rectTextField
);
636 void wxComboCtrl::OnMouseEvent( wxMouseEvent
& event
)
639 bool isOnButtonArea
= m_btnArea
.Contains(mx
,event
.m_y
);
640 int handlerFlags
= isOnButtonArea
? wxCC_MF_ON_BUTTON
: 0;
642 if ( PreprocessMouseEvent(event
,isOnButtonArea
) )
645 if ( (m_windowStyle
& (wxCC_SPECIAL_DCLICK
|wxCB_READONLY
)) == wxCB_READONLY
)
647 // if no textctrl and no special double-click, then the entire control acts
649 handlerFlags
|= wxCC_MF_ON_BUTTON
;
650 if ( HandleButtonMouseEvent(event
,handlerFlags
) )
655 if ( isOnButtonArea
|| HasCapture() ||
656 (m_widthCustomPaint
&& mx
< (m_tcArea
.x
+m_widthCustomPaint
)) )
658 handlerFlags
|= wxCC_MF_ON_CLICK_AREA
;
660 if ( HandleButtonMouseEvent(event
,handlerFlags
) )
663 else if ( m_btnState
)
665 // otherwise need to clear the hover status
667 RefreshRect(m_btnArea
);
672 // This will handle left_down and left_dclick events outside button in a Windows-like manner.
673 // See header file for further information on this method.
674 HandleNormalMouseEvent(event
);
678 #if wxUSE_COMBOCTRL_POPUP_ANIMATION
679 static wxUint32
GetUserPreferencesMask()
681 static wxUint32 userPreferencesMask
= 0;
682 static bool valueSet
= false;
685 return userPreferencesMask
;
687 wxRegKey
* pKey
= NULL
;
688 wxRegKey
key1(wxRegKey::HKCU
, wxT("Software\\Policies\\Microsoft\\Control Panel"));
689 wxRegKey
key2(wxRegKey::HKCU
, wxT("Software\\Policies\\Microsoft\\Windows\\Control Panel"));
690 wxRegKey
key3(wxRegKey::HKCU
, wxT("Control Panel\\Desktop"));
694 else if ( key2
.Exists() )
696 else if ( key3
.Exists() )
699 if ( pKey
&& pKey
->Open(wxRegKey::Read
) )
702 if ( pKey
->HasValue(wxT("UserPreferencesMask")) &&
703 pKey
->QueryValue(wxT("UserPreferencesMask"), buf
) )
705 if ( buf
.GetDataLen() >= 4 )
707 wxUint32
* p
= (wxUint32
*) buf
.GetData();
708 userPreferencesMask
= *p
;
715 return userPreferencesMask
;
719 #if wxUSE_COMBOCTRL_POPUP_ANIMATION
720 void wxComboCtrl::DoTimerEvent()
722 bool stopTimer
= false;
724 wxWindow
* win
= GetPopupWindow();
725 wxWindow
* popup
= GetPopupControl()->GetControl();
727 // Popup was hidden before it was fully shown?
728 if ( IsPopupWindowState(Hidden
) )
734 wxLongLong t
= ::wxGetLocalTimeMillis();
735 const wxRect
& rect
= m_animRect
;
737 int pos
= (int) (t
-m_animStart
).GetLo();
738 if ( pos
< COMBOBOX_ANIMATION_DURATION
)
740 int height
= rect
.height
;
741 //int h0 = rect.height;
742 int h
= (((pos
*256)/COMBOBOX_ANIMATION_DURATION
)*height
)/256;
743 int y
= (height
- h
);
747 if ( m_animFlags
& ShowAbove
)
749 win
->SetSize( rect
.x
, rect
.y
+ height
- h
, rect
.width
, h
);
753 // Note that apparently Move() should be called after
754 // SetSize() to reduce (or even eliminate) animation garbage
755 win
->SetSize( rect
.x
, rect
.y
, rect
.width
, h
);
756 popup
->Move( 0, -y
);
768 DoShowPopup( m_animRect
, m_animFlags
);
771 // Do a one final refresh to clean up the rare cases of animation
778 #if wxUSE_COMBOCTRL_POPUP_ANIMATION
779 bool wxComboCtrl::AnimateShow( const wxRect
& rect
, int flags
)
781 if ( GetUserPreferencesMask() & wxMSW_DESKTOP_USERPREFERENCESMASK_COMBOBOXANIM
)
783 m_animStart
= ::wxGetLocalTimeMillis();
787 wxWindow
* win
= GetPopupWindow();
788 win
->SetSize( rect
.x
, rect
.y
, rect
.width
, 0 );
791 m_animTimer
.SetOwner( this, wxID_ANY
);
792 m_animTimer
.Start( COMBOBOX_ANIMATION_RESOLUTION
, wxTIMER_CONTINUOUS
);
803 wxCoord
wxComboCtrl::GetNativeTextIndent() const
806 if ( wxUxThemeEngine::GetIfActive() )
807 return NATIVE_TEXT_INDENT_XP
;
809 return NATIVE_TEXT_INDENT_CLASSIC
;
812 bool wxComboCtrl::IsKeyPopupToggle(const wxKeyEvent
& event
) const
814 const bool isPopupShown
= IsPopupShown();
816 switch ( event
.GetKeyCode() )
819 // F4 toggles the popup in the native comboboxes, so emulate them
820 if ( !event
.AltDown() )
831 case WXK_NUMPAD_DOWN
:
833 // Arrow keys (and mouse wheel) toggle the popup in the native
835 if ( event
.AltDown() )
843 #endif // wxUSE_COMBOCTRL