LibreOffice Module sc (master)  1
cellvaluebinding.cxx
Go to the documentation of this file.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
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>
37 #include <comphelper/types.hxx>
38 #include <tools/diagnose_ex.h>
39 
40 namespace calc
41 {
42 
43 #define PROP_HANDLE_BOUND_CELL 1
44 
45  namespace lang = ::com::sun::star::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;
55 
56  OCellValueBinding::OCellValueBinding( const Reference< XSpreadsheetDocument >& _rxDocument, bool _bListPos )
59  ,m_xDocument( _rxDocument )
60  ,m_aModifyListeners( m_aMutex )
61  ,m_bInitialized( false )
62  ,m_bListPos( _bListPos )
63  {
64  // register our property at the base class
65  registerPropertyNoMember(
66  "BoundCell",
68  PropertyAttribute::BOUND | PropertyAttribute::READONLY,
70  css::uno::Any(CellAddress())
71  );
72 
73  // TODO: implement a ReadOnly property as required by the service,
74  // which probably maps to the cell being locked
75  }
76 
77  OCellValueBinding::~OCellValueBinding( )
78  {
79  if ( !OCellValueBinding_Base::rBHelper.bDisposed )
80  {
81  acquire(); // prevent duplicate dtor
82  dispose();
83  }
84  }
85 
87 
88  IMPLEMENT_FORWARD_XTYPEPROVIDER2( OCellValueBinding, OCellValueBinding_Base, OCellValueBinding_PBase )
89 
90  void SAL_CALL OCellValueBinding::disposing()
91  {
92  Reference<XModifyBroadcaster> xBroadcaster( m_xCell, UNO_QUERY );
93  if ( xBroadcaster.is() )
94  {
95  xBroadcaster->removeModifyListener( this );
96  }
97 
98  WeakAggComponentImplHelperBase::disposing();
99 
100  // TODO: clean up here whatever you need to clean up (e.g. deregister as XEventListener
101  // for the cell)
102  }
103 
104  Reference< XPropertySetInfo > SAL_CALL OCellValueBinding::getPropertySetInfo( )
105  {
106  return createPropertySetInfo( getInfoHelper() ) ;
107  }
108 
109  ::cppu::IPropertyArrayHelper& SAL_CALL OCellValueBinding::getInfoHelper()
110  {
111  return *OCellValueBinding_PABase::getArrayHelper();
112  }
113 
114  ::cppu::IPropertyArrayHelper* OCellValueBinding::createArrayHelper( ) const
115  {
116  Sequence< Property > aProps;
117  describeProperties( aProps );
118  return new ::cppu::OPropertyArrayHelper(aProps);
119  }
120 
121  void SAL_CALL OCellValueBinding::getFastPropertyValue( Any& _rValue, sal_Int32 _nHandle ) const
122  {
123  OSL_ENSURE( _nHandle == PROP_HANDLE_BOUND_CELL, "OCellValueBinding::getFastPropertyValue: invalid handle!" );
124  // we only have this one property...
125 
126  _rValue.clear();
127  Reference< XCellAddressable > xCellAddress( m_xCell, UNO_QUERY );
128  if ( xCellAddress.is() )
129  _rValue <<= xCellAddress->getCellAddress( );
130  }
131 
132  Sequence< Type > SAL_CALL OCellValueBinding::getSupportedValueTypes( )
133  {
134  checkDisposed( );
135  checkInitialized( );
136 
137  sal_Int32 nCount = m_xCellText.is() ? 3 : m_xCell.is() ? 1 : 0;
138  if ( m_bListPos )
139  ++nCount;
140 
141  Sequence< Type > aTypes( nCount );
142  if ( m_xCell.is() )
143  {
144  auto pTypes = aTypes.getArray();
145 
146  // an XCell can be used to set/get "double" values
147  pTypes[0] = ::cppu::UnoType<double>::get();
148  if ( m_xCellText.is() )
149  {
150  // an XTextRange can be used to set/get "string" values
151  pTypes[1] = ::cppu::UnoType<OUString>::get();
152  // and additionally, we use it to handle booleans
153  pTypes[2] = ::cppu::UnoType<sal_Bool>::get();
154  }
155 
156  // add sal_Int32 only if constructed as ListPositionCellBinding
157  if ( m_bListPos )
158  pTypes[nCount-1] = cppu::UnoType<sal_Int32>::get();
159  }
160 
161  return aTypes;
162  }
163 
164  sal_Bool SAL_CALL OCellValueBinding::supportsType( const Type& aType )
165  {
166  checkDisposed( );
167  checkInitialized( );
168 
169  // look up in our sequence
170  const Sequence< Type > aSupportedTypes( getSupportedValueTypes() );
171  for ( auto const & i : aSupportedTypes )
172  if ( aType == i )
173  return true;
174 
175  return false;
176  }
177 
178  Any SAL_CALL OCellValueBinding::getValue( const Type& aType )
179  {
180  checkDisposed( );
181  checkInitialized( );
182  checkValueType( aType );
183 
184  Any aReturn;
185  switch ( aType.getTypeClass() )
186  {
187  case TypeClass_STRING:
188  OSL_ENSURE( m_xCellText.is(), "OCellValueBinding::getValue: don't have a text!" );
189  if ( m_xCellText.is() )
190  aReturn <<= m_xCellText->getString();
191  else
192  aReturn <<= OUString();
193  break;
194 
195  case TypeClass_BOOLEAN:
196  OSL_ENSURE( m_xCell.is(), "OCellValueBinding::getValue: don't have a double value supplier!" );
197  if ( m_xCell.is() )
198  {
199  // check if the cell has a numeric value (this might go into a helper function):
200 
201  bool bHasValue = false;
202  CellContentType eCellType = m_xCell->getType();
203  if ( eCellType == CellContentType_VALUE )
204  bHasValue = true;
205  else if ( eCellType == CellContentType_FORMULA )
206  {
207  // check if the formula result is a value
208  if ( m_xCell->getError() == 0 )
209  {
210  Reference<XPropertySet> xProp( m_xCell, UNO_QUERY );
211  if ( xProp.is() )
212  {
213  sal_Int32 nResultType;
214  if ( (xProp->getPropertyValue("FormulaResultType2") >>= nResultType)
215  && nResultType == FormulaResult::VALUE )
216  bHasValue = true;
217  }
218  }
219  }
220 
221  if ( bHasValue )
222  {
223  // 0 is "unchecked", any other value is "checked", regardless of number format
224  double nCellValue = m_xCell->getValue();
225  bool bBoolValue = ( nCellValue != 0.0 );
226  aReturn <<= bBoolValue;
227  }
228  // empty cells, text cells and text or error formula results: leave return value empty
229  }
230  break;
231 
232  case TypeClass_DOUBLE:
233  OSL_ENSURE( m_xCell.is(), "OCellValueBinding::getValue: don't have a double value supplier!" );
234  if ( m_xCell.is() )
235  aReturn <<= m_xCell->getValue();
236  else
237  aReturn <<= double(0);
238  break;
239 
240  case TypeClass_LONG:
241  OSL_ENSURE( m_xCell.is(), "OCellValueBinding::getValue: don't have a double value supplier!" );
242  if ( m_xCell.is() )
243  {
244  // The list position value in the cell is 1-based.
245  // We subtract 1 from any cell value (no special handling for 0 or negative values).
246 
247  sal_Int32 nValue = static_cast<sal_Int32>(rtl::math::approxFloor( m_xCell->getValue() ));
248  --nValue;
249 
250  aReturn <<= nValue;
251  }
252  else
253  aReturn <<= sal_Int32(0);
254  break;
255 
256  default:
257  OSL_FAIL( "OCellValueBinding::getValue: unreachable code!" );
258  // a type other than double and string should never have survived the checkValueType
259  // above
260  }
261  return aReturn;
262  }
263 
264  void SAL_CALL OCellValueBinding::setValue( const Any& aValue )
265  {
266  checkDisposed( );
267  checkInitialized( );
268  if ( aValue.hasValue() )
269  checkValueType( aValue.getValueType() );
270 
271  switch ( aValue.getValueType().getTypeClass() )
272  {
273  case TypeClass_STRING:
274  {
275  OSL_ENSURE( m_xCellText.is(), "OCellValueBinding::setValue: don't have a text!" );
276 
277  OUString sText;
278  aValue >>= sText;
279  if ( m_xCellText.is() )
280  m_xCellText->setString( sText );
281  }
282  break;
283 
284  case TypeClass_BOOLEAN:
285  {
286  OSL_ENSURE( m_xCell.is(), "OCellValueBinding::setValue: don't have a double value supplier!" );
287 
288  // boolean is stored as values 0 or 1
289  // TODO: set the number format to boolean if no format is set?
290 
291  bool bValue( false );
292  aValue >>= bValue;
293  double nCellValue = bValue ? 1.0 : 0.0;
294 
295  if ( m_xCell.is() )
296  m_xCell->setValue( nCellValue );
297 
298  setBooleanFormat();
299  }
300  break;
301 
302  case TypeClass_DOUBLE:
303  {
304  OSL_ENSURE( m_xCell.is(), "OCellValueBinding::setValue: don't have a double value supplier!" );
305 
306  double nValue = 0;
307  aValue >>= nValue;
308  if ( m_xCell.is() )
309  m_xCell->setValue( nValue );
310  }
311  break;
312 
313  case TypeClass_LONG:
314  {
315  OSL_ENSURE( m_xCell.is(), "OCellValueBinding::setValue: don't have a double value supplier!" );
316 
317  sal_Int32 nValue = 0;
318  aValue >>= nValue; // list index from control layer (0-based)
319  ++nValue; // the list position value in the cell is 1-based
320  if ( m_xCell.is() )
321  m_xCell->setValue( nValue );
322  }
323  break;
324 
325  case TypeClass_VOID:
326  {
327  // #N/A error value can only be set using XCellRangeData
328 
329  Reference<XCellRangeData> xData( m_xCell, UNO_QUERY );
330  OSL_ENSURE( xData.is(), "OCellValueBinding::setValue: don't have XCellRangeData!" );
331  if ( xData.is() )
332  {
333  Sequence<Any> aInner(1); // one empty element
334  Sequence< Sequence<Any> > aOuter( &aInner, 1 ); // one row
335  xData->setDataArray( aOuter );
336  }
337  }
338  break;
339 
340  default:
341  OSL_FAIL( "OCellValueBinding::setValue: unreachable code!" );
342  // a type other than double and string should never have survived the checkValueType
343  // above
344  }
345  }
346 
347  void OCellValueBinding::setBooleanFormat()
348  {
349  // set boolean number format if not already set
350 
351  OUString sPropName( "NumberFormat" );
352  Reference<XPropertySet> xCellProp( m_xCell, UNO_QUERY );
353  Reference<XNumberFormatsSupplier> xSupplier( m_xDocument, UNO_QUERY );
354  if ( !(xSupplier.is() && xCellProp.is()) )
355  return;
356 
357  Reference<XNumberFormats> xFormats(xSupplier->getNumberFormats());
358  Reference<XNumberFormatTypes> xTypes( xFormats, UNO_QUERY );
359  if ( !xTypes.is() )
360  return;
361 
362  lang::Locale aLocale;
363  bool bWasBoolean = false;
364 
365  sal_Int32 nOldIndex = ::comphelper::getINT32( xCellProp->getPropertyValue( sPropName ) );
366  Reference<XPropertySet> xOldFormat;
367  try
368  {
369  xOldFormat.set(xFormats->getByKey( nOldIndex ));
370  }
371  catch ( Exception& )
372  {
373  // non-existing format - can happen, use defaults
374  }
375  if ( xOldFormat.is() )
376  {
377  // use the locale of the existing format
378  xOldFormat->getPropertyValue("Locale") >>= aLocale;
379 
380  sal_Int16 nOldType = ::comphelper::getINT16(
381  xOldFormat->getPropertyValue("Type") );
382  if ( nOldType & NumberFormat::LOGICAL )
383  bWasBoolean = true;
384  }
385 
386  if ( !bWasBoolean )
387  {
388  sal_Int32 nNewIndex = xTypes->getStandardFormat( NumberFormat::LOGICAL, aLocale );
389  xCellProp->setPropertyValue( sPropName, Any( nNewIndex ) );
390  }
391  }
392 
393  void OCellValueBinding::checkDisposed( ) const
394  {
395  if ( OCellValueBinding_Base::rBHelper.bInDispose || OCellValueBinding_Base::rBHelper.bDisposed )
396  throw DisposedException();
397  // TODO: is it worth having an error message here?
398  }
399 
400  void OCellValueBinding::checkInitialized()
401  {
402  if ( !m_bInitialized )
403  throw NotInitializedException("CellValueBinding is not initialized", static_cast<cppu::OWeakObject*>(this));
404  }
405 
406  void OCellValueBinding::checkValueType( const Type& _rType ) const
407  {
408  OCellValueBinding* pNonConstThis = const_cast< OCellValueBinding* >( this );
409  if ( !pNonConstThis->supportsType( _rType ) )
410  {
411  OUString sMessage = "The given type (" +
412  _rType.getTypeName() +
413  ") is not supported by this binding.";
414  // TODO: localize this error message
415 
416  throw IncompatibleTypesException( sMessage, *pNonConstThis );
417  // TODO: alternatively use a type converter service for this?
418  }
419  }
420 
421  OUString SAL_CALL OCellValueBinding::getImplementationName( )
422  {
423  return "com.sun.star.comp.sheet.OCellValueBinding";
424  }
425 
426  sal_Bool SAL_CALL OCellValueBinding::supportsService( const OUString& _rServiceName )
427  {
428  return cppu::supportsService(this, _rServiceName);
429  }
430 
431  Sequence< OUString > SAL_CALL OCellValueBinding::getSupportedServiceNames( )
432  {
433  Sequence< OUString > aServices( m_bListPos ? 3 : 2 );
434  auto pServices = aServices.getArray();
435  pServices[ 0 ] = "com.sun.star.table.CellValueBinding";
436  pServices[ 1 ] = "com.sun.star.form.binding.ValueBinding";
437  if ( m_bListPos )
438  pServices[ 2 ] = "com.sun.star.table.ListPositionCellBinding";
439  return aServices;
440  }
441 
442  void SAL_CALL OCellValueBinding::addModifyListener( const Reference< XModifyListener >& _rxListener )
443  {
444  if ( _rxListener.is() )
445  m_aModifyListeners.addInterface( _rxListener );
446  }
447 
448  void SAL_CALL OCellValueBinding::removeModifyListener( const Reference< XModifyListener >& _rxListener )
449  {
450  if ( _rxListener.is() )
451  m_aModifyListeners.removeInterface( _rxListener );
452  }
453 
454  void OCellValueBinding::notifyModified()
455  {
456  EventObject aEvent;
457  aEvent.Source.set(*this);
458 
459  ::comphelper::OInterfaceIteratorHelper3 aIter( m_aModifyListeners );
460  while ( aIter.hasMoreElements() )
461  {
462  try
463  {
464  aIter.next()->modified( aEvent );
465  }
466  catch( const RuntimeException& )
467  {
468  // silent this
469  }
470  catch( const Exception& )
471  {
472  TOOLS_WARN_EXCEPTION( "sc", "OCellValueBinding::notifyModified: caught a (non-runtime) exception!" );
473  }
474  }
475  }
476 
477  void SAL_CALL OCellValueBinding::modified( const EventObject& /* aEvent */ )
478  {
479  notifyModified();
480  }
481 
482  void SAL_CALL OCellValueBinding::disposing( const EventObject& aEvent )
483  {
484  Reference<XInterface> xCellInt( m_xCell, UNO_QUERY );
485  if ( xCellInt == aEvent.Source )
486  {
487  // release references to cell object
488  m_xCell.clear();
489  m_xCellText.clear();
490  }
491  }
492 
493  void SAL_CALL OCellValueBinding::initialize( const Sequence< Any >& _rArguments )
494  {
495  if ( m_bInitialized )
496  throw RuntimeException("CellValueBinding is already initialized", static_cast<cppu::OWeakObject*>(this));
497 
498  // get the cell address
499  CellAddress aAddress;
500  bool bFoundAddress = false;
501 
502  for ( const Any& rArg : _rArguments )
503  {
504  NamedValue aValue;
505  if ( rArg >>= aValue )
506  {
507  if ( aValue.Name == "BoundCell" )
508  {
509  if ( aValue.Value >>= aAddress )
510  {
511  bFoundAddress = true;
512  break;
513  }
514  }
515  }
516  }
517 
518  if ( !bFoundAddress )
519  throw RuntimeException("Cell not found", static_cast<cppu::OWeakObject*>(this));
520 
521  // get the cell object
522  try
523  {
524  // first the sheets collection
525  Reference< XIndexAccess > xSheets;
526  if ( m_xDocument.is() )
527  xSheets.set(m_xDocument->getSheets( ), css::uno::UNO_QUERY);
528  OSL_ENSURE( xSheets.is(), "OCellValueBinding::initialize: could not retrieve the sheets!" );
529 
530  if ( xSheets.is() )
531  {
532  // the concrete sheet
533  Reference< XCellRange > xSheet(xSheets->getByIndex( aAddress.Sheet ), UNO_QUERY);
534  OSL_ENSURE( xSheet.is(), "OCellValueBinding::initialize: NULL sheet, but no exception!" );
535 
536  // the concrete cell
537  if ( xSheet.is() )
538  {
539  m_xCell.set(xSheet->getCellByPosition( aAddress.Column, aAddress.Row ));
540  Reference< XCellAddressable > xAddressAccess( m_xCell, UNO_QUERY );
541  OSL_ENSURE( xAddressAccess.is(), "OCellValueBinding::initialize: either NULL cell, or cell without address access!" );
542  }
543  }
544  }
545  catch( const Exception& )
546  {
547  TOOLS_WARN_EXCEPTION( "sc", "OCellValueBinding::initialize: caught an exception while retrieving the cell object!" );
548  }
549 
550  if ( !m_xCell.is() )
551  throw RuntimeException("Failed to retrieve cell object", static_cast<cppu::OWeakObject*>(this));
552 
553  m_xCellText.set(m_xCell, css::uno::UNO_QUERY);
554 
555  Reference<XModifyBroadcaster> xBroadcaster( m_xCell, UNO_QUERY );
556  if ( xBroadcaster.is() )
557  {
558  xBroadcaster->addModifyListener( this );
559  }
560 
561  // TODO: add as XEventListener to the cell, so we get notified when it dies,
562  // and can dispose ourself then
563 
564  // TODO: somehow add as listener so we get notified when the address of the cell changes
565  // We need to forward this as change in our BoundCell property to our property change listeners
566 
567  // TODO: be an XModifyBroadcaster, so that changes in our cell can be notified
568  // to the BindableValue which is/will be bound to this instance.
569 
570  m_bInitialized = true;
571  // TODO: place your code here
572  }
573 
574 } // namespace calc
575 
576 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
IMPLEMENT_FORWARD_XINTERFACE2(ChildWindowPane, ChildWindowPaneInterfaceBase, Pane)
Type
::cppu::WeakAggComponentImplHelper5< css::form::binding::XValueBinding, css::lang::XServiceInfo, css::util::XModifyBroadcaster, css::util::XModifyListener, css::lang::XInitialization > OCellValueBinding_Base
OUString sMessage
#define PROP_HANDLE_BOUND_CELL
Reference< XOfficeDatabaseDocument > m_xDocument
int nCount
bool CPPUHELPER_DLLPUBLIC supportsService(css::lang::XServiceInfo *implementation, rtl::OUString const &name)
std::mutex m_aMutex
#define TOOLS_WARN_EXCEPTION(area, stream)
OCellValueBinding(const css::uno::Reference< css::sheet::XSpreadsheetDocument > &_rxDocument, bool _bListPos)
constructed as ListPositionCellBinding?
int i
void checkDisposed(bool _bThrow)
const Supported_NumberingType aSupportedTypes[]
css::uno::Reference< ListenerT > const & next()
unsigned char sal_Bool
css::uno::Type const & get()
const SvXMLTokenMapEntry aTypes[]
::comphelper::OPropertyContainer OCellValueBinding_PBase
void dispose()
virtual sal_Bool SAL_CALL supportsType(const css::uno::Type &aType) override
AnyEventRef aEvent
sal_Int16 nValue
bool m_bDetectedRangeSegmentation false