diff options
Diffstat (limited to 'sc/source/ui/unoobj/cellvaluebinding.cxx')
-rw-r--r-- | sc/source/ui/unoobj/cellvaluebinding.cxx | 576 |
1 files changed, 576 insertions, 0 deletions
diff --git a/sc/source/ui/unoobj/cellvaluebinding.cxx b/sc/source/ui/unoobj/cellvaluebinding.cxx new file mode 100644 index 000000000..6bf868cf3 --- /dev/null +++ b/sc/source/ui/unoobj/cellvaluebinding.cxx @@ -0,0 +1,576 @@ +/* -*- 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 "cellvaluebinding.hxx" +#include <rtl/math.hxx> +#include <com/sun/star/form/binding/IncompatibleTypesException.hpp> +#include <com/sun/star/lang/NotInitializedException.hpp> +#include <com/sun/star/text/XTextRange.hpp> +#include <com/sun/star/table/XCellRange.hpp> +#include <com/sun/star/sheet/FormulaResult.hpp> +#include <com/sun/star/sheet/XCellAddressable.hpp> +#include <com/sun/star/sheet/XCellRangeData.hpp> +#include <com/sun/star/sheet/XSpreadsheetDocument.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> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/types.hxx> +#include <tools/diagnose_ex.h> + +namespace calc +{ + +#define PROP_HANDLE_BOUND_CELL 1 + + namespace lang = ::com::sun::star::lang; + 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::OCellValueBinding( const Reference< XSpreadsheetDocument >& _rxDocument, bool _bListPos ) + :OCellValueBinding_Base( m_aMutex ) + ,OCellValueBinding_PBase( OCellValueBinding_Base::rBHelper ) + ,m_xDocument( _rxDocument ) + ,m_aModifyListeners( m_aMutex ) + ,m_bInitialized( false ) + ,m_bListPos( _bListPos ) + { + // register our property at the base class + registerPropertyNoMember( + "BoundCell", + PROP_HANDLE_BOUND_CELL, + PropertyAttribute::BOUND | PropertyAttribute::READONLY, + cppu::UnoType<CellAddress>::get(), + css::uno::Any(CellAddress()) + ); + + // 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(); + } + } + + IMPLEMENT_FORWARD_XINTERFACE2( OCellValueBinding, OCellValueBinding_Base, OCellValueBinding_PBase ) + + IMPLEMENT_FORWARD_XTYPEPROVIDER2( OCellValueBinding, OCellValueBinding_Base, OCellValueBinding_PBase ) + + void SAL_CALL OCellValueBinding::disposing() + { + Reference<XModifyBroadcaster> xBroadcaster( m_xCell, UNO_QUERY ); + if ( xBroadcaster.is() ) + { + xBroadcaster->removeModifyListener( this ); + } + + 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( ) + { + 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 + { + OSL_ENSURE( _nHandle == PROP_HANDLE_BOUND_CELL, "OCellValueBinding::getFastPropertyValue: invalid handle!" ); + // we only have this one property... + + _rValue.clear(); + Reference< XCellAddressable > xCellAddress( m_xCell, UNO_QUERY ); + if ( xCellAddress.is() ) + _rValue <<= xCellAddress->getCellAddress( ); + } + + Sequence< Type > SAL_CALL OCellValueBinding::getSupportedValueTypes( ) + { + 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() ) + { + auto pTypes = aTypes.getArray(); + + // an XCell can be used to set/get "double" values + pTypes[0] = ::cppu::UnoType<double>::get(); + if ( m_xCellText.is() ) + { + // an XTextRange can be used to set/get "string" values + pTypes[1] = ::cppu::UnoType<OUString>::get(); + // and additionally, we use it to handle booleans + pTypes[2] = ::cppu::UnoType<sal_Bool>::get(); + } + + // add sal_Int32 only if constructed as ListPositionCellBinding + if ( m_bListPos ) + pTypes[nCount-1] = cppu::UnoType<sal_Int32>::get(); + } + + return aTypes; + } + + sal_Bool SAL_CALL OCellValueBinding::supportsType( const Type& aType ) + { + checkDisposed( ); + checkInitialized( ); + + // look up in our sequence + const Sequence< Type > aSupportedTypes( getSupportedValueTypes() ); + for ( auto const & i : aSupportedTypes ) + if ( aType == i ) + return true; + + return false; + } + + Any SAL_CALL OCellValueBinding::getValue( const Type& aType ) + { + checkDisposed( ); + checkInitialized( ); + checkValueType( aType ); + + Any aReturn; + switch ( aType.getTypeClass() ) + { + case TypeClass_STRING: + OSL_ENSURE( m_xCellText.is(), "OCellValueBinding::getValue: don't have a text!" ); + if ( m_xCellText.is() ) + aReturn <<= m_xCellText->getString(); + else + aReturn <<= OUString(); + break; + + case TypeClass_BOOLEAN: + OSL_ENSURE( 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): + + bool bHasValue = false; + CellContentType eCellType = m_xCell->getType(); + if ( eCellType == CellContentType_VALUE ) + bHasValue = 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() ) + { + sal_Int32 nResultType; + if ( (xProp->getPropertyValue("FormulaResultType2") >>= nResultType) + && nResultType == FormulaResult::VALUE ) + bHasValue = true; + } + } + } + + if ( bHasValue ) + { + // 0 is "unchecked", any other value is "checked", regardless of number format + double nCellValue = m_xCell->getValue(); + 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: + OSL_ENSURE( 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: + OSL_ENSURE( 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 = static_cast<sal_Int32>(rtl::math::approxFloor( m_xCell->getValue() )); + --nValue; + + aReturn <<= nValue; + } + else + aReturn <<= sal_Int32(0); + break; + + default: + OSL_FAIL( "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 ) + { + checkDisposed( ); + checkInitialized( ); + if ( aValue.hasValue() ) + checkValueType( aValue.getValueType() ); + + switch ( aValue.getValueType().getTypeClass() ) + { + case TypeClass_STRING: + { + OSL_ENSURE( m_xCellText.is(), "OCellValueBinding::setValue: don't have a text!" ); + + OUString sText; + aValue >>= sText; + if ( m_xCellText.is() ) + m_xCellText->setString( sText ); + } + break; + + case TypeClass_BOOLEAN: + { + OSL_ENSURE( 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? + + bool bValue( false ); + aValue >>= bValue; + double nCellValue = bValue ? 1.0 : 0.0; + + if ( m_xCell.is() ) + m_xCell->setValue( nCellValue ); + + setBooleanFormat(); + } + break; + + case TypeClass_DOUBLE: + { + OSL_ENSURE( 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: + { + OSL_ENSURE( 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 ); + OSL_ENSURE( 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: + OSL_FAIL( "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 + + OUString sPropName( "NumberFormat" ); + Reference<XPropertySet> xCellProp( m_xCell, UNO_QUERY ); + Reference<XNumberFormatsSupplier> xSupplier( m_xDocument, UNO_QUERY ); + if ( !(xSupplier.is() && xCellProp.is()) ) + return; + + Reference<XNumberFormats> xFormats(xSupplier->getNumberFormats()); + Reference<XNumberFormatTypes> xTypes( xFormats, UNO_QUERY ); + if ( !xTypes.is() ) + return; + + lang::Locale aLocale; + bool bWasBoolean = 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("Locale") >>= aLocale; + + sal_Int16 nOldType = ::comphelper::getINT16( + xOldFormat->getPropertyValue("Type") ); + if ( nOldType & NumberFormat::LOGICAL ) + bWasBoolean = true; + } + + if ( !bWasBoolean ) + { + sal_Int32 nNewIndex = xTypes->getStandardFormat( NumberFormat::LOGICAL, aLocale ); + xCellProp->setPropertyValue( sPropName, Any( nNewIndex ) ); + } + } + + void OCellValueBinding::checkDisposed( ) const + { + if ( OCellValueBinding_Base::rBHelper.bInDispose || OCellValueBinding_Base::rBHelper.bDisposed ) + throw DisposedException(); + // TODO: is it worth having an error message here? + } + + void OCellValueBinding::checkInitialized() + { + if ( !m_bInitialized ) + throw NotInitializedException("CellValueBinding is not initialized", static_cast<cppu::OWeakObject*>(this)); + } + + void OCellValueBinding::checkValueType( const Type& _rType ) const + { + OCellValueBinding* pNonConstThis = const_cast< OCellValueBinding* >( this ); + if ( !pNonConstThis->supportsType( _rType ) ) + { + OUString sMessage = "The given type (" + + _rType.getTypeName() + + ") is not supported by this binding."; + // TODO: localize this error message + + throw IncompatibleTypesException( sMessage, *pNonConstThis ); + // TODO: alternatively use a type converter service for this? + } + } + + OUString SAL_CALL OCellValueBinding::getImplementationName( ) + { + return "com.sun.star.comp.sheet.OCellValueBinding"; + } + + sal_Bool SAL_CALL OCellValueBinding::supportsService( const OUString& _rServiceName ) + { + return cppu::supportsService(this, _rServiceName); + } + + Sequence< OUString > SAL_CALL OCellValueBinding::getSupportedServiceNames( ) + { + Sequence< OUString > aServices( m_bListPos ? 3 : 2 ); + auto pServices = aServices.getArray(); + pServices[ 0 ] = "com.sun.star.table.CellValueBinding"; + pServices[ 1 ] = "com.sun.star.form.binding.ValueBinding"; + if ( m_bListPos ) + pServices[ 2 ] = "com.sun.star.table.ListPositionCellBinding"; + return aServices; + } + + void SAL_CALL OCellValueBinding::addModifyListener( const Reference< XModifyListener >& _rxListener ) + { + if ( _rxListener.is() ) + m_aModifyListeners.addInterface( _rxListener ); + } + + void SAL_CALL OCellValueBinding::removeModifyListener( const Reference< XModifyListener >& _rxListener ) + { + if ( _rxListener.is() ) + m_aModifyListeners.removeInterface( _rxListener ); + } + + void OCellValueBinding::notifyModified() + { + EventObject aEvent; + aEvent.Source.set(*this); + + ::comphelper::OInterfaceIteratorHelper3 aIter( m_aModifyListeners ); + while ( aIter.hasMoreElements() ) + { + try + { + aIter.next()->modified( aEvent ); + } + catch( const RuntimeException& ) + { + // silent this + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "sc", "OCellValueBinding::notifyModified: caught a (non-runtime) exception!" ); + } + } + } + + void SAL_CALL OCellValueBinding::modified( const EventObject& /* aEvent */ ) + { + notifyModified(); + } + + void SAL_CALL OCellValueBinding::disposing( const EventObject& aEvent ) + { + 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 ) + { + if ( m_bInitialized ) + throw RuntimeException("CellValueBinding is already initialized", static_cast<cppu::OWeakObject*>(this)); + + // get the cell address + CellAddress aAddress; + bool bFoundAddress = false; + + for ( const Any& rArg : _rArguments ) + { + NamedValue aValue; + if ( rArg >>= aValue ) + { + if ( aValue.Name == "BoundCell" ) + { + if ( aValue.Value >>= aAddress ) + { + bFoundAddress = true; + break; + } + } + } + } + + if ( !bFoundAddress ) + throw RuntimeException("Cell not found", static_cast<cppu::OWeakObject*>(this)); + + // get the cell object + try + { + // first the sheets collection + Reference< XIndexAccess > xSheets; + if ( m_xDocument.is() ) + xSheets.set(m_xDocument->getSheets( ), css::uno::UNO_QUERY); + OSL_ENSURE( xSheets.is(), "OCellValueBinding::initialize: could not retrieve the sheets!" ); + + if ( xSheets.is() ) + { + // the concrete sheet + Reference< XCellRange > xSheet(xSheets->getByIndex( aAddress.Sheet ), UNO_QUERY); + OSL_ENSURE( 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 ); + OSL_ENSURE( xAddressAccess.is(), "OCellValueBinding::initialize: either NULL cell, or cell without address access!" ); + } + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "sc", "OCellValueBinding::initialize: caught an exception while retrieving the cell object!" ); + } + + if ( !m_xCell.is() ) + throw RuntimeException("Failed to retrieve cell object", static_cast<cppu::OWeakObject*>(this)); + + m_xCellText.set(m_xCell, css::uno::UNO_QUERY); + + 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 = true; + // TODO: place your code here + } + +} // namespace calc + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |