/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * 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 . */ #include "diagramlayoutatoms.hxx" #include #include #include #include "oox/helper/attributelist.hxx" #include "oox/drawingml/fillproperties.hxx" #include "oox/drawingml/lineproperties.hxx" #include "oox/drawingml/textbody.hxx" #include "oox/drawingml/textparagraph.hxx" #include "oox/drawingml/textrun.hxx" #include "oox/drawingml/customshapeproperties.hxx" #include "layoutnodecontext.hxx" using namespace ::com::sun::star; using namespace ::com::sun::star::uno; using namespace ::com::sun::star::xml::sax; using namespace ::oox::core; namespace oox { namespace drawingml { IteratorAttr::IteratorAttr( ) : mnAxis( 0 ) , mnCnt( -1 ) , mbHideLastTrans( false ) , mnPtType( 0 ) , mnSt( 0 ) , mnStep( 1 ) { } void IteratorAttr::loadFromXAttr( const Reference< XFastAttributeList >& xAttr ) { AttributeList attr( xAttr ); mnAxis = xAttr->getOptionalValueToken( XML_axis, 0 ); mnCnt = attr.getInteger( XML_cnt, -1 ); mbHideLastTrans = attr.getBool( XML_hideLastTrans, false ); mnPtType = xAttr->getOptionalValueToken( XML_ptType, 0 ); mnSt = attr.getInteger( XML_st, 0 ); mnStep = attr.getInteger( XML_step, 1 ); } ConditionAttr::ConditionAttr() : mnFunc( 0 ) , mnArg( 0 ) , mnOp( 0 ) { } void ConditionAttr::loadFromXAttr( const Reference< XFastAttributeList >& xAttr ) { mnFunc = xAttr->getOptionalValueToken( XML_func, 0 ); // mnArg will be -1 for "none" or any other unknown value mnArg = LayoutNodeContext::tagToVarIdx( xAttr->getOptionalValueToken( XML_arg, XML_none ) ); mnOp = xAttr->getOptionalValueToken( XML_op, 0 ); msVal = xAttr->getOptionalValue( XML_val ); } void LayoutAtom::dump(int level) { OSL_TRACE( "level = %d - %s of type %s", level, OUSTRING_TO_CSTR( msName ), typeid(*this).name() ); const std::vector& pChildren=getChildren(); std::for_each( pChildren.begin(), pChildren.end(), boost::bind( &LayoutAtom::dump, _1, level + 1 ) ); } ForEachAtom::ForEachAtom(const Reference< XFastAttributeList >& xAttributes) { maIter.loadFromXAttr(xAttributes); } void ForEachAtom::accept( LayoutAtomVisitor& rVisitor ) { rVisitor.visit(*this); } void ChooseAtom::accept( LayoutAtomVisitor& rVisitor ) { rVisitor.visit(*this); } ConditionAtom::ConditionAtom(const Reference< XFastAttributeList >& xAttributes) : mbElse( false ) { maIter.loadFromXAttr( xAttributes ); maCond.loadFromXAttr( xAttributes ); } const std::vector& ConditionAtom::getChildren() const { bool bDecisionVar=true; // HACK if( maCond.mnFunc == XML_var && maCond.mnArg == XML_dir && maCond.mnOp == XML_equ && maCond.msVal != "norm" ) bDecisionVar=false; if( bDecisionVar ) return mpChildNodes; else return mpElseChildNodes; } void ConditionAtom::accept( LayoutAtomVisitor& rVisitor ) { rVisitor.visit(*this); } void ConditionAtom::addChild( const LayoutAtomPtr & pNode ) { if( mbElse ) mpElseChildNodes.push_back( pNode ); else mpChildNodes.push_back( pNode ); } void ConstraintAtom::accept( LayoutAtomVisitor& rVisitor ) { rVisitor.visit(*this); } void AlgAtom::accept( LayoutAtomVisitor& rVisitor ) { rVisitor.visit(*this); } void AlgAtom::layoutShape( const ShapePtr& rShape, const Diagram& /*rDgm*/, const OUString& rName ) const { switch(mnType) { case XML_composite: { if( rShape->getChildren().empty() ) { rShape->setSize(awt::Size(50,50)); break; } // just put stuff below each other const sal_Int32 nIncX=0; const sal_Int32 nIncY=1; std::vector::const_iterator aCurrShape=rShape->getChildren().begin(); const std::vector::const_iterator aLastShape=rShape->getChildren().end(); // find biggest shape awt::Size aMaxSize; while( aCurrShape != aLastShape ) { const awt::Size& sz=(*aCurrShape)->getSize(); aMaxSize.Width = std::max( aMaxSize.Width, sz.Width); aMaxSize.Height = std::max( aMaxSize.Height, sz.Height); ++aCurrShape; } aCurrShape=rShape->getChildren().begin(); const awt::Point aStartPos=(*aCurrShape)->getPosition(); awt::Point aCurrPos=aStartPos; awt::Size aTotalSize; aTotalSize.Width = aMaxSize.Width; while( aCurrShape != aLastShape ) { const awt::Size& sz=(*aCurrShape)->getSize(); (*aCurrShape)->setPosition(aCurrPos); (*aCurrShape)->setSize( awt::Size(aMaxSize.Width, sz.Height)); aTotalSize.Height = std::max( aTotalSize.Height, aCurrPos.Y + sz.Height); aCurrPos.X += nIncX*sz.Width; aCurrPos.Y += nIncY*sz.Height; ++aCurrShape; } rShape->setSize(aTotalSize); break; } case XML_conn: break; case XML_cycle: { if( rShape->getChildren().empty() ) { rShape->setSize(awt::Size(50,50)); break; } const sal_Int32 nStartAngle=maMap.count(XML_stAng) ? maMap.find(XML_stAng)->second : 0; const sal_Int32 nSpanAngle=maMap.count(XML_spanAng) ? maMap.find(XML_spanAng)->second : 360; std::vector::const_iterator aCurrShape=rShape->getChildren().begin(); const std::vector::const_iterator aLastShape=rShape->getChildren().end(); const sal_Int32 nShapes=aLastShape-aCurrShape; // find biggest shape awt::Size aMaxSize; while( aCurrShape != aLastShape ) { const awt::Size& sz=(*aCurrShape)->getSize(); aMaxSize.Width = std::max( aMaxSize.Width, sz.Width); aMaxSize.Height = std::max( aMaxSize.Height, sz.Height); ++aCurrShape; } // layout shapes const sal_Int32 nMaxDim=std::max(aMaxSize.Width,aMaxSize.Height); awt::Size aTotalSize; aCurrShape=rShape->getChildren().begin(); for( sal_Int32 i=0; igetSize(); const double r=nShapes*nMaxDim/F_2PI * 360.0/nSpanAngle; const awt::Point aCurrPos( r + r*sin( (double(i)*nSpanAngle/nShapes + nStartAngle)*F_PI180 ), r - r*cos( (double(i)*nSpanAngle/nShapes + nStartAngle)*F_PI180 ) ); (*aCurrShape)->setPosition(aCurrPos); aTotalSize.Width = std::max( aTotalSize.Width, aCurrPos.X + sz.Width); aTotalSize.Height = std::max( aTotalSize.Height, aCurrPos.Y + sz.Height); } rShape->setSize(aTotalSize); break; } case XML_hierChild: case XML_hierRoot: break; case XML_lin: { if( rShape->getChildren().empty() ) { rShape->setSize(awt::Size(50,50)); break; } const sal_Int32 nDir=maMap.count(XML_linDir) ? maMap.find(XML_linDir)->second : XML_fromL; const sal_Int32 nIncX=nDir==XML_fromL ? 1 : (nDir==XML_fromR ? -1 : 0); const sal_Int32 nIncY=nDir==XML_fromT ? 1 : (nDir==XML_fromB ? -1 : 0); std::vector::const_iterator aCurrShape=rShape->getChildren().begin(); const std::vector::const_iterator aLastShape=rShape->getChildren().end(); const awt::Point aStartPos=(*aCurrShape)->getPosition(); awt::Point aCurrPos=aStartPos; awt::Size aTotalSize; while( aCurrShape != aLastShape ) { const awt::Size& sz=(*aCurrShape)->getSize(); (*aCurrShape)->setPosition(aCurrPos); aTotalSize.Width = std::max( aTotalSize.Width, aCurrPos.X + sz.Width); aTotalSize.Height = std::max( aTotalSize.Height, aCurrPos.Y + sz.Height); // HACK: the spacing is arbitrary aCurrPos.X += nIncX*(sz.Width+5); aCurrPos.Y += nIncY*(sz.Height+5); ++aCurrShape; } rShape->setSize(aTotalSize); break; } case XML_pyra: case XML_snake: break; case XML_sp: // HACK. Handled one level higher. Or rather, planned to break; case XML_tx: { TextBodyPtr pTextBody=rShape->getTextBody(); if( !pTextBody || pTextBody->getParagraphs().empty() || pTextBody->getParagraphs().front()->getRuns().empty() ) { rShape->setSize(awt::Size(5,5)); break; } // HACK - count chars & paragraphs to come up with *some* // notion of necessary size const sal_Int32 nHackyFontHeight=50; const sal_Int32 nHackyFontWidth=20; awt::Size aTotalSize; for( sal_uInt32 nPara=0; nParagetParagraphs().size(); ++nPara ) { aTotalSize.Height += nHackyFontHeight; sal_Int32 nLocalWidth=0; for( sal_uInt32 nRun=0; nRungetParagraphs().at(nPara)->getRuns().size(); ++nRun ) nLocalWidth += pTextBody->getParagraphs().at(nPara)->getRuns().at(nRun)->getText().getLength() * nHackyFontWidth; aTotalSize.Width = std::max( aTotalSize.Width, nLocalWidth); } rShape->setSize(aTotalSize); } default: break; } OSL_TRACE("Layouting shape %s: (%d,%d,%d,%d)", OUSTRING_TO_CSTR( rName ), rShape->getPosition().X, rShape->getPosition().Y, rShape->getSize().Width, rShape->getSize().Height); } void LayoutNode::accept( LayoutAtomVisitor& rVisitor ) { rVisitor.visit(*this); } bool LayoutNode::setupShape( const ShapePtr& rShape, const Diagram& rDgm, sal_uInt32 nIdx ) const { // find the data node to grab text from DiagramData::PointsNameMap::const_iterator aDataNode=rDgm.getData()->getPointsPresNameMap().find(msName); if( aDataNode != rDgm.getData()->getPointsPresNameMap().end() && aDataNode->second.size() > nIdx ) { OSL_TRACE( "Filling content from %d th layout node named \"%s\", modelId \"%s\"", nIdx, OUSTRING_TO_CSTR( msName ), OUSTRING_TO_CSTR( aDataNode->second.at(nIdx)->msModelId ) ); // got the presentation node - now, need the actual data node: const DiagramData::StringMap::const_iterator aNodeName=rDgm.getData()->getPresOfNameMap().find( aDataNode->second.at(nIdx)->msModelId); if( aNodeName != rDgm.getData()->getPresOfNameMap().end() ) { DiagramData::StringMap::value_type::second_type::const_iterator aVecIter=aNodeName->second.begin(); const DiagramData::StringMap::value_type::second_type::const_iterator aVecEnd=aNodeName->second.end(); while( aVecIter != aVecEnd ) { DiagramData::PointNameMap::const_iterator aDataNode2=rDgm.getData()->getPointNameMap().find(aVecIter->first); if( aVecIter->second == 0 ) { // grab shape attr from topmost element(s) rShape->getShapeProperties() = aDataNode2->second->mpShape->getShapeProperties(); rShape->getLineProperties() = aDataNode2->second->mpShape->getLineProperties(); rShape->getFillProperties() = aDataNode2->second->mpShape->getFillProperties(); rShape->getCustomShapeProperties() = aDataNode2->second->mpShape->getCustomShapeProperties(); rShape->setMasterTextListStyle( aDataNode2->second->mpShape->getMasterTextListStyle() ); OSL_TRACE( "Custom shape with preset type %d added for layout node named \"%s\"", rShape->getCustomShapeProperties()->getShapePresetType(), OUSTRING_TO_CSTR( msName ) ); } // append text with right outline level if( aDataNode2->second->mpShape->getTextBody() && !aDataNode2->second->mpShape->getTextBody()->getParagraphs().empty() && !aDataNode2->second->mpShape->getTextBody()->getParagraphs().front()->getRuns().empty() ) { TextBodyPtr pTextBody=rShape->getTextBody(); if( !pTextBody ) { pTextBody.reset( new TextBody() ); // also copy text attrs pTextBody->getTextListStyle() = aDataNode2->second->mpShape->getTextBody()->getTextListStyle(); pTextBody->getTextProperties() = aDataNode2->second->mpShape->getTextBody()->getTextProperties(); rShape->setTextBody(pTextBody); } TextParagraph& rPara=pTextBody->addParagraph(); if( aVecIter->second != -1 ) rPara.getProperties().setLevel(aVecIter->second); rPara.addRun( aDataNode2->second->mpShape->getTextBody()->getParagraphs().front()->getRuns().front()); rPara.getProperties().apply( aDataNode2->second->mpShape->getTextBody()->getParagraphs().front()->getProperties()); } ++aVecIter; } } else { OSL_TRACE("ShapeCreationVisitor::visit: no data node name found while processing shape type %d for layout node named \"%s\"", rShape->getCustomShapeProperties()->getShapePresetType(), OUSTRING_TO_CSTR( msName ) ); } // TODO(Q1): apply styling & coloring - taking // layout node's styleLbl for both style & color // now, but docs are a bit unclear on this if( !msStyleLabel.isEmpty() ) { OSL_TRACE("setting style with label %s", OUSTRING_TO_CSTR( msStyleLabel ) ); const DiagramQStyleMap::const_iterator aStyle=rDgm.getStyles().find(msStyleLabel); if( aStyle != rDgm.getStyles().end() ) { rShape->getShapeStyleRefs()[XML_fillRef] = aStyle->second.maFillStyle; OSL_TRACE("added fill style with id %d", aStyle->second.maFillStyle.mnThemedIdx); rShape->getShapeStyleRefs()[XML_lnRef] = aStyle->second.maLineStyle; OSL_TRACE("added line style with id %d", aStyle->second.maLineStyle.mnThemedIdx); rShape->getShapeStyleRefs()[XML_effectRef] = aStyle->second.maEffectStyle; OSL_TRACE("added effect style with id %d", aStyle->second.maEffectStyle.mnThemedIdx); rShape->getShapeStyleRefs()[XML_fontRef] = aStyle->second.maTextStyle; OSL_TRACE("added fontref style with id %d", aStyle->second.maTextStyle.mnThemedIdx); Color aColor=aStyle->second.maTextStyle.maPhClr; OSL_TRACE("added fontref color with alpha %d", aColor.getTransparency() ); } const DiagramColorMap::const_iterator aColor=rDgm.getColors().find(msStyleLabel); if( aColor != rDgm.getColors().end() ) { const DiagramColor& rColor=aColor->second; if( rColor.maFillColor.isUsed() ) rShape->getShapeStyleRefs()[XML_fillRef].maPhClr = rColor.maFillColor; if( rColor.maLineColor.isUsed() ) rShape->getShapeStyleRefs()[XML_lnRef].maPhClr = rColor.maLineColor; if( rColor.maEffectColor.isUsed() ) rShape->getShapeStyleRefs()[XML_effectRef].maPhClr = rColor.maEffectColor; if( rColor.maTextFillColor.isUsed() ) rShape->getShapeStyleRefs()[XML_fontRef].maPhClr = rColor.maTextFillColor; } } // even if no data node found, successful anyway. it's // contained at the layoutnode return true; } else { OSL_TRACE("ShapeCreationVisitor::visit: no text found while processing shape type %d for layout node named \"%s\"", rShape->getCustomShapeProperties()->getShapePresetType(), OUSTRING_TO_CSTR( msName ) ); } return false; } // Visitation class ShapeLayoutingVisitor : public LayoutAtomVisitor { ShapePtr mpParentShape; const Diagram& mrDgm; OUString maName; virtual void visit(ConstraintAtom& rAtom) SAL_OVERRIDE; virtual void visit(AlgAtom& rAtom) SAL_OVERRIDE; virtual void visit(ForEachAtom& rAtom) SAL_OVERRIDE; virtual void visit(ConditionAtom& rAtom) SAL_OVERRIDE; virtual void visit(ChooseAtom& rAtom) SAL_OVERRIDE; virtual void visit(LayoutNode& rAtom) SAL_OVERRIDE; public: ShapeLayoutingVisitor(const ShapePtr& rParentShape, const Diagram& rDgm, const OUString& rName) : mpParentShape(rParentShape), mrDgm(rDgm), maName(rName) {} void defaultVisit(LayoutAtom& rAtom); }; class ShallowPresNameVisitor : public LayoutAtomVisitor { const Diagram& mrDgm; size_t mnCnt; void defaultVisit(LayoutAtom& rAtom); virtual void visit(ConstraintAtom& rAtom) SAL_OVERRIDE; virtual void visit(AlgAtom& rAtom) SAL_OVERRIDE; virtual void visit(ForEachAtom& rAtom) SAL_OVERRIDE; virtual void visit(ConditionAtom& rAtom) SAL_OVERRIDE; virtual void visit(ChooseAtom& rAtom) SAL_OVERRIDE; virtual void visit(LayoutNode& rAtom) SAL_OVERRIDE; public: ShallowPresNameVisitor(const Diagram& rDgm) : mrDgm(rDgm), mnCnt(0) {} size_t getCount() const { return mnCnt; } }; void ShapeCreationVisitor::defaultVisit(LayoutAtom& rAtom) { const std::vector& pChildren=rAtom.getChildren(); std::for_each( pChildren.begin(), pChildren.end(), boost::bind( &LayoutAtom::accept, _1, boost::ref(*this)) ); } void ShapeCreationVisitor::visit(ConstraintAtom& /*rAtom*/) { // TODO: eval the constraints } void ShapeCreationVisitor::visit(AlgAtom& rAtom) { defaultVisit(rAtom); } void ShapeCreationVisitor::visit(ForEachAtom& rAtom) { const std::vector& pChildren=rAtom.getChildren(); sal_Int32 nChildren=1; if( rAtom.iterator().mnPtType == XML_node ) { // cound child data nodes - check all child Atoms for "name" // attribute that is contained in diagram's // getPointsPresNameMap() ShallowPresNameVisitor aVisitor(mrDgm); std::for_each( pChildren.begin(), pChildren.end(), boost::bind( &LayoutAtom::accept, _1, boost::ref(aVisitor)) ); nChildren = aVisitor.getCount(); } const sal_Int32 nCnt = std::min( nChildren, rAtom.iterator().mnCnt==-1 ? nChildren : rAtom.iterator().mnCnt); const sal_Int32 nOldIdx=mnCurrIdx; const sal_Int32 nStep=rAtom.iterator().mnStep; for( mnCurrIdx=0; mnCurrIdx0; mnCurrIdx+=nStep ) { // TODO there is likely some conditions std::for_each( pChildren.begin(), pChildren.end(), boost::bind( &LayoutAtom::accept, _1, boost::ref(*this)) ); } // and restore idx mnCurrIdx = nOldIdx; } void ShapeCreationVisitor::visit(ConditionAtom& rAtom) { defaultVisit(rAtom); } void ShapeCreationVisitor::visit(ChooseAtom& rAtom) { defaultVisit(rAtom); } void ShapeCreationVisitor::visit(LayoutNode& rAtom) { ShapePtr pCurrParent(mpParentShape); ShapePtr pCurrShape(rAtom.getShape()); if( pCurrShape ) { OSL_TRACE("ShapeCreationVisitor::visit: processing shape type %d", pCurrShape->getCustomShapeProperties()->getShapePresetType() ); // TODO(F3): cloned shape shares all properties by reference, // don't change them! ShapePtr pClonedShape( new Shape( pCurrShape )); if( rAtom.setupShape(pClonedShape, mrDgm, mnCurrIdx) ) { pCurrParent->addChild(pClonedShape); pCurrParent = pClonedShape; } } else { OSL_TRACE("ShapeCreationVisitor::visit: no shape set while processing layoutnode named %s", OUSTRING_TO_CSTR( rAtom.getName() ) ); } // set new parent for children ShapePtr pPreviousParent(mpParentShape); mpParentShape=pCurrParent; // process children defaultVisit(rAtom); // restore parent mpParentShape=pPreviousParent; // layout shapes - now all child shapes are created ShapeLayoutingVisitor aLayoutingVisitor(pCurrParent, mrDgm, rAtom.getName()); aLayoutingVisitor.defaultVisit(rAtom); } void ShapeLayoutingVisitor::defaultVisit(LayoutAtom& rAtom) { // visit all children, one of them needs to be the layout algorithm const std::vector& pChildren=rAtom.getChildren(); std::for_each( pChildren.begin(), pChildren.end(), boost::bind( &LayoutAtom::accept, _1, boost::ref(*this)) ); } void ShapeLayoutingVisitor::visit(ConstraintAtom& /*rAtom*/) { // stop processing } void ShapeLayoutingVisitor::visit(AlgAtom& rAtom) { rAtom.layoutShape(mpParentShape,mrDgm,maName); } void ShapeLayoutingVisitor::visit(ForEachAtom& /*rAtom*/) { // stop processing } void ShapeLayoutingVisitor::visit(ConditionAtom& rAtom) { defaultVisit(rAtom); } void ShapeLayoutingVisitor::visit(ChooseAtom& rAtom) { defaultVisit(rAtom); } void ShapeLayoutingVisitor::visit(LayoutNode& /*rAtom*/) { // stop processing - only traverse Condition/Choose atoms } void ShallowPresNameVisitor::defaultVisit(LayoutAtom& rAtom) { // visit all children, at least one of them needs to have proper // name set const std::vector& pChildren=rAtom.getChildren(); std::for_each( pChildren.begin(), pChildren.end(), boost::bind( &LayoutAtom::accept, _1, boost::ref(*this)) ); } void ShallowPresNameVisitor::visit(ConstraintAtom& /*rAtom*/) { // stop processing } void ShallowPresNameVisitor::visit(AlgAtom& /*rAtom*/) { // stop processing } void ShallowPresNameVisitor::visit(ForEachAtom& rAtom) { defaultVisit(rAtom); } void ShallowPresNameVisitor::visit(ConditionAtom& rAtom) { defaultVisit(rAtom); } void ShallowPresNameVisitor::visit(ChooseAtom& rAtom) { defaultVisit(rAtom); } void ShallowPresNameVisitor::visit(LayoutNode& rAtom) { DiagramData::PointsNameMap::const_iterator aDataNode= mrDgm.getData()->getPointsPresNameMap().find(rAtom.getName()); if( aDataNode != mrDgm.getData()->getPointsPresNameMap().end() ) mnCnt = std::max(mnCnt, aDataNode->second.size()); } } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */