summaryrefslogtreecommitdiffstats
path: root/svx/source/diagram/datamodel.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'svx/source/diagram/datamodel.cxx')
-rw-r--r--svx/source/diagram/datamodel.cxx498
1 files changed, 498 insertions, 0 deletions
diff --git a/svx/source/diagram/datamodel.cxx b/svx/source/diagram/datamodel.cxx
new file mode 100644
index 0000000000..0cf0541cd2
--- /dev/null
+++ b/svx/source/diagram/datamodel.cxx
@@ -0,0 +1,498 @@
+/* -*- 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 <unordered_set>
+#include <algorithm>
+#include <fstream>
+
+#include <svx/diagram/datamodel.hxx>
+#include <comphelper/xmltools.hxx>
+#include <sal/log.hxx>
+#include <utility>
+
+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<OUString> 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<sal_Int32>(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<std::pair<OUString, OUString>> DiagramData::getChildren(const OUString& rParentId) const
+{
+ const OUString sModelId = rParentId.isEmpty() ? getRootPoint()->msModelId : rParentId;
+ std::vector<std::pair<OUString, OUString>> aChildren;
+ for (const auto& rCxn : maConnections)
+ if (rCxn.mnXMLType == TypeConstant::XML_parOf && rCxn.msSourceId == sModelId)
+ {
+ if (rCxn.mnSourceOrder >= static_cast<sal_Int32>(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<OUString, OUString>& 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: */