/////////////////////////////////////////////////////////////////////////////
// Name:        src/msw/checkbox.cpp
// Purpose:     wxCheckBox
// Author:      Julian Smart
// Created:     04/01/98
// Copyright:   (c) Julian Smart
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

// ============================================================================
// declarations
// ============================================================================

// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------

// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"


#if wxUSE_CHECKBOX

#include "wx/checkbox.h"

#ifndef WX_PRECOMP
    #include "wx/dcclient.h"
    #include "wx/settings.h"
#endif

#include "wx/renderer.h"
#include "wx/msw/uxtheme.h"
#include "wx/msw/private/button.h"
#include "wx/msw/private/darkmode.h"
#include "wx/private/window.h"
#include "wx/msw/missing.h"

#if wxUSE_ACCESSIBILITY
    #include "wx/msw/private/accessible.h"
#endif

// ============================================================================
// implementation
// ============================================================================

// ----------------------------------------------------------------------------
// wxCheckBox creation
// ----------------------------------------------------------------------------

void wxCheckBox::Init()
{
    m_state = wxCHK_UNCHECKED;
}

bool wxCheckBox::Create(wxWindow *parent,
                        wxWindowID id,
                        const wxString& label,
                        const wxPoint& pos,
                        const wxSize& size, long style,
                        const wxValidator& validator,
                        const wxString& name)
{
    Init();

    WXValidateStyle(&style);
    if ( !CreateControl(parent, id, pos, size, style, validator, name) )
        return false;

    WXDWORD exstyle;
    WXDWORD msStyle = MSWGetStyle(style, &exstyle);

    msStyle |= wxMSWButton::GetMultilineStyle(label);

    return MSWCreateControl(wxT("BUTTON"), msStyle, pos, size, label, exstyle);
}

WXDWORD wxCheckBox::MSWGetStyle(long style, WXDWORD *exstyle) const
{
    // buttons never have an external border, they draw their own one
    WXDWORD msStyle = wxControl::MSWGetStyle(style, exstyle);

    if ( style & wxCHK_3STATE )
        msStyle |= BS_3STATE;
    else
        msStyle |= BS_CHECKBOX;

    if ( style & wxALIGN_RIGHT )
    {
        msStyle |= BS_LEFTTEXT | BS_RIGHT;
    }

    return msStyle;
}

void wxCheckBox::MSWSetDarkOrLightMode(SetMode setmode)
{
    wxCheckBoxBase::MSWSetDarkOrLightMode(setmode);

    // Use owner-draw mode if needed for dark mode or custom text colour
    MSWMakeOwnerDrawn(wxMSWDarkMode::IsActive() || m_hasFgCol);
}

// ----------------------------------------------------------------------------
// wxCheckBox geometry
// ----------------------------------------------------------------------------

wxSize wxCheckBox::DoGetBestClientSize() const
{
    static wxPrivate::DpiDependentValue<wxCoord> s_checkSize;

    if ( s_checkSize.HasChanged(this) )
    {
        wxInfoDC dc(const_cast<wxCheckBox*>(this));
        dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));

        s_checkSize.SetAtNewDPI(dc.GetCharHeight());
    }

    wxCoord& checkSize = s_checkSize.Get();
    wxString str = wxGetWindowText(GetHWND());

    int wCheckbox, hCheckbox;
    if ( !str.empty() )
    {
        wxInfoDC dc(const_cast<wxCheckBox *>(this));
        dc.GetMultiLineTextExtent(GetLabelText(str), &wCheckbox, &hCheckbox);
        wCheckbox += checkSize + GetCharWidth();

        if ( ::GetWindowLong(GetHwnd(), GWL_STYLE) & BS_MULTILINE )
        {
            // We need to make the checkbox even wider in this case because
            // otherwise it wraps lines automatically and not only on "\n"s as
            // we need and this makes the size computed here wrong resulting in
            // checkbox contents being truncated when it's actually displayed.
            // Without this hack simple checkbox with "Some thing\n and more"
            // label appears on 3 lines, not 2, under Windows 2003 using
            // classic look and feel (although it works fine under Windows 7,
            // with or without themes).
            wCheckbox += checkSize;
        }

        if ( hCheckbox < checkSize )
            hCheckbox = checkSize;
    }
    else
    {
        wCheckbox = checkSize;
        hCheckbox = checkSize;
    }

    return wxSize(wCheckbox, hCheckbox);
}

// ----------------------------------------------------------------------------
// wxCheckBox operations
// ----------------------------------------------------------------------------

void wxCheckBox::SetLabel(const wxString& label)
{
    wxMSWButton::UpdateMultilineStyle(GetHwnd(), label);

    wxCheckBoxBase::SetLabel(label);
}

void wxCheckBox::SetValue(bool val)
{
    Set3StateValue(val ? wxCHK_CHECKED : wxCHK_UNCHECKED);
}

bool wxCheckBox::GetValue() const
{
    return Get3StateValue() != wxCHK_UNCHECKED;
}

void wxCheckBox::Command(wxCommandEvent& event)
{
    int state = event.GetInt();
    wxCHECK_RET( (state == wxCHK_UNCHECKED) || (state == wxCHK_CHECKED)
        || (state == wxCHK_UNDETERMINED),
        wxT("event.GetInt() returned an invalid checkbox state") );

    Set3StateValue((wxCheckBoxState) state);
    ProcessCommand(event);
}

wxCOMPILE_TIME_ASSERT(wxCHK_UNCHECKED == BST_UNCHECKED
    && wxCHK_CHECKED == BST_CHECKED
    && wxCHK_UNDETERMINED == BST_INDETERMINATE, EnumValuesIncorrect);

void wxCheckBox::DoSet3StateValue(wxCheckBoxState state)
{
    if ( m_state == state )
        return;

    m_state = state;
    if ( !IsOwnerDrawn() )
        ::SendMessage(GetHwnd(), BM_SETCHECK, (WPARAM) state, 0);
    else // owner drawn buttons don't react to this message
        Refresh();
}

wxCheckBoxState wxCheckBox::DoGet3StateValue() const
{
    return m_state;
}

bool wxCheckBox::MSWCommand(WXUINT cmd, WXWORD WXUNUSED(id))
{
    if ( cmd != BN_CLICKED && cmd != BN_DBLCLK )
        return false;

    // first update the value so that user event handler gets the new checkbox
    // value

    // ownerdrawn buttons don't manage their state themselves unlike usual
    // auto checkboxes so do it ourselves in any case
    wxCheckBoxState state;
    if ( Is3rdStateAllowedForUser() )
    {
        state = (wxCheckBoxState)((m_state + 1) % 3);
    }
    else // 2 state checkbox (at least from users point of view)
    {
        // note that wxCHK_UNDETERMINED also becomes unchecked when clicked
        state = m_state == wxCHK_UNCHECKED ? wxCHK_CHECKED : wxCHK_UNCHECKED;
    }

    DoSet3StateValue(state);


    // generate the event
    wxCommandEvent event(wxEVT_CHECKBOX, m_windowId);

    event.SetInt(state);
    event.SetEventObject(this);
    ProcessCommand(event);

    return true;
}

// ----------------------------------------------------------------------------
// owner drawn checkboxes support
// ----------------------------------------------------------------------------

int wxCheckBox::MSWGetButtonStyle() const
{
    return HasFlag(wxCHK_3STATE) ? BS_3STATE : BS_CHECKBOX;
}

void wxCheckBox::MSWOnButtonResetOwnerDrawn()
{
    // ensure that controls state is consistent with internal state
    DoSet3StateValue(m_state);
}

int wxCheckBox::MSWGetButtonCheckedFlag() const
{
    switch ( Get3StateValue() )
    {
        case wxCHK_CHECKED:
            return wxCONTROL_CHECKED;

        case wxCHK_UNDETERMINED:
            return wxCONTROL_UNDETERMINED;

        case wxCHK_UNCHECKED:
            // no extra styles needed
            return wxCONTROL_NONE;
    }

    wxFAIL_MSG( wxT("unexpected Get3StateValue() return value") );

    return 0;
}

void wxCheckBox::MSWDrawButtonBitmap(wxDC& dc, const wxRect& rect, int flags)
{
    wxRendererNative::Get().DrawCheckBox(this, dc, rect, flags);
}

#if wxUSE_ACCESSIBILITY

namespace
{

// When the checkbox is owner-drawn (e.g. in dark mode), the native BS_CHECKBOX
// style is replaced with BS_OWNERDRAW, causing the standard Windows accessible
// object to report the control as a generic button. This custom accessible
// ensures the correct role and checked state are always reported.
class wxCheckBoxAccessible
    : public wxOwnerDrawnAccessible<wxCheckBox, wxROLE_SYSTEM_CHECKBUTTON>
{
public:
    explicit wxCheckBoxAccessible(wxCheckBox* win)
        : wxOwnerDrawnAccessible(win)
    {
    }

protected:
    long MSWGetCheckedState(wxCheckBox* cb) const override
    {
        switch ( cb->Get3StateValue() )
        {
            case wxCHK_CHECKED:
                return wxACC_STATE_SYSTEM_CHECKED;

            case wxCHK_UNDETERMINED:
                return wxACC_STATE_SYSTEM_MIXED;

            case wxCHK_UNCHECKED:
                return 0;
        }

        wxFAIL_MSG( wxT("unexpected Get3StateValue() return value") );

        return 0;
    }
};

} // anonymous namespace

wxAccessible* wxCheckBox::CreateAccessible()
{
    return new wxCheckBoxAccessible(this);
}

#endif // wxUSE_ACCESSIBILITY

#endif // wxUSE_CHECKBOX
