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