diff options
Diffstat (limited to 'svx/source/form/formcontroller.cxx')
-rw-r--r-- | svx/source/form/formcontroller.cxx | 4194 |
1 files changed, 4194 insertions, 0 deletions
diff --git a/svx/source/form/formcontroller.cxx b/svx/source/form/formcontroller.cxx new file mode 100644 index 000000000..be6e956cc --- /dev/null +++ b/svx/source/form/formcontroller.cxx @@ -0,0 +1,4194 @@ +/* -*- 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 <tools/diagnose_ex.h> +#include <unotools/localedatawrapper.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/window.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() + :xColumn() + ,nNullable( ColumnValue::NULLABLE_UNKNOWN ) + ,bAutoIncrement( false ) + ,bReadOnly( false ) + ,sName() + ,xFirstControlWithInputRequired() + ,xFirstGridWithInputRequiredColumn() + ,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_aColumns() + ,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 = 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() :UnoControl() + { + } + + virtual OUString GetComponentServiceName() override {return "Edit";} + 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_xFormOperations() + ,m_aMode( OUString( "DataMode" ) ) + ,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() throw () +{ + FormController_BASE::acquire(); +} + + +void SAL_CALL FormController::release() throw () +{ + 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 "org.openoffice.comp.svx.FormController"; +} + +Sequence< OUString> SAL_CALL FormController::getSupportedServiceNames() +{ + // service names which are supported only, but cannot be used to created an + // instance at a service factory + Sequence<OUString> aNonCreatableServiceNames { "com.sun.star.form.FormControllerDispatcher" }; + + // 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 + { + "com.sun.star.form.runtime.FormController", + "com.sun.star.awt.control.TabController" + }; + 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, sCriteria; + 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 ) + { + // 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( "( " ); + aFilter.append( aRowFilter.makeStringAndClear() ); + aFilter.append( " )" ); + } + } + } + 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() ); + sal_Int32 termIndex = 0; + for (const FmFilterRow& rRow : m_aFilterRows) + { + Sequence< OUString > aConjunction( m_aFilterComponents.size() ); + sal_Int32 componentIndex = 0; + for (const auto& rComp : m_aFilterComponents) + { + FmFilterRow::const_iterator predicate = rRow.find( rComp ); + if ( predicate != rRow.end() ) + aConjunction[ componentIndex ] = predicate->second; + ++componentIndex; + } + + aExpressions[ termIndex ] = 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 || + Index >= static_cast<sal_Int32>(m_aChildren.size())) + throw IndexOutOfBoundsException(); + + return makeAny( 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.get() ); + } + 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 + 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, makeAny( _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(static_cast<cppu::OWeakObject*>(this)); + 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); + 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.get(), 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()) + { + // 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 ), makeAny(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() ) + { + // 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 : std::as_const(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 = 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 + { + // there is a data source + Reference< XPropertySet > xSet(xControl->getModel(), UNO_QUERY); + if (xSet.is() && ::comphelper::hasProperty(FM_PROP_BOUNDFIELD, xSet)) + { + // 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 (::comphelper::hasProperty(FM_PROP_READONLY, xSet)) + bTouch = !::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_READONLY)); + + if (bTouch) + { + Reference< XPropertySet > xField; + xSet->getPropertyValue(FM_PROP_BOUNDFIELD) >>= xField; + if (xField.is()) + { + 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 : std::as_const(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 : std::as_const(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 : std::as_const(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 ?!" ); + + Reference< XControl >* pControls = std::find_if(_rControls.begin(), _rControls.end(), + [&xCtrlModel](const Reference< XControl >& rControl) { + return rControl.is() && rControl->getModel().get() == xCtrlModel.get(); }); + if (pControls != _rControls.end()) + { + Reference< XControl > xControl( *pControls ); + if ( _bRemove ) + { + auto i = static_cast<sal_Int32>(std::distance(_rControls.begin(), pControls)); + ::comphelper::removeElementAt( _rControls, i ); + } + else if ( _bOverWrite ) + pControls->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() ) + { + 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(m_aControls.begin(), m_aControls.end(), + [&xControl](const Reference< XControl >& rControl) { return xControl.get() == rControl.get(); }); + if (pControl != m_aControls.end()) + { + auto nIndex = static_cast<sal_Int32>(std::distance(m_aControls.begin(), 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 : std::as_const(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 ), makeAny( 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("com.sun.star.sdb.SingleSelectQueryComposer"), + 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("RealName") >>= 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("RealName") >>= 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); + OUString aCompText = aRow[rFieldInfo.xText] + " " + + OUString(aVal.getStr(),aVal.getLength(),RTL_TEXTENCODING_ASCII_US) + " " + + ::comphelper::getString(rRefValue.Value); + aRow[rFieldInfo.xText] = aCompText; + } + 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( "FilterMode" ); + + // 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, + VCLUnoHelper::GetInterface( getDialogParentWindow() ), + 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( "DataMode" ); + + // 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 + { + "DataMode", + "FilterMode" + }; + 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; +} + + +vcl::Window* FormController::getDialogParentWindow() +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + vcl::Window* pParentWindow = nullptr; + try + { + Reference< XControl > xContainerControl( getContainer(), UNO_QUERY_THROW ); + Reference< XWindowPeer > xContainerPeer( xContainerControl->getPeer(), UNO_SET_THROW ); + pParentWindow = VCLUnoHelper::GetWindow( xContainerPeer ).get(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + return pParentWindow; +} + +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, vcl::Window* _pDialogParent ) + { + SQLContext aError; + aError.Message = SvxResId(RID_STR_WRITEERROR); + aError.Details = _rMessage; + displayException( aError, _pDialogParent ); + + 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 const char s_sFormsCheckRequiredFields[] = "FormsCheckRequiredFields"; + + // 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("Settings"), + 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::OInterfaceIteratorHelper2 aIter(m_aRowSetApproveListeners); + bool bValid = true; + if (aIter.hasMoreElements()) + { + RowChangeEvent aEvt( _rEvent ); + aEvt.Source = *this; + bValid = static_cast<XRowSetApproveListener*>(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() ); + 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->wasNull() || !rColInfo.xColumn->getString().isEmpty() ) + 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() ); + 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::OInterfaceIteratorHelper2 aIter(m_aRowSetApproveListeners); + if (aIter.hasMoreElements()) + { + EventObject aEvt(event); + aEvt.Source = *this; + return static_cast<XRowSetApproveListener*>(aIter.next())->approveCursorMove(aEvt); + } + + return true; +} + + +sal_Bool SAL_CALL FormController::approveRowSetChange(const EventObject& event) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + ::comphelper::OInterfaceIteratorHelper2 aIter(m_aRowSetApproveListeners); + if (aIter.hasMoreElements()) + { + EventObject aEvt(event); + aEvt.Source = *this; + return static_cast<XRowSetApproveListener*>(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::OInterfaceIteratorHelper2 aIter(m_aErrorListeners); + if (aIter.hasMoreElements()) + { + SQLErrorEvent aEvt(aEvent); + aEvt.Source = *this; + static_cast<XSQLErrorListener*>(aIter.next())->errorOccured(aEvt); + } + else + { + aGuard.clear(); + displayException(aEvent, getDialogParentWindow()); + } +} + +// 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::OInterfaceIteratorHelper2 aIter(m_aParameterListeners); + if (aIter.hasMoreElements()) + { + DatabaseParameterEvent aEvt(aEvent); + aEvt.Source = *this; + return static_cast<XDatabaseParameterListener*>(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 + OParameterContinuation* pParamValues = new OParameterContinuation; + OInteractionAbort* pAbort = new OInteractionAbort; + // the request + ParametersRequest aRequest; + aRequest.Parameters = aEvent.Parameters; + aRequest.Connection = getConnection(Reference< XRowSet >(aEvent.Source, UNO_QUERY)); + OInteractionRequest* pParamRequest = new OInteractionRequest(makeAny(aRequest)); + Reference< XInteractionRequest > xParamRequest(pParamRequest); + // some knittings + pParamRequest->addContinuation(pParamValues); + pParamRequest->addContinuation(pAbort); + + // handle the request + m_xInteractionHandler->handle(xParamRequest); + + 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::OInterfaceIteratorHelper2 aIter(m_aDeleteListeners); + if (aIter.hasMoreElements()) + { + RowChangeEvent aEvt(aEvent); + aEvt.Source = *this; + return static_cast<XConfirmDeleteListener*>(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 + OInteractionApprove* pApprove = new OInteractionApprove; + OInteractionDisapprove* pDisapprove = new OInteractionDisapprove; + + // the request + SQLWarning aWarning; + aWarning.Message = sTitle; + SQLWarning aDetails; + aDetails.Message = SvxResId(RID_STR_DELETECONFIRM); + aWarning.NextException <<= aDetails; + + OInteractionRequest* pRequest = new OInteractionRequest( makeAny( aWarning ) ); + Reference< XInteractionRequest > xRequest( pRequest ); + + // some knittings + pRequest->addContinuation( pApprove ); + pRequest->addContinuation( pDisapprove ); + + // handle the request + m_xInteractionHandler->handle( xRequest ); + + 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... + ::std::copy( Features.begin(), Features.end(), + ::std::insert_iterator< ::std::set< sal_Int16 > >( m_aInvalidFeatures, m_aInvalidFeatures.begin() ) + ); + + // ... 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.get(); +} + + +bool FormController::ensureInteractionHandler() +{ + if ( m_xInteractionHandler.is() ) + return true; + if ( m_bAttemptedHandlerCreation ) + return false; + m_bAttemptedHandlerCreation = true; + + m_xInteractionHandler = InteractionHandler::createWithParent(m_xComponentContext, + VCLUnoHelper::GetInterface(getDialogParentWindow())); + 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(4); + + aCurrentControlDependentFeatures[0] = FormFeature::SortAscending; + aCurrentControlDependentFeatures[1] = FormFeature::SortDescending; + aCurrentControlDependentFeatures[2] = FormFeature::AutoFilter; + aCurrentControlDependentFeatures[3] = FormFeature::RefreshCurrentControl; + + invalidateFeatures( aCurrentControlDependentFeatures ); +} + + +void SAL_CALL FormController::columnChanged( const EventObject& /*_event*/ ) +{ + implInvalidateCurrentControlDependentFeatures(); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |