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 41 : double lcl_CalcViewFontSize(
66 : const Reference< beans::XPropertySet > & xProp,
67 : const awt::Size & rReferenceSize )
68 : {
69 41 : double fResult = 10.0;
70 :
71 41 : awt::Size aPropRefSize;
72 41 : float fFontHeight( 0.0 );
73 41 : if( xProp.is() && ( xProp->getPropertyValue( C2U( "CharHeight" )) >>= fFontHeight ))
74 : {
75 41 : fResult = fFontHeight;
76 : try
77 : {
78 41 : 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 41 : return (fResult * (2540.0 / 72.0));
92 : }
93 :
94 41 : 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 41 : if( xLegendProp.is())
102 : {
103 : // set rOutLineFillProperties
104 41 : ::chart::tPropertyNameValueMap aLineFillValueMap;
105 41 : ::chart::PropertyMapper::getValueMap( aLineFillValueMap, ::chart::PropertyMapper::getPropertyNameMapForFillAndLineProperties(), xLegendProp );
106 :
107 41 : aLineFillValueMap[ C2U("LineJoint") ] = uno::makeAny( drawing::LineJoint_ROUND );
108 :
109 : ::chart::PropertyMapper::getMultiPropertyListsFromValueMap(
110 41 : rOutLineFillProperties.first, rOutLineFillProperties.second, aLineFillValueMap );
111 :
112 : // set rOutTextProperties
113 41 : ::chart::tPropertyNameValueMap aTextValueMap;
114 41 : ::chart::PropertyMapper::getValueMap( aTextValueMap, ::chart::PropertyMapper::getPropertyNameMapForCharacterProperties(), xLegendProp );
115 :
116 41 : drawing::TextHorizontalAdjust eHorizAdjust( drawing::TextHorizontalAdjust_LEFT );
117 41 : aTextValueMap[ C2U("TextAutoGrowHeight") ] = uno::makeAny( sal_True );
118 41 : aTextValueMap[ C2U("TextAutoGrowWidth") ] = uno::makeAny( sal_True );
119 41 : aTextValueMap[ C2U("TextHorizontalAdjust") ] = uno::makeAny( eHorizAdjust );
120 41 : aTextValueMap[ C2U("TextMaximumFrameWidth") ] = uno::makeAny( rReferenceSize.Width ); //needs to be overwritten by actual available space in the legend
121 :
122 : // recalculate font size
123 41 : awt::Size aPropRefSize;
124 41 : float fFontHeight( 0.0 );
125 82 : if( (xLegendProp->getPropertyValue( C2U( "ReferencePageSize" )) >>= aPropRefSize) &&
126 : (aPropRefSize.Height > 0) &&
127 41 : (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 41 : rOutTextProperties.first, rOutTextProperties.second, aTextValueMap );
149 : }
150 41 : }
151 :
152 41 : 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 41 : awt::Size aResult;
160 :
161 492 : for( tViewLegendEntryContainer::const_iterator aIt( rEntries.begin());
162 328 : aIt != rEntries.end(); ++aIt )
163 : {
164 : try
165 : {
166 : // create label shape
167 : Reference< drawing::XShape > xEntry(
168 123 : xShapeFactory->createInstance(
169 123 : C2U( "com.sun.star.drawing.TextShape" )), uno::UNO_QUERY_THROW );
170 123 : xTarget->add( xEntry );
171 :
172 : // set label text
173 123 : Sequence< Reference< XFormattedString > > aLabelSeq = (*aIt).aLabel;
174 246 : for( sal_Int32 i = 0; i < aLabelSeq.getLength(); ++i )
175 : {
176 : // todo: support more than one text range
177 123 : if( i == 1 )
178 : break;
179 :
180 123 : Reference< text::XTextRange > xRange( xEntry, uno::UNO_QUERY );
181 123 : OUString aLabelString( aLabelSeq[i]->getString());
182 : // workaround for Issue #i67540#
183 123 : if( aLabelString.isEmpty())
184 0 : aLabelString = C2U(" ");
185 123 : if( xRange.is())
186 123 : xRange->setString( aLabelString );
187 :
188 : PropertyMapper::setMultiProperties(
189 : rTextProperties.first, rTextProperties.second,
190 123 : Reference< beans::XPropertySet >( xRange, uno::UNO_QUERY ));
191 :
192 : // adapt max-extent
193 123 : awt::Size aCurrSize( xEntry->getSize());
194 123 : aResult.Width = ::std::max( aResult.Width, aCurrSize.Width );
195 123 : aResult.Height = ::std::max( aResult.Height, aCurrSize.Height );
196 123 : }
197 :
198 123 : rOutTextShapes.push_back( xEntry );
199 : }
200 0 : catch( const uno::Exception & ex )
201 : {
202 : ASSERT_EXCEPTION( ex );
203 : }
204 : }
205 :
206 41 : return aResult;
207 : }
208 :
209 41 : 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 41 : rColumnWidths.clear();
213 41 : sal_Int32 nRow = 0;
214 41 : sal_Int32 nColumn = 0;
215 41 : sal_Int32 nNumberOfEntries = aTextShapes.size();
216 164 : for( ; nRow < nNumberOfRows; ++nRow )
217 : {
218 246 : for( nColumn = 0; nColumn < nNumberOfColumns; ++nColumn )
219 : {
220 123 : sal_Int32 nEntry = (nColumn + nRow * nNumberOfColumns);
221 123 : if( nEntry < nNumberOfEntries )
222 : {
223 123 : awt::Size aTextSize( aTextShapes[ nEntry ]->getSize() );
224 123 : sal_Int32 nWidth = nSymbolPlusDistanceWidth + aTextSize.Width;
225 123 : if( nRow==0 )
226 41 : rColumnWidths.push_back( nWidth );
227 : else
228 82 : rColumnWidths[nColumn] = ::std::max( nWidth, rColumnWidths[nColumn] );
229 : }
230 : }
231 : }
232 41 : }
233 :
234 41 : 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 41 : rRowHeights.clear();
240 41 : sal_Int32 nRow = 0;
241 41 : sal_Int32 nColumn = 0;
242 41 : sal_Int32 nNumberOfEntries = aTextShapes.size();
243 164 : for( ; nRow < nNumberOfRows; ++nRow )
244 : {
245 123 : sal_Int32 nCurrentRowHeight = 0;
246 246 : for( nColumn = 0; nColumn < nNumberOfColumns; ++nColumn )
247 : {
248 123 : sal_Int32 nEntry = (nColumn + nRow * nNumberOfColumns);
249 123 : if( nEntry < nNumberOfEntries )
250 : {
251 123 : awt::Size aTextSize( aTextShapes[ nEntry ]->getSize() );
252 123 : nCurrentRowHeight = ::std::max( nCurrentRowHeight, aTextSize.Height );
253 : }
254 : }
255 123 : rRowHeights.push_back( nCurrentRowHeight );
256 : }
257 41 : }
258 :
259 41 : sal_Int32 lcl_getTextLineHeight( const std::vector< sal_Int32 >& aRowHeights, const sal_Int32 nNumberOfRows, double fViewFontSize )
260 : {
261 41 : const sal_Int32 nFontHeight = static_cast< sal_Int32 >( fViewFontSize );
262 41 : sal_Int32 nTextLineHeight = nFontHeight;
263 41 : for( sal_Int32 nR=0; nR<nNumberOfRows; nR++ )
264 : {
265 41 : sal_Int32 nFullTextHeight = aRowHeights[ nR ];
266 41 : if( ( nFullTextHeight / nFontHeight ) <= 1 )
267 : {
268 41 : nTextLineHeight = nFullTextHeight;//found an entry with one line-> have real text height
269 41 : break;
270 : }
271 : }
272 41 : return nTextLineHeight;
273 : }
274 :
275 : //returns resulting legend size
276 41 : 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 41 : bool bIsCustomSize = (eExpansion == ::com::sun::star::chart::ChartLegendExpansion_CUSTOM);
288 41 : awt::Size aResultingLegendSize(0,0);
289 41 : if( bIsCustomSize )
290 0 : aResultingLegendSize = rAvailableSpace;
291 :
292 : // #i109336# Improve auto positioning in chart
293 41 : 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 41 : sal_Int32 nXOffset = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.66 ) );
296 41 : sal_Int32 nYPadding = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.2 ) );
297 41 : 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 41 : const sal_Int32 nSymbolToTextDistance = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.22 ) );//minimum 1mm
301 41 : const sal_Int32 nSymbolPlusDistanceWidth = rMaxSymbolExtent.Width + nSymbolToTextDistance;
302 41 : sal_Int32 nMaxTextWidth = rAvailableSpace.Width - (2 * nXPadding) - nSymbolPlusDistanceWidth;
303 41 : rtl::OUString aPropNameTextMaximumFrameWidth( C2U("TextMaximumFrameWidth") );
304 41 : uno::Any* pFrameWidthAny = PropertyMapper::getValuePointer( rTextProperties.second, rTextProperties.first, aPropNameTextMaximumFrameWidth);
305 41 : if(pFrameWidthAny)
306 : {
307 41 : 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 41 : nMaxTextWidth = rAvailableSpace.Width * 3 / 10;
312 : }
313 41 : *pFrameWidthAny = uno::makeAny(nMaxTextWidth);
314 : }
315 :
316 41 : ::std::vector< Reference< drawing::XShape > > aTextShapes;
317 41 : awt::Size aMaxEntryExtent = lcl_createTextShapes( rEntries, xShapeFactory, xTarget, aTextShapes, rTextProperties );
318 : OSL_ASSERT( aTextShapes.size() == rEntries.size());
319 :
320 41 : sal_Int32 nMaxEntryWidth = nXOffset + nSymbolPlusDistanceWidth + aMaxEntryExtent.Width;
321 41 : sal_Int32 nMaxEntryHeight = nYOffset + aMaxEntryExtent.Height;
322 41 : sal_Int32 nNumberOfEntries = rEntries.size();
323 :
324 41 : sal_Int32 nNumberOfColumns = 0, nNumberOfRows = 0;
325 41 : std::vector< sal_Int32 > aColumnWidths;
326 41 : std::vector< sal_Int32 > aRowHeights;
327 :
328 41 : sal_Int32 nTextLineHeight = static_cast< sal_Int32 >( fViewFontSize );
329 :
330 : // determine layout depending on LegendExpansion
331 41 : 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 41 : else if( eExpansion == ::com::sun::star::chart::ChartLegendExpansion_HIGH )
473 : {
474 : sal_Int32 nMaxNumberOfRows = nMaxEntryHeight
475 : ? (rAvailableSpace.Height - 2*nYPadding ) / nMaxEntryHeight
476 41 : : 0;
477 :
478 : nNumberOfColumns = nMaxNumberOfRows
479 : ? static_cast< sal_Int32 >(
480 : ceil( static_cast< double >( nNumberOfEntries ) /
481 41 : static_cast< double >( nMaxNumberOfRows ) ))
482 82 : : 0;
483 : nNumberOfRows = nNumberOfColumns
484 : ? static_cast< sal_Int32 >(
485 : ceil( static_cast< double >( nNumberOfEntries ) /
486 41 : static_cast< double >( nNumberOfColumns ) ))
487 82 : : 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 41 : if(nNumberOfRows<=0)
522 : return aResultingLegendSize;
523 :
524 41 : if( eExpansion != ::com::sun::star::chart::ChartLegendExpansion_CUSTOM )
525 : {
526 41 : lcl_collectColumnWidths( aColumnWidths, nNumberOfRows, nNumberOfColumns, aTextShapes, nSymbolPlusDistanceWidth );
527 41 : lcl_collectRowHeighs( aRowHeights, nNumberOfRows, nNumberOfColumns, aTextShapes );
528 41 : nTextLineHeight = lcl_getTextLineHeight( aRowHeights, nNumberOfRows, fViewFontSize );
529 : }
530 :
531 41 : sal_Int32 nCurrentXPos = nXPadding;
532 41 : sal_Int32 nCurrentYPos = nYPadding;
533 41 : if( !bSymbolsLeftSide )
534 0 : nCurrentXPos = -nXPadding;
535 :
536 : // place entries into column and rows
537 41 : sal_Int32 nMaxYPos = 0;
538 41 : sal_Int32 nRow = 0;
539 41 : sal_Int32 nColumn = 0;
540 82 : for( nColumn = 0; nColumn < nNumberOfColumns; ++nColumn )
541 : {
542 41 : nCurrentYPos = nYPadding;
543 164 : for( nRow = 0; nRow < nNumberOfRows; ++nRow )
544 : {
545 123 : sal_Int32 nEntry = (nColumn + nRow * nNumberOfColumns);
546 123 : if( nEntry >= nNumberOfEntries )
547 : break;
548 :
549 : // text shape
550 123 : Reference< drawing::XShape > xTextShape( aTextShapes[nEntry] );
551 123 : if( xTextShape.is() )
552 : {
553 123 : awt::Size aTextSize( xTextShape->getSize() );
554 123 : sal_Int32 nTextXPos = nCurrentXPos + nSymbolPlusDistanceWidth;
555 123 : if( !bSymbolsLeftSide )
556 0 : nTextXPos = nCurrentXPos - nSymbolPlusDistanceWidth - aTextSize.Width;
557 123 : xTextShape->setPosition( awt::Point( nTextXPos, nCurrentYPos ));
558 : }
559 :
560 : // symbol
561 123 : Reference< drawing::XShape > xSymbol( rEntries[ nEntry ].aSymbol );
562 123 : if( xSymbol.is() )
563 : {
564 123 : awt::Size aSymbolSize( rMaxSymbolExtent );
565 123 : sal_Int32 nSymbolXPos = nCurrentXPos;
566 123 : if( !bSymbolsLeftSide )
567 0 : nSymbolXPos = nCurrentXPos - rMaxSymbolExtent.Width;
568 123 : sal_Int32 nSymbolYPos = nCurrentYPos + ( ( nTextLineHeight - aSymbolSize.Height ) / 2 );
569 123 : xSymbol->setPosition( awt::Point( nSymbolXPos, nSymbolYPos ) );
570 : }
571 :
572 123 : nCurrentYPos += aRowHeights[ nRow ];
573 123 : if( nRow+1 < nNumberOfRows )
574 82 : nCurrentYPos += nYOffset;
575 123 : nMaxYPos = ::std::max( nMaxYPos, nCurrentYPos );
576 123 : }
577 41 : if( bSymbolsLeftSide )
578 : {
579 41 : nCurrentXPos += aColumnWidths[nColumn];
580 41 : 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 41 : if( !bIsCustomSize )
592 : {
593 41 : if( bSymbolsLeftSide )
594 41 : aResultingLegendSize.Width = nCurrentXPos + nXPadding;
595 : else
596 : {
597 0 : sal_Int32 nLegendWidth = -(nCurrentXPos-nXPadding);
598 0 : aResultingLegendSize.Width = nLegendWidth;
599 : }
600 41 : aResultingLegendSize.Height = nMaxYPos + nYPadding;
601 : }
602 :
603 41 : 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 41 : return aResultingLegendSize;
621 : }
622 :
623 : // #i109336# Improve auto positioning in chart
624 82 : sal_Int32 lcl_getLegendLeftRightMargin()
625 : {
626 82 : return 210; // 1/100 mm
627 : }
628 :
629 : // #i109336# Improve auto positioning in chart
630 41 : sal_Int32 lcl_getLegendTopBottomMargin()
631 : {
632 41 : return 185; // 1/100 mm
633 : }
634 :
635 41 : chart2::RelativePosition lcl_getDefaultPosition( LegendPosition ePos, const awt::Rectangle& rOutAvailableSpace, const awt::Size & rPageSize )
636 : {
637 41 : chart2::RelativePosition aResult;
638 :
639 41 : 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 41 : const double fDefaultDistance = ( static_cast< double >( lcl_getLegendLeftRightMargin() ) /
654 41 : static_cast< double >( rPageSize.Width ) );
655 : aResult = chart2::RelativePosition(
656 41 : 1.0 - fDefaultDistance, 0.5, drawing::Alignment_RIGHT );
657 : }
658 41 : 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 41 : 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 41 : 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 41 : static_cast< sal_Int32 >( aRelPos.Secondary * rPageSize.Height ));
704 :
705 : aResult = RelativePositionHelper::getUpperLeftCornerOfAnchoredObject(
706 41 : aResult, aLegendSize, aRelPos.Anchor );
707 :
708 : // adapt rRemainingSpace if LegendPosition is not CUSTOM
709 : // #i109336# Improve auto positioning in chart
710 41 : sal_Int32 nXDistance = lcl_getLegendLeftRightMargin();
711 41 : sal_Int32 nYDistance = lcl_getLegendTopBottomMargin();
712 41 : 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 41 : rRemainingSpace.Width -= ( aLegendSize.Width + nXDistance );
724 : }
725 41 : 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 41 : const sal_Int32 nEdgeDistance( 30 );
746 41 : 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 41 : 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 41 : return aResult;
760 : }
761 :
762 41 : bool lcl_shouldSymbolsBePlacedOnTheLeftSide( const Reference< beans::XPropertySet >& xLegendProp, sal_Int16 nDefaultWritingMode )
763 : {
764 41 : bool bSymbolsLeftSide = true;
765 : try
766 : {
767 41 : if( SvtLanguageOptions().IsCTLFontEnabled() )
768 : {
769 41 : if(xLegendProp.is())
770 : {
771 41 : sal_Int16 nWritingMode=-1;
772 41 : if( (xLegendProp->getPropertyValue( C2U("WritingMode") ) >>= nWritingMode) )
773 : {
774 41 : if( nWritingMode == text::WritingMode2::PAGE )
775 41 : nWritingMode = nDefaultWritingMode;
776 41 : 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 41 : return bSymbolsLeftSide;
787 : }
788 :
789 : } // anonymous namespace
790 :
791 41 : 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 41 : m_aLegendEntryProviderList( rLegendEntryProviderList )
798 : {
799 41 : }
800 :
801 : // ----------------------------------------
802 :
803 41 : void VLegend::init(
804 : const Reference< drawing::XShapes >& xTargetPage,
805 : const Reference< lang::XMultiServiceFactory >& xFactory,
806 : const Reference< frame::XModel >& xModel )
807 : {
808 41 : m_xTarget = xTargetPage;
809 41 : m_xShapeFactory = xFactory;
810 41 : m_xModel = xModel;
811 41 : }
812 :
813 : // ----------------------------------------
814 :
815 41 : void VLegend::setDefaultWritingMode( sal_Int16 nDefaultWritingMode )
816 : {
817 41 : m_nDefaultWritingMode = nDefaultWritingMode;
818 41 : }
819 :
820 : // ----------------------------------------
821 :
822 41 : bool VLegend::isVisible( const Reference< XLegend > & xLegend )
823 : {
824 41 : if( ! xLegend.is())
825 0 : return sal_False;
826 :
827 41 : sal_Bool bShow = sal_False;
828 : try
829 : {
830 41 : Reference< beans::XPropertySet > xLegendProp( xLegend, uno::UNO_QUERY_THROW );
831 41 : xLegendProp->getPropertyValue( C2U( "Show" )) >>= bShow;
832 : }
833 0 : catch( const uno::Exception & ex )
834 : {
835 : ASSERT_EXCEPTION( ex );
836 : }
837 :
838 41 : return bShow;
839 : }
840 :
841 : // ----------------------------------------
842 :
843 41 : void VLegend::createShapes(
844 : const awt::Size & rAvailableSpace,
845 : const awt::Size & rPageSize )
846 : {
847 82 : if(! (m_xLegend.is() &&
848 41 : m_xShapeFactory.is() &&
849 82 : m_xTarget.is()))
850 41 : return;
851 :
852 : try
853 : {
854 : //create shape and add to page
855 41 : m_xShape.set( m_xShapeFactory->createInstance(
856 41 : C2U( "com.sun.star.drawing.GroupShape" )), uno::UNO_QUERY );
857 41 : m_xTarget->add( m_xShape );
858 :
859 : // set name to enable selection
860 : {
861 41 : OUString aLegendParticle( ObjectIdentifier::createParticleForLegend( m_xLegend, m_xModel ) );
862 41 : ShapeFactory::setShapeName( m_xShape, ObjectIdentifier::createClassifiedIdentifierForParticle( aLegendParticle ) );
863 : }
864 :
865 : // create and insert sub-shapes
866 41 : Reference< drawing::XShapes > xLegendContainer( m_xShape, uno::UNO_QUERY );
867 41 : if( xLegendContainer.is())
868 : {
869 : Reference< drawing::XShape > xBorder(
870 41 : m_xShapeFactory->createInstance(
871 41 : C2U( "com.sun.star.drawing.RectangleShape" )), uno::UNO_QUERY );
872 :
873 : // for quickly setting properties
874 41 : tPropertyValues aLineFillProperties;
875 41 : tPropertyValues aTextProperties;
876 :
877 41 : Reference< beans::XPropertySet > xLegendProp( m_xLegend, uno::UNO_QUERY );
878 41 : ::com::sun::star::chart::ChartLegendExpansion eExpansion = ::com::sun::star::chart::ChartLegendExpansion_HIGH;
879 41 : awt::Size aLegendSize( rAvailableSpace );
880 :
881 41 : if( xLegendProp.is())
882 : {
883 : // get Expansion property
884 41 : xLegendProp->getPropertyValue( C2U( "Expansion" )) >>= eExpansion;
885 41 : 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 41 : lcl_getProperties( xLegendProp, aLineFillProperties, aTextProperties, rPageSize );
897 : }
898 :
899 41 : if( xBorder.is())
900 : {
901 41 : xLegendContainer->add( xBorder );
902 :
903 : // apply legend properties
904 : PropertyMapper::setMultiProperties(
905 : aLineFillProperties.first, aLineFillProperties.second,
906 41 : Reference< beans::XPropertySet >( xBorder, uno::UNO_QUERY ));
907 :
908 : //because of this name this border will be used for marking the legend
909 41 : ShapeFactory(m_xShapeFactory).setShapeName( xBorder, C2U("MarkHandles") );
910 : }
911 :
912 : // create entries
913 41 : double fViewFontSize = lcl_CalcViewFontSize( xLegendProp, rPageSize );//todo
914 : // #i109336# Improve auto positioning in chart
915 41 : sal_Int32 nSymbolHeigth = static_cast< sal_Int32 >( fViewFontSize * 0.6 );
916 41 : sal_Int32 nSymbolWidth = static_cast< sal_Int32 >( nSymbolHeigth );
917 :
918 41 : ::std::vector< LegendEntryProvider* >::const_iterator aIter = m_aLegendEntryProviderList.begin();
919 41 : const ::std::vector< LegendEntryProvider* >::const_iterator aEnd = m_aLegendEntryProviderList.end();
920 82 : for( aIter = m_aLegendEntryProviderList.begin(); aIter != aEnd; ++aIter )
921 : {
922 41 : LegendEntryProvider* pLegendEntryProvider( *aIter );
923 41 : if( pLegendEntryProvider )
924 : {
925 41 : awt::Size aCurrentRatio = pLegendEntryProvider->getPreferredLegendKeyAspectRatio();
926 41 : sal_Int32 nCurrentWidth = aCurrentRatio.Width;
927 41 : if( aCurrentRatio.Height > 0 )
928 : {
929 41 : nCurrentWidth = nSymbolHeigth* aCurrentRatio.Width/aCurrentRatio.Height;
930 : }
931 41 : nSymbolWidth = std::max( nSymbolWidth, nCurrentWidth );
932 : }
933 : }
934 41 : awt::Size aMaxSymbolExtent( nSymbolWidth, nSymbolHeigth );
935 :
936 41 : tViewLegendEntryContainer aViewEntries;
937 82 : for( aIter = m_aLegendEntryProviderList.begin(); aIter != aEnd; ++aIter )
938 : {
939 41 : LegendEntryProvider* pLegendEntryProvider( *aIter );
940 41 : if( pLegendEntryProvider )
941 : {
942 41 : std::vector< ViewLegendEntry > aNewEntries = pLegendEntryProvider->createLegendEntries( aMaxSymbolExtent, eExpansion, xLegendProp, xLegendContainer, m_xShapeFactory, m_xContext );
943 41 : aViewEntries.insert( aViewEntries.end(), aNewEntries.begin(), aNewEntries.end() );
944 : }
945 : }
946 :
947 41 : bool bSymbolsLeftSide = lcl_shouldSymbolsBePlacedOnTheLeftSide( xLegendProp, m_nDefaultWritingMode );
948 :
949 : // place entries
950 : aLegendSize = lcl_placeLegendEntries( aViewEntries, eExpansion, bSymbolsLeftSide, fViewFontSize, aMaxSymbolExtent
951 41 : , aTextProperties, xLegendContainer, m_xShapeFactory, aLegendSize );
952 :
953 41 : if( xBorder.is() )
954 41 : xBorder->setSize( aLegendSize );
955 41 : }
956 : }
957 0 : catch( const uno::Exception & ex )
958 : {
959 : ASSERT_EXCEPTION( ex );
960 : }
961 : }
962 :
963 : // ----------------------------------------
964 :
965 41 : void VLegend::changePosition(
966 : awt::Rectangle & rOutAvailableSpace,
967 : const awt::Size & rPageSize )
968 : {
969 41 : if(! m_xShape.is())
970 41 : return;
971 :
972 : try
973 : {
974 : // determine position and alignment depending on default position
975 41 : awt::Size aLegendSize = m_xShape->getSize();
976 41 : Reference< beans::XPropertySet > xLegendProp( m_xLegend, uno::UNO_QUERY_THROW );
977 41 : chart2::RelativePosition aRelativePosition;
978 :
979 : bool bAutoPosition =
980 41 : ! (xLegendProp->getPropertyValue( C2U( "RelativePosition" )) >>= aRelativePosition);
981 :
982 41 : LegendPosition ePos = LegendPosition_CUSTOM;
983 41 : xLegendProp->getPropertyValue( C2U( "AnchorPosition" )) >>= ePos;
984 :
985 : //calculate position
986 41 : if( bAutoPosition )
987 : {
988 : // auto position: relative to remaining space
989 41 : aRelativePosition = lcl_getDefaultPosition( ePos, rOutAvailableSpace, rPageSize );
990 : awt::Point aPos = lcl_calculatePositionAndRemainingSpace(
991 41 : rOutAvailableSpace, rPageSize, aRelativePosition, ePos, aLegendSize );
992 41 : 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 41 : }
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: */
|