tdf#130857 qt weld: Implement QtInstanceWidget::strip_mnemonic
[LibreOffice.git] / chart2 / source / view / main / VLegend.cxx
blobd98910468dec7d2a723525e69e4c0ae745c1fcd2
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 "VLegend.hxx"
21 #include "VButton.hxx"
22 #include <Legend.hxx>
23 #include <PropertyMapper.hxx>
24 #include <ChartModel.hxx>
25 #include <ObjectIdentifier.hxx>
26 #include <FormattedString.hxx>
27 #include <RelativePositionHelper.hxx>
28 #include <ShapeFactory.hxx>
29 #include <RelativeSizeHelper.hxx>
30 #include <LegendEntryProvider.hxx>
31 #include <chartview/DrawModelWrapper.hxx>
32 #include <com/sun/star/text/WritingMode2.hpp>
33 #include <com/sun/star/beans/XPropertySet.hpp>
34 #include <com/sun/star/drawing/TextHorizontalAdjust.hpp>
35 #include <com/sun/star/drawing/LineJoint.hpp>
36 #include <com/sun/star/chart/ChartLegendExpansion.hpp>
37 #include <com/sun/star/chart2/LegendPosition.hpp>
38 #include <com/sun/star/chart2/RelativePosition.hpp>
39 #include <com/sun/star/chart2/RelativeSize.hpp>
40 #include <com/sun/star/chart2/XFormattedString2.hpp>
41 #include <com/sun/star/chart2/data/XPivotTableDataProvider.hpp>
42 #include <com/sun/star/chart2/data/PivotTableFieldEntry.hpp>
43 #include <rtl/math.hxx>
44 #include <svl/ctloptions.hxx>
45 #include <comphelper/diagnose_ex.hxx>
46 #include <tools/UnitConversion.hxx>
48 #include <utility>
49 #include <vector>
50 #include <algorithm>
52 using namespace ::com::sun::star;
53 using namespace ::com::sun::star::chart2;
55 using ::com::sun::star::uno::Reference;
56 using ::com::sun::star::uno::Sequence;
58 namespace chart
61 namespace
64 typedef std::pair< ::chart::tNameSequence, ::chart::tAnySequence > tPropertyValues;
66 double lcl_CalcViewFontSize(
67 const Reference< beans::XPropertySet > & xProp,
68 const awt::Size & rReferenceSize )
70 double fResult = 10.0;
72 float fFontHeight( 0.0 );
73 if( xProp.is() && ( xProp->getPropertyValue( u"CharHeight"_ustr) >>= fFontHeight ))
75 fResult = fFontHeight;
76 try
78 awt::Size aPropRefSize;
79 if( (xProp->getPropertyValue( u"ReferencePageSize"_ustr) >>= aPropRefSize) &&
80 (aPropRefSize.Height > 0))
82 fResult = ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize );
85 catch( const uno::Exception & )
87 DBG_UNHANDLED_EXCEPTION("chart2");
91 return convertPointToMm100(fResult);
94 void lcl_getProperties(
95 const Reference< beans::XPropertySet > & xLegendProp,
96 tPropertyValues & rOutLineFillProperties,
97 tPropertyValues & rOutTextProperties,
98 const awt::Size & rReferenceSize )
100 // Get Line- and FillProperties from model legend
101 if( !xLegendProp.is())
102 return;
104 // set rOutLineFillProperties
105 ::chart::tPropertyNameValueMap aLineFillValueMap;
106 ::chart::PropertyMapper::getValueMap( aLineFillValueMap, ::chart::PropertyMapper::getPropertyNameMapForFillAndLineProperties(), xLegendProp );
108 aLineFillValueMap[ u"LineJoint"_ustr ] <<= drawing::LineJoint_ROUND;
110 ::chart::PropertyMapper::getMultiPropertyListsFromValueMap(
111 rOutLineFillProperties.first, rOutLineFillProperties.second, aLineFillValueMap );
113 // set rOutTextProperties
114 ::chart::tPropertyNameValueMap aTextValueMap;
115 ::chart::PropertyMapper::getValueMap( aTextValueMap, ::chart::PropertyMapper::getPropertyNameMapForCharacterProperties(), xLegendProp );
117 aTextValueMap[ u"TextAutoGrowHeight"_ustr ] <<= true;
118 aTextValueMap[ u"TextAutoGrowWidth"_ustr ] <<= true;
119 aTextValueMap[ u"TextHorizontalAdjust"_ustr ] <<= drawing::TextHorizontalAdjust_LEFT;
120 aTextValueMap[ u"TextMaximumFrameWidth"_ustr ] <<= rReferenceSize.Width; //needs to be overwritten by actual available space in the legend
122 // recalculate font size
123 awt::Size aPropRefSize;
124 float fFontHeight( 0.0 );
125 if( (xLegendProp->getPropertyValue( u"ReferencePageSize"_ustr) >>= aPropRefSize) &&
126 (aPropRefSize.Height > 0) &&
127 (aTextValueMap[ u"CharHeight"_ustr ] >>= fFontHeight) )
129 aTextValueMap[ u"CharHeight"_ustr ] <<=
130 static_cast< float >(
131 ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize ));
133 if( aTextValueMap[ u"CharHeightAsian"_ustr ] >>= fFontHeight )
135 aTextValueMap[ u"CharHeightAsian"_ustr ] <<=
136 static_cast< float >(
137 ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize ));
139 if( aTextValueMap[ u"CharHeightComplex"_ustr ] >>= fFontHeight )
141 aTextValueMap[ u"CharHeightComplex"_ustr ] <<=
142 static_cast< float >(
143 ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize ));
147 ::chart::PropertyMapper::getMultiPropertyListsFromValueMap(
148 rOutTextProperties.first, rOutTextProperties.second, aTextValueMap );
151 awt::Size lcl_createTextShapes(
152 const std::vector<ViewLegendEntry> & rEntries,
153 const rtl::Reference<SvxShapeGroupAnyD> & xTarget,
154 std::vector< rtl::Reference<SvxShapeText> > & rOutTextShapes,
155 const tPropertyValues & rTextProperties )
157 awt::Size aResult;
159 for (ViewLegendEntry const & rEntry : rEntries)
163 OUString aLabelString;
164 if (rEntry.xLabel)
166 // tdf#150034 limit legend label text
167 if (rEntry.xLabel->getString().getLength() > 520)
169 sal_Int32 nIndex = rEntry.xLabel->getString().indexOf(' ', 500);
170 rEntry.xLabel->setString(
171 rEntry.xLabel->getString().copy(0, nIndex > 500 ? nIndex : 500));
174 aLabelString += rEntry.xLabel->getString();
175 // workaround for Issue #i67540#
176 if( aLabelString.isEmpty())
177 aLabelString = " ";
180 rtl::Reference<SvxShapeText> xEntry =
181 ShapeFactory::createText( xTarget, aLabelString,
182 rTextProperties.first, rTextProperties.second, uno::Any() );
184 // adapt max-extent
185 awt::Size aCurrSize( xEntry->getSize());
186 aResult.Width = std::max( aResult.Width, aCurrSize.Width );
187 aResult.Height = std::max( aResult.Height, aCurrSize.Height );
189 rOutTextShapes.push_back( xEntry );
191 catch( const uno::Exception & )
193 DBG_UNHANDLED_EXCEPTION("chart2");
197 return aResult;
200 void lcl_collectColumnWidths( std::vector< sal_Int32 >& rColumnWidths, const sal_Int32 nNumberOfRows, const sal_Int32 nNumberOfColumns,
201 const std::vector< rtl::Reference<SvxShapeText> >& rTextShapes, sal_Int32 nSymbolPlusDistanceWidth )
203 rColumnWidths.clear();
204 sal_Int32 nNumberOfEntries = rTextShapes.size();
205 for (sal_Int32 nRow = 0; nRow < nNumberOfRows; ++nRow )
207 for (sal_Int32 nColumn = 0; nColumn < nNumberOfColumns; ++nColumn )
209 sal_Int32 nEntry = nColumn + nRow * nNumberOfColumns;
210 if( nEntry < nNumberOfEntries )
212 awt::Size aTextSize( rTextShapes[ nEntry ]->getSize() );
213 sal_Int32 nWidth = nSymbolPlusDistanceWidth + aTextSize.Width;
214 if( nRow==0 )
215 rColumnWidths.push_back( nWidth );
216 else
217 rColumnWidths[nColumn] = std::max( nWidth, rColumnWidths[nColumn] );
223 void lcl_collectRowHeighs( std::vector< sal_Int32 >& rRowHeights, const sal_Int32 nNumberOfRows, const sal_Int32 nNumberOfColumns,
224 const std::vector< rtl::Reference<SvxShapeText> >& rTextShapes )
226 // calculate maximum height for each row
227 // and collect column widths
228 rRowHeights.clear();
229 sal_Int32 nNumberOfEntries = rTextShapes.size();
230 for (sal_Int32 nRow = 0; nRow < nNumberOfRows; ++nRow)
232 sal_Int32 nCurrentRowHeight = 0;
233 for (sal_Int32 nColumn = 0; nColumn < nNumberOfColumns; ++nColumn)
235 sal_Int32 nEntry = nColumn + nRow * nNumberOfColumns;
236 if( nEntry < nNumberOfEntries )
238 awt::Size aTextSize( rTextShapes[ nEntry ]->getSize() );
239 nCurrentRowHeight = std::max( nCurrentRowHeight, aTextSize.Height );
242 rRowHeights.push_back( nCurrentRowHeight );
246 sal_Int32 lcl_getTextLineHeight( const std::vector< sal_Int32 >& aRowHeights, const sal_Int32 nNumberOfRows, double fViewFontSize )
248 const sal_Int32 nFontHeight = static_cast< sal_Int32 >( fViewFontSize );
249 if (!nFontHeight)
250 return 0;
251 sal_Int32 nTextLineHeight = nFontHeight;
252 for (sal_Int32 nRow = 0; nRow < nNumberOfRows; ++nRow)
254 sal_Int32 nFullTextHeight = aRowHeights[nRow];
255 if( ( nFullTextHeight / nFontHeight ) <= 1 )
257 nTextLineHeight = nFullTextHeight;//found an entry with one line-> have real text height
258 break;
261 return nTextLineHeight;
264 //returns resulting legend size
265 awt::Size lcl_placeLegendEntries(
266 std::vector<ViewLegendEntry> & rEntries,
267 css::chart::ChartLegendExpansion eExpansion,
268 bool bSymbolsLeftSide,
269 double fViewFontSize,
270 const awt::Size& rMaxSymbolExtent,
271 tPropertyValues & rTextProperties,
272 const rtl::Reference<SvxShapeGroupAnyD> & xTarget,
273 const awt::Size& rRemainingSpace,
274 sal_Int32 nYStartPosition,
275 const awt::Size& rPageSize,
276 bool bIsPivotChart,
277 awt::Size& rDefaultLegendSize)
279 bool bIsCustomSize = (eExpansion == css::chart::ChartLegendExpansion_CUSTOM);
280 awt::Size aResultingLegendSize(0,0);
281 // For Pivot charts set the *minimum* legend size as a function of page size.
282 if ( bIsPivotChart )
283 aResultingLegendSize = awt::Size((rPageSize.Width * 13) / 80, (rPageSize.Height * 31) / 90);
284 if( bIsCustomSize )
285 aResultingLegendSize = awt::Size(rRemainingSpace.Width, rRemainingSpace.Height + nYStartPosition);
287 // #i109336# Improve auto positioning in chart
288 sal_Int32 nXPadding = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.33 ) );
289 sal_Int32 nXOffset = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.66 ) );
290 sal_Int32 nYPadding = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.2 ) );
291 sal_Int32 nYOffset = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.2 ) );
293 const sal_Int32 nSymbolToTextDistance = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.22 ) );//minimum 1mm
294 const sal_Int32 nSymbolPlusDistanceWidth = rMaxSymbolExtent.Width + nSymbolToTextDistance;
295 sal_Int32 nMaxTextWidth = rRemainingSpace.Width - nSymbolPlusDistanceWidth;
296 uno::Any* pFrameWidthAny = PropertyMapper::getValuePointer( rTextProperties.second, rTextProperties.first, u"TextMaximumFrameWidth");
297 if(pFrameWidthAny)
299 if( eExpansion == css::chart::ChartLegendExpansion_HIGH )
301 // limit the width of texts to 30% of the total available width
302 // #i109336# Improve auto positioning in chart
303 nMaxTextWidth = rRemainingSpace.Width * 3 / 10;
305 *pFrameWidthAny <<= nMaxTextWidth;
308 std::vector< rtl::Reference<SvxShapeText> > aTextShapes;
309 awt::Size aMaxEntryExtent = lcl_createTextShapes( rEntries, xTarget, aTextShapes, rTextProperties );
310 OSL_ASSERT( aTextShapes.size() == rEntries.size());
312 sal_Int32 nMaxEntryWidth = nXOffset + nSymbolPlusDistanceWidth + aMaxEntryExtent.Width;
313 sal_Int32 nMaxEntryHeight = nYOffset + aMaxEntryExtent.Height;
314 sal_Int32 nNumberOfEntries = rEntries.size();
316 rDefaultLegendSize.Width = nMaxEntryWidth;
317 rDefaultLegendSize.Height = nMaxEntryHeight + nYPadding;
319 sal_Int32 nNumberOfColumns = 0, nNumberOfRows = 0;
320 std::vector< sal_Int32 > aColumnWidths;
321 std::vector< sal_Int32 > aRowHeights;
323 sal_Int32 nTextLineHeight = static_cast< sal_Int32 >( fViewFontSize );
325 // determine layout depending on LegendExpansion
326 if( eExpansion == css::chart::ChartLegendExpansion_CUSTOM )
328 sal_Int32 nCurrentRow=0;
329 sal_Int32 nCurrentColumn=-1;
330 sal_Int32 nMaxColumnCount=-1;
331 for( sal_Int32 nN=0; nN<static_cast<sal_Int32>(aTextShapes.size()); nN++ )
333 const rtl::Reference<SvxShapeText>& xShape( aTextShapes[nN] );
334 if( !xShape.is() )
335 continue;
336 awt::Size aSize( xShape->getSize() );
337 sal_Int32 nNewWidth = aSize.Width + nSymbolPlusDistanceWidth;
338 sal_Int32 nCurrentColumnCount = aColumnWidths.size();
340 //are we allowed to add a new column?
341 if( nMaxColumnCount==-1 || (nCurrentColumn+1) < nMaxColumnCount )
343 //try add a new column
344 nCurrentColumn++;
345 if( nCurrentColumn < nCurrentColumnCount )
347 //check whether the current column width is sufficient for the new entry
348 if( aColumnWidths[nCurrentColumn]>=nNewWidth )
350 //all good proceed with next entry
351 continue;
354 aColumnWidths[nCurrentColumn] = std::max( nNewWidth, aColumnWidths[nCurrentColumn] );
355 } else
356 aColumnWidths.push_back(nNewWidth);
358 //do the columns still fit into the given size?
359 nCurrentColumnCount = aColumnWidths.size();//update count
360 sal_Int32 nSumWidth = 0;
361 for (sal_Int32 nColumn = 0; nColumn < nCurrentColumnCount; nColumn++)
362 nSumWidth += aColumnWidths[nColumn];
364 if( nSumWidth <= rRemainingSpace.Width || nCurrentColumnCount==1 )
366 //all good proceed with next entry
367 continue;
369 else
371 //not enough space for the current amount of columns
372 //try again with less columns
373 nMaxColumnCount = nCurrentColumnCount-1;
374 nN=-1;
375 nCurrentRow=0;
376 nCurrentColumn=-1;
377 aColumnWidths.clear();
380 else
382 //add a new row and try the same entry again
383 nCurrentRow++;
384 nCurrentColumn=-1;
385 nN--;
388 nNumberOfColumns = aColumnWidths.size();
389 nNumberOfRows = nCurrentRow+1;
391 //check if there is not enough space so that some entries must be removed
392 lcl_collectRowHeighs( aRowHeights, nNumberOfRows, nNumberOfColumns, aTextShapes );
393 nTextLineHeight = lcl_getTextLineHeight( aRowHeights, nNumberOfRows, fViewFontSize );
394 sal_Int32 nSumHeight = 0;
395 for (sal_Int32 nRow=0; nRow < nNumberOfRows; nRow++)
396 nSumHeight += aRowHeights[nRow];
397 sal_Int32 nRemainingSpace = rRemainingSpace.Height - nSumHeight;
399 if( nRemainingSpace < -100 ) // 1mm tolerance for OOXML interop tdf#90404
401 //remove entries that are too big
402 for (sal_Int32 nRow = nNumberOfRows; nRow--; )
404 for (sal_Int32 nColumn = nNumberOfColumns; nColumn--; )
406 sal_Int32 nEntry = nColumn + nRow * nNumberOfColumns;
407 if( nEntry < static_cast<sal_Int32>(aTextShapes.size()) )
409 DrawModelWrapper::removeShape( aTextShapes[nEntry] );
410 aTextShapes.pop_back();
412 if( nEntry < nNumberOfEntries && ( nEntry != 0 || nNumberOfColumns != 1 ) )
414 DrawModelWrapper::removeShape( rEntries[ nEntry ].xSymbol );
415 rEntries.pop_back();
416 nNumberOfEntries--;
419 if (nRow == 0 && nNumberOfColumns == 1)
423 OUString aLabelString = rEntries[0].xLabel->getString();
424 static constexpr OUString sDots = u"..."_ustr;
425 for (sal_Int32 nNewLen = aLabelString.getLength() - sDots.getLength(); nNewLen > 0; )
427 OUString aNewLabel = aLabelString.subView(0, nNewLen) + sDots;
428 rtl::Reference<SvxShapeText> xEntry = ShapeFactory::createText(
429 xTarget, aNewLabel, rTextProperties.first, rTextProperties.second, uno::Any());
430 nSumHeight = xEntry->getSize().Height;
431 nRemainingSpace = rRemainingSpace.Height - nSumHeight;
432 if (nRemainingSpace >= 0)
434 sal_Int32 nWidth = xEntry->getSize().Width + nSymbolPlusDistanceWidth;
435 if (rRemainingSpace.Width - nWidth >= 0)
437 aTextShapes.push_back(xEntry);
438 rEntries[0].xLabel->setString(aNewLabel);
439 aRowHeights[0] = nSumHeight;
440 aColumnWidths[0] = nWidth;
441 break;
444 DrawModelWrapper::removeShape(xEntry);
445 // The intention here is to make pathological cases with extremely large labels
446 // converge a little faster
447 if (nNewLen > 10 && std::abs(nRemainingSpace) > nSumHeight / 10)
448 nNewLen -= nNewLen / 10;
449 else
450 --nNewLen;
452 if (aTextShapes.empty())
454 DrawModelWrapper::removeShape(rEntries[0].xSymbol);
455 rEntries.pop_back();
456 nNumberOfEntries--;
457 aRowHeights.pop_back();
460 catch (const uno::Exception&)
462 DBG_UNHANDLED_EXCEPTION("chart2");
465 else
467 nSumHeight -= aRowHeights[nRow];
468 aRowHeights.pop_back();
469 nRemainingSpace = rRemainingSpace.Height - nSumHeight;
470 if (nRemainingSpace >= 0)
471 break;
474 nNumberOfRows = static_cast<sal_Int32>(aRowHeights.size());
476 if( nRemainingSpace >= -100 ) // 1mm tolerance for OOXML interop tdf#90404
478 sal_Int32 nNormalSpacingHeight = 2*nYPadding+(nNumberOfRows-1)*nYOffset;
479 if( nRemainingSpace < nNormalSpacingHeight )
481 //reduce spacing between the entries
482 nYPadding = nYOffset = nRemainingSpace/(nNumberOfRows+1);
484 else
486 //we have some space left that should be spread equally between all rows
487 sal_Int32 nRemainingSingleSpace = (nRemainingSpace-nNormalSpacingHeight)/(nNumberOfRows+1);
488 nYPadding += nRemainingSingleSpace;
489 nYOffset += nRemainingSingleSpace;
493 //check spacing between columns
494 sal_Int32 nSumWidth = 0;
495 for (sal_Int32 nColumn = 0; nColumn < nNumberOfColumns; nColumn++)
496 nSumWidth += aColumnWidths[nColumn];
497 nRemainingSpace = rRemainingSpace.Width - nSumWidth;
498 if( nRemainingSpace>=0 )
500 sal_Int32 nNormalSpacingWidth = 2*nXPadding+(nNumberOfColumns-1)*nXOffset;
501 if( nRemainingSpace < nNormalSpacingWidth )
503 //reduce spacing between the entries
504 nXPadding = nXOffset = nRemainingSpace/(nNumberOfColumns+1);
506 else
508 //we have some space left that should be spread equally between all columns
509 sal_Int32 nRemainingSingleSpace = (nRemainingSpace-nNormalSpacingWidth)/(nNumberOfColumns+1);
510 nXPadding += nRemainingSingleSpace;
511 nXOffset += nRemainingSingleSpace;
515 else if( eExpansion == css::chart::ChartLegendExpansion_HIGH )
517 sal_Int32 nMaxNumberOfRows = nMaxEntryHeight
518 ? (rRemainingSpace.Height - 2*nYPadding ) / nMaxEntryHeight
519 : 0;
521 nNumberOfColumns = nMaxNumberOfRows
522 ? static_cast< sal_Int32 >(
523 ceil( static_cast< double >( nNumberOfEntries ) /
524 static_cast< double >( nMaxNumberOfRows ) ))
525 : 0;
526 nNumberOfRows = nNumberOfColumns
527 ? static_cast< sal_Int32 >(
528 ceil( static_cast< double >( nNumberOfEntries ) /
529 static_cast< double >( nNumberOfColumns ) ))
530 : 0;
532 else if( eExpansion == css::chart::ChartLegendExpansion_WIDE )
534 sal_Int32 nMaxNumberOfColumns = nMaxEntryWidth
535 ? (rRemainingSpace.Width - 2*nXPadding ) / nMaxEntryWidth
536 : 0;
538 nNumberOfRows = nMaxNumberOfColumns
539 ? static_cast< sal_Int32 >(
540 ceil( static_cast< double >( nNumberOfEntries ) /
541 static_cast< double >( nMaxNumberOfColumns ) ))
542 : 0;
543 nNumberOfColumns = nNumberOfRows
544 ? static_cast< sal_Int32 >(
545 ceil( static_cast< double >( nNumberOfEntries ) /
546 static_cast< double >( nNumberOfRows ) ))
547 : 0;
549 else // css::chart::ChartLegendExpansion_BALANCED
551 double fAspect = nMaxEntryHeight
552 ? static_cast< double >( nMaxEntryWidth ) / static_cast< double >( nMaxEntryHeight )
553 : 0.0;
555 nNumberOfRows = static_cast< sal_Int32 >(
556 ceil( sqrt( static_cast< double >( nNumberOfEntries ) * fAspect )));
557 nNumberOfColumns = nNumberOfRows
558 ? static_cast< sal_Int32 >(
559 ceil( static_cast< double >( nNumberOfEntries ) /
560 static_cast< double >( nNumberOfRows ) ))
561 : 0;
564 if(nNumberOfRows<=0)
565 return aResultingLegendSize;
567 if( eExpansion != css::chart::ChartLegendExpansion_CUSTOM )
569 lcl_collectColumnWidths( aColumnWidths, nNumberOfRows, nNumberOfColumns, aTextShapes, nSymbolPlusDistanceWidth );
570 lcl_collectRowHeighs( aRowHeights, nNumberOfRows, nNumberOfColumns, aTextShapes );
571 nTextLineHeight = lcl_getTextLineHeight( aRowHeights, nNumberOfRows, fViewFontSize );
574 sal_Int32 nCurrentXPos = bSymbolsLeftSide ? nXPadding : -nXPadding;
576 // place entries into column and rows
577 sal_Int32 nMaxYPos = 0;
579 for (sal_Int32 nColumn = 0; nColumn < nNumberOfColumns; ++nColumn)
581 sal_Int32 nCurrentYPos = nYPadding + nYStartPosition;
582 for (sal_Int32 nRow = 0; nRow < nNumberOfRows; ++nRow)
584 sal_Int32 nEntry = nColumn + nRow * nNumberOfColumns;
585 if( nEntry >= nNumberOfEntries )
586 break;
588 // text shape
589 const rtl::Reference<SvxShapeText>& xTextShape( aTextShapes[nEntry] );
590 if( xTextShape.is() )
592 awt::Size aTextSize( xTextShape->getSize() );
593 sal_Int32 nTextXPos = nCurrentXPos + nSymbolPlusDistanceWidth;
594 if( !bSymbolsLeftSide )
595 nTextXPos = nCurrentXPos - nSymbolPlusDistanceWidth - aTextSize.Width;
596 xTextShape->setPosition( awt::Point( nTextXPos, nCurrentYPos ));
599 // symbol
600 rtl::Reference<SvxShapeGroup> & xSymbol( rEntries[ nEntry ].xSymbol );
601 if( xSymbol.is() )
603 awt::Size aSymbolSize( rMaxSymbolExtent );
604 sal_Int32 nSymbolXPos = nCurrentXPos;
605 if( !bSymbolsLeftSide )
606 nSymbolXPos = nCurrentXPos - rMaxSymbolExtent.Width;
607 sal_Int32 nSymbolYPos = nCurrentYPos + ( ( nTextLineHeight - aSymbolSize.Height ) / 2 );
608 xSymbol->setPosition( awt::Point( nSymbolXPos, nSymbolYPos ) );
611 nCurrentYPos += aRowHeights[ nRow ];
612 if( nRow+1 < nNumberOfRows )
613 nCurrentYPos += nYOffset;
614 nMaxYPos = std::max( nMaxYPos, nCurrentYPos );
616 if( bSymbolsLeftSide )
618 nCurrentXPos += aColumnWidths[nColumn];
619 if( nColumn+1 < nNumberOfColumns )
620 nCurrentXPos += nXOffset;
622 else
624 nCurrentXPos -= aColumnWidths[nColumn];
625 if( nColumn+1 < nNumberOfColumns )
626 nCurrentXPos -= nXOffset;
630 if( !bIsCustomSize )
632 if( bSymbolsLeftSide )
633 aResultingLegendSize.Width = std::max( aResultingLegendSize.Width, nCurrentXPos + nXPadding );
634 else
636 sal_Int32 nLegendWidth = -(nCurrentXPos-nXPadding);
637 aResultingLegendSize.Width = std::max( aResultingLegendSize.Width, nLegendWidth );
639 aResultingLegendSize.Height = std::max( aResultingLegendSize.Height, nMaxYPos + nYPadding );
642 if( !bSymbolsLeftSide )
644 sal_Int32 nLegendWidth = aResultingLegendSize.Width;
645 awt::Point aPos(0,0);
646 for( sal_Int32 nEntry=0; nEntry<nNumberOfEntries; nEntry++ )
648 rtl::Reference<SvxShapeGroup> & xSymbol( rEntries[ nEntry ].xSymbol );
649 aPos = xSymbol->getPosition();
650 aPos.X += nLegendWidth;
651 xSymbol->setPosition( aPos );
652 rtl::Reference<SvxShapeText> & xText( aTextShapes[ nEntry ] );
653 aPos = xText->getPosition();
654 aPos.X += nLegendWidth;
655 xText->setPosition( aPos );
659 return aResultingLegendSize;
662 // #i109336# Improve auto positioning in chart
663 sal_Int32 lcl_getLegendLeftRightMargin()
665 return 210; // 1/100 mm
668 // #i109336# Improve auto positioning in chart
669 sal_Int32 lcl_getLegendTopBottomMargin()
671 return 185; // 1/100 mm
674 chart2::RelativePosition lcl_getDefaultPosition( LegendPosition ePos, const awt::Rectangle& rOutAvailableSpace, const awt::Size & rPageSize )
676 chart2::RelativePosition aResult;
678 switch( ePos )
680 case LegendPosition_LINE_START:
682 // #i109336# Improve auto positioning in chart
683 const double fDefaultDistance = static_cast< double >( lcl_getLegendLeftRightMargin() ) /
684 static_cast< double >( rPageSize.Width );
685 aResult = chart2::RelativePosition(
686 fDefaultDistance, 0.5, drawing::Alignment_LEFT );
688 break;
689 case LegendPosition_LINE_END:
691 // #i109336# Improve auto positioning in chart
692 const double fDefaultDistance = static_cast< double >( lcl_getLegendLeftRightMargin() ) /
693 static_cast< double >( rPageSize.Width );
694 aResult = chart2::RelativePosition(
695 1.0 - fDefaultDistance, 0.5, drawing::Alignment_RIGHT );
697 break;
698 case LegendPosition_PAGE_START:
700 // #i109336# Improve auto positioning in chart
701 const double fDefaultDistance = static_cast< double >( lcl_getLegendTopBottomMargin() ) /
702 static_cast< double >( rPageSize.Height );
703 double fDistance = (static_cast<double>(rOutAvailableSpace.Y)/static_cast<double>(rPageSize.Height)) + fDefaultDistance;
704 aResult = chart2::RelativePosition(
705 0.5, fDistance, drawing::Alignment_TOP );
707 break;
708 case LegendPosition_PAGE_END:
710 // #i109336# Improve auto positioning in chart
711 const double fDefaultDistance = static_cast< double >( lcl_getLegendTopBottomMargin() ) /
712 static_cast< double >( rPageSize.Height );
714 double fDistance = double(rPageSize.Height - (rOutAvailableSpace.Y + rOutAvailableSpace.Height));
715 fDistance += fDefaultDistance;
716 fDistance /= double(rPageSize.Height);
718 aResult = chart2::RelativePosition(
719 0.5, 1.0 - fDistance, drawing::Alignment_BOTTOM );
721 break;
722 case LegendPosition::LegendPosition_MAKE_FIXED_SIZE:
723 default:
724 // nothing to be set
725 break;
728 return aResult;
731 /** @return
732 a point relative to the upper left corner that can be used for
733 XShape::setPosition()
735 awt::Point lcl_calculatePositionAndRemainingSpace(
736 awt::Rectangle & rRemainingSpace,
737 const awt::Size & rPageSize,
738 const chart2::RelativePosition& rRelPos,
739 LegendPosition ePos,
740 const awt::Size& aLegendSize,
741 bool bOverlay )
743 // calculate position
744 awt::Point aResult(
745 static_cast< sal_Int32 >( rRelPos.Primary * rPageSize.Width ),
746 static_cast< sal_Int32 >( rRelPos.Secondary * rPageSize.Height ));
748 aResult = RelativePositionHelper::getUpperLeftCornerOfAnchoredObject(
749 aResult, aLegendSize, rRelPos.Anchor );
751 // adapt rRemainingSpace if LegendPosition is not CUSTOM
752 // #i109336# Improve auto positioning in chart
753 sal_Int32 nXDistance = lcl_getLegendLeftRightMargin();
754 sal_Int32 nYDistance = lcl_getLegendTopBottomMargin();
755 if (!bOverlay) switch( ePos )
757 case LegendPosition_LINE_START:
759 sal_Int32 nExtent = aLegendSize.Width;
760 rRemainingSpace.Width -= ( nExtent + nXDistance );
761 rRemainingSpace.X += ( nExtent + nXDistance );
763 break;
764 case LegendPosition_LINE_END:
766 rRemainingSpace.Width -= ( aLegendSize.Width + nXDistance );
768 break;
769 case LegendPosition_PAGE_START:
771 sal_Int32 nExtent = aLegendSize.Height;
772 rRemainingSpace.Height -= ( nExtent + nYDistance );
773 rRemainingSpace.Y += ( nExtent + nYDistance );
775 break;
776 case LegendPosition_PAGE_END:
778 rRemainingSpace.Height -= ( aLegendSize.Height + nYDistance );
780 break;
782 default:
783 // nothing
784 break;
787 // adjust the legend position. Esp. for old files that had slightly smaller legends
788 const sal_Int32 nEdgeDistance( 30 );
789 if( aResult.X + aLegendSize.Width > rPageSize.Width )
791 sal_Int32 nNewX( (rPageSize.Width - aLegendSize.Width) - nEdgeDistance );
792 if( nNewX > rPageSize.Width / 4 )
793 aResult.X = nNewX;
795 if( aResult.Y + aLegendSize.Height > rPageSize.Height )
797 sal_Int32 nNewY( (rPageSize.Height - aLegendSize.Height) - nEdgeDistance );
798 if( nNewY > rPageSize.Height / 4 )
799 aResult.Y = nNewY;
802 return aResult;
805 bool lcl_shouldSymbolsBePlacedOnTheLeftSide( const Reference< beans::XPropertySet >& xLegendProp, sal_Int16 nDefaultWritingMode )
807 bool bSymbolsLeftSide = true;
810 if( SvtCTLOptions::IsCTLFontEnabled() )
812 if(xLegendProp.is())
814 sal_Int16 nWritingMode=-1;
815 if( xLegendProp->getPropertyValue( u"WritingMode"_ustr ) >>= nWritingMode )
817 if( nWritingMode == text::WritingMode2::PAGE )
818 nWritingMode = nDefaultWritingMode;
819 if( nWritingMode == text::WritingMode2::RL_TB )
820 bSymbolsLeftSide=false;
825 catch( const uno::Exception & )
827 DBG_UNHANDLED_EXCEPTION("chart2");
829 return bSymbolsLeftSide;
832 std::vector<std::shared_ptr<VButton>> lcl_createButtons(
833 rtl::Reference<SvxShapeGroupAnyD> const & xLegendContainer,
834 ChartModel& rModel, bool bPlaceButtonsVertically, tools::Long & nUsedHeight)
836 std::vector<std::shared_ptr<VButton>> aButtons;
838 uno::Reference<chart2::data::XPivotTableDataProvider> xPivotTableDataProvider(rModel.getDataProvider(), uno::UNO_QUERY);
839 if (!xPivotTableDataProvider.is())
840 return aButtons;
842 if (!xPivotTableDataProvider->getColumnFields().hasElements())
843 return aButtons;
845 awt::Size aSize(2000, 700);
846 int x = 100;
847 int y = 100;
849 const css::uno::Sequence<chart2::data::PivotTableFieldEntry> aPivotFieldEntries = xPivotTableDataProvider->getColumnFields();
850 for (chart2::data::PivotTableFieldEntry const & sColumnFieldEntry : aPivotFieldEntries)
852 auto pButton = std::make_shared<VButton>();
853 aButtons.push_back(pButton);
854 pButton->init(xLegendContainer);
855 awt::Point aNewPosition(x, y);
856 pButton->setLabel(sColumnFieldEntry.Name);
857 pButton->setCID("FieldButton.Column." + OUString::number(sColumnFieldEntry.DimensionIndex));
858 pButton->setPosition(aNewPosition);
859 pButton->setSize(aSize);
860 if (sColumnFieldEntry.Name == "Data")
862 pButton->showArrow(false);
863 pButton->setBGColor(Color(0x00F6F6F6));
865 if (sColumnFieldEntry.HasHiddenMembers)
866 pButton->setArrowColor(Color(0x0000FF));
868 if (bPlaceButtonsVertically)
869 y += aSize.Height + 100;
870 else
871 x += aSize.Width + 100;
873 if (bPlaceButtonsVertically)
874 nUsedHeight += y + 100;
875 else
876 nUsedHeight += aSize.Height + 100;
878 return aButtons;
881 } // anonymous namespace
883 VLegend::VLegend(
884 rtl::Reference< Legend > xLegend,
885 const Reference< uno::XComponentContext > & xContext,
886 std::vector< LegendEntryProvider* >&& rLegendEntryProviderList,
887 rtl::Reference<SvxShapeGroupAnyD> xTargetPage,
888 ChartModel& rModel )
889 : m_xTarget(std::move(xTargetPage))
890 , m_xLegend(std::move(xLegend))
891 , mrModel(rModel)
892 , m_xContext(xContext)
893 , m_aLegendEntryProviderList(std::move(rLegendEntryProviderList))
894 , m_nDefaultWritingMode(text::WritingMode2::LR_TB)
898 void VLegend::setDefaultWritingMode( sal_Int16 nDefaultWritingMode )
900 m_nDefaultWritingMode = nDefaultWritingMode;
903 bool VLegend::isVisible( const rtl::Reference< Legend > & xLegend )
905 if( ! xLegend.is())
906 return false;
908 bool bShow = false;
911 xLegend->getPropertyValue( u"Show"_ustr) >>= bShow;
913 catch( const uno::Exception & )
915 DBG_UNHANDLED_EXCEPTION("chart2");
918 return bShow;
921 void VLegend::createShapes(
922 const awt::Size & rAvailableSpace,
923 const awt::Size & rPageSize,
924 awt::Size & rDefaultLegendSize )
926 if(! (m_xLegend.is() && m_xTarget.is()))
927 return;
931 //create shape and add to page
932 OUString aLegendParticle( ObjectIdentifier::createParticleForLegend( &mrModel ) );
933 m_xShape = ShapeFactory::createGroup2D( m_xTarget,
934 ObjectIdentifier::createClassifiedIdentifierForParticle( aLegendParticle ) );
936 // create and insert sub-shapes
937 rtl::Reference<SvxShapeGroupAnyD> xLegendContainer = m_xShape;
938 if( xLegendContainer.is() )
940 // for quickly setting properties
941 tPropertyValues aLineFillProperties;
942 tPropertyValues aTextProperties;
944 css::chart::ChartLegendExpansion eExpansion = css::chart::ChartLegendExpansion_HIGH;
945 awt::Size aLegendSize( rAvailableSpace );
947 bool bCustom = false;
948 LegendPosition eLegendPosition = LegendPosition_LINE_END;
949 // get Expansion property
950 m_xLegend->getPropertyValue(u"Expansion"_ustr) >>= eExpansion;
951 if( eExpansion == css::chart::ChartLegendExpansion_CUSTOM )
953 RelativeSize aRelativeSize;
954 if (m_xLegend->getPropertyValue(u"RelativeSize"_ustr) >>= aRelativeSize)
956 aLegendSize.Width = static_cast<sal_Int32>(::rtl::math::approxCeil( aRelativeSize.Primary * rPageSize.Width ));
957 aLegendSize.Height = static_cast<sal_Int32>(::rtl::math::approxCeil( aRelativeSize.Secondary * rPageSize.Height ));
958 bCustom = true;
960 else
962 eExpansion = css::chart::ChartLegendExpansion_HIGH;
965 m_xLegend->getPropertyValue(u"AnchorPosition"_ustr) >>= eLegendPosition;
966 lcl_getProperties( m_xLegend, aLineFillProperties, aTextProperties, rPageSize );
968 // create entries
969 double fViewFontSize = lcl_CalcViewFontSize( m_xLegend, rPageSize );//todo
970 // #i109336# Improve auto positioning in chart
971 sal_Int32 nSymbolHeight = static_cast< sal_Int32 >( fViewFontSize * 0.6 );
972 sal_Int32 nSymbolWidth = nSymbolHeight;
974 for (LegendEntryProvider* pLegendEntryProvider : m_aLegendEntryProviderList)
976 if (pLegendEntryProvider)
978 awt::Size aCurrentRatio = pLegendEntryProvider->getPreferredLegendKeyAspectRatio();
979 sal_Int32 nCurrentWidth = aCurrentRatio.Width;
980 if( aCurrentRatio.Height > 0 )
982 nCurrentWidth = nSymbolHeight* aCurrentRatio.Width/aCurrentRatio.Height;
984 nSymbolWidth = std::max( nSymbolWidth, nCurrentWidth );
987 awt::Size aMaxSymbolExtent( nSymbolWidth, nSymbolHeight );
989 std::vector<ViewLegendEntry> aViewEntries;
990 for(LegendEntryProvider* pLegendEntryProvider : m_aLegendEntryProviderList)
992 if (pLegendEntryProvider)
994 std::vector<ViewLegendEntry> aNewEntries = pLegendEntryProvider->createLegendEntries(
995 aMaxSymbolExtent, eLegendPosition, m_xLegend,
996 xLegendContainer, m_xContext, mrModel);
997 aViewEntries.insert( aViewEntries.end(), aNewEntries.begin(), aNewEntries.end() );
1001 bool bSymbolsLeftSide = lcl_shouldSymbolsBePlacedOnTheLeftSide( m_xLegend, m_nDefaultWritingMode );
1003 uno::Reference<chart2::data::XPivotTableDataProvider> xPivotTableDataProvider( mrModel.getDataProvider(), uno::UNO_QUERY );
1004 bool bIsPivotChart = xPivotTableDataProvider.is();
1006 if ( !aViewEntries.empty() || bIsPivotChart )
1008 // create buttons
1009 tools::Long nUsedButtonHeight = 0;
1010 bool bPlaceButtonsVertically = (eLegendPosition != LegendPosition_PAGE_START &&
1011 eLegendPosition != LegendPosition_PAGE_END &&
1012 eExpansion != css::chart::ChartLegendExpansion_WIDE);
1014 std::vector<std::shared_ptr<VButton>> aButtons = lcl_createButtons(xLegendContainer, mrModel, bPlaceButtonsVertically, nUsedButtonHeight);
1016 // A custom size includes the size we used for buttons already, so we need to
1017 // subtract that from the size that is available for the legend
1018 if (bCustom)
1019 aLegendSize.Height -= nUsedButtonHeight;
1021 // place the legend entries
1022 aLegendSize = lcl_placeLegendEntries(aViewEntries, eExpansion, bSymbolsLeftSide, fViewFontSize,
1023 aMaxSymbolExtent, aTextProperties, xLegendContainer,
1024 aLegendSize, nUsedButtonHeight, rPageSize, bIsPivotChart, rDefaultLegendSize);
1026 uno::Reference<beans::XPropertySet> xModelPage(mrModel.getPageBackground());
1028 for (std::shared_ptr<VButton> const & pButton : aButtons)
1030 // adjust the width of the buttons if we place them vertically
1031 if (bPlaceButtonsVertically)
1032 pButton->setSize({aLegendSize.Width - 200, pButton->getSize().Height});
1034 // create the buttons
1035 pButton->createShapes(xModelPage);
1038 rtl::Reference<SvxShapeRect> xBorder = ShapeFactory::createRectangle(
1039 xLegendContainer, aLegendSize, awt::Point(0, 0), aLineFillProperties.first,
1040 aLineFillProperties.second, ShapeFactory::StackPosition::Bottom);
1042 //because of this name this border will be used for marking the legend
1043 ShapeFactory::setShapeName(xBorder, u"MarkHandles"_ustr);
1047 catch( const uno::Exception & )
1049 DBG_UNHANDLED_EXCEPTION("chart2" );
1053 void VLegend::changePosition(
1054 awt::Rectangle & rOutAvailableSpace,
1055 const awt::Size & rPageSize,
1056 const css::awt::Size & rDefaultLegendSize )
1058 if(! m_xShape.is())
1059 return;
1063 // determine position and alignment depending on default position
1064 awt::Size aLegendSize = m_xShape->getSize();
1065 chart2::RelativePosition aRelativePosition;
1067 bool bDefaultLegendSize = rDefaultLegendSize.Width != 0 || rDefaultLegendSize.Height != 0;
1068 bool bAutoPosition =
1069 ! (m_xLegend->getPropertyValue( u"RelativePosition"_ustr) >>= aRelativePosition);
1071 LegendPosition ePos = LegendPosition_LINE_END;
1072 m_xLegend->getPropertyValue( u"AnchorPosition"_ustr) >>= ePos;
1074 bool bOverlay = false;
1075 m_xLegend->getPropertyValue(u"Overlay"_ustr) >>= bOverlay;
1076 //calculate position
1077 if( bAutoPosition )
1079 // auto position: relative to remaining space
1080 aRelativePosition = lcl_getDefaultPosition( ePos, rOutAvailableSpace, rPageSize );
1081 awt::Point aPos = lcl_calculatePositionAndRemainingSpace(
1082 rOutAvailableSpace, rPageSize, aRelativePosition, ePos, aLegendSize, bOverlay );
1083 m_xShape->setPosition( aPos );
1085 else
1087 // manual position: relative to whole page
1088 awt::Rectangle aAvailableSpace( 0, 0, rPageSize.Width, rPageSize.Height );
1089 awt::Point aPos = lcl_calculatePositionAndRemainingSpace(
1090 aAvailableSpace, rPageSize, aRelativePosition, ePos, bDefaultLegendSize ? rDefaultLegendSize : aLegendSize, bOverlay );
1091 m_xShape->setPosition( aPos );
1093 if (!bOverlay)
1095 // calculate remaining space as if having autoposition:
1096 aRelativePosition = lcl_getDefaultPosition( ePos, rOutAvailableSpace, rPageSize );
1097 lcl_calculatePositionAndRemainingSpace(
1098 rOutAvailableSpace, rPageSize, aRelativePosition, ePos, bDefaultLegendSize ? rDefaultLegendSize : aLegendSize, bOverlay );
1102 catch( const uno::Exception & )
1104 DBG_UNHANDLED_EXCEPTION("chart2" );
1108 } //namespace chart
1110 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */