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)
69enum
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
87enum
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
99WX_DEFINE_FLAGS( wxCheckBoxStyle )
100
101wxBEGIN_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
129wxEND_FLAGS( wxCheckBoxStyle )
130
131IMPLEMENT_DYNAMIC_CLASS_XTI(wxCheckBox, wxControl,"wx/checkbox.h")
132
133wxBEGIN_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
140wxEND_PROPERTIES_TABLE()
141
142wxBEGIN_HANDLERS_TABLE(wxCheckBox)
143wxEND_HANDLERS_TABLE()
144
145wxCONSTRUCTOR_6( wxCheckBox , wxWindow* , Parent , wxWindowID , Id , wxString , Label , wxPoint , Position , wxSize , Size , long , WindowStyle )
146#else
147IMPLEMENT_DYNAMIC_CLASS(wxCheckBox, wxControl)
148#endif
149
150
151// ----------------------------------------------------------------------------
152// wxCheckBox creation
153// ----------------------------------------------------------------------------
154
155void wxCheckBox::Init()
156{
157    m_state = wxCHK_UNCHECKED;
158    m_isPressed =
159    m_isHot = false;
160}
161
162bool 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
201wxSize 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
242void wxCheckBox::SetValue(bool val)
243{
244    Set3StateValue(val ? wxCHK_CHECKED : wxCHK_UNCHECKED);
245}
246
247bool wxCheckBox::GetValue() const
248{
249    return Get3StateValue() != wxCHK_UNCHECKED;
250}
251
252void 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
263wxCOMPILE_TIME_ASSERT(wxCHK_UNCHECKED == BST_UNCHECKED
264    && wxCHK_CHECKED == BST_CHECKED
265    && wxCHK_UNDETERMINED == BST_INDETERMINATE, EnumValuesIncorrect);
266
267void 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
276wxCheckBoxState wxCheckBox::DoGet3StateValue() const
277{
278    return m_state;
279}
280
281bool 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
319bool 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
332bool wxCheckBox::IsOwnerDrawn() const
333{
334    return
335        (::GetWindowLong(GetHwnd(), GWL_STYLE) & BS_OWNERDRAW) == BS_OWNERDRAW;
336}
337
338void 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
382void 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
393void 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
404void wxCheckBox::OnFocus(wxFocusEvent& event)
405{
406    Refresh();
407
408    event.Skip();
409}
410
411bool 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