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"
36 #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_DURATION 200 // In milliseconds
75 // ============================================================================
77 // ============================================================================
80 BEGIN_EVENT_TABLE(wxComboCtrl
, wxComboCtrlBase
)
81 EVT_PAINT(wxComboCtrl::OnPaintEvent
)
82 EVT_MOUSE_EVENTS(wxComboCtrl::OnMouseEvent
)
86 IMPLEMENT_DYNAMIC_CLASS(wxComboCtrl
, wxComboCtrlBase
)
88 void wxComboCtrl::Init()
92 bool wxComboCtrl::Create(wxWindow
*parent
,
94 const wxString
& value
,
98 const wxValidator
& validator
,
103 long border
= style
& wxBORDER_MASK
;
105 wxUxThemeEngine
* theme
= wxUxThemeEngine::GetIfActive();
109 // For XP, have 1-width custom border, for older version use sunken
112 border
= wxBORDER_NONE
;
113 m_widthCustomBorder
= 1;
116 border
= wxBORDER_SUNKEN
;
118 style
= (style
& ~(wxBORDER_MASK
)) | border
;
121 // create main window
122 if ( !wxComboCtrlBase::Create(parent
,
127 style
| wxFULL_REPAINT_ON_RESIZE
,
132 if ( style
& wxCC_STD_BUTTON
)
133 m_iFlags
|= wxCC_POPUP_ON_MOUSE_UP
;
135 // Create textctrl, if necessary
136 CreateTextCtrl( wxNO_BORDER
, validator
);
138 // Add keyboard input handlers for main control and textctrl
139 InstallInputHandlers();
141 // Prepare background for double-buffering
142 SetBackgroundStyle( wxBG_STYLE_CUSTOM
);
144 // SetBestSize should be called last
150 wxComboCtrl::~wxComboCtrl()
154 void wxComboCtrl::OnThemeChange()
156 wxUxThemeEngine
* theme
= wxUxThemeEngine::GetIfActive();
159 wxUxThemeHandle
hTheme(this, L
"COMBOBOX");
162 theme
->GetThemeColor(hTheme
,EP_EDITTEXT
,ETS_NORMAL
,TMT_FILLCOLOR
,&col
);
163 SetBackgroundColour(wxRGBToColour(col
));
164 theme
->GetThemeColor(hTheme
,EP_EDITTEXT
,ETS_NORMAL
,TMT_TEXTCOLOR
,&col
);
165 SetForegroundColour(wxRGBToColour(col
));
169 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW
));
170 SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT
));
174 void wxComboCtrl::OnResize()
177 // Recalculates button and textctrl areas
182 if ( wxUxThemeEngine::GetIfActive() )
184 textCtrlXAdjust
= TEXTCTRLXADJUST_XP
;
185 textCtrlYAdjust
= TEXTCTRLYADJUST_XP
;
189 textCtrlXAdjust
= TEXTCTRLXADJUST_CLASSIC
;
190 textCtrlYAdjust
= TEXTCTRLYADJUST_CLASSIC
;
193 // Technically Classic Windows style combo has more narrow button,
194 // but the native renderer doesn't paint it well like that.
196 CalculateAreas(btnWidth
);
198 // Position textctrl using standard routine
199 PositionTextCtrl(textCtrlXAdjust
,textCtrlYAdjust
);
202 // Draws non-XP GUI dotted line around the focus area
203 static void wxMSWDrawFocusRect( wxDC
& dc
, const wxRect
& rect
)
205 #if !defined(__WXWINCE__)
208 mswRect.left = rect.x;
209 mswRect.top = rect.y;
210 mswRect.right = rect.x + rect.width;
211 mswRect.bottom = rect.y + rect.height;
212 HDC hdc = (HDC) dc.GetHDC();
213 SetMapMode(hdc,MM_TEXT); // Just in case...
214 DrawFocusRect(hdc,&mswRect);
216 // FIXME: Use DrawFocusRect code above (currently it draws solid line
217 // for caption focus but works ok for other stuff).
218 // Also, this code below may not work in future wx versions, since
219 // it employs wxCAP_BUTT hack to have line of width 1.
220 dc
.SetLogicalFunction(wxINVERT
);
222 wxPen
pen(*wxBLACK
,1,wxDOT
);
223 pen
.SetCap(wxCAP_BUTT
);
225 dc
.SetBrush(*wxTRANSPARENT_BRUSH
);
227 dc
.DrawRectangle(rect
);
229 dc
.SetLogicalFunction(wxCOPY
);
231 dc
.SetLogicalFunction(wxINVERT
);
233 dc
.SetPen(wxPen(*wxBLACK
,1,wxDOT
));
234 dc
.SetBrush(*wxTRANSPARENT_BRUSH
);
236 dc
.DrawRectangle(rect
);
238 dc
.SetLogicalFunction(wxCOPY
);
242 // draw focus background on area in a way typical on platform
243 void wxComboCtrl::PrepareBackground( wxDC
& dc
, const wxRect
& rect
, int flags
) const
245 wxUxThemeEngine
* theme
= (wxUxThemeEngine
*) NULL
;
247 // Constructor only calls GetHWND() const, so it should be safe
248 // to cast "this" to const.
249 wxUxThemeHandle
hTheme(this, L
"COMBOBOX");
252 wxSize sz
= GetClientSize();
254 bool isFocused
; // also selected
256 // For smaller size control (and for disabled background) use less spacing
260 if ( !(flags
& wxCONTROL_ISSUBMENU
) )
263 isEnabled
= IsEnabled();
264 isFocused
= ShouldDrawFocus();
266 // Windows-style: for smaller size control (and for disabled background) use less spacing
270 focusSpacingX
= isEnabled
? 2 : 1;
271 focusSpacingY
= sz
.y
> (GetCharHeight()+2) && isEnabled
? 2 : 1;
290 // Drawing a list item
291 isEnabled
= true; // they are never disabled
292 isFocused
= flags
& wxCONTROL_SELECTED
? true : false;
298 // Set the background sub-rectangle for selection, disabled etc
299 wxRect
selRect(rect
);
300 selRect
.y
+= focusSpacingY
;
301 selRect
.height
-= (focusSpacingY
*2);
305 if ( !(flags
& wxCONTROL_ISSUBMENU
) )
306 wcp
+= m_widthCustomPaint
;
308 selRect
.x
+= wcp
+ focusSpacingX
;
309 selRect
.width
-= wcp
+ (focusSpacingX
*2);
312 theme
= wxUxThemeEngine::GetIfActive();
315 bool drawDottedEdge
= false;
319 // If popup is hidden and this control is focused,
320 // then draw the focus-indicator (selbgcolor background etc.).
324 // TODO: Proper theme color getting (JMS: I don't know which parts/colors to use,
325 // those below don't work)
328 theme
->GetThemeColor(hTheme
,EP_EDITTEXT
,ETS_SELECTED
,TMT_TEXTCOLOR
,&cref
);
329 dc
.SetTextForeground( wxRGBToColour(cref
) );
330 theme
->GetThemeColor(hTheme
,EP_EDITTEXT
,ETS_SELECTED
,TMT_FILLCOLOR
,&cref
);
331 bgCol
= wxRGBToColour(cref
);
336 dc
.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT
) );
337 bgCol
= wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT
);
338 if ( m_windowStyle
& wxCB_READONLY
)
339 drawDottedEdge
= true;
346 theme->GetThemeColor(hTheme,EP_EDITTEXT,ETS_NORMAL,TMT_TEXTCOLOR,&cref);
347 dc.SetTextForeground( wxRGBToColour(cref) );
348 theme->GetThemeColor(hTheme,EP_EDITTEXT,ETS_NORMAL,TMT_FILLCOLOR,&cref);
349 bgCol = wxRGBToColour(cref);
353 dc
.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT
) );
354 bgCol
= GetBackgroundColour();
362 theme->GetThemeColor(hTheme,EP_EDITTEXT,ETS_DISABLED,TMT_TEXTCOLOR,&cref);
363 dc.SetTextForeground( wxRGBToColour(cref) );
364 theme->GetThemeColor(hTheme,EP_EDITTEXT,ETS_DISABLED,TMT_EDGEFILLCOLOR,&cref);
365 bgCol = wxRGBToColour(cref);
369 dc
.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT
) );
370 bgCol
= wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE
);
376 dc
.DrawRectangle(selRect
);
377 if ( drawDottedEdge
)
378 wxMSWDrawFocusRect(dc
,selRect
);
380 // Don't clip exactly to the selection rectangle so we can draw
381 // to the non-selected area in front of it.
382 wxRect
clipRect(rect
.x
,rect
.y
,
383 (selRect
.x
+selRect
.width
)-rect
.x
-1,rect
.height
);
384 dc
.SetClippingRegion(clipRect
);
387 void wxComboCtrl::OnPaintEvent( wxPaintEvent
& WXUNUSED(event
) )
389 // TODO: Convert drawing in this function to Windows API Code
391 wxSize sz
= GetClientSize();
392 wxAutoBufferedPaintDC
dc(this);
394 const wxRect
& rectb
= m_btnArea
;
395 wxRect rect
= m_tcArea
;
396 bool isEnabled
= IsEnabled();
397 wxColour bgCol
= GetBackgroundColour();
400 wxUxThemeEngine
* theme
= NULL
;
401 wxUxThemeHandle
hTheme(this, L
"COMBOBOX");
404 // area around both controls
405 wxRect
rect2(0,0,sz
.x
,sz
.y
);
406 if ( m_iFlags
& wxCC_IFLAG_BUTTON_OUTSIDE
)
412 // Use theme to draw border on XP
415 theme
= wxUxThemeEngine::GetIfActive();
418 // Select correct border colour
420 etsState
= ETS_DISABLED
;
422 etsState
= ETS_NORMAL
;
424 if ( m_widthCustomBorder
)
426 theme
->GetThemeColor(hTheme
,EP_EDITTEXT
,etsState
,TMT_BORDERCOLOR
,&cref
);
429 dc
.SetPen( wxRGBToColour(cref
) );
431 dc
.SetBrush( *wxTRANSPARENT_BRUSH
);
432 dc
.DrawRectangle(rect2
);
435 theme
->GetThemeColor(hTheme
,EP_EDITTEXT
,etsState
,TMT_TEXTCOLOR
,&cref
);
436 fgCol
= wxRGBToColour(cref
);
440 // draw regular background
441 fgCol
= GetForegroundColour();
444 rect2
.Deflate(m_widthCustomBorder
);
449 // clear main background
450 dc
.DrawRectangle(rect
);
452 // Button background with theme?
453 bool drawButBg
= true;
454 if ( hTheme
&& m_blankButtonBg
)
457 wxCopyRectToRECT(rectb
, r
);
459 // Draw parent background if needed (since button looks like its out of
460 // the combo, this is preferred).
461 theme
->DrawThemeParentBackground(GetHwndOf(this),
468 // Standard button rendering
469 DrawButton(dc
,rectb
,drawButBg
);
471 // paint required portion on the control
472 if ( (!m_text
|| m_widthCustomPaint
) )
474 wxASSERT( m_widthCustomPaint
>= 0 );
476 // this is intentionally here to allow drawed rectangle's
477 // right edge to be hidden
479 rect
.width
= m_widthCustomPaint
;
481 dc
.SetFont( GetFont() );
483 dc
.SetClippingRegion(rect
);
484 if ( m_popupInterface
)
485 m_popupInterface
->PaintComboControl(dc
,rect
);
487 wxComboPopup::DefaultPaintComboControl(this,dc
,rect
);
491 void wxComboCtrl::OnMouseEvent( wxMouseEvent
& event
)
494 bool isOnButtonArea
= m_btnArea
.Contains(mx
,event
.m_y
);
495 int handlerFlags
= isOnButtonArea
? wxCC_MF_ON_BUTTON
: 0;
497 if ( PreprocessMouseEvent(event
,isOnButtonArea
) )
500 if ( (m_windowStyle
& (wxCC_SPECIAL_DCLICK
|wxCB_READONLY
)) == wxCB_READONLY
)
502 // if no textctrl and no special double-click, then the entire control acts
504 handlerFlags
|= wxCC_MF_ON_BUTTON
;
505 if ( HandleButtonMouseEvent(event
,handlerFlags
) )
510 if ( isOnButtonArea
|| HasCapture() ||
511 (m_widthCustomPaint
&& mx
< (m_tcArea
.x
+m_widthCustomPaint
)) )
513 handlerFlags
|= wxCC_MF_ON_CLICK_AREA
;
515 if ( HandleButtonMouseEvent(event
,handlerFlags
) )
518 else if ( m_btnState
)
520 // otherwise need to clear the hover status
522 RefreshRect(m_btnArea
);
527 // This will handle left_down and left_dclick events outside button in a Windows-like manner.
528 // See header file for further information on this method.
529 HandleNormalMouseEvent(event
);
533 #if !defined(__WXWINCE__)
534 static wxUint32
GetUserPreferencesMask()
536 static wxUint32 userPreferencesMask
= 0;
537 static bool valueSet
= false;
540 return userPreferencesMask
;
542 wxRegKey
key(wxRegKey::HKCU
, wxT("Control Panel\\Desktop"));
543 if( key
.Open(wxRegKey::Read
) )
546 if ( key
.QueryValue(wxT("UserPreferencesMask"), buf
) )
548 if ( buf
.GetDataLen() >= 4 )
550 wxByte
* p
= (wxByte
*) buf
.GetData();
551 userPreferencesMask
= p
[3] + (p
[2]<<8) + (p
[1]<<16) + (p
[0]<<24);
558 return userPreferencesMask
;
562 bool wxComboCtrl::AnimateShow( const wxRect
& rect
, int flags
)
564 #if !defined(__WXWINCE__)
565 if ( GetUserPreferencesMask() & (1<<26) )
567 wxLongLong tStart
= ::wxGetLocalTimeMillis();
569 int height
= rect
.height
;
571 wxWindow
* win
= GetPopupWindow();
572 wxWindow
* popup
= GetPopupControl()->GetControl();
574 const int delay
= COMBOBOX_ANIMATION_DURATION
;
575 const int resolution
= 10;
576 int h0
= popup
->GetSize().y
;
578 win
->SetSize( rect
.x
, rect
.y
, rect
.width
, 0 );
583 wxLongLong t
= ::wxGetLocalTimeMillis();
584 int pos
= (int) (t
-tStart
).GetLo();
588 int h
= (((pos
*256)/delay
)*height
)/256;
593 if ( flags
& ShowAbove
)
595 win
->SetSize( rect
.x
, rect
.y
+ h0
- h
, rect
.width
, h
);
599 popup
->Move( 0, -y
);
600 win
->SetSize( rect
.x
, rect
.y
, rect
.width
, h
);
603 wxMilliSleep( resolution
);
606 // Popup was hidden before it was fully shown?
607 if ( IsPopupWindowState(Hidden
) )
621 wxCoord
wxComboCtrl::GetNativeTextIndent() const
623 if ( wxUxThemeEngine::GetIfActive() )
624 return NATIVE_TEXT_INDENT_XP
;
625 return NATIVE_TEXT_INDENT_CLASSIC
;
628 bool wxComboCtrl::IsKeyPopupToggle(const wxKeyEvent
& event
) const
630 const bool isPopupShown
= IsPopupShown();
632 switch ( event
.GetKeyCode() )
635 // F4 toggles the popup in the native comboboxes, so emulate them
636 if ( !event
.AltDown() )
647 // On XP or with writable combo in Classic, arrows don't open the
648 // popup but Alt-arrow does
649 if ( event
.AltDown() ||
651 HasFlag(wxCB_READONLY
) &&
652 !wxUxThemeEngine::GetIfActive()
663 #endif // wxUSE_COMBOCTRL