/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 drawinglayer::primitive2d::Primitive2DReference create2DDecomposition( 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; }; drawinglayer::primitive2d::Primitive2DReference OverlayDiagramPrimitive::create2DDecomposition( 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 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()); drawinglayer::primitive2d::Primitive2DContainer aContainer; // filled polygon as BG (may get transparence for better look ?) aContainer.push_back( new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( aPolyPolygon, aFillColor.getBColor())); // outline polygon for visibility (may be accentuated shaded // top/left, would require alternative creation) aContainer.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() }); aContainer.push_back( new drawinglayer::primitive2d::PolygonStrokePrimitive2D( std::move(aPolygonLapUp), aLineAttribute, aStrokeAttribute)); aContainer.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()) { aContainer.push_back( new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( std::move(aTextAsPolyPolygon), aLineColor.getBColor())); } return new drawinglayer::primitive2d::GroupPrimitive2D(std::move(aContainer)); } 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(u".uno:EditDiagram"_ustr, {}); } 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 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(bool bSelfCreated) : mbUseDiagramThemeData(false) , mbUseDiagramModelData(true) , mbForceThemePtrRecreation(false) , mbSelfCreated(bSelfCreated) { } 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 pHdl(new DiagramFrameHdl(aTransformation)); rHdlList.AddHdl(std::move(pHdl)); } }} // end of namespace /* vim:set shiftwidth=4 softtabstop=4 expandtab: */