1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/msw/checkbox.cpp
3 // Purpose:     wxCheckBox
4 // Author:      Julian Smart
5 // Modified by:
6 // Created:     04/01/98
7 // RCS-ID:      $Id: checkbox.cpp 40331 2006-07-25 18:47:39Z VZ $
8 // Copyright:   (c) Julian Smart
9 // Licence:     wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11 
12 // ============================================================================
13 // declarations
14 // ============================================================================
15 
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19 
20 // For compilers that support precompilation, includes "wx.h".
21 #include "wx/wxprec.h"
22 
23 #ifdef __BORLANDC__
24     #pragma hdrstop
25 #endif
26 
27 #if wxUSE_CHECKBOX
28 
29 #include "wx/checkbox.h"
30 
31 #ifndef WX_PRECOMP
32     #include "wx/brush.h"
33     #include "wx/dcscreen.h"
34     #include "wx/settings.h"
35 #endif
36 
37 #include "wx/msw/uxtheme.h"
38 #include "wx/msw/private.h"
39 
40 // ----------------------------------------------------------------------------
41 // constants
42 // ----------------------------------------------------------------------------
43 
44 #ifndef BST_UNCHECKED
45     #define BST_UNCHECKED 0x0000
46 #endif
47 
48 #ifndef BST_CHECKED
49     #define BST_CHECKED 0x0001
50 #endif
51 
52 #ifndef BST_INDETERMINATE
53     #define BST_INDETERMINATE 0x0002
54 #endif
55 
56 #ifndef DFCS_HOT
57     #define DFCS_HOT 0x1000
58 #endif
59 
60 #ifndef DT_HIDEPREFIX
61     #define DT_HIDEPREFIX 0x00100000
62 #endif
63 
64 #ifndef BP_CHECKBOX
65     #define BP_CHECKBOX 3
66 #endif
67 
68 // these values are defined in tmschema.h (except the first one)
69 enum
70 {
71     CBS_INVALID,
72     CBS_UNCHECKEDNORMAL,
73     CBS_UNCHECKEDHOT,
74     CBS_UNCHECKEDPRESSED,
75     CBS_UNCHECKEDDISABLED,
76     CBS_CHECKEDNORMAL,
77     CBS_CHECKEDHOT,
78     CBS_CHECKEDPRESSED,
79     CBS_CHECKEDDISABLED,
80     CBS_MIXEDNORMAL,
81     CBS_MIXEDHOT,
82     CBS_MIXEDPRESSED,
83     CBS_MIXEDDISABLED
84 };
85 
86 // these are our own
87 enum
88 {
89     CBS_HOT_OFFSET = 1,
90     CBS_PRESSED_OFFSET = 2,
91     CBS_DISABLED_OFFSET = 3
92 };
93 
94 // ============================================================================
95 // implementation
96 // ============================================================================
97 
98 #if wxUSE_EXTENDED_RTTI
99 WX_DEFINE_FLAGS( wxCheckBoxStyle )
100 
wxBEGIN_FLAGS(wxCheckBoxStyle)101 wxBEGIN_FLAGS( wxCheckBoxStyle )
102     // new style border flags, we put them first to
103     // use them for streaming out
104     wxFLAGS_MEMBER(wxBORDER_SIMPLE)
105     wxFLAGS_MEMBER(wxBORDER_SUNKEN)
106     wxFLAGS_MEMBER(wxBORDER_DOUBLE)
107     wxFLAGS_MEMBER(wxBORDER_RAISED)
108     wxFLAGS_MEMBER(wxBORDER_STATIC)
109     wxFLAGS_MEMBER(wxBORDER_NONE)
110 
111     // old style border flags
112     wxFLAGS_MEMBER(wxSIMPLE_BORDER)
113     wxFLAGS_MEMBER(wxSUNKEN_BORDER)
114     wxFLAGS_MEMBER(wxDOUBLE_BORDER)
115     wxFLAGS_MEMBER(wxRAISED_BORDER)
116     wxFLAGS_MEMBER(wxSTATIC_BORDER)
117     wxFLAGS_MEMBER(wxNO_BORDER)
118 
119     // standard window styles
120     wxFLAGS_MEMBER(wxTAB_TRAVERSAL)
121     wxFLAGS_MEMBER(wxCLIP_CHILDREN)
122     wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW)
123     wxFLAGS_MEMBER(wxWANTS_CHARS)
124     wxFLAGS_MEMBER(wxNO_FULL_REPAINT_ON_RESIZE)
125     wxFLAGS_MEMBER(wxALWAYS_SHOW_SB )
126     wxFLAGS_MEMBER(wxVSCROLL)
127     wxFLAGS_MEMBER(wxHSCROLL)
128 
129 wxEND_FLAGS( wxCheckBoxStyle )
130 
131 IMPLEMENT_DYNAMIC_CLASS_XTI(wxCheckBox, wxControl,"wx/checkbox.h")
132 
133 wxBEGIN_PROPERTIES_TABLE(wxCheckBox)
134     wxEVENT_PROPERTY( Click , wxEVT_COMMAND_CHECKBOX_CLICKED , wxCommandEvent )
135 
136     wxPROPERTY( Font , wxFont , SetFont , GetFont , EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
137     wxPROPERTY( Label,wxString, SetLabel, GetLabel, wxString() , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
138     wxPROPERTY( Value ,bool, SetValue, GetValue, EMPTY_MACROVALUE, 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
139     wxPROPERTY_FLAGS( WindowStyle , wxCheckBoxStyle , long , SetWindowStyleFlag , GetWindowStyleFlag , EMPTY_MACROVALUE, 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style
140 wxEND_PROPERTIES_TABLE()
141 
142 wxBEGIN_HANDLERS_TABLE(wxCheckBox)
143 wxEND_HANDLERS_TABLE()
144 
145 wxCONSTRUCTOR_6( wxCheckBox , wxWindow* , Parent , wxWindowID , Id , wxString , Label , wxPoint , Position , wxSize , Size , long , WindowStyle )
146 #else
147 IMPLEMENT_DYNAMIC_CLASS(wxCheckBox, wxControl)
148 #endif
149 
150 
151 // ----------------------------------------------------------------------------
152 // wxCheckBox creation
153 // ----------------------------------------------------------------------------
154 
155 void wxCheckBox::Init()
156 {
157     m_state = wxCHK_UNCHECKED;
158     m_isPressed =
159     m_isHot = false;
160 }
161 
Create(wxWindow * parent,wxWindowID id,const wxString & label,const wxPoint & pos,const wxSize & size,long style,const wxValidator & validator,const wxString & name)162 bool wxCheckBox::Create(wxWindow *parent,
163                         wxWindowID id,
164                         const wxString& label,
165                         const wxPoint& pos,
166                         const wxSize& size, long style,
167                         const wxValidator& validator,
168                         const wxString& name)
169 {
170     Init();
171 
172     if ( !CreateControl(parent, id, pos, size, style, validator, name) )
173         return false;
174 
175     long msStyle = WS_TABSTOP;
176 
177     if ( style & wxCHK_3STATE )
178     {
179         msStyle |= BS_3STATE;
180     }
181     else
182     {
183         wxASSERT_MSG( !Is3rdStateAllowedForUser(),
184             wxT("Using wxCH_ALLOW_3RD_STATE_FOR_USER")
185             wxT(" style flag for a 2-state checkbox is useless") );
186         msStyle |= BS_CHECKBOX;
187     }
188 
189     if ( style & wxALIGN_RIGHT )
190     {
191         msStyle |= BS_LEFTTEXT | BS_RIGHT;
192     }
193 
194     return MSWCreateControl(wxT("BUTTON"), msStyle, pos, size, label, 0);
195 }
196 
197 // ----------------------------------------------------------------------------
198 // wxCheckBox geometry
199 // ----------------------------------------------------------------------------
200 
DoGetBestSize() const201 wxSize wxCheckBox::DoGetBestSize() const
202 {
203     static int s_checkSize = 0;
204 
205     if ( !s_checkSize )
206     {
207         wxScreenDC dc;
208         dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
209 
210         s_checkSize = dc.GetCharHeight();
211     }
212 
213     wxString str = wxGetWindowText(GetHWND());
214 
215     int wCheckbox, hCheckbox;
216     if ( !str.empty() )
217     {
218         GetTextExtent(GetLabelText(str), &wCheckbox, &hCheckbox);
219         wCheckbox += s_checkSize + GetCharWidth();
220 
221         if ( hCheckbox < s_checkSize )
222             hCheckbox = s_checkSize;
223     }
224     else
225     {
226         wCheckbox = s_checkSize;
227         hCheckbox = s_checkSize;
228     }
229 #ifdef __WXWINCE__
230     hCheckbox += 1;
231 #endif
232 
233     wxSize best(wCheckbox, hCheckbox);
234     CacheBestSize(best);
235     return best;
236 }
237 
238 // ----------------------------------------------------------------------------
239 // wxCheckBox operations
240 // ----------------------------------------------------------------------------
241 
SetValue(bool val)242 void wxCheckBox::SetValue(bool val)
243 {
244     Set3StateValue(val ? wxCHK_CHECKED : wxCHK_UNCHECKED);
245 }
246 
GetValue() const247 bool wxCheckBox::GetValue() const
248 {
249     return Get3StateValue() != wxCHK_UNCHECKED;
250 }
251 
Command(wxCommandEvent & event)252 void wxCheckBox::Command(wxCommandEvent& event)
253 {
254     int state = event.GetInt();
255     wxCHECK_RET( (state == wxCHK_UNCHECKED) || (state == wxCHK_CHECKED)
256         || (state == wxCHK_UNDETERMINED),
257         wxT("event.GetInt() returned an invalid checkbox state") );
258 
259     Set3StateValue((wxCheckBoxState) state);
260     ProcessCommand(event);
261 }
262 
263 wxCOMPILE_TIME_ASSERT(wxCHK_UNCHECKED == BST_UNCHECKED
264     && wxCHK_CHECKED == BST_CHECKED
265     && wxCHK_UNDETERMINED == BST_INDETERMINATE, EnumValuesIncorrect);
266 
DoSet3StateValue(wxCheckBoxState state)267 void wxCheckBox::DoSet3StateValue(wxCheckBoxState state)
268 {
269     m_state = state;
270     if ( !IsOwnerDrawn() )
271         ::SendMessage(GetHwnd(), BM_SETCHECK, (WPARAM) state, 0);
272     else // owner drawn buttons don't react to this message
273         Refresh();
274 }
275 
DoGet3StateValue() const276 wxCheckBoxState wxCheckBox::DoGet3StateValue() const
277 {
278     return m_state;
279 }
280 
MSWCommand(WXUINT cmd,WXWORD WXUNUSED (id))281 bool wxCheckBox::MSWCommand(WXUINT cmd, WXWORD WXUNUSED(id))
282 {
283     if ( cmd != BN_CLICKED && cmd != BN_DBLCLK )
284         return false;
285 
286     // first update the value so that user event handler gets the new checkbox
287     // value
288 
289     // ownerdrawn buttons don't manage their state themselves unlike usual
290     // auto checkboxes so do it ourselves in any case
291     wxCheckBoxState state;
292     if ( Is3rdStateAllowedForUser() )
293     {
294         state = (wxCheckBoxState)((m_state + 1) % 3);
295     }
296     else // 2 state checkbox (at least from users point of view)
297     {
298         // note that wxCHK_UNDETERMINED also becomes unchecked when clicked
299         state = m_state == wxCHK_UNCHECKED ? wxCHK_CHECKED : wxCHK_UNCHECKED;
300     }
301 
302     DoSet3StateValue(state);
303 
304 
305     // generate the event
306     wxCommandEvent event(wxEVT_COMMAND_CHECKBOX_CLICKED, m_windowId);
307 
308     event.SetInt(state);
309     event.SetEventObject(this);
310     ProcessCommand(event);
311 
312     return true;
313 }
314 
315 // ----------------------------------------------------------------------------
316 // owner drawn checkboxes stuff
317 // ----------------------------------------------------------------------------
318 
SetForegroundColour(const wxColour & colour)319 bool wxCheckBox::SetForegroundColour(const wxColour& colour)
320 {
321     if ( !wxCheckBoxBase::SetForegroundColour(colour) )
322         return false;
323 
324     // the only way to change the checkbox foreground colour under Windows XP
325     // is to owner draw it
326     if ( wxUxThemeEngine::GetIfActive() )
327         MakeOwnerDrawn(colour.Ok());
328 
329     return true;
330 }
331 
IsOwnerDrawn() const332 bool wxCheckBox::IsOwnerDrawn() const
333 {
334     return
335         (::GetWindowLong(GetHwnd(), GWL_STYLE) & BS_OWNERDRAW) == BS_OWNERDRAW;
336 }
337 
MakeOwnerDrawn(bool ownerDrawn)338 void wxCheckBox::MakeOwnerDrawn(bool ownerDrawn)
339 {
340     long style = ::GetWindowLong(GetHwnd(), GWL_STYLE);
341 
342     // note that BS_CHECKBOX & BS_OWNERDRAW != 0 so we can't operate on
343     // them as on independent style bits
344     if ( ownerDrawn )
345     {
346         style &= ~(BS_CHECKBOX | BS_3STATE);
347         style |= BS_OWNERDRAW;
348 
349         Connect(wxEVT_ENTER_WINDOW,
350                 wxMouseEventHandler(wxCheckBox::OnMouseEnterOrLeave));
351         Connect(wxEVT_LEAVE_WINDOW,
352                 wxMouseEventHandler(wxCheckBox::OnMouseEnterOrLeave));
353         Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(wxCheckBox::OnMouseLeft));
354         Connect(wxEVT_LEFT_UP, wxMouseEventHandler(wxCheckBox::OnMouseLeft));
355         Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(wxCheckBox::OnFocus));
356         Connect(wxEVT_KILL_FOCUS, wxFocusEventHandler(wxCheckBox::OnFocus));
357     }
358     else // reset to default colour
359     {
360         style &= ~BS_OWNERDRAW;
361         style |= HasFlag(wxCHK_3STATE) ? BS_3STATE : BS_CHECKBOX;
362 
363         Disconnect(wxEVT_ENTER_WINDOW,
364                    wxMouseEventHandler(wxCheckBox::OnMouseEnterOrLeave));
365         Disconnect(wxEVT_LEAVE_WINDOW,
366                    wxMouseEventHandler(wxCheckBox::OnMouseEnterOrLeave));
367         Disconnect(wxEVT_LEFT_DOWN, wxMouseEventHandler(wxCheckBox::OnMouseLeft));
368         Disconnect(wxEVT_LEFT_UP, wxMouseEventHandler(wxCheckBox::OnMouseLeft));
369         Disconnect(wxEVT_SET_FOCUS, wxFocusEventHandler(wxCheckBox::OnFocus));
370         Disconnect(wxEVT_KILL_FOCUS, wxFocusEventHandler(wxCheckBox::OnFocus));
371     }
372 
373     ::SetWindowLong(GetHwnd(), GWL_STYLE, style);
374 
375     if ( !ownerDrawn )
376     {
377         // ensure that controls state is consistent with internal state
378         DoSet3StateValue(m_state);
379     }
380 }
381 
OnMouseEnterOrLeave(wxMouseEvent & event)382 void wxCheckBox::OnMouseEnterOrLeave(wxMouseEvent& event)
383 {
384     m_isHot = event.GetEventType() == wxEVT_ENTER_WINDOW;
385     if ( !m_isHot )
386         m_isPressed = false;
387 
388     Refresh();
389 
390     event.Skip();
391 }
392 
OnMouseLeft(wxMouseEvent & event)393 void wxCheckBox::OnMouseLeft(wxMouseEvent& event)
394 {
395     // TODO: we should capture the mouse here to be notified about left up
396     //       event but this interferes with BN_CLICKED generation so if we
397     //       want to do this we'd need to generate them ourselves
398     m_isPressed = event.GetEventType() == wxEVT_LEFT_DOWN;
399     Refresh();
400 
401     event.Skip();
402 }
403 
OnFocus(wxFocusEvent & event)404 void wxCheckBox::OnFocus(wxFocusEvent& event)
405 {
406     Refresh();
407 
408     event.Skip();
409 }
410 
MSWOnDraw(WXDRAWITEMSTRUCT * item)411 bool wxCheckBox::MSWOnDraw(WXDRAWITEMSTRUCT *item)
412 {
413     DRAWITEMSTRUCT *dis = (DRAWITEMSTRUCT *)item;
414 
415     if ( !IsOwnerDrawn() || dis->CtlType != ODT_BUTTON )
416         return wxCheckBoxBase::MSWOnDraw(item);
417 
418     // calculate the rectangles for the check mark itself and the label
419     HDC hdc = dis->hDC;
420     RECT& rect = dis->rcItem;
421     RECT rectCheck,
422          rectLabel;
423     rectCheck.top =
424     rectLabel.top = rect.top;
425     rectCheck.bottom =
426     rectLabel.bottom = rect.bottom;
427     const int checkSize = GetBestSize().y;
428     const int MARGIN = 3;
429 
430     const bool isRightAligned = HasFlag(wxALIGN_RIGHT);
431     if ( isRightAligned )
432     {
433         rectCheck.right = rect.right;
434         rectCheck.left = rectCheck.right - checkSize;
435 
436         rectLabel.right = rectCheck.left - MARGIN;
437         rectLabel.left = rect.left;
438     }
439     else // normal, left-aligned checkbox
440     {
441         rectCheck.left = rect.left;
442         rectCheck.right = rectCheck.left + checkSize;
443 
444         rectLabel.left = rectCheck.right + MARGIN;
445         rectLabel.right = rect.right;
446     }
447 
448     // show we draw a focus rect?
449     const bool isFocused = m_isPressed || FindFocus() == this;
450 
451 
452     // draw the checkbox itself: note that this should really, really be in
453     // wxRendererNative but unfortunately we can't add a new virtual function
454     // to it without breaking backwards compatibility
455 
456     // classic Win32 version -- this can be useful when we move this into
457     // wxRendererNative
458 #if defined(__WXWINCE__) || !wxUSE_UXTHEME
459     UINT state = DFCS_BUTTONCHECK;
460     if ( !IsEnabled() )
461         state |= DFCS_INACTIVE;
462     switch ( Get3StateValue() )
463     {
464         case wxCHK_CHECKED:
465             state |= DFCS_CHECKED;
466             break;
467 
468         case wxCHK_UNDETERMINED:
469             state |= DFCS_PUSHED;
470             break;
471 
472         default:
473             wxFAIL_MSG( _T("unexpected Get3StateValue() return value") );
474             // fall through
475 
476         case wxCHK_UNCHECKED:
477             // no extra styles needed
478             break;
479     }
480 
481     if ( wxFindWindowAtPoint(wxGetMousePosition()) == this )
482         state |= DFCS_HOT;
483 
484     if ( !::DrawFrameControl(hdc, &rectCheck, DFC_BUTTON, state) )
485     {
486         wxLogLastError(_T("DrawFrameControl(DFC_BUTTON)"));
487     }
488 #else // XP version
489     wxUxThemeEngine *themeEngine = wxUxThemeEngine::GetIfActive();
490     if ( !themeEngine )
491         return false;
492 
493     wxUxThemeHandle theme(this, L"BUTTON");
494     if ( !theme )
495         return false;
496 
497     int state;
498     switch ( Get3StateValue() )
499     {
500         case wxCHK_CHECKED:
501             state = CBS_CHECKEDNORMAL;
502             break;
503 
504         case wxCHK_UNDETERMINED:
505             state = CBS_MIXEDNORMAL;
506             break;
507 
508         default:
509             wxFAIL_MSG( _T("unexpected Get3StateValue() return value") );
510             // fall through
511 
512         case wxCHK_UNCHECKED:
513             state = CBS_UNCHECKEDNORMAL;
514             break;
515     }
516 
517     if ( !IsEnabled() )
518         state += CBS_DISABLED_OFFSET;
519     else if ( m_isPressed )
520         state += CBS_PRESSED_OFFSET;
521     else if ( m_isHot )
522         state += CBS_HOT_OFFSET;
523 
524     HRESULT hr = themeEngine->DrawThemeBackground
525                               (
526                                 theme,
527                                 hdc,
528                                 BP_CHECKBOX,
529                                 state,
530                                 &rectCheck,
531                                 NULL
532                               );
533     if ( FAILED(hr) )
534     {
535         wxLogApiError(_T("DrawThemeBackground(BP_CHECKBOX)"), hr);
536     }
537 #endif // 0/1
538 
539     // draw the text
540     const wxString& label = GetLabel();
541 
542     // first we need to measure it
543     UINT fmt = DT_NOCLIP;
544 
545     // drawing underlying doesn't look well with focus rect (and the native
546     // control doesn't do it)
547     if ( isFocused )
548         fmt |= DT_HIDEPREFIX;
549     if ( isRightAligned )
550         fmt |= DT_RIGHT;
551     // TODO: also use DT_HIDEPREFIX if the system is configured so
552 
553     // we need to get the label real size first if we have to draw a focus rect
554     // around it
555     if ( isFocused )
556     {
557         if ( !::DrawText(hdc, label, label.length(), &rectLabel,
558                          fmt | DT_CALCRECT) )
559         {
560             wxLogLastError(_T("DrawText(DT_CALCRECT)"));
561         }
562     }
563 
564     if ( !IsEnabled() )
565     {
566         ::SetTextColor(hdc, ::GetSysColor(COLOR_GRAYTEXT));
567     }
568 
569     if ( !::DrawText(hdc, label, label.length(), &rectLabel, fmt) )
570     {
571         wxLogLastError(_T("DrawText()"));
572     }
573 
574     // finally draw the focus
575     if ( isFocused )
576     {
577         rectLabel.left--;
578         rectLabel.right++;
579         if ( !::DrawFocusRect(hdc, &rectLabel) )
580         {
581             wxLogLastError(_T("DrawFocusRect()"));
582         }
583     }
584 
585     return true;
586 }
587 
588 #endif // wxUSE_CHECKBOX
589