tdf#151001: Add support for EXPAND function
[LibreOffice.git] / svx / source / diagram / datamodel.cxx
blob3f7e5864bfb1c321b08b1fc87b3155858cda1739
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 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
140 return true;
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)
158 if(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
175 if (!pPoint)
176 return;
178 if (nLevel > 0)
180 for (sal_Int32 i = 0; i < nLevel-1; i++)
181 rBuf.append('\t');
182 rBuf.append('+');
183 rBuf.append(' ');
184 rBuf.append(pPoint->msTextBody->msText);
185 rBuf.append('\n');
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(); });
224 return aChildren;
227 OUString DiagramData::addNode(const OUString& rText)
229 const svx::diagram::Point& rDataRoot = *getRootPoint();
230 OUString sPresRoot;
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())
236 return OUString();
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);
278 return sNewNodeId;
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 )
299 OUStringBuffer aBuf;
300 aBuf.append('N');
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);
314 #endif
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;
333 return 0;
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;
349 #endif
350 svx::diagram::Points& rPoints = getPoints();
351 for (auto & point : rPoints)
353 #ifdef DEBUG_OOX_DIAGRAM
354 output << "\t"
355 << normalizeDotName(point.msModelId).getStr()
356 << "[";
358 if( !point.msPresentationLayoutName.isEmpty() )
359 output << "label=\""
360 << OUStringToOString(
361 point.msPresentationLayoutName,
362 RTL_TEXTENCODING_UTF8).getStr() << "\", ";
363 else
364 output << "label=\""
365 << OUStringToOString(
366 point.msModelId,
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;
373 default:
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;
381 #endif
383 // does currpoint have any text set?
384 if(!point.msTextBody->msText.isEmpty())
386 #ifdef DEBUG_OOX_DIAGRAM
387 static sal_Int32 nCount=0;
388 output << "\t"
389 << "textNode" << nCount
390 << " ["
391 << "label=\""
392 << OUStringToOString(
393 point.msTextBody->msText,
394 RTL_TEXTENCODING_UTF8).getStr()
395 << "\"" << "];" << std::endl;
396 output << "\t"
397 << normalizeDotName(point.msModelId).getStr()
398 << " -> "
399 << "textNode" << nCount++
400 << ";" << std::endl;
401 #endif
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() )
427 output << "\t"
428 << normalizeDotName(connection.msSourceId).getStr()
429 << " -> "
430 << normalizeDotName(connection.msParTransId).getStr()
431 << " -> "
432 << normalizeDotName(connection.msSibTransId).getStr()
433 << " -> "
434 << normalizeDotName(connection.msDestId).getStr()
435 << " [style=dotted,"
436 << ((connection.mnXMLType == TypeConstant::XML_presOf) ? " color=red, " : ((connection.mnXMLType == TypeConstant::XML_presParOf) ? " color=green, " : " "))
437 << "label=\""
438 << OUStringToOString(connection.msModelId,
439 RTL_TEXTENCODING_UTF8 ).getStr()
440 << "\"];" << std::endl;
442 else
444 output << "\t"
445 << normalizeDotName(connection.msParTransId).getStr()
446 << " -> "
447 << normalizeDotName(connection.msSibTransId).getStr()
448 << " ["
449 << ((connection.mnXMLType == TypeConstant::XML_presOf) ? " color=red, " : ((connection.mnXMLType == TypeConstant::XML_presParOf) ? " color=green, " : " "))
450 << "label=\""
451 << OUStringToOString(connection.msModelId,
452 RTL_TEXTENCODING_UTF8 ).getStr()
453 << "\"];" << std::endl;
456 else if( !connection.msSourceId.isEmpty() ||
457 !connection.msDestId.isEmpty() )
458 output << "\t"
459 << normalizeDotName(connection.msSourceId).getStr()
460 << " -> "
461 << normalizeDotName(connection.msDestId).getStr()
462 << " [label=\""
463 << OUStringToOString(connection.msModelId,
464 RTL_TEXTENCODING_UTF8 ).getStr()
465 << ((connection.mnXMLType == TypeConstant::XML_presOf) ? "\", color=red]" : ((connection.mnXMLType == TypeConstant::XML_presParOf) ? "\", color=green]" : "\"]"))
466 << ";" << std::endl;
467 #endif
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;
493 #endif
498 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */