///////////////////////////////////////////////////////////////////////////////
// Name:        src/msw/checklst.cpp
// Purpose:     implementation of wxCheckListBox class
// Author:      Vadim Zeitlin
// Created:     16.11.97
// Copyright:   (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
// Licence:     wxWindows licence
///////////////////////////////////////////////////////////////////////////////

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

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

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


#if wxUSE_CHECKLISTBOX && wxUSE_OWNER_DRAWN

#include "wx/checklst.h"

#ifndef WX_PRECOMP
    #include "wx/msw/wrapcctl.h"
    #include "wx/object.h"
    #include "wx/colour.h"
    #include "wx/font.h"
    #include "wx/bitmap.h"
    #include "wx/window.h"
    #include "wx/listbox.h"
    #include "wx/dcmemory.h"
    #include "wx/settings.h"
    #include "wx/log.h"
#endif

#include "wx/msw/private/listboxitem.h"

#include <windowsx.h>

#include "wx/renderer.h"
#include "wx/msw/private.h"
#include "wx/msw/dc.h"

// ----------------------------------------------------------------------------
// private functions
// ----------------------------------------------------------------------------

// get item (converted to right type)
#define GetItem(n)    ((wxCheckListBoxItem *)(GetItem(n)))

namespace
{
    // space around check mark bitmap in pixels
    static const int CHECKMARK_EXTRA_SPACE = 1;

    // space between check bitmap and text label
    static const int CHECKMARK_LABEL_SPACE = 2;

} // anonymous namespace

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

// ----------------------------------------------------------------------------
// declaration and implementation of wxCheckListBoxItem class
// ----------------------------------------------------------------------------

class wxCheckListBoxItem : public wxListBoxItemBase<wxCheckListBox>
{
public:
    wxCheckListBoxItem(wxCheckListBox *parent);

    // drawing functions
    virtual bool OnDrawItem(wxDC& dc, const wxRect& rc, wxODAction act, wxODStatus stat) override;

    bool IsChecked() const
        { return m_checked; }

    void Check(bool bCheck)
        { m_checked = bCheck; }

    void Toggle()
        { Check(!IsChecked()); }

protected:
    virtual int MSWGetTextType() const override
    {
        // Don't handle mnemonics in the label specially, they don't make sense
        // for the listbox items that can't be activated from keyboard using
        // them.
        return DST_TEXT;
    }

private:
    bool m_checked;

    wxDECLARE_NO_COPY_CLASS(wxCheckListBoxItem);
};

wxCheckListBoxItem::wxCheckListBoxItem(wxCheckListBox *parent)
    : wxListBoxItemBase<wxCheckListBox>(parent)
{
    m_checked = false;

    wxSize size = wxRendererNative::Get().GetCheckBoxSize(parent);
    size.x += 2 * CHECKMARK_EXTRA_SPACE + CHECKMARK_LABEL_SPACE;

    SetMarginWidth(size.GetWidth());
}

bool wxCheckListBoxItem::OnDrawItem(wxDC& dc, const wxRect& rc,
                                    wxODAction act, wxODStatus stat)
{
    // first draw the label
    if ( !wxOwnerDrawn::OnDrawItem(dc, rc, act, stat) )
        return false;

    // now draw the check mark part
    wxMSWDCImpl *impl = (wxMSWDCImpl*) dc.GetImpl();
    HDC hdc = GetHdcOf(*impl);

    wxSize size = wxRendererNative::Get().GetCheckBoxSize(GetParent());

    // first create bitmap in a memory DC
    MemoryHDC hdcMem(hdc);
    CompatibleBitmap hBmpCheck(hdc, size.GetWidth(), size.GetHeight());

    // then draw a check mark into it
    {
        SelectInHDC selBmp(hdcMem, hBmpCheck);

        int flags = wxCONTROL_FLAT;
        if ( IsChecked() )
            flags |= wxCONTROL_CHECKED;

        wxDCTemp dcMem(hdcMem);
        wxRendererNative::Get().DrawCheckBox(GetParent(), dcMem, wxRect(size), flags);
    } // select hBmpCheck out of hdcMem

    // finally draw bitmap to screen

    // position of check mark bitmap
    int x = rc.GetX() + CHECKMARK_EXTRA_SPACE;
    int y = rc.GetY() + (rc.GetHeight() - size.GetHeight()) / 2;

    UINT uState = stat & wxOwnerDrawn::wxODSelected ? wxDSB_SELECTED : wxDSB_NORMAL;

    // checkmarks should not be mirrored in RTL layout
    DWORD oldLayout = ::GetLayout(hdc);
    if ( oldLayout & LAYOUT_RTL )
        ::SetLayout(hdc, oldLayout | LAYOUT_BITMAPORIENTATIONPRESERVED);
    wxDrawStateBitmap(hdc, hBmpCheck, x, y, uState);
    if ( oldLayout & LAYOUT_RTL )
        ::SetLayout(hdc, oldLayout);

    return true;
}

#if wxUSE_ACCESSIBILITY

namespace
{

// ----------------------------------------------------------------------------
// declaration and implementation of wxCheckListBoxAccessible class
// ----------------------------------------------------------------------------

class wxCheckListBoxAccessible : public wxWindowAccessible
{
public:
    explicit wxCheckListBoxAccessible(wxCheckListBox* win);

    virtual wxAccStatus GetRole(int childId, wxAccRole* role) override;
    virtual wxAccStatus GetState(int childId, long* state) override;
};

wxCheckListBoxAccessible::wxCheckListBoxAccessible(wxCheckListBox* win)
    : wxWindowAccessible(win)
{
}

wxAccStatus wxCheckListBoxAccessible::GetRole(int childId, wxAccRole* role)
{
    wxCheckListBox* checkListBox = wxDynamicCast(GetWindow(), wxCheckListBox);
    wxCHECK(checkListBox, wxACC_FAIL);

    *role = childId == wxACC_SELF ? wxROLE_SYSTEM_LIST : wxROLE_SYSTEM_CHECKBUTTON;
    return wxACC_OK;
}

wxAccStatus wxCheckListBoxAccessible::GetState(int childId, long* state)
{
    wxCheckListBox* checkListBox = wxDynamicCast(GetWindow(), wxCheckListBox);
    wxCHECK(checkListBox, wxACC_FAIL);

    if ( childId <= 0 )
    {
        // Fall back to Windows default
        return wxACC_NOT_IMPLEMENTED;
    }

    long st = 0;
    if ( !checkListBox->IsEnabled() )
        st |= wxACC_STATE_SYSTEM_UNAVAILABLE;
    if ( !checkListBox->IsShown() )
        st |= wxACC_STATE_SYSTEM_INVISIBLE;

    if ( checkListBox->IsFocusable() )
        st |= wxACC_STATE_SYSTEM_FOCUSABLE;

    // This needs to be returned for the child to be recognized as focused.
    if ( checkListBox->HasFocus() )
        st |= wxACC_STATE_SYSTEM_FOCUSED;

    // Note that child IDs are 1-based.
    if ( checkListBox->IsChecked(childId - 1) )
        st |= wxACC_STATE_SYSTEM_CHECKED;

    *state = st;

    return wxACC_OK;
}

} // anonymous namespace

#endif // wxUSE_ACCESSIBILITY

// ----------------------------------------------------------------------------
// implementation of wxCheckListBox class
// ----------------------------------------------------------------------------

// define event table
// ------------------
wxBEGIN_EVENT_TABLE(wxCheckListBox, wxListBox)
  EVT_KEY_DOWN(wxCheckListBox::OnKeyDown)
  EVT_LEFT_DOWN(wxCheckListBox::OnLeftClick)
wxEND_EVENT_TABLE()

// control creation
// ----------------

// def ctor: use Create() to really create the control
wxCheckListBox::wxCheckListBox()
{
}

// ctor which creates the associated control
wxCheckListBox::wxCheckListBox(wxWindow *parent, wxWindowID id,
                               const wxPoint& pos, const wxSize& size,
                               int nStrings, const wxString choices[],
                               long style, const wxValidator& val,
                               const wxString& name)
{
    Create(parent, id, pos, size, nStrings, choices, style, val, name);
}

wxCheckListBox::wxCheckListBox(wxWindow *parent, wxWindowID id,
                               const wxPoint& pos, const wxSize& size,
                               const wxArrayString& choices,
                               long style, const wxValidator& val,
                               const wxString& name)
{
    Create(parent, id, pos, size, choices, style, val, name);
}

bool wxCheckListBox::Create(wxWindow *parent, wxWindowID id,
                            const wxPoint& pos, const wxSize& size,
                            int n, const wxString choices[],
                            long style,
                            const wxValidator& validator, const wxString& name)
{
    return wxListBox::Create(parent, id, pos, size, n, choices,
                             style | wxLB_OWNERDRAW, validator, name);
}

bool wxCheckListBox::Create(wxWindow *parent, wxWindowID id,
                            const wxPoint& pos, const wxSize& size,
                            const wxArrayString& choices,
                            long style,
                            const wxValidator& validator, const wxString& name)
{
    return wxListBox::Create(parent, id, pos, size, choices,
                             style | wxLB_OWNERDRAW, validator, name);
}

// create/retrieve item
// --------------------

// create a check list box item
wxOwnerDrawn *wxCheckListBox::CreateLboxItem(size_t WXUNUSED(n))
{
    wxCheckListBoxItem *pItem = new wxCheckListBoxItem(this);
    return pItem;
}

// return item size
// ----------------
bool wxCheckListBox::MSWOnMeasure(WXMEASUREITEMSTRUCT *item)
{
    if ( wxListBox::MSWOnMeasure(item) )
    {
        MEASUREITEMSTRUCT *pStruct = (MEASUREITEMSTRUCT *)item;

        const wxSize size = MSWGetFullItemSize(pStruct->itemWidth,
                                                 pStruct->itemHeight);
        pStruct->itemWidth = size.x;
        pStruct->itemHeight = size.y;

        return true;
    }

    return false;
}

void wxCheckListBox::MSWUpdateFontOnDPIChange(const wxSize& newDPI)
{
    wxCheckListBoxBase::MSWUpdateFontOnDPIChange(newDPI);

    wxSize size = wxRendererNative::Get().GetCheckBoxSize(this);
    size.x += 2 * CHECKMARK_EXTRA_SPACE + CHECKMARK_LABEL_SPACE;

    for ( unsigned int i = 0; i < GetCount(); ++i )
    {
        GetItem(i)->SetMarginWidth(size.GetWidth());
    }
}

// check items
// -----------

bool wxCheckListBox::IsChecked(unsigned int uiIndex) const
{
    wxCHECK_MSG( IsValid(uiIndex), false, wxT("bad wxCheckListBox index") );

    return GetItem(uiIndex)->IsChecked();
}

void wxCheckListBox::Check(unsigned int uiIndex, bool bCheck)
{
    wxCHECK_RET( IsValid(uiIndex), wxT("bad wxCheckListBox index") );

    GetItem(uiIndex)->Check(bCheck);
    RefreshItem(uiIndex);
#if wxUSE_ACCESSIBILITY
    wxAccessible::NotifyEvent(wxACC_EVENT_OBJECT_STATECHANGE, this, wxOBJID_CLIENT, uiIndex + 1);
#endif // wxUSE_ACCESSIBILITY

}

void wxCheckListBox::Toggle(unsigned int uiIndex)
{
    wxCHECK_RET( IsValid(uiIndex), wxT("bad wxCheckListBox index") );

    GetItem(uiIndex)->Toggle();
    RefreshItem(uiIndex);
#if wxUSE_ACCESSIBILITY
    wxAccessible::NotifyEvent(wxACC_EVENT_OBJECT_STATECHANGE, this, wxOBJID_CLIENT, uiIndex + 1);
#endif // wxUSE_ACCESSIBILITY
}

// process events
// --------------

void wxCheckListBox::OnKeyDown(wxKeyEvent& event)
{
    // what do we do?
    enum
    {
        NONE,
        TOGGLE,
        SET,
        CLEAR
    } oper;

    switch ( event.GetKeyCode() )
    {
        case WXK_SPACE:
            oper = TOGGLE;
            break;

        case WXK_NUMPAD_ADD:
        case '+':
            oper = SET;
            break;

        case WXK_NUMPAD_SUBTRACT:
        case '-':
            oper = CLEAR;
            break;

        default:
            oper = NONE;
    }

    if ( oper != NONE )
    {
        wxArrayInt selections;
        int count = 0;
        if ( HasMultipleSelection() )
        {
            count = GetSelections(selections);
        }
        else
        {
            int sel = GetSelection();
            if (sel != -1)
            {
                count = 1;
                selections.Add(sel);
            }
        }

        for ( int i = 0; i < count; i++ )
        {
            int nItem = selections[i];

            switch ( oper )
            {
                case TOGGLE:
                    Toggle(nItem);
                    break;

                case SET:
                case CLEAR:
                    Check(nItem, oper == SET);
                    break;

                default:
                    wxFAIL_MSG( wxT("what should this key do?") );
            }

            // we should send an event as this has been done by the user and
            // not by the program
            SendEvent(nItem);
        }
    }
    else // nothing to do
    {
        event.Skip();
    }
}

void wxCheckListBox::OnLeftClick(wxMouseEvent& event)
{
    // clicking on the item selects it, clicking on the checkmark toggles

    int nItem = HitTest(event.GetX(), event.GetY());

    if ( nItem != wxNOT_FOUND )
    {
        wxRect rect;
        GetItemRect(nItem, rect);

        // convert item rect to check mark rect
        wxSize size = wxRendererNative::Get().GetCheckBoxSize(this);
        rect.x += CHECKMARK_EXTRA_SPACE;
        rect.y += (rect.GetHeight() - size.GetHeight()) / 2;
        rect.SetSize(size);

        if ( rect.Contains(event.GetX(), event.GetY()) )
        {
            // people expect to get "kill focus" event for the currently
            // focused control before getting events from the other controls
            // and, equally importantly, they may prevent the focus change from
            // taking place at all (e.g. because the old control contents is
            // invalid and needs to be corrected) in which case we shouldn't
            // generate this event at all
            SetFocus();
            if ( FindFocus() == this )
            {
                Toggle(nItem);
                SendEvent(nItem);

                // scroll one item down if the item is the last one
                // and isn't visible at all
                int h;
                GetClientSize(nullptr, &h);
                if ( rect.GetBottom() > h )
                    ScrollLines(1);
            }
        }
        else
        {
            // implement default behaviour: clicking on the item selects it
            event.Skip();
        }
    }
    else
    {
        // implement default behaviour on click outside of client zone
        event.Skip();
    }
}

wxSize wxCheckListBox::MSWGetFullItemSize(int w, int h) const
{
    wxSize size = wxRendererNative::Get().GetCheckBoxSize(const_cast<wxCheckListBox*>(this));
    size.x += 2 * CHECKMARK_EXTRA_SPACE;
    size.y += 2 * CHECKMARK_EXTRA_SPACE;

    w += size.GetWidth();
    if ( h < size.GetHeight() )
        h = size.GetHeight();

    return wxSize(w, h);
}

wxSize wxCheckListBox::DoGetBestClientSize() const
{
    wxSize best = wxListBox::DoGetBestClientSize();

    // add room for the checkbox
    return MSWGetFullItemSize(best.x, best.y);
}

#if wxUSE_ACCESSIBILITY

wxAccessible* wxCheckListBox::CreateAccessible()
{
    return new wxCheckListBoxAccessible(this);
}

#endif // wxUSE_ACCESSIBILITY

#endif // wxUSE_CHECKLISTBOX
