diff options
Diffstat (limited to 'sc/source/ui/unoobj/cellvaluebinding.cxx')
-rw-r--r-- | sc/source/ui/unoobj/cellvaluebinding.cxx | 663 |
1 files changed, 663 insertions, 0 deletions
diff --git a/sc/source/ui/unoobj/cellvaluebinding.cxx b/sc/source/ui/unoobj/cellvaluebinding.cxx new file mode 100644 index 000000000000..3b806ccf80b7 --- /dev/null +++ b/sc/source/ui/unoobj/cellvaluebinding.cxx @@ -0,0 +1,663 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_sc.hxx" +#include "cellvaluebinding.hxx" +#include <tools/debug.hxx> +#include <rtl/math.hxx> +#include <com/sun/star/table/XCellRange.hpp> +#include <com/sun/star/sheet/XCellAddressable.hpp> +#include <com/sun/star/sheet/XCellRangeData.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/util/XNumberFormatsSupplier.hpp> +#include <com/sun/star/util/XNumberFormatTypes.hpp> +#include <com/sun/star/util/NumberFormat.hpp> + +//......................................................................... +namespace calc +{ +//......................................................................... + +#define PROP_HANDLE_BOUND_CELL 1 + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::table; + using namespace ::com::sun::star::text; + using namespace ::com::sun::star::sheet; + using namespace ::com::sun::star::container; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::util; + using namespace ::com::sun::star::form::binding; + + //===================================================================== + //= OCellValueBinding + //===================================================================== + DBG_NAME( OCellValueBinding ) + //--------------------------------------------------------------------- +#ifdef DBG_UTIL + const char* OCellValueBinding::checkConsistency_static( const void* _pThis ) + { + return static_cast< const OCellValueBinding* >( _pThis )->checkConsistency( ); + } + + const char* OCellValueBinding::checkConsistency( ) const + { + const char* pAssertion = NULL; + if ( m_xCellText.is() && !m_xCell.is() ) + // there are places (e.g. getSupportedTypes) which rely on the fact + // that m_xCellText.is() implies m_xCell.is() + pAssertion = "cell references inconsistent!"; + + // TODO: place any additional checks here to ensure consistency of this instance + return pAssertion; + } +#endif + + //--------------------------------------------------------------------- + OCellValueBinding::OCellValueBinding( const Reference< XSpreadsheetDocument >& _rxDocument, sal_Bool _bListPos ) + :OCellValueBinding_Base( m_aMutex ) + ,OCellValueBinding_PBase( OCellValueBinding_Base::rBHelper ) + ,m_xDocument( _rxDocument ) + ,m_aModifyListeners( m_aMutex ) + ,m_bInitialized( sal_False ) + ,m_bListPos( _bListPos ) + { + DBG_CTOR( OCellValueBinding, checkConsistency_static ); + + // register our property at the base class + CellAddress aInitialPropValue; + registerPropertyNoMember( + ::rtl::OUString::createFromAscii( "BoundCell" ), + PROP_HANDLE_BOUND_CELL, + PropertyAttribute::BOUND | PropertyAttribute::READONLY, + ::getCppuType( &aInitialPropValue ), + &aInitialPropValue + ); + + // TODO: implement a ReadOnly property as required by the service, + // which probably maps to the cell being locked + } + + //--------------------------------------------------------------------- + OCellValueBinding::~OCellValueBinding( ) + { + if ( !OCellValueBinding_Base::rBHelper.bDisposed ) + { + acquire(); // prevent duplicate dtor + dispose(); + } + + DBG_DTOR( OCellValueBinding, checkConsistency_static ); + } + + //-------------------------------------------------------------------- + IMPLEMENT_FORWARD_XINTERFACE2( OCellValueBinding, OCellValueBinding_Base, OCellValueBinding_PBase ) + + //-------------------------------------------------------------------- + IMPLEMENT_FORWARD_XTYPEPROVIDER2( OCellValueBinding, OCellValueBinding_Base, OCellValueBinding_PBase ) + + //-------------------------------------------------------------------- + void SAL_CALL OCellValueBinding::disposing() + { + DBG_CHKTHIS( OCellValueBinding, checkConsistency_static ); + + Reference<XModifyBroadcaster> xBroadcaster( m_xCell, UNO_QUERY ); + if ( xBroadcaster.is() ) + { + xBroadcaster->removeModifyListener( this ); + } + +// OCellValueBinding_Base::disposing(); + WeakAggComponentImplHelperBase::disposing(); + + // TODO: clean up here whatever you need to clean up (e.g. deregister as XEventListener + // for the cell) + } + + //-------------------------------------------------------------------- + Reference< XPropertySetInfo > SAL_CALL OCellValueBinding::getPropertySetInfo( ) throw(RuntimeException) + { + DBG_CHKTHIS( OCellValueBinding, checkConsistency_static ); + return createPropertySetInfo( getInfoHelper() ) ; + } + + //-------------------------------------------------------------------- + ::cppu::IPropertyArrayHelper& SAL_CALL OCellValueBinding::getInfoHelper() + { + return *OCellValueBinding_PABase::getArrayHelper(); + } + + //-------------------------------------------------------------------- + ::cppu::IPropertyArrayHelper* OCellValueBinding::createArrayHelper( ) const + { + Sequence< Property > aProps; + describeProperties( aProps ); + return new ::cppu::OPropertyArrayHelper(aProps); + } + + //-------------------------------------------------------------------- + void SAL_CALL OCellValueBinding::getFastPropertyValue( Any& _rValue, sal_Int32 _nHandle ) const + { + DBG_CHKTHIS( OCellValueBinding, checkConsistency_static ); + DBG_ASSERT( _nHandle == PROP_HANDLE_BOUND_CELL, "OCellValueBinding::getFastPropertyValue: invalid handle!" ); + // we only have this one property .... + (void)_nHandle; // avoid warning in product version + + _rValue.clear(); + Reference< XCellAddressable > xCellAddress( m_xCell, UNO_QUERY ); + if ( xCellAddress.is() ) + _rValue <<= xCellAddress->getCellAddress( ); + } + + //-------------------------------------------------------------------- + Sequence< Type > SAL_CALL OCellValueBinding::getSupportedValueTypes( ) throw (RuntimeException) + { + DBG_CHKTHIS( OCellValueBinding, checkConsistency_static ); + checkDisposed( ); + checkInitialized( ); + + sal_Int32 nCount = m_xCellText.is() ? 3 : m_xCell.is() ? 1 : 0; + if ( m_bListPos ) + ++nCount; + + Sequence< Type > aTypes( nCount ); + if ( m_xCell.is() ) + { + // an XCell can be used to set/get "double" values + aTypes[0] = ::getCppuType( static_cast< double* >( NULL ) ); + if ( m_xCellText.is() ) + { + // an XTextRange can be used to set/get "string" values + aTypes[1] = ::getCppuType( static_cast< ::rtl::OUString* >( NULL ) ); + // and additionally, we use it to handle booleans + aTypes[2] = ::getCppuType( static_cast< sal_Bool* >( NULL ) ); + } + + // add sal_Int32 only if constructed as ListPositionCellBinding + if ( m_bListPos ) + aTypes[nCount-1] = ::getCppuType( static_cast< sal_Int32* >( NULL ) ); + } + + return aTypes; + } + + //-------------------------------------------------------------------- + sal_Bool SAL_CALL OCellValueBinding::supportsType( const Type& aType ) throw (RuntimeException) + { + DBG_CHKTHIS( OCellValueBinding, checkConsistency_static ); + checkDisposed( ); + checkInitialized( ); + + // look up in our sequence + Sequence< Type > aSupportedTypes( getSupportedValueTypes() ); + const Type* pTypes = aSupportedTypes.getConstArray(); + const Type* pTypesEnd = aSupportedTypes.getConstArray() + aSupportedTypes.getLength(); + while ( pTypes != pTypesEnd ) + if ( aType.equals( *pTypes++ ) ) + return sal_True; + + return sal_False; + } + + //-------------------------------------------------------------------- + Any SAL_CALL OCellValueBinding::getValue( const Type& aType ) throw (IncompatibleTypesException, RuntimeException) + { + DBG_CHKTHIS( OCellValueBinding, checkConsistency_static ); + checkDisposed( ); + checkInitialized( ); + checkValueType( aType ); + + Any aReturn; + switch ( aType.getTypeClass() ) + { + case TypeClass_STRING: + DBG_ASSERT( m_xCellText.is(), "OCellValueBinding::getValue: don't have a text!" ); + if ( m_xCellText.is() ) + aReturn <<= m_xCellText->getString(); + else + aReturn <<= ::rtl::OUString(); + break; + + case TypeClass_BOOLEAN: + DBG_ASSERT( m_xCell.is(), "OCellValueBinding::getValue: don't have a double value supplier!" ); + if ( m_xCell.is() ) + { + // check if the cell has a numeric value (this might go into a helper function): + + sal_Bool bHasValue = sal_False; + CellContentType eCellType = m_xCell->getType(); + if ( eCellType == CellContentType_VALUE ) + bHasValue = sal_True; + else if ( eCellType == CellContentType_FORMULA ) + { + // check if the formula result is a value + if ( m_xCell->getError() == 0 ) + { + Reference<XPropertySet> xProp( m_xCell, UNO_QUERY ); + if ( xProp.is() ) + { + CellContentType eResultType; + if ( (xProp->getPropertyValue(::rtl::OUString::createFromAscii( "FormulaResultType" ) ) >>= eResultType) && eResultType == CellContentType_VALUE ) + bHasValue = sal_True; + } + } + } + + if ( bHasValue ) + { + // 0 is "unchecked", any other value is "checked", regardless of number format + double nCellValue = m_xCell->getValue(); + sal_Bool bBoolValue = ( nCellValue != 0.0 ); + aReturn <<= bBoolValue; + } + // empty cells, text cells and text or error formula results: leave return value empty + } + break; + + case TypeClass_DOUBLE: + DBG_ASSERT( m_xCell.is(), "OCellValueBinding::getValue: don't have a double value supplier!" ); + if ( m_xCell.is() ) + aReturn <<= m_xCell->getValue(); + else + aReturn <<= (double)0; + break; + + case TypeClass_LONG: + DBG_ASSERT( m_xCell.is(), "OCellValueBinding::getValue: don't have a double value supplier!" ); + if ( m_xCell.is() ) + { + // The list position value in the cell is 1-based. + // We subtract 1 from any cell value (no special handling for 0 or negative values). + + sal_Int32 nValue = (sal_Int32) rtl::math::approxFloor( m_xCell->getValue() ); + --nValue; + + aReturn <<= nValue; + } + else + aReturn <<= (sal_Int32)0; + break; + + default: + DBG_ERROR( "OCellValueBinding::getValue: unreachable code!" ); + // a type other than double and string should never have survived the checkValueType + // above + } + return aReturn; + } + + //-------------------------------------------------------------------- + void SAL_CALL OCellValueBinding::setValue( const Any& aValue ) throw (IncompatibleTypesException, NoSupportException, RuntimeException) + { + DBG_CHKTHIS( OCellValueBinding, checkConsistency_static ); + checkDisposed( ); + checkInitialized( ); + if ( aValue.hasValue() ) + checkValueType( aValue.getValueType() ); + + switch ( aValue.getValueType().getTypeClass() ) + { + case TypeClass_STRING: + { + DBG_ASSERT( m_xCellText.is(), "OCellValueBinding::setValue: don't have a text!" ); + + ::rtl::OUString sText; + aValue >>= sText; + if ( m_xCellText.is() ) + m_xCellText->setString( sText ); + } + break; + + case TypeClass_BOOLEAN: + { + DBG_ASSERT( m_xCell.is(), "OCellValueBinding::setValue: don't have a double value supplier!" ); + + // boolean is stored as values 0 or 1 + // TODO: set the number format to boolean if no format is set? + + sal_Bool bValue( sal_False ); + aValue >>= bValue; + double nCellValue = bValue ? 1.0 : 0.0; + + if ( m_xCell.is() ) + m_xCell->setValue( nCellValue ); + + setBooleanFormat(); + } + break; + + case TypeClass_DOUBLE: + { + DBG_ASSERT( m_xCell.is(), "OCellValueBinding::setValue: don't have a double value supplier!" ); + + double nValue = 0; + aValue >>= nValue; + if ( m_xCell.is() ) + m_xCell->setValue( nValue ); + } + break; + + case TypeClass_LONG: + { + DBG_ASSERT( m_xCell.is(), "OCellValueBinding::setValue: don't have a double value supplier!" ); + + sal_Int32 nValue = 0; + aValue >>= nValue; // list index from control layer (0-based) + ++nValue; // the list position value in the cell is 1-based + if ( m_xCell.is() ) + m_xCell->setValue( nValue ); + } + break; + + case TypeClass_VOID: + { + // #N/A error value can only be set using XCellRangeData + + Reference<XCellRangeData> xData( m_xCell, UNO_QUERY ); + DBG_ASSERT( xData.is(), "OCellValueBinding::setValue: don't have XCellRangeData!" ); + if ( xData.is() ) + { + Sequence<Any> aInner(1); // one empty element + Sequence< Sequence<Any> > aOuter( &aInner, 1 ); // one row + xData->setDataArray( aOuter ); + } + } + break; + + default: + DBG_ERROR( "OCellValueBinding::setValue: unreachable code!" ); + // a type other than double and string should never have survived the checkValueType + // above + } + } + //-------------------------------------------------------------------- + void OCellValueBinding::setBooleanFormat() + { + // set boolean number format if not already set + + ::rtl::OUString sPropName( ::rtl::OUString::createFromAscii( "NumberFormat" ) ); + Reference<XPropertySet> xCellProp( m_xCell, UNO_QUERY ); + Reference<XNumberFormatsSupplier> xSupplier( m_xDocument, UNO_QUERY ); + if ( xSupplier.is() && xCellProp.is() ) + { + Reference<XNumberFormats> xFormats(xSupplier->getNumberFormats()); + Reference<XNumberFormatTypes> xTypes( xFormats, UNO_QUERY ); + if ( xTypes.is() ) + { + Locale aLocale; + sal_Bool bWasBoolean = sal_False; + + sal_Int32 nOldIndex = ::comphelper::getINT32( xCellProp->getPropertyValue( sPropName ) ); + Reference<XPropertySet> xOldFormat; + try + { + xOldFormat.set(xFormats->getByKey( nOldIndex )); + } + catch ( Exception& ) + { + // non-existing format - can happen, use defaults + } + if ( xOldFormat.is() ) + { + // use the locale of the existing format + xOldFormat->getPropertyValue( ::rtl::OUString::createFromAscii( "Locale" ) ) >>= aLocale; + + sal_Int16 nOldType = ::comphelper::getINT16( + xOldFormat->getPropertyValue( ::rtl::OUString::createFromAscii( "Type" ) ) ); + if ( nOldType & NumberFormat::LOGICAL ) + bWasBoolean = sal_True; + } + + if ( !bWasBoolean ) + { + sal_Int32 nNewIndex = xTypes->getStandardFormat( NumberFormat::LOGICAL, aLocale ); + xCellProp->setPropertyValue( sPropName, makeAny( nNewIndex ) ); + } + } + } + } + + //-------------------------------------------------------------------- + void OCellValueBinding::checkDisposed( ) const SAL_THROW( ( DisposedException ) ) + { + if ( OCellValueBinding_Base::rBHelper.bInDispose || OCellValueBinding_Base::rBHelper.bDisposed ) + throw DisposedException(); + // TODO: is it worth having an error message here? + } + + //-------------------------------------------------------------------- + void OCellValueBinding::checkInitialized() SAL_THROW( ( RuntimeException ) ) + { + if ( !m_bInitialized ) + throw RuntimeException(); + // TODO: error message + } + + //-------------------------------------------------------------------- + void OCellValueBinding::checkValueType( const Type& _rType ) const SAL_THROW( ( IncompatibleTypesException ) ) + { + OCellValueBinding* pNonConstThis = const_cast< OCellValueBinding* >( this ); + if ( !pNonConstThis->supportsType( _rType ) ) + { + ::rtl::OUString sMessage( RTL_CONSTASCII_USTRINGPARAM( "The given type (" ) ); + sMessage += _rType.getTypeName(); + sMessage += ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( ") is not supported by this binding." ) ); + // TODO: localize this error message + + throw IncompatibleTypesException( sMessage, *pNonConstThis ); + // TODO: alternatively use a type converter service for this? + } + } + + //-------------------------------------------------------------------- + ::rtl::OUString SAL_CALL OCellValueBinding::getImplementationName( ) throw (RuntimeException) + { + DBG_CHKTHIS( OCellValueBinding, checkConsistency_static ); + + return ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "com.sun.star.comp.sheet.OCellValueBinding" ) ); + } + + //-------------------------------------------------------------------- + sal_Bool SAL_CALL OCellValueBinding::supportsService( const ::rtl::OUString& _rServiceName ) throw (RuntimeException) + { + DBG_CHKTHIS( OCellValueBinding, checkConsistency_static ); + + Sequence< ::rtl::OUString > aSupportedServices( getSupportedServiceNames() ); + const ::rtl::OUString* pLookup = aSupportedServices.getConstArray(); + const ::rtl::OUString* pLookupEnd = aSupportedServices.getConstArray() + aSupportedServices.getLength(); + while ( pLookup != pLookupEnd ) + if ( *pLookup++ == _rServiceName ) + return sal_True; + + return sal_False; + } + + //-------------------------------------------------------------------- + Sequence< ::rtl::OUString > SAL_CALL OCellValueBinding::getSupportedServiceNames( ) throw (RuntimeException) + { + DBG_CHKTHIS( OCellValueBinding, checkConsistency_static ); + + Sequence< ::rtl::OUString > aServices( m_bListPos ? 3 : 2 ); + aServices[ 0 ] = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "com.sun.star.table.CellValueBinding" ) ); + aServices[ 1 ] = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "com.sun.star.form.binding.ValueBinding" ) ); + if ( m_bListPos ) + aServices[ 2 ] = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "com.sun.star.table.ListPositionCellBinding" ) ); + return aServices; + } + + //-------------------------------------------------------------------- + void SAL_CALL OCellValueBinding::addModifyListener( const Reference< XModifyListener >& _rxListener ) throw (RuntimeException) + { + if ( _rxListener.is() ) + m_aModifyListeners.addInterface( _rxListener ); + } + + //-------------------------------------------------------------------- + void SAL_CALL OCellValueBinding::removeModifyListener( const Reference< XModifyListener >& _rxListener ) throw (RuntimeException) + { + if ( _rxListener.is() ) + m_aModifyListeners.removeInterface( _rxListener ); + } + + //-------------------------------------------------------------------- + void OCellValueBinding::notifyModified() + { + EventObject aEvent; + aEvent.Source.set(*this); + + ::cppu::OInterfaceIteratorHelper aIter( m_aModifyListeners ); + while ( aIter.hasMoreElements() ) + { + try + { + static_cast< XModifyListener* >( aIter.next() )->modified( aEvent ); + } + catch( const RuntimeException& ) + { + // silent this + } + catch( const Exception& ) + { + DBG_ERROR( "OCellValueBinding::notifyModified: caught a (non-runtime) exception!" ); + } + } + } + + //-------------------------------------------------------------------- + void SAL_CALL OCellValueBinding::modified( const EventObject& /* aEvent */ ) throw (RuntimeException) + { + DBG_CHKTHIS( OCellValueBinding, checkConsistency_static ); + + notifyModified(); + } + + //-------------------------------------------------------------------- + void SAL_CALL OCellValueBinding::disposing( const EventObject& aEvent ) throw (RuntimeException) + { + DBG_CHKTHIS( OCellValueBinding, checkConsistency_static ); + + Reference<XInterface> xCellInt( m_xCell, UNO_QUERY ); + if ( xCellInt == aEvent.Source ) + { + // release references to cell object + m_xCell.clear(); + m_xCellText.clear(); + } + } + + //-------------------------------------------------------------------- + void SAL_CALL OCellValueBinding::initialize( const Sequence< Any >& _rArguments ) throw (Exception, RuntimeException) + { + if ( m_bInitialized ) + throw Exception(); + // TODO: error message + + // get the cell address + CellAddress aAddress; + sal_Bool bFoundAddress = sal_False; + + const Any* pLoop = _rArguments.getConstArray(); + const Any* pLoopEnd = _rArguments.getConstArray() + _rArguments.getLength(); + for ( ; ( pLoop != pLoopEnd ) && !bFoundAddress; ++pLoop ) + { + NamedValue aValue; + if ( *pLoop >>= aValue ) + { + if ( aValue.Name.equalsAscii( "BoundCell" ) ) + { + if ( aValue.Value >>= aAddress ) + bFoundAddress = sal_True; + } + } + } + + if ( !bFoundAddress ) + // TODO: error message + throw Exception(); + + // get the cell object + try + { + // first the sheets collection + Reference< XIndexAccess > xSheets; + if ( m_xDocument.is() ) + xSheets.set(xSheets.query( m_xDocument->getSheets( ) )); + DBG_ASSERT( xSheets.is(), "OCellValueBinding::initialize: could not retrieve the sheets!" ); + + if ( xSheets.is() ) + { + // the concrete sheet + Reference< XCellRange > xSheet(xSheets->getByIndex( aAddress.Sheet ), UNO_QUERY); + DBG_ASSERT( xSheet.is(), "OCellValueBinding::initialize: NULL sheet, but no exception!" ); + + // the concrete cell + if ( xSheet.is() ) + { + m_xCell.set(xSheet->getCellByPosition( aAddress.Column, aAddress.Row )); + Reference< XCellAddressable > xAddressAccess( m_xCell, UNO_QUERY ); + DBG_ASSERT( xAddressAccess.is(), "OCellValueBinding::initialize: either NULL cell, or cell without address access!" ); + } + } + } + catch( const Exception& ) + { + DBG_ERROR( "OCellValueBinding::initialize: caught an exception while retrieving the cell object!" ); + } + + if ( !m_xCell.is() ) + throw Exception(); + // TODO error message + + m_xCellText.set(m_xCellText.query( m_xCell )); + + Reference<XModifyBroadcaster> xBroadcaster( m_xCell, UNO_QUERY ); + if ( xBroadcaster.is() ) + { + xBroadcaster->addModifyListener( this ); + } + + // TODO: add as XEventListener to the cell, so we get notified when it dies, + // and can dispose ourself then + + // TODO: somehow add as listener so we get notified when the address of the cell changes + // We need to forward this as change in our BoundCell property to our property change listeners + + // TODO: be an XModifyBroadcaster, so that changes in our cell can be notified + // to the BindableValue which is/will be bound to this instance. + + m_bInitialized = sal_True; + // TODO: place your code here + } + + +//......................................................................... +} // namespace calc +//......................................................................... |