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