/* -*- 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 <componenttools.hxx> #include <FormComponent.hxx> #include <strings.hrc> #include <frm_resource.hxx> #include <property.hxx> #include <services.hxx>
// base class for form layer controls
OControl::OControl( const Reference< XComponentContext >& _rxContext, const OUString& _rAggregateService, constbool _bSetDelegator )
:OComponentHelper(m_aMutex)
,m_xContext( _rxContext )
{ // Aggregate VCL Control // Increment the RefCount for aggregates, because the aggregate by itself increments the RefCount in the setDelegator
osl_atomic_increment( &m_refCount );
{
m_xAggregate.set(_rxContext->getServiceManager()->createInstanceWithContext(_rAggregateService, _rxContext), css::uno::UNO_QUERY);
m_xControl.set(m_xAggregate, css::uno::UNO_QUERY);
}
osl_atomic_decrement( &m_refCount );
void OControl::doSetDelegator()
{
osl_atomic_increment( &m_refCount ); if ( m_xAggregate.is() )
{ // those brackets are important for some compilers, don't remove! // (they ensure that the temporary object created in the line below // is destroyed *before* the refcount-decrement)
m_xAggregate->setDelegator( static_cast< XWeak* >( this ) );
}
osl_atomic_decrement( &m_refCount );
}
// UNO Binding
Any SAL_CALL OControl::queryAggregation( const Type& _rType )
{ // ask the base class
Any aReturn( OComponentHelper::queryAggregation(_rType) ); // ask our own interfaces if (!aReturn.hasValue())
{
aReturn = OControl_BASE::queryInterface(_rType); // ask our aggregate if (!aReturn.hasValue() && m_xAggregate.is())
aReturn = m_xAggregate->queryAggregation(_rType);
}
// does the disposing come from the aggregate? if (xAggAsIface != Reference< XInterface >(_rEvent.Source, UNO_QUERY))
{ // no -> forward it if (auto xListener = query_aggregation<css::lang::XEventListener>(m_xAggregate))
xListener->disposing(_rEvent);
}
}
Any SAL_CALL OBoundControl::queryAggregation(const Type& _rType)
{
Any aReturn;
// XTypeProvider first - don't ask the OBoundControl_BASE, it would deliver incomplete types if ( _rType.equals( cppu::UnoType<XTypeProvider>::get() ) )
aReturn = OControl::queryAggregation( _rType );
// ask our own interfaces // (do this first (except XTypeProvider ) - we want to "overwrite" XPropertiesChangeListener) if ( !aReturn.hasValue() )
aReturn = OBoundControl_BASE::queryInterface( _rType );
// ask the base class if ( !aReturn.hasValue() )
aReturn = OControl::queryAggregation( _rType );
Any SAL_CALL OControlModel::queryAggregation(const Type& _rType)
{ // base class 1
Any aReturn(OComponentHelper::queryAggregation(_rType));
// base class 2 if (!aReturn.hasValue())
{
aReturn = OControlModel_BASE::queryInterface(_rType);
// our own interfaces if (!aReturn.hasValue())
{
aReturn = OPropertySetAggregationHelper::queryInterface(_rType); // our aggregate if (!aReturn.hasValue() && m_xAggregate.is() && !_rType.equals(cppu::UnoType<XCloneable>::get()))
aReturn = m_xAggregate->queryAggregation(_rType);
}
} return aReturn;
}
void OControlModel::readHelpTextCompatibly(const css::uno::Reference< css::io::XObjectInputStream >& _rxInStream)
{
OUString sHelpText;
::comphelper::operator>>( _rxInStream, sHelpText); try
{ if (m_xAggregateSet.is())
m_xAggregateSet->setPropertyValue(PROPERTY_HELPTEXT, Any(sHelpText));
} catch(const Exception&)
{
DBG_UNHANDLED_EXCEPTION("forms.component");
SAL_WARN("forms.component", "OControlModel::readHelpTextCompatibly: could not forward the property value to the aggregate!");
}
}
void OControlModel::writeHelpTextCompatibly(const css::uno::Reference< css::io::XObjectOutputStream >& _rxOutStream)
{
OUString sHelpText; try
{ if (m_xAggregateSet.is())
m_xAggregateSet->getPropertyValue(PROPERTY_HELPTEXT) >>= sHelpText;
} catch(const Exception&)
{
DBG_UNHANDLED_EXCEPTION("forms.component");
SAL_WARN("forms.component", "OControlModel::writeHelpTextCompatibly: could not retrieve the property value from the aggregate!");
}
::comphelper::operator<<( _rxOutStream, sHelpText);
}
OControlModel::OControlModel( const Reference<XComponentContext>& _rxContext, const OUString& _rUnoControlModelTypeName, const OUString& rDefault, constbool _bSetDelegator)
:OComponentHelper(m_aMutex)
,OPropertySetAggregationHelper(OComponentHelper::rBHelper)
,m_xContext( _rxContext )
,m_lockCount( 0 )
,m_aPropertyBagHelper( *this )
,m_nTabIndex(FRM_DEFAULT_TABINDEX)
,m_nClassId(FormComponentType::CONTROL)
,m_bNativeLook( false )
,m_bStandardTheme( false )
,m_bGenerateVbEvents( false )
,m_nControlTypeinMSO(0) // 0 : default value is create from AOO
,m_nObjIDinMSO(INVALID_OBJ_ID_IN_MSO) // form controls are usually embedded into documents, not dialogs, and in documents // the native look is ugly... // #i37342#
{ if (_rUnoControlModelTypeName.isEmpty()) // the is a model we have to aggregate return;
// temporarily increment refcount because of temporary references to ourself in the following
osl_atomic_increment( &m_refCount );
{ // transfer the (only, at the very moment!) ref count
m_xAggregate = createAggregateClone( _pOriginal );
// set aggregation (retrieve other direct interfaces of the aggregate)
setAggregation( m_xAggregate );
}
// set the delegator, if allowed by our derived class if ( _bSetDelegator )
doSetDelegator();
// 2. writing a version number
_rxOutStream->writeShort(0x0003);
// 3. writing the general properties
::comphelper::operator<<( _rxOutStream, m_aName);
_rxOutStream->writeShort(m_nTabIndex);
::comphelper::operator<<( _rxOutStream, m_aTag); // 3rd version
// IMPORTANT NOTE! // don't write any new members here: this wouldn't be compatible with older versions, as OControlModel // is a base class which is called in derived classes "read" method. So if you increment the version // and write new stuff, older office versions will read this in the _derived_ classes, which may result // in anything from data loss to crash. // EOIN!
}
// 2. reading the version number
sal_uInt16 nVersion = InStream->readShort();
// 3. reading the general properties
::comphelper::operator>>( InStream, m_aName);
m_nTabIndex = InStream->readShort();
if (nVersion > 0x0002)
::comphelper::operator>>( InStream, m_aTag);
// we had a version where we wrote the help text if (nVersion == 0x0004)
readHelpTextCompatibly(InStream);
DBG_ASSERT(nVersion < 5, "OControlModel::read : suspicious version number !"); // 4 was the version where we wrote the help text // later versions shouldn't exist (see write for a detailed comment)
}
PropertyState OControlModel::getPropertyStateByHandle( sal_Int32 _nHandle )
{ // simply compare the current and the default value
Any aCurrentValue = getPropertyDefaultByHandle( _nHandle );
Any aDefaultValue; getFastPropertyValue( aDefaultValue, _nHandle );
OBoundControlModel::OBoundControlModel( const OBoundControlModel* _pOriginal, const Reference< XComponentContext>& _rxFactory )
:OControlModel( _pOriginal, _rxFactory, true, false )
,OPropertyChangeListener()
,m_nValuePropertyAggregateHandle( _pOriginal->m_nValuePropertyAggregateHandle )
,m_nFieldType( DataType::OTHER )
,m_bValuePropertyMayBeVoid( _pOriginal->m_bValuePropertyMayBeVoid )
,m_aResetHelper( *this, m_aMutex )
,m_aUpdateListeners( m_aMutex )
,m_aFormComponentListeners( m_aMutex )
,m_xValidator( _pOriginal->m_xValidator )
,m_bInputRequired( false )
,m_bFormListening( false )
,m_bLoaded( false )
,m_bRequired( false )
,m_bCommitable( _pOriginal->m_bCommitable )
,m_bSupportsExternalBinding( _pOriginal->m_bSupportsExternalBinding )
,m_bSupportsValidation( _pOriginal->m_bSupportsValidation )
,m_bForwardValueChanges( true )
,m_bTransferringValue( false )
,m_bIsCurrentValueValid( _pOriginal->m_bIsCurrentValueValid )
,m_bBindingControlsRO( false )
,m_bBindingControlsEnable( false )
,m_eControlValueChangeInstigator( eOther )
{ // start property listening at the aggregate
implInitAggMultiplexer( );
m_aLabelServiceName = _pOriginal->m_aLabelServiceName;
m_sValuePropertyName = _pOriginal->m_sValuePropertyName;
m_nValuePropertyAggregateHandle = _pOriginal->m_nValuePropertyAggregateHandle;
m_bValuePropertyMayBeVoid = _pOriginal->m_bValuePropertyMayBeVoid;
m_aValuePropertyType = _pOriginal->m_aValuePropertyType;
m_aControlSource = _pOriginal->m_aControlSource;
m_bInputRequired = _pOriginal->m_bInputRequired; // m_xLabelControl, though being a property, is not to be cloned, not even the reference will be transferred. // (the former should be clear - a clone of the object we're only referencing does not make sense) // (the second would violate the restriction for label controls that they're part of the // same form component hierarchy - we ourself are no part, yet, so we can't have a label control) // start listening for changes at the value property
implInitValuePropertyListening( );
}
OBoundControlModel::~OBoundControlModel()
{ if ( !OComponentHelper::rBHelper.bDisposed )
{
acquire();
dispose();
}
doResetDelegator( );
OSL_ENSURE( m_pAggPropMultiplexer, "OBoundControlModel::~OBoundControlModel: what about my property multiplexer?" ); if ( m_pAggPropMultiplexer )
{
m_pAggPropMultiplexer->dispose();
m_pAggPropMultiplexer = nullptr;
}
}
void OBoundControlModel::clonedFrom( const OControlModel* _pOriginal )
{ const OBoundControlModel* pBoundOriginal = static_cast< const OBoundControlModel* >( _pOriginal ); // the value binding can be handled as if somebody called setValueBinding here // By definition, bindings can be share between bindables if ( !(pBoundOriginal && pBoundOriginal->m_xExternalBinding.is()) ) return;
void OBoundControlModel::implInitValuePropertyListening( ) const
{ // start listening for changes at the value property // There are three pre-requisites for this to be done: // 1. We support external value bindings. In this case, the changes in the control value need to // be propagated to the external binding immediately when they happen // 2. We support external validation. In this case, we need to listen for changes in the value // property, since we need to revalidate then. // 3. We are not committable. In this case, changes in the control value need to be propagated // to the database column immediately when they happen. if ( m_bSupportsExternalBinding || m_bSupportsValidation || !m_bCommitable )
{
OSL_ENSURE( m_pAggPropMultiplexer, "OBoundControlModel::implInitValuePropertyListening: no multiplexer!" ); if ( m_pAggPropMultiplexer && !m_sValuePropertyName.isEmpty() )
m_pAggPropMultiplexer->addProperty( m_sValuePropertyName );
}
}
// start listening for changes at the value property
implInitValuePropertyListening( );
}
void OBoundControlModel::suspendValueListening( )
{
OSL_PRECOND( !m_sValuePropertyName.isEmpty(), "OBoundControlModel::suspendValueListening: don't have a value property!" );
OSL_PRECOND( m_pAggPropMultiplexer, "OBoundControlModel::suspendValueListening: I *am* not listening!" );
if ( m_pAggPropMultiplexer )
m_pAggPropMultiplexer->lock();
}
void OBoundControlModel::resumeValueListening( )
{
OSL_PRECOND( !m_sValuePropertyName.isEmpty(), "OBoundControlModel::resumeValueListening: don't have a value property!" );
OSL_PRECOND( m_pAggPropMultiplexer, "OBoundControlModel::resumeValueListening: I *am* not listening at all!" );
OSL_PRECOND( !m_pAggPropMultiplexer || m_pAggPropMultiplexer->locked(), "OBoundControlModel::resumeValueListening: listening not suspended currently!" ); if ( m_pAggPropMultiplexer )
m_pAggPropMultiplexer->unlock();
}
Sequence< Type > OBoundControlModel::_getTypes()
{
TypeBag aTypes(
OControlModel::_getTypes(),
OBoundControlModel_BASE1::getTypes()
);
if ( m_bCommitable )
aTypes.addTypes( OBoundControlModel_COMMITTING::getTypes() );
if ( m_bSupportsExternalBinding )
aTypes.addTypes( OBoundControlModel_BINDING::getTypes() );
if ( m_bSupportsValidation )
aTypes.addTypes( OBoundControlModel_VALIDATION::getTypes() ); return aTypes.getTypes();
}
if ( m_pAggPropMultiplexer )
m_pAggPropMultiplexer->dispose();
// notify all our listeners
css::lang::EventObject aEvt( static_cast< XWeak* >( this ) );
m_aUpdateListeners.disposeAndClear( aEvt );
m_aResetHelper.disposing();
// disconnect from our database column // TODO: could we replace the following 5 lines with a call to impl_disconnectDatabaseColumn_noNotify? // The only more thing which it does is calling onDisconnectedDbColumn - could this // cause trouble? At least when we continue to call OControlModel::disposing before, it *may*. if ( hasField() )
{
getField()->removePropertyChangeListener( PROPERTY_VALUE, this );
resetField();
}
m_xCursor = nullptr;
Reference< XComponent > xComp( m_xLabelControl, UNO_QUERY ); if ( xComp.is() )
xComp->removeEventListener(static_cast< XEventListener* >( static_cast< XPropertyChangeListener* >( this ) ) ); // disconnect from our external value binding if ( hasExternalValueBinding() )
disconnectExternalValueBinding(); // ditto for the validator if ( hasValidator() )
disconnectValidator( );
}
void OBoundControlModel::onValuePropertyChange( ControlModelLock& i_rControLock )
{ if ( hasExternalValueBinding() )
{ // the control value changed, while we have an external value binding // -> forward the value to it if ( m_eControlValueChangeInstigator != eExternalBinding )
transferControlValueToExternal( i_rControLock );
}
elseif ( !m_bCommitable && m_xColumnUpdate.is() )
{ // the control value changed, while we are bound to a database column, // but not committable (which means changes in the control have to be reflected to // the underlying database column immediately) // -> forward the value to the database column if ( m_eControlValueChangeInstigator != eDbColumnBinding )
commitControlValueToDbColumn( false );
}
// validate the new value if ( m_bSupportsValidation )
recheckValidity( true );
}
void OBoundControlModel::_propertyChanged( const PropertyChangeEvent& _rEvt )
{
ControlModelLock aLock( *this );
OSL_ENSURE( _rEvt.PropertyName == m_sValuePropertyName, "OBoundControlModel::_propertyChanged: where did this come from (1)?" );
OSL_ENSURE( m_pAggPropMultiplexer && !m_pAggPropMultiplexer->locked(), "OBoundControlModel::_propertyChanged: where did this come from (2)?" ); if ( _rEvt.PropertyName == m_sValuePropertyName )
{
onValuePropertyChange( aLock );
}
}
void OBoundControlModel::doFormListening( constbool _bStart )
{
OSL_PRECOND( !hasExternalValueBinding(), "OBoundControlModel::doFormListening: external value binding should overrule the database binding!" ); if ( isFormListening() == _bStart ) return; if ( m_xAmbientForm.is() )
_bStart ? m_xAmbientForm->addLoadListener( this ) : m_xAmbientForm->removeLoadListener( this );
Reference< XLoadable > xParentLoadable( getParent(), UNO_QUERY ); if ( getParent().is() && !xParentLoadable.is() )
{ // if our parent does not directly support the XLoadable interface, then it might support the // XRowSetSupplier/XRowSetChangeBroadcaster interfaces. In this case we have to listen for changes // broadcasted by the latter.
Reference< XRowSetChangeBroadcaster > xRowSetBroadcaster( getParent(), UNO_QUERY ); if ( xRowSetBroadcaster.is() )
_bStart ? xRowSetBroadcaster->addRowSetChangeListener( this ) : xRowSetBroadcaster->removeRowSetChangeListener( this );
}
// XChild void SAL_CALL OBoundControlModel::setParent(const Reference<XInterface>& _rxParent)
{
ControlModelLock aLock( *this );
FieldChangeNotifier aBoundFieldNotifier( aLock ); if ( getParent() == _rxParent ) return; // disconnect from database column (which is controlled by parent, directly or indirectly) if ( hasField() )
impl_disconnectDatabaseColumn_noNotify(); // log off old listeners if ( isFormListening() )
doFormListening( false ); // actually set the new parent
OControlModel::setParent( _rxParent ); // a new parent means a new ambient form
impl_determineAmbientForm_nothrow(); if ( !hasExternalValueBinding() )
{ // log on new listeners
doFormListening( true ); // re-connect to database column of the new parent if ( m_xAmbientForm.is() && m_xAmbientForm->isLoaded() )
impl_connectDatabaseColumn_noNotify( false );
}
}
elseif ( _rEvent.Source == m_xExternalBinding )
{ // *first* check for the external binding
disconnectExternalValueBinding( );
}
elseif ( _rEvent.Source == m_xValidator )
{ // *then* check for the validator. Reason is that bindings may also act as validator at the same // time, in this case, the validator is automatically revoked when the binding is revoked
disconnectValidator( );
}
// XPersist void SAL_CALL OBoundControlModel::write( const Reference<css::io::XObjectOutputStream>& _rxOutStream )
{
OControlModel::write(_rxOutStream);
osl::MutexGuard aGuard(m_aMutex); // Version
_rxOutStream->writeShort(0x0002); // Controlsource
::comphelper::operator<<( _rxOutStream, m_aControlSource); // !!! IMPORTANT NOTE !!! // don't write any new members here: this wouldn't be compatible with older versions, as OBoundControlModel // is a base class which is called in derived classes "read" method. So if you increment the version // and write new stuff, older office versions will read this in the _derived_ classes, which may result // in anything from data loss to crash. // (use writeCommonProperties instead, this is called in derived classes write-method) // !!! EOIN !!!
}
void OBoundControlModel::readCommonProperties(const Reference<css::io::XObjectInputStream>& _rxInStream)
{
sal_Int32 nLen = _rxInStream->readLong();
Reference<css::io::XMarkableStream> xMark(_rxInStream, UNO_QUERY);
DBG_ASSERT(xMark.is(), "OBoundControlModel::readCommonProperties : can only work with markable streams !");
sal_Int32 nMark = xMark->createMark(); // read the reference to the label control
Reference<css::io::XPersistObject> xPersist;
sal_Int32 nUsedFlag;
nUsedFlag = _rxInStream->readLong(); if (nUsedFlag)
xPersist = _rxInStream->readObject();
m_xLabelControl.set(xPersist, css::uno::UNO_QUERY);
Reference< XComponent > xComp( m_xLabelControl, UNO_QUERY ); if (xComp.is())
xComp->addEventListener(static_cast<css::lang::XEventListener*>(static_cast<XPropertyChangeListener*>(this))); // read any other new common properties here // skip the remaining bytes
xMark->jumpToMark(nMark);
_rxInStream->skipBytes(nLen);
xMark->deleteMark(nMark);
}
void OBoundControlModel::writeCommonProperties(const Reference<css::io::XObjectOutputStream>& _rxOutStream)
{
Reference<css::io::XMarkableStream> xMark(_rxOutStream, UNO_QUERY);
DBG_ASSERT(xMark.is(), "OBoundControlModel::writeCommonProperties : can only work with markable streams !");
sal_Int32 nMark = xMark->createMark(); // a placeholder where we will write the overall length (later in this method)
sal_Int32 nLen = 0;
_rxOutStream->writeLong(nLen); // write the reference to the label control
Reference<css::io::XPersistObject> xPersist(m_xLabelControl, UNO_QUERY);
sal_Int32 nUsedFlag = 0; if (xPersist.is())
nUsedFlag = 1;
_rxOutStream->writeLong(nUsedFlag); if (xPersist.is())
_rxOutStream->writeObject(xPersist); // write any other new common properties here // write the correct length at the beginning of the block
nLen = xMark->offsetToMark(nMark) - sizeof(nLen);
xMark->jumpToMark(nMark);
_rxOutStream->writeLong(nLen);
xMark->jumpToFurthest();
xMark->deleteMark(nMark);
}
void OBoundControlModel::getFastPropertyValue(Any& rValue, sal_Int32 nHandle) const
{ switch (nHandle)
{ case PROPERTY_ID_INPUT_REQUIRED:
rValue <<= m_bInputRequired; break; case PROPERTY_ID_CONTROLSOURCEPROPERTY:
rValue <<= m_sValuePropertyName; break; case PROPERTY_ID_CONTROLSOURCE:
rValue <<= m_aControlSource; break; case PROPERTY_ID_BOUNDFIELD:
rValue <<= getField(); break; case PROPERTY_ID_CONTROLLABEL: if (!m_xLabelControl.is())
rValue.clear(); else
rValue <<= m_xLabelControl; break; default:
OControlModel::getFastPropertyValue(rValue, nHandle);
}
}
sal_Bool OBoundControlModel::convertFastPropertyValue(
Any& _rConvertedValue, Any& _rOldValue,
sal_Int32 _nHandle, const Any& _rValue)
{ bool bModified(false); switch (_nHandle)
{ case PROPERTY_ID_INPUT_REQUIRED:
bModified = tryPropertyValue( _rConvertedValue, _rOldValue, _rValue, m_bInputRequired ); break; case PROPERTY_ID_CONTROLSOURCE:
bModified = tryPropertyValue(_rConvertedValue, _rOldValue, _rValue, m_aControlSource); break; case PROPERTY_ID_BOUNDFIELD:
SAL_WARN("forms.component", "OBoundControlModel::convertFastPropertyValue: BoundField should be a read-only property !" ); throw css::lang::IllegalArgumentException(); case PROPERTY_ID_CONTROLLABEL: if (!_rValue.hasValue())
{ // property set to void
_rConvertedValue = Any();
getFastPropertyValue(_rOldValue, _nHandle);
bModified = m_xLabelControl.is();
}
else
{
bModified = tryPropertyValue(_rConvertedValue, _rOldValue, _rValue, m_xLabelControl); if (!m_xLabelControl.is()) // an empty interface is interpreted as VOID
_rOldValue.clear();
}
// Check if we and the given model have a common ancestor (up to the forms collection)
Reference<XChild> xCont(this);
Reference< XInterface > xMyTopLevel = xCont->getParent(); while (xMyTopLevel.is())
{
Reference<XForm> xAsForm(xMyTopLevel, UNO_QUERY); if (!xAsForm.is()) // found my root break;
Reference<XChild> xLoopAsChild(xMyTopLevel, UNO_QUERY);
xMyTopLevel = xLoopAsChild.is() ? xLoopAsChild->getParent() : Reference< XInterface >();
}
if (xNewTopLevel != xMyTopLevel)
{ // the both objects don't belong to the same forms collection -> not acceptable throw css::lang::IllegalArgumentException();
}
m_xLabelControl = std::move(xAsPropSet);
Reference<css::lang::XComponent> xComp(m_xLabelControl, UNO_QUERY); if (xComp.is())
xComp->addEventListener(static_cast<css::lang::XEventListener*>(static_cast<XPropertyChangeListener*>(this)));
}
// XPropertyChangeListener void SAL_CALL OBoundControlModel::propertyChange( const PropertyChangeEvent& evt )
{ // if the DBColumn value changed, transfer it to the control if ( evt.PropertyName == PROPERTY_VALUE )
{
OSL_ENSURE( evt.Source == getField(), "OBoundControlModel::propertyChange: value changes from components other than our database column?" );
osl::MutexGuard aGuard(m_aMutex); if ( m_bForwardValueChanges && m_xColumn.is() )
transferDbValueToControl();
}
else
{
OSL_ENSURE( evt.Source == m_xExternalBinding, "OBoundControlModel::propertyChange: where did this come from?" ); // our binding has properties which can control properties of ourself
OUString sBindingControlledProperty; bool bForwardToLabelControl = false; if ( evt.PropertyName == PROPERTY_READONLY )
{
sBindingControlledProperty = PROPERTY_READONLY;
}
catch( const Exception& )
{
DBG_UNHANDLED_EXCEPTION("forms.component");
SAL_WARN("forms.component", "OBoundControlModel::propertyChange: could not adjust my binding-controlled property!");
}
}
}
void SAL_CALL OBoundControlModel::onRowSetChanged( const EventObject& /*i_Event*/ )
{
ControlModelLock aLock( *this );
FieldChangeNotifier aBoundFieldNotifier( aLock ); // disconnect from database column (which is controlled by parent, directly or indirectly) if ( hasField() )
impl_disconnectDatabaseColumn_noNotify(); // log off old listeners if ( isFormListening() )
doFormListening( false ); // determine the new ambient form
impl_determineAmbientForm_nothrow(); // log on new listeners
doFormListening( true ); // re-connect to database column if needed and possible if ( m_xAmbientForm.is() && m_xAmbientForm->isLoaded() )
impl_connectDatabaseColumn_noNotify( false );
}
sal_Bool SAL_CALL OBoundControlModel::commit()
{
ControlModelLock aLock( *this );
OSL_PRECOND( m_bCommitable, "OBoundControlModel::commit: invalid call (I'm not committable!) " ); if ( hasExternalValueBinding() )
{ // in most cases, no action is required: For most derivees, we know the value property of // our control (see initValueProperty), and when an external binding is active, we // instantly forward all changes in this property to the external binding. if ( m_sValuePropertyName.isEmpty() ) // but for those derivees which did not use this feature, we need an // explicit transfer
transferControlValueToExternal( aLock ); returntrue;
}
OSL_ENSURE( !hasExternalValueBinding(), "OBoundControlModel::commit: control flow broken!" ); // we reach this only if we're not working with an external binding if ( !hasField() ) returntrue;
::comphelper::OInterfaceIteratorHelper3 aIter( m_aUpdateListeners );
EventObject aEvent;
aEvent.Source = static_cast< XWeak* >( this ); bool bSuccess = true;
aLock.release(); // UNSAFE > while (aIter.hasMoreElements() && bSuccess)
bSuccess = aIter.next()->approveUpdate( aEvent ); // < UNSAFE
aLock.acquire(); if ( bSuccess )
{ try
{ if ( m_xColumnUpdate.is() )
bSuccess = commitControlValueToDbColumn( false );
}
void OBoundControlModel::connectToField(const Reference<XRowSet>& rForm)
{
OSL_PRECOND( !hasExternalValueBinding(), "OBoundControlModel::connectToField: invalid call (have an external binding)!" ); // if there's a connection to the database if (!(rForm.is() && getConnection(rForm).is())) return;
// determine field and PropertyChangeListener
m_xCursor = rForm;
Reference<XPropertySet> xFieldCandidate; if (m_xCursor.is())
{
Reference<XColumnsSupplier> xColumnsSupplier(m_xCursor, UNO_QUERY);
DBG_ASSERT(xColumnsSupplier.is(), "OBoundControlModel::connectToField : the row set should support the css::sdb::ResultSet service !"); if (xColumnsSupplier.is())
{
Reference<XNameAccess> xColumns = xColumnsSupplier->getColumns(); if (xColumns.is() && xColumns->hasByName(m_aControlSource))
{
OSL_VERIFY( xColumns->getByName(m_aControlSource) >>= xFieldCandidate );
}
void OBoundControlModel::initFromField( const Reference< XRowSet >& _rxRowSet )
{ // but only if the rowset is positioned on a valid record if ( !(hasField() && _rxRowSet.is()) ) return;
bool shouldTransfer(!_rxRowSet->isBeforeFirst() && !_rxRowSet->isAfterLast()); if (!shouldTransfer)
{ const Reference< XPropertySet > xPS(_rxRowSet, UNO_QUERY); if (xPS.is())
{
assert(!shouldTransfer);
xPS->getPropertyValue(u"IsNew"_ustr) >>= shouldTransfer;
}
} if ( shouldTransfer )
transferDbValueToControl(); else // reset the field if the row set is empty // #i30661#
resetNoBroadcast();
}
void OBoundControlModel::impl_connectDatabaseColumn_noNotify( bool _bFromReload )
{
OSL_PRECOND( !hasExternalValueBinding(), "OBoundControlModel::impl_connectDatabaseColumn_noNotify: not to be called with an external value binding!" ); // consistency checks
DBG_ASSERT( !( hasField() && !_bFromReload ), "OBoundControlModel::impl_connectDatabaseColumn_noNotify: the form is just *loaded*, but we already have a field!" );
Reference< XRowSet > xRowSet( m_xAmbientForm, UNO_QUERY );
OSL_ENSURE( xRowSet.is(), "OBoundControlModel::impl_connectDatabaseColumn_noNotify: no row set!" ); if ( !xRowSet.is() ) return; if ( !hasField() || _bFromReload )
{ // connect to the column
connectToField( xRowSet );
}
// now that we're connected (more or less, even if we did not find a column), // we definitely want to forward any potentially occurring value changes
m_bForwardValueChanges = true; // let derived classes react on this new connection
m_bLoaded = true;
onConnectedDbColumn( xRowSet ); // initially transfer the db column value to the control, if we successfully connected to a database column if ( hasField() )
initFromField( xRowSet );
}
void OBoundControlModel::impl_disconnectDatabaseColumn_noNotify()
{
OSL_PRECOND( !hasExternalValueBinding(), "OBoundControlModel::impl_disconnectDatabaseColumn_noNotify: not to be called with an external value binding!" ); // let derived classes react on this
onDisconnectedDbColumn(); if ( hasField() )
{
getField()->removePropertyChangeListener( PROPERTY_VALUE, this );
resetField();
}
m_xCursor = nullptr;
m_bLoaded = false;
}
// XLoadListener void SAL_CALL OBoundControlModel::loaded( const EventObject& _rEvent )
{
ControlModelLock aLock( *this );
FieldChangeNotifier aBoundFieldNotifier( aLock );
OSL_ENSURE( _rEvent.Source == m_xAmbientForm, "OBoundControlModel::loaded: where does this come from?" );
OSL_PRECOND( !hasExternalValueBinding(), "OBoundControlModel::loaded: we should never reach this with an external value binding!" ); if ( hasExternalValueBinding() ) return;
impl_connectDatabaseColumn_noNotify( false );
}
void SAL_CALL OBoundControlModel::unloaded( const css::lang::EventObject& /*aEvent*/ )
{
OSL_PRECOND( !hasExternalValueBinding(), "OBoundControlModel::unloaded: we should never reach this with an external value binding!" );
}
void SAL_CALL OBoundControlModel::reloading( const css::lang::EventObject& /*aEvent*/ )
{
OSL_PRECOND( !hasExternalValueBinding(), "OBoundControlModel::reloading: we should never reach this with an external value binding!" ); if ( hasExternalValueBinding() ) return;
osl::MutexGuard aGuard(m_aMutex);
m_bForwardValueChanges = false;
}
void SAL_CALL OBoundControlModel::unloading(const css::lang::EventObject& /*aEvent*/)
{
ControlModelLock aLock( *this );
FieldChangeNotifier aBoundFieldNotifier( aLock );
OSL_PRECOND( !hasExternalValueBinding(), "OBoundControlModel::unloading: we should never reach this with an external value binding!" ); if ( hasExternalValueBinding() ) return;
impl_disconnectDatabaseColumn_noNotify();
}
void SAL_CALL OBoundControlModel::reloaded( const EventObject& _rEvent )
{
ControlModelLock aLock( *this );
FieldChangeNotifier aBoundFieldNotifier( aLock );
OSL_ENSURE( _rEvent.Source == m_xAmbientForm, "OBoundControlModel::reloaded: where does this come from?" );
OSL_PRECOND( !hasExternalValueBinding(), "OBoundControlModel::reloaded: we should never reach this with an external value binding!" ); if ( hasExternalValueBinding() ) return;
impl_connectDatabaseColumn_noNotify( true );
}
void OBoundControlModel::doSetControlValue( const Any& _rValue )
{
OSL_PRECOND( m_xAggregateFastSet.is() && m_xAggregateSet.is(), "OBoundControlModel::doSetControlValue: invalid aggregate !" );
OSL_PRECOND( !m_sValuePropertyName.isEmpty() || ( m_nValuePropertyAggregateHandle != -1 ), "OBoundControlModel::doSetControlValue: please override if you have own value property handling!" ); try
{ // release our mutex once (it's acquired in one of the calling methods), as setting aggregate properties // may cause any uno controls belonging to us to lock the solar mutex, which is potentially dangerous with // our own mutex locked
MutexRelease aRelease( m_aMutex ); if ( ( m_nValuePropertyAggregateHandle != -1 ) && m_xAggregateFastSet.is() )
{
m_xAggregateFastSet->setFastPropertyValue( m_nValuePropertyAggregateHandle, _rValue );
}
void OBoundControlModel::onConnectedValidator( )
{ try
{ // if we have an external validator, we do not want the control to force invalid // inputs to the default value. Instead, invalid inputs should be translated // to NaN (not a number)
Reference< XPropertySetInfo > xAggregatePropertyInfo; if ( m_xAggregateSet.is() )
xAggregatePropertyInfo = m_xAggregateSet->getPropertySetInfo(); if ( xAggregatePropertyInfo.is() && xAggregatePropertyInfo->hasPropertyByName( PROPERTY_ENFORCE_FORMAT ) )
m_xAggregateSet->setPropertyValue( PROPERTY_ENFORCE_FORMAT, Any( false ) );
}
void OBoundControlModel::onConnectedDbColumn( const Reference< XInterface >& /*_rxForm*/ )
{
OSL_PRECOND( !hasExternalValueBinding(), "OBoundControlModel::onConnectedDbColumn: how this? There's an external value binding!" );
}
void OBoundControlModel::onDisconnectedDbColumn()
{
OSL_PRECOND( !hasExternalValueBinding(), "OBoundControlModel::onDisconnectedDbColumn: how this? There's an external value binding!" );
}
// XReset
Any OBoundControlModel::getDefaultForReset() const
{ return Any();
}
// #i24495# - don't count the insert row as "invalid" bool bSimpleReset =
( !m_xColumn.is() // no connection to a database column
|| ( m_xCursor.is() // OR we have an improperly positioned cursor
&& bInvalidCursorPosition
)
|| hasExternalValueBinding() // OR we have an external value binding
); if ( !bSimpleReset )
{ // The default values will be set if and only if the current value of the field which we're bound // to is NULL. // Else, the current field value should be refreshed // This behaviour is not completely ... "matured": What should happen if the field as well as the // control have a default value? bool bIsNull = true; // we have to access the field content at least once to get a reliable result by XColumn::wasNull try
{ // normally, we'd do a getString here. However, this is extremely expensive in the case // of binary fields. Unfortunately, getString is the only method which is guaranteed // to *always* succeed, all other getXXX methods may fail if the column is asked for a // non-convertible type
sal_Int32 nFieldType = DataType::OBJECT;
getField()->getPropertyValue( PROPERTY_FIELDTYPE ) >>= nFieldType; if ( ( nFieldType == DataType::BINARY )
|| ( nFieldType == DataType::VARBINARY )
|| ( nFieldType == DataType::LONGVARBINARY )
|| ( nFieldType == DataType::OBJECT ) /*|| ( nFieldType == DataType::CLOB )*/
)
m_xColumn->getBinaryStream(); elseif ( nFieldType == DataType::BLOB )
m_xColumn->getBlob(); else
m_xColumn->getString();
bIsNull = m_xColumn->wasNull();
}
catch(const Exception&)
{
DBG_UNHANDLED_EXCEPTION("forms.component");
SAL_WARN("forms.component", "OBoundControlModel::reset: this should have succeeded in all cases!");
}
bool bNeedValueTransfer = true; if ( bIsNull )
{ if ( bIsNewRecord )
{ // reset the control to its default
resetNoBroadcast(); // and immediately commit the changes to the DB column, to keep consistency
commitControlValueToDbColumn( true );
bNeedValueTransfer = false;
}
}
if ( bNeedValueTransfer )
transferDbValueToControl();
}
else
{
resetNoBroadcast(); // transfer to the external binding, if necessary if ( hasExternalValueBinding() )
transferControlValueToExternal( aLock );
}
// revalidate, if necessary if ( hasValidator() )
recheckValidity( true );
aLock.release();
m_aResetHelper.notifyResetted();
}
void OBoundControlModel::impl_setField_noNotify( const Reference< XPropertySet>& _rxField )
{
DBG_ASSERT( !hasExternalValueBinding(), "OBoundControlModel::impl_setField_noNotify: We have an external value binding!" );
m_xField = _rxField;
}
for (autoconst& type : aTypeCandidates)
{ if ( _rxBinding->supportsType( type ) ) returntrue;
} returnfalse;
}
void OBoundControlModel::connectExternalValueBinding( const Reference< XValueBinding >& _rxBinding, ControlModelLock& _rInstanceLock )
{
OSL_PRECOND( _rxBinding.is(), "OBoundControlModel::connectExternalValueBinding: invalid binding instance!" );
OSL_PRECOND( !hasExternalValueBinding( ), "OBoundControlModel::connectExternalValueBinding: precond not met (currently have a binding)!" ); // if we're connected to a database column, suspend this if ( hasField() )
impl_disconnectDatabaseColumn_noNotify(); // suspend listening for load-related events at out ambient form. // This is because an external value binding overrules a possible database binding. if ( isFormListening() )
doFormListening( false ); // remember this new binding
m_xExternalBinding = _rxBinding; // tell the derivee
onConnectedExternalValue(); try
{ // add as value listener so we get notified when the value changes
Reference< XModifyBroadcaster > xModifiable( m_xExternalBinding, UNO_QUERY ); if ( xModifiable.is() )
xModifiable->addModifyListener( this ); // add as property change listener for some (possibly present) properties we're // interested in
Reference< XPropertySet > xBindingProps( m_xExternalBinding, UNO_QUERY );
Reference< XPropertySetInfo > xBindingPropsInfo( xBindingProps.is() ? xBindingProps->getPropertySetInfo() : Reference< XPropertySetInfo >() ); if ( xBindingPropsInfo.is() )
{ if ( xBindingPropsInfo->hasPropertyByName( PROPERTY_READONLY ) )
{
xBindingProps->addPropertyChangeListener( PROPERTY_READONLY, this );
m_bBindingControlsRO = true;
}
if ( xBindingPropsInfo->hasPropertyByName( PROPERTY_RELEVANT ) )
{
xBindingProps->addPropertyChangeListener( PROPERTY_RELEVANT, this );
m_bBindingControlsEnable = true;
}
// propagate our new value
transferExternalValueToControl( _rInstanceLock ); // if the binding is also a validator, use it, too. This is a constraint of the // com.sun.star.form.binding.ValidatableBindableFormComponent service if ( !m_bSupportsValidation ) return;
// if the binding also acts as our validator, disconnect the validator, too if ( ( m_xExternalBinding == m_xValidator ) && m_xValidator.is() )
disconnectValidator( ); // no binding anymore
m_xExternalBinding.clear(); // be a load listener at our form, again. This was suspended while we had // an external value binding in place.
doFormListening( true ); // re-connect to database column of the new parent if ( m_xAmbientForm.is() && m_xAmbientForm->isLoaded() )
impl_connectDatabaseColumn_noNotify( false );
}
void SAL_CALL OBoundControlModel::setValueBinding( const Reference< XValueBinding >& _rxBinding )
{
OSL_PRECOND( m_bSupportsExternalBinding, "OBoundControlModel::setValueBinding: How did you reach this method?" ); // the interface for this method should not have been exposed if we do not // support binding to external data // allow reset if ( _rxBinding.is() && !impl_approveValueBinding_nolock( _rxBinding ) )
{ throw IncompatibleTypesException(
ResourceManager::loadString(RID_STR_INCOMPATIBLE_TYPES),
*this
);
}
ControlModelLock aLock( *this ); // since a ValueBinding overrules any potentially active database binding, the change in a ValueBinding // might trigger a change in our BoundField.
FieldChangeNotifier aBoundFieldNotifier( aLock ); // disconnect from the old binding if ( hasExternalValueBinding() )
disconnectExternalValueBinding( ); // connect to the new binding if ( _rxBinding.is() )
connectExternalValueBinding( _rxBinding, aLock );
}
Reference< XValueBinding > SAL_CALL OBoundControlModel::getValueBinding( )
{
::osl::MutexGuard aGuard( m_aMutex );
OSL_PRECOND( m_bSupportsExternalBinding, "OBoundControlModel::getValueBinding: How did you reach this method?" ); // the interface for this method should not have been exposed if we do not // support binding to external data return m_xExternalBinding;
}
void SAL_CALL OBoundControlModel::modified( const EventObject& _rEvent )
{
ControlModelLock aLock( *this );
OSL_PRECOND( hasExternalValueBinding(), "OBoundControlModel::modified: Where did this come from?" ); if ( !m_bTransferringValue && ( m_xExternalBinding == _rEvent.Source ) && m_xExternalBinding.is() )
{
transferExternalValueToControl( aLock );
}
}
Any OBoundControlModel::translateExternalValueToControlValue( const Any& _rExternalValue ) const
{
OSL_PRECOND( m_bSupportsExternalBinding && hasExternalValueBinding(), "OBoundControlModel::translateExternalValueToControlValue: precondition not met!" );
Any aControlValue( _rExternalValue ); // if the external value is VOID, and our value property is not allowed to be VOID, // then default-construct a value if ( !aControlValue.hasValue() && !m_bValuePropertyMayBeVoid )
aControlValue.setValue( nullptr, m_aValuePropertyType ); // out of here return aControlValue;
}
Any OBoundControlModel::translateControlValueToExternalValue( ) const
{ return getControlValue( );
}
Any OBoundControlModel::translateControlValueToValidatableValue( ) const
{
OSL_PRECOND( m_xValidator.is(), "OBoundControlModel::translateControlValueToValidatableValue: no validator, so why should I?" ); if ( ( m_xValidator == m_xExternalBinding ) && m_xValidator.is() ) return translateControlValueToExternalValue(); return getControlValue();
}
Any OBoundControlModel::getControlValue( ) const
{
OSL_PRECOND( m_xAggregateFastSet.is() && m_xAggregateSet.is(), "OBoundControlModel::getControlValue: invalid aggregate !" );
OSL_PRECOND( !m_sValuePropertyName.isEmpty() || ( m_nValuePropertyAggregateHandle != -1 ), "OBoundControlModel::getControlValue: please override if you have own value property handling!" ); // determine the current control value
Any aControlValue; if ( ( m_nValuePropertyAggregateHandle != -1 ) && m_xAggregateFastSet.is() )
{
aControlValue = m_xAggregateFastSet->getFastPropertyValue( m_nValuePropertyAggregateHandle );
}
// add as value listener so we get notified when the value changes if ( m_xValidator.is() )
{ try
{
m_xValidator->addValidityConstraintListener( this );
}
void OBoundControlModel::disconnectValidator( )
{
OSL_PRECOND( hasValidator( ), "OBoundControlModel::connectValidator: precond not met (don't have a validator currently)!" );
// add as value listener so we get notified when the value changes if ( m_xValidator.is() )
{ try
{
m_xValidator->removeValidityConstraintListener( this );
}
catch( const RuntimeException& )
{
}
}
m_xValidator.clear();
onDisconnectedValidator( );
}
void SAL_CALL OBoundControlModel::setValidator( const Reference< XValidator >& _rxValidator )
{
osl::MutexGuard aGuard( m_aMutex );
OSL_PRECOND( m_bSupportsValidation, "OBoundControlModel::setValidator: How did you reach this method?" ); // the interface for this method should not have been exposed if we do not // support validation
// early out if the validator does not change if ( _rxValidator == m_xValidator ) return;
// disconnect from the old validator if ( hasValidator() )
disconnectValidator( );
// connect to the new validator if ( _rxValidator.is() )
connectValidator( _rxValidator );
}
Reference< XValidator > SAL_CALL OBoundControlModel::getValidator( )
{
::osl::MutexGuard aGuard( m_aMutex );
OSL_PRECOND( m_bSupportsValidation, "OBoundControlModel::getValidator: How did you reach this method?" ); // the interface for this method should not have been exposed if we do not // support validation
return m_xValidator;
}
void SAL_CALL OBoundControlModel::validityConstraintChanged( const EventObject& /*Source*/ )
{
osl::MutexGuard aGuard( m_aMutex );
OSL_PRECOND( m_bSupportsValidation, "OBoundControlModel::validityConstraintChanged: How did you reach this method?" ); // the interface for this method should not have been exposed if we do not // support validation
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.