1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <unordered_set>
24 #include <svx/diagram/datamodel.hxx>
25 #include <comphelper/xmltools.hxx>
26 #include <sal/log.hxx>
29 namespace svx::diagram
{
31 Connection::Connection()
32 : mnXMLType( XML_none
)
39 : msTextBody(std::make_shared
< TextBody
>())
40 , msPointStylePtr(std::make_shared
< PointStyle
>())
43 , mnPreferredChildren(-1)
44 , mnDirection(XML_norm
)
45 , mnResizeHandles(XML_rel
)
47 , mnPercentageNeighbourWidth(-1)
48 , mnPercentageNeighbourHeight(-1)
49 , mnPercentageOwnWidth(-1)
50 , mnPercentageOwnHeight(-1)
51 , mnIncludeAngleScale(-1)
56 , mnHeightOverride(-1)
57 , mnLayoutStyleCount(-1)
58 , mnLayoutStyleIndex(-1)
59 , mbOrgChartEnabled(false)
60 , mbBulletEnabled(false)
61 , mbCoherent3DOffset(false)
62 , mbCustomHorizontalFlip(false)
63 , mbCustomVerticalFlip(false)
65 , mbIsPlaceholder(false)
69 DiagramData::DiagramData()
73 DiagramData::~DiagramData()
77 const Point
* DiagramData::getRootPoint() const
79 for (const auto & aCurrPoint
: maPoints
)
80 if (aCurrPoint
.mnXMLType
== TypeConstant::XML_doc
)
83 SAL_WARN("svx.diagram", "No root point");
87 OUString
DiagramData::getString() const
90 const Point
* pPoint
= getRootPoint();
91 getChildrenString(aBuf
, pPoint
, 0);
92 return aBuf
.makeStringAndClear();
95 bool DiagramData::removeNode(const OUString
& rNodeId
)
97 // check if it doesn't have children
98 for (const auto& aCxn
: maConnections
)
99 if (aCxn
.mnXMLType
== TypeConstant::XML_parOf
&& aCxn
.msSourceId
== rNodeId
)
101 SAL_WARN("svx.diagram", "Node has children - can't be removed");
106 for (const auto& aCxn
: maConnections
)
107 if (aCxn
.mnXMLType
== TypeConstant::XML_parOf
&& aCxn
.msDestId
== rNodeId
)
110 std::unordered_set
<OUString
> aIdsToRemove
;
111 aIdsToRemove
.insert(rNodeId
);
112 if (!aParCxn
.msParTransId
.isEmpty())
113 aIdsToRemove
.insert(aParCxn
.msParTransId
);
114 if (!aParCxn
.msSibTransId
.isEmpty())
115 aIdsToRemove
.insert(aParCxn
.msSibTransId
);
117 for (const Point
& rPoint
: maPoints
)
118 if (aIdsToRemove
.count(rPoint
.msPresentationAssociationId
))
119 aIdsToRemove
.insert(rPoint
.msModelId
);
121 // insert also transition nodes
122 for (const auto& aCxn
: maConnections
)
123 if (aIdsToRemove
.count(aCxn
.msSourceId
) || aIdsToRemove
.count(aCxn
.msDestId
))
124 if (!aCxn
.msPresId
.isEmpty())
125 aIdsToRemove
.insert(aCxn
.msPresId
);
127 // remove connections
128 maConnections
.erase(std::remove_if(maConnections
.begin(), maConnections
.end(),
129 [aIdsToRemove
](const Connection
& rCxn
) {
130 return aIdsToRemove
.count(rCxn
.msSourceId
) || aIdsToRemove
.count(rCxn
.msDestId
);
132 maConnections
.end());
134 // remove data and presentation nodes
135 maPoints
.erase(std::remove_if(maPoints
.begin(), maPoints
.end(),
136 [aIdsToRemove
](const Point
& rPoint
) {
137 return aIdsToRemove
.count(rPoint
.msModelId
);
141 // TODO: fix source/dest order
145 DiagramDataState::DiagramDataState(Connections aConnections
, Points aPoints
)
146 : maConnections(std::move(aConnections
))
147 , maPoints(std::move(aPoints
))
151 DiagramDataStatePtr
DiagramData::extractDiagramDataState() const
153 // Just copy all Connections && Points. The shared_ptr data in
154 // Point-entries is no problem, it just continues exiting shared
155 return std::make_shared
< DiagramDataState
>(maConnections
, maPoints
);
158 void DiagramData::applyDiagramDataState(const DiagramDataStatePtr
& rState
)
162 maConnections
= rState
->getConnections();
163 maPoints
= rState
->getPoints();
165 // Reset temporary buffered ModelData association lists & rebuild them
166 // and the Diagram DataModel. Do that here *immediately* to prevent
167 // re-usage of potentially invalid Connection/Point objects
168 buildDiagramDataModel(true);
172 void DiagramData::getChildrenString(
173 OUStringBuffer
& rBuf
,
174 const svx::diagram::Point
* pPoint
,
175 sal_Int32 nLevel
) const
182 for (sal_Int32 i
= 0; i
< nLevel
-1; i
++)
186 rBuf
.append(pPoint
->msTextBody
->msText
);
190 std::vector
< const svx::diagram::Point
* > aChildren
;
191 for (const auto& rCxn
: maConnections
)
192 if (rCxn
.mnXMLType
== TypeConstant::XML_parOf
&& rCxn
.msSourceId
== pPoint
->msModelId
)
194 if (rCxn
.mnSourceOrder
>= static_cast<sal_Int32
>(aChildren
.size()))
195 aChildren
.resize(rCxn
.mnSourceOrder
+ 1);
196 const auto pChild
= maPointNameMap
.find(rCxn
.msDestId
);
197 if (pChild
!= maPointNameMap
.end())
198 aChildren
[rCxn
.mnSourceOrder
] = pChild
->second
;
201 for (auto pChild
: aChildren
)
202 getChildrenString(rBuf
, pChild
, nLevel
+ 1);
205 std::vector
<std::pair
<OUString
, OUString
>> DiagramData::getChildren(const OUString
& rParentId
) const
207 const OUString sModelId
= rParentId
.isEmpty() ? getRootPoint()->msModelId
: rParentId
;
208 std::vector
<std::pair
<OUString
, OUString
>> aChildren
;
209 for (const auto& rCxn
: maConnections
)
210 if (rCxn
.mnXMLType
== TypeConstant::XML_parOf
&& rCxn
.msSourceId
== sModelId
)
212 if (rCxn
.mnSourceOrder
>= static_cast<sal_Int32
>(aChildren
.size()))
213 aChildren
.resize(rCxn
.mnSourceOrder
+ 1);
214 const auto pChild
= maPointNameMap
.find(rCxn
.msDestId
);
215 if (pChild
!= maPointNameMap
.end())
217 aChildren
[rCxn
.mnSourceOrder
] = std::make_pair(
218 pChild
->second
->msModelId
,
219 pChild
->second
->msTextBody
->msText
);
223 // HACK: empty items shouldn't appear there
224 aChildren
.erase(std::remove_if(aChildren
.begin(), aChildren
.end(),
225 [](const std::pair
<OUString
, OUString
>& aItem
) { return aItem
.first
.isEmpty(); }),
231 OUString
DiagramData::addNode(const OUString
& rText
)
233 const svx::diagram::Point
& rDataRoot
= *getRootPoint();
235 for (const auto& aCxn
: maConnections
)
236 if (aCxn
.mnXMLType
== TypeConstant::XML_presOf
&& aCxn
.msSourceId
== rDataRoot
.msModelId
)
237 sPresRoot
= aCxn
.msDestId
;
239 if (sPresRoot
.isEmpty())
242 OUString sNewNodeId
= OStringToOUString(comphelper::xml::generateGUIDString(), RTL_TEXTENCODING_UTF8
);
244 svx::diagram::Point aDataPoint
;
245 aDataPoint
.mnXMLType
= TypeConstant::XML_node
;
246 aDataPoint
.msModelId
= sNewNodeId
;
247 aDataPoint
.msTextBody
->msText
= rText
;
249 OUString sDataSibling
;
250 for (const auto& aCxn
: maConnections
)
251 if (aCxn
.mnXMLType
== TypeConstant::XML_parOf
&& aCxn
.msSourceId
== rDataRoot
.msModelId
)
252 sDataSibling
= aCxn
.msDestId
;
254 OUString sPresSibling
;
255 for (const auto& aCxn
: maConnections
)
256 if (aCxn
.mnXMLType
== TypeConstant::XML_presOf
&& aCxn
.msSourceId
== sDataSibling
)
257 sPresSibling
= aCxn
.msDestId
;
259 svx::diagram::Point aPresPoint
;
260 aPresPoint
.mnXMLType
= TypeConstant::XML_pres
;
261 aPresPoint
.msModelId
= OStringToOUString(comphelper::xml::generateGUIDString(), RTL_TEXTENCODING_UTF8
);
263 aPresPoint
.msPresentationAssociationId
= aDataPoint
.msModelId
;
264 if (!sPresSibling
.isEmpty())
266 // no idea where to get these values from, so copy from previous sibling
267 const svx::diagram::Point
* pSiblingPoint
= maPointNameMap
[sPresSibling
];
268 aPresPoint
.msPresentationLayoutName
= pSiblingPoint
->msPresentationLayoutName
;
269 aPresPoint
.msPresentationLayoutStyleLabel
= pSiblingPoint
->msPresentationLayoutStyleLabel
;
270 aPresPoint
.mnLayoutStyleIndex
= pSiblingPoint
->mnLayoutStyleIndex
;
271 aPresPoint
.mnLayoutStyleCount
= pSiblingPoint
->mnLayoutStyleCount
;
274 addConnection(svx::diagram::TypeConstant::XML_parOf
, rDataRoot
.msModelId
, aDataPoint
.msModelId
);
275 addConnection(svx::diagram::TypeConstant::XML_presParOf
, sPresRoot
, aPresPoint
.msModelId
);
276 addConnection(svx::diagram::TypeConstant::XML_presOf
, aDataPoint
.msModelId
, aPresPoint
.msModelId
);
278 // adding at the end, so that references are not invalidated in between
279 maPoints
.push_back(aDataPoint
);
280 maPoints
.push_back(aPresPoint
);
285 void DiagramData::addConnection(svx::diagram::TypeConstant nType
, const OUString
& sSourceId
, const OUString
& sDestId
)
287 sal_Int32 nMaxOrd
= -1;
288 for (const auto& aCxn
: maConnections
)
289 if (aCxn
.mnXMLType
== nType
&& aCxn
.msSourceId
== sSourceId
)
290 nMaxOrd
= std::max(nMaxOrd
, aCxn
.mnSourceOrder
);
292 svx::diagram::Connection
& rCxn
= maConnections
.emplace_back();
293 rCxn
.mnXMLType
= nType
;
294 rCxn
.msSourceId
= sSourceId
;
295 rCxn
.msDestId
= sDestId
;
296 rCxn
.mnSourceOrder
= nMaxOrd
+ 1;
299 // #define DEBUG_OOX_DIAGRAM
300 #ifdef DEBUG_OOX_DIAGRAM
301 OString
normalizeDotName( const OUString
& rStr
)
306 const sal_Int32
nLen(rStr
.getLength());
307 sal_Int32
nCurrIndex(0);
308 while( nCurrIndex
< nLen
)
310 const sal_Int32 aChar
=rStr
.iterateCodePoints(&nCurrIndex
);
311 if( aChar
!= '-' && aChar
!= '{' && aChar
!= '}' )
312 aBuf
.append((sal_Unicode
)aChar
);
315 return OUStringToOString(aBuf
.makeStringAndClear(),
316 RTL_TEXTENCODING_UTF8
);
320 static sal_Int32
calcDepth( std::u16string_view rNodeName
,
321 const svx::diagram::Connections
& rCnx
)
323 // find length of longest path in 'isChild' graph, ending with rNodeName
324 for (auto const& elem
: rCnx
)
326 if( !elem
.msParTransId
.isEmpty() &&
327 !elem
.msSibTransId
.isEmpty() &&
328 !elem
.msSourceId
.isEmpty() &&
329 !elem
.msDestId
.isEmpty() &&
330 elem
.mnXMLType
== TypeConstant::XML_parOf
&&
331 rNodeName
== elem
.msDestId
)
333 return calcDepth(elem
.msSourceId
, rCnx
) + 1;
340 void DiagramData::buildDiagramDataModel(bool /*bClearOoxShapes*/)
342 // build name-object maps
343 maPointNameMap
.clear();
344 maPointsPresNameMap
.clear();
345 maConnectionNameMap
.clear();
346 maPresOfNameMap
.clear();
347 msBackgroundShapeModelID
.clear();
349 #ifdef DEBUG_OOX_DIAGRAM
350 std::ofstream
output("tree.dot");
352 output
<< "digraph datatree {" << std::endl
;
354 svx::diagram::Points
& rPoints
= getPoints();
355 for (auto & point
: rPoints
)
357 #ifdef DEBUG_OOX_DIAGRAM
359 << normalizeDotName(point
.msModelId
).getStr()
362 if( !point
.msPresentationLayoutName
.isEmpty() )
364 << OUStringToOString(
365 point
.msPresentationLayoutName
,
366 RTL_TEXTENCODING_UTF8
).getStr() << "\", ";
369 << OUStringToOString(
371 RTL_TEXTENCODING_UTF8
).getStr() << "\", ";
373 switch( point
.mnXMLType
)
375 case TypeConstant::XML_doc
: output
<< "style=filled, color=red"; break;
376 case TypeConstant::XML_asst
: output
<< "style=filled, color=green"; break;
378 case TypeConstant::XML_node
: output
<< "style=filled, color=blue"; break;
379 case TypeConstant::XML_pres
: output
<< "style=filled, color=yellow"; break;
380 case TypeConstant::XML_parTrans
: output
<< "color=grey"; break;
381 case TypeConstant::XML_sibTrans
: output
<< " "; break;
384 output
<< "];" << std::endl
;
387 // does currpoint have any text set?
388 if(!point
.msTextBody
->msText
.isEmpty())
390 #ifdef DEBUG_OOX_DIAGRAM
391 static sal_Int32 nCount
=0;
393 << "textNode" << nCount
396 << OUStringToOString(
397 point
.msTextBody
->msText
,
398 RTL_TEXTENCODING_UTF8
).getStr()
399 << "\"" << "];" << std::endl
;
401 << normalizeDotName(point
.msModelId
).getStr()
403 << "textNode" << nCount
++
408 const bool bInserted1
= getPointNameMap().insert(
409 std::make_pair(point
.msModelId
,&point
)).second
;
411 SAL_WARN_IF(!bInserted1
, "oox.drawingml", "DiagramData::build(): non-unique point model id");
413 if( !point
.msPresentationLayoutName
.isEmpty() )
415 DiagramData::PointsNameMap::value_type::second_type
& rVec
=
416 getPointsPresNameMap()[point
.msPresentationLayoutName
];
417 rVec
.push_back(&point
);
421 const svx::diagram::Connections
& rConnections
= getConnections();
422 for (auto const& connection
: rConnections
)
424 #ifdef DEBUG_OOX_DIAGRAM
425 if( !connection
.msParTransId
.isEmpty() ||
426 !connection
.msSibTransId
.isEmpty() )
428 if( !connection
.msSourceId
.isEmpty() ||
429 !connection
.msDestId
.isEmpty() )
432 << normalizeDotName(connection
.msSourceId
).getStr()
434 << normalizeDotName(connection
.msParTransId
).getStr()
436 << normalizeDotName(connection
.msSibTransId
).getStr()
438 << normalizeDotName(connection
.msDestId
).getStr()
440 << ((connection
.mnXMLType
== TypeConstant::XML_presOf
) ? " color=red, " : ((connection
.mnXMLType
== TypeConstant::XML_presParOf
) ? " color=green, " : " "))
442 << OUStringToOString(connection
.msModelId
,
443 RTL_TEXTENCODING_UTF8
).getStr()
444 << "\"];" << std::endl
;
449 << normalizeDotName(connection
.msParTransId
).getStr()
451 << normalizeDotName(connection
.msSibTransId
).getStr()
453 << ((connection
.mnXMLType
== TypeConstant::XML_presOf
) ? " color=red, " : ((connection
.mnXMLType
== TypeConstant::XML_presParOf
) ? " color=green, " : " "))
455 << OUStringToOString(connection
.msModelId
,
456 RTL_TEXTENCODING_UTF8
).getStr()
457 << "\"];" << std::endl
;
460 else if( !connection
.msSourceId
.isEmpty() ||
461 !connection
.msDestId
.isEmpty() )
463 << normalizeDotName(connection
.msSourceId
).getStr()
465 << normalizeDotName(connection
.msDestId
).getStr()
467 << OUStringToOString(connection
.msModelId
,
468 RTL_TEXTENCODING_UTF8
).getStr()
469 << ((connection
.mnXMLType
== TypeConstant::XML_presOf
) ? "\", color=red]" : ((connection
.mnXMLType
== TypeConstant::XML_presParOf
) ? "\", color=green]" : "\"]"))
473 const bool bInserted1
= maConnectionNameMap
.insert(
474 std::make_pair(connection
.msModelId
,&connection
)).second
;
476 SAL_WARN_IF(!bInserted1
, "oox.drawingml", "DiagramData::build(): non-unique connection model id");
478 if( connection
.mnXMLType
== TypeConstant::XML_presOf
)
480 DiagramData::StringMap::value_type::second_type
& rVec
= getPresOfNameMap()[connection
.msDestId
];
481 rVec
[connection
.mnDestOrder
] = { connection
.msSourceId
, sal_Int32(0) };
485 // assign outline levels
486 DiagramData::StringMap
& rStringMap
= getPresOfNameMap();
487 for (auto & elemPresOf
: rStringMap
)
489 for (auto & elem
: elemPresOf
.second
)
491 const sal_Int32 nDepth
= calcDepth(elem
.second
.msSourceId
, getConnections());
492 elem
.second
.mnDepth
= nDepth
!= 0 ? nDepth
: -1;
495 #ifdef DEBUG_OOX_DIAGRAM
496 output
<< "}" << std::endl
;
502 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */