| /************************************************************** |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| *************************************************************/ |
| |
| |
| |
| // MARKER(update_precomp.py): autogen include statement, do not remove |
| #include "precompiled_chart2.hxx" |
| #include "VLegend.hxx" |
| #include "macros.hxx" |
| #include "PropertyMapper.hxx" |
| #include "CommonConverters.hxx" |
| #include "ObjectIdentifier.hxx" |
| #include "RelativePositionHelper.hxx" |
| #include "ShapeFactory.hxx" |
| #include "RelativeSizeHelper.hxx" |
| #include "LegendEntryProvider.hxx" |
| #include "chartview/DrawModelWrapper.hxx" |
| #include <com/sun/star/text/XTextRange.hpp> |
| #include <com/sun/star/text/WritingMode2.hpp> |
| #include <com/sun/star/beans/XPropertySet.hpp> |
| #include <com/sun/star/beans/XPropertyState.hpp> |
| #include <com/sun/star/drawing/TextHorizontalAdjust.hpp> |
| #include <com/sun/star/drawing/LineJoint.hpp> |
| #include <com/sun/star/chart/ChartLegendExpansion.hpp> |
| #include <com/sun/star/chart2/LegendPosition.hpp> |
| #include <com/sun/star/chart2/RelativePosition.hpp> |
| #include <rtl/ustrbuf.hxx> |
| #include <svl/languageoptions.hxx> |
| |
| #include <vector> |
| #include <algorithm> |
| |
| using namespace ::com::sun::star; |
| using namespace ::com::sun::star::chart2; |
| |
| using ::com::sun::star::uno::Reference; |
| using ::com::sun::star::uno::Sequence; |
| using ::rtl::OUString; |
| using ::rtl::OUStringBuffer; |
| |
| //............................................................................. |
| namespace chart |
| { |
| //............................................................................. |
| |
| namespace |
| { |
| |
| typedef ::std::pair< ::chart::tNameSequence, ::chart::tAnySequence > tPropertyValues; |
| |
| typedef ::std::vector< ViewLegendEntry > tViewLegendEntryContainer; |
| |
| double lcl_CalcViewFontSize( |
| const Reference< beans::XPropertySet > & xProp, |
| const awt::Size & rReferenceSize ) |
| { |
| double fResult = 10.0; |
| |
| awt::Size aPropRefSize; |
| float fFontHeight( 0.0 ); |
| if( xProp.is() && ( xProp->getPropertyValue( C2U( "CharHeight" )) >>= fFontHeight )) |
| { |
| fResult = fFontHeight; |
| try |
| { |
| if( (xProp->getPropertyValue( C2U( "ReferencePageSize" )) >>= aPropRefSize) && |
| (aPropRefSize.Height > 0)) |
| { |
| // todo: find out if asian text is really used |
| // Reference< beans::XPropertySetInfo >xInfo( xProp, uno::UNO_QUERY ); |
| // float fFontHeight2 = fFontHeight; |
| // if( xInfo.is() && |
| // xInfo->hasPropertyByName(C2U("CharHeightAsian")) && |
| // (xProp->getPropertyValue(C2U("CharHeightAsian")) >>= fFontHeight2) && |
| // fFontHeight2 > fFontHeight ) |
| // { |
| // fFontHeight = fFontHeight2; |
| // } |
| |
| // if( xInfo.is() && |
| // xInfo->hasPropertyByName(C2U("CharHeightComplex")) && |
| // (xProp->getPropertyValue(C2U("CharHeightComplex")) >>= fFontHeight2) && |
| // fFontHeight2 > fFontHeight ) |
| // { |
| // fFontHeight = fFontHeight2; |
| // } |
| |
| fResult = ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize ); |
| } |
| } |
| catch( const uno::Exception & ex ) |
| { |
| ASSERT_EXCEPTION( ex ); |
| } |
| } |
| |
| // pt -> 1/100th mm |
| return (fResult * (2540.0 / 72.0)); |
| } |
| |
| void lcl_getProperties( |
| const Reference< beans::XPropertySet > & xLegendProp, |
| tPropertyValues & rOutLineFillProperties, |
| tPropertyValues & rOutTextProperties, |
| const awt::Size & rReferenceSize ) |
| { |
| // Get Line- and FillProperties from model legend |
| if( xLegendProp.is()) |
| { |
| // set rOutLineFillProperties |
| ::chart::tPropertyNameValueMap aLineFillValueMap; |
| ::chart::PropertyMapper::getValueMap( aLineFillValueMap, ::chart::PropertyMapper::getPropertyNameMapForFillAndLineProperties(), xLegendProp ); |
| |
| aLineFillValueMap[ C2U("LineJoint") ] = uno::makeAny( drawing::LineJoint_ROUND ); |
| |
| ::chart::PropertyMapper::getMultiPropertyListsFromValueMap( |
| rOutLineFillProperties.first, rOutLineFillProperties.second, aLineFillValueMap ); |
| |
| // set rOutTextProperties |
| ::chart::tPropertyNameValueMap aTextValueMap; |
| ::chart::PropertyMapper::getValueMap( aTextValueMap, ::chart::PropertyMapper::getPropertyNameMapForCharacterProperties(), xLegendProp ); |
| |
| drawing::TextHorizontalAdjust eHorizAdjust( drawing::TextHorizontalAdjust_LEFT ); |
| aTextValueMap[ C2U("TextAutoGrowHeight") ] = uno::makeAny( sal_True ); |
| aTextValueMap[ C2U("TextAutoGrowWidth") ] = uno::makeAny( sal_True ); |
| aTextValueMap[ C2U("TextHorizontalAdjust") ] = uno::makeAny( eHorizAdjust ); |
| aTextValueMap[ C2U("TextMaximumFrameWidth") ] = uno::makeAny( rReferenceSize.Width ); //needs to be overwritten by actual available space in the legend |
| |
| // recalculate font size |
| awt::Size aPropRefSize; |
| float fFontHeight( 0.0 ); |
| if( (xLegendProp->getPropertyValue( C2U( "ReferencePageSize" )) >>= aPropRefSize) && |
| (aPropRefSize.Height > 0) && |
| (aTextValueMap[ C2U("CharHeight") ] >>= fFontHeight) ) |
| { |
| aTextValueMap[ C2U("CharHeight") ] = uno::makeAny( |
| static_cast< float >( |
| ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize ))); |
| |
| if( aTextValueMap[ C2U("CharHeightAsian") ] >>= fFontHeight ) |
| { |
| aTextValueMap[ C2U("CharHeightAsian") ] = uno::makeAny( |
| static_cast< float >( |
| ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize ))); |
| } |
| if( aTextValueMap[ C2U("CharHeightComplex") ] >>= fFontHeight ) |
| { |
| aTextValueMap[ C2U("CharHeightComplex") ] = uno::makeAny( |
| static_cast< float >( |
| ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize ))); |
| } |
| } |
| |
| ::chart::PropertyMapper::getMultiPropertyListsFromValueMap( |
| rOutTextProperties.first, rOutTextProperties.second, aTextValueMap ); |
| } |
| } |
| |
| awt::Size lcl_createTextShapes( |
| const tViewLegendEntryContainer & rEntries, |
| const Reference< lang::XMultiServiceFactory > & xShapeFactory, |
| const Reference< drawing::XShapes > & xTarget, |
| ::std::vector< Reference< drawing::XShape > > & rOutTextShapes, |
| const tPropertyValues & rTextProperties ) |
| { |
| awt::Size aResult; |
| |
| for( tViewLegendEntryContainer::const_iterator aIt( rEntries.begin()); |
| aIt != rEntries.end(); ++aIt ) |
| { |
| try |
| { |
| // create label shape |
| Reference< drawing::XShape > xEntry( |
| xShapeFactory->createInstance( |
| C2U( "com.sun.star.drawing.TextShape" )), uno::UNO_QUERY_THROW ); |
| xTarget->add( xEntry ); |
| |
| // set label text |
| Sequence< Reference< XFormattedString > > aLabelSeq = (*aIt).aLabel; |
| for( sal_Int32 i = 0; i < aLabelSeq.getLength(); ++i ) |
| { |
| // todo: support more than one text range |
| if( i == 1 ) |
| break; |
| |
| Reference< text::XTextRange > xRange( xEntry, uno::UNO_QUERY ); |
| OUString aLabelString( aLabelSeq[i]->getString()); |
| // workaround for Issue #i67540# |
| if( aLabelString.isEmpty() ) |
| aLabelString = C2U(" "); |
| if( xRange.is()) |
| xRange->setString( aLabelString ); |
| |
| PropertyMapper::setMultiProperties( |
| rTextProperties.first, rTextProperties.second, |
| Reference< beans::XPropertySet >( xRange, uno::UNO_QUERY )); |
| |
| // adapt max-extent |
| awt::Size aCurrSize( xEntry->getSize()); |
| aResult.Width = ::std::max( aResult.Width, aCurrSize.Width ); |
| aResult.Height = ::std::max( aResult.Height, aCurrSize.Height ); |
| } |
| |
| rOutTextShapes.push_back( xEntry ); |
| } |
| catch( uno::Exception & ex ) |
| { |
| ASSERT_EXCEPTION( ex ); |
| } |
| } |
| |
| return aResult; |
| } |
| |
| void lcl_collectColumnWidths( std::vector< sal_Int32 >& rColumnWidths, const sal_Int32 nNumberOfRows, const sal_Int32 nNumberOfColumns |
| , const ::std::vector< Reference< drawing::XShape > > aTextShapes, sal_Int32 nSymbolPlusDistanceWidth ) |
| { |
| rColumnWidths.clear(); |
| sal_Int32 nRow = 0; |
| sal_Int32 nColumn = 0; |
| sal_Int32 nNumberOfEntries = aTextShapes.size(); |
| for( ; nRow < nNumberOfRows; ++nRow ) |
| { |
| for( nColumn = 0; nColumn < nNumberOfColumns; ++nColumn ) |
| { |
| sal_Int32 nEntry = (nColumn + nRow * nNumberOfColumns); |
| if( nEntry < nNumberOfEntries ) |
| { |
| awt::Size aTextSize( aTextShapes[ nEntry ]->getSize() ); |
| sal_Int32 nWidth = nSymbolPlusDistanceWidth + aTextSize.Width; |
| if( nRow==0 ) |
| rColumnWidths.push_back( nWidth ); |
| else |
| rColumnWidths[nColumn] = ::std::max( nWidth, rColumnWidths[nColumn] ); |
| } |
| } |
| } |
| } |
| |
| void lcl_collectRowHeighs( std::vector< sal_Int32 >& rRowHeights, const sal_Int32 nNumberOfRows, const sal_Int32 nNumberOfColumns |
| , const ::std::vector< Reference< drawing::XShape > > aTextShapes ) |
| { |
| // calculate maximum height for each row |
| // and collect column widths |
| rRowHeights.clear(); |
| sal_Int32 nRow = 0; |
| sal_Int32 nColumn = 0; |
| sal_Int32 nNumberOfEntries = aTextShapes.size(); |
| for( ; nRow < nNumberOfRows; ++nRow ) |
| { |
| sal_Int32 nCurrentRowHeight = 0; |
| for( nColumn = 0; nColumn < nNumberOfColumns; ++nColumn ) |
| { |
| sal_Int32 nEntry = (nColumn + nRow * nNumberOfColumns); |
| if( nEntry < nNumberOfEntries ) |
| { |
| awt::Size aTextSize( aTextShapes[ nEntry ]->getSize() ); |
| nCurrentRowHeight = ::std::max( nCurrentRowHeight, aTextSize.Height ); |
| } |
| } |
| rRowHeights.push_back( nCurrentRowHeight ); |
| } |
| } |
| |
| sal_Int32 lcl_getTextLineHeight( const std::vector< sal_Int32 >& aRowHeights, const sal_Int32 nNumberOfRows, double fViewFontSize ) |
| { |
| const sal_Int32 nFontHeight = static_cast< sal_Int32 >( fViewFontSize ); |
| sal_Int32 nTextLineHeight = nFontHeight; |
| for( sal_Int32 nR=0; nR<nNumberOfRows; nR++ ) |
| { |
| sal_Int32 nFullTextHeight = aRowHeights[ nR ]; |
| if( ( nFullTextHeight / nFontHeight ) <= 1 ) |
| { |
| nTextLineHeight = nFullTextHeight;//found an entry with one line-> have real text height |
| break; |
| } |
| } |
| return nTextLineHeight; |
| } |
| |
| //returns resulting legend size |
| awt::Size lcl_placeLegendEntries( |
| tViewLegendEntryContainer & rEntries, |
| ::com::sun::star::chart::ChartLegendExpansion eExpansion, |
| bool bSymbolsLeftSide, |
| double fViewFontSize, |
| const awt::Size& rMaxSymbolExtent, |
| tPropertyValues & rTextProperties, |
| const Reference< drawing::XShapes > & xTarget, |
| const Reference< lang::XMultiServiceFactory > & xShapeFactory, |
| const awt::Size & rAvailableSpace ) |
| { |
| bool bIsCustomSize = (eExpansion == ::com::sun::star::chart::ChartLegendExpansion_CUSTOM); |
| awt::Size aResultingLegendSize(0,0); |
| if( bIsCustomSize ) |
| aResultingLegendSize = rAvailableSpace; |
| |
| // #i109336# Improve auto positioning in chart |
| sal_Int32 nXPadding = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.33 ) ); |
| //sal_Int32 nXPadding = static_cast< sal_Int32 >( std::max( 200.0, fViewFontSize * 0.33 ) ); |
| sal_Int32 nXOffset = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.66 ) ); |
| sal_Int32 nYPadding = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.2 ) ); |
| sal_Int32 nYOffset = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.2 ) ); |
| //sal_Int32 nYOffset = static_cast< sal_Int32 >( std::max( 230.0, fViewFontSize * 0.45 ) ); |
| |
| const sal_Int32 nSymbolToTextDistance = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.22 ) );//minimum 1mm |
| const sal_Int32 nSymbolPlusDistanceWidth = rMaxSymbolExtent.Width + nSymbolToTextDistance; |
| sal_Int32 nMaxTextWidth = rAvailableSpace.Width - (2 * nXPadding) - nSymbolPlusDistanceWidth; |
| rtl::OUString aPropNameTextMaximumFrameWidth( C2U("TextMaximumFrameWidth") ); |
| uno::Any* pFrameWidthAny = PropertyMapper::getValuePointer( rTextProperties.second, rTextProperties.first, aPropNameTextMaximumFrameWidth); |
| if(pFrameWidthAny) |
| { |
| if( eExpansion == ::com::sun::star::chart::ChartLegendExpansion_HIGH ) |
| { |
| // limit the width of texts to 30% of the total available width |
| // #i109336# Improve auto positioning in chart |
| nMaxTextWidth = rAvailableSpace.Width * 3 / 10; |
| } |
| *pFrameWidthAny = uno::makeAny(nMaxTextWidth); |
| } |
| |
| ::std::vector< Reference< drawing::XShape > > aTextShapes; |
| awt::Size aMaxEntryExtent = lcl_createTextShapes( rEntries, xShapeFactory, xTarget, aTextShapes, rTextProperties ); |
| OSL_ASSERT( aTextShapes.size() == rEntries.size()); |
| |
| sal_Int32 nMaxEntryWidth = nXOffset + nSymbolPlusDistanceWidth + aMaxEntryExtent.Width; |
| sal_Int32 nMaxEntryHeight = nYOffset + aMaxEntryExtent.Height; |
| sal_Int32 nNumberOfEntries = rEntries.size(); |
| |
| sal_Int32 nNumberOfColumns = 0, nNumberOfRows = 0; |
| std::vector< sal_Int32 > aColumnWidths; |
| std::vector< sal_Int32 > aRowHeights; |
| |
| sal_Int32 nTextLineHeight = static_cast< sal_Int32 >( fViewFontSize ); |
| |
| // determine layout depending on LegendExpansion |
| if( eExpansion == ::com::sun::star::chart::ChartLegendExpansion_CUSTOM ) |
| { |
| sal_Int32 nCurrentRow=0; |
| sal_Int32 nCurrentColumn=-1; |
| sal_Int32 nColumnCount=0; |
| sal_Int32 nMaxColumnCount=-1; |
| for( sal_Int32 nN=0; nN<static_cast<sal_Int32>(aTextShapes.size()); nN++ ) |
| { |
| Reference< drawing::XShape > xShape( aTextShapes[nN] ); |
| if( !xShape.is() ) |
| continue; |
| awt::Size aSize( xShape->getSize() ); |
| sal_Int32 nNewWidth = aSize.Width + nSymbolPlusDistanceWidth; |
| sal_Int32 nCurrentColumnCount = aColumnWidths.size(); |
| |
| //are we allowed to add a new column? |
| if( nMaxColumnCount==-1 || (nCurrentColumn+1) < nMaxColumnCount ) |
| { |
| //try add a new column |
| nCurrentColumn++; |
| if( nCurrentColumn < nCurrentColumnCount ) |
| { |
| //check wether the current column width is sufficient for the new entry |
| if( aColumnWidths[nCurrentColumn]>=nNewWidth ) |
| { |
| //all good proceed with next entry |
| continue; |
| } |
| } |
| if( nCurrentColumn < nCurrentColumnCount ) |
| aColumnWidths[nCurrentColumn] = std::max( nNewWidth, aColumnWidths[nCurrentColumn] ); |
| else |
| aColumnWidths.push_back(nNewWidth); |
| |
| //do the columns still fit into the given size? |
| nCurrentColumnCount = aColumnWidths.size();//update count |
| sal_Int32 nSumWidth = 0; |
| for( sal_Int32 nC=0; nC<nCurrentColumnCount; nC++ ) |
| nSumWidth += aColumnWidths[nC]; |
| |
| if( nSumWidth <= rAvailableSpace.Width || nCurrentColumnCount==1 ) |
| { |
| //all good proceed with next entry |
| continue; |
| } |
| else |
| { |
| //not enough space for the current amount of columns |
| //try again with less columns |
| nMaxColumnCount = nCurrentColumnCount-1; |
| nN=-1; |
| nCurrentRow=0; |
| nCurrentColumn=-1; |
| nColumnCount=0; |
| aColumnWidths.clear(); |
| } |
| } |
| else |
| { |
| //add a new row and try the same entry again |
| nCurrentRow++; |
| nCurrentColumn=-1; |
| nN--; |
| } |
| } |
| nNumberOfColumns = aColumnWidths.size(); |
| nNumberOfRows = nCurrentRow+1; |
| |
| //check if there is not enough space so that some entries must be removed |
| lcl_collectRowHeighs( aRowHeights, nNumberOfRows, nNumberOfColumns, aTextShapes ); |
| nTextLineHeight = lcl_getTextLineHeight( aRowHeights, nNumberOfRows, fViewFontSize ); |
| sal_Int32 nSumHeight = 0; |
| for( sal_Int32 nR=0; nR<nNumberOfRows; nR++ ) |
| nSumHeight += aRowHeights[nR]; |
| sal_Int32 nRemainingSpace = rAvailableSpace.Height - nSumHeight; |
| |
| if( nRemainingSpace<0 ) |
| { |
| //remove entries that are too big |
| for( sal_Int32 nR=nNumberOfRows; nR--; ) |
| { |
| for( sal_Int32 nC=nNumberOfColumns; nC--; ) |
| { |
| sal_Int32 nEntry = (nC + nR * nNumberOfColumns); |
| if( nEntry < static_cast<sal_Int32>(aTextShapes.size()) ) |
| { |
| DrawModelWrapper::removeShape( aTextShapes[nEntry] ); |
| aTextShapes.pop_back(); |
| } |
| if( nEntry < nNumberOfEntries ) |
| { |
| DrawModelWrapper::removeShape( rEntries[ nEntry ].aSymbol ); |
| rEntries.pop_back(); |
| nNumberOfEntries--; |
| } |
| } |
| nSumHeight -= aRowHeights[nR]; |
| aRowHeights.pop_back(); |
| nRemainingSpace = rAvailableSpace.Height - nSumHeight; |
| if( nRemainingSpace>=0 ) |
| break; |
| } |
| nNumberOfRows = static_cast<sal_Int32>(aRowHeights.size()); |
| } |
| if( nRemainingSpace > 0 ) |
| { |
| sal_Int32 nNormalSpacingHeight = 2*nYPadding+(nNumberOfRows-1)*nYOffset; |
| if( nRemainingSpace < nNormalSpacingHeight ) |
| { |
| //reduce spacing between the entries |
| nYPadding = nYOffset = nRemainingSpace/(nNumberOfRows+1); |
| } |
| else |
| { |
| //we have some space left that should be spread equally between all rows |
| sal_Int32 nRemainingSingleSpace = (nRemainingSpace-nNormalSpacingHeight)/(nNumberOfRows+1); |
| nYPadding += nRemainingSingleSpace; |
| nYOffset += nRemainingSingleSpace; |
| } |
| } |
| |
| //check spacing between columns |
| sal_Int32 nSumWidth = 0; |
| for( sal_Int32 nC=0; nC<nNumberOfColumns; nC++ ) |
| nSumWidth += aColumnWidths[nC]; |
| nRemainingSpace = rAvailableSpace.Width - nSumWidth; |
| if( nRemainingSpace>=0 ) |
| { |
| sal_Int32 nNormalSpacingWidth = 2*nXPadding+(nNumberOfColumns-1)*nXOffset; |
| if( nRemainingSpace < nNormalSpacingWidth ) |
| { |
| //reduce spacing between the entries |
| nXPadding = nXOffset = nRemainingSpace/(nNumberOfColumns+1); |
| } |
| else |
| { |
| //we have some space left that should be spread equally between all columns |
| sal_Int32 nRemainingSingleSpace = (nRemainingSpace-nNormalSpacingWidth)/(nNumberOfColumns+1); |
| nXPadding += nRemainingSingleSpace; |
| nXOffset += nRemainingSingleSpace; |
| } |
| } |
| } |
| else if( eExpansion == ::com::sun::star::chart::ChartLegendExpansion_HIGH ) |
| { |
| sal_Int32 nMaxNumberOfRows = nMaxEntryHeight |
| ? (rAvailableSpace.Height - 2*nYPadding ) / nMaxEntryHeight |
| : 0; |
| |
| nNumberOfColumns = nMaxNumberOfRows |
| ? static_cast< sal_Int32 >( |
| ceil( static_cast< double >( nNumberOfEntries ) / |
| static_cast< double >( nMaxNumberOfRows ) )) |
| : 0; |
| nNumberOfRows = nNumberOfColumns |
| ? static_cast< sal_Int32 >( |
| ceil( static_cast< double >( nNumberOfEntries ) / |
| static_cast< double >( nNumberOfColumns ) )) |
| : 0; |
| } |
| else if( eExpansion == ::com::sun::star::chart::ChartLegendExpansion_WIDE ) |
| { |
| sal_Int32 nMaxNumberOfColumns = nMaxEntryWidth |
| ? (rAvailableSpace.Width - 2*nXPadding ) / nMaxEntryWidth |
| : 0; |
| |
| nNumberOfRows = nMaxNumberOfColumns |
| ? static_cast< sal_Int32 >( |
| ceil( static_cast< double >( nNumberOfEntries ) / |
| static_cast< double >( nMaxNumberOfColumns ) )) |
| : 0; |
| nNumberOfColumns = nNumberOfRows |
| ? static_cast< sal_Int32 >( |
| ceil( static_cast< double >( nNumberOfEntries ) / |
| static_cast< double >( nNumberOfRows ) )) |
| : 0; |
| } |
| else // ::com::sun::star::chart::ChartLegendExpansion_BALANCED |
| { |
| double fAspect = nMaxEntryHeight |
| ? static_cast< double >( nMaxEntryWidth ) / static_cast< double >( nMaxEntryHeight ) |
| : 0.0; |
| |
| nNumberOfRows = static_cast< sal_Int32 >( |
| ceil( sqrt( static_cast< double >( nNumberOfEntries ) * fAspect ))); |
| nNumberOfColumns = nNumberOfRows |
| ? static_cast< sal_Int32 >( |
| ceil( static_cast< double >( nNumberOfEntries ) / |
| static_cast< double >( nNumberOfRows ) )) |
| : 0; |
| } |
| |
| if(nNumberOfRows<=0) |
| return aResultingLegendSize; |
| |
| if( eExpansion != ::com::sun::star::chart::ChartLegendExpansion_CUSTOM ) |
| { |
| lcl_collectColumnWidths( aColumnWidths, nNumberOfRows, nNumberOfColumns, aTextShapes, nSymbolPlusDistanceWidth ); |
| lcl_collectRowHeighs( aRowHeights, nNumberOfRows, nNumberOfColumns, aTextShapes ); |
| nTextLineHeight = lcl_getTextLineHeight( aRowHeights, nNumberOfRows, fViewFontSize ); |
| } |
| |
| sal_Int32 nCurrentXPos = nXPadding; |
| sal_Int32 nCurrentYPos = nYPadding; |
| if( !bSymbolsLeftSide ) |
| nCurrentXPos = -nXPadding; |
| |
| // place entries into column and rows |
| sal_Int32 nMaxYPos = 0; |
| sal_Int32 nRow = 0; |
| sal_Int32 nColumn = 0; |
| for( nColumn = 0; nColumn < nNumberOfColumns; ++nColumn ) |
| { |
| nCurrentYPos = nYPadding; |
| for( nRow = 0; nRow < nNumberOfRows; ++nRow ) |
| { |
| sal_Int32 nEntry = (nColumn + nRow * nNumberOfColumns); |
| if( nEntry >= nNumberOfEntries ) |
| break; |
| |
| // text shape |
| Reference< drawing::XShape > xTextShape( aTextShapes[nEntry] ); |
| if( xTextShape.is() ) |
| { |
| awt::Size aTextSize( xTextShape->getSize() ); |
| sal_Int32 nTextXPos = nCurrentXPos + nSymbolPlusDistanceWidth; |
| if( !bSymbolsLeftSide ) |
| nTextXPos = nCurrentXPos - nSymbolPlusDistanceWidth - aTextSize.Width; |
| xTextShape->setPosition( awt::Point( nTextXPos, nCurrentYPos )); |
| } |
| |
| // symbol |
| Reference< drawing::XShape > xSymbol( rEntries[ nEntry ].aSymbol ); |
| if( xSymbol.is() ) |
| { |
| awt::Size aSymbolSize( rMaxSymbolExtent ); |
| sal_Int32 nSymbolXPos = nCurrentXPos; |
| if( !bSymbolsLeftSide ) |
| nSymbolXPos = nCurrentXPos - rMaxSymbolExtent.Width; |
| sal_Int32 nSymbolYPos = nCurrentYPos + ( ( nTextLineHeight - aSymbolSize.Height ) / 2 ); |
| xSymbol->setPosition( awt::Point( nSymbolXPos, nSymbolYPos ) ); |
| } |
| |
| nCurrentYPos += aRowHeights[ nRow ]; |
| if( nRow+1 < nNumberOfRows ) |
| nCurrentYPos += nYOffset; |
| nMaxYPos = ::std::max( nMaxYPos, nCurrentYPos ); |
| } |
| if( bSymbolsLeftSide ) |
| { |
| nCurrentXPos += aColumnWidths[nColumn]; |
| if( nColumn+1 < nNumberOfColumns ) |
| nCurrentXPos += nXOffset; |
| } |
| else |
| { |
| nCurrentXPos -= aColumnWidths[nColumn]; |
| if( nColumn+1 < nNumberOfColumns ) |
| nCurrentXPos -= nXOffset; |
| } |
| } |
| |
| if( !bIsCustomSize ) |
| { |
| if( bSymbolsLeftSide ) |
| aResultingLegendSize.Width = nCurrentXPos + nXPadding; |
| else |
| { |
| sal_Int32 nLegendWidth = -(nCurrentXPos-nXPadding); |
| aResultingLegendSize.Width = nLegendWidth; |
| } |
| aResultingLegendSize.Height = nMaxYPos + nYPadding; |
| } |
| |
| if( !bSymbolsLeftSide ) |
| { |
| sal_Int32 nLegendWidth = aResultingLegendSize.Width; |
| awt::Point aPos(0,0); |
| for( sal_Int32 nEntry=0; nEntry<nNumberOfEntries; nEntry++ ) |
| { |
| Reference< drawing::XShape > xSymbol( rEntries[ nEntry ].aSymbol ); |
| aPos = xSymbol->getPosition(); |
| aPos.X += nLegendWidth; |
| xSymbol->setPosition( aPos ); |
| Reference< drawing::XShape > xText( aTextShapes[ nEntry ] ); |
| aPos = xText->getPosition(); |
| aPos.X += nLegendWidth; |
| xText->setPosition( aPos ); |
| } |
| } |
| |
| return aResultingLegendSize; |
| } |
| |
| // #i109336# Improve auto positioning in chart |
| sal_Int32 lcl_getLegendLeftRightMargin() |
| { |
| return 210; // 1/100 mm |
| } |
| |
| // #i109336# Improve auto positioning in chart |
| sal_Int32 lcl_getLegendTopBottomMargin() |
| { |
| return 185; // 1/100 mm |
| } |
| |
| chart2::RelativePosition lcl_getDefaultPosition( LegendPosition ePos, const awt::Rectangle& rOutAvailableSpace, const awt::Size & rPageSize ) |
| { |
| chart2::RelativePosition aResult; |
| |
| switch( ePos ) |
| { |
| case LegendPosition_LINE_START: |
| { |
| // #i109336# Improve auto positioning in chart |
| const double fDefaultDistance = ( static_cast< double >( lcl_getLegendLeftRightMargin() ) / |
| static_cast< double >( rPageSize.Width ) ); |
| aResult = chart2::RelativePosition( |
| fDefaultDistance, 0.5, drawing::Alignment_LEFT ); |
| } |
| break; |
| case LegendPosition_LINE_END: |
| { |
| // #i109336# Improve auto positioning in chart |
| const double fDefaultDistance = ( static_cast< double >( lcl_getLegendLeftRightMargin() ) / |
| static_cast< double >( rPageSize.Width ) ); |
| aResult = chart2::RelativePosition( |
| 1.0 - fDefaultDistance, 0.5, drawing::Alignment_RIGHT ); |
| } |
| break; |
| case LegendPosition_PAGE_START: |
| { |
| // #i109336# Improve auto positioning in chart |
| const double fDefaultDistance = ( static_cast< double >( lcl_getLegendTopBottomMargin() ) / |
| static_cast< double >( rPageSize.Height ) ); |
| double fDistance = (static_cast<double>(rOutAvailableSpace.Y)/static_cast<double>(rPageSize.Height)) + fDefaultDistance; |
| aResult = chart2::RelativePosition( |
| 0.5, fDistance, drawing::Alignment_TOP ); |
| } |
| break; |
| case LegendPosition_PAGE_END: |
| { |
| // #i109336# Improve auto positioning in chart |
| const double fDefaultDistance = ( static_cast< double >( lcl_getLegendTopBottomMargin() ) / |
| static_cast< double >( rPageSize.Height ) ); |
| aResult = chart2::RelativePosition( |
| 0.5, 1.0 - fDefaultDistance, drawing::Alignment_BOTTOM ); |
| } |
| break; |
| |
| case LegendPosition_CUSTOM: |
| // to avoid warning |
| case LegendPosition_MAKE_FIXED_SIZE: |
| // nothing to be set |
| break; |
| } |
| |
| return aResult; |
| } |
| |
| /** @return |
| a point relative to the upper left corner that can be used for |
| XShape::setPosition() |
| */ |
| awt::Point lcl_calculatePositionAndRemainingSpace( |
| awt::Rectangle & rRemainingSpace, |
| const awt::Size & rPageSize, |
| chart2::RelativePosition aRelPos, |
| LegendPosition ePos, |
| const awt::Size& aLegendSize ) |
| { |
| // calculate position |
| awt::Point aResult( |
| static_cast< sal_Int32 >( aRelPos.Primary * rPageSize.Width ), |
| static_cast< sal_Int32 >( aRelPos.Secondary * rPageSize.Height )); |
| |
| aResult = RelativePositionHelper::getUpperLeftCornerOfAnchoredObject( |
| aResult, aLegendSize, aRelPos.Anchor ); |
| |
| // adapt rRemainingSpace if LegendPosition is not CUSTOM |
| // #i109336# Improve auto positioning in chart |
| sal_Int32 nXDistance = lcl_getLegendLeftRightMargin(); |
| sal_Int32 nYDistance = lcl_getLegendTopBottomMargin(); |
| switch( ePos ) |
| { |
| case LegendPosition_LINE_START: |
| { |
| sal_Int32 nExtent = aLegendSize.Width; |
| rRemainingSpace.Width -= ( nExtent + nXDistance ); |
| rRemainingSpace.X += ( nExtent + nXDistance ); |
| } |
| break; |
| case LegendPosition_LINE_END: |
| { |
| rRemainingSpace.Width -= ( aLegendSize.Width + nXDistance ); |
| } |
| break; |
| case LegendPosition_PAGE_START: |
| { |
| sal_Int32 nExtent = aLegendSize.Height; |
| rRemainingSpace.Height -= ( nExtent + nYDistance ); |
| rRemainingSpace.Y += ( nExtent + nYDistance ); |
| } |
| break; |
| case LegendPosition_PAGE_END: |
| { |
| rRemainingSpace.Height -= ( aLegendSize.Height + nYDistance ); |
| } |
| break; |
| |
| default: |
| // nothing |
| break; |
| } |
| |
| // adjust the legend position. Esp. for old files that had slightly smaller legends |
| const sal_Int32 nEdgeDistance( 30 ); |
| if( aResult.X + aLegendSize.Width > rPageSize.Width ) |
| { |
| sal_Int32 nNewX( (rPageSize.Width - aLegendSize.Width) - nEdgeDistance ); |
| if( nNewX > rPageSize.Width / 4 ) |
| aResult.X = nNewX; |
| } |
| if( aResult.Y + aLegendSize.Height > rPageSize.Height ) |
| { |
| sal_Int32 nNewY( (rPageSize.Height - aLegendSize.Height) - nEdgeDistance ); |
| if( nNewY > rPageSize.Height / 4 ) |
| aResult.Y = nNewY; |
| } |
| |
| return aResult; |
| } |
| |
| bool lcl_shouldSymbolsBePlacedOnTheLeftSide( const Reference< beans::XPropertySet >& xLegendProp, sal_Int16 nDefaultWritingMode ) |
| { |
| bool bSymbolsLeftSide = true; |
| try |
| { |
| if( SvtLanguageOptions().IsCTLFontEnabled() ) |
| { |
| if(xLegendProp.is()) |
| { |
| sal_Int16 nWritingMode=-1; |
| if( (xLegendProp->getPropertyValue( C2U("WritingMode") ) >>= nWritingMode) ) |
| { |
| if( nWritingMode == text::WritingMode2::PAGE ) |
| nWritingMode = nDefaultWritingMode; |
| if( nWritingMode == text::WritingMode2::RL_TB ) |
| bSymbolsLeftSide=false; |
| } |
| } |
| } |
| } |
| catch( uno::Exception & ex ) |
| { |
| ASSERT_EXCEPTION( ex ); |
| } |
| return bSymbolsLeftSide; |
| } |
| |
| } // anonymous namespace |
| |
| VLegend::VLegend( |
| const Reference< XLegend > & xLegend, |
| const Reference< uno::XComponentContext > & xContext, |
| const std::vector< LegendEntryProvider* >& rLegendEntryProviderList ) : |
| m_xLegend( xLegend ), |
| m_xContext( xContext ), |
| m_aLegendEntryProviderList( rLegendEntryProviderList ) |
| { |
| } |
| |
| // ---------------------------------------- |
| |
| void VLegend::init( |
| const Reference< drawing::XShapes >& xTargetPage, |
| const Reference< lang::XMultiServiceFactory >& xFactory, |
| const Reference< frame::XModel >& xModel ) |
| { |
| m_xTarget = xTargetPage; |
| m_xShapeFactory = xFactory; |
| m_xModel = xModel; |
| } |
| |
| // ---------------------------------------- |
| |
| void VLegend::setDefaultWritingMode( sal_Int16 nDefaultWritingMode ) |
| { |
| m_nDefaultWritingMode = nDefaultWritingMode; |
| } |
| |
| // ---------------------------------------- |
| |
| bool VLegend::isVisible( const Reference< XLegend > & xLegend ) |
| { |
| if( ! xLegend.is()) |
| return sal_False; |
| |
| sal_Bool bShow = sal_False; |
| try |
| { |
| Reference< beans::XPropertySet > xLegendProp( xLegend, uno::UNO_QUERY_THROW ); |
| xLegendProp->getPropertyValue( C2U( "Show" )) >>= bShow; |
| } |
| catch( uno::Exception & ex ) |
| { |
| ASSERT_EXCEPTION( ex ); |
| } |
| |
| return bShow; |
| } |
| |
| // ---------------------------------------- |
| |
| void VLegend::createShapes( |
| const awt::Size & rAvailableSpace, |
| const awt::Size & rPageSize ) |
| { |
| if(! (m_xLegend.is() && |
| m_xShapeFactory.is() && |
| m_xTarget.is())) |
| return; |
| |
| try |
| { |
| //create shape and add to page |
| m_xShape.set( m_xShapeFactory->createInstance( |
| C2U( "com.sun.star.drawing.GroupShape" )), uno::UNO_QUERY ); |
| m_xTarget->add( m_xShape ); |
| |
| // set name to enable selection |
| { |
| OUString aLegendParticle( ObjectIdentifier::createParticleForLegend( m_xLegend, m_xModel ) ); |
| ShapeFactory::setShapeName( m_xShape, ObjectIdentifier::createClassifiedIdentifierForParticle( aLegendParticle ) ); |
| } |
| |
| // create and insert sub-shapes |
| Reference< drawing::XShapes > xLegendContainer( m_xShape, uno::UNO_QUERY ); |
| if( xLegendContainer.is()) |
| { |
| Reference< drawing::XShape > xBorder( |
| m_xShapeFactory->createInstance( |
| C2U( "com.sun.star.drawing.RectangleShape" )), uno::UNO_QUERY ); |
| |
| // for quickly setting properties |
| tPropertyValues aLineFillProperties; |
| tPropertyValues aTextProperties; |
| |
| Reference< beans::XPropertySet > xLegendProp( m_xLegend, uno::UNO_QUERY ); |
| ::com::sun::star::chart::ChartLegendExpansion eExpansion = ::com::sun::star::chart::ChartLegendExpansion_HIGH; |
| awt::Size aLegendSize( rAvailableSpace ); |
| |
| if( xLegendProp.is()) |
| { |
| // get Expansion property |
| xLegendProp->getPropertyValue( C2U( "Expansion" )) >>= eExpansion; |
| if( eExpansion == ::com::sun::star::chart::ChartLegendExpansion_CUSTOM ) |
| { |
| RelativeSize aRelativeSize; |
| if ((xLegendProp->getPropertyValue( C2U( "RelativeSize" )) >>= aRelativeSize)) |
| { |
| aLegendSize.Width = static_cast<sal_Int32>( ::rtl::math::approxCeil( aRelativeSize.Primary * rPageSize.Width ) ); //i117185 |
| aLegendSize.Height = static_cast<sal_Int32>( ::rtl::math::approxCeil( aRelativeSize.Secondary * rPageSize.Height ) ); //i117185 |
| } |
| else |
| eExpansion = ::com::sun::star::chart::ChartLegendExpansion_HIGH; |
| } |
| lcl_getProperties( xLegendProp, aLineFillProperties, aTextProperties, rPageSize ); |
| } |
| |
| if( xBorder.is()) |
| { |
| xLegendContainer->add( xBorder ); |
| |
| // apply legend properties |
| PropertyMapper::setMultiProperties( |
| aLineFillProperties.first, aLineFillProperties.second, |
| Reference< beans::XPropertySet >( xBorder, uno::UNO_QUERY )); |
| |
| //because of this name this border will be used for marking the legend |
| ShapeFactory(m_xShapeFactory).setShapeName( xBorder, C2U("MarkHandles") ); |
| } |
| |
| // create entries |
| double fViewFontSize = lcl_CalcViewFontSize( xLegendProp, rPageSize );//todo |
| // #i109336# Improve auto positioning in chart |
| sal_Int32 nSymbolHeigth = static_cast< sal_Int32 >( fViewFontSize * 0.6 ); |
| sal_Int32 nSymbolWidth = static_cast< sal_Int32 >( nSymbolHeigth ); |
| |
| ::std::vector< LegendEntryProvider* >::const_iterator aIter = m_aLegendEntryProviderList.begin(); |
| const ::std::vector< LegendEntryProvider* >::const_iterator aEnd = m_aLegendEntryProviderList.end(); |
| for( aIter = m_aLegendEntryProviderList.begin(); aIter != aEnd; aIter++ ) |
| { |
| LegendEntryProvider* pLegendEntryProvider( *aIter ); |
| if( pLegendEntryProvider ) |
| { |
| awt::Size aCurrentRatio = pLegendEntryProvider->getPreferredLegendKeyAspectRatio(); |
| sal_Int32 nCurrentWidth = aCurrentRatio.Width; |
| if( aCurrentRatio.Height > 0 ) |
| { |
| nCurrentWidth = nSymbolHeigth* aCurrentRatio.Width/aCurrentRatio.Height; |
| } |
| nSymbolWidth = std::max( nSymbolWidth, nCurrentWidth ); |
| } |
| } |
| awt::Size aMaxSymbolExtent( nSymbolWidth, nSymbolHeigth ); |
| |
| tViewLegendEntryContainer aViewEntries; |
| for( aIter = m_aLegendEntryProviderList.begin(); aIter != aEnd; aIter++ ) |
| { |
| LegendEntryProvider* pLegendEntryProvider( *aIter ); |
| if( pLegendEntryProvider ) |
| { |
| std::vector< ViewLegendEntry > aNewEntries = pLegendEntryProvider->createLegendEntries( aMaxSymbolExtent, eExpansion, xLegendProp, xLegendContainer, m_xShapeFactory, m_xContext ); |
| aViewEntries.insert( aViewEntries.end(), aNewEntries.begin(), aNewEntries.end() ); |
| } |
| } |
| |
| bool bSymbolsLeftSide = lcl_shouldSymbolsBePlacedOnTheLeftSide( xLegendProp, m_nDefaultWritingMode ); |
| |
| // place entries |
| aLegendSize = lcl_placeLegendEntries( aViewEntries, eExpansion, bSymbolsLeftSide, fViewFontSize, aMaxSymbolExtent |
| , aTextProperties, xLegendContainer, m_xShapeFactory, aLegendSize ); |
| |
| if( xBorder.is() ) |
| xBorder->setSize( aLegendSize ); |
| } |
| } |
| catch( uno::Exception & ex ) |
| { |
| ASSERT_EXCEPTION( ex ); |
| } |
| } |
| |
| // ---------------------------------------- |
| |
| void VLegend::changePosition( |
| awt::Rectangle & rOutAvailableSpace, |
| const awt::Size & rPageSize ) |
| { |
| if(! m_xShape.is()) |
| return; |
| |
| try |
| { |
| // determine position and alignment depending on default position |
| awt::Size aLegendSize = m_xShape->getSize(); |
| Reference< beans::XPropertySet > xLegendProp( m_xLegend, uno::UNO_QUERY_THROW ); |
| chart2::RelativePosition aRelativePosition; |
| |
| bool bAutoPosition = |
| ! (xLegendProp->getPropertyValue( C2U( "RelativePosition" )) >>= aRelativePosition); |
| |
| LegendPosition ePos = LegendPosition_CUSTOM; |
| xLegendProp->getPropertyValue( C2U( "AnchorPosition" )) >>= ePos; |
| |
| //calculate position |
| if( bAutoPosition ) |
| { |
| // auto position: relative to remaining space |
| aRelativePosition = lcl_getDefaultPosition( ePos, rOutAvailableSpace, rPageSize ); |
| awt::Point aPos = lcl_calculatePositionAndRemainingSpace( |
| rOutAvailableSpace, rPageSize, aRelativePosition, ePos, aLegendSize ); |
| m_xShape->setPosition( aPos ); |
| } |
| else |
| { |
| // manual position: relative to whole page |
| awt::Rectangle aAvailableSpace( 0, 0, rPageSize.Width, rPageSize.Height ); |
| awt::Point aPos = lcl_calculatePositionAndRemainingSpace( |
| aAvailableSpace, rPageSize, aRelativePosition, ePos, aLegendSize ); |
| m_xShape->setPosition( aPos ); |
| |
| if( ePos != LegendPosition_CUSTOM ) |
| { |
| // calculate remaining space as if having autoposition: |
| aRelativePosition = lcl_getDefaultPosition( ePos, rOutAvailableSpace, rPageSize ); |
| lcl_calculatePositionAndRemainingSpace( |
| rOutAvailableSpace, rPageSize, aRelativePosition, ePos, aLegendSize ); |
| } |
| } |
| } |
| catch( uno::Exception & ex ) |
| { |
| ASSERT_EXCEPTION( ex ); |
| } |
| } |
| |
| //............................................................................. |
| } //namespace chart |
| //............................................................................. |