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 "celllistsource.hxx"
21 #include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
22 #include <com/sun/star/lang/NotInitializedException.hpp>
23 #include <com/sun/star/lang/NullPointerException.hpp>
24 #include <com/sun/star/table/XCellRange.hpp>
25 #include <com/sun/star/text/XTextRange.hpp>
26 #include <com/sun/star/sheet/XCellRangeAddressable.hpp>
27 #include <com/sun/star/sheet/FormulaResult.hpp>
28 #include <com/sun/star/sheet/XSpreadsheetDocument.hpp>
29 #include <com/sun/star/util/XModifyBroadcaster.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 <cppuhelper/supportsservice.hxx>
34 #include <tools/diagnose_ex.h>
35 
36 namespace calc
37 {
38 
39 #define PROP_HANDLE_RANGE_ADDRESS  1
40 
41     using namespace ::com::sun::star::uno;
42     using namespace ::com::sun::star::lang;
43     using namespace ::com::sun::star::table;
44     using namespace ::com::sun::star::text;
45     using namespace ::com::sun::star::sheet;
46     using namespace ::com::sun::star::container;
47     using namespace ::com::sun::star::beans;
48     using namespace ::com::sun::star::util;
49     using namespace ::com::sun::star::form::binding;
50 
OCellListSource(const Reference<XSpreadsheetDocument> & _rxDocument)51     OCellListSource::OCellListSource( const Reference< XSpreadsheetDocument >& _rxDocument )
52         :OCellListSource_Base( m_aMutex )
53         ,OCellListSource_PBase( OCellListSource_Base::rBHelper )
54         ,m_xDocument( _rxDocument )
55         ,m_aListEntryListeners( m_aMutex )
56         ,m_bInitialized( false )
57     {
58         OSL_PRECOND( m_xDocument.is(), "OCellListSource::OCellListSource: invalid document!" );
59 
60         // register our property at the base class
61         registerPropertyNoMember(
62             "CellRange",
63             PROP_HANDLE_RANGE_ADDRESS,
64             PropertyAttribute::BOUND | PropertyAttribute::READONLY,
65             cppu::UnoType<CellRangeAddress>::get(),
66             css::uno::Any(CellRangeAddress())
67         );
68     }
69 
~OCellListSource()70     OCellListSource::~OCellListSource( )
71     {
72         if ( !OCellListSource_Base::rBHelper.bDisposed )
73         {
74             acquire();  // prevent duplicate dtor
75             dispose();
76         }
77     }
78 
IMPLEMENT_FORWARD_XINTERFACE2(OCellListSource,OCellListSource_Base,OCellListSource_PBase)79     IMPLEMENT_FORWARD_XINTERFACE2( OCellListSource, OCellListSource_Base, OCellListSource_PBase )
80 
81     IMPLEMENT_FORWARD_XTYPEPROVIDER2( OCellListSource, OCellListSource_Base, OCellListSource_PBase )
82 
83     void SAL_CALL OCellListSource::disposing()
84     {
85         ::osl::MutexGuard aGuard( m_aMutex );
86 
87         Reference<XModifyBroadcaster> xBroadcaster( m_xRange, UNO_QUERY );
88         if ( xBroadcaster.is() )
89         {
90             xBroadcaster->removeModifyListener( this );
91         }
92 
93         EventObject aDisposeEvent( *this );
94         m_aListEntryListeners.disposeAndClear( aDisposeEvent );
95 
96         WeakAggComponentImplHelperBase::disposing();
97 
98         // TODO: clean up here whatever you need to clean up (e.g. revoking listeners etc.)
99     }
100 
getPropertySetInfo()101     Reference< XPropertySetInfo > SAL_CALL OCellListSource::getPropertySetInfo(  )
102     {
103         return createPropertySetInfo( getInfoHelper() ) ;
104     }
105 
getInfoHelper()106     ::cppu::IPropertyArrayHelper& SAL_CALL OCellListSource::getInfoHelper()
107     {
108         return *OCellListSource_PABase::getArrayHelper();
109     }
110 
createArrayHelper() const111     ::cppu::IPropertyArrayHelper* OCellListSource::createArrayHelper( ) const
112     {
113         Sequence< Property > aProps;
114         describeProperties( aProps );
115         return new ::cppu::OPropertyArrayHelper(aProps);
116     }
117 
getFastPropertyValue(Any & _rValue,sal_Int32 _nHandle) const118     void SAL_CALL OCellListSource::getFastPropertyValue( Any& _rValue, sal_Int32 _nHandle ) const
119     {
120         OSL_ENSURE( _nHandle == PROP_HANDLE_RANGE_ADDRESS, "OCellListSource::getFastPropertyValue: invalid handle!" );
121             // we only have this one property...
122 
123         _rValue <<= getRangeAddress( );
124     }
125 
checkDisposed() const126     void OCellListSource::checkDisposed( ) const
127     {
128         if ( OCellListSource_Base::rBHelper.bInDispose || OCellListSource_Base::rBHelper.bDisposed )
129             throw DisposedException();
130             // TODO: is it worth having an error message here?
131     }
132 
checkInitialized()133     void OCellListSource::checkInitialized()
134     {
135         if ( !m_bInitialized )
136             throw NotInitializedException("CellListSource is not initialized", static_cast<cppu::OWeakObject*>(this));
137     }
138 
getImplementationName()139     OUString SAL_CALL OCellListSource::getImplementationName(  )
140     {
141         return "com.sun.star.comp.sheet.OCellListSource";
142     }
143 
supportsService(const OUString & _rServiceName)144     sal_Bool SAL_CALL OCellListSource::supportsService( const OUString& _rServiceName )
145     {
146         return cppu::supportsService(this, _rServiceName);
147     }
148 
getSupportedServiceNames()149     Sequence< OUString > SAL_CALL OCellListSource::getSupportedServiceNames(  )
150     {
151         return {"com.sun.star.table.CellRangeListSource",
152                 "com.sun.star.form.binding.ListEntrySource"};
153     }
154 
getRangeAddress() const155     CellRangeAddress OCellListSource::getRangeAddress( ) const
156     {
157         OSL_PRECOND( m_xRange.is(), "OCellListSource::getRangeAddress: invalid range!" );
158 
159         CellRangeAddress aAddress;
160         Reference< XCellRangeAddressable > xRangeAddress( m_xRange, UNO_QUERY );
161         if ( xRangeAddress.is() )
162             aAddress = xRangeAddress->getRangeAddress( );
163         return aAddress;
164     }
165 
getCellTextContent_noCheck(sal_Int32 _nRangeRelativeRow,css::uno::Any * pAny)166     OUString OCellListSource::getCellTextContent_noCheck( sal_Int32 _nRangeRelativeRow, css::uno::Any* pAny )
167     {
168         OUString sText;
169 
170         OSL_PRECOND( m_xRange.is(), "OCellListSource::getRangeAddress: invalid range!" );
171 
172         if (!m_xRange.is())
173             return sText;
174 
175         Reference< XCell > xCell( m_xRange->getCellByPosition( 0, _nRangeRelativeRow ));
176         if (!xCell.is())
177         {
178             if (pAny)
179                 *pAny <<= sText;
180             return sText;
181         }
182 
183         Reference< XTextRange > xCellText;
184         xCellText.set( xCell, UNO_QUERY);
185 
186         if (xCellText.is())
187             sText = xCellText->getString();     // formatted output string
188 
189         if (pAny)
190         {
191             switch (xCell->getType())
192             {
193                 case CellContentType_VALUE:
194                     *pAny <<= xCell->getValue();
195                 break;
196                 case CellContentType_TEXT:
197                     *pAny <<= sText;
198                 break;
199                 case CellContentType_FORMULA:
200                     if (xCell->getError())
201                         *pAny <<= sText;    // Err:... or #...!
202                     else
203                     {
204                         Reference< XPropertySet > xProp( xCell, UNO_QUERY);
205                         if (xProp.is())
206                         {
207                             sal_Int32 nResultType;
208                             if ((xProp->getPropertyValue("FormulaResultType2") >>= nResultType) &&
209                                     nResultType == FormulaResult::VALUE)
210                                 *pAny <<= xCell->getValue();
211                             else
212                                 *pAny <<= sText;
213                         }
214                     }
215                 break;
216                 case CellContentType_EMPTY:
217                     *pAny <<= OUString();
218                 break;
219                 default:
220                     ;   // nothing, if actually occurred it would result in #N/A being displayed if selected
221             }
222         }
223 
224         return sText;
225     }
226 
getListEntryCount()227     sal_Int32 SAL_CALL OCellListSource::getListEntryCount(  )
228     {
229         ::osl::MutexGuard aGuard( m_aMutex );
230         checkDisposed();
231         checkInitialized();
232 
233         CellRangeAddress aAddress( getRangeAddress( ) );
234         return aAddress.EndRow - aAddress.StartRow + 1;
235     }
236 
getListEntry(sal_Int32 _nPosition)237     OUString SAL_CALL OCellListSource::getListEntry( sal_Int32 _nPosition )
238     {
239         ::osl::MutexGuard aGuard( m_aMutex );
240         checkDisposed();
241         checkInitialized();
242 
243         if ( _nPosition >= getListEntryCount() )
244             throw IndexOutOfBoundsException();
245 
246         return getCellTextContent_noCheck( _nPosition, nullptr );
247     }
248 
getAllListEntries()249     Sequence< OUString > SAL_CALL OCellListSource::getAllListEntries(  )
250     {
251         ::osl::MutexGuard aGuard( m_aMutex );
252         checkDisposed();
253         checkInitialized();
254 
255         Sequence< OUString > aAllEntries( getListEntryCount() );
256         OUString* pAllEntries = aAllEntries.getArray();
257         for ( sal_Int32 i = 0; i < aAllEntries.getLength(); ++i )
258         {
259             *pAllEntries++ = getCellTextContent_noCheck( i, nullptr );
260         }
261 
262         return aAllEntries;
263     }
264 
getAllListEntriesTyped(Sequence<Any> & rDataValues)265     Sequence< OUString > SAL_CALL OCellListSource::getAllListEntriesTyped( Sequence< Any >& rDataValues )
266     {
267         ::osl::MutexGuard aGuard( m_aMutex );
268         checkDisposed();
269         checkInitialized();
270 
271         const sal_Int32 nCount = getListEntryCount();
272         Sequence< OUString > aAllEntries( nCount );
273         rDataValues = Sequence< Any >( nCount );
274         OUString* pAllEntries = aAllEntries.getArray();
275         Any* pDataValues = rDataValues.getArray();
276         for ( sal_Int32 i = 0; i < nCount; ++i )
277         {
278             *pAllEntries++ = getCellTextContent_noCheck( i, pDataValues++ );
279         }
280 
281         return aAllEntries;
282     }
283 
addListEntryListener(const Reference<XListEntryListener> & _rxListener)284     void SAL_CALL OCellListSource::addListEntryListener( const Reference< XListEntryListener >& _rxListener )
285     {
286         ::osl::MutexGuard aGuard( m_aMutex );
287         checkDisposed();
288         checkInitialized();
289 
290         if ( !_rxListener.is() )
291             throw NullPointerException();
292 
293         m_aListEntryListeners.addInterface( _rxListener );
294     }
295 
removeListEntryListener(const Reference<XListEntryListener> & _rxListener)296     void SAL_CALL OCellListSource::removeListEntryListener( const Reference< XListEntryListener >& _rxListener )
297     {
298         ::osl::MutexGuard aGuard( m_aMutex );
299         checkDisposed();
300         checkInitialized();
301 
302         if ( !_rxListener.is() )
303             throw NullPointerException();
304 
305         m_aListEntryListeners.removeInterface( _rxListener );
306     }
307 
modified(const EventObject &)308     void SAL_CALL OCellListSource::modified( const EventObject& /* aEvent */ )
309     {
310         notifyModified();
311     }
312 
notifyModified()313     void OCellListSource::notifyModified()
314     {
315         EventObject aEvent;
316         aEvent.Source.set(*this);
317 
318         ::comphelper::OInterfaceIteratorHelper2 aIter( m_aListEntryListeners );
319         while ( aIter.hasMoreElements() )
320         {
321             try
322             {
323                 static_cast< XListEntryListener* >( aIter.next() )->allEntriesChanged( aEvent );
324             }
325             catch( const RuntimeException& )
326             {
327                 // silent this
328             }
329             catch( const Exception& )
330             {
331                 TOOLS_WARN_EXCEPTION( "sc", "OCellListSource::notifyModified: caught a (non-runtime) exception!" );
332             }
333         }
334 
335     }
336 
disposing(const EventObject & aEvent)337     void SAL_CALL OCellListSource::disposing( const EventObject& aEvent )
338     {
339         Reference<XInterface> xRangeInt( m_xRange, UNO_QUERY );
340         if ( xRangeInt == aEvent.Source )
341         {
342             // release references to range object
343             m_xRange.clear();
344         }
345     }
346 
initialize(const Sequence<Any> & _rArguments)347     void SAL_CALL OCellListSource::initialize( const Sequence< Any >& _rArguments )
348     {
349         if ( m_bInitialized )
350             throw RuntimeException("CellListSource is already initialized", static_cast<cppu::OWeakObject*>(this));
351 
352         // get the cell address
353         CellRangeAddress aRangeAddress;
354         bool bFoundAddress = false;
355 
356         for ( const Any& rArg : _rArguments )
357         {
358             NamedValue aValue;
359             if ( rArg >>= aValue )
360             {
361                 if ( aValue.Name == "CellRange" )
362                 {
363                     if ( aValue.Value >>= aRangeAddress )
364                     {
365                         bFoundAddress = true;
366                         break;
367                     }
368                 }
369             }
370         }
371 
372         if ( !bFoundAddress )
373             throw RuntimeException("Cell not found", static_cast<cppu::OWeakObject*>(this));
374 
375         // determine the range we're bound to
376         try
377         {
378             if ( m_xDocument.is() )
379             {
380                 // first the sheets collection
381                 Reference< XIndexAccess > xSheets(m_xDocument->getSheets( ), UNO_QUERY);
382                 OSL_ENSURE( xSheets.is(), "OCellListSource::initialize: could not retrieve the sheets!" );
383 
384                 if ( xSheets.is() )
385                 {
386                     // the concrete sheet
387                     Reference< XCellRange > xSheet(xSheets->getByIndex( aRangeAddress.Sheet ), UNO_QUERY);
388                     OSL_ENSURE( xSheet.is(), "OCellListSource::initialize: NULL sheet, but no exception!" );
389 
390                     // the concrete cell
391                     if ( xSheet.is() )
392                     {
393                         m_xRange.set(xSheet->getCellRangeByPosition(
394                             aRangeAddress.StartColumn, aRangeAddress.StartRow,
395                             aRangeAddress.EndColumn, aRangeAddress.EndRow));
396                         OSL_ENSURE( Reference< XCellRangeAddressable >( m_xRange, UNO_QUERY ).is(), "OCellListSource::initialize: either NULL range, or cell without address access!" );
397                     }
398                 }
399             }
400         }
401         catch( const Exception& )
402         {
403             TOOLS_WARN_EXCEPTION( "sc", "OCellListSource::initialize: caught an exception while retrieving the cell object!" );
404         }
405 
406         if ( !m_xRange.is() )
407             throw RuntimeException("Failed to retrieve cell range", static_cast<cppu::OWeakObject*>(this));
408 
409         Reference<XModifyBroadcaster> xBroadcaster( m_xRange, UNO_QUERY );
410         if ( xBroadcaster.is() )
411         {
412             xBroadcaster->addModifyListener( this );
413         }
414 
415         // TODO: add as XEventListener to the cell range, so we get notified when it dies,
416         // and can dispose ourself then
417 
418         // TODO: somehow add as listener so we get notified when the address of the cell range changes
419         // We need to forward this as change in our CellRange property to our property change listeners
420 
421         // TODO: somehow add as listener to the cells in the range, so that we get notified
422         // when their content changes. We need to forward this to our list entry listeners then
423 
424         // TODO: somehow add as listener so that we get notified of insertions and removals of rows in our
425         // range. In this case, we need to fire a change in our CellRange property, and additionally
426         // notify our XListEntryListeners
427 
428         m_bInitialized = true;
429     }
430 
431 }   // namespace calc
432 
433 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
434