/* -*- 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 #include #include #include #include #include #include namespace svx::diagram { Connection::Connection() : mnXMLType( XML_none ) , mnSourceOrder( 0 ) , mnDestOrder( 0 ) { } Point::Point() : msTextBody(std::make_shared< TextBody >()) , msPointStylePtr(std::make_shared< PointStyle >()) , mnXMLType(XML_none) , mnMaxChildren(-1) , mnPreferredChildren(-1) , mnDirection(XML_norm) , mnResizeHandles(XML_rel) , mnCustomAngle(-1) , mnPercentageNeighbourWidth(-1) , mnPercentageNeighbourHeight(-1) , mnPercentageOwnWidth(-1) , mnPercentageOwnHeight(-1) , mnIncludeAngleScale(-1) , mnRadiusScale(-1) , mnWidthScale(-1) , mnHeightScale(-1) , mnWidthOverride(-1) , mnHeightOverride(-1) , mnLayoutStyleCount(-1) , mnLayoutStyleIndex(-1) , mbOrgChartEnabled(false) , mbBulletEnabled(false) , mbCoherent3DOffset(false) , mbCustomHorizontalFlip(false) , mbCustomVerticalFlip(false) , mbCustomText(false) , mbIsPlaceholder(false) { } DiagramData::DiagramData() { } DiagramData::~DiagramData() { } const Point* DiagramData::getRootPoint() const { for (const auto & aCurrPoint : maPoints) if (aCurrPoint.mnXMLType == TypeConstant::XML_doc) return &aCurrPoint; SAL_WARN("svx.diagram", "No root point"); return nullptr; } OUString DiagramData::getString() const { OUStringBuffer aBuf; const Point* pPoint = getRootPoint(); getChildrenString(aBuf, pPoint, 0); return aBuf.makeStringAndClear(); } bool DiagramData::removeNode(const OUString& rNodeId) { // check if it doesn't have children for (const auto& aCxn : maConnections) if (aCxn.mnXMLType == TypeConstant::XML_parOf && aCxn.msSourceId == rNodeId) { SAL_WARN("svx.diagram", "Node has children - can't be removed"); return false; } Connection aParCxn; for (const auto& aCxn : maConnections) if (aCxn.mnXMLType == TypeConstant::XML_parOf && aCxn.msDestId == rNodeId) aParCxn = aCxn; std::unordered_set aIdsToRemove; aIdsToRemove.insert(rNodeId); if (!aParCxn.msParTransId.isEmpty()) aIdsToRemove.insert(aParCxn.msParTransId); if (!aParCxn.msSibTransId.isEmpty()) aIdsToRemove.insert(aParCxn.msSibTransId); for (const Point& rPoint : maPoints) if (aIdsToRemove.count(rPoint.msPresentationAssociationId)) aIdsToRemove.insert(rPoint.msModelId); // insert also transition nodes for (const auto& aCxn : maConnections) if (aIdsToRemove.count(aCxn.msSourceId) || aIdsToRemove.count(aCxn.msDestId)) if (!aCxn.msPresId.isEmpty()) aIdsToRemove.insert(aCxn.msPresId); // remove connections std::erase_if(maConnections, [&aIdsToRemove](const Connection& rCxn) { return aIdsToRemove.count(rCxn.msSourceId) || aIdsToRemove.count(rCxn.msDestId); }); // remove data and presentation nodes std::erase_if(maPoints, [&aIdsToRemove](const Point& rPoint) { return aIdsToRemove.count(rPoint.msModelId); }); // TODO: fix source/dest order return true; } DiagramDataState::DiagramDataState(Connections aConnections, Points aPoints) : maConnections(std::move(aConnections)) , maPoints(std::move(aPoints)) { } DiagramDataStatePtr DiagramData::extractDiagramDataState() const { // Just copy all Connections && Points. The shared_ptr data in // Point-entries is no problem, it just continues exiting shared return std::make_shared< DiagramDataState >(maConnections, maPoints); } void DiagramData::applyDiagramDataState(const DiagramDataStatePtr& rState) { if(rState) { maConnections = rState->getConnections(); maPoints = rState->getPoints(); // Reset temporary buffered ModelData association lists & rebuild them // and the Diagram DataModel. Do that here *immediately* to prevent // re-usage of potentially invalid Connection/Point objects buildDiagramDataModel(true); } } void DiagramData::getChildrenString( OUStringBuffer& rBuf, const svx::diagram::Point* pPoint, sal_Int32 nLevel) const { if (!pPoint) return; if (nLevel > 0) { for (sal_Int32 i = 0; i < nLevel-1; i++) rBuf.append('\t'); rBuf.append('+'); rBuf.append(' '); rBuf.append(pPoint->msTextBody->msText); rBuf.append('\n'); } std::vector< const svx::diagram::Point* > aChildren; for (const auto& rCxn : maConnections) if (rCxn.mnXMLType == TypeConstant::XML_parOf && rCxn.msSourceId == pPoint->msModelId) { if (rCxn.mnSourceOrder >= static_cast(aChildren.size())) aChildren.resize(rCxn.mnSourceOrder + 1); const auto pChild = maPointNameMap.find(rCxn.msDestId); if (pChild != maPointNameMap.end()) aChildren[rCxn.mnSourceOrder] = pChild->second; } for (auto pChild : aChildren) getChildrenString(rBuf, pChild, nLevel + 1); } std::vector> DiagramData::getChildren(const OUString& rParentId) const { const OUString sModelId = rParentId.isEmpty() ? getRootPoint()->msModelId : rParentId; std::vector> aChildren; for (const auto& rCxn : maConnections) if (rCxn.mnXMLType == TypeConstant::XML_parOf && rCxn.msSourceId == sModelId) { if (rCxn.mnSourceOrder >= static_cast(aChildren.size())) aChildren.resize(rCxn.mnSourceOrder + 1); const auto pChild = maPointNameMap.find(rCxn.msDestId); if (pChild != maPointNameMap.end()) { aChildren[rCxn.mnSourceOrder] = std::make_pair( pChild->second->msModelId, pChild->second->msTextBody->msText); } } // HACK: empty items shouldn't appear there std::erase_if(aChildren, [](const std::pair& aItem) { return aItem.first.isEmpty(); }); return aChildren; } OUString DiagramData::addNode(const OUString& rText) { const svx::diagram::Point& rDataRoot = *getRootPoint(); OUString sPresRoot; for (const auto& aCxn : maConnections) if (aCxn.mnXMLType == TypeConstant::XML_presOf && aCxn.msSourceId == rDataRoot.msModelId) sPresRoot = aCxn.msDestId; if (sPresRoot.isEmpty()) return OUString(); OUString sNewNodeId = OStringToOUString(comphelper::xml::generateGUIDString(), RTL_TEXTENCODING_UTF8); svx::diagram::Point aDataPoint; aDataPoint.mnXMLType = TypeConstant::XML_node; aDataPoint.msModelId = sNewNodeId; aDataPoint.msTextBody->msText = rText; OUString sDataSibling; for (const auto& aCxn : maConnections) if (aCxn.mnXMLType == TypeConstant::XML_parOf && aCxn.msSourceId == rDataRoot.msModelId) sDataSibling = aCxn.msDestId; OUString sPresSibling; for (const auto& aCxn : maConnections) if (aCxn.mnXMLType == TypeConstant::XML_presOf && aCxn.msSourceId == sDataSibling) sPresSibling = aCxn.msDestId; svx::diagram::Point aPresPoint; aPresPoint.mnXMLType = TypeConstant::XML_pres; aPresPoint.msModelId = OStringToOUString(comphelper::xml::generateGUIDString(), RTL_TEXTENCODING_UTF8); aPresPoint.msPresentationAssociationId = aDataPoint.msModelId; if (!sPresSibling.isEmpty()) { // no idea where to get these values from, so copy from previous sibling const svx::diagram::Point* pSiblingPoint = maPointNameMap[sPresSibling]; aPresPoint.msPresentationLayoutName = pSiblingPoint->msPresentationLayoutName; aPresPoint.msPresentationLayoutStyleLabel = pSiblingPoint->msPresentationLayoutStyleLabel; aPresPoint.mnLayoutStyleIndex = pSiblingPoint->mnLayoutStyleIndex; aPresPoint.mnLayoutStyleCount = pSiblingPoint->mnLayoutStyleCount; } addConnection(svx::diagram::TypeConstant::XML_parOf, rDataRoot.msModelId, aDataPoint.msModelId); addConnection(svx::diagram::TypeConstant::XML_presParOf, sPresRoot, aPresPoint.msModelId); addConnection(svx::diagram::TypeConstant::XML_presOf, aDataPoint.msModelId, aPresPoint.msModelId); // adding at the end, so that references are not invalidated in between maPoints.push_back(aDataPoint); maPoints.push_back(aPresPoint); return sNewNodeId; } void DiagramData::addConnection(svx::diagram::TypeConstant nType, const OUString& sSourceId, const OUString& sDestId) { sal_Int32 nMaxOrd = -1; for (const auto& aCxn : maConnections) if (aCxn.mnXMLType == nType && aCxn.msSourceId == sSourceId) nMaxOrd = std::max(nMaxOrd, aCxn.mnSourceOrder); svx::diagram::Connection& rCxn = maConnections.emplace_back(); rCxn.mnXMLType = nType; rCxn.msSourceId = sSourceId; rCxn.msDestId = sDestId; rCxn.mnSourceOrder = nMaxOrd + 1; } // #define DEBUG_OOX_DIAGRAM #ifdef DEBUG_OOX_DIAGRAM OString normalizeDotName( const OUString& rStr ) { OUStringBuffer aBuf; aBuf.append('N'); const sal_Int32 nLen(rStr.getLength()); sal_Int32 nCurrIndex(0); while( nCurrIndex < nLen ) { const sal_Int32 aChar=rStr.iterateCodePoints(&nCurrIndex); if( aChar != '-' && aChar != '{' && aChar != '}' ) aBuf.append((sal_Unicode)aChar); } return OUStringToOString(aBuf.makeStringAndClear(), RTL_TEXTENCODING_UTF8); } #endif static sal_Int32 calcDepth( std::u16string_view rNodeName, const svx::diagram::Connections& rCnx ) { // find length of longest path in 'isChild' graph, ending with rNodeName for (auto const& elem : rCnx) { if( !elem.msParTransId.isEmpty() && !elem.msSibTransId.isEmpty() && !elem.msSourceId.isEmpty() && !elem.msDestId.isEmpty() && elem.mnXMLType == TypeConstant::XML_parOf && rNodeName == elem.msDestId ) { return calcDepth(elem.msSourceId, rCnx) + 1; } } return 0; } void DiagramData::buildDiagramDataModel(bool /*bClearOoxShapes*/) { // build name-object maps maPointNameMap.clear(); maPointsPresNameMap.clear(); maConnectionNameMap.clear(); maPresOfNameMap.clear(); msBackgroundShapeModelID.clear(); #ifdef DEBUG_OOX_DIAGRAM std::ofstream output("tree.dot"); output << "digraph datatree {" << std::endl; #endif svx::diagram::Points& rPoints = getPoints(); for (auto & point : rPoints) { #ifdef DEBUG_OOX_DIAGRAM output << "\t" << normalizeDotName(point.msModelId).getStr() << "["; if( !point.msPresentationLayoutName.isEmpty() ) output << "label=\"" << OUStringToOString( point.msPresentationLayoutName, RTL_TEXTENCODING_UTF8).getStr() << "\", "; else output << "label=\"" << OUStringToOString( point.msModelId, RTL_TEXTENCODING_UTF8).getStr() << "\", "; switch( point.mnXMLType ) { case TypeConstant::XML_doc: output << "style=filled, color=red"; break; case TypeConstant::XML_asst: output << "style=filled, color=green"; break; default: case TypeConstant::XML_node: output << "style=filled, color=blue"; break; case TypeConstant::XML_pres: output << "style=filled, color=yellow"; break; case TypeConstant::XML_parTrans: output << "color=grey"; break; case TypeConstant::XML_sibTrans: output << " "; break; } output << "];" << std::endl; #endif // does currpoint have any text set? if(!point.msTextBody->msText.isEmpty()) { #ifdef DEBUG_OOX_DIAGRAM static sal_Int32 nCount=0; output << "\t" << "textNode" << nCount << " [" << "label=\"" << OUStringToOString( point.msTextBody->msText, RTL_TEXTENCODING_UTF8).getStr() << "\"" << "];" << std::endl; output << "\t" << normalizeDotName(point.msModelId).getStr() << " -> " << "textNode" << nCount++ << ";" << std::endl; #endif } const bool bInserted1 = getPointNameMap().insert( std::make_pair(point.msModelId,&point)).second; SAL_WARN_IF(!bInserted1, "oox.drawingml", "DiagramData::build(): non-unique point model id"); if( !point.msPresentationLayoutName.isEmpty() ) { DiagramData::PointsNameMap::value_type::second_type& rVec= getPointsPresNameMap()[point.msPresentationLayoutName]; rVec.push_back(&point); } } const svx::diagram::Connections& rConnections = getConnections(); for (auto const& connection : rConnections) { #ifdef DEBUG_OOX_DIAGRAM if( !connection.msParTransId.isEmpty() || !connection.msSibTransId.isEmpty() ) { if( !connection.msSourceId.isEmpty() || !connection.msDestId.isEmpty() ) { output << "\t" << normalizeDotName(connection.msSourceId).getStr() << " -> " << normalizeDotName(connection.msParTransId).getStr() << " -> " << normalizeDotName(connection.msSibTransId).getStr() << " -> " << normalizeDotName(connection.msDestId).getStr() << " [style=dotted," << ((connection.mnXMLType == TypeConstant::XML_presOf) ? " color=red, " : ((connection.mnXMLType == TypeConstant::XML_presParOf) ? " color=green, " : " ")) << "label=\"" << OUStringToOString(connection.msModelId, RTL_TEXTENCODING_UTF8 ).getStr() << "\"];" << std::endl; } else { output << "\t" << normalizeDotName(connection.msParTransId).getStr() << " -> " << normalizeDotName(connection.msSibTransId).getStr() << " [" << ((connection.mnXMLType == TypeConstant::XML_presOf) ? " color=red, " : ((connection.mnXMLType == TypeConstant::XML_presParOf) ? " color=green, " : " ")) << "label=\"" << OUStringToOString(connection.msModelId, RTL_TEXTENCODING_UTF8 ).getStr() << "\"];" << std::endl; } } else if( !connection.msSourceId.isEmpty() || !connection.msDestId.isEmpty() ) output << "\t" << normalizeDotName(connection.msSourceId).getStr() << " -> " << normalizeDotName(connection.msDestId).getStr() << " [label=\"" << OUStringToOString(connection.msModelId, RTL_TEXTENCODING_UTF8 ).getStr() << ((connection.mnXMLType == TypeConstant::XML_presOf) ? "\", color=red]" : ((connection.mnXMLType == TypeConstant::XML_presParOf) ? "\", color=green]" : "\"]")) << ";" << std::endl; #endif const bool bInserted1 = maConnectionNameMap.insert( std::make_pair(connection.msModelId,&connection)).second; SAL_WARN_IF(!bInserted1, "oox.drawingml", "DiagramData::build(): non-unique connection model id"); if( connection.mnXMLType == TypeConstant::XML_presOf ) { DiagramData::StringMap::value_type::second_type& rVec = getPresOfNameMap()[connection.msDestId]; rVec[connection.mnDestOrder] = { connection.msSourceId, sal_Int32(0) }; } } // assign outline levels DiagramData::StringMap& rStringMap = getPresOfNameMap(); for (auto & elemPresOf : rStringMap) { for (auto & elem : elemPresOf.second) { const sal_Int32 nDepth = calcDepth(elem.second.msSourceId, getConnections()); elem.second.mnDepth = nDepth != 0 ? nDepth : -1; } } #ifdef DEBUG_OOX_DIAGRAM output << "}" << std::endl; #endif } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */