/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice 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 <core_resource.hxx>
#include <tabletree.hxx>
#include <imageprovider.hxx>
#include <strings.hrc>
#include <connectivity/dbtools.hxx>
#include <com/sun/star/sdb/application/DatabaseObject.hpp>
#include <com/sun/star/sdb/application/DatabaseObjectContainer.hpp>
#include <com/sun/star/sdbcx/XViewsSupplier.hpp>
#include <com/sun/star/sdbcx/XTablesSupplier.hpp>
#include <com/sun/star/sdbc/SQLException.hpp>
#include <com/sun/star/sdbc/XRow.hpp>
#include <comphelper/diagnose_ex.hxx>
#include <osl/diagnose.h>
#include <connectivity/dbmetadata.hxx>

#include <algorithm>

namespace dbaui
{

using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::sdb;
using namespace ::com::sun::star::sdbc;
using namespace ::com::sun::star::sdbcx;
using namespace ::com::sun::star::container;
using namespace ::com::sun::star::sdb::application;

using namespace ::dbtools;
using namespace ::comphelper;

namespace DatabaseObject = ::com::sun::star::sdb::application::DatabaseObject;
namespace DatabaseObjectContainer = ::com::sun::star::sdb::application::DatabaseObjectContainer;

// OTableTreeListBox
OTableTreeListBox::OTableTreeListBox(std::unique_ptr<weld::TreeView> xTreeView, bool bShowToggles)
    : TreeListBox(std::move(xTreeView), true)
    , m_xImageProvider(new ImageProvider)
    , m_bVirtualRoot(false)
    , m_bNoEmptyFolders(false)
    , m_bShowToggles(bShowToggles)
{
    if (m_bShowToggles)
        m_xTreeView->enable_toggle_buttons(weld::ColumnToggleType::Check);
}

bool OTableTreeListBox::isFolderEntry(const weld::TreeIter& rEntry) const
{
    sal_Int32 nEntryType = m_xTreeView->get_id(rEntry).toInt32();
    return ( nEntryType == DatabaseObjectContainer::TABLES )
        ||  ( nEntryType == DatabaseObjectContainer::CATALOG )
        ||  ( nEntryType == DatabaseObjectContainer::SCHEMA );
}

void OTableTreeListBox::implOnNewConnection( const Reference< XConnection >& _rxConnection )
{
    m_xConnection = _rxConnection;
    m_xImageProvider.reset( new ImageProvider( m_xConnection  ) );
}

void OTableTreeListBox::UpdateTableList( const Reference< XConnection >& _rxConnection )
{
    Sequence< OUString > sTables, sViews;

    OUString sCurrentActionError;
    try
    {
        Reference< XTablesSupplier > xTableSupp( _rxConnection, UNO_QUERY_THROW );
        sCurrentActionError = DBA_RES(STR_NOTABLEINFO);

        Reference< XNameAccess > xTables,xViews;

        Reference< XViewsSupplier > xViewSupp( _rxConnection, UNO_QUERY );
        if ( xViewSupp.is() )
        {
            xViews = xViewSupp->getViews();
            if (xViews.is())
                sViews = xViews->getElementNames();
        }

        xTables = xTableSupp->getTables();
        if (xTables.is())
            sTables = xTables->getElementNames();
    }
    catch(RuntimeException&)
    {
        TOOLS_WARN_EXCEPTION( "dbaccess", "OTableTreeListBox::UpdateTableList");
    }
    catch ( const SQLException& )
    {
        throw;
    }
    catch(Exception&)
    {
        css::uno::Any anyEx = cppu::getCaughtException();
        // a non-SQLException exception occurred ... simply throw an SQLException
        throw SQLException(sCurrentActionError, nullptr, u""_ustr, 0, anyEx);
    }

    UpdateTableList( _rxConnection, sTables, sViews );
}

namespace
{
    struct OViewSetter
    {
        const Sequence< OUString> m_aViews;
        ::comphelper::UStringMixEqual m_aEqualFunctor;

        OViewSetter(const Sequence< OUString>& _rViews,bool _bCase) : m_aViews(_rViews),m_aEqualFunctor(_bCase){}
        OTableTreeListBox::TNames::value_type operator() (const OUString& name)
        {
            OTableTreeListBox::TNames::value_type aRet;
            aRet.first = name;
            aRet.second = std::any_of(m_aViews.begin(), m_aViews.end(),
                                      [this, &name](const OUString& lhs)
                                      { return m_aEqualFunctor(lhs, name); } );

            return aRet;
        }
    };

}

void OTableTreeListBox::UpdateTableList(
                const Reference< XConnection >& _rxConnection,
                const Sequence< OUString>& _rTables,
                const Sequence< OUString>& _rViews
            )
{
    TNames aTables;
    aTables.resize(_rTables.getLength());
    try
    {
        Reference< XDatabaseMetaData > xMeta( _rxConnection->getMetaData(), UNO_SET_THROW );
        std::transform( _rTables.begin(), _rTables.end(),
            aTables.begin(), OViewSetter( _rViews, xMeta->supportsMixedCaseQuotedIdentifiers() ) );
    }
    catch(Exception&)
    {
        DBG_UNHANDLED_EXCEPTION("dbaccess");
    }
    UpdateTableList( _rxConnection, aTables );
}

namespace
{
    std::vector< OUString > lcl_getMetaDataStrings_throw( const Reference< XResultSet >& _rxMetaDataResult, sal_Int32 _nColumnIndex )
    {
        std::vector< OUString > aStrings;
        Reference< XRow > xRow( _rxMetaDataResult, UNO_QUERY_THROW );
        while ( _rxMetaDataResult->next() )
            aStrings.push_back( xRow->getString( _nColumnIndex ) );
        return aStrings;
    }

    bool lcl_shouldDisplayEmptySchemasAndCatalogs( const Reference< XConnection >& _rxConnection )
    {
        ::dbtools::DatabaseMetaData aMetaData( _rxConnection );
        return aMetaData.displayEmptyTableFolders();
    }
}

void OTableTreeListBox::UpdateTableList( const Reference< XConnection >& _rxConnection, const TNames& _rTables )
{
    implOnNewConnection( _rxConnection );

    // throw away all the old stuff
    m_xTreeView->clear();
    m_xTreeView->make_unsorted();

    try
    {
        if (haveVirtualRoot())
        {
            OUString sRootEntryText;
            if ( std::none_of(_rTables.begin(),_rTables.end(),
                                [] (const TNames::value_type& name) { return !name.second; }) )
                sRootEntryText  = DBA_RES(STR_ALL_TABLES);
            else if ( std::none_of(_rTables.begin(),_rTables.end(),
                                     [] (const TNames::value_type& name) { return name.second; }) )
                sRootEntryText  = DBA_RES(STR_ALL_VIEWS);
            else
                sRootEntryText  = DBA_RES(STR_ALL_TABLES_AND_VIEWS);
            OUString sId(OUString::number(DatabaseObjectContainer::TABLES));
            OUString sImageId = ImageProvider::getFolderImageId(DatabaseObject::TABLE);
            std::unique_ptr<weld::TreeIter> xRet(m_xTreeView->make_iterator());
            m_xTreeView->insert(nullptr, -1, nullptr, &sId, nullptr, nullptr, false, xRet.get());
            m_xTreeView->set_image(*xRet, sImageId, -1);
            if (m_bShowToggles)
                m_xTreeView->set_toggle(*xRet, TRISTATE_FALSE);
            m_xTreeView->set_text(*xRet, sRootEntryText, 0);
            m_xTreeView->set_text_emphasis(*xRet, false, 0);
        }

        if ( _rTables.empty() )
            // nothing to do (besides inserting the root entry)
            return;

        // get the table/view names
        Reference< XDatabaseMetaData > xMeta( _rxConnection->getMetaData(), UNO_SET_THROW );
        for (auto const& table : _rTables)
        {
            // add the entry
            implAddEntry(xMeta, table.first, false);
        }

        if ( !m_bNoEmptyFolders && lcl_shouldDisplayEmptySchemasAndCatalogs( _rxConnection ) )
        {
            bool bSupportsCatalogs = xMeta->supportsCatalogsInDataManipulation();
            bool bSupportsSchemas = xMeta->supportsSchemasInDataManipulation();

            if ( bSupportsCatalogs || bSupportsSchemas )
            {
                // we display empty catalogs if the DB supports catalogs, and they're noted at the beginning of a
                // composed name. Otherwise, we display empty schematas. (also see the tree structure explained in
                // implAddEntry)
                bool bCatalogs = bSupportsCatalogs && xMeta->isCatalogAtStart();

                std::vector< OUString > aFolderNames( lcl_getMetaDataStrings_throw(
                    bCatalogs ? xMeta->getCatalogs() : xMeta->getSchemas(), 1 ) );
                sal_Int32 nFolderType = bCatalogs ? DatabaseObjectContainer::CATALOG : DatabaseObjectContainer::SCHEMA;

                OUString sImageId = ImageProvider::getFolderImageId(DatabaseObject::TABLE);

                std::unique_ptr<weld::TreeIter> xRootEntry(getAllObjectsEntry());
                std::unique_ptr<weld::TreeIter> xRet(m_xTreeView->make_iterator());
                for (auto const& folderName : aFolderNames)
                {
                    std::unique_ptr<weld::TreeIter> xFolder(GetEntryPosByName(folderName, xRootEntry.get()));
                    if (!xFolder)
                    {
                        OUString sId(OUString::number(nFolderType));
                        m_xTreeView->insert(xRootEntry.get(), -1, nullptr, &sId, nullptr, nullptr, false, xRet.get());
                        m_xTreeView->set_image(*xRet, sImageId, -1);
                        if (m_bShowToggles)
                            m_xTreeView->set_toggle(*xRet, TRISTATE_FALSE);
                        m_xTreeView->set_text(*xRet, folderName, 0);
                        m_xTreeView->set_text_emphasis(*xRet, false, 0);
                    }
                }
            }
        }
    }
    catch ( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION("dbaccess");
    }

    m_xTreeView->make_sorted();
}

bool OTableTreeListBox::isWildcardChecked(const weld::TreeIter& rEntry)
{
    return m_xTreeView->get_text_emphasis(rEntry, 0);
}

void OTableTreeListBox::checkWildcard(const weld::TreeIter& rEntry)
{
    if (!m_bShowToggles)
        return;
    m_xTreeView->set_toggle(rEntry, TRISTATE_TRUE);
    checkedButton_noBroadcast(rEntry);
}

std::unique_ptr<weld::TreeIter> OTableTreeListBox::getAllObjectsEntry() const
{
    if (!haveVirtualRoot())
        return nullptr;
    auto xRet = m_xTreeView->make_iterator();
    if (!m_xTreeView->get_iter_first(*xRet))
        return nullptr;
    return xRet;
}

void OTableTreeListBox::checkedButton_noBroadcast(const weld::TreeIter& rEntry)
{
    if (!m_bShowToggles)
        return;
    TriState eState = m_xTreeView->get_toggle(rEntry);
    OSL_ENSURE(TRISTATE_INDET != eState, "OTableTreeListBox::CheckButtonHdl: user action which lead to TRISTATE?");

    if (m_xTreeView->iter_has_child(rEntry)) // if it has children, check those too
    {
        std::unique_ptr<weld::TreeIter> xChildEntry(m_xTreeView->make_iterator(&rEntry));
        std::unique_ptr<weld::TreeIter> xSiblingEntry(m_xTreeView->make_iterator(&rEntry));
        bool bChildEntry = m_xTreeView->iter_next(*xChildEntry);
        bool bSiblingEntry = m_xTreeView->iter_next_sibling(*xSiblingEntry);
        while (bChildEntry && (!bSiblingEntry || !xChildEntry->equal(*xSiblingEntry)))
        {
            m_xTreeView->set_toggle(*xChildEntry, eState);
            bChildEntry = m_xTreeView->iter_next(*xChildEntry);
        }
    }

    if (m_xTreeView->is_selected(rEntry))
    {
        m_xTreeView->selected_foreach([this, eState](weld::TreeIter& rSelected){
            m_xTreeView->set_toggle(rSelected, eState);
            if (m_xTreeView->iter_has_child(rSelected)) // if it has children, check those too
            {
                std::unique_ptr<weld::TreeIter> xChildEntry(m_xTreeView->make_iterator(&rSelected));
                std::unique_ptr<weld::TreeIter> xSiblingEntry(m_xTreeView->make_iterator(&rSelected));
                bool bChildEntry = m_xTreeView->iter_next(*xChildEntry);
                bool bSiblingEntry = m_xTreeView->iter_next_sibling(*xSiblingEntry);
                while (bChildEntry && (!bSiblingEntry || !xChildEntry->equal(*xSiblingEntry)))
                {
                    m_xTreeView->set_toggle(*xChildEntry, eState);
                    bChildEntry = m_xTreeView->iter_next(*xChildEntry);
                }
            }
            return false;
        });
    }

    CheckButtons();

    // if an entry has children, it makes a difference if the entry is checked
    // because all children are checked or if the user checked it explicitly.
    // So we track explicit (un)checking
    implEmphasize(rEntry, eState == TRISTATE_TRUE);
}

void OTableTreeListBox::implEmphasize(const weld::TreeIter& rEntry, bool _bChecked, bool _bUpdateDescendants, bool _bUpdateAncestors)
{
    // special emphasizing handling for the "all objects" entry
    bool bAllObjectsEntryAffected = haveVirtualRoot() && (getAllObjectsEntry()->equal(rEntry));
    if  (   m_xTreeView->iter_has_child(rEntry) // the entry has children
        ||  bAllObjectsEntryAffected            // or it is the "all objects" entry
        )
    {
        m_xTreeView->set_text_emphasis(rEntry, _bChecked, 0);
    }

    if (_bUpdateDescendants)
    {
        std::unique_ptr<weld::TreeIter> xChild(m_xTreeView->make_iterator(&rEntry));
        // remove the mark for all children of the checked entry
        bool bChildLoop = m_xTreeView->iter_children(*xChild);
        while (bChildLoop)
        {
            if (m_xTreeView->iter_has_child(*xChild))
                implEmphasize(*xChild, false, true, false);
            bChildLoop = m_xTreeView->iter_next_sibling(*xChild);
        }
    }

    if (_bUpdateAncestors)
    {
        std::unique_ptr<weld::TreeIter> xParent(m_xTreeView->make_iterator(&rEntry));
        // remove the mark for all ancestors of the entry
        if (m_xTreeView->iter_parent(*xParent))
            implEmphasize(*xParent, false, false);
    }
}

std::unique_ptr<weld::TreeIter> OTableTreeListBox::implAddEntry(
        const Reference< XDatabaseMetaData >& _rxMeta,
        const OUString& _rTableName,
        bool _bCheckName
    )
{
    OSL_PRECOND( _rxMeta.is(), "OTableTreeListBox::implAddEntry: invalid meta data!" );
    if ( !_rxMeta.is() )
        return nullptr;

    // split the complete name into its components
    OUString sCatalog, sSchema, sName;
    qualifiedNameComponents( _rxMeta, _rTableName, sCatalog, sSchema, sName, ::dbtools::EComposeRule::InDataManipulation );

    std::unique_ptr<weld::TreeIter> xParentEntry(getAllObjectsEntry());

    // if the DB uses catalog at the start of identifiers, then our hierarchy is
    //   catalog
    //   +- schema
    //      +- table
    // else it is
    //   schema
    //   +- catalog
    //      +- table
    bool bCatalogAtStart = _rxMeta->isCatalogAtStart();
    const OUString& rFirstName  = bCatalogAtStart ? sCatalog : sSchema;
    const sal_Int32 nFirstFolderType   = bCatalogAtStart ? DatabaseObjectContainer::CATALOG : DatabaseObjectContainer::SCHEMA;
    const OUString& rSecondName = bCatalogAtStart ? sSchema : sCatalog;
    const sal_Int32 nSecondFolderType  = bCatalogAtStart ? DatabaseObjectContainer::SCHEMA : DatabaseObjectContainer::CATALOG;

    if ( !rFirstName.isEmpty() )
    {
        std::unique_ptr<weld::TreeIter> xFolder(GetEntryPosByName(rFirstName, xParentEntry.get()));
        if (!xFolder)
        {
            xFolder = m_xTreeView->make_iterator();
            OUString sId(OUString::number(nFirstFolderType));
            OUString sImageId = ImageProvider::getFolderImageId(DatabaseObject::TABLE);
            m_xTreeView->insert(xParentEntry.get(), -1, nullptr, &sId, nullptr, nullptr, false, xFolder.get());
            m_xTreeView->set_image(*xFolder, sImageId, -1);
            if (m_bShowToggles)
                m_xTreeView->set_toggle(*xFolder, TRISTATE_FALSE);
            m_xTreeView->set_text(*xFolder, rFirstName, 0);
            m_xTreeView->set_text_emphasis(*xFolder, false, 0);
        }
        xParentEntry = std::move(xFolder);
    }

    if ( !rSecondName.isEmpty() )
    {
        std::unique_ptr<weld::TreeIter> xFolder(GetEntryPosByName(rSecondName, xParentEntry.get()));
        if (!xFolder)
        {
            xFolder = m_xTreeView->make_iterator();
            OUString sId(OUString::number(nSecondFolderType));
            OUString sImageId = ImageProvider::getFolderImageId(DatabaseObject::TABLE);
            m_xTreeView->insert(xParentEntry.get(), -1, nullptr, &sId, nullptr, nullptr, false, xFolder.get());
            m_xTreeView->set_image(*xFolder, sImageId, -1);
            if (m_bShowToggles)
                m_xTreeView->set_toggle(*xFolder, TRISTATE_FALSE);
            m_xTreeView->set_text(*xFolder, rSecondName, 0);
            m_xTreeView->set_text_emphasis(*xFolder, false, 0);
        }
        xParentEntry = std::move(xFolder);
    }

    if (!_bCheckName || !GetEntryPosByName(sName, xParentEntry.get()))
    {
        std::unique_ptr<weld::TreeIter> xEntry = m_xTreeView->make_iterator();
        m_xTreeView->insert(xParentEntry.get(), -1, nullptr, nullptr, nullptr, nullptr, false, xEntry.get());

        auto xGraphic = m_xImageProvider->getXGraphic(_rTableName, DatabaseObject::TABLE);
        if (xGraphic.is())
            m_xTreeView->set_image(*xEntry, xGraphic, -1);
        else
        {
            OUString sImageId(m_xImageProvider->getImageId(_rTableName, DatabaseObject::TABLE));
            m_xTreeView->set_image(*xEntry, sImageId, -1);
        }
        if (m_bShowToggles)
            m_xTreeView->set_toggle(*xEntry, TRISTATE_FALSE);
        m_xTreeView->set_text(*xEntry, sName, 0);
        m_xTreeView->set_text_emphasis(*xEntry, false, 0);

        return xEntry;
    }

    return nullptr;
}

NamedDatabaseObject OTableTreeListBox::describeObject(const weld::TreeIter& rEntry)
{
    NamedDatabaseObject aObject;

    sal_Int32 nEntryType = m_xTreeView->get_id(rEntry).toInt32();

    if  ( nEntryType == DatabaseObjectContainer::TABLES )
    {
        aObject.Type = DatabaseObjectContainer::TABLES;
    }
    else if (   ( nEntryType == DatabaseObjectContainer::CATALOG )
            ||  ( nEntryType == DatabaseObjectContainer::SCHEMA )
            )
    {
        // nothing useful to be done
    }
    else
    {
        aObject.Type = DatabaseObject::TABLE;
        aObject.Name = getQualifiedTableName(rEntry);
    }

    return aObject;
}

std::unique_ptr<weld::TreeIter> OTableTreeListBox::addedTable(const OUString& rName)
{
    try
    {
        Reference< XDatabaseMetaData > xMeta;
        if ( impl_getAndAssertMetaData( xMeta ) )
            return implAddEntry( xMeta, rName );
    }
    catch( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION("dbaccess");
    }
    return nullptr;
}

bool OTableTreeListBox::impl_getAndAssertMetaData( Reference< XDatabaseMetaData >& _out_rMetaData ) const
{
    if ( m_xConnection.is() )
        _out_rMetaData = m_xConnection->getMetaData();
    OSL_PRECOND( _out_rMetaData.is(), "OTableTreeListBox::impl_getAndAssertMetaData: invalid current connection!" );
    return _out_rMetaData.is();
}

OUString OTableTreeListBox::getQualifiedTableName(const weld::TreeIter& rEntry) const
{
    OSL_PRECOND( !isFolderEntry(rEntry), "OTableTreeListBox::getQualifiedTableName: folder entries not allowed here!" );

    try
    {
        Reference< XDatabaseMetaData > xMeta;
        if ( !impl_getAndAssertMetaData( xMeta ) )
            return OUString();

        OUString sCatalog;
        OUString sSchema;
        OUString sTable;

        std::unique_ptr<weld::TreeIter> xSchema(m_xTreeView->make_iterator(&rEntry));
        bool bSchema = m_xTreeView->iter_parent(*xSchema);
        if (bSchema)
        {
            std::unique_ptr<weld::TreeIter> xCatalog(m_xTreeView->make_iterator(xSchema.get()));
            bool bCatalog = m_xTreeView->iter_parent(*xCatalog);
            if  (   bCatalog
                ||  (   xMeta->supportsCatalogsInDataManipulation()
                    &&  !xMeta->supportsSchemasInDataManipulation()
                    )   // here we support catalog but no schema
                )
            {
                if (!bCatalog)
                {
                    xCatalog = std::move(xSchema);
                    bSchema = false;
                }
                sCatalog = m_xTreeView->get_text(*xCatalog);
            }
            if (bSchema)
                sSchema = m_xTreeView->get_text(*xSchema);
        }
        sTable = m_xTreeView->get_text(rEntry);

        return ::dbtools::composeTableName( xMeta, sCatalog, sSchema, sTable, false, ::dbtools::EComposeRule::InDataManipulation );
    }
    catch( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION("dbaccess");
    }
    return OUString();
}

std::unique_ptr<weld::TreeIter> OTableTreeListBox::getEntryByQualifiedName(const OUString& rName)
{
    try
    {
        Reference< XDatabaseMetaData > xMeta;
        if ( !impl_getAndAssertMetaData( xMeta ) )
            return nullptr;

        // split the complete name into its components
        OUString sCatalog, sSchema, sName;
        qualifiedNameComponents(xMeta, rName, sCatalog, sSchema, sName,::dbtools::EComposeRule::InDataManipulation);

        std::unique_ptr<weld::TreeIter> xParent(getAllObjectsEntry());
        std::unique_ptr<weld::TreeIter> xCat;
        std::unique_ptr<weld::TreeIter> xSchema;
        if (!sCatalog.isEmpty())
        {
            xCat = GetEntryPosByName(sCatalog);
            if (xCat)
                xParent = std::move(xCat);
        }

        if (!sSchema.isEmpty())
        {
            xSchema = GetEntryPosByName(sSchema, xParent.get());
            if (xSchema)
                xParent = std::move(xSchema);
        }

        return GetEntryPosByName(sName, xParent.get());
    }
    catch( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION("dbaccess");
    }
    return nullptr;
}

void OTableTreeListBox::removedTable(const OUString& rName)
{
    try
    {
        std::unique_ptr<weld::TreeIter> xEntry = getEntryByQualifiedName(rName);
        if (xEntry)
            m_xTreeView->remove(*xEntry);
    }
    catch( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION("dbaccess");
    }
}

void OTableTreeListBox::CheckButtons()
{
    if (!m_bShowToggles)
        return;

    auto xEntry(m_xTreeView->make_iterator());
    if (!m_xTreeView->get_iter_first(*xEntry))
        return;

    do
    {
        implDetermineState(*xEntry);
    } while (m_xTreeView->iter_next_sibling(*xEntry));
}

TriState OTableTreeListBox::implDetermineState(const weld::TreeIter& rEntry)
{
    if (!m_bShowToggles)
        return TRISTATE_FALSE;

    TriState eState = m_xTreeView->get_toggle(rEntry);
    if (!m_xTreeView->iter_has_child(rEntry))
        // nothing to do in this bottom-up routine if there are no children ...
        return eState;

    // loop through the children and check their states
    sal_uInt16 nCheckedChildren = 0;
    sal_uInt16 nChildrenOverall = 0;

    std::unique_ptr<weld::TreeIter> xChild(m_xTreeView->make_iterator(&rEntry));
    bool bChildLoop = m_xTreeView->iter_children(*xChild);
    while (bChildLoop)
    {
        TriState eChildState = implDetermineState(*xChild);
        if (eChildState == TRISTATE_INDET)
            break;
        if (eChildState == TRISTATE_TRUE)
            ++nCheckedChildren;
        ++nChildrenOverall;
        bChildLoop = m_xTreeView->iter_next_sibling(*xChild);
    }

    if (bChildLoop)
    {
        // we did not finish the loop because at least one of the children is in tristate
        eState = TRISTATE_INDET;

        // but this means that we did not finish all the siblings of pChildLoop,
        // so their checking may be incorrect at the moment
        // -> correct this
        while (bChildLoop)
        {
            implDetermineState(*xChild);
            bChildLoop = m_xTreeView->iter_next_sibling(*xChild);
        }
    }
    else
    {
        // none if the children are in tristate
        if (nCheckedChildren)
        {
            // we have at least one child checked
            if (nCheckedChildren != nChildrenOverall)
            {
                // not all children are checked
                eState = TRISTATE_INDET;
            }
            else
            {
                // all children are checked
                eState = TRISTATE_TRUE;
            }
        }
        else
        {
            // no children are checked
            eState = TRISTATE_FALSE;
        }
    }

    // finally set the entry to the state we just determined
    m_xTreeView->set_toggle(rEntry, eState);

    return eState;
}

DBTableTreeView::DBTableTreeView(weld::Container* pContainer)
    : DBTreeViewBase(pContainer)
{
    m_xTreeListBox.reset(new OTableTreeListBox(m_xBuilder->weld_tree_view(u"treeview"_ustr), /*bShowToggles*/false));
}

}   // namespace dbaui

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