498 lines
17 KiB
C++
498 lines
17 KiB
C++
/* -*- 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: */
|