Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / svx / source / diagram / datamodel.cxx
blob42677954badace9e373ffe4b665009bdd1bd76db
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
21 #include <algorithm>
22 #include <fstream>
24 #include <svx/diagram/datamodel.hxx>
25 #include <comphelper/xmltools.hxx>
26 #include <sal/log.hxx>
27 #include <utility>
29 namespace svx::diagram {
31 Connection::Connection()
32 : mnXMLType( XML_none )
33 , mnSourceOrder( 0 )
34 , mnDestOrder( 0 )
38 Point::Point()
39 : msTextBody(std::make_shared< TextBody >())
40 , msPointStylePtr(std::make_shared< PointStyle >())
41 , mnXMLType(XML_none)
42 , mnMaxChildren(-1)
43 , mnPreferredChildren(-1)
44 , mnDirection(XML_norm)
45 , mnResizeHandles(XML_rel)
46 , mnCustomAngle(-1)
47 , mnPercentageNeighbourWidth(-1)
48 , mnPercentageNeighbourHeight(-1)
49 , mnPercentageOwnWidth(-1)
50 , mnPercentageOwnHeight(-1)
51 , mnIncludeAngleScale(-1)
52 , mnRadiusScale(-1)
53 , mnWidthScale(-1)
54 , mnHeightScale(-1)
55 , mnWidthOverride(-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)
64 , mbCustomText(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)
81 return &aCurrPoint;
83 SAL_WARN("svx.diagram", "No root point");
84 return nullptr;
87 OUString DiagramData::getString() const
89 OUStringBuffer aBuf;
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");
102 return false;
105 Connection aParCxn;
106 for (const auto& aCxn : maConnections)
107 if (aCxn.mnXMLType == TypeConstant::XML_parOf && aCxn.msDestId == rNodeId)
108 aParCxn = aCxn;
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);
139 maPoints.end());
141 // TODO: fix source/dest order
142 return true;
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)
160 if(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
177 if (!pPoint)
178 return;
180 if (nLevel > 0)
182 for (sal_Int32 i = 0; i < nLevel-1; i++)
183 rBuf.append('\t');
184 rBuf.append('+');
185 rBuf.append(' ');
186 rBuf.append(pPoint->msTextBody->msText);
187 rBuf.append('\n');
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(); }),
226 aChildren.end());
228 return aChildren;
231 OUString DiagramData::addNode(const OUString& rText)
233 const svx::diagram::Point& rDataRoot = *getRootPoint();
234 OUString sPresRoot;
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())
240 return OUString();
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);
282 return sNewNodeId;
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 )
303 OUStringBuffer aBuf;
304 aBuf.append('N');
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);
318 #endif
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;
337 return 0;
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;
353 #endif
354 svx::diagram::Points& rPoints = getPoints();
355 for (auto & point : rPoints)
357 #ifdef DEBUG_OOX_DIAGRAM
358 output << "\t"
359 << normalizeDotName(point.msModelId).getStr()
360 << "[";
362 if( !point.msPresentationLayoutName.isEmpty() )
363 output << "label=\""
364 << OUStringToOString(
365 point.msPresentationLayoutName,
366 RTL_TEXTENCODING_UTF8).getStr() << "\", ";
367 else
368 output << "label=\""
369 << OUStringToOString(
370 point.msModelId,
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;
377 default:
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;
385 #endif
387 // does currpoint have any text set?
388 if(!point.msTextBody->msText.isEmpty())
390 #ifdef DEBUG_OOX_DIAGRAM
391 static sal_Int32 nCount=0;
392 output << "\t"
393 << "textNode" << nCount
394 << " ["
395 << "label=\""
396 << OUStringToOString(
397 point.msTextBody->msText,
398 RTL_TEXTENCODING_UTF8).getStr()
399 << "\"" << "];" << std::endl;
400 output << "\t"
401 << normalizeDotName(point.msModelId).getStr()
402 << " -> "
403 << "textNode" << nCount++
404 << ";" << std::endl;
405 #endif
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() )
431 output << "\t"
432 << normalizeDotName(connection.msSourceId).getStr()
433 << " -> "
434 << normalizeDotName(connection.msParTransId).getStr()
435 << " -> "
436 << normalizeDotName(connection.msSibTransId).getStr()
437 << " -> "
438 << normalizeDotName(connection.msDestId).getStr()
439 << " [style=dotted,"
440 << ((connection.mnXMLType == TypeConstant::XML_presOf) ? " color=red, " : ((connection.mnXMLType == TypeConstant::XML_presParOf) ? " color=green, " : " "))
441 << "label=\""
442 << OUStringToOString(connection.msModelId,
443 RTL_TEXTENCODING_UTF8 ).getStr()
444 << "\"];" << std::endl;
446 else
448 output << "\t"
449 << normalizeDotName(connection.msParTransId).getStr()
450 << " -> "
451 << normalizeDotName(connection.msSibTransId).getStr()
452 << " ["
453 << ((connection.mnXMLType == TypeConstant::XML_presOf) ? " color=red, " : ((connection.mnXMLType == TypeConstant::XML_presParOf) ? " color=green, " : " "))
454 << "label=\""
455 << OUStringToOString(connection.msModelId,
456 RTL_TEXTENCODING_UTF8 ).getStr()
457 << "\"];" << std::endl;
460 else if( !connection.msSourceId.isEmpty() ||
461 !connection.msDestId.isEmpty() )
462 output << "\t"
463 << normalizeDotName(connection.msSourceId).getStr()
464 << " -> "
465 << normalizeDotName(connection.msDestId).getStr()
466 << " [label=\""
467 << OUStringToOString(connection.msModelId,
468 RTL_TEXTENCODING_UTF8 ).getStr()
469 << ((connection.mnXMLType == TypeConstant::XML_presOf) ? "\", color=red]" : ((connection.mnXMLType == TypeConstant::XML_presParOf) ? "\", color=green]" : "\"]"))
470 << ";" << std::endl;
471 #endif
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;
497 #endif
502 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */