diff options
Diffstat (limited to 'svx/source/diagram')
-rw-r--r-- | svx/source/diagram/IDiagramHelper.cxx | 430 | ||||
-rw-r--r-- | svx/source/diagram/datamodel.cxx | 501 |
2 files changed, 931 insertions, 0 deletions
diff --git a/svx/source/diagram/IDiagramHelper.cxx b/svx/source/diagram/IDiagramHelper.cxx new file mode 100644 index 000000000..1803a3d2b --- /dev/null +++ b/svx/source/diagram/IDiagramHelper.cxx @@ -0,0 +1,430 @@ +/* -*- 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( + 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( + aPolygonLapUp, + aLineAttribute, + aStrokeAttribute)); + + rContainer.push_back( + new drawinglayer::primitive2d::PolygonStrokePrimitive2D( + aPolygonLapDown, + aLineAttribute, + 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( + 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 = 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 000000000..e45fe7fb4 --- /dev/null +++ b/svx/source/diagram/datamodel.cxx @@ -0,0 +1,501 @@ +/* -*- 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> + +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 + maConnections.erase(std::remove_if(maConnections.begin(), maConnections.end(), + [aIdsToRemove](const Connection& rCxn) { + return aIdsToRemove.count(rCxn.msSourceId) || aIdsToRemove.count(rCxn.msDestId); + }), + maConnections.end()); + + // remove data and presentation nodes + maPoints.erase(std::remove_if(maPoints.begin(), maPoints.end(), + [aIdsToRemove](const Point& rPoint) { + return aIdsToRemove.count(rPoint.msModelId); + }), + maPoints.end()); + + // TODO: fix source/dest order + return true; +} + +DiagramDataState::DiagramDataState(const Connections& rConnections, const Points& rPoints) +: maConnections(rConnections) +, maPoints(rPoints) +{ +} + +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 + aChildren.erase(std::remove_if(aChildren.begin(), aChildren.end(), + [](const std::pair<OUString, OUString>& aItem) { return aItem.first.isEmpty(); }), + aChildren.end()); + + 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: */ |