/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the Collabora Office project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <accessibility/accessibletablistbox.hxx>

#include <comphelper/types.hxx>
#include <vcl/toolkit/svtabbx.hxx>
#include <vcl/headbar.hxx>
#include <vcl/toolkit/svlbitm.hxx>
#include <vcl/toolkit/treelistbox.hxx>
#include <vcl/toolkit/treelistentry.hxx>
#include <com/sun/star/accessibility/AccessibleStateType.hpp>
#include <com/sun/star/accessibility/XAccessible.hpp>
#include <rtl/ustrbuf.hxx>
#include <sal/log.hxx>
#include <o3tl/safeint.hxx>
#include <o3tl/string_view.hxx>
#include <osl/diagnose.h>
#include <strings.hrc>
#include <svdata.hxx>
#include <memory>
#include <tools/json_writer.hxx>
#include <vcl/accessibility/AccessibleBrowseBoxCheckBoxCell.hxx>
#include <vcl/accessibility/AccessibleBrowseBoxHeaderBar.hxx>
#include <vcl/accessibility/AccessibleBrowseBoxHeaderCell.hxx>
#include <vcl/accessibility/AccessibleBrowseBoxTableCell.hxx>
#include <vcl/filter/PngImageWriter.hxx>

using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::accessibility;

static void lcl_DumpEntryAndSiblings(tools::JsonWriter& rJsonWriter,
                                     SvTreeListEntry* pEntry,
                                     SvTabListBox* pTabListBox,
                                     bool bCheckButtons)
{
    while (pEntry)
    {
        auto aNode = rJsonWriter.startStruct();

        // DEPRECATED
        // simple listbox value
        const SvLBoxItem* pIt = pEntry->GetFirstItem(SvLBoxItemType::String);
        if (pIt)
            rJsonWriter.put("text", static_cast<const SvLBoxString*>(pIt)->GetText());

        // column based data
        {
            auto aColumns = rJsonWriter.startArray("columns");

            for (size_t i = 0; i < pEntry->ItemCount(); i++)
            {
                SvLBoxItem& rItem = pEntry->GetItem(i);
                if (rItem.GetType() == SvLBoxItemType::String)
                {
                    const SvLBoxString* pStringItem = dynamic_cast<const SvLBoxString*>(&rItem);
                    if (pStringItem)
                    {
                        auto aColumn = rJsonWriter.startStruct();
                        rJsonWriter.put("text", pStringItem->GetText());

                        if (pTabListBox->IsInplaceEditingEnabled()) {
                            SvLBoxTab* pTab = pTabListBox->GetTab( pEntry, &rItem );
                            if ( pTab ) {
                                rJsonWriter.put("editable", pTab->IsEditable());
                            }
                        }

                        if (pStringItem->IsCustomRender())
                            rJsonWriter.put("customEntryRenderer", true);
                    }
                }
                else if (rItem.GetType() == SvLBoxItemType::ContextBmp)
                {
                    const SvLBoxContextBmp* pBmpItem = dynamic_cast<const SvLBoxContextBmp*>(&rItem);
                    if (pBmpItem)
                    {
                        const OUString aCollapsed = pBmpItem->GetBitmap1().GetStock();
                        const OUString aExpanded = pBmpItem->GetBitmap2().GetStock();

                        // send identifier only, we will use svg icon
                        if (!o3tl::trim(aCollapsed).empty() || !o3tl::trim(aExpanded).empty())
                        {
                            auto aColumn = rJsonWriter.startStruct();
                            if (!o3tl::trim(aCollapsed).empty())
                                rJsonWriter.put("collapsed", aCollapsed);
                            if (!o3tl::trim(aExpanded).empty())
                                rJsonWriter.put("expanded", aExpanded);
                        }
                        // custom bitmap - send png
                        else
                        {
                            Bitmap aCollapsedImage(pBmpItem->GetBitmap1().GetBitmap());
                            Bitmap aExpandedImage(pBmpItem->GetBitmap2().GetBitmap());
                            bool bHasCollapsed = !aCollapsedImage.IsEmpty() && !aCollapsedImage.GetSizePixel().IsEmpty();
                            bool bHasExpanded = !aExpandedImage.IsEmpty() && !aExpandedImage.GetSizePixel().IsEmpty();
                            if (bHasCollapsed || bHasExpanded)
                            {
                                auto aColumn = rJsonWriter.startStruct();
                                if (bHasCollapsed)
                                    rJsonWriter.put("collapsedimage",
                                                    vcl::encodeBitmapAsPngDataUri(aCollapsedImage));
                                if (bHasExpanded)
                                    rJsonWriter.put("collapsedimage",
                                                    vcl::encodeBitmapAsPngDataUri(aExpandedImage));
                            }
                        }
                    }
                }
            }
        }

        // SalInstanceTreeView does not use the flag CHILDREN_ON_DEMAND
        // and it creates a dummy child
        const SvTreeListEntries& rChildren = pEntry->GetChildEntries();
        if (rChildren.size() == 1)
        {
            auto& rChild = rChildren[0];
            if (const SvLBoxItem* pChild = rChild->GetFirstItem(SvLBoxItemType::String))
            {
                if (static_cast<const SvLBoxString*>(pChild)->GetText() == "<dummy>")
                    rJsonWriter.put("ondemand", true);
            }
        }
        if (rChildren.size() > 0 && !pTabListBox->IsExpanded(pEntry))
        {
            rJsonWriter.put("collapsed", true);
        }

        if (bCheckButtons)
        {
            SvButtonState eCheckState = pTabListBox->GetCheckButtonState(pEntry);
            if (eCheckState == SvButtonState::Unchecked)
                rJsonWriter.put("state", false);
            else if (eCheckState == SvButtonState::Checked)
                rJsonWriter.put("state", true);
            // Only carry "enabled" when this row actually has a check
            // button. The JSDialog tree treats enabled=false on the entry
            // as the whole row being disabled, so rows that simply have
            // no checkbox column would otherwise appear unselectable.
            if (pEntry->GetFirstItem(SvLBoxItemType::Button))
                rJsonWriter.put("enabled", pTabListBox->GetCheckButtonEnabled(pEntry));
        }

        if (pTabListBox->IsSelected(pEntry))
            rJsonWriter.put("selected", true);

        rJsonWriter.put("row", pTabListBox->GetModel()->GetAbsPos(pEntry));

        SvTreeListEntry* pChild = pTabListBox->FirstChild(pEntry);
        if (pChild)
        {
            auto childrenNode = rJsonWriter.startArray("children");
            lcl_DumpEntryAndSiblings(rJsonWriter, pChild, pTabListBox, bCheckButtons);
        }

        pEntry = pEntry->NextSibling();
    }
}

void SvTabListBox::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
{
    SvTreeListBox::DumpAsPropertyTree(rJsonWriter);

    rJsonWriter.put("singleclickactivate", GetActivateOnSingleClick());

    switch (m_eRole)
    {
        case SvTabListBoxRole::Unknown:
            assert(false && "this shouldn't be possible on load from .ui");
            break;
        case SvTabListBoxRole::Tree:
            rJsonWriter.put("role", "tree");
            break;
        case SvTabListBoxRole::TreeGrid:
            rJsonWriter.put("role", "treegrid");
            break;
        case SvTabListBoxRole::ListBox:
            rJsonWriter.put("role", "listbox");
            break;
        case SvTabListBoxRole::Grid:
            rJsonWriter.put("role", "grid");
            break;
    }

    bool bCheckButtons = static_cast<int>(nTreeFlags & SvTreeFlags::CHKBTN);

    bool isRadioButton = false;
    if (pCheckButtonData)
    {
        isRadioButton = pCheckButtonData -> IsRadio();
    }

    OUString checkboxtype;
    if (bCheckButtons)
    {
        checkboxtype = u"checkbox"_ustr;
        if(isRadioButton)
        {
            checkboxtype = u"radio"_ustr;
        }
    }

    rJsonWriter.put("checkboxtype", checkboxtype);
    if (GetCustomEntryRenderer())
        rJsonWriter.put("customEntryRenderer", true);

    if (!m_aColumnTitles.empty())
    {
        auto aHeaders = rJsonWriter.startArray("headers");
        for (size_t i = 0; i < m_aColumnTitles.size(); ++i)
        {
            auto aNode = rJsonWriter.startStruct();
            rJsonWriter.put("text", m_aColumnTitles[i]);
            // Pure-label headers (e.g. chart Data Table) are not clickable;
            // emit sortable=false so the client renders a plain label rather
            // than a focusable, do-nothing button.
            if (!m_bHeadersSortable)
                rJsonWriter.put("sortable", false);
            if (i < m_aColumnHeaderNames.size() && !m_aColumnHeaderNames[i].isEmpty())
            {
                rJsonWriter.put("headerName", m_aColumnHeaderNames[i]);
                // Accessible name for the editable header-name input.
                if (!m_sHeaderNameAriaLabel.isEmpty())
                    rJsonWriter.put("headerNameAriaLabel", m_sHeaderNameAriaLabel);
            }
            if (i < m_aColumnColors.size() && m_aColumnColors[i] != COL_AUTO)
            {
                rJsonWriter.put("color", m_aColumnColors[i].AsRGBHexString());
            }
        }
    }

    auto entriesNode = rJsonWriter.startArray("entries");
    lcl_DumpEntryAndSiblings(rJsonWriter, First(), this, bCheckButtons);
}

// SvTreeListBox callback

void SvTabListBox::SetTabs()
{
    SvTreeListBox::SetTabs();
    if( mvTabList.empty() )
        return;

    // The tree listbox has now inserted its tabs into the list. Now we
    // fluff up the list with additional tabs and adjust the rightmost tab
    // of the tree listbox.

    // the 1st column (index 1 or 2 depending on button flags) is always set
    // editable by SvTreeListBox::SetTabs(),
    // which prevents setting a different column to editable as the first
    // one with the flag is picked in SvTreeListBox::ImplEditEntry()
    assert(aTabs.back()->nFlags & SvLBoxTabFlags::EDITABLE);
    if (!(mvTabList[0].nFlags & SvLBoxTabFlags::EDITABLE))
    {
        aTabs.back()->nFlags &= ~SvLBoxTabFlags::EDITABLE;
    }

    // append all other tabs to the list
    for( sal_uInt16 nCurTab = 1; nCurTab < sal_uInt16(mvTabList.size()); nCurTab++ )
    {
        SvLBoxTab& rTab = mvTabList[nCurTab];
        AddTab( rTab.GetPos(), rTab.nFlags );
    }
}

void SvTabListBox::InitEntry(SvTreeListEntry* pEntry, const OUString& rStr,
    const Image& rColl, const Image& rExp)
{
    SvTreeListBox::InitEntry(pEntry, rStr, rColl, rExp);

    sal_Int32 nIndex = 0;
    // TODO: verify if nTabCount is always >0 here!
    const sal_uInt16 nCount = mvTabList.size() - 1;
    for( sal_uInt16 nToken = 0; nToken < nCount; nToken++ )
    {
        const std::u16string_view aToken = GetToken(aCurEntry, nIndex);
        pEntry->AddItem(std::make_unique<SvLBoxString>(OUString(aToken)));
    }
}

SvTabListBox::SvTabListBox( vcl::Window* pParent, WinBits nBits )
    : SvTreeListBox( pParent, nBits )
    , m_eRole(SvTabListBoxRole::Unknown)
    , m_bHeadersSortable(true)
{
    SetHighlightRange();    // select full width
}

SvTabListBox::~SvTabListBox()
{
    disposeOnce();
}

void SvTabListBox::dispose()
{
    mvTabList.clear();
    SvTreeListBox::dispose();
}

void SvTabListBox::SetTabs(const std::vector<tools::Long>& rTabPositions, MapUnit eMapUnit)
{
    assert(!rTabPositions.empty());
    mvTabList.resize(rTabPositions.size());

    MapMode aMMSource( eMapUnit );
    MapMode aMMDest( MapUnit::MapPixel );

    for( sal_uInt16 nIdx = 0; nIdx < sal_uInt16(mvTabList.size()); nIdx++)
    {
        Size aSize(rTabPositions.at(nIdx), 0);
        aSize = LogicToLogic( aSize, &aMMSource, &aMMDest );
        tools::Long nNewTab = aSize.Width();
        mvTabList[nIdx].SetPos( nNewTab );
    }
    SvTreeListBox::nTreeFlags |= SvTreeFlags::RECALCTABS;
    if( IsUpdateMode() )
        Invalidate();
}

SvTreeListEntry* SvTabListBox::InsertEntry( const OUString& rText, SvTreeListEntry* pParent,
                                        bool /*bChildrenOnDemand*/,
                                        sal_uInt32 nPos, void* pUserData )
{
    return InsertEntryToColumn( rText, pParent, nPos, 0xffff, pUserData );
}

SvTreeListEntry* SvTabListBox::InsertEntryToColumn(const OUString& rStr,SvTreeListEntry* pParent,sal_uInt32 nPos,sal_uInt16 nCol,
    void* pUser )
{
    OUString aStr;
    if( nCol != 0xffff )
    {
        while( nCol )
        {
            aStr += "\t";
            nCol--;
        }
    }
    aStr += rStr;
    OUString aFirstStr( aStr );
    sal_Int32 nEnd = aFirstStr.indexOf( '\t' );
    if( nEnd != -1 )
    {
        aFirstStr = aFirstStr.copy(0, nEnd);
        aCurEntry = aStr.copy(++nEnd);
    }
    else
        aCurEntry.clear();
    return SvTreeListBox::InsertEntry( aFirstStr, pParent, false, nPos, pUser );
}

OUString SvTabListBox::GetEntryText( SvTreeListEntry* pEntry ) const
{
    return GetEntryText( pEntry, 0xffff );
}

OUString SvTabListBox::GetEntryText( const SvTreeListEntry* pEntry, sal_uInt16 nCol )
{
    DBG_ASSERT(pEntry,"GetEntryText:Invalid Entry");
    OUStringBuffer aResult;
    if( pEntry )
    {
        sal_uInt16 nCount = pEntry->ItemCount();
        sal_uInt16 nCur = 0;
        while( nCur < nCount )
        {
            const SvLBoxItem& rStr = pEntry->GetItem( nCur );
            if (rStr.GetType() == SvLBoxItemType::String)
            {
                if( nCol == 0xffff )
                {
                    if (!aResult.isEmpty())
                        aResult.append("\t");
                    aResult.append(static_cast<const SvLBoxString&>(rStr).GetText());
                }
                else
                {
                    if( nCol == 0 )
                        return static_cast<const SvLBoxString&>(rStr).GetText();
                    nCol--;
                }
            }
            nCur++;
        }
    }
    return aResult.makeStringAndClear();
}

OUString SvTabListBox::GetEntryText( sal_uInt32 nPos, sal_uInt16 nCol ) const
{
    SvTreeListEntry* pEntry = GetEntryOnPos( nPos );
    return GetEntryText( pEntry, nCol );
}

OUString SvTabListBox::GetCellText( sal_uInt32 nPos, sal_uInt16 nCol ) const
{
    SvTreeListEntry* pEntry = GetEntryOnPos( nPos );
    DBG_ASSERT( pEntry, "SvTabListBox::GetCellText(): Invalid Entry" );
    OUString aResult;
    if (pEntry && pEntry->ItemCount() > o3tl::make_unsigned(nCol+1))
    {
        const SvLBoxItem& rStr = pEntry->GetItem( nCol + 1 );
        if (rStr.GetType() == SvLBoxItemType::String)
            aResult = static_cast<const SvLBoxString&>(rStr).GetText();
    }
    return aResult;
}

sal_uInt32 SvTabListBox::GetEntryPos( const SvTreeListEntry* pEntry ) const
{
    sal_uInt32 nPos = 0;
    SvTreeListEntry* pTmpEntry = First();
    while( pTmpEntry )
    {
        if ( pTmpEntry == pEntry )
            return nPos;
        pTmpEntry = Next( pTmpEntry );
        ++nPos;
    }
    return 0xffffffff;
}

// static
std::u16string_view SvTabListBox::GetToken( std::u16string_view sStr, sal_Int32& nIndex )
{
    return o3tl::getToken(sStr, 0, '\t', nIndex);
}

OUString SvTabListBox::GetTabEntryText( sal_uInt32 nPos, sal_uInt16 nCol ) const
{
    SvTreeListEntry* pEntry = GetEntryOnPos( nPos );
    DBG_ASSERT( pEntry, "GetTabEntryText(): Invalid entry " );
    OUStringBuffer aResult;
    if ( pEntry )
    {
        sal_uInt16 nCount = pEntry->ItemCount();
        sal_uInt16 nCur = 0;
        while( nCur < nCount )
        {
            const SvLBoxItem& rBoxItem = pEntry->GetItem( nCur );
            if (rBoxItem.GetType() == SvLBoxItemType::String)
            {
                if ( nCol == 0xffff )
                {
                    if (!aResult.isEmpty())
                        aResult.append("\t");
                    aResult.append(static_cast<const SvLBoxString&>(rBoxItem).GetText());
                }
                else
                {
                    if ( nCol == 0 )
                    {
                        OUString sRet = static_cast<const SvLBoxString&>(rBoxItem).GetText();
                        if ( sRet.isEmpty() )
                            sRet = VclResId( STR_SVT_ACC_EMPTY_FIELD );
                        return sRet;
                    }
                    --nCol;
                }
            }
            ++nCur;
        }
    }
    return aResult.makeStringAndClear();
}

SvTreeListEntry* SvTabListBox::GetEntryOnPos( sal_uInt32 _nEntryPos ) const
{
    SvTreeListEntry* pEntry = nullptr;
    sal_uInt32 i, nPos = 0, nCount = GetLevelChildCount( nullptr );
    for ( i = 0; i < nCount; ++i )
    {
        SvTreeListEntry* pParent = GetEntry(i);
        if ( nPos == _nEntryPos )
        {
            pEntry = pParent;
            break;
        }
        else
        {
            nPos++;
            pEntry = GetChildOnPos( pParent, _nEntryPos, nPos );
            if ( pEntry )
                break;
        }
    }

    return pEntry;
}

SvTreeListEntry* SvTabListBox::GetChildOnPos( SvTreeListEntry* _pParent, sal_uInt32 _nEntryPos, sal_uInt32& _rPos ) const
{
    sal_uInt32 i, nCount = GetLevelChildCount( _pParent );
    for ( i = 0; i < nCount; ++i )
    {
        SvTreeListEntry* pParent = GetEntry( _pParent, i );
        if ( _rPos == _nEntryPos )
            return pParent;
        else
        {
            _rPos++;
            SvTreeListEntry* pEntry = GetChildOnPos( pParent, _nEntryPos, _rPos );
            if ( pEntry )
                return pEntry;
        }
    }

    return nullptr;
}

void SvTabListBox::SetTabAlignCenter(sal_uInt16 nTab)
{
    DBG_ASSERT(nTab<mvTabList.size(),"GetTabPos:Invalid Tab");
    if( nTab >= mvTabList.size() )
        return;
    SvLBoxTab& rTab = mvTabList[ nTab ];
    SvLBoxTabFlags nFlags = rTab.nFlags;
    nFlags &= ~SvLBoxTabFlags::ADJUST_FLAGS;
    // see SvLBoxTab::CalcOffset for force, which only matters for centering
    nFlags |= SvLBoxTabFlags::ADJUST_CENTER | SvLBoxTabFlags::FORCE;
    rTab.nFlags = nFlags;
    SvTreeListBox::nTreeFlags |= SvTreeFlags::RECALCTABS;
    if( IsUpdateMode() )
        Invalidate();
}

void SvTabListBox::SetTabEditable(sal_uInt16 nTab, bool bEditable)
{
    DBG_ASSERT(nTab<mvTabList.size(),"GetTabPos:Invalid Tab");
    if( nTab >= mvTabList.size() )
        return;
    SvLBoxTab& rTab = mvTabList[ nTab ];
    if (bEditable)
        rTab.nFlags |= SvLBoxTabFlags::EDITABLE;
    else
        rTab.nFlags &= ~SvLBoxTabFlags::EDITABLE;
}

void SvTabListBox::SetColumnTitle(sal_uInt16 nCol, const OUString& rTitle)
{
    if (nCol >= m_aColumnTitles.size())
        m_aColumnTitles.resize(nCol + 1);
    m_aColumnTitles[nCol] = rTitle;
}

void SvTabListBox::SetColumnHeaderName(sal_uInt16 nCol, const OUString& rName)
{
    if (nCol >= m_aColumnHeaderNames.size())
        m_aColumnHeaderNames.resize(nCol + 1);
    m_aColumnHeaderNames[nCol] = rName;
}

void SvTabListBox::SetColumnCount(sal_uInt16 nCount)
{
    if (m_aColumnTitles.size() > nCount)
        m_aColumnTitles.resize(nCount);
    if (m_aColumnColors.size() > nCount)
        m_aColumnColors.resize(nCount);
    if (m_aColumnHeaderNames.size() > nCount)
        m_aColumnHeaderNames.resize(nCount);
}

void SvTabListBox::SetColumnColor(sal_uInt16 nCol, const Color& rColor)
{
    if (nCol >= m_aColumnColors.size())
        m_aColumnColors.resize(nCol + 1, COL_AUTO);
    m_aColumnColors[nCol] = rColor;
}

const OUString & SvTabListBox::GetColumnTitle(sal_uInt16 nCol) const
{
    if (nCol < m_aColumnTitles.size())
        return m_aColumnTitles[nCol];
    return EMPTY_OUSTRING;
}

tools::Long SvTabListBox::GetLogicTab( sal_uInt16 nTab )
{
    if( SvTreeListBox::nTreeFlags & SvTreeFlags::RECALCTABS )
        SetTabs();

    DBG_ASSERT(nTab<mvTabList.size(),"GetTabPos:Invalid Tab");
    return aTabs[ nTab ]->GetPos();
}

SvHeaderTabListBox::SvHeaderTabListBox(vcl::Window* pParent, WinBits nWinStyle, HeaderBar* pHeaderBar)
    : SvTabListBox(pParent, nWinStyle)
    , m_bFirstPaint(true)
{

    assert(pHeaderBar);
    m_xHeaderBar = pHeaderBar;
    SetScrolledHdl(LINK(this, SvHeaderTabListBox, ScrollHdl_Impl));
    m_xHeaderBar->SetCreateAccessibleHdl(LINK(this, SvHeaderTabListBox, CreateAccessibleHdl_Impl));
}

SvHeaderTabListBox::~SvHeaderTabListBox()
{
    disposeOnce();
}

void SvHeaderTabListBox::dispose()
{
    for (rtl::Reference<AccessibleBrowseBoxHeaderCell>& rxChild : m_aAccessibleChildren)
        comphelper::disposeComponent(rxChild);
    m_aAccessibleChildren.clear();
    m_xAccessible.clear();

    m_xHeaderBar.reset();
    SvTabListBox::dispose();
}

void SvHeaderTabListBox::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect )
{
    if (m_bFirstPaint)
    {
        m_bFirstPaint = false;
    }
    SvTabListBox::Paint(rRenderContext, rRect);
}

HeaderBar* SvHeaderTabListBox::GetHeaderBar()
{
    return m_xHeaderBar;
}

bool SvHeaderTabListBox::IsItemChecked( SvTreeListEntry* pEntry, sal_uInt16 nCol )
{
    SvButtonState eState = SvButtonState::Unchecked;
    SvLBoxButton& rItem = static_cast<SvLBoxButton&>( pEntry->GetItem( nCol + 1 ) );

    if (rItem.GetType() == SvLBoxItemType::Button)
    {
        SvItemStateFlags nButtonFlags = rItem.GetButtonFlags();
        eState = SvLBoxButtonData::ConvertToButtonState( nButtonFlags );
    }

    return ( eState == SvButtonState::Checked );
}

SvTreeListEntry* SvHeaderTabListBox::InsertEntryToColumn(
    const OUString& rStr, SvTreeListEntry* pParent, sal_uInt32 nPos, sal_uInt16 nCol, void* pUserData )
{
    SvTreeListEntry* pEntry = SvTabListBox::InsertEntryToColumn( rStr, pParent, nPos, nCol, pUserData );
    RecalculateAccessibleChildren();
    return pEntry;
}

sal_uInt32 SvHeaderTabListBox::Insert(
    SvTreeListEntry* pEnt, SvTreeListEntry* pPar, sal_uInt32 nPos )
{
    sal_uInt32 n = SvTabListBox::Insert( pEnt, pPar, nPos );
    RecalculateAccessibleChildren();
    return n;
}

sal_uInt32 SvHeaderTabListBox::Insert( SvTreeListEntry* pEntry, sal_uInt32 nRootPos )
{
    sal_uInt32 nPos = SvTabListBox::Insert( pEntry, nRootPos );
    RecalculateAccessibleChildren();
    return nPos;
}

void SvHeaderTabListBox::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
{
    SvTabListBox::DumpAsPropertyTree(rJsonWriter);

    // If the base class already emitted "headers" from m_aColumnTitles, skip
    // the HeaderBar-based emission to avoid duplicate JSON keys.
    if (HasColumnTitles())
        return;

    auto aHeaders = rJsonWriter.startArray("headers");

    HeaderBar* pHeaderBar = GetHeaderBar();
    for(sal_uInt16 i = 0; i < pHeaderBar->GetItemCount(); i++)
    {
        auto aNode = rJsonWriter.startStruct();
        sal_uInt16 nItemId = pHeaderBar->GetItemId(i);
        HeaderBarItemBits eItemBits = pHeaderBar->GetItemBits(nItemId);
        rJsonWriter.put("text", pHeaderBar->GetItemText(nItemId));
        rJsonWriter.put("sortable", !!(eItemBits & HeaderBarItemBits::CLICKABLE));
        if (eItemBits & HeaderBarItemBits::UPARROW)
            rJsonWriter.put("arrow", "up");
        else if (eItemBits & HeaderBarItemBits::DOWNARROW)
            rJsonWriter.put("arrow", "down");
    }
}

IMPL_LINK_NOARG(SvHeaderTabListBox, ScrollHdl_Impl, SvTreeListBox*, void)
{
    m_xHeaderBar->SetOffset(-GetXOffset());
}

IMPL_LINK_NOARG(SvHeaderTabListBox, CreateAccessibleHdl_Impl, HeaderBar*, void)
{
    css::uno::Reference< XAccessible > xAccParent = m_xHeaderBar->GetAccessibleParent();
    if ( xAccParent.is() )
    {
        rtl::Reference<comphelper::OAccessible> pAccessible = new AccessibleBrowseBoxHeaderBar(
            xAccParent, *this, AccessibleBrowseBoxObjType::ColumnHeaderBar);
        m_xHeaderBar->SetAccessible(pAccessible);
    }
}

void SvHeaderTabListBox::RecalculateAccessibleChildren()
{
    if ( !m_aAccessibleChildren.empty() )
    {
        sal_uInt32 nCount = ( GetRowCount() + 1 ) * GetColumnCount();
        if ( m_aAccessibleChildren.size() < nCount )
            m_aAccessibleChildren.resize( nCount );
        else
        {
            DBG_ASSERT( m_aAccessibleChildren.size() == nCount, "wrong children count" );
        }
    }
}

bool SvHeaderTabListBox::IsCellCheckBox( sal_Int32 _nRow, sal_uInt16 _nColumn, TriState& _rState ) const
{
    bool bRet = false;
    SvTreeListEntry* pEntry = GetEntryOnPos( _nRow );
    if ( pEntry )
    {
        sal_uInt16 nItemCount = pEntry->ItemCount();
        if ( nItemCount > ( _nColumn + 1 ) )
        {
            SvLBoxItem& rItem = pEntry->GetItem( _nColumn + 1 );
            if (rItem.GetType() == SvLBoxItemType::Button)
            {
                bRet = true;
                _rState = ( ( static_cast<SvLBoxButton&>(rItem).GetButtonFlags() & SvItemStateFlags::UNCHECKED ) == SvItemStateFlags::NONE )
                            ? TRISTATE_TRUE : TRISTATE_FALSE;
            }
        }
        else
        {
            SAL_WARN( "svtools.contnr", "SvHeaderTabListBox::IsCellCheckBox(): column out of range" );
        }
    }
    return bRet;
}
sal_Int32 SvHeaderTabListBox::GetRowCount() const
{
    return GetEntryCount();
}

sal_uInt16 SvHeaderTabListBox::GetColumnCount() const
{
    return m_xHeaderBar->GetItemCount();
}

sal_Int32 SvHeaderTabListBox::GetCurrRow() const
{
    sal_Int32 nRet = -1;
    SvTreeListEntry* pEntry = GetCurEntry();
    if ( pEntry )
    {
        sal_uInt32 nCount = GetEntryCount();
        for ( sal_uInt32 i = 0; i < nCount; ++i )
        {
            if ( pEntry == GetEntryOnPos(i) )
            {
                nRet = i;
                break;
            }
        }
    }

    return nRet;
}

sal_uInt16 SvHeaderTabListBox::GetCurrColumn() const
{
    return 0;
}

OUString SvHeaderTabListBox::GetRowDescription( sal_Int32 _nRow ) const
{
    return GetEntryText( _nRow );
}

OUString SvHeaderTabListBox::GetColumnDescription( sal_uInt16 _nColumn ) const
{
    return m_xHeaderBar->GetItemText(m_xHeaderBar->GetItemId(_nColumn ));
}

bool SvHeaderTabListBox::HasRowHeader() const
{
    return false;
}

bool SvHeaderTabListBox::GoToCell( sal_Int32 /*_nRow*/, sal_uInt16 /*_nColumn*/ )
{
    return false;
}

void SvHeaderTabListBox::SetNoSelection()
{
    SvTreeListBox::SelectAll(false);
}

void SvHeaderTabListBox::SelectAll()
{
    SvTreeListBox::SelectAll(true);
}

void SvHeaderTabListBox::SelectRow( sal_Int32 _nRow, bool _bSelect, bool )
{
    Select( GetEntryOnPos( _nRow ), _bSelect );
}

void SvHeaderTabListBox::SelectColumn( sal_uInt16, bool )
{
}

sal_Int32 SvHeaderTabListBox::GetSelectedRowCount() const
{
    return GetSelectionCount();
}

sal_Int32 SvHeaderTabListBox::GetSelectedColumnCount() const
{
    return 0;
}

bool SvHeaderTabListBox::IsRowSelected( sal_Int32 _nRow ) const
{
    SvTreeListEntry* pEntry = GetEntryOnPos( _nRow );
    return ( pEntry && IsSelected( pEntry ) );
}

bool SvHeaderTabListBox::IsColumnSelected( sal_Int32 ) const
{
    return false;
}

void SvHeaderTabListBox::GetAllSelectedRows(cpo::uno::Sequence<sal_Int32 >& rRowIndices) const
{
    const sal_Int32 nCount = GetSelectedRowCount();
    rRowIndices.realloc(nCount);
    auto pRows = rRowIndices.getArray();
    SvTreeListEntry* pEntry = FirstSelected();
    sal_Int32 nIndex = 0;
    while (nIndex < nCount && pEntry)
    {
        pRows[nIndex] = GetEntryPos(pEntry);
        pEntry = NextSelected( pEntry );
        ++nIndex;
    }
    assert(nIndex == nCount && "Mismatch between GetSelectedRowCount() and count of selected rows when iterating.");
}

void SvHeaderTabListBox::GetAllSelectedColumns( cpo::uno::Sequence< sal_Int32 >& ) const
{
}

bool SvHeaderTabListBox::IsCellVisible( sal_Int32, sal_uInt16 ) const
{
    return true;
}

OUString SvHeaderTabListBox::GetAccessibleCellText( sal_Int32 _nRow, sal_uInt16 _nColumnPos ) const
{
    return GetTabEntryText(_nRow, _nColumnPos);
}

tools::Rectangle SvHeaderTabListBox::calcHeaderRect(bool _bIsColumnBar)
{
    tools::Rectangle aRect;
    if ( _bIsColumnBar )
    {
        vcl::Window* pParent = m_xHeaderBar->GetAccessibleParentWindow();
        assert(pParent);
        aRect = m_xHeaderBar->GetWindowExtentsRelative(*pParent);
    }
    return aRect;
}

tools::Rectangle SvHeaderTabListBox::calcTableRect()
{
    tools::Rectangle aScreenRect(GetWindowExtentsAbsolute());
    return tools::Rectangle(Point(0, 0), aScreenRect.GetSize());
}

tools::Rectangle SvHeaderTabListBox::calcFieldRectPixel(sal_Int32 _nRow, sal_uInt16 _nColumn, bool _bIsHeader)
{
    DBG_ASSERT( !_bIsHeader || 0 == _nRow, "invalid parameters" );
    tools::Rectangle aRect;
    SvTreeListEntry* pEntry = GetEntryOnPos(_nRow );
    if ( pEntry )
    {
        aRect = _bIsHeader ? calcHeaderRect(true) : GetBoundingRect(pEntry);
        Point aTopLeft = aRect.TopLeft();
        DBG_ASSERT(m_xHeaderBar->GetItemCount() > _nColumn, "invalid column");
        tools::Rectangle aItemRect = m_xHeaderBar->GetItemRect(m_xHeaderBar->GetItemId(_nColumn));
        aTopLeft.setX( aItemRect.Left() );
        Size aSize = aItemRect.GetSize();
        aRect = tools::Rectangle( aTopLeft, aSize );
    }

    return aRect;
}

rtl::Reference<comphelper::OAccessible>
SvHeaderTabListBox::CreateAccessibleCell(sal_Int32 _nRow, sal_uInt16 _nColumnPos)
{
    OSL_ENSURE(m_xAccessible.is(), "Invalid call: Accessible is null");

    rtl::Reference< AccessibleBrowseBoxCell > xChild;

    TriState eState = TRISTATE_INDET;
    bool bIsCheckBox = IsCellCheckBox( _nRow, _nColumnPos, eState );
    if ( bIsCheckBox )
        xChild = new AccessibleCheckBoxCell(m_xAccessible->getTable(), *this, _nRow, _nColumnPos,
                                            eState, false);
    else
        xChild = new AccessibleBrowseBoxTableCell(m_xAccessible->getTable(), *this, _nRow,
                                                  _nColumnPos);

    return xChild;
}

Reference< XAccessible > SvHeaderTabListBox::CreateAccessibleRowHeader( sal_Int32 )
{
    Reference< XAccessible > xHeader;
    return xHeader;
}

Reference< XAccessible > SvHeaderTabListBox::CreateAccessibleColumnHeader( sal_uInt16 _nColumn )
{
    // first call? -> initial list
    if ( m_aAccessibleChildren.empty() )
    {
        const sal_uInt16 nColumnCount = GetColumnCount();
        m_aAccessibleChildren.resize( nColumnCount );
    }

    // get header
    rtl::Reference< AccessibleBrowseBoxHeaderCell > xChild = m_aAccessibleChildren[ _nColumn ];
    // already exists?
    if (!xChild.is() && m_xAccessible.is())
    {
        // no -> create new header cell
        xChild = new AccessibleBrowseBoxHeaderCell(_nColumn, m_xAccessible->getHeaderBar(), *this,
                                                   AccessibleBrowseBoxObjType::ColumnHeaderCell);

        // insert into list
        m_aAccessibleChildren[ _nColumn ] = xChild;
    }
    return xChild;
}

sal_Int32 SvHeaderTabListBox::GetAccessibleControlCount() const
{
    return -1;
}

rtl::Reference<comphelper::OAccessible> SvHeaderTabListBox::CreateAccessibleControl(sal_Int32)
{
    return {};
}

bool SvHeaderTabListBox::ConvertPointToControlIndex( sal_Int32&, const Point& )
{
    return false;
}

bool SvHeaderTabListBox::ConvertPointToCellAddress( sal_Int32&, sal_uInt16&, const Point& )
{
    return false;
}

bool SvHeaderTabListBox::ConvertPointToRowHeader( sal_Int32&, const Point& )
{
    return false;
}

bool SvHeaderTabListBox::ConvertPointToColumnHeader( sal_uInt16&, const Point& )
{
    return false;
}

OUString SvHeaderTabListBox::GetAccessibleObjectName( AccessibleBrowseBoxObjType _eType, sal_Int32 _nPos ) const
{
    OUString aRetText;
    switch( _eType )
    {
        case AccessibleBrowseBoxObjType::BrowseBox:
        case AccessibleBrowseBoxObjType::Table:
        case AccessibleBrowseBoxObjType::ColumnHeaderBar:
            // should be empty now (see #i63983)
            aRetText.clear();
            break;

        case AccessibleBrowseBoxObjType::TableCell:
        {
            // here we need a valid pos, we can not handle -1
            if ( _nPos >= 0 )
            {
                sal_uInt16 nColumnCount = GetColumnCount();
                if (nColumnCount > 0)
                {
                    sal_Int32 nRow = _nPos / nColumnCount;
                    sal_uInt16 nColumn  = static_cast< sal_uInt16 >( _nPos % nColumnCount );
                    aRetText = GetCellText( nRow, nColumn );
                }
            }
            break;
        }
        case AccessibleBrowseBoxObjType::CheckBoxCell:
        {
            break; // checkbox cells have no name
        }
        case AccessibleBrowseBoxObjType::ColumnHeaderCell:
        {
            aRetText = m_xHeaderBar->GetItemText(m_xHeaderBar->GetItemId(static_cast<sal_uInt16>(_nPos)));
            break;
        }

        case AccessibleBrowseBoxObjType::RowHeaderBar:
        case AccessibleBrowseBoxObjType::RowHeaderCell:
            aRetText = u"error"_ustr;
            break;

        default:
            OSL_FAIL("BrowseBox::GetAccessibleName: invalid enum!");
    }
    return aRetText;
}

OUString SvHeaderTabListBox::GetAccessibleObjectDescription( AccessibleBrowseBoxObjType _eType, sal_Int32 _nPos ) const
{
    OUString aRetText;

    if( _eType == AccessibleBrowseBoxObjType::TableCell && _nPos != -1 )
    {
        sal_uInt16 nColumnCount = GetColumnCount();
        if (nColumnCount > 0)
        {
            sal_Int32 nRow = _nPos / nColumnCount;
            sal_uInt16 nColumn  = static_cast< sal_uInt16 >( _nPos % nColumnCount );

            OUString aText( VclResId(STR_SVT_ACC_DESC_TABLISTBOX) );
            aText = aText.replaceFirst( "%1", OUString::number( nRow ) );
            OUString sColHeader = m_xHeaderBar->GetItemText(m_xHeaderBar->GetItemId(nColumn));
            if ( sColHeader.isEmpty() )
                sColHeader = OUString::number( nColumn );
            aText = aText.replaceFirst( "%2", sColHeader );
            aRetText = aText;
        }
    }

    return aRetText;
}

void SvHeaderTabListBox::FillAccessibleStateSet( sal_Int64& _rStateSet, AccessibleBrowseBoxObjType _eType ) const
{
    switch( _eType )
    {
        case AccessibleBrowseBoxObjType::BrowseBox:
        case AccessibleBrowseBoxObjType::Table:
        {
            _rStateSet |= AccessibleStateType::FOCUSABLE;
            if ( HasFocus() )
                _rStateSet |= AccessibleStateType::FOCUSED;
            if ( IsActive() )
                _rStateSet |= AccessibleStateType::ACTIVE;
            if ( IsEnabled() )
            {
                _rStateSet |= AccessibleStateType::ENABLED;
                _rStateSet |= AccessibleStateType::SENSITIVE;
            }
            if ( IsReallyVisible() )
                _rStateSet |= AccessibleStateType::VISIBLE;
            if ( _eType == AccessibleBrowseBoxObjType::Table )
            {

                _rStateSet |= AccessibleStateType::MANAGES_DESCENDANTS;
                _rStateSet |= AccessibleStateType::MULTI_SELECTABLE;
            }
            break;
        }

        case AccessibleBrowseBoxObjType::ColumnHeaderBar:
        {
            sal_Int32 nCurRow = GetCurrRow();
            sal_uInt16 nCurColumn = GetCurrColumn();
            if ( IsCellVisible( nCurRow, nCurColumn ) )
                _rStateSet |= AccessibleStateType::VISIBLE;
            if ( IsEnabled() )
                _rStateSet |= AccessibleStateType::ENABLED;
            _rStateSet |= AccessibleStateType::TRANSIENT;
            break;
        }

        case AccessibleBrowseBoxObjType::RowHeaderCell:
        case AccessibleBrowseBoxObjType::ColumnHeaderCell:
        {
            _rStateSet |= AccessibleStateType::VISIBLE;
            _rStateSet |= AccessibleStateType::FOCUSABLE;
            _rStateSet |= AccessibleStateType::TRANSIENT;
            if ( IsEnabled() )
                _rStateSet |= AccessibleStateType::ENABLED;
            break;
        }
        default:
            break;
    }
}

void SvHeaderTabListBox::FillAccessibleStateSetForCell( sal_Int64& _rStateSet, sal_Int32 _nRow, sal_uInt16 _nColumn ) const
{
    _rStateSet |= AccessibleStateType::FOCUSABLE;
    _rStateSet |= AccessibleStateType::SELECTABLE;
    _rStateSet |= AccessibleStateType::TRANSIENT;

    if ( IsCellVisible( _nRow, _nColumn ) )
    {
        _rStateSet |= AccessibleStateType::VISIBLE;
        _rStateSet |= AccessibleStateType::ENABLED;
    }

    if ( IsRowSelected( _nRow ) )
    {
        _rStateSet |= AccessibleStateType::ACTIVE;
        if (HasChildPathFocus())
            _rStateSet |= AccessibleStateType::FOCUSED;
        _rStateSet |= AccessibleStateType::SELECTED;
    }
    if ( IsEnabled() )
        _rStateSet |= AccessibleStateType::ENABLED;
}

void SvHeaderTabListBox::GrabTableFocus()
{
    GrabFocus();
}

bool SvHeaderTabListBox::GetGlyphBoundRects( const Point& rOrigin, const OUString& rStr, int nIndex, int nLen, std::vector< tools::Rectangle >& rVector )
{
    return GetOutDev()->GetGlyphBoundRects( rOrigin, rStr, nIndex, nLen, rVector );
}

tools::Rectangle SvHeaderTabListBox::GetWindowExtentsRelative(const vcl::Window& rRelativeWindow) const
{
    return Control::GetWindowExtentsRelative( rRelativeWindow );
}

void SvHeaderTabListBox::GrabFocus()
{
    Control::GrabFocus();
}

rtl::Reference<comphelper::OAccessible> SvHeaderTabListBox::GetAccessible()
{
    return Control::GetAccessible();
}

vcl::Window* SvHeaderTabListBox::GetAccessibleParentWindow() const
{
    return Control::GetAccessibleParentWindow();
}

vcl::Window* SvHeaderTabListBox::GetWindowInstance()
{
    return this;
}

rtl::Reference<comphelper::OAccessible> SvHeaderTabListBox::CreateAccessible()
{
    if (m_xAccessible.is())
        return m_xAccessible;

    rtl::Reference<comphelper::OAccessible> pAccParent = GetAccessibleParent();
    if (pAccParent.is())
    {
        m_xAccessible = new AccessibleTabListBox(pAccParent, *this);
        return m_xAccessible;
    }
    return nullptr;
}

tools::Rectangle SvHeaderTabListBox::GetFieldCharacterBounds(sal_Int32,sal_Int32,sal_Int32)
{
    return tools::Rectangle();
}

sal_Int32 SvHeaderTabListBox::GetFieldIndexAtPoint(sal_Int32 _nRow,sal_Int32 _nColumnPos,const Point& _rPoint)
{
    OUString sText = GetAccessibleCellText( _nRow, static_cast< sal_uInt16 >( _nColumnPos ) );
    std::vector< tools::Rectangle > aRects;
    if ( GetGlyphBoundRects(Point(0,0), sText, 0, sText.getLength(), aRects) )
    {
        sal_Int32 nPos = 0;
        for (auto const& rectangle : aRects)
        {
            if( rectangle.Contains(_rPoint) )
                return nPos;
            ++nPos;
        }
    }

    return -1;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
