summaryrefslogtreecommitdiffstats
path: root/svx/source/diagram
diff options
context:
space:
mode:
Diffstat (limited to 'svx/source/diagram')
-rw-r--r--svx/source/diagram/IDiagramHelper.cxx431
-rw-r--r--svx/source/diagram/datamodel.cxx498
2 files changed, 929 insertions, 0 deletions
diff --git a/svx/source/diagram/IDiagramHelper.cxx b/svx/source/diagram/IDiagramHelper.cxx
new file mode 100644
index 0000000000..92bc1afc9b
--- /dev/null
+++ b/svx/source/diagram/IDiagramHelper.cxx
@@ -0,0 +1,431 @@
+/* -*- 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 <svx/diagram/IDiagramHelper.hxx>
+#include <svx/svdogrp.hxx>
+#include <svx/svdhdl.hxx>
+#include <svx/svdmrkv.hxx>
+#include <svx/svdpagv.hxx>
+#include <svx/sdrpagewindow.hxx>
+#include <svx/sdrpaintwindow.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <svx/sdr/primitive2d/svx_primitivetypes2d.hxx>
+#include <drawinglayer/primitive2d/primitivetools2d.hxx>
+#include <svx/sdr/overlay/overlaymanager.hxx>
+#include <drawinglayer/attribute/lineattribute.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonStrokePrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
+#include <comphelper/dispatchcommand.hxx>
+#include <drawinglayer/primitive2d/textlayoutdevice.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <svx/strings.hrc>
+#include <svx/dialmgr.hxx>
+
+namespace {
+
+// helper to create the geometry for a rounded polygon, maybe
+// containing a Lap positioned inside top-left for some text
+basegfx::B2DPolygon createRoundedPolygon(
+ const basegfx::B2DRange& rRange,
+ double fDistance,
+ bool bCreateLap,
+ double fTextWidth)
+{
+ basegfx::B2DPolygon aRetval;
+
+ // TopLeft rounded edge
+ aRetval.append(
+ basegfx::utils::createPolygonFromEllipseSegment(
+ basegfx::B2DPoint(rRange.getMinX(), rRange.getMinY()),
+ fDistance,
+ fDistance,
+ M_PI * 1.0,
+ M_PI * 1.5));
+
+ // create Lap topLeft inside
+ if(bCreateLap)
+ {
+ const double fLapLeft(rRange.getMinX() + fDistance);
+ double fLapRight(rRange.getMinX() + (rRange.getWidth() * 0.5) - fDistance);
+ const double fLapTop(rRange.getMinY() - fDistance);
+ const double fLapBottom(fLapTop + (fDistance * 2.0));
+ const double fExtendedTextWidth(fTextWidth + (fDistance * 3.0));
+
+ if(0.0 != fExtendedTextWidth && fLapLeft + fExtendedTextWidth < fLapRight)
+ {
+ fLapRight = fLapLeft + fExtendedTextWidth;
+ }
+
+ aRetval.append(basegfx::B2DPoint(fLapLeft, fLapTop));
+ aRetval.append(basegfx::B2DPoint(fLapLeft + (fDistance * 0.5), fLapBottom));
+ aRetval.append(basegfx::B2DPoint(fLapRight - (fDistance * 0.5), fLapBottom));
+ aRetval.append(basegfx::B2DPoint(fLapRight, fLapTop));
+ }
+
+ // TopRight rounded edge
+ aRetval.append(
+ basegfx::utils::createPolygonFromEllipseSegment(
+ basegfx::B2DPoint(rRange.getMaxX(), rRange.getMinY()),
+ fDistance,
+ fDistance,
+ M_PI * 1.5,
+ M_PI * 0.0));
+
+ // BottomRight rounded edge
+ aRetval.append(
+ basegfx::utils::createPolygonFromEllipseSegment(
+ basegfx::B2DPoint(rRange.getMaxX(), rRange.getMaxY()),
+ fDistance,
+ fDistance,
+ M_PI * 0.0,
+ M_PI * 0.5));
+
+ // BottomLeft rounded edge
+ aRetval.append(
+ basegfx::utils::createPolygonFromEllipseSegment(
+ basegfx::B2DPoint(rRange.getMinX(), rRange.getMaxY()),
+ fDistance,
+ fDistance,
+ M_PI * 0.5,
+ M_PI * 1.0));
+
+ aRetval.setClosed(true);
+
+ return aRetval;
+}
+
+// helper primitive to create/show the overlay geometry for a DynamicDiagram
+class OverlayDiagramPrimitive final : public drawinglayer::primitive2d::DiscreteMetricDependentPrimitive2D
+{
+private:
+ basegfx::B2DHomMatrix maTransformation; // object dimensions
+ double mfDiscreteDistance; // distance from object in pixels
+ double mfDiscreteGap; // gap/width of visualization in pixels
+ Color maColor; // base color (made lighter/darker as needed, should be system selection color)
+
+ virtual void create2DDecomposition(
+ drawinglayer::primitive2d::Primitive2DContainer& rContainer,
+ const drawinglayer::geometry::ViewInformation2D& rViewInformation) const override;
+
+public:
+ OverlayDiagramPrimitive(
+ const basegfx::B2DHomMatrix& rTransformation,
+ double fDiscreteDistance,
+ double fDiscreteGap,
+ Color const & rColor);
+
+ virtual sal_uInt32 getPrimitive2DID() const override;
+};
+
+void OverlayDiagramPrimitive::create2DDecomposition(
+ drawinglayer::primitive2d::Primitive2DContainer& rContainer,
+ const drawinglayer::geometry::ViewInformation2D& /*rViewInformation*/) const
+{
+ // get the dimensions. Do *not* take rotation/shear into account,
+ // this is intended to be a pure expanded/frame visualization as
+ // needed in UI for simplified visualization
+ basegfx::B2DRange aRange(0.0, 0.0, 1.0, 1.0);
+ aRange.transform(maTransformation);
+
+ basegfx::B2DPolyPolygon aPolyPolygon;
+ const double fInnerDistance(mfDiscreteDistance * getDiscreteUnit());
+ const double fOuterDistance((mfDiscreteDistance + mfDiscreteGap) * getDiscreteUnit());
+ bool bCreateLap(true);
+ basegfx::B2DPolyPolygon aTextAsPolyPolygon;
+ double fTextWidth(0.0);
+
+ // initially try to create lap
+ if(bCreateLap)
+ {
+ // take a resource text (for now existing one that fits)
+ const OUString aName(SvxResId(RID_STR_DATANAV_EDIT_ELEMENT));
+ drawinglayer::primitive2d::TextLayouterDevice aTextLayouter;
+ basegfx::B2DPolyPolygonVector aTarget;
+ std::vector<double> aDXArray;
+
+ // to simplify things for now, do not create a TextSimplePortionPrimitive2D
+ // and needed FontAttribute, just get the TextOutlines as geometry
+ aTextLayouter.getTextOutlines(
+ aTarget,
+ aName,
+ 0,
+ aName.getLength(),
+ aDXArray,
+ {});
+
+ // put into one PolyPolygon (also simplification - overlapping chars
+ // may create XOR gaps, so these exist for a reason, but low probability)
+ for (auto const& elem : aTarget)
+ {
+ aTextAsPolyPolygon.append(elem);
+ }
+
+ // get text dimensions & transform to destination
+ const basegfx::B2DRange aTextRange(aTextAsPolyPolygon.getB2DRange());
+ basegfx::B2DHomMatrix aTextTransform;
+
+ aTextTransform.translate(aTextRange.getMinX(), aTextRange.getMinY());
+ const double fTargetTextHeight((mfDiscreteDistance + mfDiscreteGap - 2.0) * getDiscreteUnit());
+ const double fTextScale(fTargetTextHeight / aTextRange.getHeight());
+ aTextTransform.scale(fTextScale, fTextScale);
+ aTextTransform.translate(
+ aRange.getMinX() + (fInnerDistance * 2.0),
+ aRange.getMinY() + fTargetTextHeight + (fOuterDistance - fInnerDistance) - (2.0 * getDiscreteUnit()));
+ aTextAsPolyPolygon.transform(aTextTransform);
+
+ // check text size/position
+ fTextWidth = aTextRange.getWidth() * fTextScale;
+ const double fLapLeft(aRange.getMinX() + fInnerDistance);
+ const double fLapRight(aRange.getMinX() + (aRange.getWidth() * 0.5) - fInnerDistance);
+
+ // if text is too big, do not create a Lap at all
+ // to avoid trouble. It is expected that the user keeps
+ // the object he works with big enough to do useful actions
+ if(fTextWidth + (4.0 * getDiscreteUnit()) > fLapRight - fLapLeft)
+ bCreateLap = false;
+ }
+
+ // create outer polygon
+ aPolyPolygon.append(
+ createRoundedPolygon(
+ aRange,
+ fOuterDistance,
+ false,
+ 0.0));
+
+ // create inner polygon, maybe with Lap
+ aPolyPolygon.append(
+ createRoundedPolygon(
+ aRange,
+ fInnerDistance,
+ bCreateLap,
+ fTextWidth));
+
+ Color aFillColor(maColor);
+ Color aLineColor(maColor);
+
+ aFillColor.IncreaseLuminance(10);
+ aLineColor.DecreaseLuminance(30);
+
+ const drawinglayer::attribute::LineAttribute aLineAttribute(
+ aLineColor.getBColor(),
+ 1.0 * getDiscreteUnit());
+
+ // filled polygon as BG (may get transparence for better look ?)
+ rContainer.push_back(
+ new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
+ aPolyPolygon,
+ aFillColor.getBColor()));
+
+ // outline polygon for visibility (may be accentuated shaded
+ // top/left, would require alternative creation)
+ rContainer.push_back(
+ new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
+ std::move(aPolyPolygon),
+ aLineAttribute));
+
+ // top-left line pattern (as grep-here-sign to signal
+ // that this construct may be also dragged by the user)
+ const double fLapLeft(aRange.getMinX() + fInnerDistance);
+ const double fLapRight(aRange.getMinX() + (aRange.getWidth() * 0.5) - fInnerDistance);
+ const double fLapUp(aRange.getMinY() - ((mfDiscreteDistance + mfDiscreteDistance * 0.666) * getDiscreteUnit()));
+ const double fLapDown(aRange.getMinY() - ((mfDiscreteDistance + mfDiscreteDistance * 0.333) * getDiscreteUnit()));
+ basegfx::B2DPolygon aPolygonLapUp;
+ aPolygonLapUp.append(basegfx::B2DPoint(fLapLeft, fLapUp));
+ aPolygonLapUp.append(basegfx::B2DPoint(fLapRight, fLapUp));
+ basegfx::B2DPolygon aPolygonLapDown;
+ aPolygonLapDown.append(basegfx::B2DPoint(fLapLeft, fLapDown));
+ aPolygonLapDown.append(basegfx::B2DPoint(fLapRight, fLapDown));
+ drawinglayer::attribute::StrokeAttribute aStrokeAttribute({ 2.0 * getDiscreteUnit(), 2.0 * getDiscreteUnit() });
+
+ rContainer.push_back(
+ new drawinglayer::primitive2d::PolygonStrokePrimitive2D(
+ std::move(aPolygonLapUp),
+ aLineAttribute,
+ aStrokeAttribute));
+
+ rContainer.push_back(
+ new drawinglayer::primitive2d::PolygonStrokePrimitive2D(
+ std::move(aPolygonLapDown),
+ aLineAttribute,
+ std::move(aStrokeAttribute)));
+
+ // add text last. May use darker text color, go for same color
+ // as accentuation line for now
+ if(bCreateLap && 0 != aTextAsPolyPolygon.count())
+ {
+ rContainer.push_back(
+ new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
+ std::move(aTextAsPolyPolygon),
+ aLineColor.getBColor()));
+ }
+}
+
+OverlayDiagramPrimitive::OverlayDiagramPrimitive(
+ const basegfx::B2DHomMatrix& rTransformation,
+ double fDiscreteDistance,
+ double fDiscreteGap,
+ Color const & rColor)
+: drawinglayer::primitive2d::DiscreteMetricDependentPrimitive2D()
+, maTransformation(rTransformation)
+, mfDiscreteDistance(fDiscreteDistance)
+, mfDiscreteGap(fDiscreteGap)
+, maColor(rColor)
+{
+}
+
+sal_uInt32 OverlayDiagramPrimitive::getPrimitive2DID() const
+{
+ return PRIMITIVE2D_ID_OVERLAYDIAGRAMPRIMITIVE2D;
+}
+
+// helper object for DiagramOverlay
+class OverlayDiagramFrame final : public sdr::overlay::OverlayObject
+{
+private:
+ basegfx::B2DHomMatrix maTransformation; // object dimensions
+ Color maColor; // base color
+
+ virtual drawinglayer::primitive2d::Primitive2DContainer createOverlayObjectPrimitive2DSequence() override;
+
+public:
+ explicit OverlayDiagramFrame(
+ const basegfx::B2DHomMatrix& rTransformation,
+ Color const & rColor);
+};
+
+OverlayDiagramFrame::OverlayDiagramFrame(
+ const basegfx::B2DHomMatrix& rTransformation,
+ const Color& rColor)
+: sdr::overlay::OverlayObject(rColor)
+, maTransformation(rTransformation)
+, maColor(rColor)
+{
+}
+
+drawinglayer::primitive2d::Primitive2DContainer OverlayDiagramFrame::createOverlayObjectPrimitive2DSequence()
+{
+ drawinglayer::primitive2d::Primitive2DContainer aReturnContainer;
+
+ if ( !officecfg::Office::Common::Misc::ExperimentalMode::get() )
+ return aReturnContainer;
+
+ if (getOverlayManager())
+ {
+ aReturnContainer = drawinglayer::primitive2d::Primitive2DContainer {
+ new OverlayDiagramPrimitive(
+ maTransformation,
+ 8.0, // distance from geometry in pixels
+ 8.0, // gap/width of visualization in pixels
+ maColor) };
+ }
+
+ return aReturnContainer;
+}
+
+} // end of anonymous namespace
+
+namespace svx { namespace diagram {
+
+void DiagramFrameHdl::clicked(const Point& /*rPnt*/)
+{
+ // this may check for a direct hit at the text later
+ // and only then take action. That would require
+ // to evaluate & keep that (maybe during creation).
+ // For now, just trigger to open the Dialog
+ comphelper::dispatchCommand(".uno:EditDiagram", {});
+}
+
+void DiagramFrameHdl::CreateB2dIAObject()
+{
+ // first throw away old one
+ GetRidOfIAObject();
+
+ SdrMarkView* pView = m_pHdlList->GetView();
+
+ if(!pView || pView->areMarkHandlesHidden())
+ return;
+
+ SdrPageView* pPageView = pView->GetSdrPageView();
+
+ if(!pPageView)
+ return;
+
+ for(sal_uInt32 b(0); b < pPageView->PageWindowCount(); b++)
+ {
+ const SdrPageWindow& rPageWindow = *pPageView->GetPageWindow(b);
+
+ if(rPageWindow.GetPaintWindow().OutputToWindow())
+ {
+ const rtl::Reference< sdr::overlay::OverlayManager >& xManager = rPageWindow.GetOverlayManager();
+ if (xManager.is())
+ {
+ OutputDevice& rOutDev(rPageWindow.GetPaintWindow().GetOutputDevice());
+ const StyleSettings& rStyles(rOutDev.GetSettings().GetStyleSettings());
+ Color aFillColor(rStyles.GetHighlightColor());
+ std::unique_ptr<sdr::overlay::OverlayObject> pNewOverlayObject(
+ new OverlayDiagramFrame(
+ maTransformation,
+ aFillColor));
+
+ // OVERLAYMANAGER
+ insertNewlyCreatedOverlayObjectForSdrHdl(
+ std::move(pNewOverlayObject),
+ rPageWindow.GetObjectContact(),
+ *xManager);
+ }
+ }
+ }
+}
+
+DiagramFrameHdl::DiagramFrameHdl(const basegfx::B2DHomMatrix& rTransformation)
+: SdrHdl(Point(), SdrHdlKind::Move)
+, maTransformation(rTransformation)
+{
+}
+
+IDiagramHelper::IDiagramHelper()
+: mbUseDiagramThemeData(false)
+, mbUseDiagramModelData(true)
+, mbForceThemePtrRecreation(false)
+{
+}
+
+IDiagramHelper::~IDiagramHelper() {}
+
+void IDiagramHelper::anchorToSdrObjGroup(SdrObjGroup& rTarget)
+{
+ rTarget.mp_DiagramHelper.reset(this);
+}
+
+void IDiagramHelper::AddAdditionalVisualization(const SdrObjGroup& rTarget, SdrHdlList& rHdlList)
+{
+ // create an extra frame visualization here
+ basegfx::B2DHomMatrix aTransformation;
+ basegfx::B2DPolyPolygon aPolyPolygon;
+ rTarget.TRGetBaseGeometry(aTransformation, aPolyPolygon);
+
+ std::unique_ptr<SdrHdl> pHdl(new DiagramFrameHdl(aTransformation));
+ rHdlList.AddHdl(std::move(pHdl));
+}
+
+}} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svx/source/diagram/datamodel.cxx b/svx/source/diagram/datamodel.cxx
new file mode 100644
index 0000000000..0cf0541cd2
--- /dev/null
+++ b/svx/source/diagram/datamodel.cxx
@@ -0,0 +1,498 @@
+/* -*- 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: */