/* -*- 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/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/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>
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() )
{
// an XCell can be used to set/get "double" values
aTypes[0] = ::cppu::UnoType<double>::get();
if ( m_xCellText.is() )
{
// an XTextRange can be used to set/get "string" values
aTypes[1] = ::cppu::UnoType<OUString>::get();
// and additionally, we use it to handle booleans
aTypes[2] = ::cppu::UnoType<sal_Bool>::get();
}
// add sal_Int32 only if constructed as ListPositionCellBinding
if ( m_bListPos )
aTypes[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
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() )
{
Reference<XNumberFormats> xFormats(xSupplier->getNumberFormats());
Reference<XNumberFormatTypes> xTypes( xFormats, UNO_QUERY );
if ( xTypes.is() )
{
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, makeAny( 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 (" );
sMessage += _rType.getTypeName();
sMessage += ") 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 OUString( "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 );
aServices[ 0 ] = "com.sun.star.table.CellValueBinding";
aServices[ 1 ] = "com.sun.star.form.binding.ValueBinding";
if ( m_bListPos )
aServices[ 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::OInterfaceIteratorHelper2 aIter( m_aModifyListeners );
while ( aIter.hasMoreElements() )
{
try
{
static_cast< XModifyListener* >( aIter.next() )->modified( aEvent );
}
catch( const RuntimeException& )
{
// silent this
}
catch( const Exception& )
{
OSL_FAIL( "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;
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 == "BoundCell" )
{
if ( aValue.Value >>= aAddress )
bFoundAddress = true;
}
}
}
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& )
{
OSL_FAIL( "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: */
↑ V614 Uninitialized variable 'nResultType' used.