Avoid potential negative array index access to cached text.
[LibreOffice.git] / chart2 / source / tools / DiagramHelper.cxx
blob79a99688fd7a5a7be40b643623e15d1502bb2367
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 <DiagramHelper.hxx>
21 #include <Diagram.hxx>
22 #include <DataSeries.hxx>
23 #include <DataSeriesHelper.hxx>
24 #include <Axis.hxx>
25 #include <AxisHelper.hxx>
26 #include <ChartType.hxx>
27 #include <ChartTypeHelper.hxx>
28 #include <ChartTypeManager.hxx>
29 #include <ChartTypeTemplate.hxx>
30 #include <ChartModel.hxx>
31 #include <ChartModelHelper.hxx>
32 #include <ExplicitCategoriesProvider.hxx>
33 #include <servicenames_charttypes.hxx>
34 #include <RelativePositionHelper.hxx>
35 #include <ControllerLockGuard.hxx>
36 #include <NumberFormatterWrapper.hxx>
37 #include <unonames.hxx>
38 #include <BaseCoordinateSystem.hxx>
40 #include <com/sun/star/chart/MissingValueTreatment.hpp>
41 #include <com/sun/star/chart/XDiagramPositioning.hpp>
42 #include <com/sun/star/chart2/XAnyDescriptionAccess.hpp>
43 #include <com/sun/star/chart2/AxisType.hpp>
44 #include <com/sun/star/chart2/DataPointGeometry3D.hpp>
45 #include <com/sun/star/chart2/RelativePosition.hpp>
46 #include <com/sun/star/chart2/RelativeSize.hpp>
47 #include <com/sun/star/chart2/StackingDirection.hpp>
49 #include <com/sun/star/util/CloseVetoException.hpp>
50 #include <com/sun/star/util/NumberFormat.hpp>
51 #include <com/sun/star/util/XNumberFormatsSupplier.hpp>
53 #include <o3tl/safeint.hxx>
54 #include <unotools/saveopt.hxx>
55 #include <rtl/math.hxx>
56 #include <svl/numformat.hxx>
57 #include <svl/zforlist.hxx>
58 #include <vcl/svapp.hxx>
59 #include <vcl/settings.hxx>
60 #include <comphelper/sequence.hxx>
61 #include <comphelper/diagnose_ex.hxx>
62 #include <sal/log.hxx>
64 #include <cstddef>
65 #include <limits>
67 using namespace ::com::sun::star;
68 using namespace ::com::sun::star::chart2;
70 using ::com::sun::star::uno::Reference;
71 using ::com::sun::star::uno::Sequence;
72 using ::com::sun::star::uno::Any;
73 using ::com::sun::star::chart2::XAnyDescriptionAccess;
75 namespace chart
78 StackMode DiagramHelper::getStackModeFromChartType(
79 const rtl::Reference< ChartType > & xChartType,
80 bool& rbFound, bool& rbAmbiguous,
81 const rtl::Reference< BaseCoordinateSystem > & xCorrespondingCoordinateSystem )
83 StackMode eStackMode = StackMode::NONE;
84 rbFound = false;
85 rbAmbiguous = false;
87 try
89 const std::vector< rtl::Reference< DataSeries > > & aSeries = xChartType->getDataSeries2();
91 chart2::StackingDirection eCommonDirection = chart2::StackingDirection_NO_STACKING;
92 bool bDirectionInitialized = false;
94 // first series is irrelevant for stacking, start with second, unless
95 // there is only one series
96 const sal_Int32 nSeriesCount = aSeries.size();
97 sal_Int32 i = (nSeriesCount == 1) ? 0: 1;
98 for( ; i<nSeriesCount; ++i )
100 rbFound = true;
101 chart2::StackingDirection eCurrentDirection = eCommonDirection;
102 // property is not MAYBEVOID
103 bool bSuccess = ( aSeries[i]->getPropertyValue( "StackingDirection" ) >>= eCurrentDirection );
104 OSL_ASSERT( bSuccess );
105 if( ! bDirectionInitialized )
107 eCommonDirection = eCurrentDirection;
108 bDirectionInitialized = true;
110 else
112 if( eCommonDirection != eCurrentDirection )
114 rbAmbiguous = true;
115 break;
120 if( rbFound )
122 if( eCommonDirection == chart2::StackingDirection_Z_STACKING )
123 eStackMode = StackMode::ZStacked;
124 else if( eCommonDirection == chart2::StackingDirection_Y_STACKING )
126 eStackMode = StackMode::YStacked;
128 // percent stacking
129 if( xCorrespondingCoordinateSystem.is() )
131 if( 1 < xCorrespondingCoordinateSystem->getDimension() )
133 sal_Int32 nAxisIndex = 0;
134 if( nSeriesCount )
135 nAxisIndex = DataSeriesHelper::getAttachedAxisIndex(aSeries[0]);
137 rtl::Reference< Axis > xAxis =
138 xCorrespondingCoordinateSystem->getAxisByDimension2( 1,nAxisIndex );
139 if( xAxis.is())
141 chart2::ScaleData aScaleData = xAxis->getScaleData();
142 if( aScaleData.AxisType==chart2::AxisType::PERCENT )
143 eStackMode = StackMode::YStackedPercent;
150 catch( const uno::Exception & )
152 DBG_UNHANDLED_EXCEPTION("chart2");
155 return eStackMode;
158 bool DiagramHelper::isSeriesAttachedToMainAxis(
159 const rtl::Reference< ::chart::DataSeries >& xDataSeries )
161 sal_Int32 nAxisIndex = DataSeriesHelper::getAttachedAxisIndex(xDataSeries);
162 return (nAxisIndex==0);
165 static void lcl_generateAutomaticCategoriesFromChartType(
166 Sequence< OUString >& rRet,
167 const rtl::Reference< ChartType >& xChartType )
169 if(!xChartType.is())
170 return;
171 OUString aMainSeq( xChartType->getRoleOfSequenceForSeriesLabel() );
173 const std::vector< rtl::Reference< DataSeries > > & aSeriesSeq = xChartType->getDataSeries2();
174 for( rtl::Reference< DataSeries > const & dataSeries : aSeriesSeq )
176 uno::Reference< data::XLabeledDataSequence > xLabeledSeq =
177 ::chart::DataSeriesHelper::getDataSequenceByRole( dataSeries, aMainSeq );
178 if( !xLabeledSeq.is() )
179 continue;
180 Reference< chart2::data::XDataSequence > xValueSeq( xLabeledSeq->getValues() );
181 if( !xValueSeq.is() )
182 continue;
183 rRet = xValueSeq->generateLabel( chart2::data::LabelOrigin_LONG_SIDE );
184 if( rRet.hasElements() )
185 return;
189 Sequence< OUString > DiagramHelper::generateAutomaticCategoriesFromCooSys( const rtl::Reference< BaseCoordinateSystem > & xCooSys )
191 Sequence< OUString > aRet;
193 if( xCooSys.is() )
195 const std::vector< rtl::Reference< ChartType > > & aChartTypes( xCooSys->getChartTypes2() );
196 for( rtl::Reference< ChartType > const & chartType : aChartTypes )
198 lcl_generateAutomaticCategoriesFromChartType( aRet, chartType );
199 if( aRet.hasElements() )
200 return aRet;
203 return aRet;
206 Sequence< OUString > DiagramHelper::getExplicitSimpleCategories(
207 ChartModel& rModel )
209 rtl::Reference< BaseCoordinateSystem > xCooSys( ChartModelHelper::getFirstCoordinateSystem( &rModel ) );
210 ExplicitCategoriesProvider aExplicitCategoriesProvider( xCooSys, rModel );
211 return aExplicitCategoriesProvider.getSimpleCategories();
214 namespace
216 void lcl_switchToDateCategories( const rtl::Reference< ChartModel >& xChartDoc, const Reference< XAxis >& xAxis )
218 if( !xAxis.is() )
219 return;
220 if( !xChartDoc.is() )
221 return;
223 ScaleData aScale( xAxis->getScaleData() );
224 if( xChartDoc->hasInternalDataProvider() )
226 //remove all content the is not of type double and remove multiple level
227 Reference< XAnyDescriptionAccess > xDataAccess( xChartDoc->getDataProvider(), uno::UNO_QUERY );
228 if( xDataAccess.is() )
230 Sequence< Sequence< Any > > aAnyCategories( xDataAccess->getAnyRowDescriptions() );
231 auto aAnyCategoriesRange = asNonConstRange(aAnyCategories);
232 double fTest = 0.0;
233 sal_Int32 nN = aAnyCategories.getLength();
234 for( ; nN--; )
236 Sequence< Any >& rCat = aAnyCategoriesRange[nN];
237 if( rCat.getLength() > 1 )
238 rCat.realloc(1);
239 if( rCat.getLength() == 1 )
241 Any& rAny = rCat.getArray()[0];
242 if( !(rAny>>=fTest) )
244 rAny <<= std::numeric_limits<double>::quiet_NaN();
248 xDataAccess->setAnyRowDescriptions( aAnyCategories );
250 //check the numberformat at the axis
251 Reference< beans::XPropertySet > xAxisProps( xAxis, uno::UNO_QUERY );
252 if( xAxisProps.is() )
254 sal_Int32 nNumberFormat = -1;
255 xAxisProps->getPropertyValue(CHART_UNONAME_NUMFMT) >>= nNumberFormat;
257 Reference< util::XNumberFormats > xNumberFormats( xChartDoc->getNumberFormats() );
258 if( xNumberFormats.is() )
260 Reference< beans::XPropertySet > xKeyProps;
263 xKeyProps = xNumberFormats->getByKey( nNumberFormat );
265 catch( const uno::Exception & )
267 DBG_UNHANDLED_EXCEPTION("chart2");
269 sal_Int32 nType = util::NumberFormat::UNDEFINED;
270 if( xKeyProps.is() )
271 xKeyProps->getPropertyValue( "Type" ) >>= nType;
272 if( !( nType & util::NumberFormat::DATE ) )
274 //set a date format to the axis
275 const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
276 Sequence<sal_Int32> aKeySeq = xNumberFormats->queryKeys( util::NumberFormat::DATE, rLocaleDataWrapper.getLanguageTag().getLocale(), true/*bCreate*/ );
277 if( aKeySeq.hasElements() )
279 xAxisProps->setPropertyValue(CHART_UNONAME_NUMFMT, uno::Any(aKeySeq[0]));
285 if( aScale.AxisType != chart2::AxisType::DATE )
286 AxisHelper::removeExplicitScaling( aScale );
287 aScale.AxisType = chart2::AxisType::DATE;
288 xAxis->setScaleData( aScale );
291 void lcl_switchToTextCategories( const rtl::Reference< ChartModel >& xChartDoc, const Reference< XAxis >& xAxis )
293 if( !xAxis.is() )
294 return;
295 if( !xChartDoc.is() )
296 return;
297 ScaleData aScale( xAxis->getScaleData() );
298 if( aScale.AxisType != chart2::AxisType::CATEGORY )
299 AxisHelper::removeExplicitScaling( aScale );
300 //todo migrate dates to text?
301 aScale.AxisType = chart2::AxisType::CATEGORY;
302 aScale.AutoDateAxis = false;
303 xAxis->setScaleData( aScale );
308 void DiagramHelper::switchToDateCategories( const rtl::Reference<::chart::ChartModel>& xChartDoc )
310 if(xChartDoc.is())
312 ControllerLockGuardUNO aCtrlLockGuard( xChartDoc );
314 rtl::Reference< BaseCoordinateSystem > xCooSys = ChartModelHelper::getFirstCoordinateSystem( xChartDoc );
315 if( xCooSys.is() )
317 rtl::Reference< Axis > xAxis = xCooSys->getAxisByDimension2(0,0);
318 lcl_switchToDateCategories( xChartDoc, xAxis );
323 void DiagramHelper::switchToTextCategories( const rtl::Reference<::chart::ChartModel>& xChartDoc )
325 if(xChartDoc.is())
327 ControllerLockGuardUNO aCtrlLockGuard( xChartDoc );
329 rtl::Reference< BaseCoordinateSystem > xCooSys = ChartModelHelper::getFirstCoordinateSystem( xChartDoc );
330 if( xCooSys.is() )
332 rtl::Reference< Axis > xAxis = xCooSys->getAxisByDimension2(0,0);
333 lcl_switchToTextCategories( xChartDoc, xAxis );
338 bool DiagramHelper::isDateNumberFormat( sal_Int32 nNumberFormat, const Reference< util::XNumberFormats >& xNumberFormats )
340 bool bIsDate = false;
341 if( !xNumberFormats.is() )
342 return bIsDate;
344 Reference< beans::XPropertySet > xKeyProps = xNumberFormats->getByKey( nNumberFormat );
345 if( xKeyProps.is() )
347 sal_Int32 nType = util::NumberFormat::UNDEFINED;
348 xKeyProps->getPropertyValue( "Type" ) >>= nType;
349 bIsDate = nType & util::NumberFormat::DATE;
351 return bIsDate;
354 sal_Int32 DiagramHelper::getDateNumberFormat( const Reference< util::XNumberFormatsSupplier >& xNumberFormatsSupplier )
356 sal_Int32 nRet=-1;
358 //try to get a date format with full year display
359 const LanguageTag& rLanguageTag = Application::GetSettings().GetLanguageTag();
360 NumberFormatterWrapper aNumberFormatterWrapper( xNumberFormatsSupplier );
361 SvNumberFormatter* pNumFormatter = aNumberFormatterWrapper.getSvNumberFormatter();
362 if( pNumFormatter )
364 nRet = pNumFormatter->GetFormatIndex( NF_DATE_SYS_DDMMYYYY, rLanguageTag.getLanguageType() );
366 else
368 Reference< util::XNumberFormats > xNumberFormats( xNumberFormatsSupplier->getNumberFormats() );
369 if( xNumberFormats.is() )
371 Sequence<sal_Int32> aKeySeq = xNumberFormats->queryKeys( util::NumberFormat::DATE,
372 rLanguageTag.getLocale(), true/*bCreate */);
373 if( aKeySeq.hasElements() )
375 nRet = aKeySeq[0];
379 return nRet;
382 sal_Int32 DiagramHelper::getDateTimeInputNumberFormat( const Reference< util::XNumberFormatsSupplier >& xNumberFormatsSupplier, double fNumber )
384 sal_Int32 nRet = 0;
386 // Get the most detailed date/time format according to fNumber.
387 NumberFormatterWrapper aNumberFormatterWrapper( xNumberFormatsSupplier );
388 SvNumberFormatter* pNumFormatter = aNumberFormatterWrapper.getSvNumberFormatter();
389 if (!pNumFormatter)
390 SAL_WARN("chart2", "DiagramHelper::getDateTimeInputNumberFormat - no SvNumberFormatter");
391 else
393 SvNumFormatType nType;
394 // Obtain best matching date, time or datetime format.
395 nRet = pNumFormatter->GuessDateTimeFormat( nType, fNumber, LANGUAGE_SYSTEM);
396 // Obtain the corresponding edit format.
397 nRet = pNumFormatter->GetEditFormat( fNumber, nRet, nType, nullptr);
399 return nRet;
402 sal_Int32 DiagramHelper::getPercentNumberFormat( const Reference< util::XNumberFormatsSupplier >& xNumberFormatsSupplier )
404 sal_Int32 nRet=-1;
405 const LanguageTag& rLanguageTag = Application::GetSettings().GetLanguageTag();
406 NumberFormatterWrapper aNumberFormatterWrapper( xNumberFormatsSupplier );
407 SvNumberFormatter* pNumFormatter = aNumberFormatterWrapper.getSvNumberFormatter();
408 if( pNumFormatter )
410 nRet = pNumFormatter->GetFormatIndex( NF_PERCENT_INT, rLanguageTag.getLanguageType() );
412 else
414 Reference< util::XNumberFormats > xNumberFormats( xNumberFormatsSupplier->getNumberFormats() );
415 if( xNumberFormats.is() )
417 Sequence<sal_Int32> aKeySeq = xNumberFormats->queryKeys( util::NumberFormat::PERCENT,
418 rLanguageTag.getLocale(), true/*bCreate*/ );
419 if( aKeySeq.hasElements() )
421 // This *assumes* the sequence is sorted as in
422 // NfIndexTableOffset and the first format is the integer 0%
423 // format by chance... which usually is the case, but... anyway,
424 // we usually also have a number formatter so don't reach here.
425 nRet = aKeySeq[0];
429 return nRet;
432 bool DiagramHelper::areChartTypesCompatible( const rtl::Reference< ChartType >& xFirstType,
433 const rtl::Reference< ChartType >& xSecondType )
435 if( !xFirstType.is() || !xSecondType.is() )
436 return false;
438 auto aFirstRoles( comphelper::sequenceToContainer<std::vector< OUString >>( xFirstType->getSupportedMandatoryRoles() ) );
439 auto aSecondRoles( comphelper::sequenceToContainer<std::vector< OUString >>( xSecondType->getSupportedMandatoryRoles() ) );
440 std::sort( aFirstRoles.begin(), aFirstRoles.end() );
441 std::sort( aSecondRoles.begin(), aSecondRoles.end() );
442 return ( aFirstRoles == aSecondRoles );
445 static void lcl_ensureRange0to1( double& rValue )
447 if(rValue<0.0)
448 rValue=0.0;
449 if(rValue>1.0)
450 rValue=1.0;
453 bool DiagramHelper::setDiagramPositioning( const rtl::Reference<::chart::ChartModel>& xChartModel,
454 const awt::Rectangle& rPosRect /*100th mm*/ )
456 ControllerLockGuardUNO aCtrlLockGuard( xChartModel );
458 bool bChanged = false;
459 awt::Size aPageSize( ChartModelHelper::getPageSize(xChartModel) );
460 rtl::Reference< Diagram > xDiagram = xChartModel->getFirstChartDiagram();
461 if( !xDiagram.is() )
462 return bChanged;
464 RelativePosition aOldPos;
465 RelativeSize aOldSize;
466 xDiagram->getPropertyValue("RelativePosition" ) >>= aOldPos;
467 xDiagram->getPropertyValue("RelativeSize" ) >>= aOldSize;
469 RelativePosition aNewPos;
470 aNewPos.Anchor = drawing::Alignment_TOP_LEFT;
471 aNewPos.Primary = double(rPosRect.X)/double(aPageSize.Width);
472 aNewPos.Secondary = double(rPosRect.Y)/double(aPageSize.Height);
474 chart2::RelativeSize aNewSize;
475 aNewSize.Primary = double(rPosRect.Width)/double(aPageSize.Width);
476 aNewSize.Secondary = double(rPosRect.Height)/double(aPageSize.Height);
478 lcl_ensureRange0to1( aNewPos.Primary );
479 lcl_ensureRange0to1( aNewPos.Secondary );
480 lcl_ensureRange0to1( aNewSize.Primary );
481 lcl_ensureRange0to1( aNewSize.Secondary );
482 if( (aNewPos.Primary + aNewSize.Primary) > 1.0 )
483 aNewPos.Primary = 1.0 - aNewSize.Primary;
484 if( (aNewPos.Secondary + aNewSize.Secondary) > 1.0 )
485 aNewPos.Secondary = 1.0 - aNewSize.Secondary;
487 xDiagram->setPropertyValue( "RelativePosition", uno::Any(aNewPos) );
488 xDiagram->setPropertyValue( "RelativeSize", uno::Any(aNewSize) );
490 bChanged = (aOldPos.Anchor!=aNewPos.Anchor) ||
491 (aOldPos.Primary!=aNewPos.Primary) ||
492 (aOldPos.Secondary!=aNewPos.Secondary) ||
493 (aOldSize.Primary!=aNewSize.Primary) ||
494 (aOldSize.Secondary!=aNewSize.Secondary);
495 return bChanged;
498 awt::Rectangle DiagramHelper::getDiagramRectangleFromModel( const rtl::Reference<::chart::ChartModel>& xChartModel )
500 awt::Rectangle aRet(-1,-1,-1,-1);
502 rtl::Reference< Diagram > xDiagram = xChartModel->getFirstChartDiagram();
503 if( !xDiagram.is() )
504 return aRet;
506 awt::Size aPageSize( ChartModelHelper::getPageSize(xChartModel) );
508 RelativePosition aRelPos;
509 RelativeSize aRelSize;
510 xDiagram->getPropertyValue("RelativePosition" ) >>= aRelPos;
511 xDiagram->getPropertyValue("RelativeSize" ) >>= aRelSize;
513 awt::Size aAbsSize(
514 static_cast< sal_Int32 >( aRelSize.Primary * aPageSize.Width ),
515 static_cast< sal_Int32 >( aRelSize.Secondary * aPageSize.Height ));
517 awt::Point aAbsPos(
518 static_cast< sal_Int32 >( aRelPos.Primary * aPageSize.Width ),
519 static_cast< sal_Int32 >( aRelPos.Secondary * aPageSize.Height ));
521 awt::Point aAbsPosLeftTop = RelativePositionHelper::getUpperLeftCornerOfAnchoredObject( aAbsPos, aAbsSize, aRelPos.Anchor );
523 aRet = awt::Rectangle(aAbsPosLeftTop.X, aAbsPosLeftTop.Y, aAbsSize.Width, aAbsSize.Height );
525 return aRet;
528 bool DiagramHelper::switchDiagramPositioningToExcludingPositioning(
529 ChartModel& rModel, bool bResetModifiedState, bool bConvertAlsoFromAutoPositioning )
531 //return true if something was changed
532 const SvtSaveOptions::ODFSaneDefaultVersion nCurrentODFVersion(GetODFSaneDefaultVersion());
533 if (SvtSaveOptions::ODFSVER_012 < nCurrentODFVersion)
535 uno::Reference< css::chart::XDiagramPositioning > xDiagramPositioning( rModel.getFirstDiagram(), uno::UNO_QUERY );
536 if( xDiagramPositioning.is() && ( bConvertAlsoFromAutoPositioning || !xDiagramPositioning->isAutomaticDiagramPositioning() )
537 && !xDiagramPositioning->isExcludingDiagramPositioning() )
539 ControllerLockGuard aCtrlLockGuard( rModel );
540 bool bModelWasModified = rModel.isModified();
541 xDiagramPositioning->setDiagramPositionExcludingAxes( xDiagramPositioning->calculateDiagramPositionExcludingAxes() );
542 if(bResetModifiedState && !bModelWasModified )
543 rModel.setModified(false);
544 return true;
547 return false;
550 } // namespace chart
552 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */