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 std::erase_if(maConnections
,
129 [&aIdsToRemove
](const Connection
& rCxn
) {
130 return aIdsToRemove
.count(rCxn
.msSourceId
) || aIdsToRemove
.count(rCxn
.msDestId
);
133 // remove data and presentation nodes
134 std::erase_if(maPoints
,
135 [&aIdsToRemove
](const Point
& rPoint
) {
136 return aIdsToRemove
.count(rPoint
.msModelId
);
139 // TODO: fix source/dest order
143 DiagramDataState::DiagramDataState(Connections aConnections
, Points aPoints
)
144 : maConnections(std::move(aConnections
))
145 , maPoints(std::move(aPoints
))
149 DiagramDataStatePtr
DiagramData::extractDiagramDataState() const
151 // Just copy all Connections && Points. The shared_ptr data in
152 // Point-entries is no problem, it just continues exiting shared
153 return std::make_shared
< DiagramDataState
>(maConnections
, maPoints
);
156 void DiagramData::applyDiagramDataState(const DiagramDataStatePtr
& rState
)
160 maConnections
= rState
->getConnections();
161 maPoints
= rState
->getPoints();
163 // Reset temporary buffered ModelData association lists & rebuild them
164 // and the Diagram DataModel. Do that here *immediately* to prevent
165 // re-usage of potentially invalid Connection/Point objects
166 buildDiagramDataModel(true);
170 void DiagramData::getChildrenString(
171 OUStringBuffer
& rBuf
,
172 const svx::diagram::Point
* pPoint
,
173 sal_Int32 nLevel
) const
180 for (sal_Int32 i
= 0; i
< nLevel
-1; i
++)
184 rBuf
.append(pPoint
->msTextBody
->msText
);
188 std::vector
< const svx::diagram::Point
* > aChildren
;
189 for (const auto& rCxn
: maConnections
)
190 if (rCxn
.mnXMLType
== TypeConstant::XML_parOf
&& rCxn
.msSourceId
== pPoint
->msModelId
)
192 if (rCxn
.mnSourceOrder
>= static_cast<sal_Int32
>(aChildren
.size()))
193 aChildren
.resize(rCxn
.mnSourceOrder
+ 1);
194 const auto pChild
= maPointNameMap
.find(rCxn
.msDestId
);
195 if (pChild
!= maPointNameMap
.end())
196 aChildren
[rCxn
.mnSourceOrder
] = pChild
->second
;
199 for (auto pChild
: aChildren
)
200 getChildrenString(rBuf
, pChild
, nLevel
+ 1);
203 std::vector
<std::pair
<OUString
, OUString
>> DiagramData::getChildren(const OUString
& rParentId
) const
205 const OUString sModelId
= rParentId
.isEmpty() ? getRootPoint()->msModelId
: rParentId
;
206 std::vector
<std::pair
<OUString
, OUString
>> aChildren
;
207 for (const auto& rCxn
: maConnections
)
208 if (rCxn
.mnXMLType
== TypeConstant::XML_parOf
&& rCxn
.msSourceId
== sModelId
)
210 if (rCxn
.mnSourceOrder
>= static_cast<sal_Int32
>(aChildren
.size()))
211 aChildren
.resize(rCxn
.mnSourceOrder
+ 1);
212 const auto pChild
= maPointNameMap
.find(rCxn
.msDestId
);
213 if (pChild
!= maPointNameMap
.end())
215 aChildren
[rCxn
.mnSourceOrder
] = std::make_pair(
216 pChild
->second
->msModelId
,
217 pChild
->second
->msTextBody
->msText
);
221 // HACK: empty items shouldn't appear there
222 std::erase_if(aChildren
, [](const std::pair
<OUString
, OUString
>& aItem
) { return aItem
.first
.isEmpty(); });
227 OUString
DiagramData::addNode(const OUString
& rText
)
229 const svx::diagram::Point
& rDataRoot
= *getRootPoint();
231 for (const auto& aCxn
: maConnections
)
232 if (aCxn
.mnXMLType
== TypeConstant::XML_presOf
&& aCxn
.msSourceId
== rDataRoot
.msModelId
)
233 sPresRoot
= aCxn
.msDestId
;
235 if (sPresRoot
.isEmpty())
238 OUString sNewNodeId
= OStringToOUString(comphelper::xml::generateGUIDString(), RTL_TEXTENCODING_UTF8
);
240 svx::diagram::Point aDataPoint
;
241 aDataPoint
.mnXMLType
= TypeConstant::XML_node
;
242 aDataPoint
.msModelId
= sNewNodeId
;
243 aDataPoint
.msTextBody
->msText
= rText
;
245 OUString sDataSibling
;
246 for (const auto& aCxn
: maConnections
)
247 if (aCxn
.mnXMLType
== TypeConstant::XML_parOf
&& aCxn
.msSourceId
== rDataRoot
.msModelId
)
248 sDataSibling
= aCxn
.msDestId
;
250 OUString sPresSibling
;
251 for (const auto& aCxn
: maConnections
)
252 if (aCxn
.mnXMLType
== TypeConstant::XML_presOf
&& aCxn
.msSourceId
== sDataSibling
)
253 sPresSibling
= aCxn
.msDestId
;
255 svx::diagram::Point aPresPoint
;
256 aPresPoint
.mnXMLType
= TypeConstant::XML_pres
;
257 aPresPoint
.msModelId
= OStringToOUString(comphelper::xml::generateGUIDString(), RTL_TEXTENCODING_UTF8
);
259 aPresPoint
.msPresentationAssociationId
= aDataPoint
.msModelId
;
260 if (!sPresSibling
.isEmpty())
262 // no idea where to get these values from, so copy from previous sibling
263 const svx::diagram::Point
* pSiblingPoint
= maPointNameMap
[sPresSibling
];
264 aPresPoint
.msPresentationLayoutName
= pSiblingPoint
->msPresentationLayoutName
;
265 aPresPoint
.msPresentationLayoutStyleLabel
= pSiblingPoint
->msPresentationLayoutStyleLabel
;
266 aPresPoint
.mnLayoutStyleIndex
= pSiblingPoint
->mnLayoutStyleIndex
;
267 aPresPoint
.mnLayoutStyleCount
= pSiblingPoint
->mnLayoutStyleCount
;
270 addConnection(svx::diagram::TypeConstant::XML_parOf
, rDataRoot
.msModelId
, aDataPoint
.msModelId
);
271 addConnection(svx::diagram::TypeConstant::XML_presParOf
, sPresRoot
, aPresPoint
.msModelId
);
272 addConnection(svx::diagram::TypeConstant::XML_presOf
, aDataPoint
.msModelId
, aPresPoint
.msModelId
);
274 // adding at the end, so that references are not invalidated in between
275 maPoints
.push_back(aDataPoint
);
276 maPoints
.push_back(aPresPoint
);
281 void DiagramData::addConnection(svx::diagram::TypeConstant nType
, const OUString
& sSourceId
, const OUString
& sDestId
)
283 sal_Int32 nMaxOrd
= -1;
284 for (const auto& aCxn
: maConnections
)
285 if (aCxn
.mnXMLType
== nType
&& aCxn
.msSourceId
== sSourceId
)
286 nMaxOrd
= std::max(nMaxOrd
, aCxn
.mnSourceOrder
);
288 svx::diagram::Connection
& rCxn
= maConnections
.emplace_back();
289 rCxn
.mnXMLType
= nType
;
290 rCxn
.msSourceId
= sSourceId
;
291 rCxn
.msDestId
= sDestId
;
292 rCxn
.mnSourceOrder
= nMaxOrd
+ 1;
295 // #define DEBUG_OOX_DIAGRAM
296 #ifdef DEBUG_OOX_DIAGRAM
297 OString
normalizeDotName( const OUString
& rStr
)
302 const sal_Int32
nLen(rStr
.getLength());
303 sal_Int32
nCurrIndex(0);
304 while( nCurrIndex
< nLen
)
306 const sal_Int32 aChar
=rStr
.iterateCodePoints(&nCurrIndex
);
307 if( aChar
!= '-' && aChar
!= '{' && aChar
!= '}' )
308 aBuf
.append((sal_Unicode
)aChar
);
311 return OUStringToOString(aBuf
.makeStringAndClear(),
312 RTL_TEXTENCODING_UTF8
);
316 static sal_Int32
calcDepth( std::u16string_view rNodeName
,
317 const svx::diagram::Connections
& rCnx
)
319 // find length of longest path in 'isChild' graph, ending with rNodeName
320 for (auto const& elem
: rCnx
)
322 if( !elem
.msParTransId
.isEmpty() &&
323 !elem
.msSibTransId
.isEmpty() &&
324 !elem
.msSourceId
.isEmpty() &&
325 !elem
.msDestId
.isEmpty() &&
326 elem
.mnXMLType
== TypeConstant::XML_parOf
&&
327 rNodeName
== elem
.msDestId
)
329 return calcDepth(elem
.msSourceId
, rCnx
) + 1;
336 void DiagramData::buildDiagramDataModel(bool /*bClearOoxShapes*/)
338 // build name-object maps
339 maPointNameMap
.clear();
340 maPointsPresNameMap
.clear();
341 maConnectionNameMap
.clear();
342 maPresOfNameMap
.clear();
343 msBackgroundShapeModelID
.clear();
345 #ifdef DEBUG_OOX_DIAGRAM
346 std::ofstream
output("tree.dot");
348 output
<< "digraph datatree {" << std::endl
;
350 svx::diagram::Points
& rPoints
= getPoints();
351 for (auto & point
: rPoints
)
353 #ifdef DEBUG_OOX_DIAGRAM
355 << normalizeDotName(point
.msModelId
).getStr()
358 if( !point
.msPresentationLayoutName
.isEmpty() )
360 << OUStringToOString(
361 point
.msPresentationLayoutName
,
362 RTL_TEXTENCODING_UTF8
).getStr() << "\", ";
365 << OUStringToOString(
367 RTL_TEXTENCODING_UTF8
).getStr() << "\", ";
369 switch( point
.mnXMLType
)
371 case TypeConstant::XML_doc
: output
<< "style=filled, color=red"; break;
372 case TypeConstant::XML_asst
: output
<< "style=filled, color=green"; break;
374 case TypeConstant::XML_node
: output
<< "style=filled, color=blue"; break;
375 case TypeConstant::XML_pres
: output
<< "style=filled, color=yellow"; break;
376 case TypeConstant::XML_parTrans
: output
<< "color=grey"; break;
377 case TypeConstant::XML_sibTrans
: output
<< " "; break;
380 output
<< "];" << std::endl
;
383 // does currpoint have any text set?
384 if(!point
.msTextBody
->msText
.isEmpty())
386 #ifdef DEBUG_OOX_DIAGRAM
387 static sal_Int32 nCount
=0;
389 << "textNode" << nCount
392 << OUStringToOString(
393 point
.msTextBody
->msText
,
394 RTL_TEXTENCODING_UTF8
).getStr()
395 << "\"" << "];" << std::endl
;
397 << normalizeDotName(point
.msModelId
).getStr()
399 << "textNode" << nCount
++
404 const bool bInserted1
= getPointNameMap().insert(
405 std::make_pair(point
.msModelId
,&point
)).second
;
407 SAL_WARN_IF(!bInserted1
, "oox.drawingml", "DiagramData::build(): non-unique point model id");
409 if( !point
.msPresentationLayoutName
.isEmpty() )
411 DiagramData::PointsNameMap::value_type::second_type
& rVec
=
412 getPointsPresNameMap()[point
.msPresentationLayoutName
];
413 rVec
.push_back(&point
);
417 const svx::diagram::Connections
& rConnections
= getConnections();
418 for (auto const& connection
: rConnections
)
420 #ifdef DEBUG_OOX_DIAGRAM
421 if( !connection
.msParTransId
.isEmpty() ||
422 !connection
.msSibTransId
.isEmpty() )
424 if( !connection
.msSourceId
.isEmpty() ||
425 !connection
.msDestId
.isEmpty() )
428 << normalizeDotName(connection
.msSourceId
).getStr()
430 << normalizeDotName(connection
.msParTransId
).getStr()
432 << normalizeDotName(connection
.msSibTransId
).getStr()
434 << normalizeDotName(connection
.msDestId
).getStr()
436 << ((connection
.mnXMLType
== TypeConstant::XML_presOf
) ? " color=red, " : ((connection
.mnXMLType
== TypeConstant::XML_presParOf
) ? " color=green, " : " "))
438 << OUStringToOString(connection
.msModelId
,
439 RTL_TEXTENCODING_UTF8
).getStr()
440 << "\"];" << std::endl
;
445 << normalizeDotName(connection
.msParTransId
).getStr()
447 << normalizeDotName(connection
.msSibTransId
).getStr()
449 << ((connection
.mnXMLType
== TypeConstant::XML_presOf
) ? " color=red, " : ((connection
.mnXMLType
== TypeConstant::XML_presParOf
) ? " color=green, " : " "))
451 << OUStringToOString(connection
.msModelId
,
452 RTL_TEXTENCODING_UTF8
).getStr()
453 << "\"];" << std::endl
;
456 else if( !connection
.msSourceId
.isEmpty() ||
457 !connection
.msDestId
.isEmpty() )
459 << normalizeDotName(connection
.msSourceId
).getStr()
461 << normalizeDotName(connection
.msDestId
).getStr()
463 << OUStringToOString(connection
.msModelId
,
464 RTL_TEXTENCODING_UTF8
).getStr()
465 << ((connection
.mnXMLType
== TypeConstant::XML_presOf
) ? "\", color=red]" : ((connection
.mnXMLType
== TypeConstant::XML_presParOf
) ? "\", color=green]" : "\"]"))
469 const bool bInserted1
= maConnectionNameMap
.insert(
470 std::make_pair(connection
.msModelId
,&connection
)).second
;
472 SAL_WARN_IF(!bInserted1
, "oox.drawingml", "DiagramData::build(): non-unique connection model id");
474 if( connection
.mnXMLType
== TypeConstant::XML_presOf
)
476 DiagramData::StringMap::value_type::second_type
& rVec
= getPresOfNameMap()[connection
.msDestId
];
477 rVec
[connection
.mnDestOrder
] = { connection
.msSourceId
, sal_Int32(0) };
481 // assign outline levels
482 DiagramData::StringMap
& rStringMap
= getPresOfNameMap();
483 for (auto & elemPresOf
: rStringMap
)
485 for (auto & elem
: elemPresOf
.second
)
487 const sal_Int32 nDepth
= calcDepth(elem
.second
.msSourceId
, getConnections());
488 elem
.second
.mnDepth
= nDepth
!= 0 ? nDepth
: -1;
491 #ifdef DEBUG_OOX_DIAGRAM
492 output
<< "}" << std::endl
;
498 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */