/* -*- 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 <memory>
#include <filtnav.hxx>
#include <fmexch.hxx>
#include <helpids.h>
#include <fmprop.hxx>
#include <svx/strings.hrc>
 
#include <com/sun/star/awt/XControl.hpp>
#include <com/sun/star/form/runtime/XFormController.hpp>
#include <com/sun/star/util/NumberFormatter.hpp>
#include <com/sun/star/sdb/SQLContext.hpp>
 
#include <comphelper/processfactory.hxx>
#include <comphelper/string.hxx>
#include <connectivity/dbtools.hxx>
#include <connectivity/sqlnode.hxx>
#include <cppuhelper/implbase.hxx>
#include <i18nlangtag/languagetag.hxx>
#include <fmshimp.hxx>
#include <o3tl/safeint.hxx>
#include <sfx2/objitem.hxx>
#include <svx/dialmgr.hxx>
#include <svx/fmshell.hxx>
#include <svx/fmtools.hxx>
#include <svx/svxids.hrc>
#include <toolkit/helper/vclunohelper.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <utility>
#include <vcl/commandevent.hxx>
#include <vcl/event.hxx>
#include <vcl/settings.hxx>
#include <vcl/svapp.hxx>
 
#include <bitmaps.hlst>
 
#include <functional>
 
using namespace ::svxform;
using namespace ::connectivity;
using namespace ::dbtools;
 
namespace svxform
{
    using ::com::sun::star::uno::Reference;
    using ::com::sun::star::container::XIndexAccess;
    using ::com::sun::star::uno::UNO_QUERY;
    using ::com::sun::star::beans::XPropertySet;
    using ::com::sun::star::form::runtime::XFormController;
    using ::com::sun::star::form::runtime::XFilterController;
    using ::com::sun::star::form::runtime::XFilterControllerListener;
    using ::com::sun::star::form::runtime::FilterEvent;
    using ::com::sun::star::lang::EventObject;
    using ::com::sun::star::form::XForm;
    using ::com::sun::star::container::XChild;
    using ::com::sun::star::awt::XControl;
    using ::com::sun::star::sdbc::XConnection;
    using ::com::sun::star::util::XNumberFormatsSupplier;
    using ::com::sun::star::util::XNumberFormatter;
    using ::com::sun::star::util::NumberFormatter;
    using ::com::sun::star::sdbc::XRowSet;
    using ::com::sun::star::lang::Locale;
    using ::com::sun::star::sdb::SQLContext;
    using ::com::sun::star::uno::XInterface;
    using ::com::sun::star::uno::UNO_QUERY_THROW;
    using ::com::sun::star::uno::UNO_SET_THROW;
    using ::com::sun::star::uno::Exception;
    using ::com::sun::star::uno::Sequence;
 
 
OFilterItemExchange::OFilterItemExchange()
    : m_pFormItem(nullptr)
{
}
 
void OFilterItemExchange::AddSupportedFormats()
{
    AddFormat(getFormatId());
}
 
SotClipboardFormatId OFilterItemExchange::getFormatId()
{
    static SotClipboardFormatId s_nFormat =
         SotExchange::RegisterFormatName(u"application/x-openoffice;windows_formatname=\"form.FilterControlExchange\""_ustr);
    DBG_ASSERT(static_cast<SotClipboardFormatId>(-1) != s_nFormat, "OFilterExchangeHelper::getFormatId: bad exchange id!");
    return s_nFormat;
}
 
rtl::Reference<OLocalExchange> OFilterExchangeHelper::createExchange() const
{
    return new OFilterItemExchange;
}
 
OUString FmFilterData::GetImage() const
{
    return OUString();
}
 
FmParentData::~FmParentData()
{
}
 
OUString FmFormItem::GetImage() const
{
    return RID_SVXBMP_FORM;
}
 
FmFilterItem* FmFilterItems::Find( const ::sal_Int32 _nFilterComponentIndex ) const
{
    for ( auto & pData : m_aChildren )
    {
        FmFilterItem& rCondition = dynamic_cast<FmFilterItem&>(*pData);
        if ( _nFilterComponentIndex == rCondition.GetComponentIndex() )
            return &rCondition;
    }
    return nullptr;
}
 
OUString FmFilterItems::GetImage() const
{
    return RID_SVXBMP_FILTER;
}
 
FmFilterItem::FmFilterItem( FmFilterItems* pParent,
                            OUString aFieldName,
                            const OUString& aText,
                            const sal_Int32 _nComponentIndex )
          :FmFilterData(pParent, aText)
          ,m_aFieldName(std::move(aFieldName))
          ,m_nComponentIndex( _nComponentIndex )
{
}
 
OUString FmFilterItem::GetImage() const
{
    return RID_SVXBMP_FIELD;
}
 
// Hints for communication between model and view
 
namespace {
 
class FmFilterHint : public SfxHint
{
    FmFilterData*   m_pData;
 
public:
    explicit FmFilterHint(SfxHintId nHintId, FmFilterData* pData) : SfxHint(nHintId), m_pData(pData) {}
    FmFilterData* GetData() const { return m_pData; }
};
 
class FmFilterInsertedHint : public FmFilterHint
{
    size_t m_nPos;   // Position relative to the parent of the data
 
public:
    FmFilterInsertedHint(FmFilterData* pData, size_t nRelPos)
        :FmFilterHint(SfxHintId::FmFilterInserted, pData)
        ,m_nPos(nRelPos){}
 
    size_t GetPos() const { return m_nPos; }
};
 
class FmFilterRemovedHint : public FmFilterHint
{
public:
    explicit FmFilterRemovedHint(FmFilterData* pData)
        :FmFilterHint(SfxHintId::FmFilterRemoved, pData){}
};
 
 
class FmFilterTextChangedHint : public FmFilterHint
{
public:
    explicit FmFilterTextChangedHint(FmFilterData* pData)
        :FmFilterHint(SfxHintId::FmFilterTextChanged, pData){}
};
 
class FilterClearingHint : public SfxHint
{
public:
    FilterClearingHint() : SfxHint(SfxHintId::FilterClearing) {}
};
 
class FmFilterCurrentChangedHint : public SfxHint
{
public:
    FmFilterCurrentChangedHint() : SfxHint(SfxHintId::FmFilterCurrentChanged) {}
};
 
}
 
// class FmFilterAdapter, listener at the FilterControls
class FmFilterAdapter : public ::cppu::WeakImplHelper< XFilterControllerListener >
{
    FmFilterModel*              m_pModel;
    Reference< XIndexAccess >   m_xControllers;
 
public:
    FmFilterAdapter(FmFilterModel* pModel, const Reference< XIndexAccess >& xControllers);
 
// XEventListener
    virtual void SAL_CALL disposing(const EventObject& Source) override;
 
// XFilterControllerListener
    virtual void SAL_CALL predicateExpressionChanged( const FilterEvent& Event ) override;
    virtual void SAL_CALL disjunctiveTermRemoved( const FilterEvent& Event ) override;
    virtual void SAL_CALL disjunctiveTermAdded( const FilterEvent& Event ) override;
 
// helpers
    /// @throws RuntimeException
    void dispose();
 
    void AddOrRemoveListener( const Reference< XIndexAccess >& _rxControllers, const bool _bAdd );
 
    static void setText(sal_Int32 nPos,
        const FmFilterItem* pFilterItem,
        const OUString& rText);
};
 
 
FmFilterAdapter::FmFilterAdapter(FmFilterModel* pModel, const Reference< XIndexAccess >& xControllers)
    :m_pModel( pModel )
    ,m_xControllers( xControllers )
{
    AddOrRemoveListener( m_xControllers, true );
}
 
 
void FmFilterAdapter::dispose()
{
    AddOrRemoveListener( m_xControllers, false );
}
 
 
void FmFilterAdapter::AddOrRemoveListener( const Reference< XIndexAccess >& _rxControllers, const bool _bAdd )
{
    for (sal_Int32 i = 0, nLen = _rxControllers->getCount(); i < nLen; ++i)
    {
        Reference< XIndexAccess > xElement( _rxControllers->getByIndex(i), UNO_QUERY );
 
        // step down
        AddOrRemoveListener( xElement, _bAdd );
 
        // handle this particular controller
        Reference< XFilterController > xController( xElement, UNO_QUERY );
        OSL_ENSURE( xController.is(), "FmFilterAdapter::InsertElements: no XFilterController, cannot sync data!" );
        if ( xController.is() )
        {
            if ( _bAdd )
                xController->addFilterControllerListener( this );
            else
                xController->removeFilterControllerListener( this );
        }
    }
}
 
 
void FmFilterAdapter::setText(sal_Int32 nRowPos,
                              const FmFilterItem* pFilterItem,
                              const OUString& rText)
{
    FmFormItem* pFormItem = dynamic_cast<FmFormItem*>( pFilterItem->GetParent()->GetParent()  );
    assert(pFormItem);
    try
    {
        Reference< XFilterController > xController( pFormItem->GetController(), UNO_QUERY_THROW );
        xController->setPredicateExpression( pFilterItem->GetComponentIndex(), nRowPos, rText );
    }
    catch( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION("svx");
    }
}
 
 
// XEventListener
 
void SAL_CALL FmFilterAdapter::disposing(const EventObject& /*e*/)
{
}
 
 
namespace
{
    OUString lcl_getLabelName_nothrow( const Reference< XControl >& _rxControl )
    {
        OUString sLabelName;
        try
        {
            Reference< XPropertySet > xModel( _rxControl->getModel(), UNO_QUERY_THROW );
            sLabelName = getLabelName( xModel );
        }
        catch( const Exception& )
        {
            DBG_UNHANDLED_EXCEPTION("svx");
        }
        return sLabelName;
    }
 
    Reference< XPropertySet > lcl_getBoundField_nothrow( const Reference< XControl >& _rxControl )
    {
        Reference< XPropertySet > xField;
        try
        {
            Reference< XPropertySet > xModelProps( _rxControl->getModel(), UNO_QUERY_THROW );
            xField.set( xModelProps->getPropertyValue( FM_PROP_BOUNDFIELD ), UNO_QUERY_THROW );
        }
        catch( const Exception& )
        {
            DBG_UNHANDLED_EXCEPTION("svx");
        }
        return xField;
    }
}
 
// XFilterControllerListener
void FmFilterAdapter::predicateExpressionChanged( const FilterEvent& Event )
{
    SolarMutexGuard aGuard;
 
    if ( !m_pModel )
        return;
 
    // the controller which sent the event
    Reference< XFormController > xController( Event.Source, UNO_QUERY_THROW );
    Reference< XFilterController > xFilterController( Event.Source, UNO_QUERY_THROW );
    Reference< XForm > xForm( xController->getModel(), UNO_QUERY_THROW );
 
    FmFormItem* pFormItem = m_pModel->Find( m_pModel->m_aChildren, xForm );
    OSL_ENSURE( pFormItem, "FmFilterAdapter::predicateExpressionChanged: don't know this form!" );
    if ( !pFormItem )
        return;
 
    const sal_Int32 nActiveTerm( xFilterController->getActiveTerm() );
 
    FmFilterData* pData = pFormItem->GetChildren()[nActiveTerm].get();
    FmFilterItems& rFilter = dynamic_cast<FmFilterItems&>(*pData);
    FmFilterItem* pFilterItem = rFilter.Find( Event.FilterComponent );
    if ( pFilterItem )
    {
        if ( !Event.PredicateExpression.isEmpty())
        {
            pFilterItem->SetText( Event.PredicateExpression );
            // notify the UI
            FmFilterTextChangedHint aChangeHint(pFilterItem);
            m_pModel->Broadcast( aChangeHint );
        }
        else
        {
            // no text anymore so remove the condition
            m_pModel->Remove(pFilterItem);
        }
    }
    else
    {
        // searching the component by field name
        OUString aFieldName( lcl_getLabelName_nothrow( xFilterController->getFilterComponent( Event.FilterComponent ) ) );
 
        std::unique_ptr<FmFilterItem> pNewFilterItem(new FmFilterItem(&rFilter, aFieldName, Event.PredicateExpression, Event.FilterComponent));
        m_pModel->Insert(rFilter.GetChildren().end(), std::move(pNewFilterItem));
    }
 
    // ensure there's one empty term in the filter, just in case the active term was previously empty
    m_pModel->EnsureEmptyFilterRows( *pFormItem );
}
 
 
void SAL_CALL FmFilterAdapter::disjunctiveTermRemoved( const FilterEvent& Event )
{
    SolarMutexGuard aGuard;
 
    Reference< XFormController > xController( Event.Source, UNO_QUERY_THROW );
    Reference< XFilterController > xFilterController( Event.Source, UNO_QUERY_THROW );
    Reference< XForm > xForm( xController->getModel(), UNO_QUERY_THROW );
 
    FmFormItem* pFormItem = m_pModel->Find( m_pModel->m_aChildren, xForm );
    OSL_ENSURE( pFormItem, "FmFilterAdapter::disjunctiveTermRemoved: don't know this form!" );
    if ( !pFormItem )
        return;
 
    auto& rTermItems = pFormItem->GetChildren();
    const bool bValidIndex = ( Event.DisjunctiveTerm >= 0 ) && ( o3tl::make_unsigned(Event.DisjunctiveTerm) < rTermItems.size() );
    OSL_ENSURE( bValidIndex, "FmFilterAdapter::disjunctiveTermRemoved: invalid term index!" );
    if ( !bValidIndex )
        return;
 
    // if the first term was removed, then the to-be first term needs its text updated
    if ( Event.DisjunctiveTerm == 0 )
    {
        rTermItems[1]->SetText( SvxResId(RID_STR_FILTER_FILTER_FOR));
        FmFilterTextChangedHint aChangeHint( rTermItems[1].get() );
        m_pModel->Broadcast( aChangeHint );
    }
 
    // finally remove the entry from the model
    m_pModel->Remove( rTermItems.begin() + Event.DisjunctiveTerm );
 
    // ensure there's one empty term in the filter, just in case the currently removed one was the last empty one
    m_pModel->EnsureEmptyFilterRows( *pFormItem );
}
 
 
void SAL_CALL FmFilterAdapter::disjunctiveTermAdded( const FilterEvent& Event )
{
    SolarMutexGuard aGuard;
 
    Reference< XFormController > xController( Event.Source, UNO_QUERY_THROW );
    Reference< XFilterController > xFilterController( Event.Source, UNO_QUERY_THROW );
    Reference< XForm > xForm( xController->getModel(), UNO_QUERY_THROW );
 
    FmFormItem* pFormItem = m_pModel->Find( m_pModel->m_aChildren, xForm );
    OSL_ENSURE( pFormItem, "FmFilterAdapter::disjunctiveTermAdded: don't know this form!" );
    if ( !pFormItem )
        return;
 
    const sal_Int32 nInsertPos = Event.DisjunctiveTerm;
    bool bValidIndex = ( nInsertPos >= 0 ) && ( o3tl::make_unsigned(nInsertPos) <= pFormItem->GetChildren().size() );
    if ( !bValidIndex )
    {
        OSL_FAIL( "FmFilterAdapter::disjunctiveTermAdded: invalid index!" );
        return;
    }
 
    auto insertPos = pFormItem->GetChildren().begin() + nInsertPos;
 
    // "Filter for" for first position, "Or" for the other positions
    std::unique_ptr<FmFilterItems> pFilterItems(new FmFilterItems(pFormItem, (nInsertPos?SvxResId(RID_STR_FILTER_FILTER_OR):SvxResId(RID_STR_FILTER_FILTER_FOR))));
    m_pModel->Insert( insertPos, std::move(pFilterItems) );
}
 
 
FmFilterModel::FmFilterModel()
              :FmParentData(nullptr, OUString())
              ,OSQLParserClient(comphelper::getProcessComponentContext())
              ,m_pCurrentItems(nullptr)
{
}
 
 
FmFilterModel::~FmFilterModel()
{
    Clear();
}
 
 
void FmFilterModel::Clear()
{
    // notify
    FilterClearingHint aClearedHint;
    Broadcast( aClearedHint );
 
    // lose endings
    if (m_pAdapter.is())
    {
        m_pAdapter->dispose();
        m_pAdapter.clear();
    }
 
    m_pCurrentItems  = nullptr;
    m_xController    = nullptr;
    m_xControllers   = nullptr;
 
    m_aChildren.clear();
}
 
 
void FmFilterModel::Update(const Reference< XIndexAccess > & xControllers, const Reference< XFormController > & xCurrent)
{
    if ( xCurrent == m_xController )
        return;
 
    if (!xControllers.is())
    {
        Clear();
        return;
    }
 
    // there is only a new current controller
    if ( m_xControllers != xControllers )
    {
        Clear();
 
        m_xControllers = xControllers;
        Update(m_xControllers, this);
 
        DBG_ASSERT(xCurrent.is(), "FmFilterModel::Update(...) no current controller");
 
        // Listening for TextChanges
        m_pAdapter = new FmFilterAdapter(this, xControllers);
 
        SetCurrentController(xCurrent);
        EnsureEmptyFilterRows( *this );
    }
    else
        SetCurrentController(xCurrent);
}
 
 
void FmFilterModel::Update(const Reference< XIndexAccess > & xControllers, FmParentData* pParent)
{
    try
    {
        sal_Int32 nCount = xControllers->getCount();
        for ( sal_Int32 i = 0; i < nCount; ++i )
        {
            Reference< XFormController > xController( xControllers->getByIndex(i), UNO_QUERY_THROW );
 
            Reference< XPropertySet > xFormProperties( xController->getModel(), UNO_QUERY_THROW );
            OUString aName;
            OSL_VERIFY( xFormProperties->getPropertyValue( FM_PROP_NAME ) >>= aName );
 
            // Insert a new item for the form
            FmFormItem* pFormItem = new FmFormItem( pParent, xController, aName );
            Insert( pParent->GetChildren().end(), std::unique_ptr<FmFilterData>(pFormItem) );
 
            Reference< XFilterController > xFilterController( pFormItem->GetFilterController(), UNO_SET_THROW );
 
            // insert the existing filters for the form
            OUString aTitle(SvxResId(RID_STR_FILTER_FILTER_FOR));
 
            const Sequence< Sequence< OUString > > aExpressions = xFilterController->getPredicateExpressions();
            for ( auto const & conjunctionTerm : aExpressions )
            {
                // we always display one row, even if there's no term to be displayed
                FmFilterItems* pFilterItems = new FmFilterItems( pFormItem, aTitle );
                Insert( pFormItem->GetChildren().end(), std::unique_ptr<FmFilterData>(pFilterItems) );
 
                const Sequence< OUString >& rDisjunction( conjunctionTerm );
                sal_Int32 nComponentIndex = -1;
                for ( const OUString& rDisjunctiveTerm : rDisjunction )
                {
                    ++nComponentIndex;
 
                    if ( rDisjunctiveTerm.isEmpty() )
                        // no condition for this particular component in this particular conjunction term
                        continue;
 
                    // determine the display name of the control
                    const Reference< XControl > xFilterControl( xFilterController->getFilterComponent( nComponentIndex ) );
                    const OUString sDisplayName( lcl_getLabelName_nothrow( xFilterControl ) );
 
                    // insert a new entry
                    std::unique_ptr<FmFilterItem> pANDCondition(new FmFilterItem( pFilterItems, sDisplayName, rDisjunctiveTerm, nComponentIndex ));
                    Insert( pFilterItems->GetChildren().end(), std::move(pANDCondition) );
                }
 
                // title for the next conditions
                aTitle = SvxResId( RID_STR_FILTER_FILTER_OR );
            }
 
            // now add dependent controllers
            Update( xController, pFormItem );
        }
    }
    catch( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION("svx");
    }
}
 
 
FmFormItem* FmFilterModel::Find(const ::std::vector<std::unique_ptr<FmFilterData>>& rItems, const Reference< XFormController > & xController) const
{
    for (const auto& rItem : rItems)
    {
        FmFormItem* pForm = dynamic_cast<FmFormItem*>( rItem.get() );
        if (pForm)
        {
            if ( xController == pForm->GetController() )
                return pForm;
            else
            {
                pForm = Find(pForm->GetChildren(), xController);
                if (pForm)
                    return pForm;
            }
        }
    }
    return nullptr;
}
 
 
FmFormItem* FmFilterModel::Find(const ::std::vector<std::unique_ptr<FmFilterData>>& rItems, const Reference< XForm >& xForm) const
{
    for (const auto& rItem : rItems)
    {
        FmFormItem* pForm = dynamic_cast<FmFormItem*>( rItem.get() );
        if (pForm)
        {
            if (xForm == pForm->GetController()->getModel())
                return pForm;
            else
            {
                pForm = Find(pForm->GetChildren(), xForm);
                if (pForm)
                    return pForm;
            }
        }
    }
    return nullptr;
}
 
void FmFilterModel::SetCurrentController(const Reference< XFormController > & xCurrent)
{
    if ( xCurrent == m_xController )
        return;
 
    m_xController = xCurrent;
 
    FmFormItem* pItem = Find( m_aChildren, xCurrent );
    if ( !pItem )
        return;
 
    try
    {
        Reference< XFilterController > xFilterController( m_xController, UNO_QUERY_THROW );
        const sal_Int32 nActiveTerm( xFilterController->getActiveTerm() );
        if (nActiveTerm != -1 && pItem->GetChildren().size() > o3tl::make_unsigned(nActiveTerm))
        {
            SetCurrentItems( static_cast< FmFilterItems* >( pItem->GetChildren()[ nActiveTerm ].get() ) );
        }
    }
    catch( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION("svx");
    }
}
 
void FmFilterModel::AppendFilterItems( FmFormItem& _rFormItem )
{
    // insert the condition behind the last filter items
    auto iter = std::find_if(_rFormItem.GetChildren().rbegin(), _rFormItem.GetChildren().rend(),
        [](const std::unique_ptr<FmFilterData>& rChild) { return dynamic_cast<const FmFilterItems*>(rChild.get()) != nullptr; });
 
    sal_Int32 nInsertPos = iter.base() - _rFormItem.GetChildren().begin();
    // delegate this to the FilterController, it will notify us, which will let us update our model
    try
    {
        Reference< XFilterController > xFilterController( _rFormItem.GetFilterController(), UNO_SET_THROW );
        if ( nInsertPos >= xFilterController->getDisjunctiveTerms() )
            xFilterController->appendEmptyDisjunctiveTerm();
    }
    catch( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION("svx");
    }
}
 
void FmFilterModel::Insert(const ::std::vector<std::unique_ptr<FmFilterData>>::iterator& rPos, std::unique_ptr<FmFilterData> pData)
{
    auto pTemp = pData.get();
    size_t nPos;
    ::std::vector<std::unique_ptr<FmFilterData>>& rItems = pData->GetParent()->GetChildren();
    if (rPos == rItems.end())
    {
        nPos = rItems.size();
        rItems.push_back(std::move(pData));
    }
    else
    {
        nPos = rPos - rItems.begin();
        rItems.insert(rPos, std::move(pData));
    }
 
    // notify the UI
    FmFilterInsertedHint aInsertedHint(pTemp, nPos);
    Broadcast( aInsertedHint );
}
 
void FmFilterModel::Remove(FmFilterData* pData)
{
    FmParentData* pParent = pData->GetParent();
    ::std::vector<std::unique_ptr<FmFilterData>>& rItems = pParent->GetChildren();
 
    // erase the item from the model
    auto i = ::std::find_if(rItems.begin(), rItems.end(),
            [&](const std::unique_ptr<FmFilterData>& p) { return p.get() == pData; } );
    DBG_ASSERT(i != rItems.end(), "FmFilterModel::Remove(): unknown Item");
    // position within the parent
    sal_Int32 nPos = i - rItems.begin();
    if (auto pFilterItems = dynamic_cast<FmFilterItems*>( pData))
    {
        FmFormItem* pFormItem = static_cast<FmFormItem*>(pParent);
 
        try
        {
            Reference< XFilterController > xFilterController( pFormItem->GetFilterController(), UNO_SET_THROW );
 
            bool bEmptyLastTerm = ( ( nPos == 0 ) && xFilterController->getDisjunctiveTerms() == 1 );
            if ( bEmptyLastTerm )
            {
                // remove all children (by setting an empty predicate expression)
                ::std::vector< std::unique_ptr<FmFilterData> >& rChildren = pFilterItems->GetChildren();
                while ( !rChildren.empty() )
                {
                    auto removePos = rChildren.end() - 1;
                    if (FmFilterItem* pFilterItem = dynamic_cast<FmFilterItem*>( removePos->get() ))
                    {
                        FmFilterAdapter::setText( nPos, pFilterItem, OUString() );
                    }
                    Remove( removePos );
                }
            }
            else
            {
                xFilterController->removeDisjunctiveTerm( nPos );
            }
        }
        catch( const Exception& )
        {
            DBG_UNHANDLED_EXCEPTION("svx");
        }
    }
    else // FormItems can not be deleted
    {
        FmFilterItem& rFilterItem = dynamic_cast<FmFilterItem&>(*pData);
 
        // if it's the last condition remove the parent
        if (rItems.size() == 1)
            Remove(rFilterItem.GetParent());
        else
        {
            // find the position of the father within his father
            ::std::vector<std::unique_ptr<FmFilterData>>& rParentParentItems = pData->GetParent()->GetParent()->GetChildren();
            auto j = ::std::find_if(rParentParentItems.begin(), rParentParentItems.end(),
                [&](const std::unique_ptr<FmFilterData>& p) { return p.get() == rFilterItem.GetParent(); });
            DBG_ASSERT(j != rParentParentItems.end(), "FmFilterModel::Remove(): unknown Item");
            sal_Int32 nParentPos = j - rParentParentItems.begin();
 
            // EmptyText removes the filter
            FmFilterAdapter::setText(nParentPos, &rFilterItem, OUString());
            Remove( i );
        }
    }
}
 
void FmFilterModel::Remove( const ::std::vector<std::unique_ptr<FmFilterData>>::iterator& rPos )
{
    // remove from parent's child list
    std::unique_ptr<FmFilterData> pData = std::move(*rPos);
    pData->GetParent()->GetChildren().erase( rPos );
 
    // notify the view, this will remove the actual SvTreeListEntry
    FmFilterRemovedHint aRemoveHint( pData.get() );
    Broadcast( aRemoveHint );
}
 
 
bool FmFilterModel::ValidateText(FmFilterItem const * pItem, OUString& rText, OUString& rErrorMsg) const
{
    FmFormItem* pFormItem = dynamic_cast<FmFormItem*>( pItem->GetParent()->GetParent()  );
    assert(pFormItem);
    try
    {
        Reference< XFormController > xFormController( pFormItem->GetController() );
        // obtain the connection of the form belonging to the controller
        Reference< XRowSet > xRowSet( xFormController->getModel(), UNO_QUERY_THROW );
        Reference< XConnection > xConnection( getConnection( xRowSet ) );
 
        // obtain a number formatter for this connection
        // TODO: shouldn't this be cached?
        Reference< XNumberFormatsSupplier > xFormatSupplier = getNumberFormats( xConnection, true );
        Reference< XNumberFormatter > xFormatter( NumberFormatter::create( comphelper::getProcessComponentContext() ), UNO_QUERY_THROW );
        xFormatter->attachNumberFormatsSupplier( xFormatSupplier );
 
        // get the field (database column) which the item is responsible for
        Reference< XFilterController > xFilterController( xFormController, UNO_QUERY_THROW );
        Reference< XPropertySet > xField( lcl_getBoundField_nothrow( xFilterController->getFilterComponent( pItem->GetComponentIndex() ) ), UNO_SET_THROW );
 
        // parse the given text as filter predicate
        OUString aErr, aTxt( rText );
        std::unique_ptr< OSQLParseNode > pParseNode = predicateTree( aErr, aTxt, xFormatter, xField );
        rErrorMsg = aErr;
        rText = aTxt;
        if ( pParseNode != nullptr )
        {
            OUString aPreparedText;
            Locale aAppLocale = Application::GetSettings().GetUILanguageTag().getLocale();
            pParseNode->parseNodeToPredicateStr(
                aPreparedText, xConnection, xFormatter, xField, OUString(), aAppLocale, u"."_ustr, getParseContext() );
            rText = aPreparedText;
            return true;
        }
    }
    catch( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION("svx");
    }
 
    return false;
}
 
 
void FmFilterModel::Append(FmFilterItems* pItems, std::unique_ptr<FmFilterItem> pFilterItem)
{
    Insert(pItems->GetChildren().end(), std::move(pFilterItem));
}
 
 
void FmFilterModel::SetTextForItem(FmFilterItem* pItem, const OUString& rText)
{
    ::std::vector<std::unique_ptr<FmFilterData>>& rItems = pItem->GetParent()->GetParent()->GetChildren();
    auto i = ::std::find_if(rItems.begin(), rItems.end(),
                [&](const std::unique_ptr<FmFilterData>& p) { return p.get() == pItem->GetParent(); });
    sal_Int32 nParentPos = i - rItems.begin();
 
    FmFilterAdapter::setText(nParentPos, pItem, rText);
 
    if (rText.isEmpty())
        Remove(pItem);
    else
    {
        // Change the text
        pItem->SetText(rText);
        FmFilterTextChangedHint aChangeHint(pItem);
        Broadcast( aChangeHint );
    }
}
 
 
void FmFilterModel::SetCurrentItems(FmFilterItems* pCurrent)
{
    if (m_pCurrentItems == pCurrent)
        return;
 
    // search for the condition
    if (pCurrent)
    {
        FmFormItem* pFormItem = static_cast<FmFormItem*>(pCurrent->GetParent());
        ::std::vector<std::unique_ptr<FmFilterData>>& rItems = pFormItem->GetChildren();
        auto i = ::std::find_if(rItems.begin(), rItems.end(),
                    [&](const std::unique_ptr<FmFilterData>& p) { return p.get() == pCurrent; });
 
        if (i != rItems.end())
        {
            // determine the filter position
            sal_Int32 nPos = i - rItems.begin();
            try
            {
                Reference< XFilterController > xFilterController( pFormItem->GetFilterController(), UNO_SET_THROW );
                xFilterController->setActiveTerm( nPos );
            }
            catch( const Exception& )
            {
                DBG_UNHANDLED_EXCEPTION("svx");
            }
 
            if ( m_xController != pFormItem->GetController() )
                // calls SetCurrentItems again
                SetCurrentController( pFormItem->GetController() );
            else
                m_pCurrentItems = pCurrent;
        }
        else
            m_pCurrentItems = nullptr;
    }
    else
        m_pCurrentItems = nullptr;
 
 
    // notify the UI
    FmFilterCurrentChangedHint aHint;
    Broadcast( aHint );
}
 
 
void FmFilterModel::EnsureEmptyFilterRows( FmParentData& _rItem )
{
    // checks whether for each form there's one free level for input
    ::std::vector< std::unique_ptr<FmFilterData> >& rChildren = _rItem.GetChildren();
    bool bAppendLevel = dynamic_cast<const FmFormItem*>(&_rItem) !=  nullptr;
 
    for ( const auto& rpChild : rChildren )
    {
        FmFilterItems* pItems = dynamic_cast<FmFilterItems*>( rpChild.get() );
        if ( pItems && pItems->GetChildren().empty() )
        {
            bAppendLevel = false;
            break;
        }
 
        FmFormItem* pFormItem = dynamic_cast<FmFormItem*>( rpChild.get() );
        if (pFormItem)
        {
            EnsureEmptyFilterRows( *pFormItem );
            continue;
        }
    }
 
    if ( bAppendLevel )
    {
        FmFormItem* pFormItem = dynamic_cast<FmFormItem*>( &_rItem  );
        OSL_ENSURE( pFormItem, "FmFilterModel::EnsureEmptyFilterRows: no FmFormItem, but a FmFilterItems child?" );
        if ( pFormItem )
            AppendFilterItems( *pFormItem );
    }
}
 
const int nxD = 4;
const int nxDBmp = 12;
 
IMPL_STATIC_LINK(FmFilterNavigator, CustomGetSizeHdl, weld::TreeView::get_size_args, aPayload, Size)
{
    vcl::RenderContext& rRenderContext = aPayload.first;
    const OUString& rId = aPayload.second;
 
    Size aSize;
 
    FmFilterData* pData = weld::fromId<FmFilterData*>(rId);
    OUString sText = pData->GetText();
 
    if (FmFilterItem* pItem = dynamic_cast<FmFilterItem*>(pData))
    {
        rRenderContext.Push(vcl::PushFlags::FONT);
        vcl::Font aFont(rRenderContext.GetFont());
        aFont.SetWeight(WEIGHT_BOLD);
        rRenderContext.SetFont(aFont);
 
        OUString sName = pItem->GetFieldName() + ": ";
        aSize = Size(rRenderContext.GetTextWidth(sName), rRenderContext.GetTextHeight());
 
        rRenderContext.Pop();
 
        aSize.AdjustWidth(rRenderContext.GetTextWidth(sText) + nxD);
    }
    else
    {
        aSize = Size(rRenderContext.GetTextWidth(sText), rRenderContext.GetTextHeight());
        if (dynamic_cast<FmFilterItems*>(pData))
            aSize.AdjustWidth(nxDBmp);
    }
 
    return aSize;
}
 
IMPL_STATIC_LINK(FmFilterNavigator, CustomRenderHdl, weld::TreeView::render_args, aPayload, void)
{
    vcl::RenderContext& rRenderContext = std::get<0>(aPayload);
    const ::tools::Rectangle& rRect = std::get<1>(aPayload);
    ::tools::Rectangle aRect(rRect.TopLeft(), Size(rRenderContext.GetOutputSize().Width() - rRect.Left(), rRect.GetHeight()));
    bool bSelected = std::get<2>(aPayload);
    const OUString& rId = std::get<3>(aPayload);
 
    rRenderContext.Push(vcl::PushFlags::TEXTCOLOR);
    const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
    if (bSelected)
        rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor());
    else
        rRenderContext.SetTextColor(rStyleSettings.GetDialogTextColor());
 
    FmFilterData* pData = weld::fromId<FmFilterData*>(rId);
    OUString sText = pData->GetText();
    Point aPos(aRect.TopLeft());
 
    if (FmFilterItem* pFilter = dynamic_cast<FmFilterItem*>(pData))
    {
        vcl::Font aFont(rRenderContext.GetFont());
        aFont.SetWeight(WEIGHT_BOLD);
 
        rRenderContext.Push(vcl::PushFlags::FONT);
        rRenderContext.SetFont(aFont);
 
        OUString sName = pFilter->GetFieldName() + ": ";
        rRenderContext.DrawText(aPos, sName);
 
        // position for the second text
        aPos.AdjustX(rRenderContext.GetTextWidth(sName) + nxD);
        rRenderContext.Pop();
 
        rRenderContext.DrawText(aPos, sText);
    }
    else if (FmFilterItems* pRow = dynamic_cast<FmFilterItems*>(pData))
    {
        FmFormItem* pForm = static_cast<FmFormItem*>(pRow->GetParent());
 
        // current filter is significant painted
        const bool bIsCurrentFilter = pForm->GetChildren()[ pForm->GetFilterController()->getActiveTerm() ].get() == pRow;
        if (bIsCurrentFilter)
        {
            rRenderContext.Push(vcl::PushFlags::LINECOLOR);
            rRenderContext.SetLineColor(rRenderContext.GetTextColor());
 
            Point aFirst(aPos.X(), aRect.Bottom() - 6);
            Point aSecond(aFirst .X() + 2, aFirst.Y() + 3);
 
            rRenderContext.DrawLine(aFirst, aSecond);
 
            aFirst = aSecond;
            aFirst.AdjustX(1);
            aSecond.AdjustX(6);
            aSecond.AdjustY(-5);
 
            rRenderContext.DrawLine(aFirst, aSecond);
            rRenderContext.Pop();
        }
 
        rRenderContext.DrawText(Point(aPos.X() + nxDBmp, aPos.Y()), sText);
    }
    else
        rRenderContext.DrawText(aPos, sText);
 
    rRenderContext.Pop();
}
 
FmFilterNavigatorDropTarget::FmFilterNavigatorDropTarget(FmFilterNavigator& rTreeView)
    : DropTargetHelper(rTreeView.get_widget().get_drop_target())
    , m_rTreeView(rTreeView)
{
}
 
sal_Int8 FmFilterNavigatorDropTarget::AcceptDrop(const AcceptDropEvent& rEvt)
{
    sal_Int8 nAccept = m_rTreeView.AcceptDrop(rEvt);
 
    if (nAccept != DND_ACTION_NONE)
    {
        // to enable the autoscroll when we're close to the edges
        weld::TreeView& rWidget = m_rTreeView.get_widget();
        rWidget.get_dest_row_at_pos(rEvt.maPosPixel, nullptr, true);
    }
 
    return nAccept;
}
 
sal_Int8 FmFilterNavigatorDropTarget::ExecuteDrop(const ExecuteDropEvent& rEvt)
{
    return m_rTreeView.ExecuteDrop(rEvt);
}
 
FmFilterNavigator::FmFilterNavigator(vcl::Window* pTopLevel, std::unique_ptr<weld::TreeView> xTreeView)
    : m_xTopLevel(pTopLevel)
    , m_xTreeView(std::move(xTreeView))
    , m_aDropTargetHelper(*this)
    , m_nAsyncRemoveEvent(nullptr)
{
    m_xTreeView->set_help_id(HID_FILTER_NAVIGATOR);
 
    m_xTreeView->set_selection_mode(SelectionMode::Multiple);
 
    m_pModel.reset( new FmFilterModel() );
    StartListening( *m_pModel );
 
    m_xTreeView->connect_custom_get_size(LINK(this, FmFilterNavigator, CustomGetSizeHdl));
    m_xTreeView->connect_custom_render(LINK(this, FmFilterNavigator, CustomRenderHdl));
    m_xTreeView->set_column_custom_renderer(1, true);
 
    m_xTreeView->connect_changed(LINK(this, FmFilterNavigator, SelectHdl));
    m_xTreeView->connect_key_press(LINK(this, FmFilterNavigator, KeyInputHdl));
    m_xTreeView->connect_popup_menu(LINK(this, FmFilterNavigator, PopupMenuHdl));
    m_xTreeView->connect_editing(LINK(this, FmFilterNavigator, EditingEntryHdl),
                                 LINK(this, FmFilterNavigator, EditedEntryHdl));
    m_xTreeView->connect_drag_begin(LINK(this, FmFilterNavigator, DragBeginHdl));
}
 
FmFilterNavigator::~FmFilterNavigator()
{
    if (m_nAsyncRemoveEvent)
        Application::RemoveUserEvent(m_nAsyncRemoveEvent);
    EndListening(*m_pModel);
    m_pModel.reset();
}
 
void FmFilterNavigator::UpdateContent(const Reference< XIndexAccess > & xControllers, const Reference< XFormController > & xCurrent)
{
    if (xCurrent == m_pModel->GetCurrentController())
        return;
 
    m_pModel->Update(xControllers, xCurrent);
 
    // expand the filters for the current controller
    std::unique_ptr<weld::TreeIter> xEntry = FindEntry(m_pModel->GetCurrentForm());
    if (!xEntry || m_xTreeView->get_row_expanded(*xEntry))
        return;
 
    m_xTreeView->unselect_all();
 
    m_xTreeView->expand_row(*xEntry);
 
    xEntry = FindEntry(m_pModel->GetCurrentItems());
    if (xEntry)
    {
        if (!m_xTreeView->get_row_expanded(*xEntry))
            m_xTreeView->expand_row(*xEntry);
        m_xTreeView->select(*xEntry);
        SelectHdl(*m_xTreeView);
    }
}
 
IMPL_LINK(FmFilterNavigator, EditingEntryHdl, const weld::TreeIter&, rIter, bool)
{
    // returns true to allow editing
    if (dynamic_cast<const FmFilterItem*>(weld::fromId<FmFilterData*>(m_xTreeView->get_id(rIter))))
    {
        m_xEditingCurrently = m_xTreeView->make_iterator(&rIter);
        return true;
    }
    m_xEditingCurrently.reset();
    return false;
}
 
IMPL_LINK(FmFilterNavigator, EditedEntryHdl, const IterString&, rIterString, bool)
{
    const weld::TreeIter& rIter = rIterString.first;
    const OUString& rNewText = rIterString.second;
 
    assert(m_xEditingCurrently && m_xTreeView->iter_compare(rIter, *m_xEditingCurrently) == 0 &&
               "FmFilterNavigator::EditedEntry: suspicious entry!");
    m_xEditingCurrently.reset();
 
    FmFilterData* pData = weld::fromId<FmFilterData*>(m_xTreeView->get_id(rIter));
 
    DBG_ASSERT(dynamic_cast<const FmFilterItem*>(pData) != nullptr,
                    "FmFilterNavigator::EditedEntry() wrong entry");
 
    OUString aText(comphelper::string::strip(rNewText, ' '));
    if (aText.isEmpty())
    {
        // deleting the entry asynchron
        m_nAsyncRemoveEvent = Application::PostUserEvent(LINK(this, FmFilterNavigator, OnRemove), pData);
    }
    else
    {
        OUString aErrorMsg;
 
        if (m_pModel->ValidateText(static_cast<FmFilterItem*>(pData), aText, aErrorMsg))
        {
            // this will set the text at the FmFilterItem, as well as update any filter controls
            // which are connected to this particular entry
            m_pModel->SetTextForItem(static_cast<FmFilterItem*>(pData), aText);
            m_xTreeView->set_text(rIter, aText);
        }
        else
        {
            // display the error and return sal_False
            SQLContext aError(SvxResId(RID_STR_SYNTAXERROR), {}, {}, 0, {}, aErrorMsg);
            displayException(aError, VCLUnoHelper::GetInterface(m_xTopLevel));
 
            return false;
        }
    }
    return true;
}
 
IMPL_LINK( FmFilterNavigator, OnRemove, void*, p, void )
{
    m_nAsyncRemoveEvent = nullptr;
    // now remove the entry
    m_pModel->Remove(static_cast<FmFilterData*>(p));
}
 
sal_Int8 FmFilterNavigator::AcceptDrop( const AcceptDropEvent& rEvt )
{
    if (!m_aControlExchange.isDragSource())
        return DND_ACTION_NONE;
 
    if (!OFilterItemExchange::hasFormat(m_aDropTargetHelper.GetDataFlavorExVector()))
        return DND_ACTION_NONE;
 
    // do we contain the formitem?
    if (!FindEntry(m_aControlExchange->getFormItem()))
        return DND_ACTION_NONE;
 
    Point aDropPos = rEvt.maPosPixel;
    std::unique_ptr<weld::TreeIter> xDropTarget(m_xTreeView->make_iterator());
    // get_dest_row_at_pos with false cause we must drop exactly "on" a node to paste a condition into it
    if (!m_xTreeView->get_dest_row_at_pos(aDropPos, xDropTarget.get(), false))
        xDropTarget.reset();
 
    if (!xDropTarget)
        return DND_ACTION_NONE;
 
    FmFilterData* pData = weld::fromId<FmFilterData*>(m_xTreeView->get_id(*xDropTarget));
    FmFormItem* pForm = nullptr;
    if (dynamic_cast<const FmFilterItem*>(pData) !=  nullptr)
    {
        pForm = dynamic_cast<FmFormItem*>( pData->GetParent()->GetParent() );
        if (pForm != m_aControlExchange->getFormItem())
            return DND_ACTION_NONE;
    }
    else if (dynamic_cast<const FmFilterItems*>( pData) !=  nullptr)
    {
        pForm = dynamic_cast<FmFormItem*>( pData->GetParent() );
        if (pForm != m_aControlExchange->getFormItem())
            return DND_ACTION_NONE;
    }
    else
        return DND_ACTION_NONE;
 
    return rEvt.mnAction;
}
 
namespace
{
    FmFilterItems* getTargetItems(const weld::TreeView& rTreeView, const weld::TreeIter& rTarget)
    {
        FmFilterData* pData = weld::fromId<FmFilterData*>(rTreeView.get_id(rTarget));
        FmFilterItems*  pTargetItems = dynamic_cast<FmFilterItems*>(pData);
        if (!pTargetItems)
            pTargetItems = dynamic_cast<FmFilterItems*>(pData->GetParent());
        return pTargetItems;
    }
}
 
sal_Int8 FmFilterNavigator::ExecuteDrop( const ExecuteDropEvent& rEvt )
{
    if (!m_aControlExchange.isDragSource())
        return DND_ACTION_NONE;
 
    Point aDropPos = rEvt.maPosPixel;
    std::unique_ptr<weld::TreeIter> xDropTarget(m_xTreeView->make_iterator());
    // get_dest_row_at_pos with false cause we must drop exactly "on" a node to paste a condition into it
    if (!m_xTreeView->get_dest_row_at_pos(aDropPos, xDropTarget.get(), false))
        xDropTarget.reset();
    if (!xDropTarget)
        return DND_ACTION_NONE;
 
    // search the container where to add the items
    FmFilterItems* pTargetItems = getTargetItems(*m_xTreeView, *xDropTarget);
    m_xTreeView->unselect_all();
    std::unique_ptr<weld::TreeIter> xEntry = FindEntry(pTargetItems);
    if (xEntry)
    {
        m_xTreeView->select(*xEntry);
        m_xTreeView->set_cursor(*xEntry);
    }
 
    insertFilterItem(m_aControlExchange->getDraggedEntries(),pTargetItems,DND_ACTION_COPY == rEvt.mnAction);
 
    return DND_ACTION_COPY;
}
 
IMPL_LINK_NOARG(FmFilterNavigator, SelectHdl, weld::TreeView&, void)
{
    std::unique_ptr<weld::TreeIter> xIter(m_xTreeView->make_iterator());
    if (!m_xTreeView->get_selected(xIter.get()))
        return;
 
    FmFilterData* pData = weld::fromId<FmFilterData*>(m_xTreeView->get_id(*xIter));
 
    FmFormItem* pFormItem = nullptr;
    if (FmFilterItem* pItem = dynamic_cast<FmFilterItem*>(pData))
        pFormItem = static_cast<FmFormItem*>(pItem->GetParent()->GetParent());
    else if (FmFilterItems* pItems = dynamic_cast<FmFilterItems*>(pData))
        pFormItem = static_cast<FmFormItem*>(pItems->GetParent()->GetParent());
    else
        pFormItem = dynamic_cast<FmFormItem*>(pData);
 
    if (pFormItem)
    {
        // will the controller be exchanged?
        if (FmFilterItem* pItem = dynamic_cast<FmFilterItem*>(pData))
            m_pModel->SetCurrentItems(static_cast<FmFilterItems*>(pItem->GetParent()));
        else if (FmFilterItems* pItems = dynamic_cast<FmFilterItems*>(pData))
            m_pModel->SetCurrentItems(pItems);
        else
            m_pModel->SetCurrentController(pFormItem->GetController());
    }
}
 
void FmFilterNavigator::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint )
{
    if (rHint.GetId() == SfxHintId::FmFilterInserted)
    {
        const FmFilterInsertedHint* pInsertHint = static_cast<const FmFilterInsertedHint*>(&rHint);
        Insert(pInsertHint->GetData(), pInsertHint->GetPos());
    }
    else if( rHint.GetId() == SfxHintId::FilterClearing )
    {
        m_xTreeView->clear();
    }
    else if ( rHint.GetId() == SfxHintId::FmFilterRemoved )
    {
        const FmFilterRemovedHint* pRemoveHint = static_cast<const FmFilterRemovedHint*>(&rHint);
        Remove(pRemoveHint->GetData());
    }
    else if ( rHint.GetId() == SfxHintId::FmFilterTextChanged )
    {
        const FmFilterTextChangedHint *pChangeHint = static_cast<const FmFilterTextChangedHint*>(&rHint);
        std::unique_ptr<weld::TreeIter> xEntry = FindEntry(pChangeHint->GetData());
        if (xEntry)
            m_xTreeView->set_text(*xEntry, pChangeHint->GetData()->GetText());
    }
    else if( rHint.GetId() == SfxHintId::FmFilterCurrentChanged )
    {
        m_xTreeView->queue_draw();
    }
}
 
std::unique_ptr<weld::TreeIter> FmFilterNavigator::FindEntry(const FmFilterData* pItem) const
{
    if (!pItem)
        return nullptr;
    std::unique_ptr<weld::TreeIter> xEntry = m_xTreeView->make_iterator();
    if (!m_xTreeView->get_iter_first(*xEntry))
        return nullptr;
    do
    {
        FmFilterData* pEntryItem = weld::fromId<FmFilterData*>(m_xTreeView->get_id(*xEntry));
        if (pEntryItem == pItem)
            return xEntry;
    }
    while (m_xTreeView->iter_next(*xEntry));
 
    return nullptr;
}
 
void FmFilterNavigator::Insert(const FmFilterData* pItem, int nPos)
{
    const FmParentData* pParent = pItem->GetParent() ? pItem->GetParent() : m_pModel.get();
 
    // insert the item
    std::unique_ptr<weld::TreeIter> xParentEntry = FindEntry(pParent);
 
    OUString sId(weld::toId(pItem));
    std::unique_ptr<weld::TreeIter> xRet(m_xTreeView->make_iterator());
    m_xTreeView->insert(xParentEntry.get(), nPos, &pItem->GetText(), &sId,
                        nullptr, nullptr, false, xRet.get());
    m_xTreeView->set_image(*xRet, pItem->GetImage());
 
    if (!xParentEntry)
        return;
    m_xTreeView->expand_row(*xParentEntry);
}
 
void FmFilterNavigator::EndEditing()
{
    if (m_xEditingCurrently)
    {
        // end editing
        m_xTreeView->end_editing();
        m_xEditingCurrently.reset();
    }
}
 
void FmFilterNavigator::Remove(FmFilterData const * pItem)
{
    // the entry for the data
    std::unique_ptr<weld::TreeIter> xEntry = FindEntry(pItem);
    if (!xEntry)
        return;
 
    if (m_xEditingCurrently && m_xTreeView->iter_compare(*xEntry, *m_xEditingCurrently) == 0)
        EndEditing();
 
    m_xTreeView->remove(*xEntry);
}
 
FmFormItem* FmFilterNavigator::getSelectedFilterItems(::std::vector<FmFilterItem*>& _rItemList)
{
    // be sure that the data is only used within only one form!
    FmFormItem* pFirstItem = nullptr;
 
    bool bHandled = true;
    bool bFoundSomething = false;
 
    m_xTreeView->selected_foreach([this, &bHandled, &bFoundSomething, &pFirstItem, &_rItemList](weld::TreeIter& rEntry) {
        FmFilterData* pFilterEntry = weld::fromId<FmFilterData*>(m_xTreeView->get_id(rEntry));
        FmFilterItem* pFilter = dynamic_cast<FmFilterItem*>(pFilterEntry);
        if (pFilter)
        {
            FmFormItem* pForm = dynamic_cast<FmFormItem*>( pFilter->GetParent()->GetParent() );
            if (!pForm)
                bHandled = false;
            else if (!pFirstItem)
                pFirstItem = pForm;
            else if (pFirstItem != pForm)
                bHandled = false;
 
            if (bHandled)
            {
                _rItemList.push_back(pFilter);
                bFoundSomething = true;
            }
        }
        return !bHandled;
    });
 
    if ( !bHandled || !bFoundSomething )
        pFirstItem = nullptr;
    return pFirstItem;
}
 
void FmFilterNavigator::insertFilterItem(const ::std::vector<FmFilterItem*>& _rFilterList,FmFilterItems* _pTargetItems,bool _bCopy)
{
    for (FmFilterItem* pLookupItem : _rFilterList)
    {
        if ( pLookupItem->GetParent() == _pTargetItems )
            continue;
 
        FmFilterItem* pFilterItem = _pTargetItems->Find( pLookupItem->GetComponentIndex() );
        OUString aText = pLookupItem->GetText();
        if ( !pFilterItem )
        {
            pFilterItem = new FmFilterItem( _pTargetItems, pLookupItem->GetFieldName(), aText, pLookupItem->GetComponentIndex() );
            m_pModel->Append( _pTargetItems, std::unique_ptr<FmFilterItem>(pFilterItem) );
        }
 
        if ( !_bCopy )
            m_pModel->Remove( pLookupItem );
 
        // now set the text for the new dragged item
        m_pModel->SetTextForItem( pFilterItem, aText );
    }
 
    m_pModel->EnsureEmptyFilterRows( *_pTargetItems->GetParent() );
}
 
IMPL_LINK(FmFilterNavigator, DragBeginHdl, bool&, rUnsetDragIcon, bool)
{
    rUnsetDragIcon = false;
 
    // be sure that the data is only used within an only one form!
    m_aControlExchange.prepareDrag();
 
    ::std::vector<FmFilterItem*> aItemList;
    if (FmFormItem* pFirstItem = getSelectedFilterItems(aItemList))
    {
        m_aControlExchange->setDraggedEntries(std::move(aItemList));
        m_aControlExchange->setFormItem(pFirstItem);
 
        OFilterItemExchange& rExchange = *m_aControlExchange;
        rtl::Reference<TransferDataContainer> xHelper(&rExchange);
        m_xTreeView->enable_drag_source(xHelper, DND_ACTION_COPYMOVE);
        rExchange.setDragging(true);
 
        return false;
    }
    return true;
}
 
IMPL_LINK(FmFilterNavigator, PopupMenuHdl, const CommandEvent&, rEvt, bool)
{
    bool bHandled = false;
    switch (rEvt.GetCommand())
    {
        case CommandEventId::ContextMenu:
        {
            // the place where it was clicked
            Point aWhere;
            std::unique_ptr<weld::TreeIter> xClicked(m_xTreeView->make_iterator());
            if (rEvt.IsMouseEvent())
            {
                aWhere = rEvt.GetMousePosPixel();
                if (!m_xTreeView->get_dest_row_at_pos(aWhere, xClicked.get(), false))
                    break;
 
                if (!m_xTreeView->is_selected(*xClicked))
                {
                    m_xTreeView->unselect_all();
                    m_xTreeView->select(*xClicked);
                    m_xTreeView->set_cursor(*xClicked);
                }
            }
            else
            {
                if (!m_xTreeView->get_cursor(xClicked.get()))
                    break;
                aWhere = m_xTreeView->get_row_area(*xClicked).Center();
            }
 
            ::std::vector<FmFilterData*> aSelectList;
            m_xTreeView->selected_foreach([this, &aSelectList](weld::TreeIter& rEntry) {
                FmFilterData* pFilterEntry = weld::fromId<FmFilterData*>(m_xTreeView->get_id(rEntry));
 
                // don't delete forms
                FmFormItem* pForm = dynamic_cast<FmFormItem*>(pFilterEntry);
                if (!pForm)
                    aSelectList.push_back(pFilterEntry);
 
                return false;
            });
 
            if (aSelectList.size() == 1)
            {
                // don't delete the only empty row of a form
                FmFilterItems* pFilterItems = dynamic_cast<FmFilterItems*>( aSelectList[0] );
                if (pFilterItems && pFilterItems->GetChildren().empty()
                    && pFilterItems->GetParent()->GetChildren().size() == 1)
                    aSelectList.clear();
            }
 
            std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(m_xTreeView.get(), u"svx/ui/filtermenu.ui"_ustr));
            std::unique_ptr<weld::Menu> xContextMenu(xBuilder->weld_menu(u"menu"_ustr));
 
            // every condition could be deleted except the first one if it's the only one
            bool bNoDelete = false;
            if (aSelectList.empty())
            {
                bNoDelete = true;
                xContextMenu->remove(u"delete"_ustr);
            }
 
            FmFilterData* pFilterEntry = weld::fromId<FmFilterData*>(m_xTreeView->get_id(*xClicked));
            auto pFilterItem = dynamic_cast<FmFilterItem*>(pFilterEntry);
            bool bEdit = pFilterItem &&
                m_xTreeView->is_selected(*xClicked) && m_xTreeView->count_selected_rows() == 1;
 
            if (bNoDelete && !bEdit)
            {
                // nothing is in the menu, don't bother
                return true;
            }
 
            if (!bEdit)
            {
                xContextMenu->remove(u"edit"_ustr);
                xContextMenu->remove(u"isnull"_ustr);
                xContextMenu->remove(u"isnotnull"_ustr);
            }
 
            OUString sIdent = xContextMenu->popup_at_rect(m_xTreeView.get(), tools::Rectangle(aWhere, ::Size(1, 1)));
            if (sIdent == "edit")
            {
                m_xTreeView->start_editing(*xClicked);
            }
            else if (sIdent == "isnull")
            {
                OUString aErrorMsg;
                OUString aText = u"IS NULL"_ustr;
                assert(pFilterItem && "if item is null this menu entry was removed and unavailable");
                m_pModel->ValidateText(pFilterItem, aText, aErrorMsg);
                m_pModel->SetTextForItem(pFilterItem, aText);
            }
            else if (sIdent == "isnotnull")
            {
                OUString aErrorMsg;
                OUString aText = u"IS NOT NULL"_ustr;
 
                assert(pFilterItem && "if item is null this menu entry was removed and unavailable");
                m_pModel->ValidateText(pFilterItem, aText, aErrorMsg);
                m_pModel->SetTextForItem(pFilterItem, aText);
            }
            else if (sIdent == "delete")
            {
                DeleteSelection();
            }
            bHandled = true;
        }
        break;
        default: break;
    }
 
    return bHandled;
}
 
bool FmFilterNavigator::getNextEntry(weld::TreeIter& rEntry)
{
    bool bEntry = m_xTreeView->iter_next(rEntry);
    // we need the next filter entry
    if (bEntry)
    {
        while (!m_xTreeView->iter_has_child(rEntry))
        {
            std::unique_ptr<weld::TreeIter> xNext = m_xTreeView->make_iterator(&rEntry);
            if (!m_xTreeView->iter_next(*xNext))
                break;
            m_xTreeView->copy_iterator(*xNext, rEntry);
        }
    }
    return bEntry;
}
 
bool FmFilterNavigator::getPrevEntry(weld::TreeIter& rEntry)
{
    bool bEntry = m_xTreeView->iter_previous(rEntry);
    // check if the previous entry is a filter, if so get the next prev
    if (bEntry && m_xTreeView->iter_has_child(rEntry))
    {
        bEntry = m_xTreeView->iter_previous(rEntry);
        // if the entry is still no leaf return
        if (bEntry && m_xTreeView->iter_has_child(rEntry))
            bEntry = false;
    }
    return bEntry;
}
IMPL_LINK(FmFilterNavigator, KeyInputHdl, const ::KeyEvent&, rKEvt, bool)
{
    const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode();
 
    switch ( rKeyCode.GetCode() )
    {
        case KEY_UP:
        case KEY_DOWN:
        {
            if ( !rKeyCode.IsMod1() || !rKeyCode.IsMod2() || rKeyCode.IsShift() )
                break;
 
            ::std::vector<FmFilterItem*> aItemList;
            if ( !getSelectedFilterItems( aItemList ) )
                break;
 
 
            std::vector<std::unique_ptr<weld::TreeIter>> aSelected;
            m_xTreeView->selected_foreach([this, &aSelected](weld::TreeIter& rEntry){
                aSelected.emplace_back(m_xTreeView->make_iterator(&rEntry));
                return false;
            });
 
            std::unique_ptr<weld::TreeIter> xTarget;
            ::std::function<bool(FmFilterNavigator*, weld::TreeIter&)> getter;
 
            if (rKeyCode.GetCode() == KEY_UP)
            {
                xTarget = m_xTreeView->make_iterator(aSelected.front().get());
                getter = ::std::mem_fn(&FmFilterNavigator::getPrevEntry);
            }
            else
            {
                xTarget = m_xTreeView->make_iterator(aSelected.back().get());
                getter = ::std::mem_fn(&FmFilterNavigator::getNextEntry);
            }
 
            bool bTarget = getter(this, *xTarget);
            if (!bTarget)
                break;
 
            FmFilterItems* pTargetItems = getTargetItems(*m_xTreeView, *xTarget);
            if (!pTargetItems)
                break;
 
            ::std::vector<FmFilterItem*>::const_iterator aEnd = aItemList.end();
            bool bNextTargetItem = true;
            while ( bNextTargetItem )
            {
                ::std::vector<FmFilterItem*>::const_iterator i = aItemList.begin();
                for (; i != aEnd; ++i)
                {
                    if ( (*i)->GetParent() == pTargetItems )
                    {
                        bTarget = getter(this, *xTarget);
                        if (!bTarget)
                            return true;
                        pTargetItems = getTargetItems(*m_xTreeView, *xTarget);
                        break;
                    }
                    else
                    {
                        FmFilterItem* pFilterItem = pTargetItems->Find( (*i)->GetComponentIndex() );
                        // we found the text component so jump above
                        if ( pFilterItem )
                        {
                            bTarget = getter(this, *xTarget);
                            if (!bTarget)
                                return true;
 
                            pTargetItems = getTargetItems(*m_xTreeView, *xTarget);
                            break;
                        }
                    }
                }
                bNextTargetItem = i != aEnd && pTargetItems;
            }
 
            if ( pTargetItems )
            {
                insertFilterItem( aItemList, pTargetItems, false );
                return true;
            }
        }
        break;
 
        case KEY_DELETE:
        {
            if ( rKeyCode.GetModifier() )
                break;
 
            std::unique_ptr<weld::TreeIter> xEntry = m_xTreeView->make_iterator();
            if (m_xTreeView->get_iter_first(*xEntry) && !m_xTreeView->is_selected(*xEntry))
                DeleteSelection();
 
            return true;
        }
    }
 
    return false;
}
 
void FmFilterNavigator::DeleteSelection()
{
    // to avoid the deletion of an entry twice (e.g. deletion of a parent and afterward
    // the deletion of its child, I have to shrink the selection list
    std::vector<FmFilterData*> aEntryList;
 
    m_xTreeView->selected_foreach([this, &aEntryList](weld::TreeIter& rEntry) {
        FmFilterData* pFilterEntry = weld::fromId<FmFilterData*>(m_xTreeView->get_id(rEntry));
 
        if (dynamic_cast<FmFilterItem*>(pFilterEntry))
        {
            std::unique_ptr<weld::TreeIter> xParent(m_xTreeView->make_iterator(&rEntry));
            if (m_xTreeView->iter_parent(*xParent) && m_xTreeView->is_selected(*xParent))
                return false;
        }
 
        FmFormItem* pForm = dynamic_cast<FmFormItem*>(pFilterEntry);
        if (!pForm)
            aEntryList.emplace_back(pFilterEntry);
 
        return false;
    });
 
    // Remove the selection
    m_xTreeView->unselect_all();
 
    for (auto i = aEntryList.rbegin(); i != aEntryList.rend(); ++i)
        m_pModel->Remove(*i);
}
 
FmFilterNavigatorWin::FmFilterNavigatorWin(SfxBindings* _pBindings, SfxChildWindow* _pMgr,
                                           vcl::Window* _pParent)
    : SfxDockingWindow(_pBindings, _pMgr, _pParent, u"FilterNavigator"_ustr, u"svx/ui/filternavigator.ui"_ustr)
    , SfxControllerItem( SID_FM_FILTER_NAVIGATOR_CONTROL, *_pBindings )
    , m_xNavigatorTree(new FmFilterNavigator(this, m_xBuilder->weld_tree_view(u"treeview"_ustr)))
{
    SetHelpId( HID_FILTER_NAVIGATOR_WIN );
 
    SetText( SvxResId(RID_STR_FILTER_NAVIGATOR) );
    SfxDockingWindow::SetFloatingSize( Size(200,200) );
}
 
FmFilterNavigatorWin::~FmFilterNavigatorWin()
{
    disposeOnce();
}
 
void FmFilterNavigatorWin::dispose()
{
    m_xNavigatorTree.reset();
    ::SfxControllerItem::dispose();
    SfxDockingWindow::dispose();
}
 
void FmFilterNavigatorWin::UpdateContent(FmFormShell const * pFormShell)
{
    if (!m_xNavigatorTree)
        return;
 
    if (!pFormShell)
        m_xNavigatorTree->UpdateContent( nullptr, nullptr );
    else
    {
        Reference<XFormController> const xController(pFormShell->GetImpl()->getActiveInternalController_Lock());
        Reference< XIndexAccess >   xContainer;
        if (xController.is())
        {
            Reference< XChild >  xChild = xController;
            for (Reference< XInterface >  xParent(xChild->getParent());
                 xParent.is();
                 xParent = xChild.is() ? xChild->getParent() : Reference< XInterface > ())
            {
                xContainer.set(xParent, UNO_QUERY);
                xChild.set(xParent, UNO_QUERY);
            }
        }
        m_xNavigatorTree->UpdateContent(xContainer, xController);
    }
}
 
void FmFilterNavigatorWin::StateChangedAtToolBoxControl( sal_uInt16 nSID, SfxItemState eState, const SfxPoolItem* pState )
{
    if( !pState  || SID_FM_FILTER_NAVIGATOR_CONTROL != nSID )
        return;
 
    if( eState >= SfxItemState::DEFAULT )
    {
        FmFormShell* pShell = dynamic_cast<FmFormShell*>( static_cast<const SfxObjectItem*>(pState)->GetShell()  );
        UpdateContent( pShell );
    }
    else
        UpdateContent( nullptr );
}
 
bool FmFilterNavigatorWin::Close()
{
    if (m_xNavigatorTree)
        m_xNavigatorTree->EndEditing();
 
    UpdateContent( nullptr );
    return SfxDockingWindow::Close();
}
 
void FmFilterNavigatorWin::FillInfo( SfxChildWinInfo& rInfo ) const
{
    SfxDockingWindow::FillInfo( rInfo );
    rInfo.bVisible = false;
}
 
Size FmFilterNavigatorWin::CalcDockingSize( SfxChildAlignment eAlign )
{
    if ( ( eAlign == SfxChildAlignment::TOP ) || ( eAlign == SfxChildAlignment::BOTTOM ) )
        return Size();
 
    return SfxDockingWindow::CalcDockingSize( eAlign );
}
 
SfxChildAlignment FmFilterNavigatorWin::CheckAlignment( SfxChildAlignment eActAlign, SfxChildAlignment eAlign )
{
    switch (eAlign)
    {
        case SfxChildAlignment::LEFT:
        case SfxChildAlignment::RIGHT:
        case SfxChildAlignment::NOALIGNMENT:
            return eAlign;
        default:
            break;
    }
 
    return eActAlign;
}
 
void FmFilterNavigatorWin::GetFocus()
{
    // oj #97405#
    if (m_xNavigatorTree)
        m_xNavigatorTree->GrabFocus();
    else
        SfxDockingWindow::GetFocus();
}
 
SFX_IMPL_DOCKINGWINDOW( FmFilterNavigatorWinMgr, SID_FM_FILTER_NAVIGATOR )
 
 
FmFilterNavigatorWinMgr::FmFilterNavigatorWinMgr( vcl::Window *_pParent, sal_uInt16 _nId,
                                    SfxBindings *_pBindings, SfxChildWinInfo* _pInfo )
                 :SfxChildWindow( _pParent, _nId )
{
    SetWindow( VclPtr<FmFilterNavigatorWin>::Create( _pBindings, this, _pParent ) );
    static_cast<SfxDockingWindow*>(GetWindow())->Initialize( _pInfo );
}
 
 
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V522 There might be dereferencing of a potential null pointer 'pFormItem'.

V522 Dereferencing of the null pointer 'pItem' might take place. The potential null pointer is passed into 'ValidateText' function. Inspect the first argument. Check lines: 770, 1540.

V522 There might be dereferencing of a potential null pointer 'pFormItem'.