1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_chart2.hxx"
26 
27 #include "DataBrowserModel.hxx"
28 #include "DialogModel.hxx"
29 #include "ChartModelHelper.hxx"
30 #include "DiagramHelper.hxx"
31 #include "DataSeriesHelper.hxx"
32 #include "PropertyHelper.hxx"
33 #include "ControllerLockGuard.hxx"
34 #include "macros.hxx"
35 #include "StatisticsHelper.hxx"
36 #include "ContainerHelper.hxx"
37 #include "ChartTypeHelper.hxx"
38 #include "chartview/ExplicitValueProvider.hxx"
39 #include "ExplicitCategoriesProvider.hxx"
40 
41 #include <com/sun/star/container/XIndexReplace.hpp>
42 #include <com/sun/star/chart2/XAxis.hpp>
43 #include <com/sun/star/chart2/XDataSeriesContainer.hpp>
44 #include <com/sun/star/chart2/XInternalDataProvider.hpp>
45 #include <com/sun/star/chart2/XCoordinateSystemContainer.hpp>
46 #include <com/sun/star/chart2/XChartTypeContainer.hpp>
47 #include <com/sun/star/chart2/data/XDataSource.hpp>
48 #include <com/sun/star/chart2/data/XDataSink.hpp>
49 #include <com/sun/star/chart2/data/XLabeledDataSequence.hpp>
50 #include <com/sun/star/chart2/data/XNumericalDataSequence.hpp>
51 #include <com/sun/star/chart2/data/XTextualDataSequence.hpp>
52 #include <com/sun/star/util/XModifiable.hpp>
53 
54 #include <rtl/math.hxx>
55 
56 #include <algorithm>
57 
58 #if OSL_DEBUG_LEVEL > 1
59 #include <cstdio>
60 #endif
61 
62 using namespace ::com::sun::star;
63 
64 using ::com::sun::star::uno::Reference;
65 using ::com::sun::star::uno::Sequence;
66 using ::rtl::OUString;
67 
68 namespace
69 {
lcl_getRole(const Reference<chart2::data::XDataSequence> & xSeq)70 OUString lcl_getRole(
71     const Reference< chart2::data::XDataSequence > & xSeq )
72 {
73     OUString aResult;
74     Reference< beans::XPropertySet > xProp( xSeq, uno::UNO_QUERY );
75     if( xProp.is())
76     {
77         try
78         {
79             xProp->getPropertyValue( C2U("Role")) >>= aResult;
80         }
81         catch( const uno::Exception & ex )
82         {
83             ASSERT_EXCEPTION( ex );
84         }
85     }
86     return aResult;
87 }
88 
89 
lcl_getRole(const Reference<chart2::data::XLabeledDataSequence> & xLSeq)90 OUString lcl_getRole(
91     const Reference< chart2::data::XLabeledDataSequence > & xLSeq )
92 {
93     OUString aResult;
94     if( xLSeq.is())
95         aResult = lcl_getRole( xLSeq->getValues());
96     return aResult;
97 }
98 
lcl_getUIRoleName(const Reference<chart2::data::XLabeledDataSequence> & xLSeq)99 OUString lcl_getUIRoleName(
100     const Reference< chart2::data::XLabeledDataSequence > & xLSeq )
101 {
102     OUString aResult( lcl_getRole( xLSeq ));
103     if( !aResult.isEmpty() )
104         aResult = ::chart::DialogModel::ConvertRoleFromInternalToUI( aResult );
105     return aResult;
106 }
107 
lcl_copyDataSequenceProperties(const Reference<chart2::data::XDataSequence> & xOldSequence,const Reference<chart2::data::XDataSequence> & xNewSequence)108 void lcl_copyDataSequenceProperties(
109     const Reference< chart2::data::XDataSequence > & xOldSequence,
110     const Reference< chart2::data::XDataSequence > & xNewSequence )
111 {
112     Reference< beans::XPropertySet > xOldSeqProp( xOldSequence, uno::UNO_QUERY );
113     Reference< beans::XPropertySet > xNewSeqProp( xNewSequence, uno::UNO_QUERY );
114     comphelper::copyProperties( xOldSeqProp, xNewSeqProp );
115 }
116 
lcl_SequenceOfSeriesIsShared(const Reference<chart2::XDataSeries> & xSeries,const Reference<chart2::data::XDataSequence> & xValues)117 bool lcl_SequenceOfSeriesIsShared(
118     const Reference< chart2::XDataSeries > & xSeries,
119     const Reference< chart2::data::XDataSequence > & xValues )
120 {
121     bool bResult = false;
122     if( !xValues.is())
123         return bResult;
124     try
125     {
126         OUString aValuesRole( lcl_getRole( xValues ));
127         OUString aValuesRep( xValues->getSourceRangeRepresentation());
128         Reference< chart2::data::XDataSource > xSource( xSeries, uno::UNO_QUERY_THROW );
129         Sequence< Reference< chart2::data::XLabeledDataSequence > > aLSeq( xSource->getDataSequences());
130         for( sal_Int32 i=0; i<aLSeq.getLength(); ++i )
131             if( aLSeq[i].is() &&
132                 lcl_getRole( aLSeq[i] ).equals( aValuesRole ))
133             {
134                 // getValues().is(), because lcl_getRole checked that already
135                 bResult = (aValuesRep == aLSeq[i]->getValues()->getSourceRangeRepresentation());
136                 // assumption: a role appears only once in a series
137                 break;
138             }
139     }
140     catch( const uno::Exception & ex )
141     {
142         ASSERT_EXCEPTION( ex );
143     }
144     return bResult;
145 }
146 
147 typedef ::std::vector< Reference< chart2::data::XLabeledDataSequence > > lcl_tSharedSeqVec;
148 
lcl_getSharedSequences(const Sequence<Reference<chart2::XDataSeries>> & rSeries)149 lcl_tSharedSeqVec lcl_getSharedSequences( const Sequence< Reference< chart2::XDataSeries > > & rSeries )
150 {
151     // @todo: if only some series share a sequence, those have to be duplicated
152     // and made unshared for all series
153     lcl_tSharedSeqVec aResult;
154     // if we have only one series, we don't want any shared sequences
155     if( rSeries.getLength() <= 1 )
156         return aResult;
157 
158     Reference< chart2::data::XDataSource > xSource( rSeries[0], uno::UNO_QUERY );
159     Sequence< Reference< chart2::data::XLabeledDataSequence > > aLSeq( xSource->getDataSequences());
160     for( sal_Int32 nIdx=0; nIdx<aLSeq.getLength(); ++nIdx )
161     {
162         Reference< chart2::data::XDataSequence > xValues( aLSeq[nIdx]->getValues());
163         bool bShared = true;
164         for( sal_Int32 nSeriesIdx=1; nSeriesIdx<rSeries.getLength(); ++nSeriesIdx )
165         {
166             bShared = lcl_SequenceOfSeriesIsShared( rSeries[nSeriesIdx], xValues );
167             if( !bShared )
168                 break;
169         }
170         if( bShared )
171             aResult.push_back( aLSeq[nIdx] );
172     }
173 
174     return aResult;
175 }
176 
lcl_getValuesRepresentationIndex(const Reference<chart2::data::XLabeledDataSequence> & xLSeq)177 sal_Int32 lcl_getValuesRepresentationIndex(
178     const Reference< chart2::data::XLabeledDataSequence > & xLSeq )
179 {
180     sal_Int32 nResult = -1;
181     if( xLSeq.is())
182     {
183         Reference< chart2::data::XDataSequence > xSeq( xLSeq->getValues());
184         if( xSeq.is())
185         {
186             OUString aRep( xSeq->getSourceRangeRepresentation());
187             nResult = aRep.toInt32();
188         }
189     }
190     return nResult;
191 }
192 
193 struct lcl_RepresentationsOfLSeqMatch : public ::std::unary_function< Reference< chart2::data::XLabeledDataSequence >, bool >
194 {
lcl_RepresentationsOfLSeqMatch__anon69a54ce70111::lcl_RepresentationsOfLSeqMatch195     lcl_RepresentationsOfLSeqMatch( const Reference< chart2::data::XLabeledDataSequence > & xLSeq ) :
196             m_aValuesRep( xLSeq.is() ?
197                           (xLSeq->getValues().is() ? xLSeq->getValues()->getSourceRangeRepresentation() : OUString())
198                           : OUString() )
199     {}
operator ()__anon69a54ce70111::lcl_RepresentationsOfLSeqMatch200     bool operator() ( const Reference< chart2::data::XLabeledDataSequence > & xLSeq )
201     {
202         return (xLSeq.is() &&
203                 xLSeq->getValues().is() &&
204                 (xLSeq->getValues()->getSourceRangeRepresentation() == m_aValuesRep ));
205     }
206 private:
207     OUString m_aValuesRep;
208 };
209 
210 struct lcl_RolesOfLSeqMatch : public ::std::unary_function< Reference< chart2::data::XLabeledDataSequence >, bool >
211 {
lcl_RolesOfLSeqMatch__anon69a54ce70111::lcl_RolesOfLSeqMatch212     lcl_RolesOfLSeqMatch( const Reference< chart2::data::XLabeledDataSequence > & xLSeq ) :
213             m_aRole( lcl_getRole( xLSeq ))
214     {}
operator ()__anon69a54ce70111::lcl_RolesOfLSeqMatch215     bool operator() ( const Reference< chart2::data::XLabeledDataSequence > & xLSeq )
216     {
217         return lcl_getRole( xLSeq ).equals( m_aRole );
218     }
219 private:
220     OUString m_aRole;
221 };
222 
lcl_ShowCategories(const Reference<chart2::XDiagram> &)223 bool lcl_ShowCategories( const Reference< chart2::XDiagram > & /* xDiagram */ )
224 {
225     // show categories for all charts
226     return true;
227 //     return DiagramHelper::isCategoryDiagram( xDiagram );
228 }
229 
lcl_ShowCategoriesAsDataLabel(const Reference<chart2::XDiagram> & xDiagram)230 bool lcl_ShowCategoriesAsDataLabel( const Reference< chart2::XDiagram > & xDiagram )
231 {
232     return ! ::chart::DiagramHelper::isCategoryDiagram( xDiagram );
233 }
234 
235 } // anonymous namespace
236 
237 namespace chart
238 {
239 
240 
241 struct DataBrowserModel::tDataColumn
242 {
243     ::com::sun::star::uno::Reference<
244             ::com::sun::star::chart2::XDataSeries >                m_xDataSeries;
245     sal_Int32                                                  m_nIndexInDataSeries;
246     ::rtl::OUString                                            m_aUIRoleName;
247     ::com::sun::star::uno::Reference<
248             ::com::sun::star::chart2::data::XLabeledDataSequence > m_xLabeledDataSequence;
249     eCellType                                                  m_eCellType;
250     sal_Int32                                                  m_nNumberFormatKey;
251 
252     // default CTOR
tDataColumnchart::DataBrowserModel::tDataColumn253     tDataColumn() : m_nIndexInDataSeries( -1 ), m_eCellType( TEXT ), m_nNumberFormatKey( 0 ) {}
254     // "full" CTOR
tDataColumnchart::DataBrowserModel::tDataColumn255     tDataColumn(
256         const ::com::sun::star::uno::Reference<
257         ::com::sun::star::chart2::XDataSeries > & xDataSeries,
258         sal_Int32 nIndexInDataSeries,
259         ::rtl::OUString aUIRoleName,
260         ::com::sun::star::uno::Reference<
261         ::com::sun::star::chart2::data::XLabeledDataSequence >  xLabeledDataSequence,
262         eCellType aCellType,
263         sal_Int32 nNumberFormatKey ) :
264             m_xDataSeries( xDataSeries ),
265             m_nIndexInDataSeries( nIndexInDataSeries ),
266             m_aUIRoleName( aUIRoleName ),
267             m_xLabeledDataSequence( xLabeledDataSequence ),
268             m_eCellType( aCellType ),
269             m_nNumberFormatKey( nNumberFormatKey )
270     {}
271 };
272 
273 struct DataBrowserModel::implColumnLess : public ::std::binary_function<
274         DataBrowserModel::tDataColumn, DataBrowserModel::tDataColumn, bool >
275 {
operator ()chart::DataBrowserModel::implColumnLess276     bool operator() ( const first_argument_type & rLeft, const second_argument_type & rRight )
277     {
278         if( rLeft.m_xLabeledDataSequence.is() && rRight.m_xLabeledDataSequence.is())
279         {
280             return DialogModel::GetRoleIndexForSorting( lcl_getRole( rLeft.m_xLabeledDataSequence )) <
281                 DialogModel::GetRoleIndexForSorting( lcl_getRole( rRight.m_xLabeledDataSequence ));
282         }
283         return true;
284     }
285 };
286 
DataBrowserModel(const Reference<chart2::XChartDocument> & xChartDoc,const Reference<uno::XComponentContext> & xContext)287 DataBrowserModel::DataBrowserModel(
288     const Reference< chart2::XChartDocument > & xChartDoc,
289     const Reference< uno::XComponentContext > & xContext ) :
290         m_xChartDocument( xChartDoc ),
291         m_xContext( xContext ),
292         m_apDialogModel( new DialogModel( xChartDoc, xContext ))
293 {
294     updateFromModel();
295 }
296 
~DataBrowserModel()297 DataBrowserModel::~DataBrowserModel()
298 {}
299 
300 namespace
301 {
302 struct lcl_DataSeriesOfHeaderMatches : public ::std::unary_function< ::chart::DataBrowserModel::tDataHeader, bool >
303 {
lcl_DataSeriesOfHeaderMatcheschart::__anon69a54ce70211::lcl_DataSeriesOfHeaderMatches304     lcl_DataSeriesOfHeaderMatches(
305         const Reference< chart2::XDataSeries > & xSeriesToCompareWith ) :
306             m_xSeries( xSeriesToCompareWith )
307     {}
operator ()chart::__anon69a54ce70211::lcl_DataSeriesOfHeaderMatches308     bool operator() ( const ::chart::DataBrowserModel::tDataHeader & rHeader )
309     {
310         return (m_xSeries == rHeader.m_xDataSeries);
311     }
312 private:
313     Reference< chart2::XDataSeries  > m_xSeries;
314 };
315 }
316 
insertDataSeries(sal_Int32 nAfterColumnIndex)317 void DataBrowserModel::insertDataSeries( sal_Int32 nAfterColumnIndex )
318 {
319     OSL_ASSERT( m_apDialogModel.get());
320     Reference< chart2::XInternalDataProvider > xDataProvider(
321         m_apDialogModel->getDataProvider(), uno::UNO_QUERY );
322     if( xDataProvider.is())
323     {
324         if( isCategoriesColumn(nAfterColumnIndex) )
325             nAfterColumnIndex = getCategoryColumnCount()-1;
326 
327         sal_Int32 nStartCol = 0;
328         Reference< chart2::XDiagram > xDiagram( ChartModelHelper::findDiagram( m_xChartDocument ));
329         Reference< chart2::XChartType > xChartType;
330         Reference< chart2::XDataSeries > xSeries;
331         if( static_cast< tDataColumnVector::size_type >( nAfterColumnIndex ) <= m_aColumns.size())
332             xSeries.set( m_aColumns[nAfterColumnIndex].m_xDataSeries );
333 
334         sal_Int32 nSeriesNumberFormat = 0;
335         if( xSeries.is())
336         {
337             xChartType.set( DiagramHelper::getChartTypeOfSeries( xDiagram, xSeries ));
338             tDataHeaderVector::const_iterator aIt(
339                 ::std::find_if( m_aHeaders.begin(), m_aHeaders.end(),
340                                 lcl_DataSeriesOfHeaderMatches( xSeries )));
341             if( aIt != m_aHeaders.end())
342                 nStartCol = aIt->m_nEndColumn;
343 
344             Reference< beans::XPropertySet > xSeriesProps( xSeries, uno::UNO_QUERY );
345             if( xSeriesProps.is() )
346                 xSeriesProps->getPropertyValue( C2U( "NumberFormat" )) >>= nSeriesNumberFormat;
347         }
348         else
349         {
350             xChartType.set( DiagramHelper::getChartTypeByIndex( xDiagram, 0 ));
351             nStartCol = nAfterColumnIndex;
352         }
353 
354         if( xChartType.is())
355         {
356             sal_Int32 nOffset = 0;
357             if( xDiagram.is() && lcl_ShowCategories( xDiagram ))
358                 nOffset=getCategoryColumnCount();
359             // get shared sequences of current series
360             Reference< chart2::XDataSeriesContainer > xSeriesCnt( xChartType, uno::UNO_QUERY );
361             lcl_tSharedSeqVec aSharedSequences;
362             if( xSeriesCnt.is())
363                 aSharedSequences = lcl_getSharedSequences( xSeriesCnt->getDataSeries());
364             Reference< chart2::XDataSeries > xNewSeries(
365                 m_apDialogModel->insertSeriesAfter( xSeries, xChartType, true /* bCreateDataCachedSequences */ ));
366             if( xNewSeries.is())
367             {
368                 {
369                     Reference< chart2::data::XDataSource > xSource( xNewSeries, uno::UNO_QUERY );
370                     if( xSource.is())
371                     {
372                         Sequence< Reference< chart2::data::XLabeledDataSequence > > aLSequences(
373                             xSource->getDataSequences());
374                         sal_Int32 nSeqIdx = 0;
375                         sal_Int32 nSeqSize = aLSequences.getLength();
376                         nStartCol -= (nOffset - 1);
377                         for( sal_Int32 nIndex = nStartCol;
378                              (nSeqIdx < nSeqSize);
379                              ++nSeqIdx )
380                         {
381                             lcl_tSharedSeqVec::const_iterator aSharedIt(
382                                 ::std::find_if( aSharedSequences.begin(), aSharedSequences.end(),
383                                                 lcl_RolesOfLSeqMatch( aLSequences[nSeqIdx] )));
384                             if( aSharedIt != aSharedSequences.end())
385                             {
386                                 aLSequences[nSeqIdx]->setValues( (*aSharedIt)->getValues());
387                                 aLSequences[nSeqIdx]->setLabel( (*aSharedIt)->getLabel());
388                             }
389                             else
390                             {
391                                 xDataProvider->insertSequence( nIndex - 1 );
392 
393                                 // values
394                                 Reference< chart2::data::XDataSequence > xNewSeq(
395                                     xDataProvider->createDataSequenceByRangeRepresentation(
396                                         OUString::valueOf( nIndex )));
397                                 lcl_copyDataSequenceProperties(
398                                     aLSequences[nSeqIdx]->getValues(), xNewSeq );
399                                 aLSequences[nSeqIdx]->setValues( xNewSeq );
400 
401                                 // labels
402                                 Reference< chart2::data::XDataSequence > xNewLabelSeq(
403                                     xDataProvider->createDataSequenceByRangeRepresentation(
404                                         OUString( RTL_CONSTASCII_USTRINGPARAM( "label " )) +
405                                         OUString::valueOf( nIndex )));
406                                 lcl_copyDataSequenceProperties(
407                                     aLSequences[nSeqIdx]->getLabel(), xNewLabelSeq );
408                                 aLSequences[nSeqIdx]->setLabel( xNewLabelSeq );
409                                 ++nIndex;
410                             }
411                         }
412                     }
413                 }
414                 if( nSeriesNumberFormat != 0 )
415                 {
416                     //give the new series the same number format as the former series especially for bubble charts thus the bubble size values can be edited with same format immidiately
417                     Reference< beans::XPropertySet > xNewSeriesProps( xNewSeries, uno::UNO_QUERY );
418                     if( xNewSeriesProps.is() )
419                         xNewSeriesProps->setPropertyValue( C2U( "NumberFormat" ), uno::makeAny( nSeriesNumberFormat ) );
420                 }
421 
422                 updateFromModel();
423             }
424         }
425     }
426 }
427 
insertComplexCategoryLevel(sal_Int32 nAfterColumnIndex)428 void DataBrowserModel::insertComplexCategoryLevel( sal_Int32 nAfterColumnIndex )
429 {
430     //create a new text column for complex categories
431 
432     OSL_ASSERT( m_apDialogModel.get());
433     Reference< chart2::XInternalDataProvider > xDataProvider( m_apDialogModel->getDataProvider(), uno::UNO_QUERY );
434     if( xDataProvider.is() )
435     {
436         if( !isCategoriesColumn(nAfterColumnIndex) )
437             nAfterColumnIndex = getCategoryColumnCount()-1;
438 
439         if(nAfterColumnIndex<0)
440         {
441             OSL_ENSURE( false, "wrong index for category level insertion" );
442             return;
443         }
444 
445         m_apDialogModel->startControllerLockTimer();
446         ControllerLockGuard aLockedControllers( Reference< frame::XModel >( m_xChartDocument, uno::UNO_QUERY ) );
447         xDataProvider->insertComplexCategoryLevel( nAfterColumnIndex+1 );
448         updateFromModel();
449     }
450 }
451 
removeDataSeriesOrComplexCategoryLevel(sal_Int32 nAtColumnIndex)452 void DataBrowserModel::removeDataSeriesOrComplexCategoryLevel( sal_Int32 nAtColumnIndex )
453 {
454     OSL_ASSERT( m_apDialogModel.get());
455     if( static_cast< tDataColumnVector::size_type >( nAtColumnIndex ) < m_aColumns.size())
456     {
457         Reference< chart2::XDataSeries > xSeries( m_aColumns[nAtColumnIndex].m_xDataSeries );
458         if( xSeries.is())
459         {
460             m_apDialogModel->deleteSeries(
461                 xSeries, getHeaderForSeries( xSeries ).m_xChartType );
462 
463             //delete sequences from internal data provider that are not used anymore
464             //but do not delete sequences that are still in use by the remaining series
465             Reference< chart2::XInternalDataProvider > xDataProvider( m_apDialogModel->getDataProvider(), uno::UNO_QUERY );
466             Reference< chart2::data::XDataSource > xSourceOfDeletedSeries( xSeries, uno::UNO_QUERY );
467             if( xDataProvider.is() && xSourceOfDeletedSeries.is())
468             {
469                 ::std::vector< sal_Int32 > aSequenceIndexesToDelete;
470                 Sequence< Reference< chart2::data::XLabeledDataSequence > > aSequencesOfDeletedSeries( xSourceOfDeletedSeries->getDataSequences() );
471                 Reference< chart2::XDataSeriesContainer > xSeriesCnt( getHeaderForSeries( xSeries ).m_xChartType, uno::UNO_QUERY );
472                 if( xSeriesCnt.is())
473                 {
474                     Reference< chart2::data::XDataSource > xRemainingDataSource( DataSeriesHelper::getDataSource( xSeriesCnt->getDataSeries() ) );
475                     if( xRemainingDataSource.is() )
476                     {
477                         ::std::vector< Reference< chart2::data::XLabeledDataSequence > > aRemainingSeq( ContainerHelper::SequenceToVector( xRemainingDataSource->getDataSequences() ) );
478                         for( sal_Int32 i=0; i<aSequencesOfDeletedSeries.getLength(); ++i )
479                         {
480                             ::std::vector< Reference< chart2::data::XLabeledDataSequence > >::const_iterator aHitIt(
481                                 ::std::find_if( aRemainingSeq.begin(), aRemainingSeq.end(),
482                                     lcl_RepresentationsOfLSeqMatch( aSequencesOfDeletedSeries[i] )));
483                             // if not used by the remaining series this sequence can be deleted
484                             if( aHitIt == aRemainingSeq.end() )
485                                 aSequenceIndexesToDelete.push_back( lcl_getValuesRepresentationIndex( aSequencesOfDeletedSeries[i] ) );
486                         }
487                     }
488                 }
489 
490                 // delete unnecessary sequences of the internal data
491                 // iterate using greatest index first, so that deletion does not
492                 // shift other sequences that will be deleted later
493                 ::std::sort( aSequenceIndexesToDelete.begin(), aSequenceIndexesToDelete.end());
494                 for( ::std::vector< sal_Int32 >::reverse_iterator aIt(
495                          aSequenceIndexesToDelete.rbegin()); aIt != aSequenceIndexesToDelete.rend(); ++aIt )
496                 {
497                     if( *aIt != -1 )
498                         xDataProvider->deleteSequence( *aIt );
499                 }
500             }
501             updateFromModel();
502         }
503         else
504         {
505             //delete a category column if there is more than one level (in case of a single column we do not get here)
506             OSL_ENSURE(nAtColumnIndex>0, "wrong index for categories deletion" );
507 
508             Reference< chart2::XInternalDataProvider > xDataProvider( m_apDialogModel->getDataProvider(), uno::UNO_QUERY );
509             if( xDataProvider.is() )
510             {
511                 m_apDialogModel->startControllerLockTimer();
512                 ControllerLockGuard aLockedControllers( Reference< frame::XModel >( m_xChartDocument, uno::UNO_QUERY ) );
513                 xDataProvider->deleteComplexCategoryLevel( nAtColumnIndex );
514                 updateFromModel();
515             }
516         }
517     }
518 }
519 
swapDataSeries(sal_Int32 nFirstColumnIndex)520 void DataBrowserModel::swapDataSeries( sal_Int32 nFirstColumnIndex )
521 {
522     OSL_ASSERT( m_apDialogModel.get());
523     if( static_cast< tDataColumnVector::size_type >( nFirstColumnIndex ) < m_aColumns.size() - 1 )
524     {
525         Reference< chart2::XDataSeries > xSeries( m_aColumns[nFirstColumnIndex].m_xDataSeries );
526         if( xSeries.is())
527         {
528             m_apDialogModel->moveSeries( xSeries, DialogModel::MOVE_DOWN );
529             updateFromModel();
530         }
531     }
532 }
533 
swapDataPointForAllSeries(sal_Int32 nFirstIndex)534 void DataBrowserModel::swapDataPointForAllSeries( sal_Int32 nFirstIndex )
535 {
536     OSL_ASSERT( m_apDialogModel.get());
537     Reference< chart2::XInternalDataProvider > xDataProvider(
538         m_apDialogModel->getDataProvider(), uno::UNO_QUERY );
539     // lockControllers
540     ControllerLockGuard aGuard( m_apDialogModel->getChartModel());
541     if( xDataProvider.is())
542         xDataProvider->swapDataPointWithNextOneForAllSequences( nFirstIndex );
543     // unlockControllers
544 }
545 
insertDataPointForAllSeries(sal_Int32 nAfterIndex)546 void DataBrowserModel::insertDataPointForAllSeries( sal_Int32 nAfterIndex )
547 {
548     Reference< chart2::XInternalDataProvider > xDataProvider(
549         m_apDialogModel->getDataProvider(), uno::UNO_QUERY );
550     // lockControllers
551     ControllerLockGuard aGuard( m_apDialogModel->getChartModel());
552     if( xDataProvider.is())
553         xDataProvider->insertDataPointForAllSequences( nAfterIndex );
554     // unlockControllers
555 }
556 
removeDataPointForAllSeries(sal_Int32 nAtIndex)557 void DataBrowserModel::removeDataPointForAllSeries( sal_Int32 nAtIndex )
558 {
559     Reference< chart2::XInternalDataProvider > xDataProvider(
560         m_apDialogModel->getDataProvider(), uno::UNO_QUERY );
561     // lockControllers
562     ControllerLockGuard aGuard( m_apDialogModel->getChartModel());
563     if( xDataProvider.is())
564         xDataProvider->deleteDataPointForAllSequences( nAtIndex );
565     // unlockControllers
566 }
567 
getHeaderForSeries(const Reference<chart2::XDataSeries> & xSeries) const568 DataBrowserModel::tDataHeader DataBrowserModel::getHeaderForSeries(
569     const Reference< chart2::XDataSeries > & xSeries ) const
570 {
571     for( tDataHeaderVector::const_iterator aIt( m_aHeaders.begin());
572          aIt != m_aHeaders.end(); ++aIt )
573     {
574         if( aIt->m_xDataSeries == xSeries )
575             return (*aIt);
576     }
577     return tDataHeader();
578 }
579 
580 Reference< chart2::XDataSeries >
getDataSeriesByColumn(sal_Int32 nColumn) const581     DataBrowserModel::getDataSeriesByColumn( sal_Int32 nColumn ) const
582 {
583     tDataColumnVector::size_type nIndex( nColumn );
584     if( nIndex < m_aColumns.size())
585         return m_aColumns[nIndex].m_xDataSeries;
586     return 0;
587 }
588 
getCellType(sal_Int32 nAtColumn,sal_Int32) const589 DataBrowserModel::eCellType DataBrowserModel::getCellType( sal_Int32 nAtColumn, sal_Int32 /* nAtRow */ ) const
590 {
591     eCellType eResult = TEXT;
592     tDataColumnVector::size_type nIndex( nAtColumn );
593     if( nIndex < m_aColumns.size())
594         eResult = m_aColumns[nIndex].m_eCellType;
595     return eResult;
596 }
597 
getCellNumber(sal_Int32 nAtColumn,sal_Int32 nAtRow)598 double DataBrowserModel::getCellNumber( sal_Int32 nAtColumn, sal_Int32 nAtRow )
599 {
600     double fResult;
601     ::rtl::math::setNan( & fResult );
602 
603     tDataColumnVector::size_type nIndex( nAtColumn );
604     if( nIndex < m_aColumns.size() &&
605         m_aColumns[ nIndex ].m_xLabeledDataSequence.is())
606     {
607         Reference< chart2::data::XNumericalDataSequence > xData(
608             m_aColumns[ nIndex ].m_xLabeledDataSequence->getValues(), uno::UNO_QUERY );
609         if( xData.is())
610         {
611             Sequence< double > aValues( xData->getNumericalData());
612             if( nAtRow < aValues.getLength())
613                 fResult = aValues[nAtRow];
614         }
615     }
616     return fResult;
617 }
618 
getCellAny(sal_Int32 nAtColumn,sal_Int32 nAtRow)619 uno::Any DataBrowserModel::getCellAny( sal_Int32 nAtColumn, sal_Int32 nAtRow )
620 {
621     uno::Any aResult;
622 
623     tDataColumnVector::size_type nIndex( nAtColumn );
624     if( nIndex < m_aColumns.size() &&
625         m_aColumns[ nIndex ].m_xLabeledDataSequence.is())
626     {
627         Reference< chart2::data::XDataSequence > xData(
628             m_aColumns[ nIndex ].m_xLabeledDataSequence->getValues() );
629         if( xData.is() )
630         {
631             Sequence< uno::Any > aValues( xData->getData());
632             if( nAtRow < aValues.getLength())
633                 aResult = aValues[nAtRow];
634         }
635     }
636     return aResult;
637 }
638 
getCellText(sal_Int32 nAtColumn,sal_Int32 nAtRow)639 OUString DataBrowserModel::getCellText( sal_Int32 nAtColumn, sal_Int32 nAtRow )
640 {
641     OUString aResult;
642 
643     tDataColumnVector::size_type nIndex( nAtColumn );
644     if( nIndex < m_aColumns.size() &&
645         m_aColumns[ nIndex ].m_xLabeledDataSequence.is())
646     {
647         Reference< chart2::data::XTextualDataSequence > xData(
648             m_aColumns[ nIndex ].m_xLabeledDataSequence->getValues(), uno::UNO_QUERY );
649         if( xData.is())
650         {
651             Sequence< OUString > aValues( xData->getTextualData());
652             if( nAtRow < aValues.getLength())
653                 aResult = aValues[nAtRow];
654         }
655     }
656     return aResult;
657 }
658 
getNumberFormatKey(sal_Int32 nAtColumn,sal_Int32)659 sal_uInt32 DataBrowserModel::getNumberFormatKey( sal_Int32 nAtColumn, sal_Int32 /* nAtRow */ )
660 {
661     tDataColumnVector::size_type nIndex( nAtColumn );
662     if( nIndex < m_aColumns.size())
663         return m_aColumns[ nIndex ].m_nNumberFormatKey;
664     return 0;
665 }
666 
setCellAny(sal_Int32 nAtColumn,sal_Int32 nAtRow,const uno::Any & rValue)667 bool DataBrowserModel::setCellAny( sal_Int32 nAtColumn, sal_Int32 nAtRow, const uno::Any & rValue )
668 {
669     bool bResult = false;
670     tDataColumnVector::size_type nIndex( nAtColumn );
671     if( nIndex < m_aColumns.size() &&
672         m_aColumns[ nIndex ].m_xLabeledDataSequence.is())
673     {
674         bResult = true;
675         try
676         {
677             ControllerLockGuard aLockedControllers( Reference< frame::XModel >( m_xChartDocument, uno::UNO_QUERY ) );
678 
679             // label
680             if( nAtRow == -1 )
681             {
682                 Reference< container::XIndexReplace > xIndexReplace(
683                     m_aColumns[ nIndex ].m_xLabeledDataSequence->getLabel(), uno::UNO_QUERY_THROW );
684                 xIndexReplace->replaceByIndex( 0, rValue );
685             }
686             else
687             {
688                 Reference< container::XIndexReplace > xIndexReplace(
689                     m_aColumns[ nIndex ].m_xLabeledDataSequence->getValues(), uno::UNO_QUERY_THROW );
690                 xIndexReplace->replaceByIndex( nAtRow, rValue );
691             }
692 
693             m_apDialogModel->startControllerLockTimer();
694             //notify change directly to the model (this is necessary here as sequences for complex categories not known directly to the chart model so they do not notify their changes) (for complex categories see issue #i82971#)
695             Reference< util::XModifiable > xModifiable( m_xChartDocument, uno::UNO_QUERY );
696             if( xModifiable.is() )
697                 xModifiable->setModified(true);
698         }
699         catch( const uno::Exception & ex )
700         {
701             (void)(ex);
702             bResult = false;
703         }
704     }
705     return bResult;
706 }
707 
setCellNumber(sal_Int32 nAtColumn,sal_Int32 nAtRow,double fValue)708 bool DataBrowserModel::setCellNumber( sal_Int32 nAtColumn, sal_Int32 nAtRow, double fValue )
709 {
710     return (getCellType( nAtColumn, nAtRow ) == NUMBER) &&
711         setCellAny( nAtColumn, nAtRow, uno::makeAny( fValue ));
712 }
713 
setCellText(sal_Int32 nAtColumn,sal_Int32 nAtRow,const::rtl::OUString & rText)714 bool DataBrowserModel::setCellText( sal_Int32 nAtColumn, sal_Int32 nAtRow, const ::rtl::OUString & rText )
715 {
716     return (getCellType( nAtColumn, nAtRow ) == TEXT) &&
717         setCellAny( nAtColumn, nAtRow, uno::makeAny( rText ));
718 }
719 
getColumnCount() const720 sal_Int32 DataBrowserModel::getColumnCount() const
721 {
722     return static_cast< sal_Int32 >( m_aColumns.size());
723 }
724 
getMaxRowCount() const725 sal_Int32 DataBrowserModel::getMaxRowCount() const
726 {
727     sal_Int32 nResult = 0;
728     tDataColumnVector::const_iterator aIt( m_aColumns.begin());
729     for( ; aIt != m_aColumns.end(); ++aIt )
730     {
731         if( aIt->m_xLabeledDataSequence.is())
732         {
733             Reference< chart2::data::XDataSequence > xSeq(
734                 aIt->m_xLabeledDataSequence->getValues());
735             if( !xSeq.is())
736                 continue;
737             sal_Int32 nLength( xSeq->getData().getLength());
738             if( nLength > nResult )
739                 nResult = nLength;
740         }
741     }
742 
743     return nResult;
744 }
745 
getRoleOfColumn(sal_Int32 nColumnIndex) const746 OUString DataBrowserModel::getRoleOfColumn( sal_Int32 nColumnIndex ) const
747 {
748     if( nColumnIndex != -1 &&
749         static_cast< sal_uInt32 >( nColumnIndex ) < m_aColumns.size())
750         return m_aColumns[ nColumnIndex ].m_aUIRoleName;
751     return OUString();
752 }
753 
isCategoriesColumn(sal_Int32 nColumnIndex) const754 bool DataBrowserModel::isCategoriesColumn( sal_Int32 nColumnIndex ) const
755 {
756     bool bIsCategories = false;
757     if( nColumnIndex>=0 && nColumnIndex<static_cast< sal_Int32 >(m_aColumns.size()) )
758         bIsCategories = !m_aColumns[ nColumnIndex ].m_xDataSeries.is();
759     return bIsCategories;
760 }
761 
getCategoryColumnCount()762 sal_Int32 DataBrowserModel::getCategoryColumnCount()
763 {
764     sal_Int32 nLastTextColumnIndex = -1;
765     tDataColumnVector::const_iterator aIt = m_aColumns.begin();
766     for( ; aIt != m_aColumns.end(); ++aIt )
767     {
768         if( !aIt->m_xDataSeries.is() )
769             nLastTextColumnIndex++;
770         else
771             break;
772     }
773     return nLastTextColumnIndex+1;
774 }
775 
getDataHeaders() const776 const DataBrowserModel::tDataHeaderVector& DataBrowserModel::getDataHeaders() const
777 {
778     return m_aHeaders;
779 }
780 
updateFromModel()781 void DataBrowserModel::updateFromModel()
782 {
783     if( !m_xChartDocument.is())
784         return;
785     m_aColumns.clear();
786     m_aHeaders.clear();
787 
788     Reference< chart2::XDiagram > xDiagram( ChartModelHelper::findDiagram( m_xChartDocument ));
789     if( !xDiagram.is())
790         return;
791 
792     // set template at DialogModel
793     uno::Reference< lang::XMultiServiceFactory > xFact( m_xChartDocument->getChartTypeManager(), uno::UNO_QUERY );
794     DiagramHelper::tTemplateWithServiceName aTemplateAndService =
795         DiagramHelper::getTemplateForDiagram( xDiagram, xFact );
796     if( aTemplateAndService.first.is())
797         m_apDialogModel->setTemplate( aTemplateAndService.first );
798 
799     sal_Int32 nHeaderStart = 0;
800     sal_Int32 nHeaderEnd   = 0;
801     if( lcl_ShowCategories( xDiagram ))
802     {
803         Reference< frame::XModel > xChartModel( m_xChartDocument, uno::UNO_QUERY );
804         ExplicitCategoriesProvider aExplicitCategoriesProvider( ChartModelHelper::getFirstCoordinateSystem(xChartModel), xChartModel );
805 
806         const Sequence< Reference< chart2::data::XLabeledDataSequence> >& rSplitCategoriesList( aExplicitCategoriesProvider.getSplitCategoriesList() );
807         sal_Int32 nLevelCount = rSplitCategoriesList.getLength();
808         for( sal_Int32 nL = 0; nL<nLevelCount; nL++ )
809         {
810             Reference< chart2::data::XLabeledDataSequence > xCategories( rSplitCategoriesList[nL] );
811             if( !xCategories.is() )
812                 continue;
813 
814             tDataColumn aCategories;
815             aCategories.m_xLabeledDataSequence.set( xCategories );
816             if( lcl_ShowCategoriesAsDataLabel( xDiagram ))
817                 aCategories.m_aUIRoleName = DialogModel::GetRoleDataLabel();
818             else
819                 aCategories.m_aUIRoleName = lcl_getUIRoleName( xCategories );
820             aCategories.m_eCellType = TEXTORDATE;
821             m_aColumns.push_back( aCategories );
822             ++nHeaderStart;
823         }
824     }
825 
826     Reference< chart2::XCoordinateSystemContainer > xCooSysCnt( xDiagram, uno::UNO_QUERY );
827     if( !xCooSysCnt.is())
828         return;
829     Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq( xCooSysCnt->getCoordinateSystems());
830     for( sal_Int32 nCooSysIdx=0; nCooSysIdx<aCooSysSeq.getLength(); ++nCooSysIdx )
831     {
832         Reference< chart2::XChartTypeContainer > xCTCnt( aCooSysSeq[nCooSysIdx], uno::UNO_QUERY_THROW );
833         Sequence< Reference< chart2::XChartType > > aChartTypes( xCTCnt->getChartTypes());
834         sal_Int32 nXAxisNumberFormat = DataSeriesHelper::getNumberFormatKeyFromAxis( 0, aCooSysSeq[nCooSysIdx], 0, 0 );
835 
836         for( sal_Int32 nCTIdx=0; nCTIdx<aChartTypes.getLength(); ++nCTIdx )
837         {
838             Reference< chart2::XDataSeriesContainer > xSeriesCnt( aChartTypes[nCTIdx], uno::UNO_QUERY );
839             if( xSeriesCnt.is())
840             {
841                 rtl::OUString aRoleForDataLabelNumberFormat = ChartTypeHelper::getRoleOfSequenceForDataLabelNumberFormatDetection( aChartTypes[nCTIdx] );
842 
843                 Sequence< Reference< chart2::XDataSeries > > aSeries( xSeriesCnt->getDataSeries());
844                 lcl_tSharedSeqVec aSharedSequences( lcl_getSharedSequences( aSeries ));
845                 for( lcl_tSharedSeqVec::const_iterator aIt( aSharedSequences.begin());
846                      aIt != aSharedSequences.end(); ++aIt )
847                 {
848                     tDataColumn aSharedSequence;
849                     aSharedSequence.m_xLabeledDataSequence = *aIt;
850                     aSharedSequence.m_aUIRoleName = lcl_getUIRoleName( *aIt );
851                     aSharedSequence.m_eCellType = NUMBER;
852                     // as the sequences are shared it should be ok to take the first series
853                     // @todo: dimension index 0 for x-values used here. This is just a guess.
854                     // Also, the axis index is 0, as there is usually only one x-axis
855                     aSharedSequence.m_nNumberFormatKey = nXAxisNumberFormat;
856                     m_aColumns.push_back( aSharedSequence );
857                     ++nHeaderStart;
858                 }
859                 for( sal_Int32 nSeriesIdx=0; nSeriesIdx<aSeries.getLength(); ++nSeriesIdx )
860                 {
861                     tDataColumnVector::size_type nStartColIndex = m_aColumns.size();
862                     Reference< chart2::XDataSeries > xSeries( aSeries[nSeriesIdx] );
863                     Reference< chart2::data::XDataSource > xSource( xSeries, uno::UNO_QUERY );
864                     if( xSource.is())
865                     {
866                         Sequence< Reference< chart2::data::XLabeledDataSequence > > aLSeqs( xSource->getDataSequences());
867                         if( aLSeqs.getLength() == 0 )
868                             continue;
869                         nHeaderEnd = nHeaderStart;
870 
871                         // @todo: dimension index 1 for y-values used here. This is just a guess
872                         sal_Int32 nYAxisNumberFormatKey =
873                             DataSeriesHelper::getNumberFormatKeyFromAxis(
874                                 aSeries[nSeriesIdx], aCooSysSeq[nCooSysIdx], 1 );
875 
876                         sal_Int32 nSeqIdx=0;
877                         for( ; nSeqIdx<aLSeqs.getLength(); ++nSeqIdx )
878                         {
879                             sal_Int32 nSequenceNumberFormatKey = nYAxisNumberFormatKey;
880                             OUString aRole = lcl_getRole( aLSeqs[nSeqIdx] );
881 
882                             if( aRole.equals( aRoleForDataLabelNumberFormat ) )
883                             {
884                                 nSequenceNumberFormatKey = ExplicitValueProvider::getExplicitNumberFormatKeyForDataLabel(
885                                     Reference< beans::XPropertySet >( xSeries, uno::UNO_QUERY ), xSeries, -1, xDiagram );
886                             }
887                             else if( aRole.equals( C2U( "values-x" ) ) )
888                                 nSequenceNumberFormatKey = nXAxisNumberFormat;
889 
890                             if( ::std::find_if( aSharedSequences.begin(), aSharedSequences.end(),
891                                              lcl_RepresentationsOfLSeqMatch( aLSeqs[nSeqIdx] )) == aSharedSequences.end())
892                             {
893                                 // no shared sequence
894                                 m_aColumns.push_back(
895                                     tDataColumn(
896                                         aSeries[nSeriesIdx],
897                                         nSeqIdx,
898                                         lcl_getUIRoleName( aLSeqs[nSeqIdx] ),
899                                         aLSeqs[nSeqIdx],
900                                         NUMBER,
901                                         nSequenceNumberFormatKey ));
902                                 ++nHeaderEnd;
903                             }
904                             // else skip
905                         }
906                         bool bSwapXAndYAxis = false;
907                         try
908                         {
909                             Reference< beans::XPropertySet > xProp( aCooSysSeq[nCooSysIdx], uno::UNO_QUERY );
910                             xProp->getPropertyValue( OUString(RTL_CONSTASCII_USTRINGPARAM("SwapXAndYAxis"))) >>= bSwapXAndYAxis;
911                         }
912                         catch( const beans::UnknownPropertyException & ex )
913                         {
914                             (void)ex;
915                         }
916 
917                         // add ranges for error bars if present for a series
918                         if( StatisticsHelper::usesErrorBarRanges( aSeries[nSeriesIdx], /* bYError = */ true ))
919                             addErrorBarRanges( aSeries[nSeriesIdx], nYAxisNumberFormatKey, nSeqIdx, nHeaderEnd );
920 
921                         m_aHeaders.push_back(
922                             tDataHeader(
923                                 aSeries[nSeriesIdx],
924                                 aChartTypes[nCTIdx],
925                                 bSwapXAndYAxis,
926                                 nHeaderStart,
927                                 nHeaderEnd - 1 ));
928 
929                         nHeaderStart = nHeaderEnd;
930 
931                         ::std::sort( m_aColumns.begin() + nStartColIndex, m_aColumns.end(), implColumnLess() );
932                     }
933                 }
934             }
935         }
936     }
937 }
938 
addErrorBarRanges(const Reference<chart2::XDataSeries> & xDataSeries,sal_Int32 nNumberFormatKey,sal_Int32 & rInOutSequenceIndex,sal_Int32 & rInOutHeaderEnd)939 void DataBrowserModel::addErrorBarRanges(
940     const Reference< chart2::XDataSeries > & xDataSeries,
941     sal_Int32 nNumberFormatKey,
942     sal_Int32 & rInOutSequenceIndex,
943     sal_Int32 & rInOutHeaderEnd )
944 {
945     try
946     {
947         ::std::vector< Reference< chart2::data::XLabeledDataSequence > > aSequences;
948 
949         // x error bars
950         // ------------
951         Reference< chart2::data::XDataSource > xErrorSource(
952             StatisticsHelper::getErrorBars( xDataSeries, /* bYError = */ false ), uno::UNO_QUERY );
953 
954         // positive x error bars
955         Reference< chart2::data::XLabeledDataSequence > xErrorLSequence(
956             StatisticsHelper::getErrorLabeledDataSequenceFromDataSource(
957                 xErrorSource,
958                 /* bPositiveValue = */ true,
959                 /* bYError = */ false ));
960         if( xErrorLSequence.is())
961             aSequences.push_back( xErrorLSequence );
962 
963         // negative x error bars
964         xErrorLSequence.set(
965             StatisticsHelper::getErrorLabeledDataSequenceFromDataSource(
966                 xErrorSource,
967                 /* bPositiveValue = */ false,
968                 /* bYError = */ false ));
969         if( xErrorLSequence.is())
970             aSequences.push_back( xErrorLSequence );
971 
972         // y error bars
973         // ------------
974         xErrorSource.set(
975             StatisticsHelper::getErrorBars( xDataSeries, /* bYError = */ true ), uno::UNO_QUERY );
976 
977         // positive y error bars
978         xErrorLSequence.set(
979             StatisticsHelper::getErrorLabeledDataSequenceFromDataSource(
980                 xErrorSource,
981                 /* bPositiveValue = */ true,
982                 /* bYError = */ true ));
983         if( xErrorLSequence.is())
984             aSequences.push_back( xErrorLSequence );
985 
986         // negative y error bars
987         xErrorLSequence.set(
988             StatisticsHelper::getErrorLabeledDataSequenceFromDataSource(
989                 xErrorSource,
990                 /* bPositiveValue = */ false,
991                 /* bYError = */ true ));
992         if( xErrorLSequence.is())
993             aSequences.push_back( xErrorLSequence );
994 
995 
996         for( ::std::vector< Reference< chart2::data::XLabeledDataSequence > >::const_iterator aIt( aSequences.begin());
997              aIt != aSequences.end(); ++aIt )
998         {
999             m_aColumns.push_back(
1000                 tDataColumn(
1001                     xDataSeries,
1002                     rInOutSequenceIndex,
1003                     lcl_getUIRoleName( *aIt ),
1004                     *aIt,
1005                     NUMBER,
1006                     nNumberFormatKey ));
1007             ++rInOutSequenceIndex;
1008             ++rInOutHeaderEnd;
1009         }
1010     }
1011     catch( const uno::Exception & ex )
1012     {
1013         ASSERT_EXCEPTION( ex );
1014     }
1015 }
1016 
1017 } //  namespace chart
1018