/* -*- 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 <fmcontrolbordermanager.hxx>
#include <fmcontrollayout.hxx>
#include <formcontroller.hxx>
#include <formfeaturedispatcher.hxx>
#include <fmdocumentclassification.hxx>
#include <formcontrolling.hxx>
#include <fmprop.hxx>
#include <svx/dialmgr.hxx>
#include <svx/strings.hrc>
#include <fmservs.hxx>
#include <svx/fmtools.hxx>
#include <fmurl.hxx>
 
#include <com/sun/star/awt/FocusChangeReason.hpp>
#include <com/sun/star/awt/XCheckBox.hpp>
#include <com/sun/star/awt/XComboBox.hpp>
#include <com/sun/star/awt/XListBox.hpp>
#include <com/sun/star/awt/XVclWindowPeer.hpp>
#include <com/sun/star/awt/TabController.hpp>
#include <com/sun/star/beans/PropertyAttribute.hpp>
#include <com/sun/star/container/XIdentifierReplace.hpp>
#include <com/sun/star/form/TabulatorCycle.hpp>
#include <com/sun/star/form/validation/XValidatableFormComponent.hpp>
#include <com/sun/star/form/XBoundComponent.hpp>
#include <com/sun/star/form/XBoundControl.hpp>
#include <com/sun/star/form/XGridControl.hpp>
#include <com/sun/star/form/XLoadable.hpp>
#include <com/sun/star/form/XReset.hpp>
#include <com/sun/star/form/control/FilterControl.hpp>
#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/lang/NoSupportException.hpp>
#include <com/sun/star/sdb/ParametersRequest.hpp>
#include <com/sun/star/sdb/RowChangeAction.hpp>
#include <com/sun/star/sdb/SQLFilterOperator.hpp>
#include <com/sun/star/sdb/XInteractionSupplyParameters.hpp>
#include <com/sun/star/sdbc/ColumnValue.hpp>
#include <com/sun/star/task/InteractionHandler.hpp>
#include <com/sun/star/form/runtime/FormOperations.hpp>
#include <com/sun/star/form/runtime/FormFeature.hpp>
#include <com/sun/star/container/XContainer.hpp>
#include <com/sun/star/sdbcx/XColumnsSupplier.hpp>
#include <com/sun/star/util/NumberFormatter.hpp>
#include <com/sun/star/sdb/SQLContext.hpp>
#include <com/sun/star/sdb/XColumn.hpp>
 
#include <comphelper/enumhelper.hxx>
#include <comphelper/interaction.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/property.hxx>
#include <comphelper/sequence.hxx>
#include <comphelper/flagguard.hxx>
#include <comphelper/types.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <connectivity/IParseContext.hxx>
#include <connectivity/dbtools.hxx>
#include <connectivity/sqlparse.hxx>
#include <toolkit/controls/unocontrol.hxx>
#include <toolkit/helper/vclunohelper.hxx>
#include <tools/debug.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <unotools/localedatawrapper.hxx>
#include <vcl/svapp.hxx>
#include <vcl/settings.hxx>
#include <o3tl/safeint.hxx>
#include <osl/mutex.hxx>
#include <sal/log.hxx>
 
#include <algorithm>
#include <iterator>
 
using namespace ::com::sun::star;
using namespace ::comphelper;
using namespace ::connectivity;
using namespace ::dbtools;
 
 
css::uno::Reference< css::uno::XInterface >
    FormController_NewInstance_Impl( const css::uno::Reference< css::lang::XMultiServiceFactory > & _rxORB )
{
    return *( new ::svxform::FormController( comphelper::getComponentContext(_rxORB) ) );
}
 
namespace svxform
{
 
    using ::com::sun::star::sdb::XColumn;
    using ::com::sun::star::awt::XControl;
    using ::com::sun::star::awt::TabController;
    using ::com::sun::star::awt::XToolkit;
    using ::com::sun::star::awt::XWindowPeer;
    using ::com::sun::star::form::XGrid;
    using ::com::sun::star::beans::XPropertySet;
    using ::com::sun::star::uno::UNO_SET_THROW;
    using ::com::sun::star::uno::UNO_QUERY_THROW;
    using ::com::sun::star::container::XIndexAccess;
    using ::com::sun::star::uno::Exception;
    using ::com::sun::star::uno::XInterface;
    using ::com::sun::star::uno::UNO_QUERY;
    using ::com::sun::star::uno::Sequence;
    using ::com::sun::star::uno::Reference;
    using ::com::sun::star::beans::XPropertySetInfo;
    using ::com::sun::star::beans::PropertyValue;
    using ::com::sun::star::lang::IndexOutOfBoundsException;
    using ::com::sun::star::sdb::XInteractionSupplyParameters;
    using ::com::sun::star::awt::XTextComponent;
    using ::com::sun::star::awt::XTextListener;
    using ::com::sun::star::uno::Any;
    using ::com::sun::star::frame::XDispatch;
    using ::com::sun::star::lang::XMultiServiceFactory;
    using ::com::sun::star::uno::Type;
    using ::com::sun::star::lang::IllegalArgumentException;
    using ::com::sun::star::sdbc::XConnection;
    using ::com::sun::star::sdbc::XRowSet;
    using ::com::sun::star::sdbc::XDatabaseMetaData;
    using ::com::sun::star::util::XNumberFormatsSupplier;
    using ::com::sun::star::util::NumberFormatter;
    using ::com::sun::star::util::XNumberFormatter;
    using ::com::sun::star::sdbcx::XColumnsSupplier;
    using ::com::sun::star::container::XNameAccess;
    using ::com::sun::star::lang::EventObject;
    using ::com::sun::star::beans::Property;
    using ::com::sun::star::container::XEnumeration;
    using ::com::sun::star::form::XFormComponent;
    using ::com::sun::star::form::runtime::XFormOperations;
    using ::com::sun::star::form::runtime::FilterEvent;
    using ::com::sun::star::form::runtime::XFilterControllerListener;
    using ::com::sun::star::awt::XControlContainer;
    using ::com::sun::star::container::XIdentifierReplace;
    using ::com::sun::star::form::XFormControllerListener;
    using ::com::sun::star::awt::XWindow;
    using ::com::sun::star::sdbc::XResultSet;
    using ::com::sun::star::awt::XControlModel;
    using ::com::sun::star::awt::XTabControllerModel;
    using ::com::sun::star::beans::PropertyChangeEvent;
    using ::com::sun::star::form::validation::XValidatableFormComponent;
    using ::com::sun::star::form::XLoadable;
    using ::com::sun::star::form::XBoundControl;
    using ::com::sun::star::beans::XPropertyChangeListener;
    using ::com::sun::star::awt::TextEvent;
    using ::com::sun::star::form::XBoundComponent;
    using ::com::sun::star::awt::XCheckBox;
    using ::com::sun::star::awt::XComboBox;
    using ::com::sun::star::awt::XListBox;
    using ::com::sun::star::awt::ItemEvent;
    using ::com::sun::star::util::XModifyListener;
    using ::com::sun::star::form::XReset;
    using ::com::sun::star::frame::XDispatchProviderInterception;
    using ::com::sun::star::form::XGridControl;
    using ::com::sun::star::awt::XVclWindowPeer;
    using ::com::sun::star::form::validation::XValidator;
    using ::com::sun::star::awt::FocusEvent;
    using ::com::sun::star::sdb::SQLContext;
    using ::com::sun::star::container::XChild;
    using ::com::sun::star::form::TabulatorCycle_RECORDS;
    using ::com::sun::star::container::ContainerEvent;
    using ::com::sun::star::lang::DisposedException;
    using ::com::sun::star::lang::Locale;
    using ::com::sun::star::lang::NoSupportException;
    using ::com::sun::star::sdb::RowChangeEvent;
    using ::com::sun::star::frame::XStatusListener;
    using ::com::sun::star::frame::XDispatchProviderInterceptor;
    using ::com::sun::star::sdb::SQLErrorEvent;
    using ::com::sun::star::form::DatabaseParameterEvent;
    using ::com::sun::star::sdb::ParametersRequest;
    using ::com::sun::star::task::XInteractionRequest;
    using ::com::sun::star::util::URL;
    using ::com::sun::star::frame::FeatureStateEvent;
    using ::com::sun::star::form::runtime::XFormControllerContext;
    using ::com::sun::star::task::InteractionHandler;
    using ::com::sun::star::task::XInteractionHandler;
    using ::com::sun::star::form::runtime::FormOperations;
    using ::com::sun::star::container::XContainer;
    using ::com::sun::star::sdbc::SQLWarning;
 
    namespace ColumnValue = ::com::sun::star::sdbc::ColumnValue;
    namespace PropertyAttribute = ::com::sun::star::beans::PropertyAttribute;
    namespace FocusChangeReason = ::com::sun::star::awt::FocusChangeReason;
    namespace RowChangeAction = ::com::sun::star::sdb::RowChangeAction;
    namespace FormFeature = ::com::sun::star::form::runtime::FormFeature;
 
namespace {
 
struct ColumnInfo
{
    // information about the column itself
    Reference< XColumn >    xColumn;
    sal_Int32               nNullable;
    bool                bAutoIncrement;
    bool                bReadOnly;
    OUString         sName;
 
    // information about the control(s) bound to this column
 
    /// the first control which is bound to the given column, and which requires input
    Reference< XControl >   xFirstControlWithInputRequired;
    /** the first grid control which contains a column which is bound to the given database column, and requires
        input
    */
    Reference< XGrid >      xFirstGridWithInputRequiredColumn;
    /** if xFirstControlWithInputRequired is a grid control, then nRequiredGridColumn specifies the position
        of the grid column which is actually bound
    */
    sal_Int32               nRequiredGridColumn;
 
    ColumnInfo()
        :nNullable( ColumnValue::NULLABLE_UNKNOWN )
        ,bAutoIncrement( false )
        ,bReadOnly( false )
        ,nRequiredGridColumn( -1 )
    {
    }
};
 
}
 
class ColumnInfoCache
{
public:
    explicit ColumnInfoCache( const Reference< XColumnsSupplier >& _rxColSupplier );
 
    size_t        getColumnCount() const { return m_aColumns.size(); }
    const ColumnInfo&   getColumnInfo( size_t _pos );
 
    bool    controlsInitialized() const { return m_bControlsInitialized; }
    void    initializeControls( const Sequence< Reference< XControl > >& _rControls );
    void    deinitializeControls();
 
private:
    typedef ::std::vector< ColumnInfo > ColumnInfos;
    ColumnInfos                         m_aColumns;
    bool                                m_bControlsInitialized;
};
 
 
ColumnInfoCache::ColumnInfoCache( const Reference< XColumnsSupplier >& _rxColSupplier )
    :m_bControlsInitialized( false )
{
    try
    {
        m_aColumns.clear();
 
        Reference< XIndexAccess > xColumns( _rxColSupplier->getColumns(), UNO_QUERY_THROW );
        sal_Int32 nColumnCount = xColumns->getCount();
        m_aColumns.reserve( nColumnCount );
 
        Reference< XPropertySet >   xColumnProps;
        for ( sal_Int32 i = 0; i < nColumnCount; ++i )
        {
            ColumnInfo aColInfo;
            aColInfo.xColumn.set( xColumns->getByIndex(i), UNO_QUERY_THROW );
 
            xColumnProps.set( aColInfo.xColumn, UNO_QUERY_THROW );
            OSL_VERIFY( xColumnProps->getPropertyValue( FM_PROP_ISNULLABLE ) >>= aColInfo.nNullable );
            OSL_VERIFY( xColumnProps->getPropertyValue( FM_PROP_AUTOINCREMENT ) >>= aColInfo.bAutoIncrement );
            OSL_VERIFY( xColumnProps->getPropertyValue( FM_PROP_NAME ) >>= aColInfo.sName );
            OSL_VERIFY( xColumnProps->getPropertyValue( FM_PROP_ISREADONLY ) >>= aColInfo.bReadOnly );
 
            m_aColumns.push_back( aColInfo );
        }
    }
    catch( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION("svx");
    }
}
 
 
namespace
{
    bool lcl_isBoundTo( const Reference< XPropertySet >& _rxControlModel, const Reference< XInterface >& _rxNormDBField )
    {
        Reference< XInterface > xNormBoundField( _rxControlModel->getPropertyValue( FM_PROP_BOUNDFIELD ), UNO_QUERY );
        return ( xNormBoundField == _rxNormDBField );
    }
 
    bool lcl_isInputRequired( const Reference< XPropertySet >& _rxControlModel )
    {
        bool bInputRequired = false;
        OSL_VERIFY( _rxControlModel->getPropertyValue( FM_PROP_INPUT_REQUIRED ) >>= bInputRequired );
        return bInputRequired;
    }
 
    void lcl_resetColumnControlInfo( ColumnInfo& _rColInfo )
    {
        _rColInfo.xFirstControlWithInputRequired.clear();
        _rColInfo.xFirstGridWithInputRequiredColumn.clear();
        _rColInfo.nRequiredGridColumn = -1;
    }
}
 
 
void ColumnInfoCache::deinitializeControls()
{
    for (auto& rCol : m_aColumns)
    {
        lcl_resetColumnControlInfo( rCol );
    }
    m_bControlsInitialized = false;
}
 
 
void ColumnInfoCache::initializeControls( const Sequence< Reference< XControl > >& _rControls )
{
    try
    {
        // for every of our known columns, find the controls which are bound to this column
        for (auto& rCol : m_aColumns)
        {
            OSL_ENSURE( !rCol.xFirstControlWithInputRequired.is() && !rCol.xFirstGridWithInputRequiredColumn.is()
                && ( rCol.nRequiredGridColumn == -1 ), "ColumnInfoCache::initializeControls: called me twice?" );
 
            lcl_resetColumnControlInfo( rCol );
 
            Reference< XInterface > xNormColumn( rCol.xColumn, UNO_QUERY_THROW );
 
            const Reference< XControl >* pControl( _rControls.getConstArray() );
            const Reference< XControl >* pControlEnd( pControl + _rControls.getLength() );
            for ( ; pControl != pControlEnd; ++pControl )
            {
                if ( !pControl->is() )
                    continue;
 
                Reference< XPropertySet > xModel( (*pControl)->getModel(), UNO_QUERY_THROW );
                Reference< XPropertySetInfo > xModelPSI( xModel->getPropertySetInfo(), UNO_SET_THROW );
 
                // special handling for grid controls
                Reference< XGrid > xGrid( *pControl, UNO_QUERY );
                if ( xGrid.is() )
                {
                    Reference< XIndexAccess > xGridColAccess( xModel, UNO_QUERY_THROW );
                    sal_Int32 gridColCount = xGridColAccess->getCount();
                    sal_Int32 gridCol = 0;
                    for ( gridCol = 0; gridCol < gridColCount; ++gridCol )
                    {
                        Reference< XPropertySet > xGridColumnModel( xGridColAccess->getByIndex( gridCol ), UNO_QUERY_THROW );
 
                        if  (   !lcl_isBoundTo( xGridColumnModel, xNormColumn )
                            ||  !lcl_isInputRequired( xGridColumnModel )
                            )
                            continue;   // with next grid column
 
                        break;
                    }
 
                    if ( gridCol < gridColCount )
                    {
                        // found a grid column which is bound to the given
                        rCol.xFirstGridWithInputRequiredColumn = std::move(xGrid);
                        rCol.nRequiredGridColumn = gridCol;
                        break;
                    }
 
                    continue;   // with next control
                }
 
                if  (   !xModelPSI->hasPropertyByName( FM_PROP_BOUNDFIELD )
                    ||  !lcl_isBoundTo( xModel, xNormColumn )
                    ||  !lcl_isInputRequired( xModel )
                    )
                    continue;   // with next control
 
                break;
            }
 
            if ( pControl == pControlEnd )
                // did not find a control which is bound to this particular column, and for which the input is required
                continue;   // with next DB column
 
            rCol.xFirstControlWithInputRequired = *pControl;
        }
    }
    catch( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION("svx");
    }
 
    m_bControlsInitialized = true;
}
 
 
const ColumnInfo& ColumnInfoCache::getColumnInfo( size_t _pos )
{
    if ( _pos >= m_aColumns.size() )
        throw IndexOutOfBoundsException();
 
    return m_aColumns[ _pos ];
}
 
namespace {
 
class OParameterContinuation : public OInteraction< XInteractionSupplyParameters >
{
    Sequence< PropertyValue >       m_aValues;
 
public:
    OParameterContinuation() { }
 
    const Sequence< PropertyValue >&   getValues() const { return m_aValues; }
 
// XInteractionSupplyParameters
    virtual void SAL_CALL setParameters( const Sequence< PropertyValue >& _rValues ) override;
};
 
}
 
void SAL_CALL OParameterContinuation::setParameters( const Sequence< PropertyValue >& _rValues )
{
    m_aValues = _rValues;
}
 
 
// FmXAutoControl
 
struct FmFieldInfo
{
    OUString       aFieldName;
    Reference< XPropertySet >   xField;
    Reference< XTextComponent >  xText;
 
    FmFieldInfo(const Reference< XPropertySet >& _xField, const Reference< XTextComponent >& _xText)
        :xField(_xField)
        ,xText(_xText)
    {xField->getPropertyValue(FM_PROP_NAME) >>= aFieldName;}
};
 
namespace {
 
class FmXAutoControl: public UnoControl
 
{
public:
    FmXAutoControl()
    {
    }
 
    virtual OUString GetComponentServiceName() const override {return u"Edit"_ustr;}
    virtual void SAL_CALL createPeer( const Reference< XToolkit > & rxToolkit, const Reference< XWindowPeer >  & rParentPeer ) override;
 
protected:
    virtual void ImplSetPeerProperty( const OUString& rPropName, const Any& rVal ) override;
};
 
}
 
void FmXAutoControl::createPeer( const Reference< XToolkit > & rxToolkit, const Reference< XWindowPeer >  & rParentPeer )
{
    UnoControl::createPeer( rxToolkit, rParentPeer );
 
    Reference< XTextComponent >  xText(getPeer() , UNO_QUERY);
    if (xText.is())
    {
        xText->setText(SvxResId(RID_STR_AUTOFIELD));
        xText->setEditable(false);
    }
}
 
 
void FmXAutoControl::ImplSetPeerProperty( const OUString& rPropName, const Any& rVal )
{
    // these properties are ignored
    if (rPropName == FM_PROP_TEXT)
        return;
 
    UnoControl::ImplSetPeerProperty( rPropName, rVal );
}
 
 
IMPL_LINK_NOARG( FormController, OnActivateTabOrder, Timer*, void )
{
    activateTabOrder();
}
 
namespace {
 
struct UpdateAllListeners
{
    bool operator()( const Reference< XDispatch >& _rxDispatcher ) const
    {
        static_cast< svx::OSingleFeatureDispatcher* >( _rxDispatcher.get() )->updateAllListeners();
        // the return is a dummy only so we can use this struct in a lambda expression
        return true;
    }
};
 
}
 
IMPL_LINK_NOARG( FormController, OnInvalidateFeatures, Timer*, void )
{
    ::osl::MutexGuard aGuard( m_aMutex );
    for (const auto& rFeature : m_aInvalidFeatures)
    {
        DispatcherContainer::const_iterator aDispatcherPos = m_aFeatureDispatchers.find( rFeature );
        if ( aDispatcherPos != m_aFeatureDispatchers.end() )
        {
            // TODO: for the real and actual listener notifications, we should release
            // our mutex
            UpdateAllListeners( )( aDispatcherPos->second );
        }
    }
}
 
FormController::FormController(const Reference< css::uno::XComponentContext > & _rxORB )
                  :FormController_BASE( m_aMutex )
                  ,OPropertySetHelper( FormController_BASE::rBHelper )
                  ,OSQLParserClient( _rxORB )
                  ,m_xComponentContext( _rxORB )
                  ,m_aActivateListeners(m_aMutex)
                  ,m_aModifyListeners(m_aMutex)
                  ,m_aErrorListeners(m_aMutex)
                  ,m_aDeleteListeners(m_aMutex)
                  ,m_aRowSetApproveListeners(m_aMutex)
                  ,m_aParameterListeners(m_aMutex)
                  ,m_aFilterListeners(m_aMutex)
                  ,m_aTabActivationIdle("svx FormController m_aTabActivationIdle")
                  ,m_aFeatureInvalidationTimer("svx FormController m_aFeatureInvalidationTimer")
                  ,m_aMode( u"DataMode"_ustr )
                  ,m_aLoadEvent( LINK( this, FormController, OnLoad ) )
                  ,m_aToggleEvent( LINK( this, FormController, OnToggleAutoFields ) )
                  ,m_aActivationEvent( LINK( this, FormController, OnActivated ) )
                  ,m_aDeactivationEvent( LINK( this, FormController, OnDeactivated ) )
                  ,m_nCurrentFilterPosition(-1)
                  ,m_bCurrentRecordModified(false)
                  ,m_bCurrentRecordNew(false)
                  ,m_bLocked(false)
                  ,m_bDBConnection(false)
                  ,m_bCycle(false)
                  ,m_bCanInsert(false)
                  ,m_bCanUpdate(false)
                  ,m_bCommitLock(false)
                  ,m_bModified(false)
                  ,m_bControlsSorted(false)
                  ,m_bFiltering(false)
                  ,m_bAttachEvents(true)
                  ,m_bDetachEvents(true)
                  ,m_bAttemptedHandlerCreation( false )
                  ,m_bSuspendFilterTextListening( false )
{
 
    osl_atomic_increment(&m_refCount);
    {
        m_xTabController = TabController::create( m_xComponentContext );
        m_xAggregate.set( m_xTabController, UNO_QUERY_THROW );
        m_xAggregate->setDelegator( *this );
    }
    osl_atomic_decrement(&m_refCount);
 
    m_aTabActivationIdle.SetPriority( TaskPriority::LOWEST );
    m_aTabActivationIdle.SetInvokeHandler( LINK( this, FormController, OnActivateTabOrder ) );
 
    m_aFeatureInvalidationTimer.SetTimeout( 200 );
    m_aFeatureInvalidationTimer.SetInvokeHandler( LINK( this, FormController, OnInvalidateFeatures ) );
}
 
 
FormController::~FormController()
{
    {
        ::osl::MutexGuard aGuard( m_aMutex );
 
        m_aLoadEvent.CancelPendingCall();
        m_aToggleEvent.CancelPendingCall();
        m_aActivationEvent.CancelPendingCall();
        m_aDeactivationEvent.CancelPendingCall();
 
        if ( m_aTabActivationIdle.IsActive() )
            m_aTabActivationIdle.Stop();
    }
 
    if ( m_aFeatureInvalidationTimer.IsActive() )
        m_aFeatureInvalidationTimer.Stop();
 
    disposeAllFeaturesAndDispatchers();
 
    if ( m_xFormOperations.is() )
        m_xFormOperations->dispose();
    m_xFormOperations.clear();
 
    // release of aggregation
    if ( m_xAggregate.is() )
    {
        m_xAggregate->setDelegator( nullptr );
        m_xAggregate.clear();
    }
}
 
 
void SAL_CALL FormController::acquire() noexcept
{
    FormController_BASE::acquire();
}
 
 
void SAL_CALL FormController::release() noexcept
{
    FormController_BASE::release();
}
 
 
Any SAL_CALL FormController::queryInterface( const Type& _rType )
{
    Any aRet = FormController_BASE::queryInterface( _rType );
    if ( !aRet.hasValue() )
        aRet = OPropertySetHelper::queryInterface( _rType );
    if ( !aRet.hasValue() )
        aRet = m_xAggregate->queryAggregation( _rType );
    return aRet;
}
 
 
Sequence< sal_Int8 > SAL_CALL FormController::getImplementationId()
{
    return css::uno::Sequence<sal_Int8>();
}
 
Sequence< Type > SAL_CALL FormController::getTypes(  )
{
    return comphelper::concatSequences(
        FormController_BASE::getTypes(),
        ::cppu::OPropertySetHelper::getTypes()
    );
}
 
// XServiceInfo
sal_Bool SAL_CALL FormController::supportsService(const OUString& ServiceName)
{
    return cppu::supportsService(this, ServiceName);
}
 
OUString SAL_CALL FormController::getImplementationName()
{
    return u"org.openoffice.comp.svx.FormController"_ustr;
}
 
Sequence< OUString> SAL_CALL FormController::getSupportedServiceNames()
{
    // service names which are supported only, but cannot be used to created an
    // instance at a service factory
    static constexpr OUString aNonCreatableServiceNames[] { u"com.sun.star.form.FormControllerDispatcher"_ustr };
 
    // services which can be used to created an instance at a service factory
    Sequence< OUString > aCreatableServiceNames( getSupportedServiceNames_Static() );
    return ::comphelper::concatSequences( aCreatableServiceNames, aNonCreatableServiceNames );
}
 
 
sal_Bool SAL_CALL FormController::approveReset(const EventObject& /*rEvent*/)
{
    return true;
}
 
 
void SAL_CALL FormController::resetted(const EventObject& rEvent)
{
    ::osl::MutexGuard aGuard(m_aMutex);
    if (getCurrentControl().is() &&  (getCurrentControl()->getModel() == rEvent.Source))
        m_bModified = false;
}
 
 
Sequence< OUString> const & FormController::getSupportedServiceNames_Static()
{
    static Sequence< OUString> const aServices
    {
        u"com.sun.star.form.runtime.FormController"_ustr,
        u"com.sun.star.awt.control.TabController"_ustr
    };
    return aServices;
}
 
 
namespace
{
    struct ResetComponentText
    {
        void operator()( const Reference< XTextComponent >& _rxText )
        {
            _rxText->setText( OUString() );
        }
    };
 
    struct RemoveComponentTextListener
    {
        explicit RemoveComponentTextListener( const Reference< XTextListener >& _rxListener )
            :m_xListener( _rxListener )
        {
        }
 
        void operator()( const Reference< XTextComponent >& _rxText )
        {
            _rxText->removeTextListener( m_xListener );
        }
 
    private:
        Reference< XTextListener >  m_xListener;
    };
}
 
 
void FormController::impl_setTextOnAllFilter_throw()
{
    m_bSuspendFilterTextListening = true;
    ::comphelper::FlagGuard aResetFlag( m_bSuspendFilterTextListening );
 
    // reset the text for all controls
    ::std::for_each( m_aFilterComponents.begin(), m_aFilterComponents.end(), ResetComponentText() );
 
    if ( m_aFilterRows.empty() )
        // nothing to do anymore
        return;
 
    if ( m_nCurrentFilterPosition < 0 )
        return;
 
    // set the text for all filters
    OSL_ENSURE( m_aFilterRows.size() > o3tl::make_unsigned(m_nCurrentFilterPosition),
        "FormController::impl_setTextOnAllFilter_throw: m_nCurrentFilterPosition too big" );
 
    if ( o3tl::make_unsigned(m_nCurrentFilterPosition) < m_aFilterRows.size() )
    {
        FmFilterRow& rRow = m_aFilterRows[ m_nCurrentFilterPosition ];
        for (const auto& rEntry : rRow)
        {
            rEntry.first->setText( rEntry.second );
        }
    }
}
// OPropertySetHelper
 
sal_Bool FormController::convertFastPropertyValue( Any & /*rConvertedValue*/, Any & /*rOldValue*/,
                                            sal_Int32 /*nHandle*/, const Any& /*rValue*/ )
{
    return false;
}
 
 
void FormController::setFastPropertyValue_NoBroadcast( sal_Int32 /*nHandle*/, const Any& /*rValue*/ )
{
}
 
 
void FormController::getFastPropertyValue( Any& rValue, sal_Int32 nHandle ) const
{
    switch (nHandle)
    {
        case FM_ATTR_FILTER:
        {
            OUStringBuffer aFilter;
            Reference<XConnection> xConnection(getConnection(Reference< XRowSet>(m_xModelAsIndex, UNO_QUERY)));
            if (xConnection.is())
            {
                Reference< XNumberFormatsSupplier> xFormatSupplier( getNumberFormats( xConnection, true ) );
                Reference< XNumberFormatter> xFormatter = NumberFormatter::create(m_xComponentContext);
                xFormatter->attachNumberFormatsSupplier(xFormatSupplier);
 
                // now add the filter rows
                try
                {
                    for (const FmFilterRow& rRow : m_aFilterRows)
                    {
                        if ( rRow.empty() )
                            continue;
 
                        OUStringBuffer aRowFilter;
                        for ( FmFilterRow::const_iterator condition = rRow.begin(); condition != rRow.end(); ++condition )
                        {
                            // get the field of the controls map
                            Reference< XControl > xControl( condition->first, UNO_QUERY_THROW );
                            Reference< XPropertySet > xModelProps( xControl->getModel(), UNO_QUERY_THROW );
                            Reference< XPropertySet > xField( xModelProps->getPropertyValue( FM_PROP_BOUNDFIELD ), UNO_QUERY_THROW );
 
                            OUString sFilterValue( condition->second );
 
                            OUString sErrorMsg;
                            const std::unique_ptr< OSQLParseNode > pParseNode =
                                predicateTree( sErrorMsg, sFilterValue, xFormatter, xField );
                            OSL_ENSURE( pParseNode != nullptr, "FormController::getFastPropertyValue: could not parse the field value predicate!" );
                            if ( pParseNode != nullptr )
                            {
                                OUString sCriteria;
                                // don't use a parse context here, we need it unlocalized
                                pParseNode->parseNodeToStr( sCriteria, xConnection );
                                if ( condition != rRow.begin() )
                                    aRowFilter.append( " AND " );
                                aRowFilter.append( sCriteria );
                            }
                        }
                        if ( !aRowFilter.isEmpty() )
                        {
                            if ( !aFilter.isEmpty() )
                                aFilter.append( " OR " );
 
                            aFilter.append( "( " + aRowFilter + " )" );
                        }
                    }
                }
                catch( const Exception& )
                {
                    DBG_UNHANDLED_EXCEPTION("svx");
                    aFilter.setLength(0);
                }
            }
            rValue <<= aFilter.makeStringAndClear();
        }
        break;
 
        case FM_ATTR_FORM_OPERATIONS:
            rValue <<= m_xFormOperations;
            break;
    }
}
 
 
Reference< XPropertySetInfo >  FormController::getPropertySetInfo()
{
    static Reference< XPropertySetInfo >  xInfo( createPropertySetInfo( getInfoHelper() ) );
    return xInfo;
}
 
 
void FormController::fillProperties(
        Sequence< Property >& /* [out] */ _rProps,
        Sequence< Property >& /* [out] */ /*_rAggregateProps*/
        ) const
{
    _rProps.realloc(2);
    sal_Int32 nPos = 0;
    Property* pDesc = _rProps.getArray();
 
    pDesc[nPos++] = Property(FM_PROP_FILTER, FM_ATTR_FILTER,
                             cppu::UnoType<OUString>::get(),
                             PropertyAttribute::READONLY);
    pDesc[nPos++] = Property(FM_PROP_FORM_OPERATIONS, FM_ATTR_FORM_OPERATIONS,
                             cppu::UnoType<XFormOperations>::get(),
                             PropertyAttribute::READONLY);
}
 
 
::cppu::IPropertyArrayHelper& FormController::getInfoHelper()
{
    return *getArrayHelper();
}
 
// XFilterController
 
void SAL_CALL FormController::addFilterControllerListener( const Reference< XFilterControllerListener >& Listener )
{
    m_aFilterListeners.addInterface( Listener );
}
 
 
void SAL_CALL FormController::removeFilterControllerListener( const Reference< XFilterControllerListener >& Listener )
{
    m_aFilterListeners.removeInterface( Listener );
}
 
 
::sal_Int32 SAL_CALL FormController::getFilterComponents()
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    return m_aFilterComponents.size();
}
 
 
::sal_Int32 SAL_CALL FormController::getDisjunctiveTerms()
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    return m_aFilterRows.size();
}
 
 
void SAL_CALL FormController::setPredicateExpression( ::sal_Int32 Component, ::sal_Int32 Term, const OUString& PredicateExpression )
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    if ( ( Component < 0 ) || ( Component >= getFilterComponents() ) || ( Term < 0 ) || ( Term >= getDisjunctiveTerms() ) )
        throw IndexOutOfBoundsException( OUString(), *this );
 
    Reference< XTextComponent > xText( m_aFilterComponents[ Component ] );
    xText->setText( PredicateExpression );
 
    FmFilterRow& rFilterRow = m_aFilterRows[ Term ];
    if ( !PredicateExpression.isEmpty() )
        rFilterRow[ xText ] = PredicateExpression;
    else
        rFilterRow.erase( xText );
}
 
 
Reference< XControl > FormController::getFilterComponent( ::sal_Int32 Component )
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    if ( ( Component < 0 ) || ( Component >= getFilterComponents() ) )
        throw IndexOutOfBoundsException( OUString(), *this );
 
    return Reference< XControl >( m_aFilterComponents[ Component ], UNO_QUERY );
}
 
 
Sequence< Sequence< OUString > > FormController::getPredicateExpressions()
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    Sequence< Sequence< OUString > > aExpressions( m_aFilterRows.size() );
    auto aExpressionsRange = asNonConstRange(aExpressions);
    sal_Int32 termIndex = 0;
    for (const FmFilterRow& rRow : m_aFilterRows)
    {
        Sequence< OUString > aConjunction( m_aFilterComponents.size() );
        auto aConjunctionRange = asNonConstRange(aConjunction);
        sal_Int32 componentIndex = 0;
        for (const auto& rComp : m_aFilterComponents)
        {
            FmFilterRow::const_iterator predicate = rRow.find( rComp );
            if ( predicate != rRow.end() )
                aConjunctionRange[ componentIndex ] = predicate->second;
            ++componentIndex;
        }
 
        aExpressionsRange[ termIndex ] = std::move(aConjunction);
        ++termIndex;
    }
 
    return aExpressions;
}
 
 
void SAL_CALL FormController::removeDisjunctiveTerm( ::sal_Int32 Term )
{
    // SYNCHRONIZED -->
    ::osl::ClearableMutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    if ( ( Term < 0 ) || ( Term >= getDisjunctiveTerms() ) )
        throw IndexOutOfBoundsException( OUString(), *this );
 
    // if the to-be-deleted row is our current row, we need to shift
    if ( Term == m_nCurrentFilterPosition )
    {
        if ( m_nCurrentFilterPosition < sal_Int32( m_aFilterRows.size() - 1 ) )
            ++m_nCurrentFilterPosition;
        else
            --m_nCurrentFilterPosition;
    }
 
    FmFilterRows::iterator pos = m_aFilterRows.begin() + Term;
    m_aFilterRows.erase( pos );
 
    // adjust m_nCurrentFilterPosition if the removed row preceded it
    if ( Term < m_nCurrentFilterPosition )
        --m_nCurrentFilterPosition;
 
    SAL_WARN_IF( !( ( m_nCurrentFilterPosition < 0 ) != ( m_aFilterRows.empty() ) ),
        "svx.form", "FormController::removeDisjunctiveTerm: inconsistency!" );
 
    // update the texts in the filter controls
    impl_setTextOnAllFilter_throw();
 
    FilterEvent aEvent;
    aEvent.Source = *this;
    aEvent.DisjunctiveTerm = Term;
    aGuard.clear();
    // <-- SYNCHRONIZED
 
    m_aFilterListeners.notifyEach( &XFilterControllerListener::disjunctiveTermRemoved, aEvent );
}
 
 
void SAL_CALL FormController::appendEmptyDisjunctiveTerm()
{
    // SYNCHRONIZED -->
    ::osl::ClearableMutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    impl_appendEmptyFilterRow( aGuard );
    // <-- SYNCHRONIZED
}
 
 
::sal_Int32 SAL_CALL FormController::getActiveTerm()
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    return m_nCurrentFilterPosition;
}
 
 
void SAL_CALL FormController::setActiveTerm( ::sal_Int32 ActiveTerm )
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    if ( ( ActiveTerm < 0 ) || ( ActiveTerm >= getDisjunctiveTerms() ) )
        throw IndexOutOfBoundsException( OUString(), *this );
 
    if ( ActiveTerm == getActiveTerm() )
        return;
 
    m_nCurrentFilterPosition = ActiveTerm;
    impl_setTextOnAllFilter_throw();
}
 
// XElementAccess
 
sal_Bool SAL_CALL FormController::hasElements()
{
    ::osl::MutexGuard aGuard( m_aMutex );
    return !m_aChildren.empty();
}
 
 
Type SAL_CALL  FormController::getElementType()
{
    return cppu::UnoType<XFormController>::get();
 
}
 
// XEnumerationAccess
 
Reference< XEnumeration > SAL_CALL  FormController::createEnumeration()
{
    ::osl::MutexGuard aGuard( m_aMutex );
    return new ::comphelper::OEnumerationByIndex(this);
}
 
// XIndexAccess
 
sal_Int32 SAL_CALL FormController::getCount()
{
    ::osl::MutexGuard aGuard( m_aMutex );
    return m_aChildren.size();
}
 
 
Any SAL_CALL FormController::getByIndex(sal_Int32 Index)
{
    ::osl::MutexGuard aGuard( m_aMutex );
    if (Index < 0 ||
        o3tl::make_unsigned(Index) >= m_aChildren.size())
        throw IndexOutOfBoundsException();
 
    return Any( m_aChildren[ Index ] );
}
 
//  EventListener
 
void SAL_CALL FormController::disposing(const EventObject& e)
{
    // has the container been disposed
    ::osl::MutexGuard aGuard( m_aMutex );
    Reference< XControlContainer >  xContainer(e.Source, UNO_QUERY);
    if (xContainer.is())
    {
        setContainer(Reference< XControlContainer > ());
    }
    else
    {
        // has a control been disposed
        Reference< XControl >  xControl(e.Source, UNO_QUERY);
        if (xControl.is())
        {
            if (getContainer().is())
                removeControl(xControl);
        }
    }
}
 
// OComponentHelper
 
void FormController::disposeAllFeaturesAndDispatchers()
{
    for (auto& rDispatcher : m_aFeatureDispatchers)
    {
        try
        {
            ::comphelper::disposeComponent( rDispatcher.second );
        }
        catch( const Exception& )
        {
            DBG_UNHANDLED_EXCEPTION("svx");
        }
    }
    m_aFeatureDispatchers.clear();
}
 
 
void FormController::disposing()
{
    EventObject aEvt( *this );
 
    // if we're still active, simulate a "deactivated" event
    if ( m_xActiveControl.is() )
        m_aActivateListeners.notifyEach( &XFormControllerListener::formDeactivated, aEvt );
 
    // notify all our listeners
    m_aActivateListeners.disposeAndClear(aEvt);
    m_aModifyListeners.disposeAndClear(aEvt);
    m_aErrorListeners.disposeAndClear(aEvt);
    m_aDeleteListeners.disposeAndClear(aEvt);
    m_aRowSetApproveListeners.disposeAndClear(aEvt);
    m_aParameterListeners.disposeAndClear(aEvt);
    m_aFilterListeners.disposeAndClear(aEvt);
 
    removeBoundFieldListener();
    stopFiltering();
 
    m_aControlBorderManager.restoreAll();
 
    m_aFilterRows.clear();
 
    ::osl::MutexGuard aGuard( m_aMutex );
    m_xActiveControl = nullptr;
    implSetCurrentControl( nullptr );
 
    // clean up our children
    for (const auto& rpChild : m_aChildren)
    {
        // search the position of the model within the form
        Reference< XFormComponent >  xForm(rpChild->getModel(), UNO_QUERY);
        sal_uInt32 nPos = m_xModelAsIndex->getCount();
        Reference< XFormComponent > xTemp;
        for( ; nPos; )
        {
 
            m_xModelAsIndex->getByIndex( --nPos ) >>= xTemp;
            if ( xForm.get() == xTemp.get() )
            {
                Reference< XInterface > xIfc( rpChild, UNO_QUERY );
                m_xModelAsManager->detach( nPos, xIfc );
                break;
            }
        }
 
        Reference< XComponent > (rpChild, UNO_QUERY_THROW)->dispose();
    }
    m_aChildren.clear();
 
    disposeAllFeaturesAndDispatchers();
 
    if ( m_xFormOperations.is() )
        m_xFormOperations->dispose();
    m_xFormOperations.clear();
 
    if (m_bDBConnection)
        unload();
 
    setContainer( nullptr );
    setModel( nullptr );
    setParent( nullptr );
 
    ::comphelper::disposeComponent( m_xComposer );
 
    m_bDBConnection = false;
}
 
 
namespace
{
    bool lcl_shouldUseDynamicControlBorder( const Reference< XInterface >& _rxForm, const Any& _rDynamicColorProp )
    {
        bool bDoUse = false;
        if ( !( _rDynamicColorProp >>= bDoUse ) )
        {
            DocumentType eDocType = DocumentClassification::classifyHostDocument( _rxForm );
            return ControlLayouter::useDynamicBorderColor( eDocType );
        }
        return bDoUse;
    }
}
 
 
void SAL_CALL FormController::propertyChange(const PropertyChangeEvent& evt)
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
    if ( evt.PropertyName == FM_PROP_BOUNDFIELD )
    {
        Reference<XPropertySet> xOldBound;
        evt.OldValue >>= xOldBound;
        if ( !xOldBound.is() && evt.NewValue.hasValue() )
        {
            Reference< XControlModel > xControlModel(evt.Source,UNO_QUERY);
            Reference< XControl > xControl = findControl(m_aControls,xControlModel,false,false);
            if ( xControl.is() )
            {
                startControlModifyListening( xControl );
                Reference<XPropertySet> xProp(xControlModel,UNO_QUERY);
                if ( xProp.is() )
                    xProp->removePropertyChangeListener(FM_PROP_BOUNDFIELD, this);
            }
        }
    }
    else
    {
        bool bModifiedChanged = (evt.PropertyName == FM_PROP_ISMODIFIED);
        bool bNewChanged = (evt.PropertyName == FM_PROP_ISNEW);
        if (bModifiedChanged || bNewChanged)
        {
            ::osl::MutexGuard aGuard( m_aMutex );
            if (bModifiedChanged)
                m_bCurrentRecordModified = ::comphelper::getBOOL(evt.NewValue);
            else
                m_bCurrentRecordNew = ::comphelper::getBOOL(evt.NewValue);
 
            // toggle the locking
            if (m_bLocked != determineLockState())
            {
                m_bLocked = !m_bLocked;
                setLocks();
                if (isListeningForChanges())
                    startListening();
                else
                    stopListening();
            }
 
            if ( bNewChanged )
                m_aToggleEvent.Call();
 
            if (!m_bCurrentRecordModified)
                m_bModified = false;
        }
        else if ( evt.PropertyName == FM_PROP_DYNAMIC_CONTROL_BORDER )
        {
            bool bEnable = lcl_shouldUseDynamicControlBorder( evt.Source, evt.NewValue );
            if ( bEnable )
            {
                m_aControlBorderManager.enableDynamicBorderColor();
                if ( m_xActiveControl.is() )
                    m_aControlBorderManager.focusGained( m_xActiveControl );
            }
            else
            {
                m_aControlBorderManager.disableDynamicBorderColor();
            }
        }
    }
}
 
 
bool FormController::replaceControl( const Reference< XControl >& _rxExistentControl, const Reference< XControl >& _rxNewControl )
{
    bool bSuccess = false;
    try
    {
        Reference< XIdentifierReplace > xContainer( getContainer(), UNO_QUERY );
        DBG_ASSERT( xContainer.is(), "FormController::replaceControl: yes, it's not required by the service description, but XIdentifierReplace would be nice!" );
        if ( xContainer.is() )
        {
            // look up the ID of _rxExistentControl
            const Sequence< sal_Int32 > aIdentifiers( xContainer->getIdentifiers() );
            const sal_Int32* pIdentifiers = std::find_if(aIdentifiers.begin(), aIdentifiers.end(),
                [&xContainer, &_rxExistentControl](const sal_Int32 nId) {
                    Reference< XControl > xCheck( xContainer->getByIdentifier( nId ), UNO_QUERY );
                    return xCheck == _rxExistentControl;
                });
            DBG_ASSERT( pIdentifiers != aIdentifiers.end(), "FormController::replaceControl: did not find the control in the container!" );
            if ( pIdentifiers != aIdentifiers.end() )
            {
                bool bReplacedWasActive = ( m_xActiveControl.get() == _rxExistentControl.get() );
                bool bReplacedWasCurrent = ( m_xCurrentControl.get() == _rxExistentControl.get() );
 
                if ( bReplacedWasActive )
                {
                    m_xActiveControl = nullptr;
                    implSetCurrentControl( nullptr );
                }
                else if ( bReplacedWasCurrent )
                {
                    implSetCurrentControl( _rxNewControl );
                }
 
                // carry over the model
                _rxNewControl->setModel( _rxExistentControl->getModel() );
 
                xContainer->replaceByIdentifer( *pIdentifiers, Any( _rxNewControl ) );
                bSuccess = true;
 
                if ( bReplacedWasActive )
                {
                    Reference< XWindow > xControlWindow( _rxNewControl, UNO_QUERY );
                    if ( xControlWindow.is() )
                        xControlWindow->setFocus();
                }
            }
        }
    }
    catch( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION("svx");
    }
 
    Reference< XControl > xDisposeIt( bSuccess ? _rxExistentControl : _rxNewControl );
    ::comphelper::disposeComponent( xDisposeIt );
    return bSuccess;
}
 
 
void FormController::toggleAutoFields(bool bAutoFields)
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
 
 
    Sequence< Reference< XControl > > aControlsCopy( m_aControls );
    const Reference< XControl >* pControls = aControlsCopy.getConstArray();
    sal_Int32 nControls = aControlsCopy.getLength();
 
    if (bAutoFields)
    {
        // as we don't want new controls to be attached to the scripting environment
        // we change attach flags
        m_bAttachEvents = false;
        for (sal_Int32 i = nControls; i > 0;)
        {
            Reference< XControl > xControl = pControls[--i];
            if (xControl.is())
            {
                Reference< XPropertySet >  xSet(xControl->getModel(), UNO_QUERY);
                if (xSet.is() && ::comphelper::hasProperty(FM_PROP_BOUNDFIELD, xSet))
                {
                    // does the model use a bound field ?
                    Reference< XPropertySet >  xField;
                    xSet->getPropertyValue(FM_PROP_BOUNDFIELD) >>= xField;
 
                    // is it an autofield?
                    if  (   xField.is()
                        &&  ::comphelper::hasProperty( FM_PROP_AUTOINCREMENT, xField )
                        &&  ::comphelper::getBOOL( xField->getPropertyValue( FM_PROP_AUTOINCREMENT ) )
                        )
                    {
                        replaceControl( xControl, new FmXAutoControl() );
                    }
                }
            }
        }
        m_bAttachEvents = true;
    }
    else
    {
        m_bDetachEvents = false;
        for (sal_Int32 i = nControls; i > 0;)
        {
            Reference< XControl > xControl = pControls[--i];
            if (xControl.is())
            {
                Reference< XPropertySet >  xSet(xControl->getModel(), UNO_QUERY);
                if (xSet.is() && ::comphelper::hasProperty(FM_PROP_BOUNDFIELD, xSet))
                {
                    // does the model use a bound field ?
                    Reference< XPropertySet >  xField;
                    xSet->getPropertyValue(FM_PROP_BOUNDFIELD) >>= xField;
 
                    // is it an autofield?
                    if  (   xField.is()
                        &&  ::comphelper::hasProperty( FM_PROP_AUTOINCREMENT, xField )
                        &&  ::comphelper::getBOOL( xField->getPropertyValue(FM_PROP_AUTOINCREMENT ) )
                        )
                    {
                        OUString sServiceName;
                        OSL_VERIFY( xSet->getPropertyValue( FM_PROP_DEFAULTCONTROL ) >>= sServiceName );
                        Reference< XControl > xNewControl( m_xComponentContext->getServiceManager()->createInstanceWithContext( sServiceName, m_xComponentContext ), UNO_QUERY );
                        replaceControl( xControl, xNewControl );
                    }
                }
            }
        }
        m_bDetachEvents = true;
    }
}
 
 
IMPL_LINK_NOARG(FormController, OnToggleAutoFields, void*, void)
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
 
    toggleAutoFields(m_bCurrentRecordNew);
}
 
// XTextListener
void SAL_CALL FormController::textChanged(const TextEvent& e)
{
    // SYNCHRONIZED -->
    ::osl::ClearableMutexGuard aGuard( m_aMutex );
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
    if ( !m_bFiltering )
    {
        impl_onModify();
        return;
    }
 
    if ( m_bSuspendFilterTextListening )
        return;
 
    Reference< XTextComponent >  xText(e.Source,UNO_QUERY);
    OUString aText = xText->getText();
 
    if ( m_aFilterRows.empty() )
        appendEmptyDisjunctiveTerm();
 
    // find the current row
    if ( ( m_nCurrentFilterPosition < 0 ) || ( o3tl::make_unsigned(m_nCurrentFilterPosition) >= m_aFilterRows.size() )  )
    {
        OSL_ENSURE( false, "FormController::textChanged: m_nCurrentFilterPosition is wrong!" );
        return;
    }
 
    FmFilterRow& rRow = m_aFilterRows[ m_nCurrentFilterPosition ];
 
    // do we have a new filter
    if (!aText.isEmpty())
        rRow[xText] = aText;
    else
    {
        // do we have the control in the row
        FmFilterRow::iterator iter = rRow.find(xText);
        // erase the entry out of the row
        if (iter != rRow.end())
            rRow.erase(iter);
    }
 
    // multiplex the event to our FilterControllerListeners
    FilterEvent aEvent;
    aEvent.Source = *this;
    aEvent.FilterComponent = ::std::find( m_aFilterComponents.begin(), m_aFilterComponents.end(), xText ) - m_aFilterComponents.begin();
    aEvent.DisjunctiveTerm = getActiveTerm();
    aEvent.PredicateExpression = aText;
 
    aGuard.clear();
    // <-- SYNCHRONIZED
 
    // notify the changed filter expression
    m_aFilterListeners.notifyEach( &XFilterControllerListener::predicateExpressionChanged, aEvent );
}
 
// XItemListener
void SAL_CALL FormController::itemStateChanged(const ItemEvent& /*rEvent*/)
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
    impl_onModify();
}
 
// XModificationBroadcaster
void SAL_CALL FormController::addModifyListener(const Reference< XModifyListener > & l)
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
    m_aModifyListeners.addInterface( l );
}
 
void FormController::removeModifyListener(const Reference< XModifyListener > & l)
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
    m_aModifyListeners.removeInterface( l );
}
 
// XModificationListener
void FormController::modified( const EventObject& _rEvent )
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
 
    try
    {
        if ( _rEvent.Source != m_xActiveControl )
        {   // let this control grab the focus
            // (this case may happen if somebody moves the scroll wheel of the mouse over a control
            // which does not have the focus)
            // 85511 - 29.05.2001 - frank.schoenheit@germany.sun.com
 
            // also, it happens when an image control gets a new image by double-clicking it
            // #i88458# / 2009-01-12 / frank.schoenheit@sun.com
            Reference< XWindow > xControlWindow( _rEvent.Source, UNO_QUERY_THROW );
            xControlWindow->setFocus();
        }
    }
    catch( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION("svx");
    }
 
    impl_onModify();
}
 
void FormController::impl_checkDisposed_throw() const
{
    if ( impl_isDisposed_nofail() )
        throw DisposedException( OUString(), *const_cast< FormController* >( this ) );
}
 
void FormController::impl_onModify()
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
 
    {
        ::osl::MutexGuard aGuard( m_aMutex );
        if ( !m_bModified )
            m_bModified = true;
    }
 
    EventObject aEvt(getXWeak());
    m_aModifyListeners.notifyEach( &XModifyListener::modified, aEvt );
}
 
void FormController::impl_addFilterRow( const FmFilterRow& _row )
{
    m_aFilterRows.push_back( _row );
 
    if ( m_aFilterRows.size() == 1 )
    {   // that's the first row ever
        OSL_ENSURE( m_nCurrentFilterPosition == -1, "FormController::impl_addFilterRow: inconsistency!" );
        m_nCurrentFilterPosition = 0;
    }
}
 
void FormController::impl_appendEmptyFilterRow( ::osl::ClearableMutexGuard& _rClearBeforeNotify )
{
    // SYNCHRONIZED -->
    impl_addFilterRow( FmFilterRow() );
 
    // notify the listeners
    FilterEvent aEvent;
    aEvent.Source = *this;
    aEvent.DisjunctiveTerm = static_cast<sal_Int32>(m_aFilterRows.size()) - 1;
    _rClearBeforeNotify.clear();
    // <-- SYNCHRONIZED
    m_aFilterListeners.notifyEach( &XFilterControllerListener::disjunctiveTermAdded, aEvent );
}
 
bool FormController::determineLockState() const
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
    // a.) in filter mode we are always locked
    // b.) if we have no valid model or our model (a result set) is not alive -> we're locked
    // c.) if we are inserting everything is OK and we are not locked
    // d.) if are not updatable or on invalid position
    Reference< XResultSet >  xResultSet(m_xModelAsIndex, UNO_QUERY);
    if (m_bFiltering || !xResultSet.is() || !isRowSetAlive(xResultSet))
        return true;
    else
        return !(m_bCanInsert && m_bCurrentRecordNew)
            && (xResultSet->isBeforeFirst() || xResultSet->isAfterLast() || xResultSet->rowDeleted() || !m_bCanUpdate);
}
 
//  FocusListener
void FormController::focusGained(const FocusEvent& e)
{
    // SYNCHRONIZED -->
    ::osl::ClearableMutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    m_aControlBorderManager.focusGained( e.Source );
 
    Reference< XControl >  xControl(e.Source, UNO_QUERY);
    if (m_bDBConnection)
    {
        // do we need to keep the locking of the commit
        // we hold the lock as long as the control differs from the current
        // otherwise we disabled the lock
        m_bCommitLock = m_bCommitLock && xControl.get() != m_xCurrentControl.get();
        if (m_bCommitLock)
            return;
 
        // when do we have to commit a value to form or a filter
        // a.) if the current value is modified
        // b.) there must be a current control
        // c.) and it must be different from the new focus owning control or
        // d.) the focus is moving around (so we have only one control)
 
        if  (   ( m_bModified || m_bFiltering )
            &&  m_xCurrentControl.is()
            &&  (   ( xControl.get() != m_xCurrentControl.get() )
                ||  (   ( e.FocusFlags & FocusChangeReason::AROUND )
                    &&  ( m_bCycle || m_bFiltering )
                    )
                )
            )
        {
            // check the old control if the content is ok
#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
            Reference< XBoundControl >  xLockingTest(m_xCurrentControl, UNO_QUERY);
            bool bControlIsLocked = xLockingTest.is() && xLockingTest->getLock();
            assert(!bControlIsLocked && "FormController::Gained: I'm modified and the current control is locked ? How this ?");
            // normally, a locked control should not be modified, so probably my bModified must
            // have been set from a different context, which I would not understand ...
#endif
            DBG_ASSERT(m_xCurrentControl.is(), "no CurrentControl set");
            // first the control ask if it supports the IFace
            Reference< XBoundComponent >  xBound(m_xCurrentControl, UNO_QUERY);
            if (!xBound.is() && m_xCurrentControl.is())
                xBound.set(m_xCurrentControl->getModel(), UNO_QUERY);
 
            // lock if we lose the focus during commit
            m_bCommitLock = true;
 
            // commit unsuccessful, reset focus
            if (xBound.is() && !xBound->commit())
            {
                // the commit failed and we don't commit again until the current control
                // which couldn't be commit gains the focus again
                Reference< XWindow >  xWindow(m_xCurrentControl, UNO_QUERY);
                if (xWindow.is())
                    xWindow->setFocus();
                return;
            }
            else
            {
                m_bModified = false;
                m_bCommitLock = false;
            }
        }
 
        if (!m_bFiltering && m_bCycle && (e.FocusFlags & FocusChangeReason::AROUND) && m_xCurrentControl.is())
        {
            OSL_ENSURE( m_xFormOperations.is(), "FormController::focusGained: hmm?" );
                // should have been created in setModel
            try
            {
                if ( e.FocusFlags & FocusChangeReason::FORWARD )
                {
                    if ( m_xFormOperations.is() && m_xFormOperations->isEnabled( FormFeature::MoveToNext ) )
                        m_xFormOperations->execute( FormFeature::MoveToNext );
                }
                else // backward
                {
                    if ( m_xFormOperations.is() && m_xFormOperations->isEnabled( FormFeature::MoveToPrevious ) )
                        m_xFormOperations->execute( FormFeature::MoveToPrevious );
                }
            }
            catch ( const Exception& )
            {
                // don't handle this any further. That's an ... admissible error.
                DBG_UNHANDLED_EXCEPTION("svx");
            }
        }
    }
 
    // still one and the same control
    if  (   ( m_xActiveControl == xControl )
        &&  ( xControl == m_xCurrentControl )
        )
    {
        DBG_ASSERT(m_xCurrentControl.is(), "No CurrentControl selected");
        return;
    }
 
    bool bActivated = !m_xActiveControl.is() && xControl.is();
 
    m_xActiveControl  = xControl;
 
    implSetCurrentControl( xControl );
    SAL_WARN_IF( !m_xCurrentControl.is(), "svx.form", "implSetCurrentControl did nonsense!" );
 
    if ( bActivated )
    {
        // (asynchronously) call activation handlers
        m_aActivationEvent.Call();
 
        // call modify listeners
        if ( m_bModified )
            m_aModifyListeners.notifyEach( &XModifyListener::modified, EventObject( *this ) );
    }
 
    // invalidate all features which depend on the currently focused control
    if ( m_bDBConnection && !m_bFiltering )
        implInvalidateCurrentControlDependentFeatures();
 
    if ( !m_xCurrentControl.is() )
        return;
 
    // control gets focus, then possibly in the visible range
    Reference< XFormControllerContext > xContext( m_xFormControllerContext );
    Reference< XControl > xCurrentControl( m_xCurrentControl );
    aGuard.clear();
    // <-- SYNCHRONIZED
 
    if ( xContext.is() )
        xContext->makeVisible( xCurrentControl );
}
 
IMPL_LINK_NOARG( FormController, OnActivated, void*, void )
{
    EventObject aEvent;
    aEvent.Source = *this;
    m_aActivateListeners.notifyEach( &XFormControllerListener::formActivated, aEvent );
}
 
IMPL_LINK_NOARG( FormController, OnDeactivated, void*, void )
{
    EventObject aEvent;
    aEvent.Source = *this;
    m_aActivateListeners.notifyEach( &XFormControllerListener::formDeactivated, aEvent );
}
 
void FormController::focusLost(const FocusEvent& e)
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
 
    m_aControlBorderManager.focusLost( e.Source );
 
    Reference< XWindowPeer >  xNext(e.NextFocus, UNO_QUERY);
    // if focus hasn't passed to some other window, e.g. focus in a welded item, don't deactivate
    if (!xNext)
        return;
    Reference< XControl >  xNextControl = isInList(xNext);
    if (!xNextControl.is())
    {
        m_xActiveControl = nullptr;
        m_aDeactivationEvent.Call();
    }
}
 
void SAL_CALL FormController::mousePressed( const awt::MouseEvent& /*_rEvent*/ )
{
    // not interested in
}
 
void SAL_CALL FormController::mouseReleased( const awt::MouseEvent& /*_rEvent*/ )
{
    // not interested in
}
 
void SAL_CALL FormController::mouseEntered( const awt::MouseEvent& _rEvent )
{
    m_aControlBorderManager.mouseEntered( _rEvent.Source );
}
 
void SAL_CALL FormController::mouseExited( const awt::MouseEvent& _rEvent )
{
    m_aControlBorderManager.mouseExited( _rEvent.Source );
}
 
void SAL_CALL FormController::componentValidityChanged( const EventObject& _rSource )
{
    Reference< XControl > xControl( findControl( m_aControls, Reference< XControlModel >( _rSource.Source, UNO_QUERY ), false, false ) );
    Reference< XValidatableFormComponent > xValidatable( _rSource.Source, UNO_QUERY );
 
    OSL_ENSURE( xControl.is() && xValidatable.is(), "FormController::componentValidityChanged: huh?" );
 
    if ( xControl.is() && xValidatable.is() )
        m_aControlBorderManager.validityChanged( xControl, xValidatable );
}
 
 
void FormController::setModel(const Reference< XTabControllerModel > & Model)
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    DBG_ASSERT(m_xTabController.is(), "FormController::setModel : invalid aggregate !");
 
    try
    {
        // disconnect from the old model
        if (m_xModelAsIndex.is())
        {
            if (m_bDBConnection)
            {
                // we are currently working on the model
                EventObject aEvt(m_xModelAsIndex);
                unloaded(aEvt);
            }
 
            Reference< XLoadable >  xForm(m_xModelAsIndex, UNO_QUERY);
            if (xForm.is())
                xForm->removeLoadListener(this);
 
            Reference< XSQLErrorBroadcaster >  xBroadcaster(m_xModelAsIndex, UNO_QUERY);
            if (xBroadcaster.is())
                xBroadcaster->removeSQLErrorListener(this);
 
            Reference< XDatabaseParameterBroadcaster >  xParamBroadcaster(m_xModelAsIndex, UNO_QUERY);
            if (xParamBroadcaster.is())
                xParamBroadcaster->removeParameterListener(this);
 
        }
 
        disposeAllFeaturesAndDispatchers();
 
        if ( m_xFormOperations.is() )
            m_xFormOperations->dispose();
        m_xFormOperations.clear();
 
        // set the new model wait for the load event
        if (m_xTabController.is())
            m_xTabController->setModel(Model);
        m_xModelAsIndex.set(Model, UNO_QUERY);
        m_xModelAsManager.set(Model, UNO_QUERY);
 
        // only if both ifaces exit, the controller will work successful
        if (!m_xModelAsIndex.is() || !m_xModelAsManager.is())
        {
            m_xModelAsManager = nullptr;
            m_xModelAsIndex = nullptr;
        }
 
        if (m_xModelAsIndex.is())
        {
            // re-create m_xFormOperations
            m_xFormOperations = FormOperations::createWithFormController( m_xComponentContext, this );
            m_xFormOperations->setFeatureInvalidation( this );
 
            // adding load and ui interaction listeners
            Reference< XLoadable >  xForm(Model, UNO_QUERY);
            if (xForm.is())
                xForm->addLoadListener(this);
 
            Reference< XSQLErrorBroadcaster >  xBroadcaster(Model, UNO_QUERY);
            if (xBroadcaster.is())
                xBroadcaster->addSQLErrorListener(this);
 
            Reference< XDatabaseParameterBroadcaster >  xParamBroadcaster(Model, UNO_QUERY);
            if (xParamBroadcaster.is())
                xParamBroadcaster->addParameterListener(this);
 
            // well, is the database already loaded?
            // then we have to simulate a load event
            Reference< XLoadable >  xCursor(m_xModelAsIndex, UNO_QUERY);
            if (xCursor.is() && xCursor->isLoaded())
            {
                EventObject aEvt(xCursor);
                loaded(aEvt);
            }
 
            Reference< XPropertySet > xModelProps( m_xModelAsIndex, UNO_QUERY );
            Reference< XPropertySetInfo > xPropInfo( xModelProps->getPropertySetInfo() );
            if (  xPropInfo.is()
               && xPropInfo->hasPropertyByName( FM_PROP_DYNAMIC_CONTROL_BORDER )
               && xPropInfo->hasPropertyByName( FM_PROP_CONTROL_BORDER_COLOR_FOCUS )
               && xPropInfo->hasPropertyByName( FM_PROP_CONTROL_BORDER_COLOR_MOUSE )
               && xPropInfo->hasPropertyByName( FM_PROP_CONTROL_BORDER_COLOR_INVALID )
               )
            {
                bool bEnableDynamicControlBorder = lcl_shouldUseDynamicControlBorder(
                    xModelProps, xModelProps->getPropertyValue( FM_PROP_DYNAMIC_CONTROL_BORDER ) );
                if ( bEnableDynamicControlBorder )
                    m_aControlBorderManager.enableDynamicBorderColor();
                else
                    m_aControlBorderManager.disableDynamicBorderColor();
 
                Color nColor;
                if ( xModelProps->getPropertyValue( FM_PROP_CONTROL_BORDER_COLOR_FOCUS ) >>= nColor )
                    m_aControlBorderManager.setStatusColor( ControlStatus::Focused, nColor );
                if ( xModelProps->getPropertyValue( FM_PROP_CONTROL_BORDER_COLOR_MOUSE ) >>= nColor )
                    m_aControlBorderManager.setStatusColor( ControlStatus::MouseHover, nColor );
                if ( xModelProps->getPropertyValue( FM_PROP_CONTROL_BORDER_COLOR_INVALID ) >>= nColor )
                    m_aControlBorderManager.setStatusColor( ControlStatus::Invalid, nColor );
            }
        }
    }
    catch( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION("svx");
    }
}
 
 
Reference< XTabControllerModel >  FormController::getModel()
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    DBG_ASSERT(m_xTabController.is(), "FormController::getModel : invalid aggregate !");
    if (!m_xTabController.is())
        return Reference< XTabControllerModel > ();
    return m_xTabController->getModel();
}
 
 
void FormController::addToEventAttacher(const Reference< XControl > & xControl)
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
    OSL_ENSURE( xControl.is(), "FormController::addToEventAttacher: invalid control - how did you reach this?" );
    if ( !xControl.is() )
        return; /* throw IllegalArgumentException(); */
 
    // register at the event attacher
    Reference< XFormComponent >  xComp(xControl->getModel(), UNO_QUERY);
    if (!(xComp.is() && m_xModelAsIndex.is()))
        return;
 
    // and look for the position of the ControlModel in it
    sal_uInt32 nPos = m_xModelAsIndex->getCount();
    Reference< XFormComponent > xTemp;
    for( ; nPos; )
    {
        m_xModelAsIndex->getByIndex(--nPos) >>= xTemp;
        if (xComp.get() == xTemp.get())
        {
            m_xModelAsManager->attach( nPos, Reference<XInterface>( xControl, UNO_QUERY ), Any(xControl) );
            break;
        }
    }
}
 
 
void FormController::removeFromEventAttacher(const Reference< XControl > & xControl)
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
    OSL_ENSURE( xControl.is(), "FormController::removeFromEventAttacher: invalid control - how did you reach this?" );
    if ( !xControl.is() )
        return; /* throw IllegalArgumentException(); */
 
    // register at the event attacher
    Reference< XFormComponent >  xComp(xControl->getModel(), UNO_QUERY);
    if ( !(xComp.is() && m_xModelAsIndex.is()) )
        return;
 
    // and look for the position of the ControlModel in it
    sal_uInt32 nPos = m_xModelAsIndex->getCount();
    Reference< XFormComponent > xTemp;
    for( ; nPos; )
    {
        m_xModelAsIndex->getByIndex(--nPos) >>= xTemp;
        if (xComp.get() == xTemp.get())
        {
            m_xModelAsManager->detach( nPos, Reference<XInterface>( xControl, UNO_QUERY ) );
            break;
        }
    }
}
 
 
void FormController::setContainer(const Reference< XControlContainer > & xContainer)
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
    Reference< XTabControllerModel >  xTabModel(getModel());
    DBG_ASSERT(xTabModel.is() || !xContainer.is(), "No Model defined");
        // if we have a new container we need a model
    DBG_ASSERT(m_xTabController.is(), "FormController::setContainer : invalid aggregate !");
 
    ::osl::MutexGuard aGuard( m_aMutex );
    Reference< XContainer >  xCurrentContainer;
    if (m_xTabController.is())
        xCurrentContainer.set(m_xTabController->getContainer(), UNO_QUERY);
    if (xCurrentContainer.is())
    {
        xCurrentContainer->removeContainerListener(this);
 
        if ( m_aTabActivationIdle.IsActive() )
            m_aTabActivationIdle.Stop();
 
        // clear the filter map
        ::std::for_each( m_aFilterComponents.begin(), m_aFilterComponents.end(), RemoveComponentTextListener( this ) );
        m_aFilterComponents.clear();
 
        // collecting the controls
        for (const Reference<XControl>& rControl : m_aControls)
            implControlRemoved( rControl, true );
 
        // make database-specific things
        if (m_bDBConnection && isListeningForChanges())
            stopListening();
 
        m_aControls.realloc( 0 );
    }
 
    if (m_xTabController.is())
        m_xTabController->setContainer(xContainer);
 
    // What controls belong to the container?
    if (xContainer.is() && xTabModel.is())
    {
        const Sequence< Reference< XControlModel > > aModels = xTabModel->getControlModels();
        Sequence< Reference< XControl > > aAllControls = xContainer->getControls();
 
        sal_Int32 nCount = aModels.getLength();
        m_aControls = Sequence< Reference< XControl > >( nCount );
        Reference< XControl > * pControls = m_aControls.getArray();
 
        // collecting the controls
        sal_Int32 j = 0;
        for (const Reference< XControlModel >& rModel : aModels )
        {
            Reference< XControl > xControl = findControl( aAllControls, rModel, false, true );
            if ( xControl.is() )
            {
                pControls[j++] = xControl;
                implControlInserted( xControl, true );
            }
        }
 
        // not every model had an associated control
        if (j != nCount)
            m_aControls.realloc(j);
 
        // listen at the container
        Reference< XContainer >  xNewContainer(xContainer, UNO_QUERY);
        if (xNewContainer.is())
            xNewContainer->addContainerListener(this);
 
        // make database-specific things
        if (m_bDBConnection)
        {
            m_bLocked = determineLockState();
            setLocks();
            if (!isLocked())
                startListening();
        }
    }
    // the controls are in the right order
    m_bControlsSorted = true;
}
 
 
Reference< XControlContainer >  FormController::getContainer()
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    DBG_ASSERT(m_xTabController.is(), "FormController::getContainer : invalid aggregate !");
    if (!m_xTabController.is())
        return Reference< XControlContainer > ();
    return m_xTabController->getContainer();
}
 
 
Sequence< Reference< XControl > > FormController::getControls()
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    if (!m_bControlsSorted)
    {
        Reference< XTabControllerModel >  xModel = getModel();
        if (!xModel.is())
            return m_aControls;
 
        const Sequence< Reference< XControlModel > > aControlModels = xModel->getControlModels();
        sal_Int32 nModels = aControlModels.getLength();
 
        Sequence< Reference< XControl > > aNewControls(nModels);
 
        Reference< XControl > * pControls = aNewControls.getArray();
        Reference< XControl >  xControl;
 
        // rearrange the controls according to the tab order sequence
        sal_Int32 j = 0;
        for ( const Reference< XControlModel >& rModel : aControlModels )
        {
            xControl = findControl( m_aControls, rModel, true, true );
            if ( xControl.is() )
                pControls[j++] = xControl;
        }
 
        // not every model had an associated control
        if ( j != nModels )
            aNewControls.realloc( j );
 
        m_aControls = std::move(aNewControls);
        m_bControlsSorted = true;
    }
    return m_aControls;
}
 
 
void FormController::autoTabOrder()
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    DBG_ASSERT(m_xTabController.is(), "FormController::autoTabOrder : invalid aggregate !");
    if (m_xTabController.is())
        m_xTabController->autoTabOrder();
}
 
 
void FormController::activateTabOrder()
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    DBG_ASSERT(m_xTabController.is(), "FormController::activateTabOrder : invalid aggregate !");
    if (m_xTabController.is())
        m_xTabController->activateTabOrder();
}
 
 
void FormController::setControlLock(const Reference< XControl > & xControl)
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
    bool bLocked = isLocked();
 
    // It is locked
    // a. if the entire record is locked
    // b. if the associated field is locked
    Reference< XBoundControl >  xBound(xControl, UNO_QUERY);
    if (!(xBound.is() &&
        ( (bLocked && bLocked != bool(xBound->getLock())) ||
          !bLocked)))    // always uncheck individual fields when unlocking
        return;
 
    // there is a data source
    Reference< XPropertySet >  xSet(xControl->getModel(), UNO_QUERY);
    if (!(xSet.is() && ::comphelper::hasProperty(FM_PROP_BOUNDFIELD, xSet)))
        return;
 
    // what about the ReadOnly and Enable properties
    bool bTouch = true;
    if (::comphelper::hasProperty(FM_PROP_ENABLED, xSet))
        bTouch = ::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ENABLED));
    if (bTouch && ::comphelper::hasProperty(FM_PROP_READONLY, xSet))
        bTouch = !::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_READONLY));
 
    if (!bTouch)
        return;
 
    Reference< XPropertySet >  xField;
    xSet->getPropertyValue(FM_PROP_BOUNDFIELD) >>= xField;
    if (!xField.is())
        return;
 
    if (bLocked)
        xBound->setLock(bLocked);
    else
    {
        try
        {
            Any aVal = xField->getPropertyValue(FM_PROP_ISREADONLY);
            if (aVal.hasValue() && ::comphelper::getBOOL(aVal))
                xBound->setLock(true);
            else
                xBound->setLock(bLocked);
        }
        catch( const Exception& )
        {
            DBG_UNHANDLED_EXCEPTION("svx");
        }
 
    }
}
 
 
void FormController::setLocks()
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
    // lock/unlock all controls connected to a data source
    for (const Reference<XControl>& rControl : m_aControls)
        setControlLock( rControl );
}
 
 
namespace
{
    bool lcl_shouldListenForModifications( const Reference< XControl >& _rxControl, const Reference< XPropertyChangeListener >& _rxBoundFieldListener )
    {
        bool bShould = false;
 
        Reference< XBoundComponent > xBound( _rxControl, UNO_QUERY );
        if ( xBound.is() )
        {
            bShould = true;
        }
        else if ( _rxControl.is() )
        {
            Reference< XPropertySet > xModelProps( _rxControl->getModel(), UNO_QUERY );
            if ( xModelProps.is() && ::comphelper::hasProperty( FM_PROP_BOUNDFIELD, xModelProps ) )
            {
                Reference< XPropertySet > xField;
                xModelProps->getPropertyValue( FM_PROP_BOUNDFIELD ) >>= xField;
                bShould = xField.is();
 
                if ( !bShould && _rxBoundFieldListener.is() )
                    xModelProps->addPropertyChangeListener( FM_PROP_BOUNDFIELD, _rxBoundFieldListener );
            }
        }
 
        return bShould;
    }
}
 
 
void FormController::startControlModifyListening(const Reference< XControl > & xControl)
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
 
    bool bModifyListening = lcl_shouldListenForModifications( xControl, this );
 
    // artificial while
    while ( bModifyListening )
    {
        Reference< XModifyBroadcaster >  xMod(xControl, UNO_QUERY);
        if (xMod.is())
        {
            xMod->addModifyListener(this);
            break;
        }
 
        // all the text to prematurely recognize a modified
        Reference< XTextComponent >  xText(xControl, UNO_QUERY);
        if (xText.is())
        {
            xText->addTextListener(this);
            break;
        }
 
        Reference< XCheckBox >  xBox(xControl, UNO_QUERY);
        if (xBox.is())
        {
            xBox->addItemListener(this);
            break;
        }
 
        Reference< XComboBox >  xCbBox(xControl, UNO_QUERY);
        if (xCbBox.is())
        {
            xCbBox->addItemListener(this);
            break;
        }
 
        Reference< XListBox >  xListBox(xControl, UNO_QUERY);
        if (xListBox.is())
        {
            xListBox->addItemListener(this);
            break;
        }
        break;
    }
}
 
 
void FormController::stopControlModifyListening(const Reference< XControl > & xControl)
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
 
    bool bModifyListening = lcl_shouldListenForModifications( xControl, nullptr );
 
    // artificial while
    while (bModifyListening)
    {
        Reference< XModifyBroadcaster >  xMod(xControl, UNO_QUERY);
        if (xMod.is())
        {
            xMod->removeModifyListener(this);
            break;
        }
        // all the text to prematurely recognize a modified
        Reference< XTextComponent >  xText(xControl, UNO_QUERY);
        if (xText.is())
        {
            xText->removeTextListener(this);
            break;
        }
 
        Reference< XCheckBox >  xBox(xControl, UNO_QUERY);
        if (xBox.is())
        {
            xBox->removeItemListener(this);
            break;
        }
 
        Reference< XComboBox >  xCbBox(xControl, UNO_QUERY);
        if (xCbBox.is())
        {
            xCbBox->removeItemListener(this);
            break;
        }
 
        Reference< XListBox >  xListBox(xControl, UNO_QUERY);
        if (xListBox.is())
        {
            xListBox->removeItemListener(this);
            break;
        }
        break;
    }
}
 
 
void FormController::startListening()
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
    m_bModified  = false;
 
    // now register at bound fields
    for (const Reference<XControl>& rControl : m_aControls)
        startControlModifyListening( rControl );
}
 
 
void FormController::stopListening()
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
    m_bModified  = false;
 
    // now register at bound fields
    for (const Reference<XControl>& rControl : m_aControls)
        stopControlModifyListening( rControl );
}
 
 
Reference< XControl >  FormController::findControl(Sequence< Reference< XControl > >& _rControls, const Reference< XControlModel > & xCtrlModel ,bool _bRemove,bool _bOverWrite) const
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
    DBG_ASSERT( xCtrlModel.is(), "findControl - which ?!" );
 
    const Reference< XControl >* pControls = std::find_if(std::cbegin(_rControls), std::cend(_rControls),
        [&xCtrlModel](const Reference< XControl >& rControl) {
            return rControl.is() && rControl->getModel().get() == xCtrlModel.get(); });
    if (pControls != std::cend(_rControls))
    {
        Reference< XControl > xControl( *pControls );
        auto i = static_cast<sal_Int32>(std::distance(std::cbegin(_rControls), pControls));
        if ( _bRemove )
            ::comphelper::removeElementAt( _rControls, i );
        else if ( _bOverWrite )
            _rControls.getArray()[i].clear();
        return xControl;
    }
    return Reference< XControl > ();
}
 
 
void FormController::implControlInserted( const Reference< XControl>& _rxControl, bool _bAddToEventAttacher )
{
    Reference< XWindow > xWindow( _rxControl, UNO_QUERY );
    if ( xWindow.is() )
    {
        xWindow->addFocusListener( this );
        xWindow->addMouseListener( this );
 
        if ( _bAddToEventAttacher )
            addToEventAttacher( _rxControl );
    }
 
    // add a dispatch interceptor to the control (if supported)
    Reference< XDispatchProviderInterception > xInterception( _rxControl, UNO_QUERY );
    if ( xInterception.is() )
        createInterceptor( xInterception );
 
    if ( !_rxControl.is() )
        return;
 
    Reference< XControlModel > xModel( _rxControl->getModel() );
 
    // we want to know about the reset of the model of our controls
    // (for correctly resetting m_bModified)
    Reference< XReset >  xReset( xModel, UNO_QUERY );
    if ( xReset.is() )
        xReset->addResetListener( this );
 
    // and we want to know about the validity, to visually indicate it
    Reference< XValidatableFormComponent > xValidatable( xModel, UNO_QUERY );
    if ( xValidatable.is() )
    {
        xValidatable->addFormComponentValidityListener( this );
        m_aControlBorderManager.validityChanged( _rxControl, xValidatable );
    }
 
}
 
 
void FormController::implControlRemoved( const Reference< XControl>& _rxControl, bool _bRemoveFromEventAttacher )
{
    Reference< XWindow > xWindow( _rxControl, UNO_QUERY );
    if ( xWindow.is() )
    {
        xWindow->removeFocusListener( this );
        xWindow->removeMouseListener( this );
 
        if ( _bRemoveFromEventAttacher )
            removeFromEventAttacher( _rxControl );
    }
 
    Reference< XDispatchProviderInterception > xInterception( _rxControl, UNO_QUERY);
    if ( xInterception.is() )
        deleteInterceptor( xInterception );
 
    if ( _rxControl.is() )
    {
        Reference< XControlModel > xModel( _rxControl->getModel() );
 
        Reference< XReset >  xReset( xModel, UNO_QUERY );
        if ( xReset.is() )
            xReset->removeResetListener( this );
 
        Reference< XValidatableFormComponent > xValidatable( xModel, UNO_QUERY );
        if ( xValidatable.is() )
            xValidatable->removeFormComponentValidityListener( this );
    }
}
 
 
void FormController::implSetCurrentControl( const Reference< XControl >& _rxControl )
{
    if ( m_xCurrentControl.get() == _rxControl.get() )
        return;
 
    Reference< XGridControl > xGridControl( m_xCurrentControl, UNO_QUERY );
    if ( xGridControl.is() )
        xGridControl->removeGridControlListener( this );
 
    m_xCurrentControl = _rxControl;
 
    xGridControl.set( m_xCurrentControl, UNO_QUERY );
    if ( xGridControl.is() )
        xGridControl->addGridControlListener( this );
}
 
 
void FormController::insertControl(const Reference< XControl > & xControl)
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
    m_bControlsSorted = false;
    m_aControls.realloc(m_aControls.getLength() + 1);
    m_aControls.getArray()[m_aControls.getLength() - 1] = xControl;
 
    if (m_pColumnInfoCache)
        m_pColumnInfoCache->deinitializeControls();
 
    implControlInserted( xControl, m_bAttachEvents );
 
    if (m_bDBConnection && !m_bFiltering)
        setControlLock(xControl);
 
    if (isListeningForChanges() && m_bAttachEvents)
        startControlModifyListening( xControl );
}
 
 
void FormController::removeControl(const Reference< XControl > & xControl)
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
    auto pControl = std::find_if(std::cbegin(m_aControls), std::cend(m_aControls),
        [&xControl](const Reference< XControl >& rControl) { return xControl.get() == rControl.get(); });
    if (pControl != std::cend(m_aControls))
    {
        auto nIndex = static_cast<sal_Int32>(std::distance(std::cbegin(m_aControls), pControl));
        ::comphelper::removeElementAt( m_aControls, nIndex );
    }
 
    FilterComponents::iterator componentPos = ::std::find( m_aFilterComponents.begin(), m_aFilterComponents.end(), xControl );
    if ( componentPos != m_aFilterComponents.end() )
        m_aFilterComponents.erase( componentPos );
 
    implControlRemoved( xControl, m_bDetachEvents );
 
    if ( isListeningForChanges() && m_bDetachEvents )
        stopControlModifyListening( xControl );
}
 
// XLoadListener
 
void FormController::loaded(const EventObject& rEvent)
{
    OSL_ENSURE( rEvent.Source == m_xModelAsIndex, "FormController::loaded: where did this come from?" );
 
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
    ::osl::MutexGuard aGuard( m_aMutex );
    Reference< XRowSet >  xForm(rEvent.Source, UNO_QUERY);
    // do we have a connected data source
    if (xForm.is() && getConnection(xForm).is())
    {
        Reference< XPropertySet >  xSet(xForm, UNO_QUERY);
        if (xSet.is())
        {
            Any aVal        = xSet->getPropertyValue(FM_PROP_CYCLE);
            sal_Int32 aVal2 = 0;
            ::cppu::enum2int(aVal2,aVal);
            m_bCycle        = !aVal.hasValue() || static_cast<form::TabulatorCycle>(aVal2) == TabulatorCycle_RECORDS;
            m_bCanUpdate    = canUpdate(xSet);
            m_bCanInsert    = canInsert(xSet);
            m_bCurrentRecordModified = ::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ISMODIFIED));
            m_bCurrentRecordNew      = ::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ISNEW));
 
            startFormListening( xSet, false );
 
            // set the locks for the current controls
            if (getContainer().is())
            {
                m_aLoadEvent.Call();
            }
        }
        else
        {
            m_bCanInsert = m_bCanUpdate = m_bCycle = false;
            m_bCurrentRecordModified = false;
            m_bCurrentRecordNew = false;
            m_bLocked = false;
        }
        m_bDBConnection = true;
    }
    else
    {
        m_bDBConnection = false;
        m_bCanInsert = m_bCanUpdate = m_bCycle = false;
        m_bCurrentRecordModified = false;
        m_bCurrentRecordNew = false;
        m_bLocked = false;
    }
 
    Reference< XColumnsSupplier > xFormColumns( xForm, UNO_QUERY );
    m_pColumnInfoCache.reset( xFormColumns.is() ? new ColumnInfoCache( xFormColumns ) : nullptr );
 
    updateAllDispatchers();
}
 
 
void FormController::updateAllDispatchers() const
{
    ::std::for_each(
        m_aFeatureDispatchers.begin(),
        m_aFeatureDispatchers.end(),
        [] (const DispatcherContainer::value_type& dispatcher) {
            UpdateAllListeners()(dispatcher.second);
        });
}
 
 
IMPL_LINK_NOARG(FormController, OnLoad, void*, void)
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
    m_bLocked = determineLockState();
 
    setLocks();
 
    if (!m_bLocked)
        startListening();
 
    // just one exception toggle the auto values
    if (m_bCurrentRecordNew)
        toggleAutoFields(true);
}
 
 
void FormController::unloaded(const EventObject& /*rEvent*/)
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    updateAllDispatchers();
}
 
 
void FormController::reloading(const EventObject& /*aEvent*/)
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    // do the same like in unloading
    // just one exception toggle the auto values
    m_aToggleEvent.CancelPendingCall();
    unload();
}
 
 
void FormController::reloaded(const EventObject& aEvent)
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    loaded(aEvent);
}
 
 
void FormController::unloading(const EventObject& /*aEvent*/)
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    unload();
}
 
 
void FormController::unload()
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    m_aLoadEvent.CancelPendingCall();
 
    // be sure not to have autofields
    if (m_bCurrentRecordNew)
        toggleAutoFields(false);
 
    // remove bound field listing again
    removeBoundFieldListener();
 
    if (m_bDBConnection && isListeningForChanges())
        stopListening();
 
    Reference< XPropertySet >  xSet( m_xModelAsIndex, UNO_QUERY );
    if ( m_bDBConnection && xSet.is() )
        stopFormListening( xSet, false );
 
    m_bDBConnection = false;
    m_bCanInsert = m_bCanUpdate = m_bCycle = false;
    m_bCurrentRecordModified = m_bCurrentRecordNew = m_bLocked = false;
 
    m_pColumnInfoCache.reset();
}
 
 
void FormController::removeBoundFieldListener()
{
    for (const Reference<XControl>& rControl : m_aControls)
    {
        Reference< XPropertySet > xProp( rControl, UNO_QUERY );
        if ( xProp.is() )
            xProp->removePropertyChangeListener( FM_PROP_BOUNDFIELD, this );
    }
}
 
 
void FormController::startFormListening( const Reference< XPropertySet >& _rxForm, bool _bPropertiesOnly )
{
    try
    {
        if ( m_bCanInsert || m_bCanUpdate )   // form can be modified
        {
            _rxForm->addPropertyChangeListener( FM_PROP_ISNEW, this );
            _rxForm->addPropertyChangeListener( FM_PROP_ISMODIFIED, this );
 
            if ( !_bPropertiesOnly )
            {
                // set the Listener for UI interaction
                Reference< XRowSetApproveBroadcaster > xApprove( _rxForm, UNO_QUERY );
                if ( xApprove.is() )
                    xApprove->addRowSetApproveListener( this );
 
                // listener for row set changes
                Reference< XRowSet > xRowSet( _rxForm, UNO_QUERY );
                if ( xRowSet.is() )
                    xRowSet->addRowSetListener( this );
            }
        }
 
        Reference< XPropertySetInfo > xInfo = _rxForm->getPropertySetInfo();
        if ( xInfo.is() && xInfo->hasPropertyByName( FM_PROP_DYNAMIC_CONTROL_BORDER ) )
            _rxForm->addPropertyChangeListener( FM_PROP_DYNAMIC_CONTROL_BORDER, this );
    }
    catch( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION("svx");
    }
}
 
 
void FormController::stopFormListening( const Reference< XPropertySet >& _rxForm, bool _bPropertiesOnly )
{
    try
    {
        if ( m_bCanInsert || m_bCanUpdate )
        {
            _rxForm->removePropertyChangeListener( FM_PROP_ISNEW, this );
            _rxForm->removePropertyChangeListener( FM_PROP_ISMODIFIED, this );
 
            if ( !_bPropertiesOnly )
            {
                Reference< XRowSetApproveBroadcaster > xApprove( _rxForm, UNO_QUERY );
                if (xApprove.is())
                    xApprove->removeRowSetApproveListener(this);
 
                Reference< XRowSet > xRowSet( _rxForm, UNO_QUERY );
                if ( xRowSet.is() )
                    xRowSet->removeRowSetListener( this );
            }
        }
 
        Reference< XPropertySetInfo > xInfo = _rxForm->getPropertySetInfo();
        if ( xInfo.is() && xInfo->hasPropertyByName( FM_PROP_DYNAMIC_CONTROL_BORDER ) )
            _rxForm->removePropertyChangeListener( FM_PROP_DYNAMIC_CONTROL_BORDER, this );
    }
    catch( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION("svx");
    }
}
 
// css::sdbc::XRowSetListener
 
void FormController::cursorMoved(const EventObject& /*event*/)
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    // toggle the locking ?
    if (m_bLocked != determineLockState())
    {
        m_bLocked = !m_bLocked;
        setLocks();
        if (isListeningForChanges())
            startListening();
        else
            stopListening();
    }
 
    // neither the current control nor the current record are modified anymore
    m_bCurrentRecordModified = m_bModified = false;
}
 
 
void FormController::rowChanged(const EventObject& /*event*/)
{
    // not interested in ...
}
 
void FormController::rowSetChanged(const EventObject& /*event*/)
{
    // not interested in ...
}
 
 
// XContainerListener
 
void SAL_CALL FormController::elementInserted(const ContainerEvent& evt)
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    Reference< XControl > xControl( evt.Element, UNO_QUERY );
    if ( !xControl.is() )
        return;
 
    Reference< XFormComponent >  xModel(xControl->getModel(), UNO_QUERY);
    if (xModel.is() && m_xModelAsIndex == xModel->getParent())
    {
        insertControl(xControl);
 
        if ( m_aTabActivationIdle.IsActive() )
            m_aTabActivationIdle.Stop();
 
        m_aTabActivationIdle.Start();
    }
    // are we in filtermode and a XModeSelector has inserted an element
    else if (m_bFiltering && Reference< XModeSelector > (evt.Source, UNO_QUERY).is())
    {
        xModel.set(evt.Source, UNO_QUERY);
        if (xModel.is() && m_xModelAsIndex == xModel->getParent())
        {
            Reference< XPropertySet >  xSet(xControl->getModel(), UNO_QUERY);
            if (xSet.is() && ::comphelper::hasProperty(FM_PROP_BOUNDFIELD, xSet))
            {
                // does the model use a bound field ?
                Reference< XPropertySet >  xField;
                xSet->getPropertyValue(FM_PROP_BOUNDFIELD) >>= xField;
 
                Reference< XTextComponent >  xText(xControl, UNO_QUERY);
                // may we filter the field?
                if (xText.is() && xField.is() && ::comphelper::hasProperty(FM_PROP_SEARCHABLE, xField) &&
                    ::comphelper::getBOOL(xField->getPropertyValue(FM_PROP_SEARCHABLE)))
                {
                    m_aFilterComponents.push_back( xText );
                    xText->addTextListener( this );
                }
            }
        }
    }
}
 
 
void SAL_CALL FormController::elementReplaced(const ContainerEvent& evt)
{
    // simulate an elementRemoved
    ContainerEvent aRemoveEvent( evt );
    aRemoveEvent.Element = evt.ReplacedElement;
    aRemoveEvent.ReplacedElement = Any();
    elementRemoved( aRemoveEvent );
 
    // simulate an elementInserted
    ContainerEvent aInsertEvent( evt );
    aInsertEvent.ReplacedElement = Any();
    elementInserted( aInsertEvent );
}
 
 
void SAL_CALL FormController::elementRemoved(const ContainerEvent& evt)
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    Reference< XControl >  xControl;
    evt.Element >>= xControl;
    if (!xControl.is())
        return;
 
    Reference< XFormComponent >  xModel(xControl->getModel(), UNO_QUERY);
    if (xModel.is() && m_xModelAsIndex == xModel->getParent())
    {
        removeControl(xControl);
        // Do not recalculate TabOrder, because it must already work internally!
    }
    // are we in filtermode and a XModeSelector has inserted an element
    else if (m_bFiltering && Reference< XModeSelector > (evt.Source, UNO_QUERY).is())
    {
        FilterComponents::iterator componentPos = ::std::find(
            m_aFilterComponents.begin(), m_aFilterComponents.end(), xControl );
        if ( componentPos != m_aFilterComponents.end() )
            m_aFilterComponents.erase( componentPos );
    }
}
 
 
Reference< XControl >  FormController::isInList(const Reference< XWindowPeer > & xPeer) const
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
    const Reference< XControl >* pControls = m_aControls.getConstArray();
 
    sal_uInt32 nCtrls = m_aControls.getLength();
    for ( sal_uInt32 n = 0; n < nCtrls && xPeer.is(); ++n, ++pControls )
    {
        if ( pControls->is() )
        {
            Reference< XVclWindowPeer >  xCtrlPeer( (*pControls)->getPeer(), UNO_QUERY);
            if ( ( xCtrlPeer.get() == xPeer.get() ) || xCtrlPeer->isChild( xPeer ) )
                return *pControls;
        }
    }
    return Reference< XControl > ();
}
 
 
void FormController::activateFirst()
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    DBG_ASSERT(m_xTabController.is(), "FormController::activateFirst : invalid aggregate !");
    if (m_xTabController.is())
        m_xTabController->activateFirst();
}
 
 
void FormController::activateLast()
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    DBG_ASSERT(m_xTabController.is(), "FormController::activateLast : invalid aggregate !");
    if (m_xTabController.is())
        m_xTabController->activateLast();
}
 
// XFormController
 
Reference< XFormOperations > SAL_CALL FormController::getFormOperations()
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    return m_xFormOperations;
}
 
 
Reference< XControl> SAL_CALL FormController::getCurrentControl()
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
    return m_xCurrentControl;
}
 
 
void SAL_CALL FormController::addActivateListener(const Reference< XFormControllerListener > & l)
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
    m_aActivateListeners.addInterface(l);
}
 
void SAL_CALL FormController::removeActivateListener(const Reference< XFormControllerListener > & l)
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
    m_aActivateListeners.removeInterface(l);
}
 
 
void SAL_CALL FormController::addChildController( const Reference< XFormController >& ChildController )
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    if ( !ChildController.is() )
        throw IllegalArgumentException( OUString(), *this, 1 );
        // TODO: (localized) error message
 
    // the parent of our (to-be-)child must be our own model
    Reference< XFormComponent > xFormOfChild( ChildController->getModel(), UNO_QUERY );
    if ( !xFormOfChild.is() )
        throw IllegalArgumentException( OUString(), *this, 1 );
        // TODO: (localized) error message
 
    if ( xFormOfChild->getParent() != m_xModelAsIndex )
        throw IllegalArgumentException( OUString(), *this, 1 );
        // TODO: (localized) error message
 
    m_aChildren.push_back( ChildController );
    ChildController->setParent( *this );
 
    // search the position of the model within the form
    sal_uInt32 nPos = m_xModelAsIndex->getCount();
    Reference< XFormComponent > xTemp;
    for( ; nPos; )
    {
        m_xModelAsIndex->getByIndex(--nPos) >>= xTemp;
        if ( xFormOfChild == xTemp )
        {
            m_xModelAsManager->attach( nPos, Reference<XInterface>( ChildController, UNO_QUERY ), Any( ChildController) );
            break;
        }
    }
}
 
 
Reference< XFormControllerContext > SAL_CALL FormController::getContext()
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
    return m_xFormControllerContext;
}
 
 
void SAL_CALL FormController::setContext( const Reference< XFormControllerContext >& _context )
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
    m_xFormControllerContext = _context;
}
 
 
Reference< XInteractionHandler > SAL_CALL FormController::getInteractionHandler()
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
    return m_xInteractionHandler;
}
 
 
void SAL_CALL FormController::setInteractionHandler( const Reference< XInteractionHandler >& _interactionHandler )
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
    m_xInteractionHandler = _interactionHandler;
}
 
 
void FormController::setFilter(::std::vector<FmFieldInfo>& rFieldInfos)
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
    // create the composer
    Reference< XRowSet > xForm(m_xModelAsIndex, UNO_QUERY);
    Reference< XConnection > xConnection(getConnection(xForm));
    if (xForm.is())
    {
        try
        {
            Reference< XMultiServiceFactory > xFactory( xConnection, UNO_QUERY_THROW );
            m_xComposer.set(
                xFactory->createInstance(u"com.sun.star.sdb.SingleSelectQueryComposer"_ustr),
                UNO_QUERY_THROW );
 
            Reference< XPropertySet > xSet( xForm, UNO_QUERY );
            OUString sStatement  = ::comphelper::getString( xSet->getPropertyValue( FM_PROP_ACTIVECOMMAND ) );
            OUString sFilter     = ::comphelper::getString( xSet->getPropertyValue( FM_PROP_FILTER ) );
            m_xComposer->setElementaryQuery( sStatement );
            m_xComposer->setFilter( sFilter );
        }
        catch( const Exception& )
        {
            DBG_UNHANDLED_EXCEPTION("svx");
        }
    }
 
    if (m_xComposer.is())
    {
        const Sequence< Sequence < PropertyValue > > aFilterRows = m_xComposer->getStructuredFilter();
 
        // ok, we receive the list of filters as sequence of fieldnames, value
        // now we have to transform the fieldname into UI names, that could be a label of the field or
        // an aliasname or the fieldname itself
 
        // first adjust the field names if necessary
        Reference< XNameAccess > xQueryColumns =
            Reference< XColumnsSupplier >( m_xComposer, UNO_QUERY_THROW )->getColumns();
 
        for (auto& rFieldInfo : rFieldInfos)
        {
            if ( xQueryColumns->hasByName(rFieldInfo.aFieldName) )
            {
                if ( (xQueryColumns->getByName(rFieldInfo.aFieldName) >>= rFieldInfo.xField) && rFieldInfo.xField.is() )
                    rFieldInfo.xField->getPropertyValue(FM_PROP_REALNAME) >>= rFieldInfo.aFieldName;
            }
        }
 
        Reference< XDatabaseMetaData> xMetaData(xConnection->getMetaData());
        // now transfer the filters into Value/TextComponent pairs
        ::comphelper::UStringMixEqual aCompare(xMetaData->storesMixedCaseQuotedIdentifiers());
 
        // need to parse criteria localized
        Reference< XNumberFormatsSupplier> xFormatSupplier( getNumberFormats(xConnection, true));
        Reference< XNumberFormatter> xFormatter = NumberFormatter::create(m_xComponentContext);
        xFormatter->attachNumberFormatsSupplier(xFormatSupplier);
        Locale aAppLocale = Application::GetSettings().GetUILanguageTag().getLocale();
        const LocaleDataWrapper& rLocaleWrapper( Application::GetSettings().GetUILocaleDataWrapper() );
        OUString strDecimalSeparator = rLocaleWrapper.getNumDecimalSep();
 
        // retrieving the filter
        for (const Sequence < PropertyValue >& rRow : aFilterRows)
        {
            FmFilterRow aRow;
 
            // search a field for the given name
            for (const PropertyValue& rRefValue : rRow)
            {
                // look for the text component
                Reference< XPropertySet > xField;
                try
                {
                    Reference< XPropertySet > xSet;
                    OUString aRealName;
 
                    // first look with the given name
                    if (xQueryColumns->hasByName(rRefValue.Name))
                    {
                        xQueryColumns->getByName(rRefValue.Name) >>= xSet;
 
                        // get the RealName
                        xSet->getPropertyValue(u"RealName"_ustr) >>= aRealName;
 
                        // compare the condition field name and the RealName
                        if (aCompare(aRealName, rRefValue.Name))
                            xField = xSet;
                    }
                    if (!xField.is())
                    {
                        // no we have to check every column to find the realname
                        Reference< XIndexAccess > xColumnsByIndex(xQueryColumns, UNO_QUERY);
                        for (sal_Int32 n = 0, nCount = xColumnsByIndex->getCount(); n < nCount; n++)
                        {
                            xColumnsByIndex->getByIndex(n) >>= xSet;
                            xSet->getPropertyValue(u"RealName"_ustr) >>= aRealName;
                            if (aCompare(aRealName, rRefValue.Name))
                            {
                                // get the column by its alias
                                xField = xSet;
                                break;
                            }
                        }
                    }
                    if (!xField.is())
                        continue;
                }
                catch (const Exception&)
                {
                    continue;
                }
 
                // find the text component
                for (const auto& rFieldInfo : rFieldInfos)
                {
                    // we found the field so insert a new entry to the filter row
                    if (rFieldInfo.xField == xField)
                    {
                        // do we already have the control ?
                        if (aRow.find(rFieldInfo.xText) != aRow.end())
                        {
                            OString aVal = m_pParser->getContext().getIntlKeywordAscii(IParseContext::InternationalKeyCode::And);
                            aRow[rFieldInfo.xText] = aRow[rFieldInfo.xText] + " "  +
                                                        OStringToOUString(aVal, RTL_TEXTENCODING_ASCII_US) + " " +
                                                        ::comphelper::getString(rRefValue.Value);
                        }
                        else
                        {
                            OUString sPredicate,sErrorMsg;
                            rRefValue.Value >>= sPredicate;
                            std::unique_ptr< OSQLParseNode > pParseNode = predicateTree(sErrorMsg, sPredicate, xFormatter, xField);
                            if ( pParseNode != nullptr )
                            {
                                OUString sCriteria;
                                switch (rRefValue.Handle)
                                {
                                    case css::sdb::SQLFilterOperator::EQUAL:
                                        sCriteria += "=";
                                        break;
                                    case css::sdb::SQLFilterOperator::NOT_EQUAL:
                                        sCriteria += "!=";
                                        break;
                                    case css::sdb::SQLFilterOperator::LESS:
                                        sCriteria += "<";
                                        break;
                                    case css::sdb::SQLFilterOperator::GREATER:
                                        sCriteria += ">";
                                        break;
                                    case css::sdb::SQLFilterOperator::LESS_EQUAL:
                                        sCriteria += "<=";
                                        break;
                                    case css::sdb::SQLFilterOperator::GREATER_EQUAL:
                                        sCriteria += ">=";
                                        break;
                                    case css::sdb::SQLFilterOperator::LIKE:
                                        sCriteria += "LIKE ";
                                        break;
                                    case css::sdb::SQLFilterOperator::NOT_LIKE:
                                        sCriteria += "NOT LIKE ";
                                        break;
                                    case css::sdb::SQLFilterOperator::SQLNULL:
                                        sCriteria += "IS NULL";
                                        break;
                                    case css::sdb::SQLFilterOperator::NOT_SQLNULL:
                                        sCriteria += "IS NOT NULL";
                                        break;
                                }
                                pParseNode->parseNodeToPredicateStr( sCriteria
                                                                    ,xConnection
                                                                    ,xFormatter
                                                                    ,xField
                                                                    ,OUString()
                                                                    ,aAppLocale
                                                                    ,strDecimalSeparator
                                                                    ,getParseContext());
                                aRow[rFieldInfo.xText] = sCriteria;
                            }
                        }
                    }
                }
            }
 
            if (aRow.empty())
                continue;
 
            impl_addFilterRow( aRow );
        }
    }
 
    // now set the filter controls
    for (const auto& rFieldInfo : rFieldInfos)
    {
        m_aFilterComponents.push_back( rFieldInfo.xText );
    }
}
 
 
void FormController::startFiltering()
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
 
    Reference< XConnection >  xConnection( getConnection( Reference< XRowSet >( m_xModelAsIndex, UNO_QUERY ) ) );
    if ( !xConnection.is() )
        // nothing to do - can't filter a form which is not connected
        return;
 
    // stop listening for controls
    if (isListeningForChanges())
        stopListening();
 
    m_bFiltering = true;
 
    // as we don't want new controls to be attached to the scripting environment
    // we change attach flags
    m_bAttachEvents = false;
 
    // exchanging the controls for the current form
    Sequence< Reference< XControl > > aControlsCopy( m_aControls );
    const Reference< XControl >* pControls = aControlsCopy.getConstArray();
    sal_Int32 nControlCount = aControlsCopy.getLength();
 
    // the control we have to activate after replacement
    Reference< XNumberFormatsSupplier >  xFormatSupplier = getNumberFormats(xConnection, true);
    Reference< XNumberFormatter >  xFormatter = NumberFormatter::create(m_xComponentContext);
    xFormatter->attachNumberFormatsSupplier(xFormatSupplier);
 
    // structure for storing the field info
    ::std::vector<FmFieldInfo> aFieldInfos;
 
    for (sal_Int32 i = nControlCount; i > 0;)
    {
        Reference< XControl > xControl = pControls[--i];
        if (xControl.is())
        {
            // no events for the control anymore
            removeFromEventAttacher(xControl);
 
            // do we have a mode selector
            Reference< XModeSelector >  xSelector(xControl, UNO_QUERY);
            if (xSelector.is())
            {
                xSelector->setMode( u"FilterMode"_ustr );
 
                // listening for new controls of the selector
                Reference< XContainer >  xContainer(xSelector, UNO_QUERY);
                if (xContainer.is())
                    xContainer->addContainerListener(this);
 
                Reference< XEnumerationAccess >  xElementAccess(xSelector, UNO_QUERY);
                if (xElementAccess.is())
                {
                    Reference< XEnumeration >  xEnumeration(xElementAccess->createEnumeration());
                    Reference< XControl >  xSubControl;
                    while (xEnumeration->hasMoreElements())
                    {
                        xEnumeration->nextElement() >>= xSubControl;
                        if (xSubControl.is())
                        {
                            Reference< XPropertySet >  xSet(xSubControl->getModel(), UNO_QUERY);
                            if (xSet.is() && ::comphelper::hasProperty(FM_PROP_BOUNDFIELD, xSet))
                            {
                                // does the model use a bound field ?
                                Reference< XPropertySet >  xField;
                                xSet->getPropertyValue(FM_PROP_BOUNDFIELD) >>= xField;
 
                                Reference< XTextComponent >  xText(xSubControl, UNO_QUERY);
                                // may we filter the field?
                                if (xText.is() && xField.is() && ::comphelper::hasProperty(FM_PROP_SEARCHABLE, xField) &&
                                    ::comphelper::getBOOL(xField->getPropertyValue(FM_PROP_SEARCHABLE)))
                                {
                                    aFieldInfos.emplace_back(xField, xText);
                                    xText->addTextListener(this);
                                }
                            }
                        }
                    }
                }
                continue;
            }
 
            Reference< XPropertySet >  xModel( xControl->getModel(), UNO_QUERY );
            if (xModel.is() && ::comphelper::hasProperty(FM_PROP_BOUNDFIELD, xModel))
            {
                // does the model use a bound field ?
                Any aVal = xModel->getPropertyValue(FM_PROP_BOUNDFIELD);
                Reference< XPropertySet >  xField;
                aVal >>= xField;
 
                // may we filter the field?
 
                if  (   xField.is()
                    &&  ::comphelper::hasProperty( FM_PROP_SEARCHABLE, xField )
                    && ::comphelper::getBOOL( xField->getPropertyValue( FM_PROP_SEARCHABLE ) )
                    )
                {
                    // create a filter control
                    Reference< XControl > xFilterControl = form::control::FilterControl::createWithFormat(
                        m_xComponentContext,
                        getDialogParentWindow(this),
                        xFormatter,
                        xModel);
 
                    if ( replaceControl( xControl, xFilterControl ) )
                    {
                        Reference< XTextComponent > xFilterText( xFilterControl, UNO_QUERY );
                        aFieldInfos.emplace_back( xField, xFilterText );
                        xFilterText->addTextListener(this);
                    }
                }
            }
            else
            {
                // unsubscribe from EventManager
            }
        }
    }
 
    // we have all filter controls now, so the next step is to read the filters from the form
    // resolve all aliases and set the current filter to the according structure
    setFilter(aFieldInfos);
 
    Reference< XPropertySet > xSet( m_xModelAsIndex, UNO_QUERY );
    if ( xSet.is() )
        stopFormListening( xSet, true );
 
    impl_setTextOnAllFilter_throw();
 
    // lock all controls which are not used for filtering
    m_bLocked = determineLockState();
    setLocks();
    m_bAttachEvents = true;
}
 
 
void FormController::stopFiltering()
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
    if ( !m_bFiltering ) // #104693# OJ
    {   // nothing to do
        return;
    }
 
    m_bFiltering = false;
    m_bDetachEvents = false;
 
    ::comphelper::disposeComponent(m_xComposer);
 
    // exchanging the controls for the current form
    Sequence< Reference< XControl > > aControlsCopy( m_aControls );
    const Reference< XControl > * pControls = aControlsCopy.getConstArray();
    sal_Int32 nControlCount = aControlsCopy.getLength();
 
    // clear the filter control map
    ::std::for_each( m_aFilterComponents.begin(), m_aFilterComponents.end(), RemoveComponentTextListener( this ) );
    m_aFilterComponents.clear();
 
    for ( sal_Int32 i = nControlCount; i > 0; )
    {
        Reference< XControl > xControl = pControls[--i];
        if (xControl.is())
        {
            // now enable event handling again
            addToEventAttacher(xControl);
 
            Reference< XModeSelector >  xSelector(xControl, UNO_QUERY);
            if (xSelector.is())
            {
                xSelector->setMode( u"DataMode"_ustr );
 
                // listening for new controls of the selector
                Reference< XContainer >  xContainer(xSelector, UNO_QUERY);
                if (xContainer.is())
                    xContainer->removeContainerListener(this);
                continue;
            }
 
            Reference< XPropertySet >  xSet(xControl->getModel(), UNO_QUERY);
            if (xSet.is() && ::comphelper::hasProperty(FM_PROP_BOUNDFIELD, xSet))
            {
                // does the model use a bound field ?
                Reference< XPropertySet >  xField;
                xSet->getPropertyValue(FM_PROP_BOUNDFIELD) >>= xField;
 
                // may we filter the field?
                if  (   xField.is()
                    &&  ::comphelper::hasProperty( FM_PROP_SEARCHABLE, xField )
                    &&  ::comphelper::getBOOL( xField->getPropertyValue( FM_PROP_SEARCHABLE ) )
                    )
                {
                    OUString sServiceName;
                    OSL_VERIFY( xSet->getPropertyValue( FM_PROP_DEFAULTCONTROL ) >>= sServiceName );
                    Reference< XControl > xNewControl( m_xComponentContext->getServiceManager()->createInstanceWithContext( sServiceName, m_xComponentContext ), UNO_QUERY );
                    replaceControl( xControl, xNewControl );
                }
            }
        }
    }
 
    Reference< XPropertySet >  xSet( m_xModelAsIndex, UNO_QUERY );
    if ( xSet.is() )
        startFormListening( xSet, true );
 
    m_bDetachEvents = true;
 
    m_aFilterRows.clear();
    m_nCurrentFilterPosition = -1;
 
    // release the locks if possible
    // lock all controls which are not used for filtering
    m_bLocked = determineLockState();
    setLocks();
 
    // restart listening for control modifications
    if (isListeningForChanges())
        startListening();
}
 
// XModeSelector
 
void FormController::setMode(const OUString& Mode)
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    if (!supportsMode(Mode))
        throw NoSupportException();
 
    if (Mode == m_aMode)
        return;
 
    m_aMode = Mode;
 
    if ( Mode == "FilterMode" )
        startFiltering();
    else
        stopFiltering();
 
    for (const auto& rChild : m_aChildren)
    {
        Reference< XModeSelector > xMode(rChild, UNO_QUERY);
        if ( xMode.is() )
            xMode->setMode(Mode);
    }
}
 
 
OUString SAL_CALL FormController::getMode()
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    return m_aMode;
}
 
 
Sequence< OUString > SAL_CALL FormController::getSupportedModes()
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    static Sequence< OUString > const aModes
    {
        u"DataMode"_ustr,
        u"FilterMode"_ustr
    };
    return aModes;
}
 
sal_Bool SAL_CALL FormController::supportsMode(const OUString& Mode)
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    Sequence< OUString > aModes(getSupportedModes());
    return comphelper::findValue(aModes, Mode) != -1;
}
 
css::uno::Reference<css::awt::XWindow> FormController::getDialogParentWindow(const css::uno::Reference<css::form::runtime::XFormController> & xFormController)
{
    try
    {
        Reference< XControl > xContainerControl( xFormController->getContainer(), UNO_QUERY_THROW );
        Reference<XWindow> xContainerWindow(xContainerControl->getPeer(), UNO_QUERY_THROW);
        return xContainerWindow;
    }
    catch( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION("svx");
    }
    return nullptr;
}
 
bool FormController::checkFormComponentValidity( OUString& /* [out] */ _rFirstInvalidityExplanation, Reference< XControlModel >& /* [out] */ _rxFirstInvalidModel )
{
    try
    {
        Reference< XEnumerationAccess > xControlEnumAcc( getModel(), UNO_QUERY );
        Reference< XEnumeration > xControlEnumeration;
        if ( xControlEnumAcc.is() )
            xControlEnumeration = xControlEnumAcc->createEnumeration();
        OSL_ENSURE( xControlEnumeration.is(), "FormController::checkFormComponentValidity: cannot enumerate the controls!" );
        if ( !xControlEnumeration.is() )
            // assume all valid
            return true;
 
        Reference< XValidatableFormComponent > xValidatable;
        while ( xControlEnumeration->hasMoreElements() )
        {
            if ( !( xControlEnumeration->nextElement() >>= xValidatable ) )
                // control does not support validation
                continue;
 
            if ( xValidatable->isValid() )
                continue;
 
            Reference< XValidator > xValidator( xValidatable->getValidator() );
            OSL_ENSURE( xValidator.is(), "FormController::checkFormComponentValidity: invalid, but no validator?" );
            if ( !xValidator.is() )
                // this violates the interface definition of css.form.validation.XValidatableFormComponent ...
                continue;
 
            _rFirstInvalidityExplanation = xValidator->explainInvalid( xValidatable->getCurrentValue() );
            _rxFirstInvalidModel.set(xValidatable, css::uno::UNO_QUERY);
            return false;
        }
    }
    catch( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION("svx");
    }
    return true;
}
 
 
Reference< XControl > FormController::locateControl( const Reference< XControlModel >& _rxModel )
{
    try
    {
        const Sequence< Reference< XControl > > aControls( getControls() );
 
        for ( auto const & control : aControls )
        {
            OSL_ENSURE( control.is(), "FormController::locateControl: NULL-control?" );
            if ( control.is() )
            {
                if ( control->getModel() == _rxModel )
                    return control;
            }
        }
        OSL_FAIL( "FormController::locateControl: did not find a control for this model!" );
    }
    catch( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION("svx");
    }
    return nullptr;
}
 
 
namespace
{
    void displayErrorSetFocus(const OUString& _rMessage, const Reference<XControl>& _rxFocusControl,
                              const css::uno::Reference<css::awt::XWindow>& rDialogParent)
    {
        SQLContext aError(SvxResId(RID_STR_WRITEERROR), {}, {}, 0, {}, _rMessage);
        displayException(aError, rDialogParent);
 
        if ( _rxFocusControl.is() )
        {
            Reference< XWindow > xControlWindow( _rxFocusControl, UNO_QUERY );
            OSL_ENSURE( xControlWindow.is(), "displayErrorSetFocus: invalid control!" );
            if ( xControlWindow.is() )
                xControlWindow->setFocus();
        }
    }
 
    bool lcl_shouldValidateRequiredFields_nothrow( const Reference< XInterface >& _rxForm )
    {
        try
        {
            static constexpr OUString s_sFormsCheckRequiredFields = u"FormsCheckRequiredFields"_ustr;
 
            // first, check whether the form has a property telling us the answer
            // this allows people to use the XPropertyContainer interface of a form to control
            // the behaviour on a per-form basis.
            Reference< XPropertySet > xFormProps( _rxForm, UNO_QUERY_THROW );
            Reference< XPropertySetInfo > xPSI( xFormProps->getPropertySetInfo() );
            if ( xPSI->hasPropertyByName( s_sFormsCheckRequiredFields ) )
            {
                bool bShouldValidate = true;
                OSL_VERIFY( xFormProps->getPropertyValue( s_sFormsCheckRequiredFields ) >>= bShouldValidate );
                return bShouldValidate;
            }
 
            // next, check the data source which created the connection
            Reference< XChild > xConnectionAsChild( xFormProps->getPropertyValue( FM_PROP_ACTIVE_CONNECTION ), UNO_QUERY_THROW );
            Reference< XPropertySet > xDataSource( xConnectionAsChild->getParent(), UNO_QUERY );
            if ( !xDataSource.is() )
                // seldom (but possible): this is not a connection created by a data source
                return true;
 
            Reference< XPropertySet > xDataSourceSettings(
                xDataSource->getPropertyValue(u"Settings"_ustr),
                UNO_QUERY_THROW );
 
            bool bShouldValidate = true;
            OSL_VERIFY( xDataSourceSettings->getPropertyValue( s_sFormsCheckRequiredFields ) >>= bShouldValidate );
            return bShouldValidate;
        }
        catch( const Exception& )
        {
            DBG_UNHANDLED_EXCEPTION("svx");
        }
 
        return true;
    }
}
 
// XRowSetApproveListener
 
sal_Bool SAL_CALL FormController::approveRowChange(const RowChangeEvent& _rEvent)
{
    ::osl::ClearableMutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    ::comphelper::OInterfaceIteratorHelper3 aIter(m_aRowSetApproveListeners);
    bool bValid = true;
    if (aIter.hasMoreElements())
    {
        RowChangeEvent aEvt( _rEvent );
        aEvt.Source = *this;
        bValid = aIter.next()->approveRowChange(aEvt);
    }
 
    if ( !bValid )
        return bValid;
 
    if  (   ( _rEvent.Action != RowChangeAction::INSERT )
        &&  ( _rEvent.Action != RowChangeAction::UPDATE )
        )
        return bValid;
 
    // if some of the control models are bound to validators, check them
    OUString sInvalidityExplanation;
    Reference< XControlModel > xInvalidModel;
    if ( !checkFormComponentValidity( sInvalidityExplanation, xInvalidModel ) )
    {
        Reference< XControl > xControl( locateControl( xInvalidModel ) );
        aGuard.clear();
        displayErrorSetFocus( sInvalidityExplanation, xControl, getDialogParentWindow(this) );
        return false;
    }
 
    // check values on NULL and required flag
    if ( !lcl_shouldValidateRequiredFields_nothrow( _rEvent.Source ) )
        return true;
 
    OSL_ENSURE(m_pColumnInfoCache, "FormController::approveRowChange: no column infos!");
    if (!m_pColumnInfoCache)
        return true;
 
    try
    {
        if ( !m_pColumnInfoCache->controlsInitialized() )
            m_pColumnInfoCache->initializeControls( getControls() );
 
        size_t colCount = m_pColumnInfoCache->getColumnCount();
        for ( size_t col = 0; col < colCount; ++col )
        {
            const ColumnInfo& rColInfo = m_pColumnInfoCache->getColumnInfo( col );
 
            if ( rColInfo.bAutoIncrement )
                continue;
 
            if ( rColInfo.bReadOnly )
                continue;
 
            if ( !rColInfo.xFirstControlWithInputRequired.is() && !rColInfo.xFirstGridWithInputRequiredColumn.is() )
            {
                continue;
            }
 
            // TODO: in case of binary fields, this "getString" below is extremely expensive
            if ( !rColInfo.xColumn->getString().isEmpty() || !rColInfo.xColumn->wasNull() )
                continue;
 
            OUString sMessage( SvxResId( RID_ERR_FIELDREQUIRED ) );
            sMessage = sMessage.replaceFirst( "#", rColInfo.sName );
 
            // the control to focus
            Reference< XControl > xControl( rColInfo.xFirstControlWithInputRequired );
            if ( !xControl.is() )
                xControl.set( rColInfo.xFirstGridWithInputRequiredColumn, UNO_QUERY );
 
            aGuard.clear();
            displayErrorSetFocus( sMessage, rColInfo.xFirstControlWithInputRequired, getDialogParentWindow(this) );
            return false;
        }
    }
    catch( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION("svx");
    }
 
    return true;
}
 
 
sal_Bool SAL_CALL FormController::approveCursorMove(const EventObject& event)
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    ::comphelper::OInterfaceIteratorHelper3 aIter(m_aRowSetApproveListeners);
    if (aIter.hasMoreElements())
    {
        EventObject aEvt(event);
        aEvt.Source = *this;
        return aIter.next()->approveCursorMove(aEvt);
    }
 
    return true;
}
 
 
sal_Bool SAL_CALL FormController::approveRowSetChange(const EventObject& event)
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    ::comphelper::OInterfaceIteratorHelper3 aIter(m_aRowSetApproveListeners);
    if (aIter.hasMoreElements())
    {
        EventObject aEvt(event);
        aEvt.Source = *this;
        return aIter.next()->approveRowSetChange(aEvt);
    }
 
    return true;
}
 
// XRowSetApproveBroadcaster
 
void SAL_CALL FormController::addRowSetApproveListener(const Reference< XRowSetApproveListener > & _rxListener)
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    m_aRowSetApproveListeners.addInterface(_rxListener);
}
 
 
void SAL_CALL FormController::removeRowSetApproveListener(const Reference< XRowSetApproveListener > & _rxListener)
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    m_aRowSetApproveListeners.removeInterface(_rxListener);
}
 
// XErrorListener
 
void SAL_CALL FormController::errorOccured(const SQLErrorEvent& aEvent)
{
    ::osl::ClearableMutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    ::comphelper::OInterfaceIteratorHelper3 aIter(m_aErrorListeners);
    if (aIter.hasMoreElements())
    {
        SQLErrorEvent aEvt(aEvent);
        aEvt.Source = *this;
        aIter.next()->errorOccured(aEvt);
    }
    else
    {
        aGuard.clear();
        displayException(aEvent, getDialogParentWindow(this));
    }
}
 
// XErrorBroadcaster
 
void SAL_CALL FormController::addSQLErrorListener(const Reference< XSQLErrorListener > & aListener)
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    m_aErrorListeners.addInterface(aListener);
}
 
 
void SAL_CALL FormController::removeSQLErrorListener(const Reference< XSQLErrorListener > & aListener)
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    m_aErrorListeners.removeInterface(aListener);
}
 
// XDatabaseParameterBroadcaster2
 
void SAL_CALL FormController::addDatabaseParameterListener(const Reference< XDatabaseParameterListener > & aListener)
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    m_aParameterListeners.addInterface(aListener);
}
 
 
void SAL_CALL FormController::removeDatabaseParameterListener(const Reference< XDatabaseParameterListener > & aListener)
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    m_aParameterListeners.removeInterface(aListener);
}
 
// XDatabaseParameterBroadcaster
 
void SAL_CALL FormController::addParameterListener(const Reference< XDatabaseParameterListener > & aListener)
{
    FormController::addDatabaseParameterListener( aListener );
}
 
 
void SAL_CALL FormController::removeParameterListener(const Reference< XDatabaseParameterListener > & aListener)
{
    FormController::removeDatabaseParameterListener( aListener );
}
 
// XDatabaseParameterListener
 
sal_Bool SAL_CALL FormController::approveParameter(const DatabaseParameterEvent& aEvent)
{
    SolarMutexGuard aSolarGuard;
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    ::comphelper::OInterfaceIteratorHelper3 aIter(m_aParameterListeners);
    if (aIter.hasMoreElements())
    {
        DatabaseParameterEvent aEvt(aEvent);
        aEvt.Source = *this;
        return aIter.next()->approveParameter(aEvt);
    }
    else
    {
        // default handling: instantiate an interaction handler and let it handle the parameter request
        try
        {
            if ( !ensureInteractionHandler() )
                return false;
 
            // two continuations allowed: OK and Cancel
            rtl::Reference<OParameterContinuation> pParamValues = new OParameterContinuation;
            rtl::Reference<OInteractionAbort> pAbort = new OInteractionAbort;
            // the request
            ParametersRequest aRequest;
            aRequest.Parameters = aEvent.Parameters;
            aRequest.Connection = getConnection(Reference< XRowSet >(aEvent.Source, UNO_QUERY));
            rtl::Reference<OInteractionRequest> pParamRequest = new OInteractionRequest(Any(aRequest));
            // some knittings
            pParamRequest->addContinuation(pParamValues);
            pParamRequest->addContinuation(pAbort);
 
            // handle the request
            m_xInteractionHandler->handle(pParamRequest);
 
            if (!pParamValues->wasSelected())
                // canceled
                return false;
 
            // transfer the values into the parameter supplier
            Sequence< PropertyValue > aFinalValues = pParamValues->getValues();
            if (aFinalValues.getLength() != aRequest.Parameters->getCount())
            {
                OSL_FAIL("FormController::approveParameter: the InteractionHandler returned nonsense!");
                return false;
            }
            const PropertyValue* pFinalValues = aFinalValues.getConstArray();
            for (sal_Int32 i=0; i<aFinalValues.getLength(); ++i, ++pFinalValues)
            {
                Reference< XPropertySet > xParam(
                    aRequest.Parameters->getByIndex(i), css::uno::UNO_QUERY);
                if (xParam.is())
                {
#ifdef DBG_UTIL
                    OUString sName;
                    xParam->getPropertyValue(FM_PROP_NAME) >>= sName;
                    DBG_ASSERT(sName == pFinalValues->Name, "FormController::approveParameter: suspicious value names!");
#endif
                    try { xParam->setPropertyValue(FM_PROP_VALUE, pFinalValues->Value); }
                    catch(Exception&)
                    {
                        OSL_FAIL("FormController::approveParameter: setting one of the properties failed!");
                    }
                }
            }
        }
        catch(Exception&)
        {
            DBG_UNHANDLED_EXCEPTION("svx");
        }
    }
    return true;
}
 
// XConfirmDeleteBroadcaster
 
void SAL_CALL FormController::addConfirmDeleteListener(const Reference< XConfirmDeleteListener > & aListener)
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    m_aDeleteListeners.addInterface(aListener);
}
 
 
void SAL_CALL FormController::removeConfirmDeleteListener(const Reference< XConfirmDeleteListener > & aListener)
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    m_aDeleteListeners.removeInterface(aListener);
}
 
// XConfirmDeleteListener
 
sal_Bool SAL_CALL FormController::confirmDelete(const RowChangeEvent& aEvent)
{
    ::osl::MutexGuard aGuard( m_aMutex );
    impl_checkDisposed_throw();
 
    ::comphelper::OInterfaceIteratorHelper3 aIter(m_aDeleteListeners);
    if (aIter.hasMoreElements())
    {
        RowChangeEvent aEvt(aEvent);
        aEvt.Source = *this;
        return aIter.next()->confirmDelete(aEvt);
    }
    // default handling: instantiate an interaction handler and let it handle the request
 
    OUString sTitle;
    sal_Int32 nLength = aEvent.Rows;
    if ( nLength > 1 )
    {
        sTitle = SvxResId( RID_STR_DELETECONFIRM_RECORDS );
        sTitle = sTitle.replaceFirst( "#", OUString::number(nLength) );
    }
    else
        sTitle = SvxResId( RID_STR_DELETECONFIRM_RECORD );
 
    try
    {
        if ( !ensureInteractionHandler() )
            return false;
 
        // two continuations allowed: Yes and No
        rtl::Reference<OInteractionApprove> pApprove = new OInteractionApprove;
        rtl::Reference<OInteractionDisapprove> pDisapprove = new OInteractionDisapprove;
 
        // the request
        SQLWarning aDetails(SvxResId(RID_STR_DELETECONFIRM), {}, {}, 0, {});
        SQLWarning aWarning(sTitle, {}, {}, 0, css::uno::Any(aDetails));
 
        rtl::Reference<OInteractionRequest> pRequest = new OInteractionRequest( Any( aWarning ) );
 
        // some knittings
        pRequest->addContinuation( pApprove );
        pRequest->addContinuation( pDisapprove );
 
        // handle the request
        m_xInteractionHandler->handle( pRequest );
 
        if ( pApprove->wasSelected() )
            return true;
    }
    catch( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION("svx");
    }
 
    return false;
}
 
 
void SAL_CALL FormController::invalidateFeatures( const Sequence< ::sal_Int16 >& Features )
{
    ::osl::MutexGuard aGuard( m_aMutex );
    // for now, just copy the ids of the features, because...
    m_aInvalidFeatures.insert( Features.begin(), Features.end() );
 
    // ... we will do the real invalidation asynchronously
    if ( !m_aFeatureInvalidationTimer.IsActive() )
        m_aFeatureInvalidationTimer.Start();
}
 
 
void SAL_CALL FormController::invalidateAllFeatures(  )
{
    ::osl::ClearableMutexGuard aGuard( m_aMutex );
 
    Sequence< sal_Int16 > aInterceptedFeatures( comphelper::mapKeysToSequence(m_aFeatureDispatchers) );
 
    aGuard.clear();
    if ( aInterceptedFeatures.hasElements() )
        invalidateFeatures( aInterceptedFeatures );
}
 
 
Reference< XDispatch >
FormController::interceptedQueryDispatch( const URL& aURL,
                                            const OUString& /*aTargetFrameName*/, sal_Int32 /*nSearchFlags*/)
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
    Reference< XDispatch >  xReturn;
    // dispatches handled by ourself
    if  (   ( aURL.Complete == FMURL_CONFIRM_DELETION )
        ||  (   ( aURL.Complete == "private:/InteractionHandler" )
            &&  ensureInteractionHandler()
            )
        )
        xReturn = static_cast< XDispatch* >( this );
 
    // dispatches of FormSlot-URLs we have to translate
    if ( !xReturn.is() && m_xFormOperations.is() )
    {
        // find the slot id which corresponds to the URL
        sal_Int32 nFeatureSlotId = svx::FeatureSlotTranslation::getControllerFeatureSlotIdForURL( aURL.Main );
        sal_Int16 nFormFeature = ( nFeatureSlotId != -1 ) ? svx::FeatureSlotTranslation::getFormFeatureForSlotId( nFeatureSlotId ) : -1;
        if ( nFormFeature > 0 )
        {
            // get the dispatcher for this feature, create if necessary
            DispatcherContainer::const_iterator aDispatcherPos = m_aFeatureDispatchers.find( nFormFeature );
            if ( aDispatcherPos == m_aFeatureDispatchers.end() )
            {
                aDispatcherPos = m_aFeatureDispatchers.emplace(
                    nFormFeature, new svx::OSingleFeatureDispatcher( aURL, nFormFeature, m_xFormOperations, m_aMutex )
                ).first;
            }
 
            OSL_ENSURE( aDispatcherPos->second.is(), "FormController::interceptedQueryDispatch: should have a dispatcher by now!" );
            return aDispatcherPos->second;
        }
    }
 
    // no more to offer
    return xReturn;
}
 
 
void SAL_CALL FormController::dispatch( const URL& _rURL, const Sequence< PropertyValue >& _rArgs )
{
    if ( _rArgs.getLength() != 1 )
    {
        OSL_FAIL( "FormController::dispatch: no arguments -> no dispatch!" );
        return;
    }
 
    if ( _rURL.Complete == "private:/InteractionHandler" )
    {
        Reference< XInteractionRequest > xRequest;
        OSL_VERIFY( _rArgs[0].Value >>= xRequest );
        if ( xRequest.is() )
            handle( xRequest );
        return;
    }
 
    if  ( _rURL.Complete == FMURL_CONFIRM_DELETION )
    {
        OSL_FAIL( "FormController::dispatch: How do you expect me to return something via this call?" );
            // confirmDelete has a return value - dispatch hasn't
        return;
    }
 
    OSL_FAIL( "FormController::dispatch: unknown URL!" );
}
 
 
void SAL_CALL FormController::addStatusListener( const Reference< XStatusListener >& _rxListener, const URL& _rURL )
{
    if (_rURL.Complete == FMURL_CONFIRM_DELETION)
    {
        if (_rxListener.is())
        {   // send an initial statusChanged event
            FeatureStateEvent aEvent;
            aEvent.FeatureURL = _rURL;
            aEvent.IsEnabled = true;
            _rxListener->statusChanged(aEvent);
            // and don't add the listener at all (the status will never change)
        }
    }
    else
        OSL_FAIL("FormController::addStatusListener: invalid (unsupported) URL!");
}
 
 
Reference< XInterface > SAL_CALL FormController::getParent()
{
    return m_xParent;
}
 
 
void SAL_CALL FormController::setParent( const Reference< XInterface >& Parent)
{
    m_xParent = Parent;
}
 
 
void SAL_CALL FormController::removeStatusListener( const Reference< XStatusListener >& /*_rxListener*/, const URL& _rURL )
{
    OSL_ENSURE(_rURL.Complete == FMURL_CONFIRM_DELETION, "FormController::removeStatusListener: invalid (unsupported) URL!");
    // we never really added the listener, so we don't need to remove it
}
 
 
Reference< XDispatchProviderInterceptor >  FormController::createInterceptor(const Reference< XDispatchProviderInterception > & _xInterception)
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
#ifdef DBG_UTIL
    // check if we already have an interceptor for the given object
    for ( const auto & it : m_aControlDispatchInterceptors )
    {
        if (it->getIntercepted() == _xInterception)
            OSL_FAIL("FormController::createInterceptor : we already do intercept this objects dispatches !");
    }
#endif
 
    rtl::Reference<DispatchInterceptionMultiplexer> pInterceptor(new DispatchInterceptionMultiplexer( _xInterception, this ));
    m_aControlDispatchInterceptors.push_back( pInterceptor );
 
    return pInterceptor;
}
 
 
bool FormController::ensureInteractionHandler()
{
    if ( m_xInteractionHandler.is() )
        return true;
    if ( m_bAttemptedHandlerCreation )
        return false;
    m_bAttemptedHandlerCreation = true;
 
    m_xInteractionHandler = InteractionHandler::createWithParent(m_xComponentContext,
                                                                 getDialogParentWindow(this));
    return m_xInteractionHandler.is();
}
 
 
void SAL_CALL FormController::handle( const Reference< XInteractionRequest >& _rRequest )
{
    if ( !ensureInteractionHandler() )
        return;
    m_xInteractionHandler->handle( _rRequest );
}
 
 
void FormController::deleteInterceptor(const Reference< XDispatchProviderInterception > & _xInterception)
{
    OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" );
    // search the interceptor responsible for the given object
    auto aIter = std::find_if(m_aControlDispatchInterceptors.begin(), m_aControlDispatchInterceptors.end(),
        [&_xInterception](const rtl::Reference<DispatchInterceptionMultiplexer>& rpInterceptor) {
            return rpInterceptor->getIntercepted() == _xInterception;
        });
    if (aIter != m_aControlDispatchInterceptors.end())
    {
        // log off the interception from its interception object
        (*aIter)->dispose();
        // remove the interceptor from our array
        m_aControlDispatchInterceptors.erase(aIter);
    }
}
 
 
void FormController::implInvalidateCurrentControlDependentFeatures()
{
    Sequence< sal_Int16 > aCurrentControlDependentFeatures
    {
        FormFeature::SortAscending,
        FormFeature::SortDescending,
        FormFeature::AutoFilter,
        FormFeature::RefreshCurrentControl
    };
 
    invalidateFeatures( aCurrentControlDependentFeatures );
}
 
 
void SAL_CALL FormController::columnChanged( const EventObject& /*_event*/ )
{
    implInvalidateCurrentControlDependentFeatures();
}
 
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V728 An excessive check can be simplified. The '||' operator is surrounded by opposite expressions '!bLocked' and 'bLocked'.

V1044 Loop break conditions do not depend on the number of iterations.

V1044 Loop break conditions do not depend on the number of iterations.