Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /*
3 : * This file is part of the LibreOffice project.
4 : *
5 : * This Source Code Form is subject to the terms of the Mozilla Public
6 : * License, v. 2.0. If a copy of the MPL was not distributed with this
7 : * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 : *
9 : * This file incorporates work covered by the following license notice:
10 : *
11 : * Licensed to the Apache Software Foundation (ASF) under one or more
12 : * contributor license agreements. See the NOTICE file distributed
13 : * with this work for additional information regarding copyright
14 : * ownership. The ASF licenses this file to you under the Apache
15 : * License, Version 2.0 (the "License"); you may not use this file
16 : * except in compliance with the License. You may obtain a copy of
17 : * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 : */
19 :
20 : #include "VLegend.hxx"
21 : #include "macros.hxx"
22 : #include "PropertyMapper.hxx"
23 : #include "CommonConverters.hxx"
24 : #include "ObjectIdentifier.hxx"
25 : #include "RelativePositionHelper.hxx"
26 : #include "AbstractShapeFactory.hxx"
27 : #include "RelativeSizeHelper.hxx"
28 : #include "LegendEntryProvider.hxx"
29 : #include "chartview/DrawModelWrapper.hxx"
30 : #include <com/sun/star/text/XTextRange.hpp>
31 : #include <com/sun/star/text/WritingMode2.hpp>
32 : #include <com/sun/star/beans/XPropertySet.hpp>
33 : #include <com/sun/star/beans/XPropertyState.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 <rtl/ustrbuf.hxx>
40 : #include <svl/languageoptions.hxx>
41 :
42 : #include <vector>
43 : #include <algorithm>
44 :
45 : using namespace ::com::sun::star;
46 : using namespace ::com::sun::star::chart2;
47 :
48 : using ::com::sun::star::uno::Reference;
49 : using ::com::sun::star::uno::Sequence;
50 :
51 : namespace chart
52 : {
53 :
54 : namespace
55 : {
56 :
57 : typedef ::std::pair< ::chart::tNameSequence, ::chart::tAnySequence > tPropertyValues;
58 :
59 : typedef ::std::vector< ViewLegendEntry > tViewLegendEntryContainer;
60 :
61 930 : double lcl_CalcViewFontSize(
62 : const Reference< beans::XPropertySet > & xProp,
63 : const awt::Size & rReferenceSize )
64 : {
65 930 : double fResult = 10.0;
66 :
67 930 : awt::Size aPropRefSize;
68 930 : float fFontHeight( 0.0 );
69 930 : if( xProp.is() && ( xProp->getPropertyValue( "CharHeight") >>= fFontHeight ))
70 : {
71 930 : fResult = fFontHeight;
72 : try
73 : {
74 930 : if( (xProp->getPropertyValue( "ReferencePageSize") >>= aPropRefSize) &&
75 0 : (aPropRefSize.Height > 0))
76 : {
77 0 : fResult = ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize );
78 : }
79 : }
80 0 : catch( const uno::Exception & ex )
81 : {
82 : ASSERT_EXCEPTION( ex );
83 : }
84 : }
85 :
86 : // pt -> 1/100th mm
87 930 : return (fResult * (2540.0 / 72.0));
88 : }
89 :
90 930 : void lcl_getProperties(
91 : const Reference< beans::XPropertySet > & xLegendProp,
92 : tPropertyValues & rOutLineFillProperties,
93 : tPropertyValues & rOutTextProperties,
94 : const awt::Size & rReferenceSize )
95 : {
96 : // Get Line- and FillProperties from model legend
97 930 : if( xLegendProp.is())
98 : {
99 : // set rOutLineFillProperties
100 930 : ::chart::tPropertyNameValueMap aLineFillValueMap;
101 930 : ::chart::PropertyMapper::getValueMap( aLineFillValueMap, ::chart::PropertyMapper::getPropertyNameMapForFillAndLineProperties(), xLegendProp );
102 :
103 930 : aLineFillValueMap[ "LineJoint" ] = uno::makeAny( drawing::LineJoint_ROUND );
104 :
105 : ::chart::PropertyMapper::getMultiPropertyListsFromValueMap(
106 930 : rOutLineFillProperties.first, rOutLineFillProperties.second, aLineFillValueMap );
107 :
108 : // set rOutTextProperties
109 1860 : ::chart::tPropertyNameValueMap aTextValueMap;
110 930 : ::chart::PropertyMapper::getValueMap( aTextValueMap, ::chart::PropertyMapper::getPropertyNameMapForCharacterProperties(), xLegendProp );
111 :
112 930 : drawing::TextHorizontalAdjust eHorizAdjust( drawing::TextHorizontalAdjust_LEFT );
113 930 : aTextValueMap[ "TextAutoGrowHeight" ] = uno::makeAny( sal_True );
114 930 : aTextValueMap[ "TextAutoGrowWidth" ] = uno::makeAny( sal_True );
115 930 : aTextValueMap[ "TextHorizontalAdjust" ] = uno::makeAny( eHorizAdjust );
116 930 : aTextValueMap[ "TextMaximumFrameWidth" ] = uno::makeAny( rReferenceSize.Width ); //needs to be overwritten by actual available space in the legend
117 :
118 : // recalculate font size
119 930 : awt::Size aPropRefSize;
120 930 : float fFontHeight( 0.0 );
121 3720 : if( (xLegendProp->getPropertyValue( "ReferencePageSize") >>= aPropRefSize) &&
122 2790 : (aPropRefSize.Height > 0) &&
123 930 : (aTextValueMap[ "CharHeight" ] >>= fFontHeight) )
124 : {
125 0 : aTextValueMap[ "CharHeight" ] = uno::makeAny(
126 : static_cast< float >(
127 0 : ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize )));
128 :
129 0 : if( aTextValueMap[ "CharHeightAsian" ] >>= fFontHeight )
130 : {
131 0 : aTextValueMap[ "CharHeightAsian" ] = uno::makeAny(
132 : static_cast< float >(
133 0 : ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize )));
134 : }
135 0 : if( aTextValueMap[ "CharHeightComplex" ] >>= fFontHeight )
136 : {
137 0 : aTextValueMap[ "CharHeightComplex" ] = uno::makeAny(
138 : static_cast< float >(
139 0 : ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize )));
140 : }
141 : }
142 :
143 : ::chart::PropertyMapper::getMultiPropertyListsFromValueMap(
144 1860 : rOutTextProperties.first, rOutTextProperties.second, aTextValueMap );
145 : }
146 930 : }
147 :
148 928 : awt::Size lcl_createTextShapes(
149 : const tViewLegendEntryContainer & rEntries,
150 : const Reference< lang::XMultiServiceFactory > & xShapeFactory,
151 : const Reference< drawing::XShapes > & xTarget,
152 : ::std::vector< Reference< drawing::XShape > > & rOutTextShapes,
153 : const tPropertyValues & rTextProperties )
154 : {
155 928 : awt::Size aResult;
156 928 : AbstractShapeFactory* pShapeFactory = AbstractShapeFactory::getOrCreateShapeFactory(xShapeFactory);
157 :
158 10908 : for( tViewLegendEntryContainer::const_iterator aIt( rEntries.begin());
159 7272 : aIt != rEntries.end(); ++aIt )
160 : {
161 : try
162 : {
163 2708 : OUString aLabelString;
164 5416 : Sequence< Reference< XFormattedString2 > > aLabelSeq = (*aIt).aLabel;
165 5416 : for( sal_Int32 i = 0; i < aLabelSeq.getLength(); ++i )
166 : {
167 : // todo: support more than one text range
168 2708 : if( i == 1 )
169 0 : break;
170 :
171 2708 : aLabelString = aLabelString + aLabelSeq[i]->getString();
172 : // workaround for Issue #i67540#
173 2708 : if( aLabelString.isEmpty())
174 6 : aLabelString = " ";
175 : }
176 :
177 : Reference< drawing::XShape > xEntry =
178 : pShapeFactory->createText( xTarget, aLabelString,
179 5416 : rTextProperties.first, rTextProperties.second, uno::Any() );
180 :
181 : // adapt max-extent
182 2708 : awt::Size aCurrSize( xEntry->getSize());
183 2708 : aResult.Width = ::std::max( aResult.Width, aCurrSize.Width );
184 2708 : aResult.Height = ::std::max( aResult.Height, aCurrSize.Height );
185 :
186 5416 : rOutTextShapes.push_back( xEntry );
187 : }
188 0 : catch( const uno::Exception & ex )
189 : {
190 : ASSERT_EXCEPTION( ex );
191 : }
192 : }
193 :
194 928 : return aResult;
195 : }
196 :
197 923 : void lcl_collectColumnWidths( std::vector< sal_Int32 >& rColumnWidths, const sal_Int32 nNumberOfRows, const sal_Int32 nNumberOfColumns,
198 : const ::std::vector< Reference< drawing::XShape > >& rTextShapes, sal_Int32 nSymbolPlusDistanceWidth )
199 : {
200 923 : rColumnWidths.clear();
201 923 : sal_Int32 nRow = 0;
202 923 : sal_Int32 nColumn = 0;
203 923 : sal_Int32 nNumberOfEntries = rTextShapes.size();
204 3545 : for( ; nRow < nNumberOfRows; ++nRow )
205 : {
206 5338 : for( nColumn = 0; nColumn < nNumberOfColumns; ++nColumn )
207 : {
208 2716 : sal_Int32 nEntry = (nColumn + nRow * nNumberOfColumns);
209 2716 : if( nEntry < nNumberOfEntries )
210 : {
211 2695 : awt::Size aTextSize( rTextShapes[ nEntry ]->getSize() );
212 2695 : sal_Int32 nWidth = nSymbolPlusDistanceWidth + aTextSize.Width;
213 2695 : if( nRow==0 )
214 996 : rColumnWidths.push_back( nWidth );
215 : else
216 1699 : rColumnWidths[nColumn] = ::std::max( nWidth, rColumnWidths[nColumn] );
217 : }
218 : }
219 : }
220 923 : }
221 :
222 928 : void lcl_collectRowHeighs( std::vector< sal_Int32 >& rRowHeights, const sal_Int32 nNumberOfRows, const sal_Int32 nNumberOfColumns,
223 : const ::std::vector< Reference< drawing::XShape > >& rTextShapes )
224 : {
225 : // calculate maximum height for each row
226 : // and collect column widths
227 928 : rRowHeights.clear();
228 928 : sal_Int32 nRow = 0;
229 928 : sal_Int32 nColumn = 0;
230 928 : sal_Int32 nNumberOfEntries = rTextShapes.size();
231 3561 : for( ; nRow < nNumberOfRows; ++nRow )
232 : {
233 2633 : sal_Int32 nCurrentRowHeight = 0;
234 5362 : for( nColumn = 0; nColumn < nNumberOfColumns; ++nColumn )
235 : {
236 2729 : sal_Int32 nEntry = (nColumn + nRow * nNumberOfColumns);
237 2729 : if( nEntry < nNumberOfEntries )
238 : {
239 2708 : awt::Size aTextSize( rTextShapes[ nEntry ]->getSize() );
240 2708 : nCurrentRowHeight = ::std::max( nCurrentRowHeight, aTextSize.Height );
241 : }
242 : }
243 2633 : rRowHeights.push_back( nCurrentRowHeight );
244 : }
245 928 : }
246 :
247 928 : sal_Int32 lcl_getTextLineHeight( const std::vector< sal_Int32 >& aRowHeights, const sal_Int32 nNumberOfRows, double fViewFontSize )
248 : {
249 928 : const sal_Int32 nFontHeight = static_cast< sal_Int32 >( fViewFontSize );
250 928 : sal_Int32 nTextLineHeight = nFontHeight;
251 928 : for( sal_Int32 nR=0; nR<nNumberOfRows; nR++ )
252 : {
253 928 : sal_Int32 nFullTextHeight = aRowHeights[ nR ];
254 928 : if( ( nFullTextHeight / nFontHeight ) <= 1 )
255 : {
256 928 : nTextLineHeight = nFullTextHeight;//found an entry with one line-> have real text height
257 928 : break;
258 : }
259 : }
260 928 : return nTextLineHeight;
261 : }
262 :
263 : //returns resulting legend size
264 928 : awt::Size lcl_placeLegendEntries(
265 : tViewLegendEntryContainer & rEntries,
266 : ::com::sun::star::chart::ChartLegendExpansion eExpansion,
267 : bool bSymbolsLeftSide,
268 : double fViewFontSize,
269 : const awt::Size& rMaxSymbolExtent,
270 : tPropertyValues & rTextProperties,
271 : const Reference< drawing::XShapes > & xTarget,
272 : const Reference< lang::XMultiServiceFactory > & xShapeFactory,
273 : const awt::Size & rAvailableSpace )
274 : {
275 928 : bool bIsCustomSize = (eExpansion == ::com::sun::star::chart::ChartLegendExpansion_CUSTOM);
276 928 : awt::Size aResultingLegendSize(0,0);
277 928 : if( bIsCustomSize )
278 5 : aResultingLegendSize = rAvailableSpace;
279 :
280 : // #i109336# Improve auto positioning in chart
281 928 : sal_Int32 nXPadding = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.33 ) );
282 : //sal_Int32 nXPadding = static_cast< sal_Int32 >( std::max( 200.0, fViewFontSize * 0.33 ) );
283 928 : sal_Int32 nXOffset = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.66 ) );
284 928 : sal_Int32 nYPadding = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.2 ) );
285 928 : sal_Int32 nYOffset = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.2 ) );
286 : //sal_Int32 nYOffset = static_cast< sal_Int32 >( std::max( 230.0, fViewFontSize * 0.45 ) );
287 :
288 928 : const sal_Int32 nSymbolToTextDistance = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.22 ) );//minimum 1mm
289 928 : const sal_Int32 nSymbolPlusDistanceWidth = rMaxSymbolExtent.Width + nSymbolToTextDistance;
290 928 : sal_Int32 nMaxTextWidth = rAvailableSpace.Width - (2 * nXPadding) - nSymbolPlusDistanceWidth;
291 928 : OUString aPropNameTextMaximumFrameWidth( "TextMaximumFrameWidth" );
292 928 : uno::Any* pFrameWidthAny = PropertyMapper::getValuePointer( rTextProperties.second, rTextProperties.first, aPropNameTextMaximumFrameWidth);
293 928 : if(pFrameWidthAny)
294 : {
295 928 : if( eExpansion == ::com::sun::star::chart::ChartLegendExpansion_HIGH )
296 : {
297 : // limit the width of texts to 30% of the total available width
298 : // #i109336# Improve auto positioning in chart
299 885 : nMaxTextWidth = rAvailableSpace.Width * 3 / 10;
300 : }
301 928 : *pFrameWidthAny = uno::makeAny(nMaxTextWidth);
302 : }
303 :
304 1856 : ::std::vector< Reference< drawing::XShape > > aTextShapes;
305 928 : awt::Size aMaxEntryExtent = lcl_createTextShapes( rEntries, xShapeFactory, xTarget, aTextShapes, rTextProperties );
306 : OSL_ASSERT( aTextShapes.size() == rEntries.size());
307 :
308 928 : sal_Int32 nMaxEntryWidth = nXOffset + nSymbolPlusDistanceWidth + aMaxEntryExtent.Width;
309 928 : sal_Int32 nMaxEntryHeight = nYOffset + aMaxEntryExtent.Height;
310 928 : sal_Int32 nNumberOfEntries = rEntries.size();
311 :
312 928 : sal_Int32 nNumberOfColumns = 0, nNumberOfRows = 0;
313 1856 : std::vector< sal_Int32 > aColumnWidths;
314 1856 : std::vector< sal_Int32 > aRowHeights;
315 :
316 928 : sal_Int32 nTextLineHeight = static_cast< sal_Int32 >( fViewFontSize );
317 :
318 : // determine layout depending on LegendExpansion
319 928 : if( eExpansion == ::com::sun::star::chart::ChartLegendExpansion_CUSTOM )
320 : {
321 5 : sal_Int32 nCurrentRow=0;
322 5 : sal_Int32 nCurrentColumn=-1;
323 5 : sal_Int32 nMaxColumnCount=-1;
324 28 : for( sal_Int32 nN=0; nN<static_cast<sal_Int32>(aTextShapes.size()); nN++ )
325 : {
326 23 : Reference< drawing::XShape > xShape( aTextShapes[nN] );
327 23 : if( !xShape.is() )
328 0 : continue;
329 23 : awt::Size aSize( xShape->getSize() );
330 23 : sal_Int32 nNewWidth = aSize.Width + nSymbolPlusDistanceWidth;
331 23 : sal_Int32 nCurrentColumnCount = aColumnWidths.size();
332 :
333 : //are we allowed to add a new column?
334 23 : if( nMaxColumnCount==-1 || (nCurrentColumn+1) < nMaxColumnCount )
335 : {
336 : //try add a new column
337 17 : nCurrentColumn++;
338 17 : if( nCurrentColumn < nCurrentColumnCount )
339 : {
340 : //check whether the current column width is sufficient for the new entry
341 6 : if( aColumnWidths[nCurrentColumn]>=nNewWidth )
342 : {
343 : //all good proceed with next entry
344 4 : continue;
345 : }
346 : }
347 13 : if( nCurrentColumn < nCurrentColumnCount )
348 2 : aColumnWidths[nCurrentColumn] = std::max( nNewWidth, aColumnWidths[nCurrentColumn] );
349 : else
350 11 : aColumnWidths.push_back(nNewWidth);
351 :
352 : //do the columns still fit into the given size?
353 13 : nCurrentColumnCount = aColumnWidths.size();//update count
354 13 : sal_Int32 nSumWidth = 0;
355 30 : for( sal_Int32 nC=0; nC<nCurrentColumnCount; nC++ )
356 17 : nSumWidth += aColumnWidths[nC];
357 :
358 13 : if( nSumWidth <= rAvailableSpace.Width || nCurrentColumnCount==1 )
359 : {
360 : //all good proceed with next entry
361 11 : continue;
362 : }
363 : else
364 : {
365 : //not enough space for the current amount of columns
366 : //try again with less columns
367 2 : nMaxColumnCount = nCurrentColumnCount-1;
368 2 : nN=-1;
369 2 : nCurrentRow=0;
370 2 : nCurrentColumn=-1;
371 2 : aColumnWidths.clear();
372 2 : }
373 : }
374 : else
375 : {
376 : //add a new row and try the same entry again
377 6 : nCurrentRow++;
378 6 : nCurrentColumn=-1;
379 6 : nN--;
380 : }
381 8 : }
382 5 : nNumberOfColumns = aColumnWidths.size();
383 5 : nNumberOfRows = nCurrentRow+1;
384 :
385 : //check if there is not enough space so that some entries must be removed
386 5 : lcl_collectRowHeighs( aRowHeights, nNumberOfRows, nNumberOfColumns, aTextShapes );
387 5 : nTextLineHeight = lcl_getTextLineHeight( aRowHeights, nNumberOfRows, fViewFontSize );
388 5 : sal_Int32 nSumHeight = 0;
389 16 : for( sal_Int32 nR=0; nR<nNumberOfRows; nR++ )
390 11 : nSumHeight += aRowHeights[nR];
391 5 : sal_Int32 nRemainingSpace = rAvailableSpace.Height - nSumHeight;
392 :
393 5 : if( nRemainingSpace < -100 ) // 1mm tolerance for OOXML interop tdf#90404
394 : {
395 : //remove entries that are too big
396 0 : for( sal_Int32 nR=nNumberOfRows; nR--; )
397 : {
398 0 : for( sal_Int32 nC=nNumberOfColumns; nC--; )
399 : {
400 0 : sal_Int32 nEntry = (nC + nR * nNumberOfColumns);
401 0 : if( nEntry < static_cast<sal_Int32>(aTextShapes.size()) )
402 : {
403 0 : DrawModelWrapper::removeShape( aTextShapes[nEntry] );
404 0 : aTextShapes.pop_back();
405 : }
406 0 : if( nEntry < nNumberOfEntries )
407 : {
408 0 : DrawModelWrapper::removeShape( rEntries[ nEntry ].aSymbol );
409 0 : rEntries.pop_back();
410 0 : nNumberOfEntries--;
411 : }
412 : }
413 0 : nSumHeight -= aRowHeights[nR];
414 0 : aRowHeights.pop_back();
415 0 : nRemainingSpace = rAvailableSpace.Height - nSumHeight;
416 0 : if( nRemainingSpace>=0 )
417 0 : break;
418 : }
419 0 : nNumberOfRows = static_cast<sal_Int32>(aRowHeights.size());
420 : }
421 5 : if( nRemainingSpace >= -100 ) // 1mm tolerance for OOXML interop tdf#90404
422 : {
423 5 : sal_Int32 nNormalSpacingHeight = 2*nYPadding+(nNumberOfRows-1)*nYOffset;
424 5 : if( nRemainingSpace < nNormalSpacingHeight )
425 : {
426 : //reduce spacing between the entries
427 0 : nYPadding = nYOffset = nRemainingSpace/(nNumberOfRows+1);
428 : }
429 : else
430 : {
431 : //we have some space left that should be spread equally between all rows
432 5 : sal_Int32 nRemainingSingleSpace = (nRemainingSpace-nNormalSpacingHeight)/(nNumberOfRows+1);
433 5 : nYPadding += nRemainingSingleSpace;
434 5 : nYOffset += nRemainingSingleSpace;
435 : }
436 : }
437 :
438 : //check spacing between columns
439 5 : sal_Int32 nSumWidth = 0;
440 12 : for( sal_Int32 nC=0; nC<nNumberOfColumns; nC++ )
441 7 : nSumWidth += aColumnWidths[nC];
442 5 : nRemainingSpace = rAvailableSpace.Width - nSumWidth;
443 5 : if( nRemainingSpace>=0 )
444 : {
445 5 : sal_Int32 nNormalSpacingWidth = 2*nXPadding+(nNumberOfColumns-1)*nXOffset;
446 5 : if( nRemainingSpace < nNormalSpacingWidth )
447 : {
448 : //reduce spacing between the entries
449 0 : nXPadding = nXOffset = nRemainingSpace/(nNumberOfColumns+1);
450 : }
451 : else
452 : {
453 : //we have some space left that should be spread equally between all columns
454 5 : sal_Int32 nRemainingSingleSpace = (nRemainingSpace-nNormalSpacingWidth)/(nNumberOfColumns+1);
455 5 : nXPadding += nRemainingSingleSpace;
456 5 : nXOffset += nRemainingSingleSpace;
457 : }
458 : }
459 : }
460 923 : else if( eExpansion == ::com::sun::star::chart::ChartLegendExpansion_HIGH )
461 : {
462 : sal_Int32 nMaxNumberOfRows = nMaxEntryHeight
463 885 : ? (rAvailableSpace.Height - 2*nYPadding ) / nMaxEntryHeight
464 1770 : : 0;
465 :
466 : nNumberOfColumns = nMaxNumberOfRows
467 : ? static_cast< sal_Int32 >(
468 : ceil( static_cast< double >( nNumberOfEntries ) /
469 885 : static_cast< double >( nMaxNumberOfRows ) ))
470 1770 : : 0;
471 : nNumberOfRows = nNumberOfColumns
472 : ? static_cast< sal_Int32 >(
473 : ceil( static_cast< double >( nNumberOfEntries ) /
474 885 : static_cast< double >( nNumberOfColumns ) ))
475 1770 : : 0;
476 : }
477 38 : else if( eExpansion == ::com::sun::star::chart::ChartLegendExpansion_WIDE )
478 : {
479 : sal_Int32 nMaxNumberOfColumns = nMaxEntryWidth
480 38 : ? (rAvailableSpace.Width - 2*nXPadding ) / nMaxEntryWidth
481 76 : : 0;
482 :
483 : nNumberOfRows = nMaxNumberOfColumns
484 : ? static_cast< sal_Int32 >(
485 : ceil( static_cast< double >( nNumberOfEntries ) /
486 38 : static_cast< double >( nMaxNumberOfColumns ) ))
487 76 : : 0;
488 : nNumberOfColumns = nNumberOfRows
489 : ? static_cast< sal_Int32 >(
490 : ceil( static_cast< double >( nNumberOfEntries ) /
491 38 : static_cast< double >( nNumberOfRows ) ))
492 76 : : 0;
493 : }
494 : else // ::com::sun::star::chart::ChartLegendExpansion_BALANCED
495 : {
496 : double fAspect = nMaxEntryHeight
497 0 : ? static_cast< double >( nMaxEntryWidth ) / static_cast< double >( nMaxEntryHeight )
498 0 : : 0.0;
499 :
500 : nNumberOfRows = static_cast< sal_Int32 >(
501 0 : ceil( sqrt( static_cast< double >( nNumberOfEntries ) * fAspect )));
502 : nNumberOfColumns = nNumberOfRows
503 : ? static_cast< sal_Int32 >(
504 : ceil( static_cast< double >( nNumberOfEntries ) /
505 0 : static_cast< double >( nNumberOfRows ) ))
506 0 : : 0;
507 : }
508 :
509 928 : if(nNumberOfRows<=0)
510 0 : return aResultingLegendSize;
511 :
512 928 : if( eExpansion != ::com::sun::star::chart::ChartLegendExpansion_CUSTOM )
513 : {
514 923 : lcl_collectColumnWidths( aColumnWidths, nNumberOfRows, nNumberOfColumns, aTextShapes, nSymbolPlusDistanceWidth );
515 923 : lcl_collectRowHeighs( aRowHeights, nNumberOfRows, nNumberOfColumns, aTextShapes );
516 923 : nTextLineHeight = lcl_getTextLineHeight( aRowHeights, nNumberOfRows, fViewFontSize );
517 : }
518 :
519 928 : sal_Int32 nCurrentXPos = nXPadding;
520 928 : sal_Int32 nCurrentYPos = nYPadding;
521 928 : if( !bSymbolsLeftSide )
522 0 : nCurrentXPos = -nXPadding;
523 :
524 : // place entries into column and rows
525 928 : sal_Int32 nMaxYPos = 0;
526 928 : sal_Int32 nRow = 0;
527 928 : sal_Int32 nColumn = 0;
528 1931 : for( nColumn = 0; nColumn < nNumberOfColumns; ++nColumn )
529 : {
530 1003 : nCurrentYPos = nYPadding;
531 3711 : for( nRow = 0; nRow < nNumberOfRows; ++nRow )
532 : {
533 2729 : sal_Int32 nEntry = (nColumn + nRow * nNumberOfColumns);
534 2729 : if( nEntry >= nNumberOfEntries )
535 21 : break;
536 :
537 : // text shape
538 2708 : Reference< drawing::XShape > xTextShape( aTextShapes[nEntry] );
539 2708 : if( xTextShape.is() )
540 : {
541 2708 : awt::Size aTextSize( xTextShape->getSize() );
542 2708 : sal_Int32 nTextXPos = nCurrentXPos + nSymbolPlusDistanceWidth;
543 2708 : if( !bSymbolsLeftSide )
544 0 : nTextXPos = nCurrentXPos - nSymbolPlusDistanceWidth - aTextSize.Width;
545 2708 : xTextShape->setPosition( awt::Point( nTextXPos, nCurrentYPos ));
546 : }
547 :
548 : // symbol
549 5416 : Reference< drawing::XShape > xSymbol( rEntries[ nEntry ].aSymbol );
550 2708 : if( xSymbol.is() )
551 : {
552 2708 : awt::Size aSymbolSize( rMaxSymbolExtent );
553 2708 : sal_Int32 nSymbolXPos = nCurrentXPos;
554 2708 : if( !bSymbolsLeftSide )
555 0 : nSymbolXPos = nCurrentXPos - rMaxSymbolExtent.Width;
556 2708 : sal_Int32 nSymbolYPos = nCurrentYPos + ( ( nTextLineHeight - aSymbolSize.Height ) / 2 );
557 2708 : xSymbol->setPosition( awt::Point( nSymbolXPos, nSymbolYPos ) );
558 : }
559 :
560 2708 : nCurrentYPos += aRowHeights[ nRow ];
561 2708 : if( nRow+1 < nNumberOfRows )
562 1726 : nCurrentYPos += nYOffset;
563 2708 : nMaxYPos = ::std::max( nMaxYPos, nCurrentYPos );
564 2708 : }
565 1003 : if( bSymbolsLeftSide )
566 : {
567 1003 : nCurrentXPos += aColumnWidths[nColumn];
568 1003 : if( nColumn+1 < nNumberOfColumns )
569 75 : nCurrentXPos += nXOffset;
570 : }
571 : else
572 : {
573 0 : nCurrentXPos -= aColumnWidths[nColumn];
574 0 : if( nColumn+1 < nNumberOfColumns )
575 0 : nCurrentXPos -= nXOffset;
576 : }
577 : }
578 :
579 928 : if( !bIsCustomSize )
580 : {
581 923 : if( bSymbolsLeftSide )
582 923 : aResultingLegendSize.Width = nCurrentXPos + nXPadding;
583 : else
584 : {
585 0 : sal_Int32 nLegendWidth = -(nCurrentXPos-nXPadding);
586 0 : aResultingLegendSize.Width = nLegendWidth;
587 : }
588 923 : aResultingLegendSize.Height = nMaxYPos + nYPadding;
589 : }
590 :
591 928 : if( !bSymbolsLeftSide )
592 : {
593 0 : sal_Int32 nLegendWidth = aResultingLegendSize.Width;
594 0 : awt::Point aPos(0,0);
595 0 : for( sal_Int32 nEntry=0; nEntry<nNumberOfEntries; nEntry++ )
596 : {
597 0 : Reference< drawing::XShape > xSymbol( rEntries[ nEntry ].aSymbol );
598 0 : aPos = xSymbol->getPosition();
599 0 : aPos.X += nLegendWidth;
600 0 : xSymbol->setPosition( aPos );
601 0 : Reference< drawing::XShape > xText( aTextShapes[ nEntry ] );
602 0 : aPos = xText->getPosition();
603 0 : aPos.X += nLegendWidth;
604 0 : xText->setPosition( aPos );
605 0 : }
606 : }
607 :
608 1856 : return aResultingLegendSize;
609 : }
610 :
611 : // #i109336# Improve auto positioning in chart
612 2097 : inline sal_Int32 lcl_getLegendLeftRightMargin()
613 : {
614 2097 : return 210; // 1/100 mm
615 : }
616 :
617 : // #i109336# Improve auto positioning in chart
618 1248 : inline sal_Int32 lcl_getLegendTopBottomMargin()
619 : {
620 1248 : return 185; // 1/100 mm
621 : }
622 :
623 929 : chart2::RelativePosition lcl_getDefaultPosition( LegendPosition ePos, const awt::Rectangle& rOutAvailableSpace, const awt::Size & rPageSize )
624 : {
625 929 : chart2::RelativePosition aResult;
626 :
627 929 : switch( ePos )
628 : {
629 : case LegendPosition_LINE_START:
630 : {
631 : // #i109336# Improve auto positioning in chart
632 3 : const double fDefaultDistance = ( static_cast< double >( lcl_getLegendLeftRightMargin() ) /
633 3 : static_cast< double >( rPageSize.Width ) );
634 : aResult = chart2::RelativePosition(
635 3 : fDefaultDistance, 0.5, drawing::Alignment_LEFT );
636 : }
637 3 : break;
638 : case LegendPosition_LINE_END:
639 : {
640 : // #i109336# Improve auto positioning in chart
641 886 : const double fDefaultDistance = ( static_cast< double >( lcl_getLegendLeftRightMargin() ) /
642 886 : static_cast< double >( rPageSize.Width ) );
643 : aResult = chart2::RelativePosition(
644 886 : 1.0 - fDefaultDistance, 0.5, drawing::Alignment_RIGHT );
645 : }
646 886 : break;
647 : case LegendPosition_PAGE_START:
648 : {
649 : // #i109336# Improve auto positioning in chart
650 3 : const double fDefaultDistance = ( static_cast< double >( lcl_getLegendTopBottomMargin() ) /
651 3 : static_cast< double >( rPageSize.Height ) );
652 3 : double fDistance = (static_cast<double>(rOutAvailableSpace.Y)/static_cast<double>(rPageSize.Height)) + fDefaultDistance;
653 : aResult = chart2::RelativePosition(
654 3 : 0.5, fDistance, drawing::Alignment_TOP );
655 : }
656 3 : break;
657 : case LegendPosition_PAGE_END:
658 : {
659 : // #i109336# Improve auto positioning in chart
660 37 : const double fDefaultDistance = ( static_cast< double >( lcl_getLegendTopBottomMargin() ) /
661 37 : static_cast< double >( rPageSize.Height ) );
662 : aResult = chart2::RelativePosition(
663 37 : 0.5, 1.0 - fDefaultDistance, drawing::Alignment_BOTTOM );
664 : }
665 37 : break;
666 :
667 : case LegendPosition_CUSTOM:
668 : // to avoid warning
669 : case LegendPosition_MAKE_FIXED_SIZE:
670 : // nothing to be set
671 0 : break;
672 : }
673 :
674 929 : return aResult;
675 : }
676 :
677 : /** @return
678 : a point relative to the upper left corner that can be used for
679 : XShape::setPosition()
680 : */
681 1208 : awt::Point lcl_calculatePositionAndRemainingSpace(
682 : awt::Rectangle & rRemainingSpace,
683 : const awt::Size & rPageSize,
684 : const chart2::RelativePosition& rRelPos,
685 : LegendPosition ePos,
686 : const awt::Size& aLegendSize )
687 : {
688 : // calculate position
689 : awt::Point aResult(
690 1208 : static_cast< sal_Int32 >( rRelPos.Primary * rPageSize.Width ),
691 2416 : static_cast< sal_Int32 >( rRelPos.Secondary * rPageSize.Height ));
692 :
693 : aResult = RelativePositionHelper::getUpperLeftCornerOfAnchoredObject(
694 1208 : aResult, aLegendSize, rRelPos.Anchor );
695 :
696 : // adapt rRemainingSpace if LegendPosition is not CUSTOM
697 : // #i109336# Improve auto positioning in chart
698 1208 : sal_Int32 nXDistance = lcl_getLegendLeftRightMargin();
699 1208 : sal_Int32 nYDistance = lcl_getLegendTopBottomMargin();
700 1208 : switch( ePos )
701 : {
702 : case LegendPosition_LINE_START:
703 : {
704 3 : sal_Int32 nExtent = aLegendSize.Width;
705 3 : rRemainingSpace.Width -= ( nExtent + nXDistance );
706 3 : rRemainingSpace.X += ( nExtent + nXDistance );
707 : }
708 3 : break;
709 : case LegendPosition_LINE_END:
710 : {
711 1161 : rRemainingSpace.Width -= ( aLegendSize.Width + nXDistance );
712 : }
713 1161 : break;
714 : case LegendPosition_PAGE_START:
715 : {
716 3 : sal_Int32 nExtent = aLegendSize.Height;
717 3 : rRemainingSpace.Height -= ( nExtent + nYDistance );
718 3 : rRemainingSpace.Y += ( nExtent + nYDistance );
719 : }
720 3 : break;
721 : case LegendPosition_PAGE_END:
722 : {
723 40 : rRemainingSpace.Height -= ( aLegendSize.Height + nYDistance );
724 : }
725 40 : break;
726 :
727 : default:
728 : // nothing
729 1 : break;
730 : }
731 :
732 : // adjust the legend position. Esp. for old files that had slightly smaller legends
733 1208 : const sal_Int32 nEdgeDistance( 30 );
734 1208 : if( aResult.X + aLegendSize.Width > rPageSize.Width )
735 : {
736 2 : sal_Int32 nNewX( (rPageSize.Width - aLegendSize.Width) - nEdgeDistance );
737 2 : if( nNewX > rPageSize.Width / 4 )
738 0 : aResult.X = nNewX;
739 : }
740 1208 : if( aResult.Y + aLegendSize.Height > rPageSize.Height )
741 : {
742 2 : sal_Int32 nNewY( (rPageSize.Height - aLegendSize.Height) - nEdgeDistance );
743 2 : if( nNewY > rPageSize.Height / 4 )
744 2 : aResult.Y = nNewY;
745 : }
746 :
747 1208 : return aResult;
748 : }
749 :
750 930 : bool lcl_shouldSymbolsBePlacedOnTheLeftSide( const Reference< beans::XPropertySet >& xLegendProp, sal_Int16 nDefaultWritingMode )
751 : {
752 930 : bool bSymbolsLeftSide = true;
753 : try
754 : {
755 930 : if( SvtLanguageOptions().IsCTLFontEnabled() )
756 : {
757 930 : if(xLegendProp.is())
758 : {
759 930 : sal_Int16 nWritingMode=-1;
760 930 : if( (xLegendProp->getPropertyValue( "WritingMode" ) >>= nWritingMode) )
761 : {
762 930 : if( nWritingMode == text::WritingMode2::PAGE )
763 930 : nWritingMode = nDefaultWritingMode;
764 930 : if( nWritingMode == text::WritingMode2::RL_TB )
765 0 : bSymbolsLeftSide=false;
766 : }
767 : }
768 : }
769 : }
770 0 : catch( const uno::Exception & ex )
771 : {
772 : ASSERT_EXCEPTION( ex );
773 : }
774 930 : return bSymbolsLeftSide;
775 : }
776 :
777 : } // anonymous namespace
778 :
779 930 : VLegend::VLegend(
780 : const Reference< XLegend > & xLegend,
781 : const Reference< uno::XComponentContext > & xContext,
782 : const std::vector< LegendEntryProvider* >& rLegendEntryProviderList,
783 : const Reference< drawing::XShapes >& xTargetPage,
784 : const Reference< lang::XMultiServiceFactory >& xFactory,
785 : ChartModel& rModel )
786 : : m_xTarget(xTargetPage)
787 : , m_xShapeFactory(xFactory)
788 : , m_xLegend(xLegend)
789 : , mrModel(rModel)
790 : , m_xContext(xContext)
791 : , m_aLegendEntryProviderList(rLegendEntryProviderList)
792 930 : , m_nDefaultWritingMode(text::WritingMode2::LR_TB)
793 : {
794 930 : }
795 :
796 930 : void VLegend::setDefaultWritingMode( sal_Int16 nDefaultWritingMode )
797 : {
798 930 : m_nDefaultWritingMode = nDefaultWritingMode;
799 930 : }
800 :
801 1121 : bool VLegend::isVisible( const Reference< XLegend > & xLegend )
802 : {
803 1121 : if( ! xLegend.is())
804 163 : return false;
805 :
806 958 : bool bShow = false;
807 : try
808 : {
809 958 : Reference< beans::XPropertySet > xLegendProp( xLegend, uno::UNO_QUERY_THROW );
810 958 : xLegendProp->getPropertyValue( "Show") >>= bShow;
811 : }
812 0 : catch( const uno::Exception & ex )
813 : {
814 : ASSERT_EXCEPTION( ex );
815 : }
816 :
817 958 : return bShow;
818 : }
819 :
820 930 : void VLegend::createShapes(
821 : const awt::Size & rAvailableSpace,
822 : const awt::Size & rPageSize )
823 : {
824 2790 : if(! (m_xLegend.is() &&
825 930 : m_xShapeFactory.is() &&
826 1860 : m_xTarget.is()))
827 930 : return;
828 :
829 : try
830 : {
831 : //create shape and add to page
832 930 : AbstractShapeFactory* pShapeFactory = AbstractShapeFactory::getOrCreateShapeFactory(m_xShapeFactory);
833 930 : OUString aLegendParticle( ObjectIdentifier::createParticleForLegend( m_xLegend, mrModel ) );
834 : m_xShape.set( pShapeFactory->createGroup2D( m_xTarget,
835 930 : ObjectIdentifier::createClassifiedIdentifierForParticle( aLegendParticle )),
836 930 : uno::UNO_QUERY);
837 :
838 : // create and insert sub-shapes
839 1860 : Reference< drawing::XShapes > xLegendContainer( m_xShape, uno::UNO_QUERY );
840 930 : if( xLegendContainer.is())
841 : {
842 : // for quickly setting properties
843 930 : tPropertyValues aLineFillProperties;
844 1860 : tPropertyValues aTextProperties;
845 :
846 1860 : Reference< beans::XPropertySet > xLegendProp( m_xLegend, uno::UNO_QUERY );
847 930 : ::com::sun::star::chart::ChartLegendExpansion eExpansion = ::com::sun::star::chart::ChartLegendExpansion_HIGH;
848 930 : awt::Size aLegendSize( rAvailableSpace );
849 :
850 930 : if( xLegendProp.is())
851 : {
852 : // get Expansion property
853 930 : xLegendProp->getPropertyValue( "Expansion") >>= eExpansion;
854 930 : if( eExpansion == ::com::sun::star::chart::ChartLegendExpansion_CUSTOM )
855 : {
856 5 : RelativeSize aRelativeSize;
857 5 : if ((xLegendProp->getPropertyValue( "RelativeSize") >>= aRelativeSize))
858 : {
859 5 : aLegendSize.Width = static_cast<sal_Int32>(::rtl::math::approxCeil( aRelativeSize.Primary * rPageSize.Width ));
860 5 : aLegendSize.Height = static_cast<sal_Int32>(::rtl::math::approxCeil( aRelativeSize.Secondary * rPageSize.Height ));
861 : }
862 : else
863 0 : eExpansion = ::com::sun::star::chart::ChartLegendExpansion_HIGH;
864 : }
865 930 : lcl_getProperties( xLegendProp, aLineFillProperties, aTextProperties, rPageSize );
866 : }
867 :
868 : // create entries
869 930 : double fViewFontSize = lcl_CalcViewFontSize( xLegendProp, rPageSize );//todo
870 : // #i109336# Improve auto positioning in chart
871 930 : sal_Int32 nSymbolHeight = static_cast< sal_Int32 >( fViewFontSize * 0.6 );
872 930 : sal_Int32 nSymbolWidth = static_cast< sal_Int32 >( nSymbolHeight );
873 :
874 930 : ::std::vector< LegendEntryProvider* >::const_iterator aIter = m_aLegendEntryProviderList.begin();
875 930 : const ::std::vector< LegendEntryProvider* >::const_iterator aEnd = m_aLegendEntryProviderList.end();
876 1876 : for( aIter = m_aLegendEntryProviderList.begin(); aIter != aEnd; ++aIter )
877 : {
878 946 : LegendEntryProvider* pLegendEntryProvider( *aIter );
879 946 : if( pLegendEntryProvider )
880 : {
881 946 : awt::Size aCurrentRatio = pLegendEntryProvider->getPreferredLegendKeyAspectRatio();
882 946 : sal_Int32 nCurrentWidth = aCurrentRatio.Width;
883 946 : if( aCurrentRatio.Height > 0 )
884 : {
885 874 : nCurrentWidth = nSymbolHeight* aCurrentRatio.Width/aCurrentRatio.Height;
886 : }
887 946 : nSymbolWidth = std::max( nSymbolWidth, nCurrentWidth );
888 : }
889 : }
890 930 : awt::Size aMaxSymbolExtent( nSymbolWidth, nSymbolHeight );
891 :
892 1860 : tViewLegendEntryContainer aViewEntries;
893 1876 : for( aIter = m_aLegendEntryProviderList.begin(); aIter != aEnd; ++aIter )
894 : {
895 946 : LegendEntryProvider* pLegendEntryProvider( *aIter );
896 946 : if( pLegendEntryProvider )
897 : {
898 946 : std::vector< ViewLegendEntry > aNewEntries = pLegendEntryProvider->createLegendEntries( aMaxSymbolExtent, eExpansion, xLegendProp, xLegendContainer, m_xShapeFactory, m_xContext );
899 946 : aViewEntries.insert( aViewEntries.end(), aNewEntries.begin(), aNewEntries.end() );
900 : }
901 : }
902 :
903 930 : bool bSymbolsLeftSide = lcl_shouldSymbolsBePlacedOnTheLeftSide( xLegendProp, m_nDefaultWritingMode );
904 :
905 930 : if( !aViewEntries.empty() ) {
906 : // place entries
907 : aLegendSize = lcl_placeLegendEntries( aViewEntries, eExpansion, bSymbolsLeftSide, fViewFontSize, aMaxSymbolExtent,
908 928 : aTextProperties, xLegendContainer, m_xShapeFactory, aLegendSize );
909 :
910 : }
911 :
912 : Reference< drawing::XShape > xBorder =
913 : pShapeFactory->createRectangle( xLegendContainer,
914 : aLegendSize,
915 : awt::Point(0,0),
916 : aLineFillProperties.first,
917 1860 : aLineFillProperties.second, AbstractShapeFactory::Bottom );
918 :
919 : //because of this name this border will be used for marking the legend
920 1860 : AbstractShapeFactory::setShapeName( xBorder, "MarkHandles" );
921 930 : }
922 : }
923 0 : catch( const uno::Exception & ex )
924 : {
925 : ASSERT_EXCEPTION( ex );
926 : }
927 : }
928 :
929 930 : void VLegend::changePosition(
930 : awt::Rectangle & rOutAvailableSpace,
931 : const awt::Size & rPageSize )
932 : {
933 930 : if(! m_xShape.is())
934 930 : return;
935 :
936 : try
937 : {
938 : // determine position and alignment depending on default position
939 930 : awt::Size aLegendSize = m_xShape->getSize();
940 930 : Reference< beans::XPropertySet > xLegendProp( m_xLegend, uno::UNO_QUERY_THROW );
941 930 : chart2::RelativePosition aRelativePosition;
942 :
943 : bool bAutoPosition =
944 930 : ! (xLegendProp->getPropertyValue( "RelativePosition") >>= aRelativePosition);
945 :
946 930 : LegendPosition ePos = LegendPosition_CUSTOM;
947 930 : xLegendProp->getPropertyValue( "AnchorPosition") >>= ePos;
948 :
949 : //calculate position
950 930 : if( bAutoPosition )
951 : {
952 : // auto position: relative to remaining space
953 651 : aRelativePosition = lcl_getDefaultPosition( ePos, rOutAvailableSpace, rPageSize );
954 : awt::Point aPos = lcl_calculatePositionAndRemainingSpace(
955 651 : rOutAvailableSpace, rPageSize, aRelativePosition, ePos, aLegendSize );
956 651 : m_xShape->setPosition( aPos );
957 : }
958 : else
959 : {
960 : // manual position: relative to whole page
961 279 : awt::Rectangle aAvailableSpace( 0, 0, rPageSize.Width, rPageSize.Height );
962 : awt::Point aPos = lcl_calculatePositionAndRemainingSpace(
963 279 : aAvailableSpace, rPageSize, aRelativePosition, ePos, aLegendSize );
964 279 : m_xShape->setPosition( aPos );
965 :
966 279 : if( ePos != LegendPosition_CUSTOM )
967 : {
968 : // calculate remaining space as if having autoposition:
969 278 : aRelativePosition = lcl_getDefaultPosition( ePos, rOutAvailableSpace, rPageSize );
970 : lcl_calculatePositionAndRemainingSpace(
971 278 : rOutAvailableSpace, rPageSize, aRelativePosition, ePos, aLegendSize );
972 : }
973 930 : }
974 : }
975 0 : catch( const uno::Exception & ex )
976 : {
977 : ASSERT_EXCEPTION( ex );
978 : }
979 : }
980 :
981 57 : } //namespace chart
982 :
983 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|