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