bump product version to 4.1.6.2
[LibreOffice.git] / chart2 / source / model / main / DataSeries.cxx
blobe6632ec7e23527928a1653582fd3bc9ca849481a
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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 "DataSeries.hxx"
21 #include "DataSeriesProperties.hxx"
22 #include "DataPointProperties.hxx"
23 #include "CharacterProperties.hxx"
24 #include "UserDefinedProperties.hxx"
25 #include "DataPoint.hxx"
26 #include "macros.hxx"
27 #include "DataSeriesHelper.hxx"
28 #include "ContainerHelper.hxx"
29 #include "CloneHelper.hxx"
30 #include "ModifyListenerHelper.hxx"
31 #include "EventListenerHelper.hxx"
33 #include <algorithm>
35 using namespace ::com::sun::star;
37 using ::com::sun::star::beans::Property;
38 using ::com::sun::star::uno::Sequence;
39 using ::com::sun::star::uno::Reference;
40 using ::com::sun::star::uno::Any;
41 using ::osl::MutexGuard;
43 // ----------------------------------------
45 namespace
48 struct StaticDataSeriesDefaults_Initializer
50 ::chart::tPropertyValueMap* operator()()
52 static ::chart::tPropertyValueMap aStaticDefaults;
53 lcl_AddDefaultsToMap( aStaticDefaults );
54 return &aStaticDefaults;
56 private:
57 void lcl_AddDefaultsToMap( ::chart::tPropertyValueMap & rOutMap )
59 ::chart::DataSeriesProperties::AddDefaultsToMap( rOutMap );
60 ::chart::CharacterProperties::AddDefaultsToMap( rOutMap );
62 float fDefaultCharHeight = 10.0;
63 ::chart::PropertyHelper::setPropertyValue( rOutMap, ::chart::CharacterProperties::PROP_CHAR_CHAR_HEIGHT, fDefaultCharHeight );
64 ::chart::PropertyHelper::setPropertyValue( rOutMap, ::chart::CharacterProperties::PROP_CHAR_ASIAN_CHAR_HEIGHT, fDefaultCharHeight );
65 ::chart::PropertyHelper::setPropertyValue( rOutMap, ::chart::CharacterProperties::PROP_CHAR_COMPLEX_CHAR_HEIGHT, fDefaultCharHeight );
69 struct StaticDataSeriesDefaults : public rtl::StaticAggregate< ::chart::tPropertyValueMap, StaticDataSeriesDefaults_Initializer >
73 struct StaticDataSeriesInfoHelper_Initializer
75 ::cppu::OPropertyArrayHelper* operator()()
77 static ::cppu::OPropertyArrayHelper aPropHelper( lcl_GetPropertySequence() );
78 return &aPropHelper;
81 private:
82 uno::Sequence< Property > lcl_GetPropertySequence()
84 ::std::vector< ::com::sun::star::beans::Property > aProperties;
85 ::chart::DataSeriesProperties::AddPropertiesToVector( aProperties );
86 ::chart::CharacterProperties::AddPropertiesToVector( aProperties );
87 ::chart::UserDefinedProperties::AddPropertiesToVector( aProperties );
89 ::std::sort( aProperties.begin(), aProperties.end(),
90 ::chart::PropertyNameLess() );
92 return ::chart::ContainerHelper::ContainerToSequence( aProperties );
97 struct StaticDataSeriesInfoHelper : public rtl::StaticAggregate< ::cppu::OPropertyArrayHelper, StaticDataSeriesInfoHelper_Initializer >
101 struct StaticDataSeriesInfo_Initializer
103 uno::Reference< beans::XPropertySetInfo >* operator()()
105 static uno::Reference< beans::XPropertySetInfo > xPropertySetInfo(
106 ::cppu::OPropertySetHelper::createPropertySetInfo(*StaticDataSeriesInfoHelper::get() ) );
107 return &xPropertySetInfo;
111 struct StaticDataSeriesInfo : public rtl::StaticAggregate< uno::Reference< beans::XPropertySetInfo >, StaticDataSeriesInfo_Initializer >
115 void lcl_SetParent(
116 const uno::Reference< uno::XInterface > & xChildInterface,
117 const uno::Reference< uno::XInterface > & xParentInterface )
119 uno::Reference< container::XChild > xChild( xChildInterface, uno::UNO_QUERY );
120 if( xChild.is())
121 xChild->setParent( xParentInterface );
124 typedef ::std::map< sal_Int32, ::com::sun::star::uno::Reference< ::com::sun::star::beans::XPropertySet > >
125 lcl_tDataPointMap;
127 void lcl_CloneAttributedDataPoints(
128 const lcl_tDataPointMap & rSource, lcl_tDataPointMap & rDestination,
129 const uno::Reference< uno::XInterface > & xSeries )
131 for( lcl_tDataPointMap::const_iterator aIt( rSource.begin());
132 aIt != rSource.end(); ++aIt )
134 Reference< beans::XPropertySet > xPoint( (*aIt).second );
135 if( xPoint.is())
137 Reference< util::XCloneable > xCloneable( xPoint, uno::UNO_QUERY );
138 if( xCloneable.is())
140 xPoint.set( xCloneable->createClone(), uno::UNO_QUERY );
141 if( xPoint.is())
143 lcl_SetParent( xPoint, xSeries );
144 rDestination.insert( lcl_tDataPointMap::value_type( (*aIt).first, xPoint ));
151 } // anonymous namespace
153 // ----------------------------------------
155 namespace chart
158 DataSeries::DataSeries( const uno::Reference< uno::XComponentContext > & xContext ) :
159 ::property::OPropertySet( m_aMutex ),
160 m_xContext( xContext ),
161 m_xModifyEventForwarder( ModifyListenerHelper::createModifyEventForwarder())
165 DataSeries::DataSeries( const DataSeries & rOther ) :
166 MutexContainer(),
167 impl::DataSeries_Base(),
168 ::property::OPropertySet( rOther, m_aMutex ),
169 m_xContext( rOther.m_xContext ),
170 m_xModifyEventForwarder( ModifyListenerHelper::createModifyEventForwarder())
172 if( ! rOther.m_aDataSequences.empty())
174 CloneHelper::CloneRefVector< tDataSequenceContainer::value_type >(
175 rOther.m_aDataSequences, m_aDataSequences );
176 ModifyListenerHelper::addListenerToAllElements( m_aDataSequences, m_xModifyEventForwarder );
179 CloneHelper::CloneRefVector< Reference< chart2::XRegressionCurve > >( rOther.m_aRegressionCurves, m_aRegressionCurves );
180 ModifyListenerHelper::addListenerToAllElements( m_aRegressionCurves, m_xModifyEventForwarder );
182 // add as listener to XPropertySet properties
183 Reference< beans::XPropertySet > xPropertySet;
184 uno::Any aValue;
186 getFastPropertyValue( aValue, DataPointProperties::PROP_DATAPOINT_ERROR_BAR_X );
187 if( ( aValue >>= xPropertySet )
188 && xPropertySet.is())
189 ModifyListenerHelper::addListener( xPropertySet, m_xModifyEventForwarder );
191 getFastPropertyValue( aValue, DataPointProperties::PROP_DATAPOINT_ERROR_BAR_Y );
192 if( ( aValue >>= xPropertySet )
193 && xPropertySet.is())
194 ModifyListenerHelper::addListener( xPropertySet, m_xModifyEventForwarder );
197 // late initialization to call after copy-constructing
198 void DataSeries::Init( const DataSeries & rOther )
200 if( ! rOther.m_aDataSequences.empty())
201 EventListenerHelper::addListenerToAllElements( m_aDataSequences, this );
203 Reference< uno::XInterface > xThisInterface( static_cast< ::cppu::OWeakObject * >( this ));
204 if( ! rOther.m_aAttributedDataPoints.empty())
206 lcl_CloneAttributedDataPoints(
207 rOther.m_aAttributedDataPoints, m_aAttributedDataPoints, xThisInterface );
208 ModifyListenerHelper::addListenerToAllMapElements( m_aAttributedDataPoints, m_xModifyEventForwarder );
211 // add as parent to error bars
212 Reference< beans::XPropertySet > xPropertySet;
213 uno::Any aValue;
215 getFastPropertyValue( aValue, DataPointProperties::PROP_DATAPOINT_ERROR_BAR_X );
216 if( ( aValue >>= xPropertySet )
217 && xPropertySet.is())
218 lcl_SetParent( xPropertySet, xThisInterface );
220 getFastPropertyValue( aValue, DataPointProperties::PROP_DATAPOINT_ERROR_BAR_Y );
221 if( ( aValue >>= xPropertySet )
222 && xPropertySet.is())
223 lcl_SetParent( xPropertySet, xThisInterface );
226 DataSeries::~DataSeries()
230 ModifyListenerHelper::removeListenerFromAllMapElements( m_aAttributedDataPoints, m_xModifyEventForwarder );
231 ModifyListenerHelper::removeListenerFromAllElements( m_aRegressionCurves, m_xModifyEventForwarder );
232 ModifyListenerHelper::removeListenerFromAllElements( m_aDataSequences, m_xModifyEventForwarder );
234 // remove listener from XPropertySet properties
235 Reference< beans::XPropertySet > xPropertySet;
236 uno::Any aValue;
238 getFastPropertyValue( aValue, DataPointProperties::PROP_DATAPOINT_ERROR_BAR_X );
239 if( ( aValue >>= xPropertySet )
240 && xPropertySet.is())
241 ModifyListenerHelper::removeListener( xPropertySet, m_xModifyEventForwarder );
243 getFastPropertyValue( aValue, DataPointProperties::PROP_DATAPOINT_ERROR_BAR_Y );
244 if( ( aValue >>= xPropertySet )
245 && xPropertySet.is())
246 ModifyListenerHelper::removeListener( xPropertySet, m_xModifyEventForwarder );
248 catch( const uno::Exception & ex )
250 ASSERT_EXCEPTION( ex );
254 // ____ XCloneable ____
255 uno::Reference< util::XCloneable > SAL_CALL DataSeries::createClone()
256 throw (uno::RuntimeException)
258 DataSeries * pNewSeries( new DataSeries( *this ));
259 // hold a reference to the clone
260 uno::Reference< util::XCloneable > xResult( pNewSeries );
261 // do initialization that uses uno references to the clone
262 pNewSeries->Init( *this );
264 return xResult;
267 Sequence< OUString > DataSeries::getSupportedServiceNames_Static()
269 Sequence< OUString > aServices( 3 );
270 aServices[ 0 ] = "com.sun.star.chart2.DataSeries";
271 aServices[ 1 ] = "com.sun.star.chart2.DataPointProperties";
272 aServices[ 2 ] = "com.sun.star.beans.PropertySet";
273 return aServices;
276 // ____ OPropertySet ____
277 uno::Any DataSeries::GetDefaultValue( sal_Int32 nHandle ) const
278 throw(beans::UnknownPropertyException)
280 const tPropertyValueMap& rStaticDefaults = *StaticDataSeriesDefaults::get();
281 tPropertyValueMap::const_iterator aFound( rStaticDefaults.find( nHandle ) );
282 if( aFound == rStaticDefaults.end() )
283 return uno::Any();
284 return (*aFound).second;
287 // ____ OPropertySet ____
288 ::cppu::IPropertyArrayHelper & SAL_CALL DataSeries::getInfoHelper()
290 return *StaticDataSeriesInfoHelper::get();
293 // ____ XPropertySet ____
294 uno::Reference< beans::XPropertySetInfo > SAL_CALL DataSeries::getPropertySetInfo()
295 throw (uno::RuntimeException)
297 return *StaticDataSeriesInfo::get();
300 void SAL_CALL DataSeries::getFastPropertyValue
301 ( uno::Any& rValue,
302 sal_Int32 nHandle ) const
304 // special handling for get. set is not possible for this property
305 if( nHandle == DataSeriesProperties::PROP_DATASERIES_ATTRIBUTED_DATA_POINTS )
307 // ToDo: only add those property sets that are really modified
308 uno::Sequence< sal_Int32 > aSeq( m_aAttributedDataPoints.size());
309 sal_Int32 * pIndexArray = aSeq.getArray();
310 sal_Int32 i = 0;
312 for( tDataPointAttributeContainer::const_iterator aIt( m_aAttributedDataPoints.begin());
313 aIt != m_aAttributedDataPoints.end(); ++aIt )
315 pIndexArray[ i ] = (*aIt).first;
316 ++i;
319 rValue <<= aSeq;
321 else
322 OPropertySet::getFastPropertyValue( rValue, nHandle );
325 void SAL_CALL DataSeries::setFastPropertyValue_NoBroadcast(
326 sal_Int32 nHandle, const uno::Any& rValue )
327 throw (uno::Exception)
329 if( nHandle == DataPointProperties::PROP_DATAPOINT_ERROR_BAR_Y
330 || nHandle == DataPointProperties::PROP_DATAPOINT_ERROR_BAR_X )
332 uno::Any aOldValue;
333 Reference< util::XModifyBroadcaster > xBroadcaster;
334 this->getFastPropertyValue( aOldValue, nHandle );
335 if( aOldValue.hasValue() &&
336 (aOldValue >>= xBroadcaster) &&
337 xBroadcaster.is())
339 ModifyListenerHelper::removeListener( xBroadcaster, m_xModifyEventForwarder );
342 OSL_ASSERT( rValue.getValueType().getTypeClass() == uno::TypeClass_INTERFACE );
343 if( rValue.hasValue() &&
344 (rValue >>= xBroadcaster) &&
345 xBroadcaster.is())
347 ModifyListenerHelper::addListener( xBroadcaster, m_xModifyEventForwarder );
351 ::property::OPropertySet::setFastPropertyValue_NoBroadcast( nHandle, rValue );
354 Reference< beans::XPropertySet >
355 SAL_CALL DataSeries::getDataPointByIndex( sal_Int32 nIndex )
356 throw (lang::IndexOutOfBoundsException,
357 uno::RuntimeException)
359 Reference< beans::XPropertySet > xResult;
361 Sequence< Reference< chart2::data::XLabeledDataSequence > > aSequences;
363 MutexGuard aGuard( GetMutex() );
364 aSequences = ContainerHelper::ContainerToSequence( m_aDataSequences );
367 ::std::vector< Reference< chart2::data::XLabeledDataSequence > > aValuesSeries(
368 DataSeriesHelper::getAllDataSequencesByRole( aSequences , "values", true ) );
369 if( !aValuesSeries.empty() )
371 Reference< chart2::data::XDataSequence > xSeq( aValuesSeries.front()->getValues() );
372 if( 0 <= nIndex && nIndex < xSeq->getData().getLength() )
375 MutexGuard aGuard( GetMutex() );
376 tDataPointAttributeContainer::iterator aIt( m_aAttributedDataPoints.find( nIndex ) );
377 if( aIt != m_aAttributedDataPoints.end() )
378 xResult = (*aIt).second;
380 if( !xResult.is() )
382 Reference< beans::XPropertySet > xParentProperties;
383 Reference< util::XModifyListener > xModifyEventForwarder;
385 MutexGuard aGuard( GetMutex() );
386 xParentProperties = this;
387 xModifyEventForwarder = m_xModifyEventForwarder;
390 // create a new XPropertySet for this data point
391 xResult.set( new DataPoint( xParentProperties ) );
393 MutexGuard aGuard( GetMutex() );
394 m_aAttributedDataPoints[ nIndex ] = xResult;
396 ModifyListenerHelper::addListener( xResult, xModifyEventForwarder );
400 else
402 throw lang::IndexOutOfBoundsException();
405 return xResult;
408 void SAL_CALL DataSeries::resetDataPoint( sal_Int32 nIndex )
409 throw (uno::RuntimeException)
411 Reference< beans::XPropertySet > xDataPointProp;
412 Reference< util::XModifyListener > xModifyEventForwarder;
414 MutexGuard aGuard( GetMutex() );
415 xModifyEventForwarder = m_xModifyEventForwarder;
416 tDataPointAttributeContainer::iterator aIt( m_aAttributedDataPoints.find( nIndex ));
417 if( aIt != m_aAttributedDataPoints.end())
419 xDataPointProp = (*aIt).second;
420 m_aAttributedDataPoints.erase(aIt);
424 if( xDataPointProp.is() )
426 Reference< util::XModifyBroadcaster > xBroadcaster( xDataPointProp, uno::UNO_QUERY );
427 if( xBroadcaster.is() && xModifyEventForwarder.is())
428 xBroadcaster->removeModifyListener( xModifyEventForwarder );
429 fireModifyEvent();
433 void SAL_CALL DataSeries::resetAllDataPoints()
434 throw (uno::RuntimeException)
436 tDataPointAttributeContainer aOldAttributedDataPoints;
437 Reference< util::XModifyListener > xModifyEventForwarder;
439 MutexGuard aGuard( GetMutex() );
440 xModifyEventForwarder = m_xModifyEventForwarder;
441 std::swap( aOldAttributedDataPoints, m_aAttributedDataPoints );
443 ModifyListenerHelper::removeListenerFromAllMapElements( aOldAttributedDataPoints, xModifyEventForwarder );
444 aOldAttributedDataPoints.clear();
445 fireModifyEvent();
448 // ____ XDataSink ____
449 void SAL_CALL DataSeries::setData( const uno::Sequence< Reference< chart2::data::XLabeledDataSequence > >& aData )
450 throw (uno::RuntimeException)
452 tDataSequenceContainer aOldDataSequences;
453 tDataSequenceContainer aNewDataSequences;
454 Reference< util::XModifyListener > xModifyEventForwarder;
455 Reference< lang::XEventListener > xListener;
457 MutexGuard aGuard( GetMutex() );
458 xModifyEventForwarder = m_xModifyEventForwarder;
459 xListener = this;
460 std::swap( aOldDataSequences, m_aDataSequences );
461 aNewDataSequences = ContainerHelper::SequenceToVector( aData );
462 m_aDataSequences = aNewDataSequences;
464 ModifyListenerHelper::removeListenerFromAllElements( aOldDataSequences, xModifyEventForwarder );
465 EventListenerHelper::removeListenerFromAllElements( aOldDataSequences, xListener );
466 EventListenerHelper::addListenerToAllElements( aNewDataSequences, xListener );
467 ModifyListenerHelper::addListenerToAllElements( aNewDataSequences, xModifyEventForwarder );
468 fireModifyEvent();
471 // ____ XDataSource ____
472 Sequence< Reference< chart2::data::XLabeledDataSequence > > SAL_CALL DataSeries::getDataSequences()
473 throw (uno::RuntimeException)
475 MutexGuard aGuard( GetMutex() );
476 return ContainerHelper::ContainerToSequence( m_aDataSequences );
480 // ____ XRegressionCurveContainer ____
481 void SAL_CALL DataSeries::addRegressionCurve(
482 const uno::Reference< chart2::XRegressionCurve >& xRegressionCurve )
483 throw (lang::IllegalArgumentException,
484 uno::RuntimeException)
486 Reference< util::XModifyListener > xModifyEventForwarder;
488 MutexGuard aGuard( GetMutex() );
489 xModifyEventForwarder = m_xModifyEventForwarder;
490 if( ::std::find( m_aRegressionCurves.begin(), m_aRegressionCurves.end(), xRegressionCurve )
491 != m_aRegressionCurves.end())
492 throw lang::IllegalArgumentException();
493 m_aRegressionCurves.push_back( xRegressionCurve );
495 ModifyListenerHelper::addListener( xRegressionCurve, xModifyEventForwarder );
496 fireModifyEvent();
499 void SAL_CALL DataSeries::removeRegressionCurve(
500 const uno::Reference< chart2::XRegressionCurve >& xRegressionCurve )
501 throw (container::NoSuchElementException,
502 uno::RuntimeException)
504 if( !xRegressionCurve.is() )
505 throw container::NoSuchElementException();
507 Reference< util::XModifyListener > xModifyEventForwarder;
509 MutexGuard aGuard( GetMutex() );
510 xModifyEventForwarder = m_xModifyEventForwarder;
511 tRegressionCurveContainerType::iterator aIt(
512 ::std::find( m_aRegressionCurves.begin(), m_aRegressionCurves.end(), xRegressionCurve ) );
513 if( aIt == m_aRegressionCurves.end())
514 throw container::NoSuchElementException(
515 "The given regression curve is no element of this series",
516 static_cast< uno::XWeak * >( this ));
517 m_aRegressionCurves.erase( aIt );
520 ModifyListenerHelper::removeListener( xRegressionCurve, xModifyEventForwarder );
521 fireModifyEvent();
524 uno::Sequence< uno::Reference< chart2::XRegressionCurve > > SAL_CALL DataSeries::getRegressionCurves()
525 throw (uno::RuntimeException)
527 MutexGuard aGuard( GetMutex() );
528 return ContainerHelper::ContainerToSequence( m_aRegressionCurves );
531 void SAL_CALL DataSeries::setRegressionCurves(
532 const Sequence< Reference< chart2::XRegressionCurve > >& aRegressionCurves )
533 throw (uno::RuntimeException)
535 tRegressionCurveContainerType aOldCurves;
536 tRegressionCurveContainerType aNewCurves( ContainerHelper::SequenceToVector( aRegressionCurves ) );
537 Reference< util::XModifyListener > xModifyEventForwarder;
539 MutexGuard aGuard( GetMutex() );
540 xModifyEventForwarder = m_xModifyEventForwarder;
541 std::swap( aOldCurves, m_aRegressionCurves );
542 m_aRegressionCurves = aNewCurves;
544 ModifyListenerHelper::removeListenerFromAllElements( aOldCurves, xModifyEventForwarder );
545 ModifyListenerHelper::addListenerToAllElements( aNewCurves, xModifyEventForwarder );
546 fireModifyEvent();
549 // ____ XModifyBroadcaster ____
550 void SAL_CALL DataSeries::addModifyListener( const Reference< util::XModifyListener >& aListener )
551 throw (uno::RuntimeException)
555 Reference< util::XModifyBroadcaster > xBroadcaster( m_xModifyEventForwarder, uno::UNO_QUERY_THROW );
556 xBroadcaster->addModifyListener( aListener );
558 catch( const uno::Exception & ex )
560 ASSERT_EXCEPTION( ex );
564 void SAL_CALL DataSeries::removeModifyListener( const Reference< util::XModifyListener >& aListener )
565 throw (uno::RuntimeException)
569 Reference< util::XModifyBroadcaster > xBroadcaster( m_xModifyEventForwarder, uno::UNO_QUERY_THROW );
570 xBroadcaster->removeModifyListener( aListener );
572 catch( const uno::Exception & ex )
574 ASSERT_EXCEPTION( ex );
578 // ____ XModifyListener ____
579 void SAL_CALL DataSeries::modified( const lang::EventObject& aEvent )
580 throw (uno::RuntimeException)
582 m_xModifyEventForwarder->modified( aEvent );
585 // ____ XEventListener (base of XModifyListener) ____
586 void SAL_CALL DataSeries::disposing( const lang::EventObject& rEventObject )
587 throw (uno::RuntimeException)
589 // forget disposed data sequences
590 tDataSequenceContainer::iterator aIt(
591 ::std::find( m_aDataSequences.begin(), m_aDataSequences.end(), rEventObject.Source ));
592 if( aIt != m_aDataSequences.end())
593 m_aDataSequences.erase( aIt );
596 // ____ OPropertySet ____
597 void DataSeries::firePropertyChangeEvent()
599 fireModifyEvent();
602 void DataSeries::fireModifyEvent()
604 m_xModifyEventForwarder->modified( lang::EventObject( static_cast< uno::XWeak* >( this )));
608 // ================================================================================
610 using impl::DataSeries_Base;
611 using ::property::OPropertySet;
613 IMPLEMENT_FORWARD_XINTERFACE2( DataSeries, DataSeries_Base, OPropertySet )
614 IMPLEMENT_FORWARD_XTYPEPROVIDER2( DataSeries, DataSeries_Base, OPropertySet )
616 // implement XServiceInfo methods basing upon getSupportedServiceNames_Static
617 APPHELPER_XSERVICEINFO_IMPL( DataSeries,
618 OUString("com.sun.star.comp.chart.DataSeries") );
620 } // namespace chart
622 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */