1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include "cellvaluebinding.hxx"
21 #include <rtl/math.hxx>
22 #include <com/sun/star/form/binding/IncompatibleTypesException.hpp>
23 #include <com/sun/star/lang/NotInitializedException.hpp>
24 #include <com/sun/star/text/XTextRange.hpp>
25 #include <com/sun/star/table/XCellRange.hpp>
26 #include <com/sun/star/sheet/FormulaResult.hpp>
27 #include <com/sun/star/sheet/XCellAddressable.hpp>
28 #include <com/sun/star/sheet/XCellRangeData.hpp>
29 #include <com/sun/star/sheet/XSpreadsheetDocument.hpp>
30 #include <com/sun/star/container/XIndexAccess.hpp>
31 #include <com/sun/star/beans/PropertyAttribute.hpp>
32 #include <com/sun/star/beans/NamedValue.hpp>
33 #include <com/sun/star/util/XNumberFormatsSupplier.hpp>
34 #include <com/sun/star/util/XNumberFormatTypes.hpp>
35 #include <com/sun/star/util/NumberFormat.hpp>
36 #include <cppuhelper/supportsservice.hxx>
37 #include <comphelper/types.hxx>
38 #include <comphelper/diagnose_ex.hxx>
43 #define PROP_HANDLE_BOUND_CELL 1
45 namespace lang
= css::lang
;
46 using namespace ::com::sun::star::uno
;
47 using namespace ::com::sun::star::lang
;
48 using namespace ::com::sun::star::table
;
49 using namespace ::com::sun::star::text
;
50 using namespace ::com::sun::star::sheet
;
51 using namespace ::com::sun::star::container
;
52 using namespace ::com::sun::star::beans
;
53 using namespace ::com::sun::star::util
;
54 using namespace ::com::sun::star::form::binding
;
56 OCellValueBinding::OCellValueBinding( const Reference
< XSpreadsheetDocument
>& _rxDocument
, bool _bListPos
)
57 :m_xDocument( _rxDocument
)
58 ,m_bInitialized( false )
59 ,m_bListPos( _bListPos
)
61 // register our property at the base class
62 registerPropertyNoMember(
64 PROP_HANDLE_BOUND_CELL
,
65 PropertyAttribute::BOUND
| PropertyAttribute::READONLY
,
66 cppu::UnoType
<CellAddress
>::get(),
67 css::uno::Any(CellAddress())
70 // TODO: implement a ReadOnly property as required by the service,
71 // which probably maps to the cell being locked
74 OCellValueBinding::~OCellValueBinding( )
78 acquire(); // prevent duplicate dtor
83 IMPLEMENT_FORWARD_XINTERFACE2( OCellValueBinding
, OCellValueBinding_Base
, OCellValueBinding_PBase
)
85 IMPLEMENT_FORWARD_XTYPEPROVIDER2( OCellValueBinding
, OCellValueBinding_Base
, OCellValueBinding_PBase
)
87 void OCellValueBinding::disposing( std::unique_lock
<std::mutex
>& rGuard
)
89 Reference
<XModifyBroadcaster
> xBroadcaster( m_xCell
, UNO_QUERY
);
90 if ( xBroadcaster
.is() )
92 xBroadcaster
->removeModifyListener( this );
95 WeakComponentImplHelperBase::disposing(rGuard
);
97 // TODO: clean up here whatever you need to clean up (e.g. deregister as XEventListener
101 Reference
< XPropertySetInfo
> SAL_CALL
OCellValueBinding::getPropertySetInfo( )
103 return createPropertySetInfo( getInfoHelper() ) ;
106 ::cppu::IPropertyArrayHelper
& OCellValueBinding::getInfoHelper()
108 return *OCellValueBinding_PABase::getArrayHelper();
111 ::cppu::IPropertyArrayHelper
* OCellValueBinding::createArrayHelper( ) const
113 Sequence
< Property
> aProps
;
114 describeProperties( aProps
);
115 return new ::cppu::OPropertyArrayHelper(aProps
);
118 void OCellValueBinding::getFastPropertyValue( std::unique_lock
<std::mutex
>& /*rGuard*/, Any
& _rValue
, sal_Int32 _nHandle
) const
120 OSL_ENSURE( _nHandle
== PROP_HANDLE_BOUND_CELL
, "OCellValueBinding::getFastPropertyValue: invalid handle!" );
121 // we only have this one property...
124 Reference
< XCellAddressable
> xCellAddress( m_xCell
, UNO_QUERY
);
125 if ( xCellAddress
.is() )
126 _rValue
<<= xCellAddress
->getCellAddress( );
129 Sequence
< Type
> SAL_CALL
OCellValueBinding::getSupportedValueTypes( )
131 std::unique_lock
<std::mutex
> aGuard(m_aMutex
);
132 throwIfDisposed(aGuard
);
134 return getSupportedValueTypes(aGuard
);
137 Sequence
< Type
> OCellValueBinding::getSupportedValueTypes( std::unique_lock
<std::mutex
>& /*rGuard*/ ) const
139 sal_Int32 nCount
= m_xCellText
.is() ? 3 : m_xCell
.is() ? 1 : 0;
143 Sequence
< Type
> aTypes( nCount
);
146 auto pTypes
= aTypes
.getArray();
148 // an XCell can be used to set/get "double" values
149 pTypes
[0] = ::cppu::UnoType
<double>::get();
150 if ( m_xCellText
.is() )
152 // an XTextRange can be used to set/get "string" values
153 pTypes
[1] = ::cppu::UnoType
<OUString
>::get();
154 // and additionally, we use it to handle booleans
155 pTypes
[2] = ::cppu::UnoType
<sal_Bool
>::get();
158 // add sal_Int32 only if constructed as ListPositionCellBinding
160 pTypes
[nCount
-1] = cppu::UnoType
<sal_Int32
>::get();
166 sal_Bool SAL_CALL
OCellValueBinding::supportsType( const Type
& aType
)
168 std::unique_lock
<std::mutex
> aGuard(m_aMutex
);
169 throwIfDisposed(aGuard
);
171 return supportsType(aGuard
, aType
);
174 bool OCellValueBinding::supportsType( std::unique_lock
<std::mutex
>& rGuard
, const Type
& aType
) const
176 // look up in our sequence
177 const Sequence
< Type
> aSupportedTypes( getSupportedValueTypes(rGuard
) );
178 for ( auto const & i
: aSupportedTypes
)
185 Any SAL_CALL
OCellValueBinding::getValue( const Type
& aType
)
187 std::unique_lock
<std::mutex
> aGuard(m_aMutex
);
188 throwIfDisposed(aGuard
);
190 checkValueType( aGuard
, aType
);
193 switch ( aType
.getTypeClass() )
195 case TypeClass_STRING
:
196 OSL_ENSURE( m_xCellText
.is(), "OCellValueBinding::getValue: don't have a text!" );
197 if ( m_xCellText
.is() )
198 aReturn
<<= m_xCellText
->getString();
200 aReturn
<<= OUString();
203 case TypeClass_BOOLEAN
:
204 OSL_ENSURE( m_xCell
.is(), "OCellValueBinding::getValue: don't have a double value supplier!" );
207 // check if the cell has a numeric value (this might go into a helper function):
209 bool bHasValue
= false;
210 CellContentType eCellType
= m_xCell
->getType();
211 if ( eCellType
== CellContentType_VALUE
)
213 else if ( eCellType
== CellContentType_FORMULA
)
215 // check if the formula result is a value
216 if ( m_xCell
->getError() == 0 )
218 Reference
<XPropertySet
> xProp( m_xCell
, UNO_QUERY
);
221 sal_Int32 nResultType
;
222 if ( (xProp
->getPropertyValue(u
"FormulaResultType2"_ustr
) >>= nResultType
)
223 && nResultType
== FormulaResult::VALUE
)
231 // 0 is "unchecked", any other value is "checked", regardless of number format
232 double nCellValue
= m_xCell
->getValue();
233 bool bBoolValue
= ( nCellValue
!= 0.0 );
234 aReturn
<<= bBoolValue
;
236 // empty cells, text cells and text or error formula results: leave return value empty
240 case TypeClass_DOUBLE
:
241 OSL_ENSURE( m_xCell
.is(), "OCellValueBinding::getValue: don't have a double value supplier!" );
243 aReturn
<<= m_xCell
->getValue();
245 aReturn
<<= double(0);
249 OSL_ENSURE( m_xCell
.is(), "OCellValueBinding::getValue: don't have a double value supplier!" );
252 // The list position value in the cell is 1-based.
253 // We subtract 1 from any cell value (no special handling for 0 or negative values).
255 sal_Int32 nValue
= static_cast<sal_Int32
>(rtl::math::approxFloor( m_xCell
->getValue() ));
261 aReturn
<<= sal_Int32(0);
265 OSL_FAIL( "OCellValueBinding::getValue: unreachable code!" );
266 // a type other than double and string should never have survived the checkValueType
272 void SAL_CALL
OCellValueBinding::setValue( const Any
& aValue
)
274 std::unique_lock
<std::mutex
> aGuard(m_aMutex
);
275 throwIfDisposed(aGuard
);
277 if ( aValue
.hasValue() )
278 checkValueType( aGuard
, aValue
.getValueType() );
280 switch ( aValue
.getValueTypeClass() )
282 case TypeClass_STRING
:
284 OSL_ENSURE( m_xCellText
.is(), "OCellValueBinding::setValue: don't have a text!" );
288 if ( m_xCellText
.is() )
290 // might call back into us via modified(EventObject&)
292 m_xCellText
->setString( sText
);
298 case TypeClass_BOOLEAN
:
300 OSL_ENSURE( m_xCell
.is(), "OCellValueBinding::setValue: don't have a double value supplier!" );
302 // boolean is stored as values 0 or 1
303 // TODO: set the number format to boolean if no format is set?
305 bool bValue( false );
307 double nCellValue
= bValue
? 1.0 : 0.0;
311 // might call back into us via modified(EventObject&)
313 m_xCell
->setValue( nCellValue
);
321 case TypeClass_DOUBLE
:
323 OSL_ENSURE( m_xCell
.is(), "OCellValueBinding::setValue: don't have a double value supplier!" );
329 // might call back into us via modified(EventObject&)
331 m_xCell
->setValue( nValue
);
339 OSL_ENSURE( m_xCell
.is(), "OCellValueBinding::setValue: don't have a double value supplier!" );
341 sal_Int32 nValue
= 0;
342 aValue
>>= nValue
; // list index from control layer (0-based)
343 ++nValue
; // the list position value in the cell is 1-based
346 // might call back into us via modified(EventObject&)
348 m_xCell
->setValue( nValue
);
356 // #N/A error value can only be set using XCellRangeData
358 Reference
<XCellRangeData
> xData( m_xCell
, UNO_QUERY
);
359 OSL_ENSURE( xData
.is(), "OCellValueBinding::setValue: don't have XCellRangeData!" );
362 Sequence
<Any
> aInner(1); // one empty element
363 Sequence
< Sequence
<Any
> > aOuter( &aInner
, 1 ); // one row
364 // might call back into us via modified(EventObject&)
366 xData
->setDataArray( aOuter
);
373 OSL_FAIL( "OCellValueBinding::setValue: unreachable code!" );
374 // a type other than double and string should never have survived the checkValueType
379 void OCellValueBinding::setBooleanFormat()
381 // set boolean number format if not already set
383 OUString
sPropName( u
"NumberFormat"_ustr
);
384 Reference
<XPropertySet
> xCellProp( m_xCell
, UNO_QUERY
);
385 Reference
<XNumberFormatsSupplier
> xSupplier( m_xDocument
, UNO_QUERY
);
386 if ( !(xSupplier
.is() && xCellProp
.is()) )
389 Reference
<XNumberFormats
> xFormats(xSupplier
->getNumberFormats());
390 Reference
<XNumberFormatTypes
> xTypes( xFormats
, UNO_QUERY
);
394 lang::Locale aLocale
;
395 bool bWasBoolean
= false;
397 sal_Int32 nOldIndex
= ::comphelper::getINT32( xCellProp
->getPropertyValue( sPropName
) );
398 Reference
<XPropertySet
> xOldFormat
;
401 xOldFormat
.set(xFormats
->getByKey( nOldIndex
));
405 // non-existing format - can happen, use defaults
407 if ( xOldFormat
.is() )
409 // use the locale of the existing format
410 xOldFormat
->getPropertyValue(u
"Locale"_ustr
) >>= aLocale
;
412 sal_Int16 nOldType
= ::comphelper::getINT16(
413 xOldFormat
->getPropertyValue(u
"Type"_ustr
) );
414 if ( nOldType
& NumberFormat::LOGICAL
)
420 sal_Int32 nNewIndex
= xTypes
->getStandardFormat( NumberFormat::LOGICAL
, aLocale
);
421 xCellProp
->setPropertyValue( sPropName
, Any( nNewIndex
) );
425 void OCellValueBinding::checkInitialized()
427 if ( !m_bInitialized
)
428 throw NotInitializedException(u
"CellValueBinding is not initialized"_ustr
, getXWeak());
431 void OCellValueBinding::checkValueType( std::unique_lock
<std::mutex
>& rGuard
, const Type
& _rType
) const
433 if ( !supportsType( rGuard
, _rType
) )
435 OUString sMessage
= "The given type (" +
436 _rType
.getTypeName() +
437 ") is not supported by this binding.";
438 // TODO: localize this error message
440 throw IncompatibleTypesException( sMessage
, const_cast<OCellValueBinding
&>(*this) );
441 // TODO: alternatively use a type converter service for this?
445 OUString SAL_CALL
OCellValueBinding::getImplementationName( )
447 return u
"com.sun.star.comp.sheet.OCellValueBinding"_ustr
;
450 sal_Bool SAL_CALL
OCellValueBinding::supportsService( const OUString
& _rServiceName
)
452 return cppu::supportsService(this, _rServiceName
);
455 Sequence
< OUString
> SAL_CALL
OCellValueBinding::getSupportedServiceNames( )
457 Sequence
< OUString
> aServices( m_bListPos
? 3 : 2 );
458 auto pServices
= aServices
.getArray();
459 pServices
[ 0 ] = "com.sun.star.table.CellValueBinding";
460 pServices
[ 1 ] = "com.sun.star.form.binding.ValueBinding";
462 pServices
[ 2 ] = "com.sun.star.table.ListPositionCellBinding";
466 void SAL_CALL
OCellValueBinding::addModifyListener( const Reference
< XModifyListener
>& _rxListener
)
468 if ( _rxListener
.is() )
470 std::unique_lock
<std::mutex
> aGuard(m_aMutex
);
471 m_aModifyListeners
.addInterface( aGuard
, _rxListener
);
475 void SAL_CALL
OCellValueBinding::removeModifyListener( const Reference
< XModifyListener
>& _rxListener
)
477 if ( _rxListener
.is() )
479 std::unique_lock
<std::mutex
> aGuard(m_aMutex
);
480 m_aModifyListeners
.removeInterface( aGuard
, _rxListener
);
484 void OCellValueBinding::notifyModified()
487 aEvent
.Source
.set(*this);
489 std::unique_lock
<std::mutex
> aGuard(m_aMutex
);
490 m_aModifyListeners
.forEach(aGuard
,
491 [&aEvent
] (const css::uno::Reference
<css::util::XModifyListener
> & l
)
495 l
->modified( aEvent
);
497 catch( const RuntimeException
& )
501 catch( const Exception
& )
503 TOOLS_WARN_EXCEPTION( "sc", "OCellValueBinding::notifyModified: caught a (non-runtime) exception!" );
508 void SAL_CALL
OCellValueBinding::modified( const EventObject
& /* aEvent */ )
513 void SAL_CALL
OCellValueBinding::disposing( const EventObject
& aEvent
)
515 Reference
<XInterface
> xCellInt( m_xCell
, UNO_QUERY
);
516 if ( xCellInt
== aEvent
.Source
)
518 // release references to cell object
524 void SAL_CALL
OCellValueBinding::initialize( const Sequence
< Any
>& _rArguments
)
526 if ( m_bInitialized
)
527 throw RuntimeException(u
"CellValueBinding is already initialized"_ustr
, getXWeak());
529 // get the cell address
530 CellAddress aAddress
;
531 bool bFoundAddress
= false;
533 for ( const Any
& rArg
: _rArguments
)
536 if ( rArg
>>= aValue
)
538 if ( aValue
.Name
== "BoundCell" )
540 if ( aValue
.Value
>>= aAddress
)
542 bFoundAddress
= true;
549 if ( !bFoundAddress
)
550 throw RuntimeException(u
"Cell not found"_ustr
, getXWeak());
552 // get the cell object
555 // first the sheets collection
556 Reference
< XIndexAccess
> xSheets
;
557 if ( m_xDocument
.is() )
558 xSheets
.set(m_xDocument
->getSheets( ), css::uno::UNO_QUERY
);
559 OSL_ENSURE( xSheets
.is(), "OCellValueBinding::initialize: could not retrieve the sheets!" );
563 // the concrete sheet
564 Reference
< XCellRange
> xSheet(xSheets
->getByIndex( aAddress
.Sheet
), UNO_QUERY
);
565 OSL_ENSURE( xSheet
.is(), "OCellValueBinding::initialize: NULL sheet, but no exception!" );
570 m_xCell
.set(xSheet
->getCellByPosition( aAddress
.Column
, aAddress
.Row
));
571 Reference
< XCellAddressable
> xAddressAccess( m_xCell
, UNO_QUERY
);
572 OSL_ENSURE( xAddressAccess
.is(), "OCellValueBinding::initialize: either NULL cell, or cell without address access!" );
576 catch( const Exception
& )
578 TOOLS_WARN_EXCEPTION( "sc", "OCellValueBinding::initialize: caught an exception while retrieving the cell object!" );
582 throw RuntimeException(u
"Failed to retrieve cell object"_ustr
, getXWeak());
584 m_xCellText
.set(m_xCell
, css::uno::UNO_QUERY
);
586 Reference
<XModifyBroadcaster
> xBroadcaster( m_xCell
, UNO_QUERY
);
587 if ( xBroadcaster
.is() )
589 xBroadcaster
->addModifyListener( this );
592 // TODO: add as XEventListener to the cell, so we get notified when it dies,
593 // and can dispose ourself then
595 // TODO: somehow add as listener so we get notified when the address of the cell changes
596 // We need to forward this as change in our BoundCell property to our property change listeners
598 // TODO: be an XModifyBroadcaster, so that changes in our cell can be notified
599 // to the BindableValue which is/will be bound to this instance.
601 m_bInitialized
= true;
602 // TODO: place your code here
607 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */