blob: cf7b43be82c5cd5738d1fa6905fb96af5c728186 [file] [log] [blame]
/**************************************************************
*
* 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
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the 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
//.........................................................................